Skip to content

perf(build loop): pipeline modes — journeys, dual-viewport LOOK, pre-push build and unit tests move to module close (#686) - #700

Open
MendixMau wants to merge 4 commits into
masterfrom
perf/module-close-proofs-686
Open

MendixMau wants to merge 4 commits into
masterfrom
perf/module-close-proofs-686

Conversation

@MendixMau

@MendixMau MendixMau commented Nov 6, 2026 •

Copy link
Copy Markdown
Owner

What changed

The cadence of the per-row proof stack is now a project setting: pipeline modes. PROJECT.md Pipeline mode: high-speed | balanced | high-quality. No line means balanced.

high-speed balanced (default) high-quality
Per row, visual none (exec gate + mx check only) one quick LOOK at 1280 of the changed page journeys of reached pages + LOOK at 390 and 1280 (master today)
Module close journeys + polish loop: 75% fidelity AND a written LOOK, max 2 rounds journeys + polish loop: 85% AND LOOK, max 3 rounds journeys + polish loop: 90% AND LOOK, max 5 rounds
Pre-push full build once per module once per module every push
Unit tests per module per module per row

The single source of this table is skills/iterative-build-loop.md § "Pipeline mode". The other skills say "balanced" and point there.

  • Cut 1: journeys and the dual-viewport LOOK move to module close. project-bin/exec.sh (change_lane, ~:497–535) prints the lane for the mode and names it (test lane (…; Pipeline mode: balanced)). The init-project CLAUDE.local.md text (bin/init-project.sh step 5) now asks for one viewport per page script and both at module review. It also names the three modes. The protected 2026-09-19 "a fidelity score is not a LOOK" line is unchanged. skills/ui-loop.md, testing-shape.md, ui-preflight-pages.md and checkpoint-build.md change only where they stated the per-row cadence. skills/dispatching.md gets the STOP row Research: Stage 5 per-row cost (avg 19 min, up to 41 min per build-plan row), where the time goes and what the toolkit should prescribe instead #686 proposed: a per-row brief carries only the per-row checks of the project's mode.

  • The per-row page check keys on page statements, not on the change class. The class still decides the tests. But any CREATE/ALTER PAGE, SNIPPET or LAYOUT statement now also adds the mode's per-row page check:

    • balanced: a 1280 LOOK;
    • high-quality: journeys plus the 390 + 1280 LOOK;
    • high-speed: none, and the lane says so.

    So a page script with its co-located GRANT, which classifies as logic, gets both the logic lane's tests and the page check. This came from the mode experiment below.

  • Cut 2: the pre-push full mxbuild runs once per module. project-bin/install-project-hooks.sh writes a pre-push hook v2. Under Gate mode: check, a stamp that says the model was only mx-checked now leads to a full build only when one of these holds:

    • a docs/progress/module-close/*.md report is newer than the hook's last full build (.claude/loop/prepush-build.last, written only on a passing build);
    • a pushed commit touches docs/progress/module-close/;
    • the mode is high-quality;
    • MXTK_PREPUSH_BUILD=always is set.

    It also builds in these cases:

    • no full build is recorded yet on this machine;
    • MXTK_PREPUSH_MAX_DEFERRED (10) pushes in a row deferred it, so a project that never runs module-close still gets one;
    • the Java/JavaScript/theme fingerprint differs from the one recorded at the last full build;
    • the mode cannot be read (an older _common.sh). Unsure means build.

    module-close.sh records its own full build for the hook, after writing its report, so the next module's first push does not build again. Otherwise the hook prints one line saying the build is deferred to module close. --check accepts only v2, so sync-project.sh refreshes a v1 hook. Our own v1 hook is replaced in place, never chained.

  • Cut 3: unit tests run per module. The per-row driver is the affected tests: ./bin/affected-tests.sh <script> pointer that exec.sh prints. In balanced and high-speed it now prints tests owed at module close: ./bin/affected-tests.sh --moment module --module <Module>. Per-row tests stay in two cases. high-quality keeps everything per row. Below it, a ## Test strategy row Every change is honoured column by column: a unit scope keeps unit tests per row, and a journeys scope keeps the reached pages' journeys per row. A CONFIRMED affected | affected row therefore keeps both. The CAC-5 offer now defaults that row to none (exec gate only) | none. affected-tests.sh itself is unchanged apart from a comment: it is a calculator and still lists a change's touched set when asked. PROJECT.md's Unit tests default and module flips still decide which modules have unit tests at all.

  • Module-close polish loop (maintainer addition). module-close.sh's report now names the journeys and unit tests owed and the 390 + 1280 LOOK. It also gives the bounded polish loop per page, with that mode's numbers:

    • up to R rounds of fix → page-fidelity → LOOK;
    • stop when fidelity ≥ T% AND a written LOOK finds no blocking issue. A score alone never passes a page. Where fidelity is UNMEASURED, the LOOK decides;
    • after R rounds the gap is logged in design/ui-reviews/ and reported at the gate. It does not block the module.

    UI polish: <N>% / <R> rounds in PROJECT.md overrides either number in any mode. The round cap is not enforced: the agent counts the rounds. The report carries one Polish: n of cap row per page to count in. It is a template only; no gate reads it yet. The Page | Role table stays last, because row-groups.sh reads it.

  • One Java fingerprint, taken from the model folder. exec.sh (gate kind), verify-model.sh and the pre-push hook all call mxtk_model_java_fp in _common.sh. It fingerprints the folder that holds the .mpr, which is app/ on a two-tree checkout. mxtk_java_fp now lists --full-name paths and hashes from the repository top. Before this, git hash-object --stdin-paths could not open the cwd-relative names below the top, so on a two-tree checkout the fingerprint never changed and a Java edit never forced a full build. Single-tree fingerprints are byte-identical to master's.

  • Settings reading. _common.sh had no PROJECT.md setting helper (exec.sh's _first_match is private to exec.sh, and the pre-push hook cannot reach it). So this PR adds mxtk_pipeline_mode and mxtk_ui_polish to _common.sh. exec.sh, module-close.sh and the pre-push hook all call them. The hook sources _common.sh the same way the post-commit hook already does for resolve_py. Env MXTK_PIPELINE_MODE wins. An unrecognised value reads as balanced, and the lane prints the mode in effect.

  • sync-project.sh prints one line when PROJECT.md has no Pipeline mode: line: "Pipeline mode not set → balanced: journeys, the 390+1280 LOOK and the full build now run at module close. Set Pipeline mode: high-quality in PROJECT.md to keep the old per-row cadence."

  • Intake Q15 asks for the mode in plain words, with three options and their trade-offs. A new PROJECT.md gets Pipeline mode: balanced plus a short comment. sync-project.sh reports Q15 as missing on existing intakes and appends it only with --repair-intake, the existing contract. This changes no behaviour.

  • Disclaimer. "Screens are polished and checked at module close; mid-module pages may look rough, that's expected." It prints under the lane (balanced and high-speed) and on the progress report's build tab.

  • Word budget: three incident narratives were moved, verbatim, to process/baseline-incident-history.md (UI5, UI6, UI7, DP1), each leaving a one-line pointer. Moved: ui-loop.md "why cadence", module-review.md 4a-0 gallery measurement and rows 8–9 history, and dispatching.md restart history. No rule, STOP row or denominator was removed.

Mode experiment (build run on 19a848a, same plan in all three modes)

  • Must-fix found and fixed here. mxtk_change_class marks any script that contains GRANT, ALTER NAVIGATION or CREATE MICROFLOW as logic, and the toolkit mandates co-located page grants. So 8 of 10 page-creating scripts printed "per row: the exec gate only" under balanced. In practice balanced ≈ high-speed per row, and high-quality ≈ balanced plus a build. Fixed above; fixture E9.
  • Pre-push. In balanced and high-speed, mid-module pushes printed "deferred". The full build ran inside module-close.sh. The push after close did not build, because the stamp was already full. In high-quality, 9 of 10 pushes ran a full build, with 0 errors. No full-build-only error first appeared at module close (the app has no Java actions, so the risk was low).
  • Polish loop. 0 rounds ran anywhere. module-close.sh printed the right numbers per mode. The cap had no counter, hence the new Polish: n of cap rows. Two screens stayed at 86% static fidelity from row 9/13 to module close in all three arms, so high-quality's default is lowered from 95% to 90% / 5.
  • Cost of module close. The instruments took 41 / 39 / 25 s (balanced / high-speed / high-quality). These figures exclude LOOK and journey work, which is where Research: Stage 5 per-row cost (avg 19 min, up to 41 min per build-plan row), where the time goes and what the toolkit should prescribe instead #686's 19 min/row lives. The ≥8 min/row question still needs the field run below; the experiment's container was memory-limited.
  • Every change row. The test base had no ## Test strategy, and Unit tests default: no left nothing to run per row, so no arm ran unit tests or journeys per row.

Why (#686 numbers)

  • Stage 5 rows averaged 19 min / 204k tokens, up to 41 min / 415k. exec.sh was only about 8% of wall-clock; most of the time went to per-row journeys, both viewports and screenshot sets.
  • Over 42 findings from rows 1–50 there were about 10 browser-only defects. About 7 would also be caught by one journey plus a LOOK at module close. Per-row LOOKs had 0 unique catches. The click-eating overlay was missed by every per-row LOOK.
  • Build-breaking errors came from the exec gate (mxbuild), not the browser. The unit tests (21 tests, 619 assertions) found no logic bug beyond one wrong-key case.
  • A batch brief without journeys applied two rows in about 10 min. Per-row row briefs took 17–41 min each.
  • One full mxbuild per push costs 1–2 min.

Time cost (per #687)

Step Granularity before → after (balanced) Est. saved per row Can it force rework?
Journeys of reached pages per row → per module ~5–10 min (the 36–41 min rows were journey rows) Yes, at module close. Fixes land there, against a module whose pages all exist
LOOK at 390 + 1280 per row → one 1280 per row; both at module close ~2–4 min (10–19 screenshots per row incl. retakes) Yes, at module close, inside the bounded polish loop
Pre-push full mxbuild per push → per module ~1–2 min per push Yes. A full-build-only error (Java compile) is now found at module close, not at the next push. The mx check gate still runs per exec
Unit tests (mxcli test) per row (when on) → per module ~1–6 min when on (a full mxcli test measured ~6 min) Yes, at module close
Polish loop none → per module, bounded (max 3 rounds per page) adds minutes per page at close Never blocks: the remaining gap is logged and reported

Estimated ≥8 min per row saved in balanced. high-quality costs the same as master per row and adds a stricter polish loop at close.

Regression risk after sync-project.sh

  • Existing projects change cadence: a missing Pipeline mode: line means balanced. After sync, a project's exec.sh stops pointing at per-row journeys and per-row unit tests. Its pre-push hook (refreshed to v2 by sync, because --check fails on v1) stops building on every push. A project that wants today's behaviour adds Pipeline mode: high-quality. The CHANGELOG line says so in bold.
  • A project whose ## Test strategy row Every change names a scope keeps that column per row in any mode. Projects that accepted the old CAC-5 default (affected, modules on only | affected) are in this group, which means their CONFIRMED decision wins over balanced. To take cuts 1 and 3 they set that row to none | none.
  • The first push after sync builds once ("no full build recorded yet"), then the once-per-module cadence applies.
  • No gate verdict, exit code or parsed output changes:
    • exec.sh's lane text is advisory and parsed by nothing;
    • the module-close report keeps the mxtk-module-close: summary line and keeps the Page | Role table last;
    • the pre-push hook still refuses only on a full build that finds errors (exit 1) and never blocks when mxbuild cannot run.
  • On a two-tree or nested checkout, exec.sh's Java-change rule now fires; before, it never could. A project with a recorded fingerprint there sees one extra full build after sync, because the value changes once.
  • The pre-push hook adds one bash spawn to source _common.sh. If _common.sh is old or missing, the mode reads as balanced.
  • Both layouts: every new read goes through $b/.. (the project root the hook already used) or _common.sh's PROJECT_ROOT. Platforms: only find -newer, sed -n, git diff/log and a bash here-string are used. There is no GNU-only flag, and check-portability.sh is clean.

Always-loaded words

bin/render-routing.sh --check: 65,053 before → 65,070 after (budget 80,000). That is +4 words for the journeys-column rule in testing-shape.md and +13 for the Polish: n of R pointer in module-review.md. The added mode/STOP/polish text is balanced by moving four incident narratives out verbatim.

Field-validation plan

  1. The maintainer's live app runs its next module in balanced (no line, or Pipeline mode: balanced).
  2. Compare minutes per row from progress-report / .claude/loop/metrics-ledger.tsv for that module against the previous modules (baseline: 19 min average, Research: Stage 5 per-row cost (avg 19 min, up to 41 min per build-plan row), where the time goes and what the toolkit should prescribe instead #686).
  3. List the defects found at module close (the module-close.sh report, Page | Role table, polish-loop gaps in design/ui-reviews/) next to those found per row. Classify each defect by the step that could first have seen it.
  4. Target: ≥8 min/row saved, and no defect class that only the per-row step caught. Research: Stage 5 per-row cost (avg 19 min, up to 41 min per build-plan row), where the time goes and what the toolkit should prescribe instead #686 found per-row LOOKs had 0 unique catches over 42 findings. If module close surfaces a class only a per-row journey would have caught earlier, that is the evidence to make that step per-row in balanced.

Proof

  • New fixture tests/wave2/test-module-close-cadence.sh: 59/59 pass. Against a worktree of master ee12a61, 45 fail. The 14 that pass there are guards that master's behaviour survives where it should. It covers:
    • the exec lane for all three modes, a missing line and an unknown value;
    • the unit and journeys columns of the strategy row (E3, E8);
    • a page script with a co-located GRANT in all three modes (E9);
    • two-tree Java fingerprints (T1–T3): no change means deferred; a change under app/javasource means a full build in the hook and in exec.sh;
    • init-project's CLAUDE.local.md, intake Q15, and PROJECT.md (exactly one line reads as the setting);
    • pre-push in all three modes, always, first push, report-newer, module-close commit, failing build, an unreadable mode, N deferred pushes, a Java fingerprint change, and v1 → v2;
    • module-close polish numbers per mode, the override, the build record (M4) and the Polish: n of cap row (M5);
    • the sync note (S1).
  • Scoped fixtures for the touched files: all pass. After the two-tree fix I re-ran test-exec-gate-kind 32/32, test-restatement-git-hooks 28/28 and test-model-stamp 35/35. After the experiment fix I re-ran test-exec-change-class 21/21, test-exec-gate-kind 32/32 and test-module-close 10/10. After the review fixes I re-ran test-exec-gate-kind 32/32, test-test-strategy-docs 15/15, test-restatement-git-hooks 28/28, test-module-close 10/10, test-exec-change-class 21/21 and test-bug12-sync 64/64. Earlier runs on the first commit: test-exec-change-class 21/21, test-exec-gate-kind 32/32, test-test-strategy-docs 15/15, test-affected-tests 150/150, test-exec-subjects 6/6, test-exec-cr-brief-warn 19/19, test-model-stamp 35/35, test-mx-metadata-note 40/40, test-restatement-git-hooks 28/28, test-module-close 10/10, test-metrics-ledger 62/62, test-progress-report-field-fixes 106/106, test-bug12-sync 64/64, test-slim-claude-md (rc 0), test-init-autocompact 15/15, test-guide-reopen 10/10, test-dryrun-writes-nothing 6/6, test-sync-past-stock 22/22 and test-common-root-multiapp 67/67.
  • check-scripts, check-portability, the leak guard (LEAKGUARD_ALLOW_NO_DENYLIST=1 LEAKGUARD_BASE=origin/master), check-pr-discipline and render-routing --check are clean. No full suites were run.
  • Fixture edits to existing tests, both deliberate behaviour changes:
    • test-exec-gate-kind.sh K8c–e now run with MXTK_PREPUSH_BUILD=always, because the every-push build is opt-in;
    • test-test-strategy-docs.sh asserts the new CAC-5 default row and the mode-aware logic lane. Master's logic lane text is still asserted, since it is the high-quality branch.

Review: pending independent review

https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr


Generated by Claude Code

…push build and unit tests move to module close (#686)

PROJECT.md `Pipeline mode: high-speed | balanced | high-quality`; no line = balanced.
balanced: per row the exec gate + one 1280 LOOK; journeys, 390+1280 LOOK, a bounded polish
loop (85% fidelity AND a written LOOK, max 3 rounds) and unit tests at module close; the
pre-push full mxbuild once per module (hook v2). high-speed: no per-row LOOK, 75%/2.
high-quality: master's per-row cadence + pre-push build every push, 95%/5. `UI polish:`
overrides the polish numbers. Intake Q15 asks the mode. Read via new _common.sh helpers
mxtk_pipeline_mode / mxtk_ui_polish (exec.sh, module-close.sh, pre-push hook).

Field run: #686 (19 min/row average, up to 41; per-row LOOKs 0 unique catches over 42
findings). Always-loaded words 65,053 -> 65,053 (narratives moved verbatim to
process/baseline-incident-history.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
A: honour the journeys column of a CONFIRMED `## Test strategy` "Every change" row, not only
unit. B: correct the #686 defect figure (about 7 of about 10). C: sync-project.sh prints one line
when PROJECT.md has no `Pipeline mode:`. D: intake Q15 says estimated >=8 min less per row.
E: drop the dangling pointer in UI7. F: module-close.sh records its full build for the pre-push
hook; the hook also builds with no build recorded, after 10 deferred pushes, or on a Java
fingerprint change. G: an unreadable mode builds (safe side). Also: a PROJECT.md comment line no
longer reads as a `Pipeline mode:` setting.

Fixture test-module-close-cadence.sh: 50/50 (38 fail on master ee12a61).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…e polish cap; high-quality 90%/5 (#686, PR #700 experiment)

P1: mxtk_change_class marks a page script with its co-located GRANT as logic, so balanced printed
"the exec gate only" for 8 of 10 page scripts in the mode experiment. The class still decides the
tests; any page/snippet/layout statement now adds the mode's per-row page check (balanced: 1280
LOOK; high-quality: journeys + 390/1280 LOOK; high-speed: none, said so). (b) module-close.sh
writes a `Polish: n of cap` row per page (template; no gate). (c) high-quality polish default
90% / 5 rounds: a correct first-build popup reached 86% statically.

Fixture test-module-close-cadence.sh: 55/55 (42 fail on master ee12a61).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…sh, verify-model.sh and the hook (PR #700 re-review)

exec.sh recorded mxtk_java_fp of PROJECT_ROOT while the hook compared the model dir, so on a
two-tree checkout every push read as a Java change. Deeper: mxtk_java_fp ran `git hash-object
--stdin-paths` with cwd-relative names, which git resolves from the top level, so in any folder
below it the hashing failed and the fingerprint never changed. New _common.sh helper
mxtk_model_java_fp (model folder via find_model_dir) used by all three; mxtk_java_fp now lists
--full-name and hashes from the top (single-tree values unchanged). exec.sh's Java-actions probe
also looks in the model folder. Intake Q15: "an estimate from #686's field data".

Fixture test-module-close-cadence.sh: 59/59 (45 fail on master ee12a61), new T1-T3 two-tree cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants