diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dd1392d..0912c21d 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(row-groups, exec): **with `Row groups: on`, a script that names no build-plan row now gets a visible reminder instead of a silent "not checked"**: `row-groups.sh check` exits 4 and exec.sh prints "start the script with `-- rows: N`" and notes it in the BUILD-LOG row; it never refuses. In the #581 A/B (purchase-requests app, Sonnet) all 31 scripts were numbered on their own sequence with no header, so row grouping never ran once while the project had it switched on. Fixture: `test-row-groups.sh` G4 (2 of 3 cases fail on the previous row-groups.sh) — A/B 2026-10-27 of #581 - new(exec, row-groups): **row-group gate**: `bin/row-groups.sh` plans scripts from the build plan (consecutive low-risk BUILD rows of one module, at most 4, in one script; security, workflow, agents, REST, Java, Marketplace and changes to built documents stay one row per script), and `exec.sh` refuses a split low-risk group, a mixed high-risk script, or the first script past a module with no `docs/progress/module-close/.md`. The gate checks a script that names its rows in a `-- rows:` header, or, when PROJECT.md says `Row groups: on`, its file name; otherwise it is skipped, because `brd-to-build-plan.md` numbers scripts on their own sequence (on the real SampleDashboards plan `16-cutover.mdl` is row 26 and was refused as row 16; `tests/wave2/test-row-groups.sh`). A row counts as built only when its State says built / done / complete / verified / proven / passed / applied (`planned`, `partial`, `**blocked**` and an empty State do not), or when an applied script's `-- rows:` header names it. Override: `MXTK_ROWS_OVERRIDE=""` (ProcureFlow run 3 arm D: 0.93 → 0.48 scripts per row, 0 overrides; build to Test 2 12/12 in 322 min vs arm C stopped at row 121 after 650) — cook-off ProcureFlow run 2/3 - fix(exec, lint): **lint prints and counts only the findings THIS script added, plus any earlier one still unfixed**, diffed against the previous exec (`.claude/loop/lint-prev-exec.json`); a finding that was reported and is neither fixed nor accepted into the baseline is carried (`.claude/loop/lint-unacked.json`) and stays "lint rose" on every later exec, and a diff that cannot run or cannot read `lint-last.json` (missing, unparseable, no `new_findings`), or itemises nothing with no previous exec to explain the rise, keeps the rise as lint-gate returned it (and does not advance `lint-prev-exec.json`, so the next exec still reads it as a rise), never "0 new" (`tests/wave2/test-exec-lint-diff.sh`); the baseline cannot be refreshed unattended, so "rose vs baseline" stayed true for every later exec (run 2 arm C: ROSE on 123 of 147 rows, ~60 lines each, 6 real SEC010 unread) — cook-off ProcureFlow run 2 - new(reload): **`bin/reload.sh` hot-reloads instead of restarting** and finds the app URL from `APP_URL`, then `.docker/.env` `APP_PORT`, then CLAUDE.md (run 2 arm C: 176 app starts; run 3 arm D: 33, 1 failed); it and `bin/module-close.sh` find the `.mpr` and mxcli through `_common.sh`, so a two-tree checkout (`.mpr` under `app/`) works, and `reload.sh` reads the `.docker/` folder beside the `.mpr`, where mxcli writes it (`test-module-close.sh` C2, R1); `bin/module-close.sh` runs the module-close checks and lists pages still owed a LOOK; its report carries ``, and the row-group gate counts a module as closed only at `lint=0` or with a filled row in the report's Page | Role LOOK table, so a report with findings and no page looked at does not open the next module (`tests/wave2/test-row-groups.sh` M1, `test-module-close.sh` with a captured mxcli v0.28.0 lint) — cook-off ProcureFlow run 2/3 diff --git a/project-bin/exec.sh b/project-bin/exec.sh index ccbfc5a7..fba7cfc8 100755 --- a/project-bin/exec.sh +++ b/project-bin/exec.sh @@ -626,6 +626,10 @@ if [ "$PATCH_MODE" != "1" ] && [ -x "$PROJECT_ROOT/bin/row-groups.sh" ]; then fi echo "⚠ Row-group gate OVERRIDDEN: ${MXTK_ROWS_OVERRIDE}" >&2 ROWS_NOTE="rows override: $(printf '%s' "$MXTK_ROWS_OVERRIDE" | tr '|' '/')" + elif [ "$_rg_rc" = 4 ]; then + # Opted in, but the script names no plan row: a reminder, never a refusal. + echo "⚠ $_rg_out" + ROWS_NOTE="rows: no -- rows: header (not grouped)" fi fi diff --git a/project-bin/row-groups.sh b/project-bin/row-groups.sh index 4499d74c..f9315e51 100755 --- a/project-bin/row-groups.sh +++ b/project-bin/row-groups.sh @@ -33,6 +33,7 @@ # The remedy is never blocked: run bin/module-close.sh , fix and re-run, or fill the table. # Override, with a reason that lands in the BUILD-LOG row: MXTK_ROWS_OVERRIDE="". # exit 0 pass, 1 refused, 3 nothing to check (no plan, no rows header and no opt-in, or no BUILD row). +# exit 4: `Row groups: on` but the script names no BUILD row; exec.sh prints the hint, never refuses. # # WHY (run 2, 2026-10-26, arm C). 147 build-plan rows ran as ~137 scripts and ~185 execs; each exec # paid check, snapshot, baseline mxbuild, apply, mxbuild gate (~2 min of instruments). A prompt rule @@ -155,13 +156,22 @@ except OSError: if mine is None and not optin: print("row-groups: script has no `-- rows:` header and PROJECT.md does not say `Row groups: on` — not checked") sys.exit(3) +# Opted in, but the script names no BUILD row of the plan: exit 4, a reminder exec.sh prints and +# lets through. WHY (A/B 2026-10-27, purchase-requests app): with `Row groups: on` every script was +# numbered on its own sequence (052-…, 076-…) and none carried a header, so all 31 read "not +# checked" and the run never grouped a row; exit 3 said nothing anyone saw. +OPTIN_HINT = ("row-groups: `Row groups: on`, but this script names no build-plan BUILD row, so it was not " + "checked or grouped. Start the script with `-- rows: N` (or `-- rows: N,M,…` for a group); " + "bin/row-groups.sh lists the groups.") if mine is None: m = re.match(r"(\d+)(?:-(\d+)(?=-))?-", os.path.basename(script)) - if not m: print("row-groups: script names no row — not checked"); sys.exit(3) + if not m: print(OPTIN_HINT); sys.exit(4) a, b = int(m.group(1)), int(m.group(2) or m.group(1)) mine = set(range(a, b + 1)) if b >= a else {a} mine &= {n for n, k, *_ in rows if k == "BUILD"} -if not mine: print("row-groups: script names no BUILD row — not checked"); sys.exit(3) +if not mine: + if optin: print(OPTIN_HINT); sys.exit(4) + print("row-groups: script names no BUILD row — not checked"); sys.exit(3) problems = [] for risk, ns in groups: diff --git a/tests/wave2/test-row-groups.sh b/tests/wave2/test-row-groups.sh index 8644029b..90dbb084 100644 --- a/tests/wave2/test-row-groups.sh +++ b/tests/wave2/test-row-groups.sh @@ -11,6 +11,7 @@ # G1 real plan, no opt-in: no script name in it is refused (16-cutover.mdl was, as row 16) # G2 `-- rows:` header: the split-group refusal still works on the real plan # G3 `Row groups: on`: a file-name number is read as the row again +# G4 `Row groups: on` but the script names no BUILD row: exit 4, the `-- rows:` hint exec.sh prints # S1 built = an explicit built/done/applied State; planned, **blocked**, partial, empty are not # M1 module close: the report must record 0 lint findings for the module, or a filled LOOK row set -uo pipefail @@ -60,6 +61,15 @@ chk "$P3" "14-snippets.mdl" "create snippet ..." printf '# Project\n\n- Row groups: off\n' > "$P3/PROJECT.md" chk "$P3" "14-snippets.mdl" "create snippet ..." [ "$rc" = 3 ] && ok "G3: Row groups: off → not checked" || bad "G3 off: rc=$rc" "$out" +printf '# Project\n\n- Row groups: on — CONFIRMED\n' > "$P3/PROJECT.md" +chk "$P3" "999-cleanup.mdl" "create microflow ..." +{ [ "$rc" = 4 ] && printf '%s' "$out" | grep -q -- '-- rows: N'; } \ + && ok "G4: Row groups: on, a script numbered past the plan → exit 4 with the -- rows: hint" || bad "G4 numbered: rc=$rc" "$out" +chk "$P3" "cleanup.mdl" "create microflow ..." +[ "$rc" = 4 ] && ok "G4: Row groups: on, an unnumbered script → exit 4 hint" || bad "G4 unnumbered: rc=$rc" "$out" +printf '# Project\n\n- Row groups: off\n' > "$P3/PROJECT.md" +chk "$P3" "999-cleanup.mdl" "create microflow ..." +[ "$rc" = 3 ] && ok "G4: Row groups: off, same script → still exit 3, no hint" || bad "G4 off: rc=$rc" "$out" echo "== S1 what counts as built" plan_states() { # $1 $2 $3 = states of rows 1..3 (one low group)