From f0051db6bed6d7b6b302a4e69cdd4ec0e92425a5 Mon Sep 17 00:00:00 2001 From: MendixMau Date: Fri, 16 Oct 2026 23:12:27 +0800 Subject: [PATCH 1/2] fix(build-plan-status): --refresh keeps an existing build-plan.json current exec.sh and gate-check.sh run --refresh after every step, but only an explicit --json rewrote build-plan.json, so a Stage 4 snapshot read 0 built after a day of building. --refresh now regenerates an existing json with the same parser (skipping the write when only generatedAt would differ) and never creates one. Docs, manifest and routing rows updated. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + README.md | 2 +- ROUTING.md | 2 +- bin/lib/artifact-manifest.tsv | 2 +- bin/lib/skill-routing.tsv | 2 +- project-bin/build-plan-status.sh | 18 +++++++++++++++++- tests/wave2/test-build-plan-json.sh | 29 +++++++++++++++++++++++++++++ 7 files changed, 51 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5592c10d..e3c4d494 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(build-plan-status): --refresh keeps an existing build-plan.json current, so a day of building no longer reads as 0 built — MendixMau - process(testing): **unit tests are chosen per flow, not per module: a module brief lists `Unit flows:` (one `- ACT_X: reason` line each) and `affected-tests.sh` runs only the listed flows the change touched.** Follow-up to #417 on the maintainer's policy that unit-testing everything is overkill (logic and integrations only, UI journeys catch the rest). Module done runs the `--module` modules' listed flows only; a list wins for its module even under `Unit tests default: yes`; bullets (`-`, `*`, `+`, `1.`, `1)`) may be separated by blank lines, carry `(note)` or `Module.` forms, or sit inline (`Unit flows: A: money, B: rest`), a `## Unit flows` heading works with or without a colon, a line that cannot be read warns with its text, and a Unit flows block that yields no flow warns; under default yes a test is dropped only when every module it names has a list that excludes it; CRLF briefs are read; `Unit tests: yes` or `Unit modules:` with no list keeps the module-level selection; a flow listed without a reason, with no test, with a different casing than the model, or not in the model (`SHOW MICROFLOWS IN`), and a module the model lacks, warn. Test: wave2 `test-affected-tests.sh` L10 (35 checks). Baseline unchanged at 75,194 words (budget 80,000) — MendixMau - fix(sync-project, doctor): **a project whose `.claude/settings.json` lacks the `look-ledger.sh seen` hook now gets it back on the next sync, and `doctor.sh` warns when it is missing.** In a lab project the look ledger read "0 of 31 pages seen" while 22 of them had screenshots: `install-claude-permissions.sh` (the only writer of the hook) had never run there, `sync-project.sh` only printed a warning in a long report, and `doctor.sh` checked the allow-list but not the two hooks, so `seen.tsv` stayed empty and any gate built on the ledger would have stalled the build. `install-claude-permissions.sh` gains `--hooks-only` (add the SessionStart and PostToolUse hooks, touch no allow, deny or ask entry, prune nothing, write nothing to `settings.local.json`); `sync-project.sh` runs it when the hooks are missing (`--dry-run` says what it would add); `doctor.sh` names the missing hook and the fix. `tests/wave2/test-look-ledger-hook-repair.sh` reproduces the gap, then proves detect, repair, idempotence and that a Read of a screenshot writes a seen row. Review follow-up: sync repairs only a project that installed the profile before (sidecar with a non-empty allow, or `.mxtk/permission-profile`; checked by `--consent-check`), otherwise it WARNs with the one-line `--hooks-only` command; `.mxtk/no-hook-repair` is the documented opt-out (sync and doctor); `--hooks-only` creates no empty allow/deny, leaves no `.bak`, keeps the file's indent and non-ASCII, and leaves a hand-formatted file untouched (exit 3, lines printed). — lab project field run (night 2026-10-16) - new(bug-log): **`docs/bugs.jsonl` per project plus `bin/file-bugs.sh` to file it safely to mendixlabs/mxcli or this repo.** A session that hits or works around a tool bug appends a row (`--add`, next free `PBUG-NN`, project-local so it never collides with the toolkit's BUG-n; the public number is the issue; the one-line instruction sits in learned-detection-gaps.md). The script normalises Unicode (NFKC, zero-width and lookalike letters, names split by a line break), redacts every title and body (home and spaced paths, user and host names, emails, IPv4/IPv6, tokens, key=value, JSON and `--flag value` secrets, hex tokens, AWS keys, any dotted hostname, server names from config files, URLs other than the two repos matched exactly and cut to the repo prefix plus /issues/N or /pull/N, a URL scheme from a fixed list (jdbc, postgres, mysql, mongodb, redis, amqp, ssh, git and the like) stays and loses the host, any other scheme becomes ``, the project's .mpr, module, entity, attribute, page and microflow names (distinctive ones inside identifiers; ordinary-word and short names only as whole words or segments, never inside mxcli verbs, toolkit file names or other words), Authorization and cookie header values, `$LEAKGUARD_DENY` as a regex plus the project's .leakguard-deny), builds the versions line from version-shaped strings only, scans again with the same matcher and refuses a bug that still looks unsafe: a refused bug gets no duplicate search, no URL and no text in `--links`/`--json`. Default is a dry run with likely duplicates; posting needs `--file PBUG-NN` or `--all` and a y/N per bug read from /dev/tty only (no stdin test hook; a pty cannot be told from a human, so the shipped permissions deny `file-bugs.sh` and `file-bugs.py` with `--file` or `--all`, abbreviations are refused, and a human runs posting with `!`), and a deny list must exist and be valid (an invalid pattern counts as missing) or posting, `--links` and `--json` are refused, passes `--repo= --title= --body-file=` to gh, keeps the log's file mode; `--links` caps title and body under the URL limit. `tests/wave2/test-file-bugs.sh` covers each case with a stubbed gh and a pty helper — MendixMau diff --git a/README.md b/README.md index 867a1b4b..e8dbe865 100644 --- a/README.md +++ b/README.md @@ -604,7 +604,7 @@ Every mxcli project has a `.ai-context/skills/` directory (bundled by `mxcli ini |---|---| | Dispatching a build step to a helper — generate its one-file pack (the brief's Build steps row, mxcli brain brief, live DESCRIBEs, example, folder) and hand over the path instead of a reading list. Measured: 25-38% fewer tool calls and 30-41% less time, same quality; the token saving is not proven | `project-bin/context-pack.sh` | | An exec.sh guard refused a write (not a permission prompt), or building a module with mxcli — verified, iterative, coverage-checklist gated; on a refusal: commit, wait or stop, never FORCE_EXEC | `skills/iterative-build-loop.md` | -| After marking a module done, or any time "how much is built vs proven" is asked — renders build-plan.html from done- prefixes and verify-module.sh/improvement-register.md, kept as two honestly separate views; --json writes architecture/build-plan.json parsed from build-plan.md's Phase headings (a plan with no Phase headings gets no file) | `project-bin/build-plan-status.sh` | +| After marking a module done, or any time "how much is built vs proven" is asked — renders build-plan.html from done- prefixes and verify-module.sh/improvement-register.md, kept as two honestly separate views; --json writes architecture/build-plan.json parsed from build-plan.md's Phase headings (a plan with no Phase headings gets no file); --refresh keeps an existing build-plan.json current but never creates one | `project-bin/build-plan-status.sh` | | Turning a client-derived Mendix app into a clean, shareable demo with zero client fingerprint — branding, data, custom widgets | `skills/anonymize-client-app-for-demo.md` | | Handing a headless-built model to a person — opening it in Studio Pro, a free sandbox, or a colleague's machine: the model travels, the demo data and runtime config (keys, an agent's bound model) do not, and each needs its own re-establish step | `skills/handoff-to-studio-pro.md` | | Stage 5 start, before the first module of any entry mode — one entity, flow, page, nav, demo user, journey and screenshot proven in the running app, so build/run/look/test are known to work before a module depends on them | `skills/walking-skeleton.md` | diff --git a/ROUTING.md b/ROUTING.md index a2a3f1de..72f7fb21 100644 --- a/ROUTING.md +++ b/ROUTING.md @@ -143,7 +143,7 @@ picks the row up. That is the whole procedure — there is no second list to rem | Before any mxcli exec / exec.sh / --mcp write — ask or run? the knob decides | `bin/exec-approval.sh` | mdl,gate | 5,6 | baseline | | Dispatching a build step to a helper — generate its one-file pack (the brief's Build steps row, mxcli brain brief, live DESCRIBEs, example, folder) and hand over the path instead of a reading list. Measured: 25-38% fewer tool calls and 30-41% less time, same quality; the token saving is not proven | `project-bin/context-pack.sh` | mdl | 5 | ondemand | | An exec.sh guard refused a write (not a permission prompt), or building a module with mxcli — verified, iterative, coverage-checklist gated; on a refusal: commit, wait or stop, never FORCE_EXEC | `skills/iterative-build-loop.md` | mdl,gate | 5 | ondemand | -| After marking a module done, or any time "how much is built vs proven" is asked — renders build-plan.html from done- prefixes and verify-module.sh/improvement-register.md, kept as two honestly separate views; --json writes architecture/build-plan.json parsed from build-plan.md's Phase headings (a plan with no Phase headings gets no file) | `project-bin/build-plan-status.sh` | architect,gate,review | 4,5,6 | ondemand | +| After marking a module done, or any time "how much is built vs proven" is asked — renders build-plan.html from done- prefixes and verify-module.sh/improvement-register.md, kept as two honestly separate views; --json writes architecture/build-plan.json parsed from build-plan.md's Phase headings (a plan with no Phase headings gets no file); --refresh keeps an existing build-plan.json current but never creates one | `project-bin/build-plan-status.sh` | architect,gate,review | 4,5,6 | ondemand | | Turning a client-derived Mendix app into a clean, shareable demo with zero client fingerprint — branding, data, custom widgets | `skills/anonymize-client-app-for-demo.md` | mdl,review | 6 | ondemand | | Handing a headless-built model to a person — opening it in Studio Pro, a free sandbox, or a colleague's machine: the model travels, the demo data and runtime config (keys, an agent's bound model) do not, and each needs its own re-establish step | `skills/handoff-to-studio-pro.md` | test,review | 6,7 | ondemand | | Sharing the project with a colleague on Mendix Team Server while the build loop stays on GitHub — content-transplant snapshots between the two clones (status/push/pull); SUPERSEDED by platform-link.md §3 (adopt without rewriting history, field-proven) — only if that path fails; UNPROVEN against a real Team Server | `project-bin/ts-sync.sh` | mdl,gate | 6,7 | experimental | diff --git a/bin/lib/artifact-manifest.tsv b/bin/lib/artifact-manifest.tsv index 93c912ab..a31ac2ad 100644 --- a/bin/lib/artifact-manifest.tsv +++ b/bin/lib/artifact-manifest.tsv @@ -92,7 +92,7 @@ wireframes 3 design/wireframes/*.html skills/design-artifacts.md Step 3, one per module-definition 3 architecture/modules/*/definition.md skills/architecture-blueprint.md Step 1 (:60 — the directory shape, one doc per module) THIS ROW IS ITS FIRST MECHANICAL CONSUMER — as of 2026-08-22 nothing executable read definition.md (improvement-plan P7 residue, the newest instance of the pattern this table exists to stop); prose consumers: skills/architecture-blueprint.md:43, skills/coverage-ledger.md:27 migration,requirements,existing-app-change report build-plan 4 architecture/build-plan.md skills/brd-to-build-plan.md bin/gate-check.sh:955 (check_stage_4), project-bin/build-plan-status.sh (progress board), project-bin/coverage-preflight.sh:238 (the claims join) migration,requirements,existing-app-change gate build-plan-html 4 architecture/build-plan.html project-bin/build-plan-status.sh --html (skills/brd-to-build-plan.md:47 — derived view, never hand-edited; refreshed by exec.sh and full gate-check.sh runs via --refresh) conversion-runbook.md §2 Stage-4 Surface row, parsed at bin/gate-check.sh:1284 migration,requirements,existing-app-change report -build-plan-json 4 architecture/build-plan.json project-bin/build-plan-status.sh --json (derived from build-plan.md's Step-5 Phase headings, row tables and claims: blocks, never hand-edited; not written when the markdown has no Phase headings) MXTK Studio Plan room (a view built from data, not from the HTML); nothing in the toolkit reads it and no verdict may depend on it migration,requirements,existing-app-change report +build-plan-json 4 architecture/build-plan.json project-bin/build-plan-status.sh --json (derived from build-plan.md's Step-5 Phase headings, row tables and claims: blocks, never hand-edited; not written when the markdown has no Phase headings; --refresh keeps an existing file current but never creates it) MXTK Studio Plan room (a view built from data, not from the HTML); nothing in the toolkit reads it and no verdict may depend on it migration,requirements,existing-app-change report module-brief 4 architecture/modules/*/module-brief.md|architecture/modules/*-brief.md skills/module-brief.md — ba-agent writes the first at Stage 4, the rest just-in-time bin/gate-check.sh:1121 (build-ready fails without one), project-bin/build-plan-status.sh:109 migration,requirements,existing-app-change gate coverage-ledger 4 architecture/modules/*/coverage-ledger.md|architecture/modules/*-coverage-ledger.md|architecture/coverage-ledger.md skills/coverage-ledger.md procedure at Stage 4 (BRD leaves × build-plan claims); the three shapes are §"Where the ledger lives", first canonical project-bin/conformance-check.sh:85-93, project-bin/coverage-preflight.sh:134-137, project-bin/verify-module.sh:299-300, project-bin/review-module.sh:289, conversion-runbook.md:487 (Stage-4 produces row) — the eleven-consumers-no-producer artifact of improvement-plan Finding 12 migration,requirements report ui-reviews 5 design/ui-reviews/ui-review-*.html skills/module-review.md pass (review-agent), one report per module close-out bin/gate-check.sh:984 (check_stage_6), bin/lib/obligations.tsv:47 (the look row — per-module coverage and denominators live THERE; this row only asks whether the stage produced any report at all) migration,requirements,greenfield,existing-app-change gate diff --git a/bin/lib/skill-routing.tsv b/bin/lib/skill-routing.tsv index a3634c94..4e2b41fa 100644 --- a/bin/lib/skill-routing.tsv +++ b/bin/lib/skill-routing.tsv @@ -186,7 +186,7 @@ tool-output-is-not-ground-truth skills/tool-output-is-not-ground-truth.md Any ti agent-permission-friction skills/agent-permission-friction.md Any refused, denied or blocked command — BEFORE rewriting a permission rule and before telling the user a tool is blocked. A rule matches the START of the command line, so an allowlisted tool prefixed with cd matches nothing all - ondemand diagnose process-coherence-pass skills/process-coherence-pass.md Checking whether the whole journey hangs together rather than each piece — finds correctly-built components nothing reaches, which per-element conformance and UI tests both miss review 5,6 ondemand verify coherence-cadence project-bin/coherence-cadence.sh After every module's CONFIRM stage — counts proven modules since the last cluster/full coherence pass and exits DUE once the threshold is reached, so the cadence isn't left to memory mdl,gate,review 5,6 ondemand verify -build-plan-status project-bin/build-plan-status.sh After marking a module done, or any time "how much is built vs proven" is asked — renders build-plan.html from done- prefixes and verify-module.sh/improvement-register.md, kept as two honestly separate views; --json writes architecture/build-plan.json parsed from build-plan.md's Phase headings (a plan with no Phase headings gets no file) architect,gate,review 4,5,6 ondemand build +build-plan-status project-bin/build-plan-status.sh After marking a module done, or any time "how much is built vs proven" is asked — renders build-plan.html from done- prefixes and verify-module.sh/improvement-register.md, kept as two honestly separate views; --json writes architecture/build-plan.json parsed from build-plan.md's Phase headings (a plan with no Phase headings gets no file); --refresh keeps an existing build-plan.json current but never creates one architect,gate,review 4,5,6 ondemand build e2e-evidence-report skills/e2e-evidence-report.md Turning an already-rigorous run into a narrated proof a stakeholder can trust without running anything test,review 6 ondemand verify record-demo-video skills/record-demo-video.md Recording a narrated screen-capture demo of a running app for a human to watch — opening on the app instead of a blank frame, and keeping captions synced to the pixels test 6 ondemand verify demo-recording-polish skills/demo-recording-polish.md A demo film someone will watch looks captured, not produced — 25 fps smear, scroll jumps, no visible pointer, a dense page nobody can read: 2x CDP screencast at 60 fps, drawn cursor and click ripple, eased scroll, and a zoom rendered afterwards from the click log (never a CSS transform in the page) test 6 ondemand verify diff --git a/project-bin/build-plan-status.sh b/project-bin/build-plan-status.sh index 779ae491..f96bc1b5 100755 --- a/project-bin/build-plan-status.sh +++ b/project-bin/build-plan-status.sh @@ -34,7 +34,9 @@ # by views A and B # --refresh the unattended form of --html, for exec.sh and gate-check.sh: quiet, writes only # when build-plan.md exists, never over a build-plan.html this script did not -# write, and leaves the file untouched when nothing but the timestamp would change +# write, and leaves the file untouched when nothing but the timestamp would change. +# It also keeps an EXISTING architecture/build-plan.json current (same parser as +# --json, same no-rewrite-for-a-timestamp rule) and never creates one # --json also write architecture/build-plan.json, parsed from architecture/build-plan.md's # Step-5 Phase headings, row tables and claims: blocks (see "C." below) # --quiet suppress the stdout table (useful when only --html/--json output is wanted) @@ -425,6 +427,20 @@ if [ "$WRITE_JSON" -eq 1 ] || [ "$WRITE_HTML" -eq 1 ]; then case $? in 0) PLAN_JSON="$PLAN_TARGET" JSON_MSG="wrote ${JSON_OUT#$ROOT/}" + if [ "$REFRESH" -eq 1 ] && [ "$WRITE_JSON" -eq 0 ] && [ -f "$JSON_OUT" ]; then + # --refresh keeps an EXISTING build-plan.json current (never creates one): exec.sh and + # gate-check.sh run --refresh after every build step, so a json written once at Stage 4 + # would otherwise read 0 built after a day of building. Same parser as --json; the + # write is skipped when only generatedAt would differ, like the html below. + if "$PY" -c 'import json,sys +def load(f): + d=json.load(open(f,encoding="utf-8")); d.pop("generatedAt",None); return d +sys.exit(0 if load(sys.argv[1])==load(sys.argv[2]) else 1)' "$JSON_OUT" "$PLAN_TARGET" >/dev/null 2>&1; then + JSON_MSG="${JSON_OUT#$ROOT/} already current" + else + cp -f "$PLAN_TARGET" "$JSON_OUT" 2>/dev/null && JSON_MSG="refreshed ${JSON_OUT#$ROOT/}" + fi + fi if [ "$WRITE_JSON" -eq 1 ]; then # A producer guarantees its own output is ignored (snapshot-mpr.sh's rule). The JSON # is a build product: regenerated on demand in under a second, derived entirely from diff --git a/tests/wave2/test-build-plan-json.sh b/tests/wave2/test-build-plan-json.sh index 589813d7..07410fdf 100644 --- a/tests/wave2/test-build-plan-json.sh +++ b/tests/wave2/test-build-plan-json.sh @@ -39,6 +39,8 @@ # --json, a `done-` script reads done-, and a row whose State says built while its script # is not done- is flagged. --refresh leaves an unchanged page byte-identical, never writes # over a page this script did not generate, and writes nothing without a build-plan.md. +# T11 --refresh keeps an existing build-plan.json current (a State flipped in the md shows in +# the json), rewrites nothing when only generatedAt would differ, and never creates a json. # # Usage: bash tests/wave2/test-build-plan-json.sh /path/to/build-plan-status.sh @@ -365,6 +367,33 @@ P10B="$(new_project htmlnoplan)" bash "$SCRIPT" "$P10B" --refresh >/dev/null 2>&1 [ ! -f "$P10B/architecture/build-plan.html" ] && ok "T10 --refresh writes nothing without build-plan.md" || bad "T10 --refresh wrote a page with no plan" +# ── T11: --refresh keeps an existing build-plan.json current, never creates one ── +P11="$(new_project jsonrefresh)" +cat > "$P11/architecture/build-plan.md" <<'MD' +# Build Plan + +### Phase 1 — Alpha + +| # | Kind | Step | Produces/Proves | Depends on | Skills | State | +|---|---|---|---|---|---|---| +| 1.1 | script | `01-alpha.mdl` | Alpha entity | — | — | not built | +MD +J11="$P11/architecture/build-plan.json" +bash "$SCRIPT" "$P11" --json --quiet >/dev/null 2>&1 +q "$J11" "json.dumps(d)" | grep -q 'not built' && ok "T11 --json snapshot shows the row not built" || bad "T11 setup: snapshot not 'not built'" "$(cat "$J11" 2>/dev/null)" +sed 's/| not built |/| built |/' "$P11/architecture/build-plan.md" > "$P11/md.tmp" && mv "$P11/md.tmp" "$P11/architecture/build-plan.md" +bash "$SCRIPT" "$P11" --refresh >/dev/null 2>&1 +S11="$(q "$J11" "d['phases'][0]['steps'][0]")" +case "$S11" in *"'not built'"*|*PARSE-ERROR*) bad "T11 --refresh left the json stale" "$S11" ;; *built*) ok "T11 --refresh updates the existing json to built" ;; *) bad "T11 unexpected step" "$S11" ;; esac +SUM11="$(cksum < "$J11")" +sleep 1 +bash "$SCRIPT" "$P11" --refresh >/dev/null 2>&1 +[ "$(cksum < "$J11")" = "$SUM11" ] && ok "T11 --refresh rewrites nothing when only the timestamp would change" || bad "T11 --refresh rewrote an unchanged json" +P11B="$(new_project jsonrefreshnone)" +cp "$P11/architecture/build-plan.md" "$P11B/architecture/build-plan.md" +bash "$SCRIPT" "$P11B" --refresh >/dev/null 2>&1 +[ ! -f "$P11B/architecture/build-plan.json" ] && ok "T11 --refresh does not create a json when there is none" || bad "T11 --refresh created build-plan.json" + echo "" echo "PASS=$PASS FAIL=$FAIL ($WORK)" [ "$FAIL" -eq 0 ] From dd8f301d974626a4f45d91fe930a5bf63af994d3 Mon Sep 17 00:00:00 2001 From: MendixMau Date: Fri, 16 Oct 2026 23:51:21 +0800 Subject: [PATCH 2/2] ci: retrigger checks Co-Authored-By: Claude Opus 5.5