diff --git a/CHANGELOG.md b/CHANGELOG.md index ff60ea83..649e7cbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,24 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m ## Unreleased +- learn(learned-detection-gaps, testing-shape, ui-loop): **a class on a snippet call never reaches the DOM; a 403 bounce and a widget-pinned session probe both read as sign-outs; app restarts matched a "signs me out" report by timing.** Detection-gaps row: `Class` on a `SNIPPETCALL` is green through check, exec, mxbuild, lint and DESCRIBE, and absent in the browser ([#336](https://github.com/MendixMau/mxcli-project-toolkit/issues/336)). Testing-shape §4 gains two false-red rows: `login.html?messageCode=403` with the session kept ([#338](https://github.com/MendixMau/mxcli-project-toolkit/issues/338)), and instruments pinned to one grid name or a typed menu list (1/7 and 7/34 false FAILs after a redesign, [#339](https://github.com/MendixMau/mxcli-project-toolkit/issues/339)). ui-loop: say so before a restart while a human uses the app ([#337](https://github.com/MendixMau/mxcli-project-toolkit/issues/337)) — three-app field run, 2026-10-14 +- log(bug-logs): **upstream draft for `mxcli run --local --watch`: a theme-only rebuild deletes `dist/pages` and still reports a successful reload** ([#284](https://github.com/MendixMau/mxcli-project-toolkit/issues/284)). `bug-logs/pending-github-issues/watch-theme-rebuild-drops-dist-pages.md`, status DRAFT — NOT FILED: the repro (one SCSS edit, every page 404s on `dist/pages/.js`, a restart restores all 135 bundles; twice in one app on mxcli v0.26.0 / Mendix 11.15.0), the other change classes watch serves stale, the timing table (reload 35 builds median 3.8 s, restart 44 builds median 19.2 s, max 55.2 s, 928 s total) and the upstream questions: why a restart is about 5× a reload, and whether a recreated microflow keeps its old compiled version by name. The toolkit side is already on master (exec.sh restart verdicts, `app-restart.sh`, ui-loop.md "What watch picks up"). Field evidence: the 2026-10-12 three-app UX run and its 2026-10-14 recurrence, as recorded in #284. — field retro #284, MendixMau +- fix(close-task): **`close-task.sh` resolves `checkpoint.sh` instead of printing it bare.** The script ships at `claude-hooks/bin/checkpoint.sh` and reaches `~/.claude/bin` only via the opt-in `bin/install-claude-hooks.sh --basic`, so a project without the hooks got a `checkpoint.sh --write` step it could not run. `close-task.sh` now looks in PATH, then `~/.claude/bin`, then `$MXTK_ROOT/claude-hooks/bin`, and prints the full path it found, single-quoted so a path with spaces survives copy-paste. If none is found it prints `checkpoint.sh not installed — run /bin/install-claude-hooks.sh --basic, or skip: RESUME.md is the resume source` and carries on; the exit code still means only "writes owed". `close-the-loop.md` step 4 says the same. The `~/.claude/bin` copy (`claude-hooks/bin/close-task.sh`) now prints its sibling `checkpoint.sh` by full quoted path, because `~/.claude/bin` is usually not on PATH. Fixture `tests/wave2/test-close-task-checkpoint.sh` (14 cases, including a space and `'` in HOME and the toolkit path; 11 fail on master). Field evidence: the reporter's existing-app run, where the closing agent had to skip the step with no pointer. — joshuamoesa (#358) +- new(mdl-scan): **`bin/mdl-scan.sh` flags a script that carries a scannable STOP-row pattern, inside `exec.sh` before the lock, the snapshot and the apply — warn-only until a field run** ([#349](https://github.com/MendixMau/mxcli-project-toolkit/issues/349)). Four of the fourteen live STOP rows are text patterns with a captured failure, and each is one row of `bin/lib/mdl-scan-rules.tsv`: 1 (conditional Visible/Editable inside a customContent column, BUG-18 StorageLoadException), 12 (an expression in `ContentParams`, BUG-142 CE1613), 16 (a path inside `[%CurrentUser%]`, CE0161), 26 (a quoted association path in `Attribute:`, CE1613). Output is `STOP- : → [warn|block]`; exit 0 clean, 1 a `block` hit, 3 `warn` hits only, 2 usage. Every rule ships as `warn`: exec.sh prints the hits, notes them in the BUILD-LOG row and goes on. A rule moves to `block` after one field run where it caught a real failure and flagged nothing valid (candidates 12, 16, 26; STOP-1 stays `warn`, its evidence predates v0.24.0). The `block` path is kept and tested: it refuses before anything is touched, `FORCE_EXEC`/`SKIP_CHECK` do not skip it, and the only override is the user's `MXTK_SCAN_OVERRIDE=""`, printed and logged (#348 hardens it). The other rows need model state or judgement and stay prose; 1b, 4, 18 and 24 are scannable but have no captured failure in `bug-logs/`, so they are proposed, not shipped. `learned-mdl-preflight.md` marks the enforced rows with 🔎 (+6 baseline words), and row 16's example now uses generic entity names (`Decision_Partner`, `Account_Partner`) in place of project-specific ones; the verbatim form stays in the bug log. Fixture `tests/wave2/test-mdl-scan.sh` (52 checks: each rule fires on the bad MDL captured in its bug entry and is silent on the corrected form, keys and CE text asserted verbatim in the ledger, the independent review's three false-hit forms clean, warn and block exec paths). `tests/wave2/bench-mdl-scan.sh`: 4/9 captured failures flagged, 0/19 clean files flagged, 88–260 s of applies avoidable over the corpus. Field evidence: captured bug-log output only, no live run. — external reviewer feedback (2026-10-14), independent review of #355 +- process(app-restart, test-scratch): **the mxcli-test-injection guard and the `mxcli run` pid check are one copy in `_common.sh`, not two drifting ones.** `mxtest_after_startup`, `mxtest_left` and `mxtest_refusal` were duplicated in `project-bin/app-restart.sh` and `project-bin/test-scratch.sh` (the copies differed only in a refusal branch test-scratch never takes; the tool-name argument still selects it, so each script prints exactly what it printed). `pid_is_mxcli_run` becomes `mxtk_pid_is_mxcli_run` on `mxtk_pid_args`: on Linux `run` must be a whole argument (an argument that merely contains " run " no longer counts), macOS keeps the joined `ps` line, Windows keeps the `tasklist` image check. Both scripts stop with exit 2 and the `sync-project.sh --upgrade-bin _common.sh` remedy when the installed `_common.sh` predates the move; one fixture case each. — engineering backlog review 2026-10-14 (item 6) +- new(journey-proof): **every journey names its claim and ships a control; `named-controls.js` proves the claim can fail** ([#159](https://github.com/MendixMau/mxcli-project-toolkit/issues/159)). `--positive-control` proves the harness can fail, not that the journey's own claim can: one journey passed every rung while its positive control proved 7 of 7, yet its claim held only by accident. New `project-tests/e2e/named-controls.js` (installed via `install-manifest.sh`) runs control files, named `*.control.json` so no real walk, `report-normalize.js` or `fixture-manifest.sh` picks them up as journeys (`"control": {"of", "mustFail"}`), through the runner and reads PROVEN only when the runner exits 1, every check named by `mustFail` is FAIL (at least one), and nothing else is FAIL or INVALID; a `persona` other than the run user is a FAULT and is not run. `journey-runner.js` gains `--out=` (creates its directory; control findings never overwrite the walk's) and a warn-only `WARN named control:` line for a journey with no `claim` or no `*.control.json` beside it (or in `controls/`), also written as `namedControlGaps`; no row, no exit-code change. `journey-proof.md` gains the rule and a checklist line. Fixture `tests/wave2/test-named-controls.sh` (24 cases, over the captured card-disbursement walk). Field evidence: the issue's card-disbursement build (5 of 5 named controls PROVEN after the fix, with the project's own copy); no field run of this toolkit port yet. — MendixMau (card-disbursement requirements-driven build) +- process(testing): **three check moments — every exec (~1 minute), module close (minutes), release gate before UAT or production.** Only fast checks run during development; heavy checks wait for module close or the release gate. `testing-shape.md` §3 holds the check-moment table as the single source, beside the existing lanes and re-run events; the event row "Project close / cutover" is renamed "Release gate (before UAT/production)" so the project-wide suite has one name and runs once. The every-exec row lists the chain `exec.sh` already runs. Checks not built yet are marked `planned: #N`: the static STOP-row scan (#349), the Studio Pro warning ratchet (#351) and human-locked baselines (#348) for every exec; new page/action has a test (#354) and per-module server-side security tests (#352) at module close; page × role smoke on the production DB engine (#353), the full security report (#352) and language/RTL layout (#354) at the release gate. The runbook's Stage 6 paragraph points at it in one line. Field evidence: none yet. — MendixMau (from external reviewer feedback) +- fix(dg2-patterns, detection-gaps, obligation-check): **a DataGrid 2 column re-attributed by `ALTER PAGE … REPLACE` is named as a whole-page re-emit, and the old-shots LOOK FAULT names its remedy** ([#356](https://github.com/MendixMau/mxcli-project-toolkit/issues/356), [#357](https://github.com/MendixMau/mxcli-project-toolkit/issues/357)). #356: the REPLACE form passed `check --references` and failed mxbuild (v0.26.0, Mendix 11.15.0); `learned-dg2-patterns.md` rule 1 now covers replace/re-attribute, plus a detection-gaps row, a preflight gotcha pointer and `BUG-DRAFT-dg2-replace-column-attribute-passes-check` (upstream candidate: `check` should reject it as it rejects the SET form). #357 change 1: after a partial re-take, the "screenshots older than their section's VALID AT" FAULT now says to keep the section and append an addendum with its own `VALID AT:` and PROOF-OF-LOOK lines; `close-the-loop.md` routes re-shot pages there; `test-obligation-valid-at.sh` T5 asserts the text. Per-page staleness (#357 change 2) is not implemented. — joshuamoesa (#356, #357) +- learn(microflow-patterns): **the microflow header grammar, in one line.** `create microflow M.Name ()` keeps its parentheses even with no parameters, `returns` follows them, and placement is `folder '…'` (not `in`). The wrong shape fails `check` with three errors that point at `returns` and `List of` instead of the missing `()`, which cost a check cycle on a field build (mxcli v0.26.0, Mendix 11.15.0). Upstream draft for a clearer message: `BUG-DRAFT-microflow-header-grammar`. (#361) — joshuamoesa, from an existing-app connector project +- learn(lint): **known answers for four stock-rule findings on the toolkit's own conventions, and four modelling-error rule candidates.** New table in `lint-that-actually-runs.md`, with one entry for each finding. QUAL004 on a `DS_` flow used only as a Data grid 2 datasource (seen on v0.26.0 although the rule counts `datasource` refs) is accepted into the baseline with a reason, never renamed or given a fake caller (#360, BUG-DRAFT-qual004-dg2-datasource). QUAL004 on `TEST_` helpers and CUSTOM002 on `TEST_`/`GET_`/`PRS_`/`STUB_` are handled by baselining them with a reason, or (CUSTOM002) by `enabled: false` recorded in `PROJECT.md` (#241). QUAL002 on import mappings and published REST services, which MDL cannot document (MDL089), is handled by the rule's existing per-kind options (`check_import_mappings`, `check_published_rest_services`; #246). `process-coherence-pass.md` warns against deleting either QUAL004 kind. Upstream drafts for all three are in `bug-logs/pending-github-issues/`. `process/lint-backlog.md` Batch 7 adds the four missing #169 candidates (rows 51–54), and its inbox file is promoted and deleted. — joshuamoesa (#360), MendixMau (#241, #246, #169) +- fix(test-agent): **the test-agent brief now names `journeys/` as a write location** ([#359](https://github.com/MendixMau/mxcli-project-toolkit/issues/359)). The brief confined writes to `{{TEST_DIR}}` (filled as `tests/e2e/`), while `verify-module.sh`, `fixture-manifest.sh`, `journey-proof.md` and the Stage-6 artifact gate read journeys from `${JOURNEY_DIR}` (default `journeys/`). A test-agent that obeyed its brief wrote `tests/e2e/journeys/*.journey.json`, which the gate never found, so the journey row stayed owed until the project added a root symlink. The brief and its workflow step now send journeys to `journeys/.journey.json` at the project root and say not to relocate them (`fixture-manifest.sh` reads only `journeys/`, whatever `JOURNEY_DIR` says); `agent-roles.md` says nothing fills `{{TEST_DIR}}` and how to fill it. A project's already-completed `test-agent.md` keeps its old text: `bin/sync-project.sh --diff-completed` shows the change to copy in. No auto-fill: `init-agents.sh` and `sync-project.sh`'s pristine-stub comparison would both have to apply it, and the test directory is a project fact. Field evidence: an existing-app project (Ollama connector app, macOS) whose journey row cleared only after `ln -s tests/e2e/journeys journeys`. — joshuamoesa +- learn(design-spacing, module-review): **breadcrumbs are links, and the LOOK asks "can I get back?" and "does every tile go somewhere?"** ([#232](https://github.com/MendixMau/mxcli-project-toolkit/issues/232), the still-open part). `design-spacing.md` §3 prescribed the page-header crumb as one plain-text `dynamictext`, so every wireframe and page copied it and `page-fidelity.js` scored the text crumb as correct. The scaffold now builds the crumb the way the StyleGallery does (`crumbs` container, `btn-link` link buttons, `crumb-sep` separators), with the current page and non-page sections as text, plus a rule that each crumb's target is in the wireframe's binding table, that an ancestor still open underneath is reached by close page (pointer to `examples/stylegallery/.../18-breadcrumbs.mdl`), and that a stepper says per step whether it is navigable. `module-review.md` §4c (the one LOOK question list) gains the two questions, the second pointing at `wiring-sweep.md` for the does-it-do-anything half. The ✅ example in `oneshot-page-structure-patterns.md` (on-demand) now shows a linked crumb. Baseline: +131 words (75,028 → 75,159). Field evidence: an 81-script Stage-5 build where every gate went green and a user found text-only crumbs and a menu entry opening one record instead of a list. — MendixMau +- fix(sync-project, review-module, conformance-check): **the last eight `producer | grep -q` pipelines in these three scripts are here-strings, so pipefail cannot turn a match into a miss.** `grep -q` exits on its first match; a producer still writing then dies of SIGPIPE and, under `set -o pipefail`, the pipeline reports failure. `review-module.sh` read `SHOW MODULES` this way, and on a large app that output is over one pipe buffer, so a real module could be reported unknown. Five sites in `sync-project.sh` (one a `| head -1 | grep -q` chain), one in `review-module.sh`, two in `conformance-check.sh` (one short line each, cannot race; changed so the static check stays absolute). Same 1:1 rewrite as #328/#329. `test-pipefail-grep-q.sh` T3 now covers the three files (15 scripts); against master it lists all eight sites. Field evidence: none yet. — r-eng-backlog item 2, MendixMau +- process(context): **stories and old measurements move out of four always-loaded skills; baseline 75,028 → 74,223 words (−805).** Verbatim moves into `process/baseline-incident-history.md` (new sections UI1–UI4, TO1–TO3, MF1, MP1), each leaving a one-line pointer: `ui-preflight-pages.md` (the 2026-09-09 design-reach story, the ToeicBuddy fidelity numbers, the fidelity-vs-checklist table, the repeated 2026-08-31 A/B paragraph), `tool-output-is-not-ground-truth.md` (the three 2026-07-29 instance narratives, the `$?` instance table, the Part 3 workflow story), `microflow-preflight.md` (`mxcli layout` probe history) and `learned-mdl-preflight.md` (#879 re-probe numbers). Rules, STOP rows and their numbers, denominators and one-line failure examples stay in place. New `bin/check-moved-text.sh` makes the #315 line-by-line review mechanical: every removed line must be found with `grep -F` in its new home (83 of 83 here). It exits 2 on a source with no diff or on 0 removed lines (never "0 of 0" green), lists lines under 15 characters as trivially found, and has a fixture (`tests/wave2/test-check-moved-text.sh`). Behaviour A/B still owed before merge. — T4 of the stub-diet research report +- new(app-facts): **the app map's loop sweep is incremental, and `bin/status.sh` says how stale the map is** ([#297](https://github.com/MendixMau/mxcli-project-toolkit/issues/297)). Every run re-described every loop-containing microflow: on a 17,500-unit app that was 1,917 describes in 2 h 45 at 6 workers (about 31 s each, not the 1-2 s `app-analysis.md` promised), while about 80 units had changed between rounds. `manifest.json` now stores a `{UnitID: ContentsHash}` snapshot of the `.mpr` Unit table and an `mdl_index` recording the unit state each `mdl/` file was described from. A refresh describes only candidates whose hash, module or folder changed, or that have no output, keeps the rest and deletes outputs of removed or loop-free units. The sweep is full on the first run, with `--full`, after an mxcli version change, or when the Unit table cannot be read (MPR v1); `sections.loops.sweep` says which and why. UnitID-to-name mapping is `mx-metadata-note.py units`, the Studio Pro History note's own walk, refactored into a shared `Model` base for git revisions and the working tree. The hash is computed from the `.mxunit` bytes in the column's format, so a Unit-table row that lags its file cannot hide a change; disagreements are counted in the manifest. `bin/status.sh` prints `app map: N units changed since ` or `app map: fresh` when a map exists (`app-facts.sh --freshness`, no mxcli needed; the unit list goes through a temp file, never an env var, so it works past ~1,600 units), and nothing otherwise. A `--skip-loops` run keeps the last loop sweep's snapshot, so a stale loop map never reads fresh. `app-analysis.md` gives the measured cost by app size and names `--full` as the remedy after edits made outside Studio Pro. Fixture `tests/wave2/test-app-facts-incremental.sh`, 108 checks on both layouts with captured template units and a stub mxcli: all describes on a first run, 0 when nothing changed, 1 for a changed, moved, or stale-row unit, a removed unit's output deleted, a failed describe retried, a renamed module re-described under its new name, `--freshness` and status.sh on 2,101 units, a `--skip-loops` run not reading fresh, outputs byte-identical to `--full` and to a fresh full run, and the status line; 78 checks fail against master. `tests/wave2/bench-app-sweep.sh`, 2,000 units with 1 % changed: 20 describes incremental against 2,000 full, and `--freshness` reads fresh / 20 changed / fresh. Follow-up, not built: skip re-hashing unchanged `.mxunit` files by mtime + size. Field evidence: no field run; checked on synthetic and captured fixtures only. Not built: replacing describe with catalog queries (mxcli v0.25 `activities` with loop depth), NEEDS-FIELD-DATA, a real v0.25 catalog capture to check parity with the parser. — jopterhorst (#297) +- fix(app-facts): **the catalog is opened through an escaped read-only URI, so a model folder with `#` or `?` in its name no longer breaks step 2 or leaves a stray file beside the project.** Step 2 built `"file:%s?mode=ro" % DB` from the raw path; `#` and `?` end the path in a URI, so `mode=ro` was dropped and SQLite created an empty database at the truncated path (a zero-byte `a ` beside `a #2/`) and opened that. On master that ends in a Python traceback (`no such table: modules_data`, exit 2) after the shell's catalog check had passed. The URI now comes from new `mxtk_sqlite_ro_uri` in `_common.sh`, the `pathname2url` escaping `mxtk_model_version` already did, which now calls the same helper. Step 2 also refuses with a FAULT line when the catalog it opened has 0 tables, rather than writing facts from it, and with a `_common.sh` that predates the helper (or none) app-facts builds the same `pathname2url` URI inline, as its `require_py`/`find_mpr` fallbacks already do, passing the path through `native_path` when that exists. Fixture `tests/wave2/test-app-facts-db-uri.sh` (10 cases: folders `a #2`, `b?c` and a plain control, the URI shape, the 0-table refusal, and `a #2` beside an older `_common.sh` without the helper); 8 fail on master, and the older-`_common.sh` case fails on the previous revision of this fix, which refused. Windows (MSYS path conversion of the env var) is unchanged and unverified. Field evidence: none yet. — r-eng-backlog item 3, MendixMau +- chore(context): **a routing row can be on demand for one role: `mdl?` in the agents column — mdl at Stage 5 reads 43,599 → 30,647 words, review 32,181 → 30,265.** The tier column is per row, so a row one role needs every session and another needs once per module was baseline for both. The `?` token keeps a baseline row in that role's stub, among the on-demand rows (trigger only, still findable), and out of its always-read set and word count; the Role(s) column names it (`review (mdl: on demand)`, `every role except mdl, review (on demand for those)`), and the agent audit refuses a `?` on a non-baseline row or beside its own plain token. Applied for mdl to module-review, ui-loop, teamserver-alignment, learned-css-that-never-applied and skills-over-scripts; for review to teamserver-alignment and skills-over-scripts. Baseline total unchanged (73,233). Behaviour A/B (5 reps × 2 tasks + 5 probes × 3 reps, sonnet): trimmed pack no worse than master on every scored metric — planted STOP refused 10/10 each arm, mxcli-check parse failures page 2/5 vs 3/5, retrieval probes 15/15. — context-cost research (r-stub-diet, T1) +- chore(context): **helpers read the runbook slice for their stage, and the runbook no longer carries a copy of the baseline table — mdl at Stage 5 reads 56,236 → 43,599 words; baseline 75,028 → 73,233.** `skills/conversion-runbook.md` held a fourth rendered copy of the baseline routing table (README, ROUTING.md and every project's CLAUDE.local.md carry it too); it is out of the render surfaces and replaced by a one-line pointer. New `bin/runbook-slice.sh ` prints §1b plus that stage's section, using the span logic gate-check already had (now `bin/lib/runbook-span.sh`, shared). The runbook routing row is split: lead, ba and architect keep the runbook; mdl, gate, test and review get the slice, and `render-routing.sh --check` counts the slice's printed words for them, so the per-role numbers stay honest. New `tests/bench/trim-noloss.sh` checks that every removed line is found again or is a still-routed table row. Behaviour A/B (5 reps × 2 tasks + 5 probes × 3 reps, sonnet): trimmed pack no worse than master on every scored metric — planted STOP refused 10/10 each arm, mxcli-check parse failures page 2/5 vs 3/5, retrieval probes 15/15. — context-cost research (r-stub-diet, T2+T3) - process(build): **Stage 5 drafts the ready set in parallel and applies serially.** The runbook dispatch row said "sequential within a module", which serialised drafting even for independent scripts; it now points to `iterative-build-loop.md` → Gate cost, the single statement of the rule. The unit is the build-plan row: every `BUILD` row whose "Depends on" rows are all applied, minus overlapping writers (two rows that replace, alter or drop+create the same document never share a ready set, and no row is drafted while it overlaps a queued unapplied one; `brd-to-build-plan.md` has Produces name every document a row writes), runs at once, its scripts in order, one `mdl-agent` per script. Applies stay one at a time through `bin/exec.sh`, and the ready set is recomputed after each. A queued draft made before a sibling apply landed is re-checked with `mxcli diff --format struct` (preflight STOP rule 23); any removal line (checked against `describe` per BUG-146) or STOP rule 21 target means re-draft, never patch, because a stale restatement of a shared document is exactly rule 21's silent seed-call loss and passes `check --references`. Report `ready set N, drafted N, applied k`; the `mdl-agent` template says a drafter never applies and never depends on an unapplied draft. Field evidence: none yet. — MendixMau - fix(platform-link): **CLI commits now show their changed documents in Studio Pro History.** Studio Pro's History → Model changes pane reads a `ModelChanges` array from the commit's `refs/notes/mx_metadata` note. Trap 6c's hand-written template set it to `[]`, so every mxcli/agent commit showed "Model changes 0" (field project: 270 of 270 notes empty). New `project-bin/mx-metadata-note.py` (stdlib only) computes the array from the commit's changed `.mxunit` files, in Studio Pro's entry shape: Status, UnitID, UnitType, UnitName and Module (walked up the `.mpr` Unit tree). It has `changes` / `write` / `backfill` / `sync ` subcommands. `install-project-hooks.sh` now also installs post-commit and post-rewrite hooks that write the note and never block, and `--check` covers them, so `sync-project.sh` rolls them out to wired projects. Notes are published with `mx-metadata-note.py sync ` after the branch push. Push config is never touched. With mxcli v0.26+ in the project root the hooks call `mxcli git note --write` instead (mendixlabs/mxcli#1337); the Python script is the fallback for older pins, and on 160 field commits both wrote the same 3,594 entries. Trap 6c rewritten. Measured against Studio Pro's own notes, 51 commits in 5 apps: 3387/3389 entries exact on all five fields. Field run on a 357-commit Mendix 11.15.0 app: 158 notes backfilled and pushed, and Studio Pro History lists the documents per revision. Review fixes: an earlier revision added `HEAD` + the notes ref to `remote..push`. It was dropped because any push refspec replaces `push.default`. A branch tracking a differently named upstream got a new same-named branch on Team Server where `simple` refuses, a missing local notes ref failed the whole push, and nobody knows whether Studio Pro's own push reads that config. The installer now only fetches the remote's notes (non-forced). It removes that exact `HEAD` + notes pair if present, and leaves and reports any other entry. `sync` handles a remote with no notes ref (it used to exit 1) and retries a rejected push once after re-merging. A note that already lists changes (Studio Pro's) is never overwritten by `write`/`backfill` without `--force`, and `sync` keeps a colleague's non-empty note over ours. The mxcli probe greps `git note --help` for `--write`. Hooks and docs call the script through `resolve_py`. Fixture `tests/wave2/test-mx-metadata-note.sh` (40 cases, over real captured template units; single- and two-tree layouts, amend, backfill and `--force`, push exit codes identical to a no-hooks clone, leftover-refspec cleanup, sync to an empty remote and its retry, notes fetch at install, a diverged notes merge; green under `init.defaultBranch` master and main). — field project (Mendix 11.15 app), MendixMau - learn(fixture-seeding): **explore the data structure first, choose rows by use case, scan actions for resets before any run.** Two seeding runs on public template apps: seeding raised journey passes 33 -> 58 and 20 -> 51 of the same sets (FAIL 93 -> 68, 163 -> 132; INVALID unchanged), and a "Reset all demodata" journey wiped accounts so 104 of 127 journeys reported `The username or password you entered is incorrect`, identical on empty and seeded databases. Skill gains three questions a seed must answer, a use-case checklist, a destructive-action scan, a generic traps table with symptoms, measured expectations and a denominator checklist; routing trigger now fires on seed/test/demo data and empty pages. Independent review fixes: object ids follow the Mendix bigint pattern with a business-key guard (no uuids), associations are seeded where the page's path stores them, four new traps (enum names, inheritance chain, calculated attributes, view/external entities), and the port-offset and container-label traps moved to `harness-architecture.md`. — MendixMau diff --git a/README.md b/README.md index 618fe870..ff32756a 100644 --- a/README.md +++ b/README.md @@ -771,11 +771,12 @@ Then filter by Role(s). A helper dispatched with a role stub (ba, architect, mdl | Always relevant for | Reference this | Stage(s) | Role(s) | |---|---|---|---| -| Before writing any .js or .sh for a check, gate or report — and before adding a rule to an existing one: judgement goes in a skill, code only fetches facts a reader cannot | `skills/skills-over-scripts.md` | every stage | every role | +| Before writing any .js or .sh for a check, gate or report — and before adding a rule to an existing one: judgement goes in a skill, code only fetches facts a reader cannot | `skills/skills-over-scripts.md` | every stage | every role except mdl, review (on demand for those) | | Any pass whose input is missing, stale or unresolvable — before recording UNMEASURED, N/A or a silent skip: name what was missing, say what you assessed against instead, still deliver a verdict | `skills/degrade-to-judgement.md` | every stage | every role | | Any time an exit code, a tool's output or a subagent's report is about to become a stated finding — verify before you conclude | `skills/tool-output-is-not-ground-truth.md` | every stage | every role | | Before obeying any learned-* STOP or workaround that costs a detour — probe the binary you actually have, then stamp the verdict back into the rule | `skills/retesting-learned-rules.md` | every stage | every role | -| Any pipeline work at all — every session, before producing any stage artifact: read §1b plus your own stage's section, not the whole file (gate-check.sh prints the line spans); READMEs and the guide are orientation only | `skills/conversion-runbook.md` | P,0,1,2,3,4,5,6,7 | every role | +| Any pipeline work at all — every session, before producing any stage artifact: read §1b plus your own stage's section, not the whole file (gate-check.sh prints the line spans); READMEs and the guide are orientation only | `skills/conversion-runbook.md` | P,0,1,2,3,4,5,6,7 | lead, ba, architect | +| Any pipeline work at all, as a helper — every dispatch, before producing anything: run the toolkit's bin/runbook-slice.sh , which prints §1b plus your stage's section of the conversion runbook; never open skills/conversion-runbook.md whole | `bin/runbook-slice.sh` | P,0,1,2,3,4,5,6,7 | mdl, gate, test, review | | Any question before asking the user or writing anything — query the model, then read the source, then ask the human, in that order | `skills/query-the-model.md` | P,0,1,2,3,4,5,6,7 | every role | | Putting a question TO the user — any gate, any stage: ask in chat not in a file, two named options plus your recommendation, one batch per gate then end the turn | `skills/interview-protocol.md` | P,0,1,2,3,4,5,6,7 | lead, ba, architect | | The user gives feedback or asks for a change mid-project — "change X", "it also needs Y", a remark on a screenshot or the running app, a stakeholder's ask relayed in chat: log a CR row in PROJECT.md ## Change log BEFORE any edit, size it, then edit the highest artifact it alters (BRD, decision, architecture, wireframe), never the MDL first | `skills/change-intake.md` | 2,3,4,5,6,7 | lead, ba | @@ -796,20 +797,20 @@ Then filter by Role(s). A helper dispatched with a role stub (ba, architect, mdl | Writing a microflow with any loop, a retrieve/commit/call inside a loop, nested or multiple loops, >20 activities counting loop bodies, or a list built from a list — post the checklist before the first MDL line | `skills/microflow-preflight.md` | 5 | mdl | | Building any page or snippet — before the first widget. Wireframe, tokens, gallery reuse, cross-check; no wireframe means STOP | `skills/ui-preflight-pages.md` | 5 | mdl | | Writing or reviewing any page or snippet — the spacing scale (8/16/24/32/48), section rhythm, and the page-header scaffold every full page starts with; sections at 0px apart and pages with no H1 are the defects it retires | `skills/design-spacing.md` | 5 | mdl, review | -| After every page-building script, and any time the UI looks wrong — the cheap repeatable look during the build: one page, one screenshot, four questions, scored when a wireframe exists. Feeds Gate: UI, never replaces it | `skills/ui-loop.md` | 5 | mdl, review | +| After every page-building script, and any time the UI looks wrong — the cheap repeatable look during the build: one page, one screenshot, four questions, scored when a wireframe exists. Feeds Gate: UI, never replaces it | `skills/ui-loop.md` | 5 | review (mdl: on demand) | | After the FIRST build that follows any design-system port, and before any page is built on it — reads the BUILT stylesheet and reports how many framework knobs point at a design token, how many tokens arrived, how many component classes arrived, each with its denominator. Measured on a real run: 55 tokens ported correctly into the right file, 0 of 35 knobs bound and 0 of 20 classes present, two build phases shipped in the framework's default blue with mx check, mxcli lint, the MDL suite and two e2e journeys all green | `project-bin/check-design-reaches-app.sh` | 3,5 | architect, mdl, gate, review | | Before exec'ing ANY page script — compares the drafted MDL's shell against the wireframe's: page column, layout/nav shell, one H1. Measured 0/10 pages on a real first build, repaired wholesale 47 scripts later | `project-bin/check-page-shell.sh` | 5 | mdl, gate, review | | After drafting and again after exec'ing any page script — scores the page MDL (or `mxcli describe` output on stdin) against its wireframe: headings/actions/content/classes, weighted. The scored companion to check-page-shell's binary gate; 32% median measured without it, 90% first-draft with it. Every run is appended to the project's docs/PAGE-FIDELITY.tsv — first non-stub row per page = first-build score of record vs the ≥80% target (forward-reference stubs score with --stub, exempt) | `project-bin/page-fidelity.js` | 5 | mdl, gate, review | -| Reviewing any module before calling it done — the ONE pass: build, gate, prove, LOOK (is it logical, does it look right, does it match our design, over every page not just the tested ones), confirm with the denominator stated | `skills/module-review.md` | 5,6 | mdl, review, test | +| Reviewing any module before calling it done — the ONE pass: build, gate, prove, LOOK (is it logical, does it look right, does it match our design, over every page not just the tested ones), confirm with the denominator stated | `skills/module-review.md` | 5,6 | review, test (mdl: on demand) | | Before any mxcli exec / exec.sh / --mcp write — ask or run? the knob decides | `bin/exec-approval.sh` | 5,6 | mdl, gate | | Before calling any module tested, or before re-running tests after a change — what testing a module means, which re-runs a change owes (UI-only vs logic lane), and the false-green register of confirmed ways a test reports green over a broken feature | `skills/testing-shape.md` | 5,6 | test, gate, review | | Finishing any module — before calling it done. One command that runs every instrument and keeps "instrument faulted" apart from "feature failed"; in a wired project run the installed copy at bin/verify-module.sh | `project-bin/verify-module.sh` | 5,6 | mdl, gate, test, review | -| A style change that appears to have done nothing, a focus ring that paints on every page landing, or an app still grey after a design port every instrument called green — the three ways a correct rule paints nothing (matches nothing / matches chrome / loses the cascade), the two reads that tell them apart, and the class that arrived in the stylesheet and is bound to no widget | `skills/learned-css-that-never-applied.md` | 5,6 | mdl, review, gate | +| A style change that appears to have done nothing, a focus ring that paints on every page landing, or an app still grey after a design port every instrument called green — the three ways a correct rule paints nothing (matches nothing / matches chrome / loses the cascade), the two reads that tell them apart, and the class that arrived in the stylesheet and is bound to no widget | `skills/learned-css-that-never-applied.md` | 5,6 | review, gate (mdl: on demand) | | Before trusting a green check/exec/DESCRIBE result as proof, or when a runtime symptom appears over a fully green model — the register of constructs that pass early rungs and fail later ones | `skills/learned-detection-gaps.md` | 5,6 | mdl, gate, review | | Creating any entity, or calling a module security-ready — entity and grants land in one script, and ready means SHOW SECURITY MATRIX proves it | `skills/security-is-not-a-later-script.md` | 5 | mdl, gate, review | | A CE error or behavior that looks like a known mxcli quirk rather than a modeling mistake — `bin/bug-lookup.sh CE0117` / `BUG-102` / "keyword" prints the matching ledger entries, so the session reads one entry, not the 32k-word ledger | `bin/bug-lookup.sh` | 5,6 | mdl, gate | | The first command of every session, and any time someone asks "where are we" or "what next" — one screen: stage, done/overdue, the ONE next action, from the instruments, never from memory | `bin/status.sh` | P,0,1,2,3,4,5,6,7 | every role | -| Any session that will push a model to Mendix Team Server, and BEFORE telling the user a Team Server push is blocked — which remote is authoritative, settle-then-push order, and the four checks that have to fail first | `skills/teamserver-alignment.md` | 5,6,7 | mdl, gate, review | +| Any session that will push a model to Mendix Team Server, and BEFORE telling the user a Team Server push is blocked — which remote is authoritative, settle-then-push order, and the four checks that have to fail first | `skills/teamserver-alignment.md` | 5,6,7 | gate (mdl, review: on demand) | **Why this has to be explicit instead of implicit:** a project's own skill files are usually written before a given toolkit learning exists, or before a new one is added later — they never grow a cross-reference to it on their own. When you `git pull` this toolkit and it brings in a new baseline-worthy skill (most often a new `learned-*.md`), update every consuming project's routing to match — don't assume the next session will find it by chance. diff --git a/ROUTING.md b/ROUTING.md index c99f42a7..2d8c9d15 100644 --- a/ROUTING.md +++ b/ROUTING.md @@ -32,9 +32,10 @@ picks the row up. That is the whole procedure — there is no second list to rem | Always relevant for | Load this | Agent(s) | Stage(s) | Tier | |---|---|---|---|---| -| Any pipeline work at all — every session, before producing any stage artifact: read §1b plus your own stage's section, not the whole file (gate-check.sh prints the line spans); READMEs and the guide are orientation only | `skills/conversion-runbook.md` | all | P,0,1,2,3,4,5,6,7 | baseline | +| Any pipeline work at all — every session, before producing any stage artifact: read §1b plus your own stage's section, not the whole file (gate-check.sh prints the line spans); READMEs and the guide are orientation only | `skills/conversion-runbook.md` | lead,ba,architect | P,0,1,2,3,4,5,6,7 | baseline | +| Any pipeline work at all, as a helper — every dispatch, before producing anything: run the toolkit's bin/runbook-slice.sh , which prints §1b plus your stage's section of the conversion runbook; never open skills/conversion-runbook.md whole | `bin/runbook-slice.sh` | mdl,gate,test,review | P,0,1,2,3,4,5,6,7 | baseline | | Any question before asking the user or writing anything — query the model, then read the source, then ask the human, in that order | `skills/query-the-model.md` | all | P,0,1,2,3,4,5,6,7 | baseline | -| Before writing any .js or .sh for a check, gate or report — and before adding a rule to an existing one: judgement goes in a skill, code only fetches facts a reader cannot | `skills/skills-over-scripts.md` | all | - | baseline | +| Before writing any .js or .sh for a check, gate or report — and before adding a rule to an existing one: judgement goes in a skill, code only fetches facts a reader cannot | `skills/skills-over-scripts.md` | all,mdl?,review? | - | baseline | | Any pass whose input is missing, stale or unresolvable — before recording UNMEASURED, N/A or a silent skip: name what was missing, say what you assessed against instead, still deliver a verdict | `skills/degrade-to-judgement.md` | all | - | baseline | | Putting a question TO the user — any gate, any stage: ask in chat not in a file, two named options plus your recommendation, one batch per gate then end the turn | `skills/interview-protocol.md` | lead,ba,architect | P,0,1,2,3,4,5,6,7 | baseline | | The user gives feedback or asks for a change mid-project — "change X", "it also needs Y", a remark on a screenshot or the running app, a stakeholder's ask relayed in chat: log a CR row in PROJECT.md ## Change log BEFORE any edit, size it, then edit the highest artifact it alters (BRD, decision, architecture, wireframe), never the MDL first | `skills/change-intake.md` | lead,ba | 2,3,4,5,6,7 | baseline | @@ -120,7 +121,7 @@ picks the row up. That is the whole procedure — there is no second list to rem | Building any page or snippet — before the first widget. Wireframe, tokens, gallery reuse, cross-check; no wireframe means STOP | `skills/ui-preflight-pages.md` | mdl | 5 | baseline | | Writing or reviewing any page or snippet — the spacing scale (8/16/24/32/48), section rhythm, and the page-header scaffold every full page starts with; sections at 0px apart and pages with no H1 are the defects it retires | `skills/design-spacing.md` | mdl,review | 5 | baseline | | Building or using the in-app design gallery | `skills/learned-stylegallery.md` | mdl | 5 | ondemand | -| After every page-building script, and any time the UI looks wrong — the cheap repeatable look during the build: one page, one screenshot, four questions, scored when a wireframe exists. Feeds Gate: UI, never replaces it | `skills/ui-loop.md` | mdl,review | 5 | baseline | +| After every page-building script, and any time the UI looks wrong — the cheap repeatable look during the build: one page, one screenshot, four questions, scored when a wireframe exists. Feeds Gate: UI, never replaces it | `skills/ui-loop.md` | mdl?,review | 5 | baseline | | Before porting ds.css into SCSS, and at the Stage-3 gate — greps the stylesheet for rules that cannot match the HTML Mendix emits (rem against the real root, table/th/td selectors, positional row selectors). mx check, mxcli check and mxcli lint are all blind to CSS | `project-bin/check-design-portability.sh` | architect,mdl,gate | 3,5 | ondemand | | After the FIRST build that follows any design-system port, and before any page is built on it — reads the BUILT stylesheet and reports how many framework knobs point at a design token, how many tokens arrived, how many component classes arrived, each with its denominator. Measured on a real run: 55 tokens ported correctly into the right file, 0 of 35 knobs bound and 0 of 20 classes present, two build phases shipped in the framework's default blue with mx check, mxcli lint, the MDL suite and two e2e journeys all green | `project-bin/check-design-reaches-app.sh` | architect,mdl,gate,review | 3,5 | baseline | | Before exec'ing ANY page script — compares the drafted MDL's shell against the wireframe's: page column, layout/nav shell, one H1. Measured 0/10 pages on a real first build, repaired wholesale 47 scripts later | `project-bin/check-page-shell.sh` | mdl,gate,review | 5 | baseline | @@ -130,13 +131,13 @@ picks the row up. That is the whole procedure — there is no second list to rem | Designing the brand and ONE ANNOTATED WIREFRAME PER SCREEN before building pages — the design system alone is half the deliverable | `skills/design-artifacts.md` | architect | 3 | ondemand | | Stage 3, after the role model is CONFIRMED and BEFORE THE FIRST WIREFRAME — and any time a page shows ONE object, a menu item or home page is added, or a data source retrieves the current/first record: role homes, inboxes, and per page how it is reached and which object it takes as parameter | `skills/interaction-model.md` | lead,ba,architect | 3 | ondemand | | Before a wireframe or a design commits to a WIDGET, when a requirement shows a widget to one role and not another, and when a page script hits a parse error that looks like a syntax mistake: the short list of things MDL cannot write at all, and the four-minute probe that answers it at Stage 3 instead of at build time | `skills/learned-mdl-cannot-express.md` | architect,mdl | 3,5 | ondemand | -| A style change that appears to have done nothing, a focus ring that paints on every page landing, or an app still grey after a design port every instrument called green — the three ways a correct rule paints nothing (matches nothing / matches chrome / loses the cascade), the two reads that tell them apart, and the class that arrived in the stylesheet and is bound to no widget | `skills/learned-css-that-never-applied.md` | mdl,review,gate | 5,6 | baseline | +| A style change that appears to have done nothing, a focus ring that paints on every page landing, or an app still grey after a design port every instrument called green — the three ways a correct rule paints nothing (matches nothing / matches chrome / loses the cascade), the two reads that tell them apart, and the class that arrived in the stylesheet and is bound to no widget | `skills/learned-css-that-never-applied.md` | mdl?,review,gate | 5,6 | baseline | #### Build — the loop itself | Always relevant for | Load this | Agent(s) | Stage(s) | Tier | |---|---|---|---|---| -| Reviewing any module before calling it done — the ONE pass: build, gate, prove, LOOK (is it logical, does it look right, does it match our design, over every page not just the tested ones), confirm with the denominator stated | `skills/module-review.md` | mdl,review,test | 5,6 | baseline | +| Reviewing any module before calling it done — the ONE pass: build, gate, prove, LOOK (is it logical, does it look right, does it match our design, over every page not just the tested ones), confirm with the denominator stated | `skills/module-review.md` | mdl?,review,test | 5,6 | baseline | | 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 | @@ -146,7 +147,7 @@ picks the row up. That is the whole procedure — there is no second list to rem | 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 | | 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` | mdl,gate,test | 5 | ondemand | | At project birth (before the first build script) and any time a model needs a platform home: creating the Team Server app, adopting an existing GitHub-born model into it without rewriting history, or deploying; also when the Platform SDK returns 403, git rejects the PAT, a deploy cannot be triggered from a PAT, or the app turns out to be a Free App | `skills/platform-link.md` | all | P,5,6,7 | ondemand | -| Any session that will push a model to Mendix Team Server, and BEFORE telling the user a Team Server push is blocked — which remote is authoritative, settle-then-push order, and the four checks that have to fail first | `skills/teamserver-alignment.md` | mdl,gate,review | 5,6,7 | baseline | +| Any session that will push a model to Mendix Team Server, and BEFORE telling the user a Team Server push is blocked — which remote is authoritative, settle-then-push order, and the four checks that have to fail first | `skills/teamserver-alignment.md` | mdl?,gate,review? | 5,6,7 | baseline | | Promoting an app to a deployed sandbox or cloud node, or before a customer tests a deployment — "it works locally" is not evidence about a deployment; also when a test suite fails only against the remote URL | `skills/deploy-to-sandbox.md` | all | 7 | ondemand | #### Build · MDL — the language and tool reference diff --git a/agents/architect-agent.md b/agents/architect-agent.md index 767880f5..7257cbd8 100644 --- a/agents/architect-agent.md +++ b/agents/architect-agent.md @@ -29,7 +29,7 @@ You own architecture and build-plan decisions for {{PROJECT}}. Hard rule: you ne Open a file when its When cell happens in your task, not all of them at the start. Below the baseline rows, a When cell is only the trigger; the file says the rest. A page task never opens the microflow rows; a microflow task never opens the page rows. -This table is your whole list. The baseline table in the project's CLAUDE.local.md is shared with the main session and the other helpers: skip every row there whose Role(s) cell does not say *every role* or name architect. +This table is your whole list. The baseline table in the project's CLAUDE.local.md is shared with the main session and the other helpers: skip every row there whose Role(s) cell does not say *every role* or name architect. A cell that names architect as *on demand* is a row this table lists below the baseline rows, by its trigger. | Load this | When | |---|---| diff --git a/agents/ba-agent.md b/agents/ba-agent.md index 0e15e852..91af7929 100644 --- a/agents/ba-agent.md +++ b/agents/ba-agent.md @@ -34,7 +34,7 @@ You run discovery and the interview gates for {{PROJECT}}. You never touch the ` Open a file when its When cell happens in your task, not all of them at the start. Below the baseline rows, a When cell is only the trigger; the file says the rest. A page task never opens the microflow rows; a microflow task never opens the page rows. -This table is your whole list. The baseline table in the project's CLAUDE.local.md is shared with the main session and the other helpers: skip every row there whose Role(s) cell does not say *every role* or name ba. +This table is your whole list. The baseline table in the project's CLAUDE.local.md is shared with the main session and the other helpers: skip every row there whose Role(s) cell does not say *every role* or name ba. A cell that names ba as *on demand* is a row this table lists below the baseline rows, by its trigger. | Load this | When | |---|---| diff --git a/agents/gate-agent.md b/agents/gate-agent.md index b7da6484..7c2993c6 100644 --- a/agents/gate-agent.md +++ b/agents/gate-agent.md @@ -26,11 +26,11 @@ own bug log before running anything you have not run here before. Open a file when its When cell happens in your task, not all of them at the start. Below the baseline rows, a When cell is only the trigger; the file says the rest. A page task never opens the microflow rows; a microflow task never opens the page rows. -This table is your whole list. The baseline table in the project's CLAUDE.local.md is shared with the main session and the other helpers: skip every row there whose Role(s) cell does not say *every role* or name gate. +This table is your whole list. The baseline table in the project's CLAUDE.local.md is shared with the main session and the other helpers: skip every row there whose Role(s) cell does not say *every role* or name gate. A cell that names gate as *on demand* is a row this table lists below the baseline rows, by its trigger. | Load this | When | |---|---| -| `skills/conversion-runbook.md` | Any pipeline work at all — every session, before producing any stage artifact: read §1b plus your own stage's section, not the whole file (gate-check.sh prints the line spans); READMEs and the guide are orientation only | +| `bin/runbook-slice.sh` | Any pipeline work at all, as a helper — every dispatch, before producing anything: run the toolkit's bin/runbook-slice.sh , which prints §1b plus your stage's section of the conversion runbook; never open skills/conversion-runbook.md whole | | `skills/query-the-model.md` | Any question before asking the user or writing anything — query the model, then read the source, then ask the human, in that order | | `skills/skills-over-scripts.md` | Before writing any .js or .sh for a check, gate or report — and before adding a rule to an existing one: judgement goes in a skill, code only fetches facts a reader cannot | | `skills/degrade-to-judgement.md` | Any pass whose input is missing, stale or unresolvable — before recording UNMEASURED, N/A or a silent skip: name what was missing, say what you assessed against instead, still deliver a verdict | diff --git a/agents/mdl-agent.md b/agents/mdl-agent.md index 1aa9e31b..7f4250b6 100644 --- a/agents/mdl-agent.md +++ b/agents/mdl-agent.md @@ -32,13 +32,12 @@ a rule below names an asset (e.g. "the wireframe", "the brief"), it means the pa Open a file when its When cell happens in your task, not all of them at the start. Below the baseline rows, a When cell is only the trigger; the file says the rest. A page task never opens the microflow rows; a microflow task never opens the page rows. -This table is your whole list. The baseline table in the project's CLAUDE.local.md is shared with the main session and the other helpers: skip every row there whose Role(s) cell does not say *every role* or name mdl. +This table is your whole list. The baseline table in the project's CLAUDE.local.md is shared with the main session and the other helpers: skip every row there whose Role(s) cell does not say *every role* or name mdl. A cell that names mdl as *on demand* is a row this table lists below the baseline rows, by its trigger. | Load this | When | |---|---| -| `skills/conversion-runbook.md` | Any pipeline work at all — every session, before producing any stage artifact: read §1b plus your own stage's section, not the whole file (gate-check.sh prints the line spans); READMEs and the guide are orientation only | +| `bin/runbook-slice.sh` | Any pipeline work at all, as a helper — every dispatch, before producing anything: run the toolkit's bin/runbook-slice.sh , which prints §1b plus your stage's section of the conversion runbook; never open skills/conversion-runbook.md whole | | `skills/query-the-model.md` | Any question before asking the user or writing anything — query the model, then read the source, then ask the human, in that order | -| `skills/skills-over-scripts.md` | Before writing any .js or .sh for a check, gate or report — and before adding a rule to an existing one: judgement goes in a skill, code only fetches facts a reader cannot | | `skills/degrade-to-judgement.md` | Any pass whose input is missing, stale or unresolvable — before recording UNMEASURED, N/A or a silent skip: name what was missing, say what you assessed against instead, still deliver a verdict | | `skills/module-brief.md` | Building any module — before the first script. The mdl-agent's single per-module input | | `skills/learned-mdl-preflight.md` | Writing ANY MDL script — before the first line. Step 0 picks the write mode, then the STOP table overrides it for corrupting operations | @@ -47,26 +46,25 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/microflow-preflight.md` | Writing a microflow with any loop, a retrieve/commit/call inside a loop, nested or multiple loops, >20 activities counting loop bodies, or a list built from a list — post the checklist before the first MDL line | | `skills/ui-preflight-pages.md` | Building any page or snippet — before the first widget. Wireframe, tokens, gallery reuse, cross-check; no wireframe means STOP | | `skills/design-spacing.md` | Writing or reviewing any page or snippet — the spacing scale (8/16/24/32/48), section rhythm, and the page-header scaffold every full page starts with; sections at 0px apart and pages with no H1 are the defects it retires | -| `skills/ui-loop.md` | After every page-building script, and any time the UI looks wrong — the cheap repeatable look during the build: one page, one screenshot, four questions, scored when a wireframe exists. Feeds Gate: UI, never replaces it | | `project-bin/check-design-reaches-app.sh` | After the FIRST build that follows any design-system port, and before any page is built on it — reads the BUILT stylesheet and reports how many framework knobs point at a design token, how many tokens arrived, how many component classes arrived, each with its denominator. Measured on a real run: 55 tokens ported correctly into the right file, 0 of 35 knobs bound and 0 of 20 classes present, two build phases shipped in the framework's default blue with mx check, mxcli lint, the MDL suite and two e2e journeys all green | | `project-bin/check-page-shell.sh` | Before exec'ing ANY page script — compares the drafted MDL's shell against the wireframe's: page column, layout/nav shell, one H1. Measured 0/10 pages on a real first build, repaired wholesale 47 scripts later | | `project-bin/page-fidelity.js` | After drafting and again after exec'ing any page script — scores the page MDL (or `mxcli describe` output on stdin) against its wireframe: headings/actions/content/classes, weighted. The scored companion to check-page-shell's binary gate; 32% median measured without it, 90% first-draft with it. Every run is appended to the project's docs/PAGE-FIDELITY.tsv — first non-stub row per page = first-build score of record vs the ≥80% target (forward-reference stubs score with --stub, exempt) | -| `skills/module-review.md` | Reviewing any module before calling it done — the ONE pass: build, gate, prove, LOOK (is it logical, does it look right, does it match our design, over every page not just the tested ones), confirm with the denominator stated | | `bin/exec-approval.sh` | Before any mxcli exec / exec.sh / --mcp write — ask or run? the knob decides | | `project-bin/verify-module.sh` | Finishing any module — before calling it done. One command that runs every instrument and keeps "instrument faulted" apart from "feature failed"; in a wired project run the installed copy at bin/verify-module.sh | | `skills/tool-output-is-not-ground-truth.md` | Any time an exit code, a tool's output or a subagent's report is about to become a stated finding — verify before you conclude | -| `skills/learned-css-that-never-applied.md` | A style change that appears to have done nothing, a focus ring that paints on every page landing, or an app still grey after a design port every instrument called green — the three ways a correct rule paints nothing (matches nothing / matches chrome / loses the cascade), the two reads that tell them apart, and the class that arrived in the stylesheet and is bound to no widget | | `skills/learned-detection-gaps.md` | Before trusting a green check/exec/DESCRIBE result as proof, or when a runtime symptom appears over a fully green model — the register of constructs that pass early rungs and fail later ones | | `skills/security-is-not-a-later-script.md` | Creating any entity, or calling a module security-ready — entity and grants land in one script, and ready means SHOW SECURITY MATRIX proves it | | `bin/bug-lookup.sh` | A CE error or behavior that looks like a known mxcli quirk rather than a modeling mistake — `bin/bug-lookup.sh CE0117` / `BUG-102` / "keyword" prints the matching ledger entries, so the session reads one entry, not the 32k-word ledger | | `bin/status.sh` | The first command of every session, and any time someone asks "where are we" or "what next" — one screen: stage, done/overdue, the ONE next action, from the instruments, never from memory | -| `skills/teamserver-alignment.md` | Any session that will push a model to Mendix Team Server, and BEFORE telling the user a Team Server push is blocked — which remote is authoritative, settle-then-push order, and the four checks that have to fail first | | `skills/retesting-learned-rules.md` | Before obeying any learned-* STOP or workaround that costs a detour — probe the binary you actually have, then stamp the verdict back into the rule | +| `skills/skills-over-scripts.md` | Before writing any .js or .sh for a check, gate or report | | `skills/checkpoints/checkpoint-build.md` | CAC-5, after design sign-off and before the build plan | | `skills/image-transcription.md` | Images to read listed by images-to-md or the documents index | | `skills/mendix-best-practices-index.md` | Asked "is there a Mendix best practice for this", or mapping a lint rule that rose in the ratchet back to the practice and the skill that prevents it | | `skills/learned-stylegallery.md` | Building or using the in-app design gallery | +| `skills/ui-loop.md` | After every page-building script, and any time the UI looks wrong | | `project-bin/check-design-portability.sh` | Before porting ds.css into SCSS, and at the Stage-3 gate | +| `skills/module-review.md` | Reviewing any module before calling it done | | `project-bin/context-pack.sh` | Dispatching a build step to a helper | | `bug-logs/mxcli-bugs.md` | Reading a whole class of tool defects (a retest, a new mxcli release, an audit) | | `skills/cloud-dev-environment.md` | Setting up or resuming an mxcli project in a cloud/ephemeral container | @@ -104,12 +102,14 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/anonymize-client-app-for-demo.md` | Turning a client-derived Mendix app into a clean, shareable demo with zero client fingerprint | | `skills/field-run.md` | Driving the whole toolkit pipeline on a real source to find what the written skills don't say | | `skills/learned-mdl-cannot-express.md` | Before a wireframe or a design commits to a WIDGET, when a requirement shows a widget to one role and not another, and when a page script hits a parse error that looks like a syntax mistake: the short list of things MDL cannot write at all, and the four-minute probe that answers it at Stage 3 instead of at build time | +| `skills/learned-css-that-never-applied.md` | A style change that appears to have done nothing, a focus ring that paints on every page landing, or an app still grey after a design port every instrument called green | | `skills/learned-dg2-patterns.md` | Building or altering any data grid | | `skills/learned-file-upload-widget.md` | Putting any file upload / attachment / document field on a page, an uploader page failing mx check with CE0463, or an uploader DESCRIBE that will not re-execute | | `skills/scriptable-sp-verification.md` | Needing Studio Pro load evidence without a human at the GUI | | `skills/learned-local-db-confusion.md` | A runtime test reads/writes data that then is not there, or vice versa | | `skills/walking-skeleton.md` | Stage 5 start, before the first module of any entry mode | | `skills/platform-link.md` | At project birth (before the first build script) and any time a model needs a platform home: creating the Team Server app, adopting an existing GitHub-born model into it without rewriting history, or deploying; also when the Platform SDK returns 403, git rejects the PAT, a deploy cannot be triggered from a PAT, or the app turns out to be a Free App | +| `skills/teamserver-alignment.md` | Any session that will push a model to Mendix Team Server, and BEFORE telling the user a Team Server push is blocked | | `skills/deploy-to-sandbox.md` | Promoting an app to a deployed sandbox or cloud node, or before a customer tests a deployment | | `skills/learned-constants-and-secrets.md` | Adding a constant, installing a marketplace module, deploying to a new environment, or needing a Mendix PAT | | `project-bin/constants-audit.sh` | Before any first deploy to a new environment, and after installing or updating any marketplace module | diff --git a/agents/review-agent.md b/agents/review-agent.md index 02245473..9cc01424 100644 --- a/agents/review-agent.md +++ b/agents/review-agent.md @@ -32,13 +32,12 @@ protects: you have no Write or Edit tool, and you never run `mxcli exec`. Open a file when its When cell happens in your task, not all of them at the start. Below the baseline rows, a When cell is only the trigger; the file says the rest. A page task never opens the microflow rows; a microflow task never opens the page rows. -This table is your whole list. The baseline table in the project's CLAUDE.local.md is shared with the main session and the other helpers: skip every row there whose Role(s) cell does not say *every role* or name review. +This table is your whole list. The baseline table in the project's CLAUDE.local.md is shared with the main session and the other helpers: skip every row there whose Role(s) cell does not say *every role* or name review. A cell that names review as *on demand* is a row this table lists below the baseline rows, by its trigger. | Load this | When | |---|---| -| `skills/conversion-runbook.md` | Any pipeline work at all — every session, before producing any stage artifact: read §1b plus your own stage's section, not the whole file (gate-check.sh prints the line spans); READMEs and the guide are orientation only | +| `bin/runbook-slice.sh` | Any pipeline work at all, as a helper — every dispatch, before producing anything: run the toolkit's bin/runbook-slice.sh , which prints §1b plus your stage's section of the conversion runbook; never open skills/conversion-runbook.md whole | | `skills/query-the-model.md` | Any question before asking the user or writing anything — query the model, then read the source, then ask the human, in that order | -| `skills/skills-over-scripts.md` | Before writing any .js or .sh for a check, gate or report — and before adding a rule to an existing one: judgement goes in a skill, code only fetches facts a reader cannot | | `skills/degrade-to-judgement.md` | Any pass whose input is missing, stale or unresolvable — before recording UNMEASURED, N/A or a silent skip: name what was missing, say what you assessed against instead, still deliver a verdict | | `skills/design-spacing.md` | Writing or reviewing any page or snippet — the spacing scale (8/16/24/32/48), section rhythm, and the page-header scaffold every full page starts with; sections at 0px apart and pages with no H1 are the defects it retires | | `skills/ui-loop.md` | After every page-building script, and any time the UI looks wrong — the cheap repeatable look during the build: one page, one screenshot, four questions, scored when a wireframe exists. Feeds Gate: UI, never replaces it | @@ -53,8 +52,8 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/learned-detection-gaps.md` | Before trusting a green check/exec/DESCRIBE result as proof, or when a runtime symptom appears over a fully green model — the register of constructs that pass early rungs and fail later ones | | `skills/security-is-not-a-later-script.md` | Creating any entity, or calling a module security-ready — entity and grants land in one script, and ready means SHOW SECURITY MATRIX proves it | | `bin/status.sh` | The first command of every session, and any time someone asks "where are we" or "what next" — one screen: stage, done/overdue, the ONE next action, from the instruments, never from memory | -| `skills/teamserver-alignment.md` | Any session that will push a model to Mendix Team Server, and BEFORE telling the user a Team Server push is blocked — which remote is authoritative, settle-then-push order, and the four checks that have to fail first | | `skills/retesting-learned-rules.md` | Before obeying any learned-* STOP or workaround that costs a detour — probe the binary you actually have, then stamp the verdict back into the rule | +| `skills/skills-over-scripts.md` | Before writing any .js or .sh for a check, gate or report | | `skills/image-transcription.md` | Images to read listed by images-to-md or the documents index | | `skills/mendix-best-practices-index.md` | Asked "is there a Mendix best practice for this", or mapping a lint rule that rose in the ratchet back to the practice and the skill that prevents it | | `project-bin/assemble-prototype.js` | After every wireframe edit: assembles design/wireframes/*.html into design/prototype.html, one hash-routed page a stakeholder can click through instead of twenty separate files. Generated, never edited (design-artifacts.md Step 3) | @@ -102,6 +101,7 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/handoff-to-studio-pro.md` | Handing a headless-built model to a person | | `skills/preview-over-hub-tunnel.md` | Exposing a container-run app at a public URL (mxcli run --hub) | | `skills/platform-link.md` | At project birth (before the first build script) and any time a model needs a platform home: creating the Team Server app, adopting an existing GitHub-born model into it without rewriting history, or deploying; also when the Platform SDK returns 403, git rejects the PAT, a deploy cannot be triggered from a PAT, or the app turns out to be a Free App | +| `skills/teamserver-alignment.md` | Any session that will push a model to Mendix Team Server, and BEFORE telling the user a Team Server push is blocked | | `skills/deploy-to-sandbox.md` | Promoting an app to a deployed sandbox or cloud node, or before a customer tests a deployment | | `skills/wizard-walkthrough.md` | Handing the human a batch of steps only they can perform (Stage 7 cutover, browser-only GitHub settings) | diff --git a/agents/test-agent.md b/agents/test-agent.md index b9fb108d..7ba42246 100644 --- a/agents/test-agent.md +++ b/agents/test-agent.md @@ -12,11 +12,14 @@ tools: Read, Grep, Glob, Bash, Write, Edit You author and run e2e test specs for {{PROJECT}}'s running UI. -**The boundary that matters:** you may create and edit files under {{TEST_DIR}}. You may NEVER -edit MDL, never touch the `.mpr`, never run `mxcli exec` or any `--mcp` write — those are model -changes and are out of scope regardless of what a test failure seems to call for. If a test -proves the model is wrong, that is a finding you report, not a fix you make. `mxcli -c "SHOW ..."` -and `"DESCRIBE ..."` reads are always fine, and are how you ground every name you use. +**The boundary that matters:** you may create and edit files under {{TEST_DIR}}, and journey +files (`.journey.json`) under `journeys/` at the project root. Do not relocate journeys: +`verify-module.sh`, `fixture-manifest.sh` and the Stage-6 artifact gate read them there, and a +journey under {{TEST_DIR}} stays owed. You may NEVER edit MDL, never touch the `.mpr`, never run +`mxcli exec` or any `--mcp` write — those are model changes and are out of scope regardless of +what a test failure seems to call for. If a test proves the model is wrong, that is a finding +you report, not a fix you make. `mxcli -c "SHOW ..."` and `"DESCRIBE ..."` reads are always +fine, and are how you ground every name you use. ## Skills — open each one when its trigger fires Open a file when its When cell happens in your task, not all of them at the start. Below the baseline rows, a When cell is only the trigger; the file says the rest. A page task never opens the microflow rows; a microflow task never opens the page rows. -This table is your whole list. The baseline table in the project's CLAUDE.local.md is shared with the main session and the other helpers: skip every row there whose Role(s) cell does not say *every role* or name test. +This table is your whole list. The baseline table in the project's CLAUDE.local.md is shared with the main session and the other helpers: skip every row there whose Role(s) cell does not say *every role* or name test. A cell that names test as *on demand* is a row this table lists below the baseline rows, by its trigger. | Load this | When | |---|---| -| `skills/conversion-runbook.md` | Any pipeline work at all — every session, before producing any stage artifact: read §1b plus your own stage's section, not the whole file (gate-check.sh prints the line spans); READMEs and the guide are orientation only | +| `bin/runbook-slice.sh` | Any pipeline work at all, as a helper — every dispatch, before producing anything: run the toolkit's bin/runbook-slice.sh , which prints §1b plus your stage's section of the conversion runbook; never open skills/conversion-runbook.md whole | | `skills/query-the-model.md` | Any question before asking the user or writing anything — query the model, then read the source, then ask the human, in that order | | `skills/skills-over-scripts.md` | Before writing any .js or .sh for a check, gate or report — and before adding a rule to an existing one: judgement goes in a skill, code only fetches facts a reader cannot | | `skills/degrade-to-judgement.md` | Any pass whose input is missing, stale or unresolvable — before recording UNMEASURED, N/A or a silent skip: name what was missing, say what you assessed against instead, still deliver a verdict | @@ -174,7 +177,8 @@ assert *which* user you are logged in as, not merely that login succeeded. ## Workflow 1. Take the module/scenario you were given (or the BRD/brief, if none was specified). 2. Ground every selector, microflow and entity name per "Grounding a new spec" above. -3. Write or extend the spec under {{TEST_DIR}}, following the established rung shape. +3. Write or extend the spec under {{TEST_DIR}}, following the established rung shape; a journey + goes to `journeys/.journey.json`. 4. Syntax-check the file before the first real run. 5. Run it, and cross-check state-changing results against the database. 6. If a page isn't reachable via nav, check grants before rewriting the nav path. diff --git a/bin/check-moved-text.sh b/bin/check-moved-text.sh new file mode 100755 index 00000000..74273691 --- /dev/null +++ b/bin/check-moved-text.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# check-moved-text.sh — prove a "move, never delete" trim lost no line. +# +# WHY. CLAUDE.md "Always-loaded skill text — the word budget": room in the baseline is made by +# MOVING stories, examples and old numbers to an on-demand file, never by deleting the rules. +# The first such trim (#315) was checked by hand, line by line. This makes the mechanical half of +# that review a command: every line the diff removes from the source files must still exist, +# verbatim, in the file it moved to. What it cannot judge is whether a line that stayed behind +# should have — that half stays with the reviewer. +# +# bin/check-moved-text.sh [--base ] ... +# bin/check-moved-text.sh process/baseline-incident-history.md skills/ui-preflight-pages.md +# +# Compares the working tree against merge-base(, HEAD) (default ref origin/master), so it +# runs the same before and after the commit. A removed line counts as found when `grep -F` finds +# it (trailing whitespace trimmed) anywhere in the destination's working-tree copy. Blank lines +# are not counted. Each line not found is printed for hand review — a line rewritten in place +# (a sentence kept, its neighbour moved) shows up here, and the PR says why. +# +# LIMIT. `grep -F` matches substrings, and a line that occurs twice in the sources is satisfied +# by one copy in the destination. So a very short line (`|---|`, "```", "---") is found almost +# anywhere and proves nothing. Lines under 15 characters are listed separately as "trivially +# found, review by hand"; they count as found, but the reviewer reads them. +# +# A named source with no diff (a mistyped path, or a file nothing was removed from) exits 2, +# and so does a run that removed 0 lines in total: "0 of 0 found" is not a pass. +# +# Field run: the T4 stub-diet trim, 2026-10-14 (four baseline skills into +# process/baseline-incident-history.md). +# +# Exit: 0 every removed line found · 1 some not found, listed · 2 could not run, a source with +# no diff, or nothing removed at all. +set -u + +BASE="origin/master" +if [ "${1:-}" = "--base" ]; then BASE="${2:-}"; shift 2; fi +DEST="${1:-}" +[ -n "$DEST" ] && [ "$#" -ge 2 ] || { grep '^# bin/check-moved-text.sh' "$0" | sed 's/^# *//' >&2; exit 2; } +shift +[ -f "$DEST" ] || { echo "check-moved-text: destination '$DEST' not found." >&2; exit 2; } +MB="$(git merge-base "$BASE" HEAD 2>/dev/null)" || { + echo "check-moved-text: no merge base with '$BASE' (fetch it first)." >&2; exit 2; } + +TOTAL=0; FOUND=0; MISSING=""; SHORT=""; NODIFF="" +for SRC in "$@"; do + REMOVED="$(git diff --no-color -U0 "$MB" -- "$SRC" | awk ' + /^--- (a\/|\/dev\/null)/ { next } + /^-/ { print substr($0, 2) }')" + if [ -z "$(printf '%s' "$REMOVED" | tr -d '[:space:]')" ]; then + NODIFF="${NODIFF} ${SRC} +" + continue + fi + while IFS= read -r line; do + t="$(printf '%s' "$line" | sed 's/[[:space:]]*$//')" + [ -n "$(printf '%s' "$t" | tr -d '[:space:]')" ] || continue + TOTAL=$((TOTAL + 1)) + if grep -F -q -- "$t" "$DEST"; then + FOUND=$((FOUND + 1)) + if [ "${#t}" -lt 15 ]; then SHORT="${SHORT} ${SRC}: ${t} +"; fi + else + MISSING="${MISSING} ${SRC}: ${t} +" + fi + done <&2 + printf '%s' "$NODIFF" >&2 + exit 2 +fi +if [ "$TOTAL" -eq 0 ]; then + echo "check-moved-text: 0 removed lines in total — nothing was checked, which is not a pass." >&2 + exit 2 +fi +echo "check-moved-text: $FOUND of $TOTAL removed non-blank line(s) found verbatim in $DEST." +if [ -n "$SHORT" ]; then + echo "Trivially found (under 15 characters, a substring match proves little — review by hand):" + printf '%s' "$SHORT" +fi +if [ -n "$MISSING" ]; then + echo "Not found (review by hand; each needs a reason in the PR):" + printf '%s' "$MISSING" + exit 1 +fi +exit 0 diff --git a/bin/check-skill-routing.sh b/bin/check-skill-routing.sh index 11dbbb93..a20a2725 100755 --- a/bin/check-skill-routing.sh +++ b/bin/check-skill-routing.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Proves the skill-routing table actually reaches an agent, not just that it renders. # -# render-routing.sh --check already proves the nine in-repo surfaces match the table. What it +# render-routing.sh --check already proves the in-repo surfaces match the table. What it # does NOT catch: two rows sharing a name (one silently shadows the other in agent: views), # a [[wikilink]] between skills pointing at a file that was never written, or — the part that # actually matters — whether bin/lib/skill-routing.sh's routing_sync_claude_local() genuinely @@ -62,7 +62,7 @@ while IFS= read -r -d '' f; do done < <(find "$ROOT/skills" -name '*.md' -print0) [ "$dangling" -eq 0 ] && ok "no dangling cross-references" || FAIL=1 -echo "== 5. the nine in-repo surfaces match the table ==" +echo "== 5. the in-repo surfaces match the table ==" if "$SCRIPT_DIR/render-routing.sh" --check >/tmp/ckroute.render 2>&1; then ok "README/ROUTING.md/agent templates/gate-check.sh all in sync" else diff --git a/bin/gate-check.sh b/bin/gate-check.sh index 61f648b3..77b06c21 100755 --- a/bin/gate-check.sh +++ b/bin/gate-check.sh @@ -2379,12 +2379,10 @@ fi # grepping the runbook's actual headings ("### Stage N — …", "## 1b. …") on every run — never # a hard-coded number, which would be wrong after the next edit. The runbook's own "how to # read this" paragraph states the same rule; this line is what makes it actionable. -runbook_span() { # runbook_span — "A–B" (1-based, inclusive) or "" - awk -v h="$2" -v e="$3" ' - !start && $0 ~ h { start = NR; next } - start && $0 ~ e { end = NR - 1; exit } - END { if (start) { if (!end) end = NR; printf "%d–%d", start, end } }' "$1" 2>/dev/null -} +# The span logic lives in lib/runbook-span.sh, shared with bin/runbook-slice.sh (which prints +# these same lines for a helper that never runs a gate) and with the per-role word count in +# lib/skill-routing.sh. +. "$TOOLKIT_DIR/bin/lib/runbook-span.sh" if [ -n "$REQUESTED_STAGE" ]; then RB_FILE="$TOOLKIT_DIR/skills/conversion-runbook.md" case "$REQUESTED_STAGE" in @@ -2395,8 +2393,8 @@ if [ -n "$REQUESTED_STAGE" ]; then # Keyed on the " — " every stage heading carries: "### Stage 0 runs in every entry mode" # (under Entry Modes) also starts with "### Stage 0", and matched first without it. RB_HEAD="$(grep -m1 "^### Stage $RB_STAGE — " "$RB_FILE" | sed 's/^### //; s/[[:space:]]*$//')" - RB_SPAN="$(runbook_span "$RB_FILE" "^### Stage $RB_STAGE — " "^###? ")" - RB_1B="$(runbook_span "$RB_FILE" "^## 1b\\." "^## ")" + RB_SPAN="$(runbook_stage_span "$RB_FILE" "$RB_STAGE")" + RB_1B="$(runbook_1b_span "$RB_FILE")" if [ -n "$RB_HEAD" ] && [ -n "$RB_SPAN" ] && [ -n "$RB_1B" ]; then printf 'Read for this gate: skills/conversion-runbook.md §"%s" (lines %s) + §1b Live Checklist (lines %s) — not the whole file\n' \ "$RB_HEAD" "$RB_SPAN" "$RB_1B" diff --git a/bin/init-project.sh b/bin/init-project.sh index 6523aabc..cabed48e 100755 --- a/bin/init-project.sh +++ b/bin/init-project.sh @@ -309,7 +309,8 @@ This project uses the shared toolkit at \`$TOOLKIT_ROOT\`. For ANY pipeline work 1. **Read \`$TOOLKIT_ROOT/skills/conversion-runbook.md\` §1b plus your own stage's section FIRST — every session; not the whole file** (\`bin/gate-check.sh \` prints the line spans). It is the executable spec. The toolkit README and toolkit-guide.html are orientation only; stage - summaries anywhere are routing, not deliverable lists. + summaries anywhere are routing, not deliverable lists. A helper agent (mdl, gate, test, review) + runs \`$TOOLKIT_ROOT/bin/runbook-slice.sh \` instead, which prints exactly those lines. 2. **Before producing any stage artifact, open that stage's owning skill** and follow its full output list (e.g. Stage 3 design work = \`design-artifacts.md\`, incl. one wireframe per screen — not just a design system). diff --git a/bin/lib/install-manifest.sh b/bin/lib/install-manifest.sh index f33a52b0..17a02a98 100644 --- a/bin/lib/install-manifest.sh +++ b/bin/lib/install-manifest.sh @@ -190,7 +190,7 @@ MXTK_LINT_RULES_NOINSTALL="README.md STOCK-HASHES.txt" # # project.config.template.js is listed separately because it is the one file a project is # EXPECTED to edit — installing it over an edited copy would silently revert the port. -MXTK_PROJECT_TESTS="config.js helpers.js settle.js otel.js journey-runner.js journey-runner.selftest.js journey-rung4-scope.test.js monkey.js monkey.selftest.js page-audit.js page-audit-rules.js design-audit.js full-app-walkthrough.js report-normalize.js report-render.js review-report.js example.journey.json" +MXTK_PROJECT_TESTS="config.js helpers.js settle.js otel.js journey-runner.js named-controls.js journey-runner.selftest.js journey-rung4-scope.test.js monkey.js monkey.selftest.js page-audit.js page-audit-rules.js design-audit.js full-app-walkthrough.js report-normalize.js report-render.js review-report.js example.journey.json" MXTK_PROJECT_TESTS_TEMPLATE="project.config.template.js" # Deliberately not installed. Same contract as the lists above. diff --git a/bin/lib/mdl-scan-rules.tsv b/bin/lib/mdl-scan-rules.tsv new file mode 100644 index 00000000..88cefa0f --- /dev/null +++ b/bin/lib/mdl-scan-rules.tsv @@ -0,0 +1,24 @@ +# mdl-scan-rules.tsv — one row per STOP row of skills/learned-mdl-preflight.md that a text pattern +# identifies. Read by bin/mdl-scan.sh. Columns, TAB-separated: +# id STOP-, the preflight row number +# kind line ERE on the comment-stripped, lowercased line (strings kept) +# customcontent ERE on the same line with string literals blanked, only inside a +# datagrid column body whose header says ShowContentAs: customContent +# contentparams built-in: a ContentParams value that is not a bare attribute, +# an unquoted association path or a quoted literal (pattern unused) +# severity warn exec.sh prints the hit and goes on; the BUILD-LOG row notes it +# block exec.sh refuses (MXTK_SCAN_OVERRIDE="" is the user's override) +# A rule ships as `warn`. It goes to `block` only after ONE field run in which it +# caught a real failure and flagged nothing valid, cited on the commit that flips +# it. Candidates: STOP-12, STOP-16, STOP-26. STOP-1 stays `warn`: its evidence +# predates v0.24.0, where `mx check` is 0 errors and Studio Pro load is untested. +# pattern lowercase ERE (POSIX awk; no \b, no lookaround) +# message what the hit is +# fix the rewrite +# evidence the bug-log entry holding the captured failure; its fixture is +# tests/wave2/fixtures/mdl-scan/.{bad,good}.mdl (`-- evidence:` lines) +# A rule with no captured failure in bug-logs/ does not get a row (issue #349). +STOP-1 customcontent warn (visible|editable)[[:space:]]*:[[:space:]]*\[ conditional Visible/Editable inside a datagrid customContent column (StorageLoadException on Studio Pro open) set it in Studio Pro by hand, or bind dynamictext (Attribute: "State") BUG-18 +STOP-12 contentparams warn - ContentParams value is an expression; it is stored as an attribute path (CE1613 at mxbuild) bind a bare attribute ({1} = Attr) or a quoted literal; make a computed label a String attribute BUG-142 +STOP-16 line warn \[%currentuser/ path inside the [%CurrentUser%] token in an XPath constraint (CE0161) keep the token bare and walk the whole path on the left: [Mod.A_B/Mod.B/Mod.B_Account = '[%CurrentUser%]'] bug-logs/mxcli-bugs.md "Any GRANT/REVOKE exec in a module fails once ANY entity there has an Account_Supplier-style cross-module XPath" +STOP-26 line warn attribute[[:space:]]*:[[:space:]]*"[^"]*/[^"]*" quoted association path in Attribute: is stored as one attribute named "Assoc/Attr" (CE1613) write the path unquoted: Attribute: Assoc_A_B/Name BUG-DRAFT-dg2-association-path-bindings diff --git a/bin/lib/obligation-check.sh b/bin/lib/obligation-check.sh index af0c17ba..7bc04164 100755 --- a/bin/lib/obligation-check.sh +++ b/bin/lib/obligation-check.sh @@ -405,7 +405,9 @@ _ob_look_proof_broken() { local why="" [ -n "$missing" ] && why="cited screenshots not on disk:$missing" [ -n "$small" ] && why="${why:+$why; }cited screenshots under 10KB (blank/placeholder):$small" - [ -n "$oldshots" ] && why="${why:+$why; }screenshots older than their section's VALID AT (a previous review's shots):$oldshots" + # The remedy is named in the message (#357): after a partial re-take, agents read this FAULT as + # "re-shoot the whole module" or "accept the FAULT", because nothing pointed at the addendum form. + [ -n "$oldshots" ] && why="${why:+$why; }screenshots older than their section's VALID AT (a previous review's shots):$oldshots — partial re-take? keep the old section and its original stamp (restore it if bumped); append an addendum (VALID AT: + PROOF-OF-LOOK for the re-shot pages) — module-review.md stage 5" if [ -n "$claimed" ] && [ "$n_cited" -lt "$claimed" ]; then why="${why:+$why; }$n_cited citation(s) for $claimed reviewed page(s)" fi diff --git a/bin/lib/runbook-span.sh b/bin/lib/runbook-span.sh new file mode 100644 index 00000000..b187d080 --- /dev/null +++ b/bin/lib/runbook-span.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# runbook-span.sh — locate the runbook sections a stage session needs, by heading, never by a +# hard-coded line number. Source it; do not execute it. +# +# Callers: bin/gate-check.sh (the "Read for this gate" line), bin/runbook-slice.sh (prints the +# lines), bin/lib/skill-routing.sh (counts the slice's words for the per-role budget lines). +# One copy, so the three never disagree about where a section starts or ends. +# +# What a stage session needs is §1b (the Live Checklist Protocol, which applies to every stage) +# plus its own stage's section under §2. A helper reading through bin/runbook-slice.sh also gets +# the gate-verdict table and §3b's ownership paragraph (runbook_verdicts_span, runbook_3b_owner_span). +# Stage headings are keyed on the " — " every one carries: +# "### Stage 0 runs in every entry mode" (under Entry Modes) also starts with "### Stage 0", and +# matched first without it. +# +# bash 3.2 (stock macOS) and POSIX awk. + +# runbook_span — "A–B" (1-based, inclusive) or "" +runbook_span() { + awk -v h="$2" -v e="$3" ' + !start && $0 ~ h { start = NR; next } + start && $0 ~ e { end = NR - 1; exit } + END { if (start) { if (!end) end = NR; printf "%d–%d", start, end } }' "$1" 2>/dev/null +} + +# runbook_stage_token — P, 0-7 as the runbook spells them ("build-ready" reads Stage +# 5's section; "p" is "P"). Prints nothing for anything else. +runbook_stage_token() { + case "$1" in + build-ready) echo "5" ;; + p|P) echo "P" ;; + 0|1|2|3|4|5|6|7) echo "$1" ;; + *) : ;; + esac +} + +# runbook_stage_span — the "A–B" span of "### Stage N — …" +runbook_stage_span() { runbook_span "$1" "^### Stage $2 — " "^###? "; } +# runbook_1b_span — the "A–B" span of "## 1b. …" +runbook_1b_span() { runbook_span "$1" "^## 1b\\." "^## "; } + +# runbook_span_through — "A–B" from the start line through +# the first line matching (inclusive), or through the line before the next heading +# if the anchor is gone, so a reworded anchor shortens the span instead of running to EOF. +runbook_span_through() { + awk -v h="$2" -v a="$3" ' + !start && $0 ~ h { start = NR; next } + start && $0 ~ a { end = NR; exit } + start && /^#/ { end = NR - 1; exit } + END { if (start) { if (!end) end = NR; printf "%d–%d", start, end } }' "$1" 2>/dev/null +} + +# Two spans every helper needs whatever its stage (review of PR #378): +# runbook_verdicts_span — the gate-verdict table under "### What a gate verdict means" +# (PASS/PENDING/FAIL/WAIVED/MANUAL and their exit codes), through "An informational run …". +runbook_verdicts_span() { runbook_span_through "$1" "^### What a gate verdict means" "^An informational run"; } +# runbook_3b_owner_span — §3b's "Ownership is split" paragraph and its bullets ("a helper +# that hits a BRD-touching decision reports it to the lead"), through the next blank line. +runbook_3b_owner_span() { + awk ' + !in3b && /^## 3b\./ { in3b = 1; next } + in3b && /^## / { exit } + in3b && !start && /^\*\*Ownership is split/ { start = NR; next } + start && /^[[:space:]]*$/ { end = NR - 1; exit } + END { if (start) { if (!end) end = NR; printf "%d–%d", start, end } }' "$1" 2>/dev/null +} + +# runbook_print_span <"A–B"> — the lines themselves. +runbook_print_span() { + local a b + a="${2%%–*}"; b="${2##*–}" + [ -n "$a" ] && [ -n "$b" ] || return 1 + sed -n "${a},${b}p" "$1" +} diff --git a/bin/lib/skill-routing.sh b/bin/lib/skill-routing.sh index c0f3dbd8..da534835 100644 --- a/bin/lib/skill-routing.sh +++ b/bin/lib/skill-routing.sh @@ -4,7 +4,6 @@ # Every routing surface in this repo is a VIEW of that table, rendered by this library: # ROUTING.md full (human-readable index) # README.md readme-baseline + readme-situational -# skills/conversion-runbook.md readme-baseline # agents/-agent.md agent: # bin/gate-check.sh stage-map (shell case arms) # /CLAUDE.local.md baseline (written by init-project.sh / sync-project.sh) @@ -36,16 +35,56 @@ _routing_has() { return 1 } -# The Role(s) cell of the baseline view: "all" reads as "every role", a list as "lead, mdl". +# ── Per-role on-demand token ─────────────────────────────────────────────────────────────── +# +# An agents-cell token "?" (e.g. "mdl?") makes a BASELINE row on-demand for that one role: +# the role still finds the row in its stub's on-demand rows (trigger only), but it is not in +# that role's always-read set and not in its word count. Every other role the cell names keeps +# the row at baseline. "all,mdl?" = every role at baseline except mdl, which gets it on demand. +# +# WHY (2026-10-14, context-cost research r-stub-diet). The tier column is per ROW, so a row that +# one role needs every session and another needs once a module had to be baseline for both. The +# mdl helper never closes a module, pushes or writes a gate script, yet carried module-review, +# ui-loop, teamserver-alignment, learned-css-that-never-applied and skills-over-scripts as +# always-read: ~13k of its Stage 5 words. + +# The Role(s) cell of the baseline view: "all" reads as "every role", a list as "lead, mdl"; +# "?" tokens are named after, as "gate (mdl, review: on demand)" or, with "all", +# "every role except mdl, review (on demand for those)". _routing_roles_cell() { - if [ "$1" = "all" ]; then printf 'every role'; else printf '%s' "$1" | sed 's/,/, /g'; fi + local plain od + plain="$(printf '%s' "$1" | tr ',' '\n' | grep -v '?$' | tr '\n' ',' | sed 's/,$//')" + od="$(printf '%s' "$1" | tr ',' '\n' | grep '?$' | sed 's/?$//' | tr '\n' ',' | sed 's/,$//; s/,/, /g')" + # "every role" alone would read as "mdl too" to a helper scanning for its name, so the + # all-case names the exceptions in the same phrase. + if _routing_has "$plain" all; then + if [ -n "$od" ]; then printf 'every role except %s (on demand for those)' "$od"; else printf 'every role'; fi + return 0 + fi + printf '%s' "$plain" | sed 's/,/, /g' + if [ -n "$od" ]; then + [ -n "$plain" ] && printf ' ' + printf '(%s: on demand)' "$od" + fi } -# Does a row's agents cell reach ? "all" reaches everyone, lead included; an empty -# means "no role filter" (the whole stage pack, as before roles existed). +# _routing_role_tier — the tier this row has FOR , or nothing when +# the row does not name at all. "?" turns baseline into ondemand for only. +_routing_role_tier() { + if _routing_has "$1" "$2?"; then + if [ "$3" = "baseline" ]; then echo ondemand; else echo "$3"; fi + return 0 + fi + if _routing_has "$1" all || _routing_has "$1" "$2"; then echo "$3"; fi +} + +# Does a row's agents cell reach AT BASELINE? "all" reaches everyone, lead included, +# except a role named with the on-demand token ("all,mdl?" does not reach mdl here); an empty +# means "no role filter" (the whole stage pack, as before roles existed). _routing_reaches() { [ -z "$2" ] && return 0 - [ "$1" = "all" ] && return 0 + _routing_has "$1" "$2?" && return 1 + _routing_has "$1" all && return 0 _routing_has "$1" "$2" } @@ -151,27 +190,55 @@ _routing_stage_arm() { # # Optional third argument (lead|ba|architect|mdl|gate|test|review): count only rows # whose agents cell is "all" or names that role — what one helper at that stage is pointed -# at. Omitted = every role, the number this function always returned. +# at. A "?" token excludes the row for that role (it is on demand there, see +# _routing_role_tier). Omitted = every role, the number this function always returned. +# +# One script row IS counted: bin/runbook-slice.sh. Its whole output is read into context — +# it replaces the runbook for the helper roles — so it is counted as the words it prints for +# (about 1.7k at Stage 5), not as zero and not as the script's own text. With an +# unrecognised (the "full" pack) it is counted as the whole runbook, the upper bound. +# When skills/conversion-runbook.md is itself in the pack (no , or lead/ba/architect) +# the slice counts 0: it prints a subset of a file already counted whole. Before this (review +# of PR #378) the no-role full pack counted the runbook twice — 85,797 words, over the 80,000 +# budget, on gate-check's advisory line. +_ROUTING_READ_SCRIPTS="bin/runbook-slice.sh" +_routing_slice_words() { # _routing_slice_words + if [ "$3" -eq 1 ]; then + LC_ALL=C wc -w < "$1/skills/conversion-runbook.md" 2>/dev/null | tr -d '[:space:]' + else + bash "$1/$_ROUTING_READ_SCRIPTS" "$2" 2>/dev/null | LC_ALL=C wc -w | tr -d '[:space:]' + fi +} routing_baseline_pack() { local stage="$1" root="$2" role="${3:-}" words=0 files=0 paths="" p wc_out local name path when agents stages tier group - local known_stages=" P 0 1 2 3 4 5 6 7 " full=0 + local known_stages=" P 0 1 2 3 4 5 6 7 " full=0 rb_counted=0 slice_words=0 case "$known_stages" in *" $stage "*) : ;; *) full=1 ;; esac while IFS=$'\t' read -r name path when agents stages tier group; do [ "$tier" = "baseline" ] || continue - case "$path" in *.md) : ;; *) continue ;; esac + case "$path" in *.md|"$_ROUTING_READ_SCRIPTS") : ;; *) continue ;; esac _routing_reaches "$agents" "$role" || continue if [ "$full" -eq 0 ] && [ "$stages" != "-" ]; then _routing_has "$stages" "$stage" || continue fi p="$root/$path" [ -f "$p" ] || continue - wc_out="$(LC_ALL=C wc -w < "$p" 2>/dev/null | tr -d '[:space:]')" # LC_ALL=C: see BASELINE_BUDGET in render-routing.sh + if [ "$path" = "$_ROUTING_READ_SCRIPTS" ]; then + # Held back until the loop ends: whether the runbook row is counted too is known only + # then, whatever the TSV order. + slice_words="$(_routing_slice_words "$root" "$stage" "$full")" + [ -n "$slice_words" ] || slice_words=0 + wc_out=0 + else + [ "$path" = "skills/conversion-runbook.md" ] && rb_counted=1 + wc_out="$(LC_ALL=C wc -w < "$p" 2>/dev/null | tr -d '[:space:]')" # LC_ALL=C: see BASELINE_BUDGET in render-routing.sh + fi [ -n "$wc_out" ] || wc_out=0 words=$((words + wc_out)) files=$((files + 1)) paths="$paths $path" done < <(routing_rows) + [ "$rb_counted" -eq 1 ] || words=$((words + slice_words)) if [ "$full" -eq 1 ]; then printf '%s\t%s\t%s\tfull\n' "$words" "$files" "${paths# }" else @@ -234,7 +301,7 @@ routing_render() { # trigger text and the reasoning behind each row live in ROUTING.md, one read away (a fresh # scaffold's CLAUDE.local.md went 17.5 KB → 11.7 KB, 2026-10-07). readme-baseline keeps the # full text: README and the runbook are read on purpose, not loaded every turn. - echo "Triggers, not a reading list: open a row's file only when its trigger happens now, at *every stage* or the stage PROJECT.md records. Main session = **lead**; a role stub reads only its own rows and *every role*." + echo "Triggers, not a reading list: open a row's file only when its trigger happens now, at *every stage* or the stage PROJECT.md records. Main session = **lead**; a role stub reads only its own rows and *every role*; a role named *on demand* opens that row only when its trigger fires." echo "" echo "Full trigger text and the why behind each row: \`${prefix%/}/ROUTING.md\`." echo "" @@ -276,15 +343,15 @@ routing_render() { local who="${view#agent:}" echo "Open a file when its When cell happens in your task, not all of them at the start. Below the baseline rows, a When cell is only the trigger; the file says the rest. A page task never opens the microflow rows; a microflow task never opens the page rows." echo "" - echo "This table is your whole list. The baseline table in the project's CLAUDE.local.md is shared with the main session and the other helpers: skip every row there whose Role(s) cell does not say *every role* or name $who." + echo "This table is your whole list. The baseline table in the project's CLAUDE.local.md is shared with the main session and the other helpers: skip every row there whose Role(s) cell does not say *every role* or name $who. A cell that names $who as *on demand* is a row this table lists below the baseline rows, by its trigger." echo "" echo "| Load this | When |" echo "|---|---|" local t for t in baseline ondemand experimental; do routing_rows | while IFS=$'\t' read -r name path when agents stages tier group; do - [ "$tier" = "$t" ] || continue - if [ "$agents" != "all" ]; then _routing_has "$agents" "$who" || continue; fi + # The tier FOR THIS ROLE: a "?" token moves a baseline row to the on-demand rows. + [ "$(_routing_role_tier "$agents" "$who" "$tier")" = "$t" ] || continue # A helper reads its stub on every dispatch, so non-baseline rows keep only the trigger # (the When cell up to its first " — "); the tail describes the file, which the helper # reads once the trigger fires. Baseline tails stay whole: several are instructions diff --git a/bin/lib/skill-routing.tsv b/bin/lib/skill-routing.tsv index edf47908..7816b6a1 100644 --- a/bin/lib/skill-routing.tsv +++ b/bin/lib/skill-routing.tsv @@ -21,6 +21,9 @@ # Role(s) column for exactly that (added 2026-10-05: before it, the view filtered by # stage only, so a Stage 5 page helper was pointed at the lead's, the tester's and # the reviewer's rows too — process/context-cost-research-2026-09-30.md §1). +# "?" (e.g. "mdl?", or "all,mdl?") keeps a baseline row on demand for that one +# role: in its stub's on-demand rows, out of its always-read set and word count +# (added 2026-10-14; bin/lib/skill-routing.sh _routing_role_tier). Baseline rows only. # 5 stages comma list of P,0,1,2,3,4,5,6,7 (conversion-runbook stages), or "-" for none # 6 tier baseline (always-on: goes into every project's CLAUDE.local.md) # ondemand (situational: load when the task calls for it) @@ -68,9 +71,10 @@ # protocol-staleness ack on every routing edit. # # name path when agents stages tier group -conversion-runbook skills/conversion-runbook.md Any pipeline work at all — every session, before producing any stage artifact: read §1b plus your own stage's section, not the whole file (gate-check.sh prints the line spans); READMEs and the guide are orientation only all P,0,1,2,3,4,5,6,7 baseline spine +conversion-runbook skills/conversion-runbook.md Any pipeline work at all — every session, before producing any stage artifact: read §1b plus your own stage's section, not the whole file (gate-check.sh prints the line spans); READMEs and the guide are orientation only lead,ba,architect P,0,1,2,3,4,5,6,7 baseline spine +runbook-slice bin/runbook-slice.sh Any pipeline work at all, as a helper — every dispatch, before producing anything: run the toolkit's bin/runbook-slice.sh , which prints §1b plus your stage's section of the conversion runbook; never open skills/conversion-runbook.md whole mdl,gate,test,review P,0,1,2,3,4,5,6,7 baseline spine query-the-model skills/query-the-model.md Any question before asking the user or writing anything — query the model, then read the source, then ask the human, in that order all P,0,1,2,3,4,5,6,7 baseline spine -skills-over-scripts skills/skills-over-scripts.md Before writing any .js or .sh for a check, gate or report — and before adding a rule to an existing one: judgement goes in a skill, code only fetches facts a reader cannot all - baseline spine +skills-over-scripts skills/skills-over-scripts.md Before writing any .js or .sh for a check, gate or report — and before adding a rule to an existing one: judgement goes in a skill, code only fetches facts a reader cannot all,mdl?,review? - baseline spine degrade-to-judgement skills/degrade-to-judgement.md Any pass whose input is missing, stale or unresolvable — before recording UNMEASURED, N/A or a silent skip: name what was missing, say what you assessed against instead, still deliver a verdict all - baseline spine interview-protocol skills/interview-protocol.md Putting a question TO the user — any gate, any stage: ask in chat not in a file, two named options plus your recommendation, one batch per gate then end the turn lead,ba,architect P,0,1,2,3,4,5,6,7 baseline spine change-intake skills/change-intake.md The user gives feedback or asks for a change mid-project — "change X", "it also needs Y", a remark on a screenshot or the running app, a stakeholder's ask relayed in chat: log a CR row in PROJECT.md ## Change log BEFORE any edit, size it, then edit the highest artifact it alters (BRD, decision, architecture, wireframe), never the MDL first lead,ba 2,3,4,5,6,7 baseline spine @@ -107,14 +111,14 @@ mendix-best-practices-index skills/mendix-best-practices-index.md Asked "is ther ui-preflight-pages skills/ui-preflight-pages.md Building any page or snippet — before the first widget. Wireframe, tokens, gallery reuse, cross-check; no wireframe means STOP mdl 5 baseline design design-spacing skills/design-spacing.md Writing or reviewing any page or snippet — the spacing scale (8/16/24/32/48), section rhythm, and the page-header scaffold every full page starts with; sections at 0px apart and pages with no H1 are the defects it retires mdl,review 5 baseline design learned-stylegallery skills/learned-stylegallery.md Building or using the in-app design gallery mdl 5 ondemand design -ui-loop skills/ui-loop.md After every page-building script, and any time the UI looks wrong — the cheap repeatable look during the build: one page, one screenshot, four questions, scored when a wireframe exists. Feeds Gate: UI, never replaces it mdl,review 5 baseline design +ui-loop skills/ui-loop.md After every page-building script, and any time the UI looks wrong — the cheap repeatable look during the build: one page, one screenshot, four questions, scored when a wireframe exists. Feeds Gate: UI, never replaces it mdl?,review 5 baseline design check-design-portability project-bin/check-design-portability.sh Before porting ds.css into SCSS, and at the Stage-3 gate — greps the stylesheet for rules that cannot match the HTML Mendix emits (rem against the real root, table/th/td selectors, positional row selectors). mx check, mxcli check and mxcli lint are all blind to CSS architect,mdl,gate 3,5 ondemand design check-design-reaches-app project-bin/check-design-reaches-app.sh After the FIRST build that follows any design-system port, and before any page is built on it — reads the BUILT stylesheet and reports how many framework knobs point at a design token, how many tokens arrived, how many component classes arrived, each with its denominator. Measured on a real run: 55 tokens ported correctly into the right file, 0 of 35 knobs bound and 0 of 20 classes present, two build phases shipped in the framework's default blue with mx check, mxcli lint, the MDL suite and two e2e journeys all green architect,mdl,gate,review 3,5 baseline design check-page-shell project-bin/check-page-shell.sh Before exec'ing ANY page script — compares the drafted MDL's shell against the wireframe's: page column, layout/nav shell, one H1. Measured 0/10 pages on a real first build, repaired wholesale 47 scripts later mdl,gate,review 5 baseline design page-fidelity project-bin/page-fidelity.js After drafting and again after exec'ing any page script — scores the page MDL (or `mxcli describe` output on stdin) against its wireframe: headings/actions/content/classes, weighted. The scored companion to check-page-shell's binary gate; 32% median measured without it, 90% first-draft with it. Every run is appended to the project's docs/PAGE-FIDELITY.tsv — first non-stub row per page = first-build score of record vs the ≥80% target (forward-reference stubs score with --stub, exempt) mdl,gate,review 5 baseline design assemble-prototype project-bin/assemble-prototype.js After every wireframe edit: assembles design/wireframes/*.html into design/prototype.html, one hash-routed page a stakeholder can click through instead of twenty separate files. Generated, never edited (design-artifacts.md Step 3) architect,review 3 ondemand design check-prototype-links project-bin/check-prototype-links.js Before wireframes pass to the build loop, and with --brd before a BRD is signed off: dead #/route links, orphan screens, controls with no data-bind and no data-cut, BRD routes no screen has, screens no use case walks (design-artifacts.md Step 3c, brd-validation.md check 8) architect,review 3 ondemand design -module-review skills/module-review.md Reviewing any module before calling it done — the ONE pass: build, gate, prove, LOOK (is it logical, does it look right, does it match our design, over every page not just the tested ones), confirm with the denominator stated mdl,review,test 5,6 baseline build +module-review skills/module-review.md Reviewing any module before calling it done — the ONE pass: build, gate, prove, LOOK (is it logical, does it look right, does it match our design, over every page not just the tested ones), confirm with the denominator stated mdl?,review,test 5,6 baseline build exec-approval bin/exec-approval.sh Before any mxcli exec / exec.sh / --mcp write — ask or run? the knob decides mdl,gate 5,6 baseline build context-pack project-bin/context-pack.sh 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 mdl 5 ondemand build testing-shape skills/testing-shape.md Before calling any module tested, or before re-running tests after a change — what testing a module means, which re-runs a change owes (UI-only vs logic lane), and the false-green register of confirmed ways a test reports green over a broken feature test,gate,review 5,6 baseline verify @@ -206,7 +210,7 @@ corpus-extraction-integrity skills/corpus-extraction-integrity.md Extracting str anonymize-client-app-for-demo skills/anonymize-client-app-for-demo.md Turning a client-derived Mendix app into a clean, shareable demo with zero client fingerprint — branding, data, custom widgets mdl,review 6 ondemand build field-run skills/field-run.md Driving the whole toolkit pipeline on a real source to find what the written skills don't say — the toolkit is the subject, not the app it builds all - ondemand diagnose learned-mdl-cannot-express skills/learned-mdl-cannot-express.md Before a wireframe or a design commits to a WIDGET, when a requirement shows a widget to one role and not another, and when a page script hits a parse error that looks like a syntax mistake: the short list of things MDL cannot write at all, and the four-minute probe that answers it at Stage 3 instead of at build time architect,mdl 3,5 ondemand design -learned-css-that-never-applied skills/learned-css-that-never-applied.md A style change that appears to have done nothing, a focus ring that paints on every page landing, or an app still grey after a design port every instrument called green — the three ways a correct rule paints nothing (matches nothing / matches chrome / loses the cascade), the two reads that tell them apart, and the class that arrived in the stylesheet and is bound to no widget mdl,review,gate 5,6 baseline design +learned-css-that-never-applied skills/learned-css-that-never-applied.md A style change that appears to have done nothing, a focus ring that paints on every page landing, or an app still grey after a design port every instrument called green — the three ways a correct rule paints nothing (matches nothing / matches chrome / loses the cascade), the two reads that tell them apart, and the class that arrived in the stylesheet and is bound to no widget mdl?,review,gate 5,6 baseline design learned-detection-gaps skills/learned-detection-gaps.md Before trusting a green check/exec/DESCRIBE result as proof, or when a runtime symptom appears over a fully green model — the register of constructs that pass early rungs and fail later ones mdl,gate,review 5,6 baseline diagnose learned-dg2-patterns skills/learned-dg2-patterns.md Building or altering any data grid — native DATAGRID vs pluggable DG2 decision rule, the ALTER PAGE INSERT corruption, sort-by and filter-binding traps mdl 5 ondemand build/pages learned-file-upload-widget skills/learned-file-upload-widget.md Putting any file upload / attachment / document field on a page, an uploader page failing mx check with CE0463, or an uploader DESCRIBE that will not re-execute — the File Uploader MDL shape proven end to end on v0.23 and v0.24, the widgets mxcli cannot author, and the upload instrument mdl,test 5 ondemand build/pages @@ -295,7 +299,7 @@ walking-skeleton skills/walking-skeleton.md Stage 5 start, before the first modu bug-lookup bin/bug-lookup.sh A CE error or behavior that looks like a known mxcli quirk rather than a modeling mistake — `bin/bug-lookup.sh CE0117` / `BUG-102` / "keyword" prints the matching ledger entries, so the session reads one entry, not the 32k-word ledger mdl,gate 5,6 baseline diagnose status bin/status.sh The first command of every session, and any time someone asks "where are we" or "what next" — one screen: stage, done/overdue, the ONE next action, from the instruments, never from memory all P,0,1,2,3,4,5,6,7 baseline spine platform-link skills/platform-link.md At project birth (before the first build script) and any time a model needs a platform home: creating the Team Server app, adopting an existing GitHub-born model into it without rewriting history, or deploying; also when the Platform SDK returns 403, git rejects the PAT, a deploy cannot be triggered from a PAT, or the app turns out to be a Free App all P,5,6,7 ondemand build -teamserver-alignment skills/teamserver-alignment.md Any session that will push a model to Mendix Team Server, and BEFORE telling the user a Team Server push is blocked — which remote is authoritative, settle-then-push order, and the four checks that have to fail first mdl,gate,review 5,6,7 baseline build +teamserver-alignment skills/teamserver-alignment.md Any session that will push a model to Mendix Team Server, and BEFORE telling the user a Team Server push is blocked — which remote is authoritative, settle-then-push order, and the four checks that have to fail first mdl?,gate,review? 5,6,7 baseline build retesting-learned-rules skills/retesting-learned-rules.md Before obeying any learned-* STOP or workaround that costs a detour — probe the binary you actually have, then stamp the verdict back into the rule all - baseline spine deploy-to-sandbox skills/deploy-to-sandbox.md Promoting an app to a deployed sandbox or cloud node, or before a customer tests a deployment — "it works locally" is not evidence about a deployment; also when a test suite fails only against the remote URL all 7 ondemand build learned-constants-and-secrets skills/learned-constants-and-secrets.md Adding a constant, installing a marketplace module, deploying to a new environment, or needing a Mendix PAT — where an encryption key, API credential, endpoint or access token gets its value, why a free node has only one channel, and why a token missing from the files you grepped is not missing mdl,architect,gate 4,5,6 ondemand build/integration diff --git a/bin/mdl-scan.sh b/bin/mdl-scan.sh new file mode 100755 index 00000000..0cb6690f --- /dev/null +++ b/bin/mdl-scan.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash +# mdl-scan.sh — flag an MDL script that contains a STOP-row pattern, before anyone applies it. +# +# bin/mdl-scan.sh [-p ] +# +# One rule per row of skills/learned-mdl-preflight.md's STOP table that a text pattern in the +# script identifies (issue #349). The rules are data: bin/lib/mdl-scan-rules.tsv, whose header +# documents the columns and the three rule kinds. Rows that need model state or judgement are +# not here; they stay prose in the skill, unmarked. +# +# Output: one line per hit, `STOP- : → [warn|block]`. +# Exit: 0 clean · 1 at least one hit of a `block` rule · 3 hits, all of `warn` rules · +# 2 usage error (no script, unreadable file, bad flag, rules file missing). +# Severity is a column of the rules file; its header says when a rule may go to `block`. +# exec.sh refuses on 1 and prints the hits as warnings on 3. +# +# -p is accepted and checked, so a caller can pass the model the same way it passes it to +# `mxcli check`. No current rule reads it: every row that needs model state stayed prose +# (the PR for #349 lists them). A future rule that uses it must stay read-only. +# +# WHY. The STOP table is prose an agent reads, or skips. Every rule here was a script that +# passed `mxcli check`, cost a full apply, and failed only at mxbuild or on Studio Pro open +# (bug ids in the rules file). A text scan costs milliseconds; a failed apply costs 22-65 s +# plus the restore (iterative-build-loop.md, "Gate cost"). +# +# HOW IT READS A SCRIPT. Comments (`--`, `//`, `/* */`) and `$$ ... $$` bodies are removed +# and the line is lowercased. `line` rules see string literals (STOP-16 lives inside one); +# `customcontent` rules and all bracket counting see the line with literals blanked. A +# customContent column's body is known by `{ }` depth, not indentation; a column with no body +# is forgotten at the `)` that closes its header's scope or at the next `}`. A ContentParams +# list ends at its matching `]` by depth, not at the last `]` on the line. It is a line +# scanner, not a parser: a one-line column whose body opens and holds the conditional on the +# header line itself is not seen as inside the column. +# +# Golden input: the bad MDL captured in bug-logs/mxcli-bugs.md for each rule, and the +# StyleGallery example (applied, 0 errors in mx check) as the clean corpus — +# tests/wave2/test-mdl-scan.sh, tests/wave2/bench-mdl-scan.sh. Plain bash 3.2 + POSIX awk. +set -u + +usage() { echo "usage: bin/mdl-scan.sh [-p ]" >&2; exit 2; } + +SCRIPT=""; MPR="" +while [ $# -gt 0 ]; do + case "$1" in + -p) [ $# -ge 2 ] || usage; MPR="$2"; shift 2 ;; + -h|--help) sed -n '2,13p' "$0"; exit 0 ;; + -*) echo "mdl-scan: unknown option '$1'" >&2; usage ;; + *) [ -z "$SCRIPT" ] || usage; SCRIPT="$1"; shift ;; + esac +done +[ -n "$SCRIPT" ] || usage +[ -f "$SCRIPT" ] && [ -r "$SCRIPT" ] || { echo "mdl-scan: cannot read script: $SCRIPT" >&2; exit 2; } +if [ -n "$MPR" ] && [ ! -f "$MPR" ]; then echo "mdl-scan: -p: no such model: $MPR" >&2; exit 2; fi + +RULES="${MDL_SCAN_RULES:-$(cd "$(dirname "$0")" && pwd)/lib/mdl-scan-rules.tsv}" +[ -r "$RULES" ] || { echo "mdl-scan: rules file missing: $RULES" >&2; exit 2; } + +awk -F'\t' -v file="$SCRIPT" ' + FNR == NR { # the rules file + if ($0 ~ /^#/ || NF < 6) next + n++; rid[n] = $1; rkind[n] = $2; rsev[n] = $3; rpat[n] = $4; rmsg[n] = $5; rfix[n] = $6 + next + } + function hit(i, ln) { + printf "%s %s:%d %s → %s [%s]\n", rid[i], file, ln, rmsg[i], rfix[i], rsev[i] + hits++; if (rsev[i] == "block") blocks++ + } + # strip(line): sets code (comments and $$-bodies removed, strings kept) and bare (strings + # blanked too). Same length, so an offset found in one cuts the other. inblock / indollar + # carry across lines. + function strip(line, m, i, c, c2, instr) { + code = ""; bare = ""; m = length(line); i = 1; instr = 0 + while (i <= m) { + c = substr(line, i, 1); c2 = substr(line, i, 2) + if (inblock) { if (c2 == "*/") { inblock = 0; i += 2 } else i++; continue } + if (indollar) { if (c2 == "$$") { indollar = 0; i += 2 } else i++; continue } + if (instr) { + code = code c; bare = bare " " + if (c == "\047") instr = 0 + i++; continue + } + if (c2 == "/*") { inblock = 1; i += 2; continue } + if (c2 == "$$") { indollar = 1; i += 2; continue } + if (c2 == "--" || c2 == "//") break + if (c == "\047") instr = 1 + code = code c; bare = bare (instr ? " " : c) + i++ + } + code = tolower(code); bare = tolower(bare) + } + # A ContentParams value is fine when it is a quoted literal, or a bare (optionally + # double-quoted) identifier, or an unquoted identifier path. Anything else is an expression. + function bad_value(v) { + sub(/^[[:space:]]+/, "", v); sub(/[[:space:],]+$/, "", v) + if (v ~ /^\047([^\047]|\047\047)*\047$/) return 0 + if (v ~ /^"?[a-z_][a-z0-9_]*"?$/) return 0 + if (v ~ /^[a-z_][a-z0-9_.]*(\/[a-z_][a-z0-9_.]*)*$/) return 0 + return 1 + } + function check_params(text, ln, i, rest, v) { + rest = text + while (match(rest, /\{[0-9]+\}[[:space:]]*=/)) { + rest = substr(rest, RSTART + RLENGTH) + if (match(rest, /,[[:space:]]*\{[0-9]+\}[[:space:]]*=/)) v = substr(rest, 1, RSTART - 1) + else v = rest + if (bad_value(v)) { hit(i, ln); return } + } + } + # cp_feed(c, b): add one stretch of an open ContentParams list (code c, bare b) to cp_text, + # counting [ ] in b. Returns 1 when the list closes, cutting c at its matching ]. + function cp_feed(c, b, k, m, ch) { + m = length(b) + for (k = 1; k <= m; k++) { + ch = substr(b, k, 1) + if (ch == "[") cp_depth++ + else if (ch == "]" && --cp_depth == 0) { cp_text = cp_text " " substr(c, 1, k - 1); return 1 } + } + cp_text = cp_text " " c; return 0 + } + { + strip($0) + incc = (ccn > 0) # inside a customContent column body? + for (i = 1; i <= n; i++) { + if (rkind[i] == "line" && code ~ rpat[i]) hit(i, FNR) + else if (rkind[i] == "customcontent" && incc && bare ~ rpat[i]) hit(i, FNR) + else if (rkind[i] == "contentparams") { + if (cp_open) { # a ContentParams list still open from above + if (cp_feed(code, bare)) { check_params(cp_text, cp_line, i); cp_open = 0 } + } else if (match(bare, /contentparams[[:space:]]*:[[:space:]]*\[/)) { + k0 = RSTART + RLENGTH; cp_text = ""; cp_line = FNR; cp_depth = 1 + if (cp_feed(substr(code, k0), substr(bare, k0))) check_params(cp_text, cp_line, i) + else cp_open = 1 + } + } + } + if (bare ~ /^[[:space:]]*column[[:space:]]/) { pending = 0; colbase = pdepth } + if (bare ~ /showcontentas[[:space:]]*:[[:space:]]*customcontent/) pending = 1 + m = length(bare) + for (j = 1; j <= m; j++) { + c = substr(bare, j, 1) + if (c == "(") pdepth++ + else if (c == ")") { pdepth--; if (pending && pdepth < colbase) pending = 0 } # header scope ended, no body + else if (c == "{") { depth++; if (pending) { cc[++ccn] = depth; pending = 0 } } + else if (c == "}") { pending = 0; if (ccn > 0 && cc[ccn] == depth) ccn--; depth-- } + } + } + END { exit (blocks > 0 ? 1 : (hits > 0 ? 3 : 0)) } +' "$RULES" "$SCRIPT" diff --git a/bin/render-routing.sh b/bin/render-routing.sh index ecde20ef..38ec1ea8 100755 --- a/bin/render-routing.sh +++ b/bin/render-routing.sh @@ -33,10 +33,12 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" # The surfaces. Anything listed here MUST carry at least one ROUTING:BEGIN marker; a surface # that has lost its markers is a structural error (exit 1), not silence — that silent-drop is # precisely how the old six-copy arrangement lost track of a table. +# skills/conversion-runbook.md left this list 2026-10-14: it carried a fourth rendered copy of the +# baseline table (~1,800 words) inside a baseline file every role reads, the same rows already in +# README, ROUTING.md and every project's CLAUDE.local.md. It now points at those instead. SURFACES=" ROUTING.md README.md -skills/conversion-runbook.md agents/ba-agent.md agents/architect-agent.md agents/mdl-agent.md @@ -216,6 +218,9 @@ fi # `lead` (the main session) is a valid token with no stub: a lead-only row reaches the project # baseline view's Role(s) column, and no helper — that is its point (2026-10-05). AGENT_NAMES="lead $(for f in "$ROOT"/agents/*-agent.md; do [ -f "$f" ] || continue; basename "$f" | sed 's/-agent\.md$//'; done | tr '\n' ' ')" +# "?" (2026-10-14) is the per-role on-demand token: the name part is checked like any +# other token, and it is refused on a non-baseline row (nothing to demote) and beside its own +# plain form ("mdl,mdl?" says two things at once). "all" may lead a list of "?" tokens. BAD_AGENT="$(awk -F'\t' -v ok="$AGENT_NAMES" ' /^#/ || NF < 6 { next } $4 == "all" { next } @@ -223,16 +228,21 @@ BAD_AGENT="$(awk -F'\t' -v ok="$AGENT_NAMES" ' n = split($4, toks, ",") m = split(ok, agents, " ") for (i = 1; i <= n; i++) { + t = toks[i]; od = 0 + if (t ~ /\?$/) { od = 1; t = substr(t, 1, length(t) - 1) } + if (t == "all" && !od) continue hit = 0 - for (j = 1; j <= m; j++) if (agents[j] == toks[i]) { hit = 1; break } + for (j = 1; j <= m; j++) if (agents[j] == t) { hit = 1; break } if (!hit) { printf "%s (agents: %s, bad token: %s)\n", $1, $4, toks[i]; next } + if (od && $6 != "baseline") { printf "%s (agents: %s, bad token: %s — on-demand token on a %s row)\n", $1, $4, toks[i], $6; next } + if (od) for (k = 1; k <= n; k++) if (toks[k] == t) { printf "%s (agents: %s, bad token: %s — also listed plain)\n", $1, $4, toks[i]; next } } } ' "$MXTK_ROUTING_TSV")" if [ -n "$BAD_AGENT" ]; then echo "UNKNOWN AGENT: routing row(s) whose agents column no agent stub renders — the skill is routed into no stub:" printf '%s\n' "$BAD_AGENT" | sed 's/^/ /' - echo " → agents must be \"all\" or a comma-list of: $AGENT_NAMES" + echo " → agents must be \"all\" or a comma-list of: $AGENT_NAMES — each may carry \"?\" (on demand for that role, baseline rows only)" fi # Baseline budget: the baseline tier is read by EVERY session of EVERY project before it diff --git a/bin/runbook-slice.sh b/bin/runbook-slice.sh new file mode 100755 index 00000000..109b3b0c --- /dev/null +++ b/bin/runbook-slice.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# runbook-slice.sh — print the part of the conversion runbook a helper at one stage needs: +# §1b (the Live Checklist Protocol, every stage) plus that stage's section under §2, then the +# gate-verdict table and §3b's "Ownership is split" paragraph, which every helper needs too. +# +# bin/runbook-slice.sh 5 # §1b + "### Stage 5 — Build" + verdicts + §3b ownership +# bin/runbook-slice.sh build-ready # same as 5 +# bin/runbook-slice.sh --spans 5 # only the line spans, as gate-check.sh prints them +# +# WHY (2026-10-14). skills/conversion-runbook.md is ~14k words. Its own "how to read this" rule +# says a session reads §1b plus its stage's section, and gate-check.sh prints those line spans — +# but a helper (mdl, gate, test, review) never runs a gate, so it never saw the spans and opened +# the whole file: 26% of an mdl helper's Stage 5 reading. This prints the ~1.5k words instead, the +# same way bin/bug-lookup.sh turned the bug ledger into a lookup. Sections are found by heading +# (bin/lib/runbook-span.sh, shared with gate-check.sh), never by line number. +# +# The two extra spans (review of PR #378): gate-agent reports PASS/PENDING/FAIL/WAIVED/MANUAL and +# needs their exit codes; mdl and gate need "a helper that hits a BRD-touching decision reports it +# to the lead" (§3b). Neither is in §1b or a stage section. If either anchor is gone the slice +# still prints, with a line naming the section to read in the runbook, and warns on stderr. +# +# Exit: 0 printed · 1 usage · 2 runbook or heading not found +set -eu +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +. "$ROOT/bin/lib/runbook-span.sh" +RB="${MXTK_RUNBOOK:-$ROOT/skills/conversion-runbook.md}" + +SPANS_ONLY=0 +if [ "${1:-}" = "--spans" ]; then SPANS_ONLY=1; shift; fi +case "${1:-}" in + ""|-h|--help) sed -n '2,8p' "$0" | sed 's/^# \{0,1\}//'; exit 1 ;; +esac +ST="$(runbook_stage_token "$1")" +[ -n "$ST" ] || { echo "runbook-slice: unknown stage '$1' (P, 0-7 or build-ready)" >&2; exit 1; } +[ -f "$RB" ] || { echo "runbook-slice: no runbook at $RB" >&2; exit 2; } + +S1B="$(runbook_1b_span "$RB")" +SST="$(runbook_stage_span "$RB" "$ST")" +if [ -z "$S1B" ] || [ -z "$SST" ]; then + echo "runbook-slice: could not locate '## 1b.' or '### Stage $ST — ' in $RB — read §1b and your stage's §2 section there" >&2 + exit 2 +fi + +if [ "$SPANS_ONLY" -eq 1 ]; then + printf 'skills/conversion-runbook.md §1b lines %s + Stage %s lines %s\n' "$S1B" "$ST" "$SST" + exit 0 +fi +SVD="$(runbook_verdicts_span "$RB")" +S3B="$(runbook_3b_owner_span "$RB")" +printf '\n\n' \ + "$S1B" "$ST" "$SST" "${SVD:-not found}" "${S3B:-not found}" +runbook_print_span "$RB" "$S1B" +echo +runbook_print_span "$RB" "$SST" +echo +if [ -n "$SVD" ]; then + runbook_print_span "$RB" "$SVD" +else + echo "runbook-slice: '### What a gate verdict means' not found in $RB" >&2 + echo "(Gate verdict table not found — read \"What a gate verdict means\" in §2 of skills/conversion-runbook.md.)" +fi +echo +echo "From §3b (Requirements drift-sync):" +echo +if [ -n "$S3B" ]; then + runbook_print_span "$RB" "$S3B" +else + echo "runbook-slice: '**Ownership is split' not found under '## 3b.' in $RB" >&2 + echo "(Ownership paragraph not found — read §3b of skills/conversion-runbook.md.)" +fi diff --git a/bin/status.sh b/bin/status.sh index fd11576d..cd648493 100755 --- a/bin/status.sh +++ b/bin/status.sh @@ -149,6 +149,16 @@ if [ -f "$MS_FILE" ]; then END { printf "manual steps pending %d of %d", p, t }' "$MS_FILE")" fi +# --- app map freshness (#297) -------------------------------------------------------------- +# The map (skills/app-analysis.md) costs hours on a large model, so how stale it is matters: +# app-facts.sh --freshness compares the .mpr Unit table now with the {UnitID: ContentsHash} +# snapshot the last map stored. No map, no line — the screen is unchanged for projects without one. +APPMAP="" +if [ -f "$PROJECT_DIR/analysis/app-facts/manifest.json" ]; then + APPMAP="$(PROJECT_ROOT="$PROJECT_DIR" bash "$TOOLKIT_ROOT/project-bin/app-facts.sh" --freshness \ + --facts-dir "$PROJECT_DIR/analysis/app-facts" 2>/dev/null | head -1)" +fi + # --- doctor receipt ------------------------------------------------------------------------- DR="$PROJECT_DIR/.claude/.doctor-receipt" if [ -f "$DR" ]; then @@ -267,7 +277,7 @@ done if [ "$BRIEF" = 1 ]; then echo "WHERE $NAME · $STAGE_LINE${ENTRY:+ · $ENTRY}${ADOPTED:+ · joined at $ADOPTED}" [ -n "$ROUND_LINE" ] && echo "ROUND $ROUND_LINE" - echo "STATE scripts $N_SCRIPTS written / $N_PASS gate-pass / $N_DONE done- · modules opened $MOD_OPENED${SKELETON:+ · skeleton $SKELETON} · UNSYNCED $UNSYNCED · open questions $OPEN_Q${MANUAL:+ · $MANUAL}" + echo "STATE scripts $N_SCRIPTS written / $N_PASS gate-pass / $N_DONE done- · modules opened $MOD_OPENED${SKELETON:+ · skeleton $SKELETON} · UNSYNCED $UNSYNCED · open questions $OPEN_Q${MANUAL:+ · $MANUAL}${APPMAP:+ · $APPMAP}" [ -n "$WATCH" ] && echo "WATCH $WATCH" echo "NEXT $NEXT" "$TOOLKIT_ROOT/bin/token-burn.sh" "$PROJECT_DIR" --brief 2>/dev/null @@ -279,6 +289,7 @@ printf '%s\n\n' "$TK" printf 'DONE scripts: %s written, %s gate-pass, %s done- · modules opened: %s%s\n' "$N_SCRIPTS" "$N_PASS" "$N_DONE" "$MOD_OPENED" "${SKELETON:+ · skeleton proven $SKELETON}" printf 'OVERDUE %s · %s · %s · UNSYNCED markers: %s · open questions: %s\n' "$DOCTOR" "$LINT" "${COH:-coherence: cadence script not installed}" "$UNSYNCED" "$OPEN_Q" [ -n "$MANUAL" ] && printf ' %s (docs/manual-steps.md)\n' "$MANUAL" +[ -n "$APPMAP" ] && printf ' %s (refresh: bin/app-facts.sh)\n' "$APPMAP" [ -n "$OB_PENDING" ] && printf ' obligations pending: %s\n' "$OB_PENDING" [ -n "$LAST_FAIL" ] && printf ' last gate FAILED: %s\n' "$LAST_FAIL" [ -n "$WATCH" ] && printf 'WATCH %s\n' "$WATCH" diff --git a/bin/sync-project.sh b/bin/sync-project.sh index 78c893cf..513c0810 100755 --- a/bin/sync-project.sh +++ b/bin/sync-project.sh @@ -98,7 +98,7 @@ AGENTS="$MXTK_AGENTS" is_pure_stub() { local f="$1" grep -q "STUB GENERATED" "$f" || return 1 - sed 's/{{DOUBLE_BRACE}}//g' "$f" | grep -q '{{[A-Z_]*[^}]*}}' + grep -q -- '{{[A-Z_]*[^}]*}}' <<<"$(sed 's/{{DOUBLE_BRACE}}//g' "$f")" } DIFF_COMPLETED=0 @@ -342,7 +342,7 @@ elif [ -n "$INTAKE" ]; then echo "Updated: $INTAKE — appended Q9 (interview mode). Answer it this session." fi CHANGES=$((CHANGES + 1)) - elif ! q9_section "$INTAKE" | grep -q 'Answered\|Unverified'; then + elif ! grep -q -- 'Answered\|Unverified' <<<"$(q9_section "$INTAKE")"; then # Present, but ungradeable by gate-check's Stage P check. Q9_TMP=$(mktemp "${TMPDIR:-/tmp}/q9sec.XXXXXX") || exit 2 Q9_REF=$(mktemp "${TMPDIR:-/tmp}/q9ref.XXXXXX") || exit 2 @@ -358,7 +358,7 @@ elif [ -n "$INTAKE" ]; then # into the "edited, NOT touching it" warning below. q9_current_text > "$Q9_REF" if cmp -s "$Q9_TMP" "$Q9_REF" \ - || sed -n '2,$p' "$Q9_TMP" | grep -v '^[[:space:]]*$' | head -1 | grep -q '^_Not yet asked\._'; then + || grep -q -- '^_Not yet asked\._' <<<"$(sed -n '2,$p' "$Q9_TMP" | grep -v '^[[:space:]]*$' | head -1)"; then echo "Note: $INTAKE Q9 is the unanswered template text — Stage P will FAIL until" echo " the kickoff interview happens. That is correct; nothing to repair." @@ -428,7 +428,7 @@ elif [ -n "$INTAKE" ]; then MISSING_N=0 while IFS= read -r _q; do [ -n "$_q" ] || continue - if ! sed -n 's/^## [0-9][0-9]*\. //p' "$INTAKE" | grep -Fxq "$_q"; then + if ! grep -Fxq -- "$_q" <<<"$(sed -n 's/^## [0-9][0-9]*\. //p' "$INTAKE")"; then MISSING_Q="$MISSING_Q$_q " MISSING_N=$((MISSING_N + 1)) @@ -1368,7 +1368,7 @@ _ea_rewrite_item3() { # $1=file $2=start-prefix $3=end-prefix $4=replacemen { if (skip) { if (index($0, endp) == 1) { exit }; print; next } if (index($0, start) == 1) { print; skip = 1 } } ' "$f" | tr '\n' ' ' | tr -s ' ')" - printf '%s' "$_blk" | grep -qF -- "$needle" || return 1 + grep -qF -- "$needle" <<<"$_blk" || return 1 fi [ "$DRY_RUN" -eq 1 ] && return 0 _tmp="$(mktemp "${TMPDIR:-/tmp}/eaitem3.XXXXXX")" || return 1 diff --git a/bug-logs/mxcli-bugs.md b/bug-logs/mxcli-bugs.md index c068c838..f2042415 100644 --- a/bug-logs/mxcli-bugs.md +++ b/bug-logs/mxcli-bugs.md @@ -5949,7 +5949,9 @@ service and its callers. 4. **Doc comments** (`/** */`) set documentation on microflows, entities and Java actions. Before an import or export mapping, or a published REST service, they are silently ignored ("already in sync"). Before a Java action parameter they are a syntax error. QUAL002 therefore - cannot be cleared for those from MDL. Add a baseline entry with the reason. + cannot be cleared for those from MDL. Add a baseline entry with the reason, or switch that kind + off in QUAL002's options (`check_import_mappings`, `check_published_rest_services`; mxcli + >= 0.24.0). The MDL089 warning on v0.25.0 names the case (#246). 5. **`import from mapping … ($Call/ResponseBody)` is a parse error** ("mismatched input '/'"). The source must be a variable: `declare $Body String = $Call/ResponseBody;` then `($Body)`. 6. **Import mappings, re-run.** @@ -6950,3 +6952,95 @@ Two related findings from reading the v0.26.0 source (not field-run): matching the project. Recipe and the rule-set comparison to run before any diff: `skills/lint-that-actually-runs.md` → "Linting a copy of the model". `lint-gate.sh`'s collapse guard catches this inside the gate. Nothing catches it when lint is run directly. + +## BUG-DRAFT-dg2-replace-column-attribute-passes-check: `ALTER PAGE … REPLACE` of a DataGrid 2 column with a new `Attribute:` passes `check --references`, then fails mxbuild (2026-10-14) + +**Severity:** Low. No damage: `exec.sh`'s mxbuild gate fails and auto-restores the snapshot. The cost is one wasted exec/mxbuild cycle, and the form is tempting because `check` approves it. +**mxcli version:** v0.26.0. **Mendix version:** 11.15.0, macOS. +**Discovered:** 2026-10-14, an Ollama connector app (existing-app mode): one build row swapped the `Attribute` of an existing DG2 column (raw bytes → a formatted String attribute on the same entity, same module). Reported by joshuamoesa (#356). +**Reproducible:** yes, by the reporter: against a snapshot copy, `mxcli check -p .mpr --references