Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ three commits past it), and a bug report can name a release instead of a sha nob
Sections dated before 2026-09-19 predate the cycle and stay as they are.

## Unreleased
- fix(gate-check): **Stage 4 now reads the build plan and FAILs a plan without its three closing rows.** `brd-to-build-plan.md` (#163) requires, per module, a `verify-module.sh <Module>` row followed by LOOK + CONFIRM; a coherence-pass row for every 2–3 modules; and a last row `gate-check.sh <project> 5`. `check_stage_4` only tested that the plan existed and was approved, so a plan written before the rule was never reopened. Field case: a 137-row plan for 7 modules had 0 / 0 / 0 of these rows and Stage 4 printed PASS. The build then reached DONE with look, sweep and journeys at 0 of 7. The verdict now names each module with no close row, the coherence count against its need, and a wrong last row. N comes from `architecture/modules/<Module>/` and `## Module brief — <Module>` sections; with neither, at least one close row is owed. Plans with no numbered rows are reported as not counted. Fixture: T16 in `test-bug03-gates.sh`; the pre-fix script passes its first case. — field report from an unattended requirements-driven build (#189)
- fix(bin/context-audit.sh): **a Read `offset` or `limit` stored as a string no longer crashes the audit, and the script now exits 0 as its header promises.** Older transcripts store these as strings, sometimes as junk like `'30, 90'`, and `offset - 1` raised a TypeError in the embedded reader, which stopped the whole run. They are now read as numbers when they parse and fall back to the Read tool's defaults (offset 1, limit 2000) when they do not; if the reader ever dies on an unseen transcript shape, the script says the numbers are partial and still exits 0. Field run: 1,300 sessions and 724 subagent runs on a Mac (2026-08-27 to 2026-09-30), which crashed on the old version. — MendixMau
- fix(routing): **`learned-mcp-patterns.md` is no longer always-on in the build stage; it loads before the first MCP write in a session.** It sat in the Stage 5 baseline pack and in `mdl-agent`'s always-read rows, so every build session and every MDL helper agent carried ~4,900 tokens of MCP save/handoff rules and JSON payloads, including sessions that never open Studio Pro and cloud containers where MCP does not exist. Choosing the write mode is already Step 0 of `learned-mdl-preflight.md`, which stays always-on, so nothing is lost at the moment of choice; the MCP skill's trigger now names the moment it is needed (`mxcli --mcp` exec or a `pg_*`/`ped_*` call). Stage 5 pack: 74,055 → 71,443 words, 23 → 22 files; baseline 79,752 → 77,140 words. Found by the context report (`bin/context-audit.sh`, `bin/render-routing.sh --check`). — MendixMau
- new(bin/context-audit.sh): **what fills the context window, per file, from the real Claude Code transcripts on this machine.** `token-burn.sh` says how many tokens a project burned; this says which files burned them, so decisions about splitting, trimming or un-routing a skill rest on measured runs instead of `wc` on the skill files. It reports: the context size before any work (first call, input + cache, for sessions and for subagents separately); the instruction files loaded every run (CLAUDE.md, CLAUDE.local.md) and their size; every file read (Read tool and simple shell reads like `cat`, `sed -n`, `git show REV:path`, following `cd` and `VAR=`) with reads, sessions, total and per-read size, and re-reads within a session (paging through a file is not a re-read; asking for the same part again is); other tool output by tool; and each compaction with the files read before it. Project names are masked by default (`project-1/architecture/modules/*.md`), so the output is safe to paste; `--names` shows them locally. First field numbers, from a captured pipeline-start subagent: it starts at 52,503 tokens before reading anything, then reads the runbook in 6 pages with 2 repeats. Fixture: `tests/wave2/test-context-audit.sh` over a scrubbed real capture. — MendixMau
Expand Down
68 changes: 65 additions & 3 deletions bin/gate-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1174,10 +1174,72 @@ check_stage_4() {
return
fi
if reg_unavailable; then reg_unavailable_note; return; fi
if has_confirmed_decision 4; then
echo "PASS|build-plan.md present and a Stage-4 CONFIRMED decision is in $REGISTER"
else
if ! has_confirmed_decision 4; then
echo "FAIL|build-plan.md exists but ${REGISTER:-PROJECT.md} has no Stage-4 CONFIRMED decision — ✋ gate: a plan nobody approved doesn't pass"
return
fi
local closing
closing="$(stage4_closing_rows "$build_plan")"
case "$closing" in
FAIL\|*) echo "$closing" ;;
*) echo "PASS|build-plan.md present, ${closing#OK|}, and a Stage-4 CONFIRMED decision is in $REGISTER" ;;
esac
}

