From c0047ad2e87c74a08dc313bbd86e512bab10fcc1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 15:34:26 +0000 Subject: [PATCH 1/2] fix(progress-report): sort script phases by (number, name), never by dict (#513) script_phases() sorted (leading number, phase dict) tuples with no key, so two scripts sharing a leading number (10-data.mdl, 10b-data-flows.mdl), each matched to a phase, compared the dicts and raised TypeError; every exec.sh run then warned that progress-report.html was not updated. Sort on (number, script name). Fixture: test-progress-report.sh T17 (3 FAIL on master, 104/104 with the fix); test-progress-harness-verify.sh 30/30. Fixes #513 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr --- CHANGELOG.md | 1 + project-bin/progress-report.py | 3 ++- tests/wave2/test-progress-report.sh | 38 +++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee82ab78..b0c68f5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m ## Unreleased +- fix(progress-report): **`progress-report.py` no longer crashes with `TypeError: '<' not supported between instances of 'dict' and 'dict'` when two exec'd scripts share a leading number** (#513). `script_phases()` sorted `(number, phase dict)` pairs, so `10-data.mdl` and `10b-data-flows.mdl` (or one script logged under two paths), both matched to a plan phase, compared the dicts and every `exec.sh` run ended with `WARN: progress-report.html not updated`. It now sorts on (number, script name) only; an unmatched numbered script still takes the phase of the nearest lower-numbered matched one, ties broken by name. Fixture: `test-progress-report.sh` T17 (fails 3/3 on master). — jopterhorst (#513) - fix(progress-report, build-plan-status): **HARNESS rows are no longer counted as built rows, and a module's `verify-module.sh` row shows once, as that module's sign-off status on its phase card, instead of as a row.** A HARNESS step is a module's sign-off, not a build, so it is out of every "x of y built" count: the phase cards, the plan rollup and the Stage 5 card in `progress-report.html`, and the lede of `build-plan-status.sh --html` ("N build rows", plus a "Not counted: N HARNESS rows" note). The verify row's evidence is read exactly as #501 reads it (`harness_verify`'s rule, now `verify_evidence` + `module_status` in `progress-report.py`; nothing new measured, no extra script run, no mxcli or mxbuild call) and shown as one line per module: `Verified ✓ (LOOK n/n)`, `Verify owed - LOOK + CONFIRM`, `Verify stale (N builds since)` (or `(a script was marked done- after it)`), `Verify not run`, `built? no verify summary`, `Verify not clean ()` for a summary with a FAULT, FINDING or SKIPPED row, or `Verify clean , LOOK n/n - held: State says ` when State is blocked or pending a person (held is not signed off, as #501 never promoted it); a State cell that disagrees is flagged under it, the rollup says how many modules are verified, and the Stage 5 card, KPI and owed text add ", k of n modules signed off". The Phase state badge is read from the build rows only (a phase of HARNESS rows alone says "sign-off only" and its card "Sign-off phase: no build rows"). A module with no verify row shows no status. Other HARNESS rows (a coherence pass, a harness run) stay in the row list, marked "(not counted)". `build-plan.md`, `build-plan.json` and the text output are unchanged, so agents still see every HARNESS step. **Behaviour change to check in your project:** the progress report's built counts drop the HARNESS rows (a plan of 10 rows with 2 HARNESS reads "x of 8"), and the verify rows leave the row tables. Fixture: `test-progress-harness-verify.sh` (H1-H10, N1-N6, both layouts). — MendixMau (decision 2026-10-21) - fix(exec): **a script restated from a stale DESCRIBE now WARNs instead of silently reverting an earlier exec** (#261). A script drafted from a DESCRIBE taken before an earlier script of the batch ran put four old messages back, with check, mxbuild and lint green. New `project-bin/describe-baseline.sh stamp` writes `-- baseline: sha256=` (plus `-- baseline-mxcli: `) from a DESCRIBE at draft time; `exec.sh` runs `verify` only for a script that carries the header, so other scripts cost one grep and no mxcli call. It reports DRIFT with the first lines of the diff, STALEHEADER, and RESTAMP after an mxcli upgrade (DESCRIBE text may change, so that is not called drift). A re-run by the script's own last recorded write (the #269 ledger) and a fresh model are not drift. Nothing is refused and the exit codes do not change. Preflight STOP 21 gains one clause (`-- supersedes:`, `stamp`, exec.sh only warns) and its trigger names `create or modify` (+24 words). Not-found DESCRIBE and `--version` output are captured from mxcli v0.26.0. Lands after the #269 PR; with it, supersedes draft #311. **Behaviour change to check in your project:** `exec.sh` output can carry `WARN: stale draft: …` lines, and the BUILD-LOG detail cell `WARN stale baseline: `; `MXTK_RESTATE_GUARD=off` skips the check. — AlvinnYeo (#261) - fix(exec): **re-running an older `mdlsource/` script over a newer owner's live work now WARNs instead of passing silently** (#269). Three field apps had 99/579, 130/357 and 38/159 documents created by more than one script, and a replay of an older one reverted the newer with exit 0 and every gate green. New `project-bin/script-owners.sh` maps each microflow, nanoflow, page and snippet to the scripts that create or restate it, in run order (digit runs compared as numbers, so `2-core/` runs before `11-fixes/`; `done-` ignored), and reads `-- supersedes:` / `-- superseded-by:` headers. `exec.sh` records every kept write in a machine-local last-writer ledger (`.claude/loop/script-owners/`, self-gitignored, compacted per write); `restore-mpr.sh` marks it on a manual restore. A replay WARNs only when the newer owner's write is live: ledger first, else one DESCRIBE per document, at most 5 per exec (the rest print `CONFLICT (not checked)`). A new owner without a supersedes header gets one `NOTE:` line on its first run, kept out of BUILD-LOG. Nothing is refused and the exit codes do not change. With no co-owner, `exec.sh` runs one grep over `mdlsource/` and no mxcli call. `gate-check.sh` prints the co-owned count when it is above 0. Part of the rebuilt draft #311 (with the #261 PR). **Behaviour change to check in your project:** `exec.sh` output can carry `WARN: restatement: …` and `NOTE: new owner of …` lines; the BUILD-LOG detail cell can carry `WARN reverts: `. Set `MXTK_RESTATE_GUARD=off` to skip the check. — MendixMau (#269) diff --git a/project-bin/progress-report.py b/project-bin/progress-report.py index 8583b530..9c2440d4 100755 --- a/project-bin/progress-report.py +++ b/project-bin/progress-report.py @@ -781,7 +781,8 @@ def script_phases(runs, phases): direct[sc] = phase_for_script(sc, phases) m = re.match(r"0*(\d+)", os.path.splitext(os.path.basename(sc.strip().split()[-1]) if sc.strip() else "")[0]) num[sc] = int(m.group(1)) if m else None - known = sorted((num[k], direct[k]) for k in direct if direct[k] and num[k] is not None) + # sort on (number, script name) only: two scripts can share a leading number (10-a, 10b-b) and dicts do not compare (#513) + known = [(n_, pp) for n_, _k, pp in sorted(((num[k], k, direct[k]) for k in direct if direct[k] and num[k] is not None), key=lambda t: (t[0], t[1]))] out = {} for k, p in direct.items(): if not p and num[k] is not None: diff --git a/tests/wave2/test-progress-report.sh b/tests/wave2/test-progress-report.sh index 119fe4fc..52b0d153 100755 --- a/tests/wave2/test-progress-report.sh +++ b/tests/wave2/test-progress-report.sh @@ -20,6 +20,8 @@ # T13 context windows are 200k or 1M only; "window reached" only at 98% of the real window. # T14 --session-log overrides discovery and is recorded in the footer; --help lists it and the rates file. # T15 --open matches this project's exact file URL (osascript stubbed); a generator crash leaves one WARN. +# T17 two scripts sharing a leading number (10-data.mdl, 10b-data-flows.mdl), each matched to a phase: +# the page is still written, no TypeError (#513); the unmatched 11- script takes the later 10 phase. # # Usage: bash tests/wave2/test-progress-report.sh /path/to/build-plan-status.sh @@ -314,6 +316,42 @@ CLAUDE_CONFIG_DIR="$CFG2" bash "$SCRIPT" "$P5" --html >/dev/null 2>&1 has "$P5/architecture/progress-report.html" "location.reload" "reload script present while active" hasnt "$R2" "location.reload" "no reload script when idle" +echo "T17 scripts sharing a leading number (#513)" +# BUILD-LOG rows: the exec.sh row captured in fixtures/verify-446/BUILD-LOG.captured.md, time and script replaced. +CAP="$ROOT/tests/wave2/fixtures/verify-446/BUILD-LOG.captured.md" +P6="$WORK/p6"; mkdir -p "$P6/architecture" "$P6/docs" +cat > "$P6/architecture/build-plan.md" <<'MD' +# Build plan + +### Phase 1 - Data + +| # | Kind | Step | Produces / Proves | Depends on | Skills | State | +|---|---|---|---|---|---|---| +| 1 | DATA | Create entities: `mdlsource/10-data.mdl` | Order | none | none | built | + +### Phase 2 - Flows + +| # | Kind | Step | Produces / Proves | Depends on | Skills | State | +|---|---|---|---|---|---|---| +| 2 | LOGIC | Create flows: `mdlsource/10b-data-flows.mdl` | ACT_Order | 1 | none | built | +MD +{ sed -n '1,/^|---|/p' "$CAP" + for x in "2026-10-19T09:00:00+0000 10-data.mdl" "2026-10-19T10:00:00+0000 10b-data-flows.mdl" "2026-10-19T11:00:00+0000 11-misc.mdl"; do + set -- $x; grep '^| 20' "$CAP" | head -1 | sed -e "s/^| [^|]* | \`[^\`]*\` |/| $1 | \`$2\` |/" + done; } > "$P6/docs/BUILD-LOG.md" +out6="$(CLAUDE_CONFIG_DIR="$EMPTY" bash "$SCRIPT" "$P6" --html 2>&1)" +printf '%s' "$out6" | grep -q "generator failed\|not updated\|TypeError" && bad "generator does not fail on a shared leading number" || ok "generator does not fail on a shared leading number" +printf '%s' "$out6" | grep -q "wrote architecture/progress-report.html" && ok "page written with two scripts numbered 10" || bad "page written with two scripts numbered 10" +SP="$("$PY" - "$ROOT/project-bin" "$P6" <<'PYEOF' +import sys, importlib.util +spec = importlib.util.spec_from_file_location("pr", sys.argv[1] + "/progress-report.py"); m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m) +ph = [{"id": "1", "name": "Data", "steps": [{"step": "mdlsource/10-data.mdl"}]}, {"id": "2", "name": "Flows", "steps": [{"step": "mdlsource/10b-data-flows.mdl"}]}] +runs = [{"script": s} for s in ("10b-data-flows.mdl", "10-data.mdl", "11-misc.mdl")] +print(m.script_phases(runs, ph)["11-misc.mdl"]["name"]) +PYEOF +)" +[ "$SP" = "Flows" ] && ok "11- script takes the phase of the later-named 10 script (order by number, then name)" || bad "11- script takes the phase of the later-named 10 script (got: $SP)" + echo "T16 narrow-width CSS (phone)" GEN="$ROOT/project-bin/progress-report.py" grep -q "@media(max-width:520px){code,pre,.stage dd" "$GEN" && ok "narrow rules wrap code and values" || bad "narrow rules wrap code and values" From 3c34ca43daa218e09a1218d982af7a95ed939fbd Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 15:55:58 +0000 Subject: [PATCH 2/2] test(progress-report): T17 reads its Python into a variable (bash 3.2 portability) check-portability flagged the heredoc opened inside $(...) at line 345. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr --- tests/wave2/test-progress-report.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/wave2/test-progress-report.sh b/tests/wave2/test-progress-report.sh index 52b0d153..852a0ff8 100755 --- a/tests/wave2/test-progress-report.sh +++ b/tests/wave2/test-progress-report.sh @@ -342,14 +342,14 @@ MD out6="$(CLAUDE_CONFIG_DIR="$EMPTY" bash "$SCRIPT" "$P6" --html 2>&1)" printf '%s' "$out6" | grep -q "generator failed\|not updated\|TypeError" && bad "generator does not fail on a shared leading number" || ok "generator does not fail on a shared leading number" printf '%s' "$out6" | grep -q "wrote architecture/progress-report.html" && ok "page written with two scripts numbered 10" || bad "page written with two scripts numbered 10" -SP="$("$PY" - "$ROOT/project-bin" "$P6" <<'PYEOF' +IFS= read -r -d '' SP_PY <<'PYEOF' || true import sys, importlib.util spec = importlib.util.spec_from_file_location("pr", sys.argv[1] + "/progress-report.py"); m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m) ph = [{"id": "1", "name": "Data", "steps": [{"step": "mdlsource/10-data.mdl"}]}, {"id": "2", "name": "Flows", "steps": [{"step": "mdlsource/10b-data-flows.mdl"}]}] runs = [{"script": s} for s in ("10b-data-flows.mdl", "10-data.mdl", "11-misc.mdl")] print(m.script_phases(runs, ph)["11-misc.mdl"]["name"]) PYEOF -)" +SP="$("$PY" -c "$SP_PY" "$ROOT/project-bin" "$P6")" [ "$SP" = "Flows" ] && ok "11- script takes the phase of the later-named 10 script (order by number, then name)" || bad "11- script takes the phase of the later-named 10 script (got: $SP)" echo "T16 narrow-width CSS (phone)"