CLAUDE.md · git:20261015.1512b09 · 2026-10-15 · sha256 9b024f877e8aadb7
CLAUDE.md git:20261015.1512b09A
Immutable. This exact content never changes and is served at /api/v1/blob/9b024f877e8aadb7.
# mxcli-project-toolkit — Claude Context
## What this repo is
Shared skills, stage-gate tooling, and learnings for **Mendix migration and development projects**.
Serves four entry modes (see `skills/conversion-runbook.md` → "Entry Modes"): **migration** (legacy source, all stages), **requirements-driven** (specs/SME input, no legacy code, stages 1–6), **greenfield** (stage 5 onward), and **change an existing app** (a live `.mpr`, a slice being added or altered — stages P, 0–6 per slice, `skills/existing-app-change.md`). Plus **à-la-carte use with no pipeline at all** — auditing or regression/e2e-testing an existing app routes straight to `skills/existing-app-assurance.md`, skipping intake/stages/gates entirely.
## The front door
- `CONVERSION-RUNBOOK.md` (root) — thin "how to start" pointer.
- `toolkit-guide.html` (root) — the visual onboarding page. **First-touch rule — this is the single source of the rule; every other mention defers here:**
```
[ -e "<project-root>/.claude/.guide-shown" ] && DO NOT OPEN
```
Open it in the user's browser (`open toolkit-guide.html` / `xdg-open`) **only** when that sentinel is absent, and immediately `touch` it so no later session repeats. `bin/init-project.sh` opens it at scaffold time and writes the sentinel then. The user may also ask for it by name at any time — that's always allowed and does not consult the sentinel.
**Do not re-open it on a hunch** that the user "seems unsure" — a session cannot tell a first-timer from someone who has seen it fifty times, and every session that guesses guesses "yes". The sentinel is the only test. (Real incident, 2026-08-12: this rule was stated conditionally here and in `README.md` but *unconditionally* in `skills/conversion-runbook.md`, `CONVERSION-RUNBOOK.md`, and inside `toolkit-guide.html` itself. Because the runbook is the one file every session is required to read first, every pipeline session opened the guide, and the user got a browser tab per session.)
It's also the shared CSS shell/tokens for every stage HTML surface — that use has nothing to do with opening it.
- `skills/conversion-runbook.md` — **the spine**: 9-stage matrix, interview protocol, gates, entry modes. Start here when unsure what stage anything is in.
- `bin/init-project.sh <project-root>` — **the one-command install**: `intake.md`, `PROJECT.md`, `CLAUDE.local.md` (runbook wiring), all six agent stubs, `index.html` dashboard; opens the guide. Idempotent.
- `bin/gate-check.sh <project-dir> [stage]` — mechanical stage gates; regenerates the project dashboard from real files. Verdicts are `PASS` / `PENDING` (nothing there yet) / `FAIL` (there and wrong) / `WAIVED` (declared out of scope in the register, with a reason, via `--adopt` / `--waive`) / `MANUAL`. Never infer a project's position — read the register line, or ask. It also runs the **obligation check** (`bin/lib/obligations.tsv` + `bin/lib/obligation-check.sh`): per-module *passes* that owe a mark — LOOK, wiring sweep, journeys, coherence — each with the artifact that proves it and the denominator that artifact must state. A pass nobody performed reports `PENDING`/`FAULT`, never green-by-absence; `--waive look/<Module> --reason "..."` records a pass deliberately not performed, and obligations below an `--adopt` point report `ADOPTED`. Alongside it runs the **artifact check** (`bin/lib/artifact-manifest.tsv` + `bin/lib/artifact-check.sh`): the producer-side mirror — one row per artifact a stage's "Agent produces" row promises, each row citing the consumer that reads it, so a stage that never emitted its artifact reports `PENDING` out loud instead of being found by a human reading a failed project. File presence only, never content quality; waivers are register lines (`Waived artifact <id>: reason`, or the stage-level waiver covering its artifacts wholesale). Third, the **source ledger** (`bin/source-ledger.sh`, over the every-file inventory `bin/source-sufficiency.sh init` writes): each source file must name the artifact that consumed it — text *and* embedded images/pages — or carry a `--waive source/<file>` register line; this one **blocks Stages 1–2**, because "the triage already used the deck" is a grep, not a conversation (real incident, 2026-09-02).
- `bin/sync-project.sh <project-root>` — run after every toolkit `git pull`: refreshes the artifacts that were *copied* into the project (new intake questions, untouched agent stubs) and flags stale baseline routing. Referenced skills need no sync — they update with the pull.
## Live checklist — every stage, in the chat
Every stage and module build follows the **Live Checklist Protocol** (`skills/conversion-runbook.md` §1b): post the stage's checklist in chat at start, update with ✅/🔄/⬜/❌/⏭ marks as items land, full repost before every gate. No stage works silently between gates. This is not build-phase-only.
## One decision register
All gate decisions land in the consuming project's `PROJECT.md`, marked `CONFIRMED` or `ASSUMED`. The `skills/checkpoints/` CAC files are the packaged mechanism that runs the runbook's interview protocol at the six busiest transitions — they write to `PROJECT.md`, never to a separate state file.
**Ask, then stop.** Gate questions are actually asked in chat — on Claude Code via `AskUserQuestion` (clickable options), on any other harness as plain numbered markdown with "something else" always available — and **either way the agent ends its turn and waits for the answer**. `AskUserQuestion` is a Claude Code built-in, not something a Copilot/Cursor/Windsurf session can install; the rendering is tool-specific, the asking and the stopping are not. `skills/interview-protocol.md` §3 → "Asking on a non-Claude agent" is the single source of that rule; every other mention defers there. `ASSUMED` is earned by asking (user said "you decide"), never by skipping the question — finding the answer in the source justifies the *recommendation*, not silence. Unattended runs are opt-in only (`Interview mode: auto` in `PROJECT.md`, alias `unattended`, at the user's explicit request; modes in `skills/interview-protocol.md`).
## Key skills and when to load them
Load skill files **on demand when the task calls for it** — not all upfront. Full routing table: `README.md` → "When to use which skill". The always-on set (`README.md` → "Baseline routing"): `query-the-model.md`, `learned-mdl-preflight.md`, `learned-microflow-patterns.md`, and `bin/bug-lookup.sh` (a CE code, BUG-n or keyword → the matching ledger entries; `bug-logs/mxcli-bugs.md` itself is read on demand — it outgrew the always-on budget).
| Task | Read this file |
|------|---------------|
| Any conversion/build — what stage, what gate, who owns it | `skills/conversion-runbook.md` |
| Audit / lint / regression-test an existing app (no pipeline) | `skills/existing-app-assurance.md` |
| Deciding what source answers a question, before asking the user | `skills/query-the-model.md` |
| Running a stage-transition checkpoint (2+1 questions) | `skills/checkpoints/checkpoint-*.md` |
| Extract-vs-not, extractor coverage, scoping a large source | `skills/source-triage.md` |
| Building/validating a new extractor for an uncovered stack | `skills/extractor-quality-loop.md` |
| Running or explaining the extraction pipeline | `skills/migration-pipeline.md` |
| Mendix module boundaries (Stage 3, before `create module`) | `skills/modularize-domain.md` |
| Architecture blueprint: diagrams, fit-gap, marketplace, security, NFRs, integrations | `skills/architecture-blueprint.md` |
| UI/brand layer: design system, wireframes, branding interview | `skills/design-artifacts.md` |
| BRDs + architecture → ordered build plan | `skills/brd-to-build-plan.md` |
| Per-module build discipline (gates, coverage checklist) | `skills/iterative-build-loop.md` |
| Writing/validating/enriching BRD JSON | `skills/brd-generation.md`, `skills/brd-validation.md` |
| Document folder scan / Excel-Word-PDF extraction | `skills/document-discovery.md`, `skills/kb-generation.md` |
| OS XML structure | `skills/source-os11.md` + `skills/os-xml-schema.md` |
| MDL microflow scripting patterns | `skills/mdl-cookbook-microflows.md` |
| Page/snippet pre-flight (wireframe → tokens → StyleGallery) | `skills/ui-preflight-pages.md`, `skills/learned-stylegallery.md` |
| Diagnosing a mxcli CLI error | `bug-logs/mxcli-bugs.md` |
| Generating a new project's CLAUDE.md | `skills/bootstrap-project.md` (run `mxcli init` FIRST — init overwrites, bootstrap merges) |
| Setting up dev-process subagents (ba/architect/mdl/gate/test/review) | `skills/agent-roles.md` |
| Past process decisions | `process/process-learnings.md` |
Migration assessment (`assess-migration`) is **bundled with mxcli** (`.ai-context/skills/`); `skills/assess-migration.md` here is a pointer plus toolkit-specific deltas only — the toolkit never duplicates bundled skills.
## Pipelines (extraction tooling — code lives in this repo)
| Source platform | Pipeline | Run |
|-----------------|----------|-----|
| OutSystems | `pipelines/outsystems/` | `cd pipelines/outsystems/pipeline && npm install` — see its `README.md` / `pipeline-guide.html` |
| Java + Angular / Spring Boot | `pipelines/java-angular/` | `cd pipelines/java-angular/pipeline && npm install` — see its `README.md` |
| Node/Express + React | `pipelines/node-express-react/` — **regex-based, proven on one source shape only; read its README's "Known gap" sections first** | `cd pipelines/node-express-react/pipeline && npm install` |
`node_modules/` is gitignored — `npm install` locally per pipeline. Set local paths in `pipelines/<x>/pipeline/config.json`; **never commit real local paths**. Curated sample outputs live under each pipeline.
## Consuming this toolkit (reference model)
Clone once, point projects at it — no copies, no drift:
```
git clone https://github.com/MendixMau/mxcli-project-toolkit.git ~/Mendix/mxcli-project-toolkit
```
Each project scaffolded with `bin/init-project.sh` gets the **Baseline routing** table rendered into its `CLAUDE.local.md` (kept current by `bin/sync-project.sh`); its `CLAUDE.md` carries only a short pointer to that file, never a copy of the table (`skills/bootstrap-project.md` Step 2). Only a project with no `CLAUDE.local.md` convention copies the table from `README.md` into `CLAUDE.md` directly. For a self-contained handoff, use a git submodule.
**Company-specific material never lives here either** — own conventions, client-named learnings, house lint rules, approved MPKs go to a *company brain*, a private repo instantiated from `templates/company-brain/` (`skills/company-brain.md`). The public toolkit never cites one: `bin/check-no-private-citations.sh` runs in CI.
**Project output never lives here** — `analysis/`, `sources/`, `knowledge-base/`, `*.mpr` are gitignored. A project's build plan, `PROJECT.md`, and session notes live in that project's own repo; promote reusable patterns into `skills/learned-*.md` instead of accumulating project docs here.
## Writing in this toolkit — generic first
**Authoring rule. This section governs work *on* this repo. It is not shipped guidance:
consuming projects load their own generated `CLAUDE.local.md`, and the per-tool files
(`AGENTS.md`, `.github/copilot-instructions.md`, `.cursorrules`, `.windsurfrules`) are
POINTERS, never copies — see `project-bin/check-root-clean.sh`.**
> **Build it generic. Source-specific work stops at the extractor. Everything downstream of
> the BRD must work for any input — extracted code, documents, SME interview, or requirements
> typed by hand — and therefore lives in `bin/`, not in `pipelines/<platform>/`.**
The BRD is the platform boundary. Upstream of it a source is OutSystems, Java, or a folder of
epics. Downstream of it there are only entities, microflows, pages and use cases. This repo
serves four entry modes (migration, requirements-driven, greenfield, change an existing app) plus à-la-carte use, and
only the first of those runs a pipeline at all.
**Where things go.**
| Kind of thing | Home | Why |
|---|---|---|
| Reading a specific source shape | `pipelines/<platform>/extractors/` | genuinely per-stack; `extractor-quality-loop.md` governs building one for a new stack |
| Anything reading BRDs | `bin/`, via `bin/lib/discover-brds.sh` | BRDs arrive from extractors, `kb-generation.md`, SME interviews, or by hand — the reader must not care |
| Judgement, verdicts, gates | `skills/` | `skills-over-scripts.md` is the governing rule and it wins over this one |
**Specialisation is the consumer's job, and it is already supported** — `extractor-quality-loop.md`
(any stack: interface, validator contract, `run.sh`, 6 scored dimensions, 95% gate) plus three
worked pipelines to copy. Do not add a scaffold that stamps those files out; a prose spec and
worked examples are the correct form here.
**Why this is written down (2026-08-19).** `generate-enrichment-report.js` — the Stage 2 surface
— was built three times, once inside each pipeline, because nothing said it shouldn't be. The
three copies drifted onto three different BRD shapes (`module` vs `modules[]`, `attributeCount`
vs `attributes[]`, `a.to` vs `target`), so a BRD written by hand per `brd-generation.md` rendered
a page of `undefined` and zeros that looked complete. Requirements-driven and greenfield projects
got no Stage 2 surface at all, though `conversion-runbook.md` promises one unconditionally. The
same disease had already been diagnosed one layer down a week earlier — see the header of
`bin/lib/discover-brds.sh`: *"That is what duplication costs: the fix does not travel."*
**Before adding any file under `pipelines/`, ask: does this read a source, or does it read a
BRD?** If it reads a BRD, it is in the wrong directory.
## Shipping an instrument — field-proof before merge
Every defect in the F-042 cluster (2026-08-28) shipped the same way: an instrument written
against **imagined** input and merged without ever executing against real input. The
selftests all passed — selftests prove the logic, and the logic was fine; what was wrong was
the assumptions. So, before a new or materially changed instrument under `bin/`,
`project-bin/` or `project-tests/` merges:
1. **Golden input is captured, never hand-written.** A parser of tool output (`SHOW PAGES`,
`SHOW MODULES`, mxbuild logs) gets a verbatim capture from a real model committed beside
its fixture, with the parsed counts asserted. Hand-written test input encodes the same
assumption the code makes — F-042's header-row bug (6 real pages counted as 13) was
invisible to anything but real output, because real output had columns nobody imagined.
2. **Both layouts.** Paths resolve through `_common.sh` / config-derived roots, probed on
single-tree AND two-tree (`.mpr` under `app/`) checkouts. F-020 and F-042 are the same
bug found twice, three weeks apart, in different files.
3. **Both platforms.** Bash runs under Git Bash on Windows, where MSYS maps `foo` →
`foo.exe` transparently — but Node does not: never `execFileSync` a bare wrapper name
without an `.exe`/`.cmd` probe or env override. Guard mac-only tools (`sips`,
`/usr/libexec/java_home`) behind `mxtk_platform` / `process.platform`.
4. **One field run, cited.** The commit that ships an instrument names the real project it
ran against and what it measured there. An instrument nobody has run is a claim, not a
check.
5. **A producer for every consumer.** If the instrument reads an artifact
(`page-scope.json`, `stack.env`), say in its header which **mandatory** chain step
produces that artifact — "documented in a skill" is how design-audit.js sat installed
and never ran for an entire build. If no mandatory step produces it, wire one before
shipping the consumer.
For an instrument that **blocks** (a guard, a refusing gate), two extra rules — learned when
a context guard blocked a session that had done everything right except use the guard's own
checkpoint script, and an expert user's reaction was "I can't even compact?":
6. **A blocking guard must accept evidence it did not itself create** — a recent commit, a
recently modified handoff doc — never only its own stamp file.
7. **A guard must never block the action that resolves it.** Blocking the remedy is
perverse: warn once, then let it through.
## Merging a PR — independent review before every merge
**A merge go from the maintainer is not a review.** Before any PR merges, an agent that did
**not** write it reviews the diff and returns a verdict. The builder's own report and green CI
never count: the builder grades its own work, and CI proves only the tests the builder wrote.
1. **Who reviews, and with what.** A fresh agent given the PR diff, the linked issues and the PR
body's description of *what* changed — never the builder's verdict, risk assessment or "all
checks pass" claims. One reviewer per PR, plus one composition check when several PRs in a
batch touch the same files. The reviewer may run the fixtures the PR adds or that target its
changed files, in a detached worktree — that is part of the review the maintainer asked for, not
a free-standing test run (see "Testing this toolkit").
2. **What it answers, each with file:line.** *Value*: does it fix the issue as reported, and is
anything overbuilt? *Regression risk*: what changes for an existing project after
`sync-project.sh` (new FAIL / BLIND / refusals, exit codes, output another tool parses, gate
verdicts that flip), across both layouts and macOS / Git Bash? *Correctness*: real bugs, tests
that do not test what they claim, hand-written golden input where a capture was required.
*Context cost*: words added to always-loaded skill files — re-measured by the reviewer, not taken
from the PR body.
3. **Verdict.** `MERGE`, `MERGE-AFTER-FIX` (exact fixes listed; the fixes are pushed and
re-reviewed before merge) or `HOLD` (goes back to the maintainer with the reason). Only `MERGE`
merges.
4. **Record the verdict durably.** The squash commit body carries a line
`Review: MERGE — <one-line top finding or "no findings">`, and the merge announcement repeats it
next to CI. A merge with no `Review:` line was not reviewed.
5. **No exceptions for size.** A one-line fix, a docs-only PR and an outside contributor's PR all
get the same review. "Small", "only skill text" and "CI is green" are how the exceptions start.
The one exception is guard rule 7's: a fix whose only purpose is to unblock master (a broken CI
check, a leak guard that blocks every commit) may merge first, with its review within 24 hours
and a `Review: PENDING (unblock)` line; the follow-up verdict is recorded as a comment on the
merged PR (and a follow-up fix PR if it finds anything), since a squash commit cannot be edited.
**Why (real incident, 2026-10-13).** Nine PRs merged in one day on the builders' own reports and
green CI, each carrying its builder's "all checks pass". When the maintainer asked whether value
and regression risk had been checked, the honest answer was no: nobody who had not written the
code had read it. The after-the-fact reviews found, among others, a journey preflight that made
every synced project's journey rung FAULT, and an exec lock that let three writers in at once. The same day showed the cost of unread diffs in another form — the always-loaded
skill text hit its 80,000-word cap because each PR added words that nobody outside the PR weighed.
## Always-loaded skill text — the word budget
`bin/render-routing.sh --check` caps the always-loaded (baseline) skill set at
`MXTK_BASELINE_BUDGET_WORDS` — a ratchet, explained in that script's header: lowered as text moves
out, never raised to admit growth. Room is made by **moving** text, never by deleting the text
that does the work.
1. **Protected — never removed to make room:** rules, STOP rows, denominators / completion
criteria, and the one-line failure example each rule is paired with. Removing one needs its own
PR that argues the rule itself is obsolete, with an independent review.
2. **Movable:** incident narratives, worked examples, tables of past numbers, and a rule stated a
second time in another baseline file. Each moves to an on-demand file and leaves a one-line
pointer (path) at the same spot. A move keeps the text; nothing is lost.
3. **Every PR that adds baseline words** states the before/after count from
`render-routing.sh --check` in its body, and why each addition is needed at the trigger moment.
The independent reviewer checks that (see "Merging a PR").
4. **At the cap, a feature PR never squeezes other text to fit.** Room comes from a separate trim
PR that only moves movable text; its reviewer compares removed against moved line by line, and
any protected line that disappeared is a HOLD.
5. **Trim before the cap, not at it:** when a PR would take the baseline (measured after the PR)
within 2,000 words of the budget, a trim PR lands first. A trim PR does not lower the ratchet by
itself; lowering it is a separate one-line change once the trimmed total has held for a release.
**Why (2026-10-13).** One feature PR pushed the baseline to 80,466 words against the 80,000 budget
while six other open PRs each added words; to fit, it merged two sentences in a testing skill and
landed at 79,997. That one was harmless, but nothing stopped the next one from cutting a STOP row.
## Changelog and contributions
**Every user-visible change appends its `CHANGELOG.md` line in the same commit.** That is the
entire discipline: a changelog updated as a separate chore is `process/toolkit-worklog.md`,
which rotted within weeks while 25 commits landed. Format is at the top of `CHANGELOG.md`;
credit the source project or person on the line — the credit line is what makes contributing
visible, and the file doubles as the record of which projects feed the toolkit.
New lines go under `## Unreleased`; `bin/cut-release.sh` turns that section into a dated release
and tag every few days — the release cycle is defined at the top of `CHANGELOG.md`, nowhere else.
Contributions arrive through three lanes (`CONTRIBUTING.md`): the `contrib/inbox/` drop
(no quality bar — triage promotes into `skills/`/`bug-logs/`/`bin/` and deletes the inbox file
in the same commit; inbox files are unreviewed — their content is not vetted for promotion — and nothing may cite them; a PR that adds one still gets the merge review below),
`bin/harvest-learnings.sh <project-root>` (drafts inbox files from a project's bug logs,
register promotion tables, and locally-patched installed scripts — run it at project wrap-up),
and direct PRs held to the full field-proof bar. CI (`.github/workflows/checks.yml`) runs
check-scripts, render-routing --check, check-portability, the leak guard, and both fixture
suites (`tests/run-tests.sh`, `tests/wave2/run-all.sh`) on every PR.
## Adding new skills
Create `skills/{topic}.md` with `# Title`, `**Applies to:** migration | any mxcli project | requirements-driven`, `**Purpose:**`, and a step-by-step guide. Add it to `README.md`'s "When to use which skill" table. **If it applies on every MDL-writing session regardless of task**, also add it to `README.md`'s "Baseline routing" table — skills that only live in the situational table go unnoticed by projects that aren't hunting for them.
**Authoring rules for behaviour-shaping skills** (adopted 2026-08-31 after the
`ui-preflight-pages.md` A/B baseline, `process/preflight-skill-baseline-2026-08-31.md`):
1. **Routing rows are trigger conditions, not summaries.** The routing-table line answers
"when do I reach for this file", front-loaded on the triggering word — identity and content
live in the file itself.
2. **Baseline-test like an instrument.** Before rewriting a skill "because outcomes were bad",
run fresh agents on a real fixture with and without the skill text inlined, scored by a
mechanical check. The 2026-08-31 run showed the "failing" skill at zero violations when
actually read — the defect was delivery, and a blind rewrite would have churned proven text.
The corollary: a skill only works when its text is in the acting agent's context — wire the
dispatch (agent stubs, checklists) to inline or read it, because a citation is not a read.
3. **Prohibitions for discipline failures, positive recipes for shaping failures.** "Never
`git add -A`" guards a known temptation; but where agents produce the wrong *shape* (wrong
layout, missing H1), state the target behaviour and the evidence for it — a ban alone keeps
the wrong pattern in view.
4. **Completion criteria carry denominators.** A step ends on a checkable bound ("all N
gallery files listed", "0 violations from the shell check", "NOT RUN is legal, silence is
not") — a step ending in prose nobody can fail is the unfalsifiable-checklist defect the
obligation check exists to prevent.
5. **No caches of another project's environment.** A class table, file inventory, or path list
copied into a skill goes stale the day the next project scaffolds differently; instruct
"list/grep the actual file" instead (both stale tables in `ui-preflight-pages.md` were
flagged mid-run by the very agents following them).
6. **Write for the trigger, then stop.** A skill is retrieved mid-task by an agent with a
specific trigger; everything not needed at that moment is dilution — one skill, one
situation. (The arm-C verification of the 2026-08-31 baseline saw a possible dilution
effect from ~800 words of growth in a single revision.)
7. **Show the failure, not just the rule.** Pair each rule with the concrete bad output it
prevents — a before/after or a verbatim wrong answer. This repo does it consistently in
`CLAUDE.md` incident notes and inconsistently inside `skills/*.md`; the skills are where
the acting agent reads.
8. **Test retrieval, not just content.** A skill can be perfect and unreachable: periodically
check that its routing line actually fires for the phrasings users and agents really
produce (rule 1 makes the row a trigger condition; this rule is the check that the trigger
works). The coverage audit in `render-routing.sh --check` catches the unrouted case
mechanically; wrong-trigger routing it cannot see.
`tests/advisory/authoring-audit.py routing` is that check (BM25 over trigger text vs the
labelled `routing-phrasings.tsv`); its `dupes` lists rules stated twice. Advisory, never a gate.
(Rules 6–8 adapted 2026-08-31 from public skill-authoring guidance in obra/superpowers
`writing-skills` and Matt Pocock's `writing-for-agents` — patterns only. Their fourth
point, "degrade to a pointer", is already this repo's "single source of the rule; every
other mention defers here" convention plus rule 5 — one principle, stated there.)
## Committing in this clone — never `git add -A`
Several Claude sessions routinely have this clone open at once, in **one working tree and
therefore one git index**. So:
> **Stage and commit in the same breath, or pass explicit paths: `git commit -- <paths>`.
> Never `git add -A`.** Before resetting a commit, look at what else is in the index.
**Why (real incident, 2026-08-21).** A session ran `git commit` with no pathspec while another
session had 29 unrelated files staged. Git takes the whole index, so that work landed inside a
commit titled "Log BUG-95: …". The resulting `git reset` restored the 29 files — and silently
took the committer's *own* bug-log entry out of the working tree with it, where it survived
only in a dangling commit. Authorship is not readable from mtimes either: eight files were
mislabelled as a peer's until someone read the diffs.
Same root cause as the testing rule below — a shared, moving tree makes any whole-tree
operation untrustworthy. Coordinate with `ListAgents` / `SendMessage` before committing, and
if a peer's untracked scratch file trips the pre-commit leak guard
(`bin/check-no-client-data.sh` scans untracked files too), ask them to fix it. Do not gitignore
it and do not narrow the denylist.
## Testing this toolkit — scoped by default
`tests/wave2/run-all.sh` runs every fixture in the suite. **Do not run it as a reflex "did I
break anything" check after a normal edit.** Run only the fixture(s) that exercise the file(s)
you actually touched (e.g. changed `bin/open-questions.sh` → run `test-open-questions.sh`;
changed `bin/gate-check.sh` → also run `test-bug03-gates.sh`, `test-source-sufficiency-gate.sh`, `test-source-ledger.sh`,
whatever else targets it). Grep `tests/wave2/` for the filename you changed if you're unsure
which fixtures cover it.
There are two separate suites. `tests/run-tests.sh [-v]` is self-contained (guard-script
fixtures with no external argument). Each `tests/wave2/test-*.sh` fixture instead takes its
subject-under-test as `$1` — read its own `usage:` comment rather than assuming a fixed target;
several take something other than `gate-check.sh` (`exec.sh`, `sync-project.sh`,
`check-docs-numbering.sh`, a skill file). Once you have permission for a single fixture (see
below), run it as e.g. `bash tests/wave2/test-bug02-register.sh bin/gate-check.sh`.
**Why:** this repo is routinely edited by more than one agent session at once (real incident,
2026-08-19 — a peer session mid-edit reported known, unrelated failures already present in the
shared working tree). A full-suite run over a moving target mixes someone else's in-flight
breakage into your result and tells you nothing trustworthy about your own change — it looks
like verification but isn't. It is also strictly slower for no more signal than the scoped run,
since the untouched fixtures were never at risk.
**Run the full suite only when the user explicitly asks for it in that message. There is no
other trigger, and none of the following is one: about to open a PR, closing out a multi-file
change, or a change that looks like it had wide blast radius.** Those read as licence to
self-authorize, and they were — the rule said "ask first" and then handed out three reasons not
to. Wide blast radius is a reason to pick the right *scoped* check, not to run everything: for
`bin/lib/skill-routing.tsv` and its re-render (README.md, ROUTING.md, every agent template, and
`gate-check.sh`'s stage map at once) that check is `bin/render-routing.sh --check`.
**One standing exception: the independent reviewer of a PR** (see "Merging a PR") may run the
fixtures the PR adds or that target its changed files, without asking, in a detached worktree of
the PR branch. That isolation is what avoids the shared-tree problem above; the reviewer still
never runs the full suites.
**Outside that reviewer exception, asking is also required for a single fixture** — name it, say what it will tell us, and wait.
Where a fixture's assertions are greps over a file you changed, prefer verifying by inspection:
read the fixture, replicate its greps against the changed file, and report that. Same signal, no
suite run, no interference with whatever another session has in the tree right now.