# stage4_closing_rows <build-plan.md> → "OK|<what was counted>" or "FAIL|<what is missing>".
#
# THE PLAN IS READ, NOT ONLY FOUND. brd-to-build-plan.md (#163) makes three closing rows
# mandatory: per module a HARNESS row `bin/verify-module.sh <Module>` (then LOOK + CONFIRM per
# module-review.md), a process-coherence-pass row per 2-3 modules, and a last row
# `gate-check.sh <project> 5`. This check used to test only that the file existed and was
# approved, so a plan approved before that rule was never reopened. Field case, 2026-10-02: a
# 137-row plan for 7 modules had 0 / 0 / 0 of them, Stage 4 printed PASS, and the build reached
# DONE with look, sweep and journeys at 0 of 7 — nothing that walks a plan does a step the plan
# does not list.
#
# Denominator: the modules declared under architecture/modules/<Module>/ and as
# `## Module brief — <Module>` sections in the plan. When neither exists yet (briefs are written
# just in time), the modules named by close rows are the denominator, and at least one is owed.
# Coherence rows are owed only from two modules up (there is no cluster of one). A plan with no
# numbered rows at all is not counted — there is nothing to walk — and the verdict says so.
stage4_closing_rows() {
local plan="$1" rows declared closed m missing="" n ncoh need last
rows="$(tr -d '\r' < "$plan" | grep -E '^\|[[:space:]]*[0-9]+[A-Za-z.]*[[:space:]]*\|')"
# Rows with a Kind cell are the plan's steps; a numbered question or decision table elsewhere
# in the file is not. Plans written before the Kind column keep every numbered row.
printf '%s\n' "$rows" | grep -qE '\|[[:space:]]*`?(BRIEF|BUILD|PROVE|RUN|HARNESS)`?[[:space:]]*\|' \
&& rows="$(printf '%s\n' "$rows" | grep -E '\|[[:space:]]*`?(BRIEF|BUILD|PROVE|RUN|HARNESS)`?[[:space:]]*\|')"
if [ -z "$rows" ]; then
echo "OK|no numbered rows (closing rows not counted)"
return
fi
declared="$( { for m in "$(dirname "$plan")/modules"/*/; do [ -d "$m" ] && basename "$m"; done
tr -d '\r' < "$plan" | sed -nE 's/^#+[[:space:]]+Module brief[[:space:]]+(—|–|-|:)[[:space:]]*([A-Za-z0-9_]+).*/\2/p'
} 2>/dev/null | sed '/^$/d' | sort -u)"
closed="$(printf '%s\n' "$rows" | grep 'verify-module\.sh' \
| sed -nE 's/.*verify-module\.sh[`"[:space:]]+([A-Za-z0-9_]+).*/\1/p' | sort -u)"
if [ -n "$declared" ]; then
for m in $declared; do
printf '%s\n' "$rows" | grep 'verify-module\.sh' | grep -qw -- "$m" || missing="$missing $m"
done
n="$(printf '%s\n' "$declared" | wc -l | tr -d ' ')"
else
n="$(printf '%s\n' "$closed" | sed '/^$/d' | wc -l | tr -d ' ')"
[ "$n" -gt 0 ] || missing=" (no module has one)"
fi
ncoh="$(printf '%s\n' "$rows" | grep -ciE 'process-coherence-pass|coherence pass')"
need=0; [ "$n" -ge 2 ] && need=$(( (n + 2) / 3 ))
last="$(printf '%s\n' "$rows" | tail -1)"

local why=""
[ -n "$missing" ] && why="no \`verify-module.sh <Module>\` close row for:$missing"
[ "$ncoh" -lt "$need" ] && why="${why:+$why; }$ncoh of $need coherence-pass row(s) for $n modules"
printf '%s' "$last" | grep -qE 'gate-check\.sh[^|]*[[:space:]]5([^0-9]|$)' \
|| why="${why:+$why; }the last numbered row is not \`gate-check.sh <project> 5\`"
if [ -n "$why" ]; then
echo "FAIL|build-plan.md is missing its closing rows (brd-to-build-plan.md, \"three closing rows\"): $why — add them; nothing that walks the plan does a step it does not list"
else
echo "OK|closing rows for $n of $n modules, $ncoh coherence row(s), final gate row"
fi
}

Expand Down
7 changes: 7 additions & 0 deletions skills/brd-to-build-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,13 @@ harness. This is the *moment*; the shape is Step 1's column and the detail is th
> | the plan's **last** row | `RUN` | `gate-check.sh <project> 5` | every Stage 5 obligation is discharged or waived with a reason: 0 PENDING |
>
> Denominator: N modules → N close rows, ⌈N/3⌉ or more coherence rows, and exactly one final gate row.
>
> `gate-check.sh <project> 4` counts them. N is the modules under `architecture/modules/<Module>/`
> plus any `## Module brief — <Module>` section; a close row is a step row naming
> `verify-module.sh <Module>`; coherence rows are owed from two modules up; the last step row
> must be `gate-check.sh … 5`. A plan approved before this rule FAILs Stage 4 until the rows are
> added — re-run the gate after any change to this skill, because an approved plan is not
> reopened by anything else.

**Why the closing rows are rows, not a step in the build loop (an unattended benchmark build,
2026-09-27).** A 137-row plan was built unattended to DONE. The full e2e suite showed 62 pass and
Expand Down
31 changes: 30 additions & 1 deletion tests/wave2/test-bug03-gates.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env bash
# Fixture for wave-2 #3: the unanchored substring CONTENT gates — Stage 0 (triage sign-off),
# Stage 2 (validation stop condition) and Stage 7 (cutover decision row); T12 adds the
# existing-app-change parked state (Stage 0) and that mode's Stage 1 hint.
# existing-app-change parked state (Stage 0) and that mode's Stage 1 hint; T16 the Stage 4
# closing rows (brd-to-build-plan.md, #163).
#
# Stage P is covered by test-stage-p.sh and is deliberately not retested here.
#
Expand Down Expand Up @@ -253,6 +254,34 @@ P="$(mkproj t15)"
V="$(verdict "$P" 7)"
case "$V" in *PASS*) ok "header-less register keeps the old behaviour" ;; *) bad "header-less register regressed: $V" ;; esac

echo "== T16: Stage 4 reads the plan — the three closing rows are counted (#163, field case 2026-10-02) =="
# check_stage_4 tested only that build-plan.md existed and was approved. A 137-row plan for 7
# modules with 0 close rows, 0 coherence rows and no final gate row printed PASS, and the build
# reached DONE with look/sweep/journeys at 0 of 7. Positive control: the pre-fix script PASSES
# the first case below.
HDR='| # | Kind | Step | Produces | Depends on | Skills | State |\n|---|---|---|---|---|---|---|\n'
P="$(mkproj t16)"; printf '| 4 | Build plan approved | CONFIRMED | |\n' >> "$P/PROJECT.md"
mkdir -p "$P/architecture/modules/Orders" "$P/architecture/modules/Customers" "$P/architecture/modules/Billing"
printf "# Plan\n\n$HDR| 1 | BRIEF | Orders brief | | | none | built |\n| 2 | BUILD | 10-orders.mdl | | 1 | none | built |\n| 3 | PROVE | acceptance test | | 2 | none | built |\n" \
> "$P/architecture/build-plan.md"
V="$(verdict "$P" 4)"
case "$V" in *FAIL*'Billing Customers Orders'*'0 of 1 coherence'*'last numbered row'*) ok "a plan with no closing rows FAILs and names each missing one" ;;
*) bad "a plan with no closing rows was not refused with its gaps named: $V" ;; esac
printf "# Plan\n\n$HDR| 1 | BUILD | 10-orders.mdl | | | none | built |\n| 2 | HARNESS | \`bin/verify-module.sh Orders\`, then LOOK + CONFIRM | | 1 | none | |\n| 3 | HARNESS | bin/verify-module.sh Customers | | | none | |\n| 4 | HARNESS | process-coherence-pass.md on Orders + Customers | | | none | |\n| 5 | HARNESS | bin/verify-module.sh Billing | | | none | |\n| 6 | RUN | \`gate-check.sh <project> 5\` | | | none | |\n\n## Open questions\n\n| # | Question | Status |\n|---|---|---|\n| 1 | who signs off | open |\n" \
> "$P/architecture/build-plan.md"
V="$(verdict "$P" 4)"
case "$V" in *PASS*'3 of 3 modules'*) ok "a plan with every closing row passes; a numbered question table after it is not a step" ;;
*) bad "false red on a complete plan: $V" ;; esac
sed -i.bak 's/gate-check.sh <project> 5/gate-check.sh <project> 3/' "$P/architecture/build-plan.md"
V="$(verdict "$P" 4)"
case "$V" in *FAIL*'last numbered row'*) ok "a final gate row for the wrong stage is not the closing row" ;;
*) bad "a final 'gate-check.sh 3' row passed: $V" ;; esac
P="$(mkproj t16b)"; printf '| 4 | Build plan approved | CONFIRMED | |\n' >> "$P/PROJECT.md"; mkdir -p "$P/architecture"
printf "# Plan\n\n$HDR| 1 | BUILD | 10.mdl | | | none | |\n| 2 | RUN | gate-check.sh . 5 | | | none | |\n" > "$P/architecture/build-plan.md"
V="$(verdict "$P" 4)"
case "$V" in *FAIL*'no module has one'*) ok "with no briefs yet, a plan still owes at least one module close row" ;;
*) bad "a plan with no module dirs and no close row passed: $V" ;; esac

printf '\n%s: %d ok, %d FAIL\n' "$(basename "$0")" "$PASS" "$FAIL"
rm -rf "$WORK"
[ "$FAIL" -eq 0 ]
Loading