@sylad/cadence 1.13.0 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +3 -0
  4. package/agents/reference-planner.md +26 -0
  5. package/dist/cli.js +174 -20
  6. package/dist/config-set.js +6 -3
  7. package/dist/config.js +47 -9
  8. package/dist/corrections.js +1 -0
  9. package/dist/git.js +11 -1
  10. package/dist/lead.js +5 -1
  11. package/dist/orchestrate/accept.js +63 -1
  12. package/dist/orchestrate/baseline.js +39 -3
  13. package/dist/orchestrate/changelog-merge.js +40 -1
  14. package/dist/orchestrate/command.js +97 -10
  15. package/dist/orchestrate/cycle.js +242 -63
  16. package/dist/orchestrate/decisions.js +2 -0
  17. package/dist/orchestrate/integrate.js +26 -3
  18. package/dist/orchestrate/launch.js +2 -0
  19. package/dist/orchestrate/state.js +1 -1
  20. package/dist/orchestrate/workers.js +51 -0
  21. package/dist/orchestrate/worktree.js +15 -4
  22. package/dist/perf.js +9 -1
  23. package/dist/plan-commit.js +88 -0
  24. package/dist/plan.js +41 -3
  25. package/dist/ref/analyse.js +34 -4
  26. package/dist/ref/command.js +2 -1
  27. package/dist/ref/coverage.js +136 -0
  28. package/dist/ref/diff.js +99 -15
  29. package/dist/release.js +5 -1
  30. package/dist/session.js +7 -1
  31. package/dist/stale-notes.js +22 -0
  32. package/dist/verify-gate.js +138 -0
  33. package/docs/deliver.md +37 -0
  34. package/docs/fonctionnement.md +2 -1
  35. package/docs/lead.md +5 -4
  36. package/docs/orchestrate.md +57 -11
  37. package/docs/overview.md +3 -2
  38. package/docs/perf.md +3 -1
  39. package/docs/raf.md +48 -1
  40. package/docs/ref.md +61 -3
  41. package/docs/releasing.md +5 -3
  42. package/docs/session.md +8 -0
  43. package/docs/skills.md +1 -0
  44. package/docs/spec.md +2 -0
  45. package/docs/stabilite.md +2 -0
  46. package/docs/whats-new.md +6 -0
  47. package/package.json +3 -3
  48. package/skills/lead/SKILL.md +6 -4
  49. package/skills/orchestrate/SKILL.md +1 -1
  50. package/skills/session-close/SKILL.md +2 -0
  51. package/skills/session-start/SKILL.md +2 -1
@@ -139,8 +139,8 @@ keeps its `/` (`maritime-atlas:Q4/accueil-4-ux12@haiku`); this holds for `--dry-
139
139
  same for the lot folder and the dry-run briefs); ids without `/` are unchanged.
140
140
 
141
141
  You choose the lots; the order is the order given (one queue per repository, as many repositories at the
142
- same time as the session cap allows — 2 by default; the `orchestrate.worktrees: true` mode (on in cadence's own `cadence.yaml`, off where the key is absent) plays the lots of one repository in parallel, each in its own worktree — « One worktree per lot »). `@haiku|@sonnet|@opus` sets the model of the implementation and corrections of that
143
- lot (default Sonnet; reviews are Opus, except the light review below; Haiku only when you write it, for a mechanical lot). Run it
142
+ same time as the session cap allows — 2 by default; the `orchestrate.worktrees: true` mode (on in cadence's own `cadence.yaml`, off where the key is absent) plays the lots of one repository in parallel, each in its own worktree — « One worktree per lot »). `@<model>` sets the model of the implementation and corrections of that
143
+ lot (`@haiku`, `@sonnet`, `@opus`, or any other name, passed as it stands to `claude --model` like `orchestrate.escalation.model`: `@fable`, `@claude-fable-5-1` — L345; the dry-run warns, without refusing, for a name outside the three aliases; default Sonnet; reviews are Opus, except the light review below; Haiku only when you write it, for a mechanical lot). Run it
144
144
  in the background and read `--status`: it prints one line per transition and the final table.
145
145
 
146
146
  **Following a wave (L49)**: `--status --watch` clears the screen and redraws the live waves and the table every
@@ -166,11 +166,13 @@ usability (code only when the lot is not `visible`). Failing tests (reported red
166
166
 
167
167
  **A project without `orchestrate.test` is flagged (L327)**: without the key the wave never runs the suite itself — only the review does, if it decides to (it ran 12 test files of a project on 2026-10-25 and a red test reached « ready »). `cadence orchestrate --dry-run` writes one line per repository, under the first lot of that repository: `<repo> · porte de tests absente : orchestrate.test n'est pas déclaré, la vague ne lancera pas la suite (seule la revue le fera si elle le décide) · commande probable : npm test (à déclarer en orchestrate.test)`, and [`cadence lead tour`](lead.md#lead-tour) writes the same line for every project of the folder. The probable command is read from the repository, never run: `npm test` when `package.json` has a `scripts.test` (the `npm init` placeholder « no test specified » does not count), `pytest` when `pyproject.toml` names pytest, `mvn test` when there is a `pom.xml`; with none of them the line carries no command. A `cadence.yaml` that cannot be read gives no such line (it has its own error). Declaring `test:` (and `build:`) in the project's `cadence.yaml`, once the command is known to be green, removes the line.
168
168
 
169
- **Tests already red at the start (L213)**: a lot is no longer handed back « tests rouges » for failures that were there before it. When `orchestrate.test` fails (or the session reports red tests), the program reads the failing tests from the runner's output (vitest/jest `FAIL`/`●`, pytest `FAILED`, go `--- FAIL:`, TAP `not ok`), replays the same command at the sha the lot started from (the parent of the lot's oldest work commit, plan-only commits left out, else the `HEAD` before its first session) in a throwaway `git worktree` outside the repository, and compares: if every failing test was already red, it is not a defect of the lot — no correction pass, the code review takes place, the reviewer is told, and the final table lists them apart (`déjà rouge au départ (<sha>) : <test>`, 30 at most then a count). The worktree gets links to the repository's ignored folders at any depth, such as `.venv` and, in a monorepo, `packages/*/node_modules`, when their parent exists at that sha; build outputs such as `dist` or `coverage` are left out. Each `node_modules` is rebuilt entry by entry, every entry linked to the live one except the workspace links of npm, pnpm and yarn (`node_modules/@x/shared -> ../../packages/shared`, a link to code of the repository outside any `node_modules`), which are remade towards the worktree's own copy so that the start sha is tested against its own sources, not the lot's (a workspace package that does not exist at the start sha gets no link). Known limit: only `node_modules` links are remade; whatever points into the live repository by an absolute path, such as an editable Python install (`.venv` with a `.pth` file naming the live `src`, `pip install -e`) or an absolute symlink in another ignored folder, still runs the lot's code in the baseline. Likewise, a workspace package consumed through its build output (`main: dist/...`) cannot load in the baseline worktree: output dirs are not linked, so such a monorepo keeps the pre-L213 « tests rouges » outcome unless `orchestrate.build` is declared (the build is then played first in the baseline worktree, see below). A lot resumed in a later wave keeps the same baseline: the sha is the parent of its oldest work commit, not the `HEAD` of the new wave, so a failure the lot itself introduced in an earlier wave is never excused. A test red now and not at the start stays a blocking finding, and only that one is given to the correction (the others are counted as « already red »). When the output names no test, or the baseline cannot be run, the tests stay red as before (no comparison is guessed). A failure the output does not tie to a test (unhandled error, a spec file that fails to load such as jest « Test suite failed to run » or vitest `FAIL a.test.ts [ a.test.ts ]`, coverage threshold) keeps the tests red. Without `orchestrate.test` the program cannot replay anything: a session that reports green tests and lists under `echecsDeBase` the failures it saw at the start sha gets them listed apart, marked « non vérifié » in the table, and the review takes place; a session that reports the tests red stays red, the brief asking it not to report red for base failures alone — unless (L215) the report says so in a structured way: with `vert: false`, a non-empty `echecsDeBase` and an empty `nouveauxEchecs` (the list of tests red now that were not red at the start sha), `vert: false` is not a reason to correct, the lot carries a « non vérifié » warning, the reviewer is told that the baseline is unverified and the review takes place. A non-empty `nouveauxEchecs` with `vert: false` triggers a correction pass (the new failures are in the finding); an absent `nouveauxEchecs`, or an empty `echecsDeBase`, keeps the correction. The program never guesses from the text of the result (« 24 failed »). The `implement` and `fix` briefs ask for `echecsDeBase` and `nouveauxEchecs`.
169
+ **Tests already red at the start (L213)**: a lot is no longer handed back « tests rouges » for failures that were there before it. When `orchestrate.test` fails (or the session reports red tests), the program reads the failing tests from the runner's output (vitest/jest `FAIL`/`●`, pytest `FAILED`, go `--- FAIL:`, TAP `not ok`), replays the same command at the sha the lot started from (the parent of the lot's oldest work commit, plan-only commits left out, else the `HEAD` before its first session) in a throwaway `git worktree` outside the repository, and compares: if every failing test was already red, it is not a defect of the lot — no correction pass, the code review takes place, the reviewer is told, and the final table lists them apart (`déjà rouge au départ (<sha>) : <test>`, 30 at most then a count). The worktree gets links to the repository's ignored folders at any depth, such as `.venv` and, in a monorepo, `packages/*/node_modules`, when their parent exists at that sha; build outputs such as `dist` or `coverage` are left out. Each `node_modules` is rebuilt entry by entry, every entry linked to the live one except the workspace links of npm, pnpm and yarn (`node_modules/@x/shared -> ../../packages/shared`, a link to code of the repository outside any `node_modules`), which are remade towards the worktree's own copy so that the start sha is tested against its own sources, not the lot's (a workspace package that does not exist at the start sha gets no link). Known limit: only `node_modules` links are remade; whatever points into the live repository by an absolute path, such as an editable Python install (`.venv` with a `.pth` file naming the live `src`, `pip install -e`) or an absolute symlink in another ignored folder, still runs the lot's code in the baseline. Likewise, a workspace package consumed through its build output (`main: dist/...`) cannot load in the baseline worktree: output dirs are not linked, so such a monorepo keeps the pre-L213 « tests rouges » outcome unless `orchestrate.build` is declared (the build is then played first in the baseline worktree, see below). A lot resumed in a later wave keeps the same baseline: the sha is the parent of its oldest work commit, not the `HEAD` of the new wave, so a failure the lot itself introduced in an earlier wave is never excused. A test red now and not at the start stays a blocking finding, and only that one is given to the correction (the others are counted as « already red »). When the output names no test, or the baseline cannot be run, the tests stay red as before (no comparison is guessed). A failure the output does not tie to a test (unhandled error, a spec file that fails to load such as jest « Test suite failed to run » or vitest `FAIL a.test.ts [ a.test.ts ]`, coverage threshold) keeps the tests red. Without `orchestrate.test` the program cannot replay anything: a session that reports green tests and lists under `echecsDeBase` the failures it saw at the start sha gets them listed apart, marked « non vérifié » in the table, and the review takes place; a session that reports the tests red stays red, the brief asking it not to report red for base failures alone — unless (L215) the report says so in a structured way: with `vert: false`, a non-empty `echecsDeBase` and an empty `nouveauxEchecs` (the list of tests red now that were not red at the start sha), `vert: false` is not a reason to correct, the lot carries a « non vérifié » warning, the reviewer is told that the baseline is unverified and the review takes place. A non-empty `nouveauxEchecs` with `vert: false` triggers a correction pass (the new failures are in the finding); an absent `nouveauxEchecs`, or an empty `echecsDeBase`, keeps the correction. With `orchestrate.test` declared (L361), the same structured report is honoured when the program's own gate is green (`orchestrate.test` exits 0): `vert: false` with a non-empty `echecsDeBase` and an empty `nouveauxEchecs` goes straight to the review, with a warning, without a correction pass and without the « tests rouges » escalation; without those two fields the claim of red tests still triggers the correction. The program never guesses from the text of the result (« 24 failed »). The `implement` and `fix` briefs ask for `echecsDeBase` and `nouveauxEchecs`.
170
170
 
171
171
  **The build is played before the tests (L333)**: the gate of the wave runs `orchestrate.build` first, then `orchestrate.test`, because tests that read the build output (end-to-end tests over `dist`) are red without it — on 2026-10-25 three projects had to declare `test: astro build && npm test` (13 tests out of 15 red without `dist`), which played the build twice on every pass. A red build hands the lot back as a single « build » finding and the tests are not run. The L213 baseline plays the build first in its throwaway worktree too, then the tests: without it the start sha, with no `dist`, would excuse a test wrongly or turn everything red. A build that fails at the start sha gives no baseline (the build says nothing about the tests there): the tests stay red as before, no comparison is guessed. Without `orchestrate.build` nothing changes. A project that wrote `build && test` into its `orchestrate.test` can drop the duplicate build.
172
172
 
173
- **Each session gets its share of the machine (L350)**: the wave computes `CADENCE_TEST_WORKERS = max(1, cores ÷ session cap)` (`--max-sessions` or `CADENCE_MAX_SESSIONS`; 32 cores and a cap of 4 give 8; a `CADENCE_TEST_WORKERS` already set in the environment that launches the wave wins when it is a positive integer). It is put in the environment of every session and of every command of the gate (tests, build, baseline), recorded as `testWorkers` in the wave state, and the `implement`, `fix`, minors and librarian briefs tell the session to respect it: `vitest run --minWorkers=1 --maxWorkers=$CADENCE_TEST_WORKERS` (vitest 2 refuses `--maxWorkers` alone, its default minimum being the core count: « options.minThreads and options.maxThreads must not conflict »), `jest --maxWorkers=$CADENCE_TEST_WORKERS`, `pytest -n $CADENCE_TEST_WORKERS`. The gate command itself must honour it for the share to bind: cadence's own `vitest.config.ts` reads it (`poolOptions.forks.maxForks`, `threads.maxThreads`); in another project write `orchestrate.test: npx vitest run --minWorkers=1 --maxWorkers=$CADENCE_TEST_WORKERS` (or the equivalent of its runner).
173
+ **A red test is replayed alone before it counts, only on a declared command (L354)**: on 2026-10-26, with 32 cores and a cap of 4 sessions, every suite ran vitest at 31 workers, and four correction passes (L344 11 min, L342 9, L349 7, B28.12 4) found nothing to fix — each time « a test that passes alone ». cadence does not guess how a file is replayed on your runner (a first version appended the files to `orchestrate.test` and, on a composed gate, excused a real regression), so the replay is **off unless the project declares its command**: `orchestrate.testReplay`, where `{files}` is replaced by the files of the red tests, e.g. `testReplay: "npx vitest run --maxWorkers=$CADENCE_TEST_WORKERS --minWorkers=1 {files}"` (a value without `{files}` is refused when the file is read). **Without it nothing is replayed and nothing is held for unstable**: a red test is counted as before. With it, a red test of the gate (`orchestrate.test`, played after every writing session and at the rebased tip) that is absent from the baseline is **replayed alone, twice at most**, by that command, in the lot's worktree (vitest names `file > suite > test`, pytest `file::test`, jest `FAIL file` and `● Suite › test`, the latter tied to the `FAIL` line that precedes it in the same output and replayed with that file; the file is the part before the first `::` or ` > `, so either of them inside a test title is not part of the path). Green at a replay, the test is **« instable sous charge »**: it is not counted, it triggers no correction pass, even when the session reported its tests red (`vert: false`: a session that plays the suite under the same load sees the same red; the announcement is not held against the lot when every red of the gate is unstable, as it is not when every red was already red at the start), the lot goes on to its review, and the trace is a lot warning (so a line of the final table), a journal line `<project>:<lot> · N test(s) instable(s) sous charge …`, the lot's `instables` list in its journal, and the `cadence perf` line `tests instables sous charge` (`instables` in `--json`). Red at both replays it is counted as before. A red name that designates no file (a plain `FAIL name`) cannot be replayed alone and is counted at once. **A replay in which the test did not run counts red**: only a replay that exits 0 after running tests proves a test green, and only for the tests whose file appears in the replay's output, as a whole path on a line of the runner — never in the launcher's echo of the command: npm 12 writes `npm notice run 'vitest' run … <files>` on stderr for `npx …` as for `npm test -- …`, npm and pnpm write `> vitest run … <files>`, yarn `$ …`, and those lines name every file asked for, played or not (a runner that silently skips a file it cannot find, such as vitest given a path of another workspace, exits 0 without playing it: that test stays red; with a reporter that does not list files, nothing is ever excused; `a.test.ts` is not found in `src/a.test.ts`); a replay that fails, or that runs no test (the runner's closing line `No test files found, exiting with code`, `No tests found, exiting with code` or `no tests ran in`, even with exit code 0; the same words inside a test title or a test's output do not count), leaves every test it was asked about red. In a vitest workspace the names read `|package| src/a.test.ts > case`: the `|package|` prefix is dropped to find the file. The reviewer is told which tests were red under load (`instables` of the check, also when the baseline holds red tests of its own), so it does not report them, and the correction finding lists only the tests still red after the replays. The baseline run is included: with `testReplay`, a red test of the L213 baseline (the gate played at the start sha, in its throwaway worktree) is replayed alone there, twice at most, before it is retained as « already red at the start »; green at a replay it is dropped from the baseline (a journal line `… · ligne de base <sha> : N test(s) rouge(s) sous charge, vert(s) rejoué(s) seul(s), non retenu(s) …`), so a test the lot really breaks is not excused by a load failure of the start sha. The baseline replays are not the lot's « instables »: they say nothing about the lot's code. **A composed gate is not supported (decision of 26-10)**: `orchestrate.testReplay` is refused when `cadence.yaml` is read if `orchestrate.test` has `&&` or `||` outside quotes, or a parenthesis (sub-shell `( … )`, `$( … )`, function): a first runner that goes red cuts the next one, so a green replay of its tests says nothing of the rest, and a replay command written for one runner of the gate cannot replay the tests of the other. Write the replay for a gate that launches a single runner; without `testReplay` a composed gate is played as before, nothing is replayed. A jest `FAIL` line may carry a duration (`FAIL src/a.test.ts (6.21 s)`) or a project name (`FAIL backend src/a.test.ts`): the file is its last word, and any other `PASS`/`FAIL` line ties no file to the `●` lines that follow. The `FAIL project file` line is itself a red name (`project file`) and is replayed with that file, like the `●` tests under it.
174
+
175
+ **Each session gets its share of the machine (L350)**: the wave computes `CADENCE_TEST_WORKERS = max(1, cores ÷ session cap)` (`--max-sessions` or `CADENCE_MAX_SESSIONS`; 32 cores and a cap of 4 give 8; a `CADENCE_TEST_WORKERS` already set in the environment that launches the wave wins when it is a positive integer). It is put in the environment of every session and of every command of the gate (tests, build, baseline, replays), recorded as `testWorkers` in the wave state, and the `implement`, `fix`, minors and librarian briefs tell the session to respect it: `vitest run --minWorkers=1 --maxWorkers=$CADENCE_TEST_WORKERS` (vitest 2 refuses `--maxWorkers` alone, its default minimum being the core count: « options.minThreads and options.maxThreads must not conflict »), `jest --maxWorkers=$CADENCE_TEST_WORKERS`, `pytest -n $CADENCE_TEST_WORKERS`. The gate command itself must honour it for the share to bind: cadence's own `vitest.config.ts` reads it (`poolOptions.forks.maxForks`, `threads.maxThreads`); in another project write `orchestrate.test: npx vitest run --minWorkers=1 --maxWorkers=$CADENCE_TEST_WORKERS` (or the equivalent of its runner).
174
176
 
175
177
  **Review sized to the lot (L108)**: a lot whose `estimate` is ≤ `orchestrate.review.threshold` (0.25 day by default)
176
178
  is *light*: one single review (Sonnet by default, `code-reviewer` agent, same criteria), no minors pass — the minors are
@@ -180,7 +182,7 @@ chain described above (Opus, two corrections, minors pass). `--dry-run` shows «
180
182
 
181
183
  ```yaml
182
184
  orchestrate:
183
- review: { threshold: 0.25, light: sonnet, full: opus } # defaults; threshold in days (0 = no light lot), models haiku|sonnet|opus
185
+ review: { threshold: 0.25, light: sonnet, full: opus } # defaults; threshold in days (0 = no light lot), models: haiku|sonnet|opus or any name passed as it stands to claude --model (L345)
184
186
  ```
185
187
 
186
188
  `full` is also the model of the UX review and of the single pass of a small lot that is not light.
@@ -192,7 +194,7 @@ orchestrate:
192
194
  escalation: { model: fable } # no default: absent = nothing changes
193
195
  ```
194
196
 
195
- The name is passed as it stands to `claude --model` — no closed list `haiku|sonnet|opus`, and no default value, because the account may not have the model. When the review is still not compliant after the planned passes (`MAX_PASSES`, or `MAX_PASSES_CONVERGING` when it converges), or when the lot would be handed back « tests rouges » after them, the wave plays **one** correction and then **one** fresh code review, both on that model, before handing the lot back. The lot is handed back only if that escalated review is not compliant (`… ; rendu malgré l'escalade <model>`); the escalated correction that leaves the tests red hands it back without a review. A compliant escalated review ends the lot as any compliant review does (minors pass, UX review, `ready`). The UX review keeps its own rule: a lot handed back for a non-compliant UX review is not escalated. The escalation counts as a correction pass (`passes` of the table) and is played at most once per lot, a resume included (`escalation` in the lot's journal).
197
+ The name is passed as it stands to `claude --model` — no closed list `haiku|sonnet|opus` (the same holds for `@<model>` of a lot and for `orchestrate.review.light` / `full`, L345: the three aliases keep their meaning, any other name goes through unchanged, and `--dry-run` prints `⚠ modèle « <name> » hors des alias …` for each such name, without refusing), and no default value, because the account may not have the model. When the review is still not compliant after the planned passes (`MAX_PASSES`, or `MAX_PASSES_CONVERGING` when it converges), or when the lot would be handed back « tests rouges » after them, the wave plays **one** correction and then **one** fresh code review, both on that model, before handing the lot back. The lot is handed back only if that escalated review is not compliant (`… ; rendu malgré l'escalade <model>`); the escalated correction that leaves the tests red hands it back without a review. A compliant escalated review ends the lot as any compliant review does (minors pass, UX review, `ready`). The UX review keeps its own rule: a lot handed back for a non-compliant UX review is not escalated. The escalation counts as a correction pass (`passes` of the table) and is played at most once per lot, a resume included (`escalation` in the lot's journal).
196
198
 
197
199
  The lot's budget is respected: the escalation needs what a correction and its review need (`REVIEW_RESERVE`, as for any correction pass), and the budget of a lot in a project that declares `escalation` reserves its share for it (`ESCALATION_RESERVE`, see **Budget**). When the budget does not cover it, it is **not played** and the outcome says so (`escalade <model> non jouée (budget du lot …)`); the lot is handed back to the lead with its findings. The final table prints one line per escalated lot, `<project>:<lot> — escalade <model> : <outcome>` (`en cours` only while the lot still runs — the table also serves `--status` and a suspended wave —; a lot withdrawn (`--drop`), failed by an error or stopped by an incident of the repository before the model's verdict says `engagée, lot rendu avant le verdict du modèle`: the model did not judge it and it is not counted against the model —, compliant review, lot handed back despite the escalation — for the human or an arbitration of the lead —, not played, or an escalated session that returned nothing (empty output, launch error, cut, timeout; the only case said `session escaladée sans résultat`) — then the model never judged the lot and the lead takes it up, `rendu malgré l'escalade` is said only for a lot handed back on a verdict). A lot whose escalated review was compliant but is handed back later (non-compliant UX review, refused integration) keeps `revue neuve conforme` and says its lot line is the reason, the wave log traces the three moments, and `cadence perf` counts the escalated lots of each window and their outcome (`docs/perf.md`). The price grid of `cadence perf` knows `sonnet`, `opus` and `haiku`: the cost of an escalated step on another model reads `n/d`.
198
200
 
@@ -273,7 +275,7 @@ the first implementation: `precheck → spec → maquette → accept → impleme
273
275
  `#etat-<état>` section per state and a `data-testid` per field; every state has its capture at both widths; everything is committed and the tree is clean. Anything missing is listed back to a fresh `spec` session whose brief names the points, within the lot's session budget: `orchestrate.maquetteReviews` sessions (3 by default) when the mockup review is on (see [The mockup review](#the-mockup-review-before-any-code)), 2 otherwise; if the spec is still not valid on the last one the lot is handed back (`spec non conforme après 3 passes : …`, `après 2 passes` with `orchestrate.maquette: false`).
274
276
  - **A costly choice is a question, not a hypothesis.** A rule marked `hypothese` whose `si_faux` involves a data model or storage, a public or shared API contract or an already published route,
275
277
  a destructive or irreversible action, or a contradiction with a sourced rule, puts the lot in `question` (the program recognises these by keywords as a safety net behind the agent's own
276
- judgement; a keyword right after a negation — « rien d'irréversible », « sans suppression », « pas de paiement » — does not count); `--resume --wave <wave> --answer <lot> "…"` gives the answer to the resumed `spec` session, **and the program itself turns the rules it asked about into `statut: sourcee`** (L297) — `source: réponse du lead, <date>, vague <id>`, no `si_faux`, the sheet committed alone as `spec(<lot>): R3 sourcée par la réponse du lead (orchestrate <wave>)` — before that session starts, so a session that is lost or writes nothing no longer leaves the rule an `hypothese` for the sheet review (seen on 24-10: L279 handed back « fiche non conforme » for R27 although the lead had answered). Only the `statut`, `source` and `si_faux` lines of the rule are edited, the rest of the sheet is kept byte for byte (L302: `[R2]` stays `[R2]`, comments and folded lines stay; a rule written in flow style `{id: R1, …}` hands the lot back instead of being edited). An answer that deletes or changes the rule (« non, supprime R3 », « change en X »: a word of refusal or correction such as `non`, `pas`, `supprime`, `change`, `plutôt`, `remove`, `instead`) sources nothing: the rule stays an `hypothese` for the resumed `spec` session, which applies the answer, and if it does not, the question goes back to the lead. The session only rewrites a rule the lead corrected; only the rules named by the program's question are touched, and an answer to a question of the session itself leaves the sheet alone. If the commit is refused (a hook), the lot is handed back saying so, with no `spec` session played. Every other hypothesis (wording, order of fields, error state, spacing) stays written in the sheet.
278
+ judgement; a keyword right after a negation — « rien d'irréversible », « sans suppression », « pas de paiement » — does not count); `--resume --wave <wave> --answer <lot> "…"` gives the answer to the resumed `spec` session, **and the program itself turns the rules it asked about into `statut: sourcee`** (L297) — `source: réponse du lead, <date>, vague <id>`, no `si_faux`, the sheet committed alone as `docs(<lot>): règle R3 sourcée par la réponse du lead (orchestrate <wave>)` (the `plan.commit` template when set, L343) — before that session starts, so a session that is lost or writes nothing no longer leaves the rule an `hypothese` for the sheet review (seen on 24-10: L279 handed back « fiche non conforme » for R27 although the lead had answered). Only the `statut`, `source` and `si_faux` lines of the rule are edited, the rest of the sheet is kept byte for byte (L302: `[R2]` stays `[R2]`, comments and folded lines stay; a rule written in flow style `{id: R1, …}` hands the lot back instead of being edited). An answer that deletes or changes the rule (« non, supprime R3 », « change en X »: a word of refusal or correction such as `non`, `pas`, `supprime`, `change`, `plutôt`, `remove`, `instead`) sources nothing: the rule stays an `hypothese` for the resumed `spec` session, which applies the answer, and if it does not, the question goes back to the lead. The session only rewrites a rule the lead corrected; only the rules named by the program's question are touched, and an answer to a question of the session itself leaves the sheet alone. If the commit is refused (a hook), the lot is handed back saying so, with no `spec` session played. Every other hypothesis (wording, order of fields, error state, spacing) stays written in the sheet.
277
279
  The agent can also ask a question itself in its report.
278
280
  - **Under the threshold**: if the session finds that the lot changes no screen, it gives the reason in one line (`sousSeuil`), writes no mockup, and the lot goes on to `implement` as before; the reason is
279
281
  kept as a warning of the lot. On resume the spec step is skipped only when a sheet of the lot (`ecran.lot` is the lot) already passes the program's checks (`cadence spec check`, mockup, captures) and carries no costly hypothesis; otherwise the spec is played again. The sheet path in the report is relative to the repository (an absolute or `./` path is normalised, a path outside the repository is a finding).
@@ -354,10 +356,18 @@ session, `accept` (L173): `precheck → spec → maquette → accept → impleme
354
356
  (`git diff <red commit> -- <files>`, empty expected, working tree included, and the fingerprints), and each `acceptation.json` with its version before the session. The only difference tolerated on `acceptation.json` is the `vert` key, and only **the cycle** sets it: a session that changes it, rewrites the
355
357
  file, deletes it, creates one outside the accept step, or modifies a locked test hands the lot back (`verrou des tests d'acceptation : …`) — for a modified test the message ends with `; premier commit fautif : <sha7> <subject>`, the oldest commit after the reference (red commit, or the head before the lot) that touched the sealed path (a deletion or a rename included, renames not followed), and names no commit when the deviation is only in the working tree (L231) — a useful change is a question to the lead or a review finding, never a silent gesture. The lock does not depend on `orchestrate.accept` being declared. **Exception — a delivered lot (`vert: true`):** a later lot that makes the rule evolve may modify the listed tests of that acceptation when **a commit that cites the lot in progress amends the sheet of the screen and comes after the red commit** (a strict descendant of it; the commit may have been made before the wave, and once one exists the exception covers the whole acceptation: the tests may change in the same commit as the sheet, in a later one or in a following session, a `fix` after the `implement` for instance, L231): the sheet is what evolves the rules, and tests and sheet change together. A sheet amended by another lot, by a commit that cites no lot, by a commit that is not after the red commit, or only in the working tree (not committed) opens nothing. The exception never covers the acceptation of the lot in progress (its `vert: true` is set right after the implement): that one keeps the whole lock. Without a sheet change the difference is still a deviation — except one narrow case (L184): a listed file whose content is identical to what it was at the head before the lot's first **work** commit (commits found by the plan's refs) is not a deviation, the lot having taken its own addition back (a lot handed back for writing in a locked file and resumed in a new wave, which then removes that addition). This applies to every acceptation, `vert: false` included, but only to a file that **exists** at that head and now: a file absent at that head (e.g. created by the lot's own red commit) or deleted since is never exempted, so deleting a locked test stays a deviation; nor when the sheet of the screen has changed since that head (an evolution admitted for another lot: going back to the old content would cancel it, not the lot's addition); apart from that, with `vert: false` the whole lock applies, sheet change or not; `acceptation.json` itself stays locked (only its `vert` key moves).
356
358
  - **A sealed test that contradicts the sheet: `cadence spec reseal` (L200).** The lock is total for the lot in progress (the delivered-lot exception above never covers it; for a delivered lot, see the end of this item), so a test that is wrong against its sheet — e.g. the regex `/\d{1,2} /` of an acceptance test rejecting « T3 2027 annoncée », which the sheet allows — would leave the lot handed back with no way out. The way out is the lead's, outside any wave: correct the test in the working tree (the sheet is left as it is: unchanged since the red commit, or amended by commits of the lot and committed, see « A sheet amended in the same lot » below), then `cadence spec reseal <lot> <test> --reason "why the test contradicts the sheet"`. The program checks that the lot is in progress (`doing`; for a delivered lot, see the end of this item), that the test is listed in the lot's sealed `acceptation-<lot>.json`, that the sheet is unchanged since the red commit or amended by commits of the lot (below), that **no other listed test** changed (the lock keeps everything else) and that the test did change; it then redoes the fingerprint of that one test, appends `{ test, raison, date, avant, apres }` (plus `ficheAmendee` for an amended sheet, below) to the `resceaux` key of `acceptation.json`, commits the test and `acceptation.json` (`test(L35): acceptation rescellée, <test> : <reason>`, the new red commit) and the lot's note with the reason (`plan: …`). It is refused during an orchestrated wave (`CADENCE_ORCHESTRATED`, or the repository's wave lock while one of its lots still works, like `cadence deliver`), **while a wave that is not `done` (interrupted, suspended, or still alive with a lot waiting on a question) keeps open any lot of this repository that is still `doing` in this repository's plan (L238)** — the resealed lot, the `--porteur` or any other lot of the repository: the lock hands back every open lot of the repository, so a reseal committed in the meantime was handed back to any of them as the « first offending commit » of the lock (ol-companion L172, then L173). Open means any non-final state in the wave (a question waiting for its `--answer`, a lot suspended by the quota or the budget, a queued lot, a session running): a stopped wave holds no lock, but its `--resume` replays those lots. The filter is by repository (the repository of the lot's project, not its neighbour repositories: lot ids belong to each plan, so a finance-tracker L172 never blocks an ol-companion L172) and by plan: a lot that is `done` or absent in the plan never refuses, so an old interrupted wave whose lots are long delivered blocks nothing, and neither does a `done` wave. The error is a single message that names **every** wave concerned, with its launch folder and its open lots, the wave that holds the repository lock included (alive: while it has an open lot the lock refuses first, so the same message lists it together with the stopped waves of the repository, not the bare « orchestration en cours » line `cadence deliver` gives), and gives for each stopped wave ONE command, runnable as it stands, with a `--drop` per open lot of the repository in that wave, the queued ones that are not `doing` in the plan included (L239: `--resume` would relaunch them, and the lot would take the lock back and find the tree dirty with the corrected test): `cd <launch folder> && cadence orchestrate --resume --wave <wave> --drop <project>:<lot> --drop <project>:<lot>` (`--resume` replays the whole wave except the dropped lots, so one command per lot would start sessions on the others; it looks for the wave in the folder it is run from only); a wave that is still alive is not called suspended, the error says it is running and is to be waited for. A repository reached by another path (a worktree, a symbolic link) is the same repository: the `.git` directories are compared by their real path. The waves read are those of `.cadence/runs/` of the repository root, of the plan's folder and of every folder above them (a wave keeps its state where `cadence orchestrate` was launched, usually the parent folder of the projects), with no `--reason`, and on a lot that is neither in progress nor delivered with a green acceptation. Then, depending on how the lot stands: a lot waiting on a `question` sits in a wave that is not `done`, which refuses the reseal (L238): answer it first (`--resume --wave <wave> --answer`, the lot then goes on or is handed back) or close it (`--resume --wave <wave> --drop`), reseal afterwards if the lot is handed back (a rewrite of `acceptation.json` without a `resceaux` entry stays a violation of the lock); a lot handed back with `verrou des tests d'acceptation` is finished for `--resume`, so it is replayed by a new wave `cadence orchestrate <project>:<lot>`, which starts again from the red commit. A session never does it, three guards: (1) `cadence spec reseal` is in the sessions' `--disallowedTools` (like `raf done`/`review`/`ux`); (2) a resealed commit is adopted as the new red commit only if it belongs to **no session of the wave** (commits listed in each `steps[].commits`, or in `headBefore..headAfter` of any session of any lot of the wave) — otherwise it stays a violation of the lock; (3) the lock is checked at the end of **every** work session, a session that ends with a question included, so a session that replaces the test, forges the fingerprint, commits and asks a question is handed back with `verrou des tests d'acceptation` without waiting for the lead's `--answer`. The reseal is atomic: if the commit fails (a `pre-commit` hook exiting 1, say) `acceptation.json` and the test are put back as they were before the command (the lead's correction in the working tree is kept, nothing staged) and a readable error says nothing was resealed. With a plan kept read-only by another tool, the reseal itself succeeds but the plan is not touched: the note of the lot is left to the lead (the reason stays in `acceptation.json` and in the commit message; the command only says a note is to be added). With a plan outside the repository or untracked, the note **is** written in the plan file but not committed (the command says `plan à commiter`); do not add it a second time. If the commit of the note is refused (a hook), the reseal stays done: the error gives its commit and says the note, already written, is to be committed by hand. **A sheet amended in the same lot (L217):** a sheet amended since the red commit is accepted, for a lot in progress as for a delivered one (the sheet is then not put back to its red state to be restored afterwards). When its net difference with the red commit (`git diff <red> -- <sheet>`) is not empty, the command reads `git log <red>..HEAD -- <sheet>`: every commit that touched it must cite the lot (read like `raf commits`, i.e. `citedRefs`: the scope of the subject first, the body otherwise; for a delivered lot, the lot, the `--porteur` or any delivered lot of the plan, status `done`, L229: the living sheet is the reference; one cited delivered lot among several is enough), and the sheet must be committed. The fingerprint of the test is then redone against the current sheet and the `resceaux` entry gains the optional key `ficheAmendee` (short sha of the last commit that touched the sheet); the lot's note and the commit message say `fiche amendée en <sha>` instead of `fiche inchangée`. Still refused, with nothing written: a sheet modified by a commit that cites neither the lot nor (delivered lot) the `--porteur` or a delivered lot (`la fiche … a été modifiée depuis le commit rouge … par un commit qui ne cite pas <lot> : <sha>`; for a delivered lot `… ne cite ni <lot>, ni le porteur <porteur>, ni un lot livré : <sha> (cite : <lots ou aucun lot>) — faire citer le porteur ou un lot livré par ce commit (le réécrire avant qu'il soit poussé) ou, sinon, rescelle à la main avec le lead`), a sheet deleted or renamed since the red commit, a sheet with uncommitted changes (working tree or index: `a des modifications non commitées : la commiter (en citant <lot>) avant le rescellé`), as before any other listed test touched, a test left unchanged, an orchestrated wave and a missing `--reason`; the atomicity is unchanged. A sheet amended then brought back to the identical content is treated as unchanged, whoever committed in between. After the reseal the new red commit is the reseal commit, so a second reseal sees the sheet as unchanged. **A delivered lot (L212):** a lot whose acceptation is green (`vert: true`) and whose sealed test turns out unstable while the contract does not change (seen: a tab read as « blank » in CI) had no way out but the « sheet modified in the same lot » exception. `cadence spec reseal <lot> <test> --reason "why" --porteur <lot in progress>` now covers it, with the same guards (the sheet unchanged since the red commit, or amended as above by commits that cite the delivered lot, the carrier or another delivered lot; no other listed test touched, no wave, `--reason`) and three differences: `--porteur` is required and names the lot in progress that carries the fix (refused if it is the delivered lot itself, unknown or not `doing`); the commit cites the **carrier** (`test(L113): acceptation rescellée du lot livré L47, <test> : <reason>`) and so does the plan note and its commit, the delivered lot being closed; the dated `resceaux` entry also records `porteur`, and `vert` stays `true`. `--porteur` on a lot in progress, or `spec reseal` on a delivered lot without `--porteur`, is refused.
359
+ - **A closed lot left at `vert: false`: `cadence spec green` (L366).** The cycle sets `vert: true` itself; a lot driven and closed by hand (`raf done`) never gets it, and its `acceptation-<lot>.json` then keeps the lock total on its tests: every later wave of the repository is handed back with `verrou des tests d'acceptation … premier commit fautif` on lots that never touched them. Three guards:
360
+ - `cadence spec green <lot>` runs the dedicated command (`orchestrate.accept`, same rules as the cycle: the commands whose root holds a listed file, `{acceptation}` and `CADENCE_ACCEPTATION` set; a listed file under no command's root is refused) and, when it is green, sets `vert: true` and commits the file alone, citing the lot (`chore(L12): acceptation verte, vert: true posé par cadence spec green`, or the `plan.commit` template of the project when it has one). The subject stays short and fixed: the resealed tests are listed in the commit body, one `- <path>` line each, so a `commit-msg` hook bounding the header (commitlint `header-max-length` 100) accepts the commit. Tests refined by the lot after the red commit (the case of issue #18: its own fix commits changed them) would still be reported by the lock, whose reference is the red commit: the command therefore reseals every listed test whose fingerprint changed (the `empreintes` are redone, one `resceaux` entry each) in that same commit, which becomes the red commit; a listed test changed but not committed is refused before anything runs. Red: the tail of the output on stderr, exit 1, nothing changed. Already green: nothing to do. Refused for a `todo` lot, with `CADENCE_ORCHESTRATED` set, while a wave holds the repository lock and while an unfinished wave keeps a lot of the repository open (the same refusals as `spec reseal`, worded for `spec green`: « spec green : refusé … puis relancer spec green »).
361
+ - `raf done <lot>` refuses a lot whose acceptance is `vert: false` (the message names the file and the command above); `--force` closes it anyway and warns on stderr.
362
+ - `cadence orchestrate … --dry-run` prints once per repository `⚠ <repo> · acceptation rouge d'un lot clos : L12 (<file>, vert: false)` for each `done` lot left that way, before the wave starts.
357
363
  - **Back to green, set by the cycle.** At the end of each `implement`/`fix`, once the session reports green, the cycle runs the dedicated command. Red: the findings go to a `fix` pass (the tests are made green without editing the locked files). Green: the cycle sets `vert: true` and commits it (`chore(L7): acceptation verte …`;
358
364
  `commitPlan` may commit the `acceptation.json` files, otherwise the dirty tree would stop the lot). The files then join `orchestrate.test` and the CI, which keep the lot green from then on (an `"e2e": true` acceptation excepted: its files stay in the e2e suite after the build); the following lot, running with `vert: true`, is compared with the head before its own work, not with the red commit, only once the sheet has changed between the red commit and that head: after an admitted evolution (tests changed along with the sheet) it sees no difference either; only a test changed *during* that lot without the sheet is a deviation.
359
365
  - **When the lot is handed back** (question, budget, passes exhausted) its tests stay red but excluded from the project's runner and CI: the next lots and `cadence deliver` do not see them; `--resume` continues with the same lock. On resume, the step is skipped when the lot already has a valid committed **and sealed** `acceptation.json` (its `empreintes` set by the program); a conforming file without them proves nothing and `accept` runs again. A sealed green one (`vert: true`) also skips the step when its seal no longer holds (L233); a green one without `empreintes`, with `empreintes: {}`, or whose `empreintes` lack one of the listed files (copied from another file or lot) proves nothing and `accept` runs again (the contents are not compared here, only the presence of one fingerprint per listed file). The fingerprints are keyed by normalised path, so a listed `./test/x.ts` is recognised like `test/x.ts`, on resume and by the lock (which compares it to its fingerprint).
360
366
 
367
+ ## Elements of a reference document in the briefs (L367)
368
+
369
+ When the title, the goal or a note of a lot quotes ids of a reference inventory (`<slug>#<type>-<n>`, see [ref](ref.md#cadence-ref-coverage-inventairejson)), the `spec` brief lists them and asks the spec writer to take each into the sheet (its `source` quotes the id) or to justify it (`<id> : <reason>` under « choix » and in `perimetre.hors_perimetre`), and the `ux` brief asks the ux-reviewer to check each on the finished screen (a small visible lot has no `ux` pass: its single `review-small` pass gets the same instruction and checks the ids on the finished screen). The ids are read from the plan at launch (`lotIds`, `src/ref/coverage.ts`); a lot without id has unchanged briefs.
370
+
361
371
  ## The downstream briefs: sheet, rule → test → green, conformity, Hypothèses
362
372
 
363
373
  Once the sheet exists, the steps after it all read it (L174, `src/orchestrate/aval.ts`, the `{{fiche}}` marker of the templates `implement.md`, `fix.md`, `fix-minors.md`, `review.md`, `review-small.md` and `ux.md`). A lot without a sheet (no `screen:`, or under the threshold) keeps exactly the briefs it had.
@@ -368,7 +378,7 @@ Once the sheet exists, the steps after it all read it (L174, `src/orchestrate/av
368
378
  - **Real walkthrough before the auto-verification (L344).** Reading a diff did not show three major review findings of 26-10 (a command impossible in its own documented path, a « champs exclus » read against a decision of 22-10, a failure banner out of view). `implement.md` therefore asks, once the tests are green, to play what was delivered as a user would: a command in a throwaway repository (`git init` in a fresh temporary folder), following the documentation the lot wrote, command by command; a screen in each of its states, failure included and shown where the action takes place, with a capture of each. What breaks is fixed in the same session (test first) and played again; the report says it in a paragraph of `"resume"` that starts with « parcours réel » (no new field; a lot with nothing to play says so). `fix.md` does not carry it: a correction pass fixes the findings it is given.
369
379
  - **Decisions to respect (L344).** The program (not the pre-check session, which many lots skip) reads the notes of the lot and of its parent lot (`parent:`) and keeps those that name who decided (Sylvain, the human, the lead, the user) and say that they decided, validated, asked or refused (`src/orchestrate/decisions.ts`). The notes the program writes itself are never taken for one, whatever they quote: a hypothesis of a fiche (« Hypothèses (fiche …) — », a choice nobody validated), a minor of a review (« Revue de … — mineur : »), a replaced title (« titre remplacé : », the stale one), « remis en attente » and « abandonné : ». Each is given with its date and its text as written (cut at 1500 characters) under « Decisions to respect » in the `implement` and `fix` briefs (and in the `--dry-run` briefs), with the rule: an interpretation that departs from one of them is a question under `"questions"`, never a choice under `"choix"`. Nothing is written when no note qualifies. The effect is measured by the share right the first time of `cadence perf` over two windows.
370
380
  - **The code review (`review`, `review-small`) checks rule → test → green** (`règle → test → vert`). For each `Rn` of the sheet the reviewer checks that its test exists (named after the rule), that it is green, and that the behaviour is in the diff; when an acceptance file is named, that each listed test is unchanged since the red commit (`git diff <red commit> -- <file>` empty; the brief gives the red commit). A rule without a test, an acceptance test modified since the red commit, a rule coded that is not in the sheet, and a test that is green without exercising its rule are **major** findings. The reviewer no longer rebuilds the cases from the code.
371
- - **The UX pass becomes a conformity pass.** For a lot with a sheet, the `ux` session (the `ux-reviewer` agent, **conformity mode**) no longer audits the whole screen: it compares the running app to the mockup and to its captures `maquette-<état>-<largeur>.png`, state by state, at 1440 and 390 px, and reports the differences of rendering and ergonomics the tests do not cover, each tied to a rule or a measure. The rules of the sheet are the tests' job. Its verdict feeds `raf ux`, which stays the lead's, decided on the captures. A small visible lot (single `review-small` pass) gets the conformity instruction in that pass.
381
+ - **The UX pass becomes a conformity pass.** For a lot with a sheet, the `ux` session (the `ux-reviewer` agent, **conformity mode**) no longer audits the whole screen: it compares the running app to the mockup and to its captures `maquette-<état>-<largeur>.png`, state by state, at 1440 and 390 px, and reports the differences of rendering and ergonomics the tests do not cover, each tied to a rule or a measure. The rules of the sheet are the tests' job. Its verdict feeds `raf ux`, which stays the lead's, decided on the captures. A small visible lot (single `review-small` pass) gets the conformity instruction in that pass. It gets the verification of the reference ids the lot quotes there too (L367, see above).
372
382
  - **The `## Hypothèses` section of the news entry.** The `implement` brief asks for an entry that ends with `## Hypothèses`: one line `- Rn — …` per rule whose status is `hypothese`, with what changes if it is wrong (its `si_faux`) — it is what the lead shows for the `raf ux` decision, the choices nobody validated.
373
383
 
374
384
  ```markdown
@@ -490,6 +500,21 @@ except a copy that is still needed, which stays and is said: a copy that a live
490
500
  The waves that count are those of the launch folders the script knows (`<folder>/.cadence/runs/<wave>/wave.json`): the repository itself and the working directory of every wave the orchestrator's registry still lists (`~/.cadence/orchestrate/waves/<pid>.json`; the registry drops a dead wave when another registers, so it does not know suspended waves by itself). There is no search over the whole disk: a suspended wave launched from another folder is not protected here, and `--resume` repairs its snapshot instead (above). A wave never marked `done` keeps its copy until it is, or until its folder is removed. A failure of the purge (unreadable copy, busy file) once `service` and the links have switched is a warning on the error output (`avertissement : purge des anciennes copies interrompue …`): the new version is in service, the fingerprint is printed, the exit code is 0, the next put in service tries again. The first run is also what installs the links in `~/.local/bin`. It prints the fingerprint of the copy
491
501
  (bin, dist, package.json).
492
502
 
503
+ ## The subject of the plan commits (`plan.commit`, L343)
504
+
505
+ The commits the program writes for the plan in a wave (`plan: <lot> démarré (orchestrate <wave>)`, `… revue de code enregistrée`, `… revue de code notée`, `… hypothèses de la fiche <sujet> notées`, `… mineurs de la revue de <revue> notés`) take the template of the `plan.commit` key of `cadence.yaml`
506
+ (`{lot}`, `{action}`, `{vague}`; the same key sets the plan commit of `cadence spec reseal`; absent, the subject is unchanged — keys and rules in [docs/raf.md](raf.md#the-subject-of-the-plan-commits-plancommit-l343)).
507
+ A project whose `commit-msg` hook enforces Conventional Commits sets for instance `plan: { commit: 'chore({lot}): {action} (orchestrate {vague})' }`.
508
+
509
+ `--dry-run` plays the subjects the lot would write, for each lot, through the repository's **real** `commit-msg` hook, as git would run it (`git rev-parse --git-path hooks/commit-msg`: `core.hooksPath` and husky included; a hook that is not executable is skipped by git and here). Every lot writes `démarré`, `revue de code enregistrée` and `revue de code notée`; a lot with a spec stage (`spec: true` or `screen:`) also writes `mineurs de la revue de la fiche notés`, the sheet sourced by the lead's answer (action `règles R1, R2 sourcées par la réponse du lead`, through the same `plan.commit` template; absent, `docs(<lot>): règles R1, R2 sourcées par la réponse du lead (orchestrate <wave>)`, lower-case after the colon because `subject-case` refuses a capital) and, for a `spec: true` lot without a screen, `hypothèses de la fiche <sujet> notées`. Those sheet subjects are measured with a subject of **real length**, since `header-max-length` (100) depends on it: the lot's sheet when it is already written (its `sujet.id` for the hypotheses, its **costly** hypotheses for the lead's answer, since the answer only sources the rules the wave asked about — with none, no answer is committed and none is played), otherwise the longest subject of the sheets folder and as many rules as the sheet of the folder that has the most costly hypotheses (one at least); with no sheet in the repository at all, `<sujet>` and one rule, with a warning that the measure is short. Measuring the answer with every hypothesis of a sheet (13) declared refused an answer that cites five. It prints under the lot:
510
+
511
+ - `messages de plan : 3 message(s) acceptés par le hook commit-msg du dépôt (<hook>)` (5 or 6 for a lot with a spec stage: the hypotheses subject only exists for a `spec: true` lot without a screen) when none is refused;
512
+ - `✗ message de plan refusé par le hook commit-msg : « <subject> » — <what the hook said>` for each one refused, then the advice to set `plan.commit` (it governs every one of them, the lead's answer included): without it the lot would fail on its first refused plan commit, before or during its sessions (the hypotheses are noted after the spec session). The dry-run still exits 0 (it simulates, it does not gate);
513
+ - for a lot with a spec stage, what the measure used: `fiche du lot : docs/specs/<sujet>/fiche.yaml (hypothèses R1, R2)`, or `fiche du lot pas encore écrite : mesure avec le sujet le plus long de docs/specs (<sujet>) et <n> règle(s)`;
514
+ - nothing when the repository has no `commit-msg` hook and no `plan.commit` is set.
515
+
516
+ When a plan commit is refused all the same (a hook of the project, a header too long), the plan files are unstaged and what cadence wrote is undone before the lot fails, so the next lot of the wave does not find a dirty repository and is not handed back. What the lead had changed in the plan without committing before cadence wrote (a `raf add` or `raf note` in the project repository during a session: the plan files, the files maintained with them, the `acceptation.json`) is put back as it was, not erased: those files stay modified, as before the commit, and only the content cadence added (the hypotheses notes, the review record) is gone.
517
+
493
518
  ## One worktree per lot (`orchestrate.worktrees`, L279)
494
519
 
495
520
  > **On by default in cadence (L320, 2026-10-25).** After the trials L310 and L316 (−45 % and −22 % of wall time, three automatic integrations without incident) cadence's own
@@ -522,7 +547,7 @@ nothing below applies and the wave behaves as before; any other value is refused
522
547
  read-only plan (`plan:` in `cadence.yaml`) gets `orchestrate.start` and `orchestrate.verdict` run in that checkout, never in the worktree; a plan
523
548
  outside the repository is read and written in place, without a commit. The lots' branches carry work commits only (the acceptance seal and
524
549
  `vert: true` are work of the lot), so a plan conflict at the rebase cannot happen. A session that modifies the plan file, a file generated with it
525
- (`plan.files`, the Gantt page) in its worktree — committed or not — gets its lot handed back, naming the file and the commit.
550
+ (`plan.files`, the files of `plan.onWrite`, the Gantt page) in its worktree — committed or not — gets its lot handed back, naming the file and the commit.
526
551
  Before each of these writes the program checks that the repository is still on the branch the lots started from, that no tracked file other than the
527
552
  plan is modified in the project's checkout, and takes the lock; a refusal hands the lot back with the cause
528
553
  (`écriture du plan refusée : arbre du dépôt modifié (<files>)`, `dépôt sur <branch> et non <start branch>`, `verrou tenu par <wave> (pid <pid>)`), writes
@@ -535,6 +560,11 @@ nothing below applies and the wave behaves as before; any other value is refused
535
560
  (`arbre du dépôt modifié (<files>)`): the program never changes the lead's branch or tree. The check is made again right before the
536
561
  fast-forward (the replayed checks and the short review can last) and right before the plan is written: a lead moved to another branch, or whose
537
562
  tree got dirty, in the meantime gets neither the fast-forward nor a plan commit, and the lot is handed back with `next: integrate` (L299);
563
+ **When the repository's branch itself moved** during that time (a plan commit of the lead, another lot closed by hand), the fast-forward is no longer
564
+ possible: instead of handing the lot back, the program replays steps 2 to 5 at the new tip (rebase, replayed checks, verdict carry-over or short
565
+ review, fast-forward), at most **twice**, and logs `<project>:<lot> main a avancé pendant l'intégration, rebase rejoué (n/2)`. A branch that moves again
566
+ after the second replay hands the lot back with `intégration refusée : main a encore avancé après 2 rebases rejoués`, `next: integrate`, nothing of
567
+ the lot in the repository's branch (L364);
538
568
  2. the lot's branch is rebased on the tip of the repository's branch (`git rebase`); a conflict is **aborted** (`git rebase --abort`), the lot
539
569
  is handed back with the files in conflict, its worktree and branch untouched, and no session tries to resolve it. **One exception** (L314): two
540
570
  parallel lots that each add an entry under `## [Unreleased]` of `CHANGELOG.md` (what `docs.sync` imposes on every lot) always collide, so a commit
@@ -545,6 +575,11 @@ nothing below applies and the wave behaves as before; any other value is refused
545
575
  branch) was added by an earlier commit of the same lot, so the replayed commit may reword or remove it — its version wins, the earlier one is dropped
546
576
  from the repository's side — while an entry that was already there at the starting point, reworded, stays a conflict by hand. The
547
577
  journal says `<project>:<lot> CHANGELOG.md : conflit d'ajout pur résolu par union sous [Unreleased] (<n> commit(s))`. Anything else — an entry that
578
+ **A release during the branch's life** (L362): when the repository's branch was released after the lot's branch was cut, its `## [Unreleased]` became
579
+ `## [x.y.z]` and git, merging without conflict, files the lot's entries under that published version. After the rebase the program moves the entries of
580
+ the lot (those in its `[Unreleased]` and not in the `CHANGELOG.md` of the starting point) that landed in the published part, and were not already
581
+ there on the repository's branch, back under the repository's `## [Unreleased]`, in a commit of its own (`docs: CHANGELOG — les entrées du lot replacées
582
+ sous [Unreleased] …`); the journal says `<project>:<lot> CHANGELOG.md : <n> entrée(s) du lot replacée(s) sous [Unreleased], …`.
548
583
  was already there removed or reworded, free text under `[Unreleased]`, the rest of the
549
584
  file changed on both sides, another file in conflict with it — aborts the rebase and hands the lot back as above. The rebased commits differ, so
550
585
  steps 3 and 4 apply: tests replayed, and a short review since the patch-id changed;
@@ -793,7 +828,9 @@ News instruction (`cadence news new <lot>`, factual user-side text, a screenshot
793
828
  ```yaml
794
829
  orchestrate:
795
830
  test: npm test # run by the orchestrator after a work step (optional)
831
+ testReplay: npx vitest run --minWorkers=1 --maxWorkers=$CADENCE_TEST_WORKERS {files} # replay alone of a red test before it counts (L354, see « A red test is replayed alone »); `{files}` required; absent: no replay, nothing held for unstable (optional)
796
832
  build: npm run build # run BEFORE the tests (L333); both results go to the reviewer, who does not redo them (optional)
833
+ worktreeDeps: [node_modules, .venv] # untracked dependencies that `cadence verify --gate` links into its throwaway worktree (L348, `docs/deliver.md`); absent: the ignored folders of every level, as the worktrees of a wave (optional)
797
834
  precheck: true # default: before the first implementation of a lot with no commit whose title cites a finding or an earlier lot, and whose title or `goal:` cites a finding (`constat`, `revue`, `review`, `audit`) or a plan id (an earlier `L<number>`, also followed by a hyphen as in `L12-L15` or `L12-bis`; any other id such as `Q-CAN5`; `project:L<number>`), a read-only Sonnet session checks whether the deliverable is already in the repository (see below); false skips it, local runs it on claude-local (Ollama) with Sonnet as fallback, for every lot with no commit whatever its title (L296); a lot created after the last code commit of the repository is not pre-checked, local or Sonnet, unless it cites a lot that already has commits (L322)
798
835
  accept: npx vitest run -c vitest.acceptation.config.ts # accept step (see « The accept step »): the dedicated command that runs ONLY the acceptance tests of the lot (optional; without it the step is skipped with a warning)
799
836
  maquette: true # default: UX review of the mockup between spec and accept (see « The mockup review »); false skips it
@@ -860,7 +897,12 @@ the `.nvmrc` again for every live lot and refuses it like at start if it can no
860
897
  The file is read trimmed (`22`, `v22`, `22.22`, `v22.22.3`); the highest matching version installed under
861
898
  `$NVM_DIR/versions/node` (default `~/.nvm/versions/node`) **that has an executable `bin/node`** is used: a higher
862
899
  version with an empty or broken `bin` is skipped for a lower valid one (and `--dry-run` says so); if none is valid
863
- the lot is refused. Only the sessions' environment changes,
900
+ the lot is refused. The same `PATH` goes to every command the program
901
+ launches itself — `orchestrate.test`, `orchestrate.build` (the baseline run at the start sha included), `start`, the acceptance
902
+ runs and `verdict` — so the gate judges the lot under the Node the session wrote it for, not under the one the wave was
903
+ started with. `--dry-run` adds `⚠ .nvmrc 24 ≠ node du PATH v20.20.2 : …` under the lot when `orchestrate.test` or
904
+ `orchestrate.build` is declared and the `node` the `PATH` resolves does not match the `.nvmrc` (nothing is printed without a
905
+ gate, without a `node` on the `PATH`, or when they match). Only the environment of the sessions and of these commands changes,
864
906
  never the orchestrator's own. No `.nvmrc` → nothing changes. A `.nvmrc` that cannot be resolved (`lts/*`, an
865
907
  alias, a version not installed) refuses the lot before anything is started (exit 2), naming the requested
866
908
  version and the folder searched — there is no silent fallback to the default Node. `--dry-run` prints
@@ -886,7 +928,11 @@ folder whenever Playwright was loaded for the lot, even if no `ux` step ran
886
928
 
887
929
  Without `start`, a read-only plan's `todo` lot is refused (start it with the project's tool); without
888
930
  `verdict`, the review verdict stays in the wave's state and you report it. Only the plan's files
889
- (`plan.path`, `plan.files`, the QA expectations file) are committed from those commands; anything else dirty stops the lot (`.cadence/runs/`, `.cadence/corrections.json` and `.playwright-mcp/` excepted, as above).
931
+ (`plan.path`, `plan.files`, the files of `plan.onWrite`, the QA expectations file) are committed from those commands; anything else dirty stops the lot (`.cadence/runs/`, `.cadence/corrections.json` and `.playwright-mcp/` excepted, as above).
932
+
933
+ ### `plan.onWrite`: the published plan follows every write (L346)
934
+
935
+ When `cadence.yaml` declares `plan.onWrite: { command, files }` ([raf](raf.md#regenerate-the-published-copy-on-every-write-planonwrite-l346)), the wave runs `command` at the repository root after each write of the plan it makes (start of the lot, notes of the sheet and of the review, review recorded), and the produced `files` go into the same `plan: …` commit as the plan, even when they were not tracked yet. If the command fails the write is refused: the plan is put back, nothing is committed, and the lot is handed back naming `plan.onWrite en échec`.
890
936
 
891
937
  ### Time limit of a write step (L298)
892
938
 
package/docs/overview.md CHANGED
@@ -65,9 +65,9 @@ The two plugins are independent. `cadence@cadence` carries the rituals, which ca
65
65
  | [docs/orchestrate.md](orchestrate.md) | waves of sessions: budgets, spec, accept and review steps, neighbouring repositories, one worktree per lot (`orchestrate.worktrees: true`, on in cadence's own `cadence.yaml`, off by default elsewhere; `--integrate`, `--replay`, `--remove-worktree`; recovery of a handed-back lot's branch and the `CHANGELOG.md` union at the integration are described there) |
66
66
  | [docs/deliver.md](deliver.md) | delivery: wait for CI, deploy, verify the effect |
67
67
  | [docs/releasing.md](releasing.md) | releasing: `cadence release patch\|minor\|major` prepares the version commit (version fields, CHANGELOG, `docs/whats-new.md` check) and prints the lead's steps; nothing is published by it |
68
- | [docs/spec.md](spec.md) | the screen and back-end sheets, `cadence spec check\|qa\|reseal` |
68
+ | [docs/spec.md](spec.md) | the screen and back-end sheets, `cadence spec check\|qa\|reseal\|green` |
69
69
  | [docs/lead.md](lead.md) | the lead workflow and `cadence lead tour` |
70
- | [docs/ref.md](ref.md) | reference documents: `cadence ref add` (rendered pages, positioned text, images, figure index), `cadence ref measure` (colours, WCAG contrast, boxes, OCR, font size), `cadence ref check` (the inventory schema, `templates/inventaire.json`), `cadence ref eval` (recall, precision, false « read », tokens per page against a known inventory), `cadence ref corpus` (the synthetic corpus with a known truth, rendered by Playwright) `cadence ref diff` (reconciles two readings of a document by program) and `cadence ref analyse` (one reading by the `reference-analyst` agent; `--double` for two independent readings, `ref diff`, a third look by `reference-verifier` and the reconciled inventory, an option whose measurement showed no gain; 20 pages at most per reading) |
70
+ | [docs/ref.md](ref.md) | reference documents: `cadence ref add` (rendered pages, positioned text, images, figure index), `cadence ref measure` (colours, WCAG contrast, boxes, OCR, font size), `cadence ref check` (the inventory schema, `templates/inventaire.json`), `cadence ref eval` (recall, precision, false « read », tokens per page against a known inventory), `cadence ref corpus` (the synthetic corpus with a known truth, rendered by Playwright) `cadence ref coverage` (which elements of the inventory no done lot covers, nor a written exclusion), `cadence ref diff` (reconciles two readings of a document by program) and `cadence ref analyse` (one reading by the `reference-analyst` agent; `--double` for two independent readings, `ref diff`, a third look by `reference-verifier` and the reconciled inventory, an option whose measurement showed no gain; 20 pages at most per reading) |
71
71
  | [docs/stabilite.md](stabilite.md) | what is guaranteed from 1.0.0 and what is not |
72
72
 
73
73
  Also: [news](news.md) (changelog, UX, code and QA reviews, documentation that follows the code), [session](session.md), [corrections](corrections.md), [perf](perf.md), [skills and agents](skills.md), [cadence-hud](cadence-hud.md), [releasing](releasing.md), [overview](overview.md), [how it all works](fonctionnement.md) (four figures).
@@ -83,6 +83,7 @@ Also: [news](news.md) (changelog, UX, code and QA reviews, documentation that fo
83
83
  - **real walkthrough and decisions** (L344): once the tests are green an `implement` session plays what it delivered as a user (the command in a throwaway repository following its own documentation, the screen in each state with the failure in view) and reports it under « parcours réel »; the notes of the lot and of its parent that record a decision of the human reach the `implement` and `fix` briefs under « Decisions to respect », a departure being a question, never a choice (`docs/orchestrate.md`).
84
84
  - **perf**: `cadence perf` is the dashboard of the workflow (speed next to quality, `⚠` on a trend), including the red deliveries and the blocking QA findings; `cadence lead tour` opens with its *perf header*, one line per `⚠`, nothing when stable; a rise of the tokens per lot names the step that carries it, and when the share right the first time rose too it reads as quality paid for, not a wrong estimate ([docs/perf.md](perf.md)).
85
85
  - **doc-keeper** (L306): a generic agent that reads the diff of a lot and the project's map of documents (`docs.sync`, README, `docs/`, CHANGELOG, news), says which documents must change and updates them (or only lists them, read-only); `cadence orchestrate` calls it before the review when `docs.sync` reports a document behind the lot (a lot never ends ready with that gap open: the documentation is written, or the gap is acknowledged with `raf docs-ok <lot> --reason`, L317), the lead by hand; a document outside the repository only through the `docs.articles` key of the project's `cadence.yaml` (it then proposes a lot, never writes it) ([docs/news.md](news.md#the-librarian-doc-keeper)).
86
+ - **reference-planner** (L367): an agent that reads an inventory and the plan and proposes lots that cite the ids they cover, feasible or blocked, with their dependencies; it creates nothing (`cadence ref coverage` lists what is left).
86
87
  - **reference-analyst** (L265): an agent that reads one reference document (mock-ups, a spec with figures, a norm) page by page and zone by zone, measures with `cadence ref measure`, and writes a reading (`lectures/a.json`) in the format of `templates/inventaire.json`; `cadence ref analyse` launches it for one reading and has the program check the file and count the tokens (once by default; with `--double` (L266) twice, readings A and B in `lectures/a.json` and `b.json`, which must not see each other; [docs/ref.md](ref.md#cadence-ref-analyse-documentpdf---sections-3-59---slug-name---model-sonnetopushaiku---model-b-sonnetopushaiku---seuil-30---double)).
87
88
  Reviews are opt-in gates: `ux-reviewer` (no user-facing change is done before its usability review), `code-reviewer` (no lot with commits is done before its code review) and `qa-reviewer`, which walks the delivered app in a real browser.
88
89
 
package/docs/perf.md CHANGED
@@ -37,9 +37,11 @@ current window, with at least 10 classified passes) is flagged by a `⚠` even w
37
37
  - **Pre-check measured per lot** (L322). Under the section « Contrôle préalable », for the current window: the split `oui` / `partiel` / `non` of the rendered lots (`précheck : oui 1 · partiel 0 · non 1`); the local sessions (`précheck local : n sessions, k échecs, m min`: how many, how many ended without a usable report, wall time; a local session that ended `ok` but whose report was unreadable, so that the lot fell back on Sonnet, counts as a failure, and its line says `local échec`, `--json` `local.status: "illisible"`); the Sonnet sessions of the pre-check, which are the fallback of a local failure and the confirmation of a local `oui` (`précheck Sonnet : n sessions, t k tokens, m min`; the local model counts no token); the gain of the `oui` (`gain des « oui » : n sessions d'implémentation évitées (≈ t k tokens)`: the lots handed back without implementation, times the median of the `implement` steps of the window; without any, the tokens read `(gain en tokens : n/d)` and are `null` in `--json`); then one line per lot (`projet:lot · verdict · local échec 10 min · Sonnet 2 min, 20 k tokens`). Nothing is printed for a window without pre-check session or verdict. In `--json`: `precheck.verdicts`, `precheck.cost` (`local`, `sonnet`, `avoided`, each `{ current, previous }` as a whole) and `precheck.lots` (current window). Read it before changing `orchestrate.precheck` or the lots it applies to: the cost of the pre-check is what it spends (`local` minutes, `sonnet` tokens), its worth is `avoided`.
38
38
  - **Share within budget** (L349). `part des rendus sur budget` counts a lot against its budget **minus the share kept for the escalation** (`escalationReserve` of the journal, `docs/orchestrate.md`) as long as its escalation was not engaged: the ordinary passes stop that share earlier, so counting them against the whole budget would call a lot that hit its limit « within budget ». Once the escalation is engaged (a journal `escalation` whose issue is not `non jouée`) the share is released at run time, so all the steps of the lot, whatever their model, are counted against the whole budget. An escalation `non jouée` engaged nothing: the ordinary passes ate the share, so the lot stays counted against its budget minus the share. A journal with no `escalationReserve` (a wave before L349, or no `orchestrate.escalation`) is counted against its whole budget, as before.
39
39
  - **Model escalations** (L337). Under the section « Escalade de modèle », for the current window: `escalades de modèle : n lots · k conforme · r rendu malgré l'escalade · b non jouée · e en échec` — the finished lots whose journal carries an `escalation` (`orchestrate.escalation`, `docs/orchestrate.md`) and how it ended: the fresh review on the escalation model was compliant (`conforme`), the lot was handed back despite it (`rendu`), the lot's budget did not cover it (`non jouée`), or an escalated session returned nothing — model unavailable to the account, cut, timeout (`en échec`, the model never judged the lot); a lot interrupted during its escalation, withdrawn from the wave (`--drop`) or stopped by an incident of the repository is counted among the lots but in no outcome, never as `en échec`. Nothing is printed without an escalation. In `--json`: `escalations` (`{ current, previous }`, each `{ lots, conforme, rendu, nonJouee, echec }`). Read it to see whether the escalation model earns its cost: many `rendu` means the stronger model does not settle what the passes could not, so the brief or the sheet is at fault.
40
+
41
+ - **Tests unstable under load** (L354). Under the section « Porte de tests sous charge », for the current window: `tests instables sous charge : n tests dans k lots (rouges dans la suite, verts rejoués seuls ; précédente m)` — the tests of the rendered lots that were red in the gate's whole suite and green when the gate replayed them alone (`instables` of the lot journal, only for a project that declares `orchestrate.testReplay`, `docs/orchestrate.md`): they were neither counted nor corrected. Nothing is printed without one. In `--json`: `instables` (`{ current, previous }`, each `{ lots, tests }`). A rise says the machine is too loaded for the suite: lower the session cap or the `CADENCE_TEST_WORKERS` share before reading it as flaky tests.
40
42
  - **`--since date`** is an ISO date that ignores everything before that day in both windows (a wave created before that day is not read, even if its lots end after it); a window starting before it is *truncated* (its per-day rates divide by the days covered, no `⚠`).
41
43
  It is **not** the instant of `cadence corrections --since` (waves created strictly after).
42
- - **`--json`** prints one object `{ since, today, windows: { current, previous }, throughput, corrections, interventions, precheck, escalations, tokens, delays, durations, cost, quality, orphans, warnings }`; every indicator carries
44
+ - **`--json`** prints one object `{ since, today, windows: { current, previous }, throughput, corrections, interventions, precheck, escalations, instables, tokens, delays, durations, cost, quality, orphans, warnings }`; every indicator carries
43
45
  `{ current, previous, change, warn, twin, lots, note }` (`twin`: the path of its quality twin, e.g. `throughput.escaped`). Same values as the text.
44
46
 
45
47
  ## Tokens, delays, wall time and cost (L226)
package/docs/raf.md CHANGED
@@ -55,7 +55,8 @@ raf gantt # .cadence/gantt.html (next to the plan)
55
55
  | `raf spec <id> on\|off` | set or remove `spec:` on an existing lot (a lot that changes a behaviour, for the back-end spec step of `cadence orchestrate`, see [The spec step of a lot with `spec: true`](orchestrate.md#the-spec-step-of-a-lot-with-spec-true)); refused on a sub-task; `off` is refused on a lot that has `screen: page` (the page implies the spec: remove `screen:` first) and `on` there only says so and changes nothing; on `component` and `journey` (ordinary path since L209) both work and set or remove the back-end `spec:`. `raf spec` only takes `on`/`off`: `cadence spec check\|qa <fiche.yaml>` keeps its own subcommands. `raf check` reports a `spec:` that is neither `true` nor `false` (`spec : true ou false attendu`) |
56
56
  | `raf uses <lot> [<resource>…]` | replace the `uses:` list of a lot of any status (`done` and `dropped` included) by the names given (duplicates removed, order kept); with no name, remove the key. Prints nothing. A name is a word of lowercase letters, digits, `-` and `_` (else `ressource invalide : <name> (lettres minuscules, chiffres, - et _)`, plan unchanged); a sub-task is refused (`uses se pose sur un lot, pas sur une sous-tâche : L1/t1`); an unknown lot is `lot inconnu : <id>`. A name not yet declared is accepted: `raf check` refuses it (L251) |
57
57
  | `raf public <id> "title"` · `raf public <id> --clear` | set, replace or remove the public title of a lot |
58
- | `raf start <id>` · `raf done <id> [--force]` · `raf drop <id> [--reason text]` | dated transitions (`done` refuses open sub-tasks unless `--force`) |
58
+ | `raf start <id>` · `raf done <id> [--force]` · `raf drop <id> [--reason text]` | dated transitions (`done` refuses open sub-tasks unless `--force`, and a lot whose `acceptation-<lot>.json` is still `vert: false`, L366: closed like that it would lock every later wave of the repository, see `cadence spec green`; with `--force` it only warns on stderr) |
59
+ | `cadence spec green <lot>` | L366: run the dedicated acceptance command (`orchestrate.accept`) on a lot driven or closed by hand; green, set `vert: true` in its `acceptation-<lot>.json` and commit that file alone (subject `chore(<lot>): acceptation verte, vert: true posé par cadence spec green` or the `plan.commit` template; the resealed tests are listed in the commit body, never in the subject), resealing the fingerprints of the listed tests refined since the red commit (journal `resceaux`) so the lock goes quiet; a listed test changed but not committed is refused. Red: exit 1, nothing changed. Refused for a `todo` lot, during a wave, and while an unfinished wave keeps a lot of the repository open; see [orchestrate](orchestrate.md) |
59
60
  | `raf todo <id> [--reason text]` | put a started lot back to `todo` (`doing` → `todo`, `started:` removed, dated note « remis en attente : reason »): refused when work commits cite the lot (plan-only and version commits do not count, as `raf commits`), when the lot is not `doing` (a `done` or `dropped` lot is reopened by hand), or on a sub-task; sub-tasks and other keys are left as they are |
60
61
  | `raf migrate [--dry-run]` | move `docs/plan/raf.yaml` to `.cadence/raf.yaml` and rewrite the readers of the old path in the repository, see [Moving the plan: `raf migrate`](#moving-the-plan-raf-migrate) |
61
62
  | `raf note <id> "text"` | dated note — keep decisions next to the work |
@@ -142,6 +143,26 @@ then the legacy `docs/plan/raf.yaml`.
142
143
  - `cadence lead tour`, `session --all` and `cadence orchestrate` recognise a project by `.cadence/raf.yaml`, `docs/plan/raf.yaml` or a
143
144
  `cadence.yaml` with `plan:`.
144
145
 
146
+ ## The subject of the plan commits: `plan.commit` (L343)
147
+
148
+ `cadence orchestrate` (the lot started, the code review recorded or noted, the hypotheses and minors of a lot noted) and `cadence spec reseal` (the lot's note) commit
149
+ the plan on their own. Their subject has always been `plan: <lot> <action> (orchestrate <wave>)` (`plan: <lot> <action>` outside a wave), which a `commit-msg` hook
150
+ that enforces Conventional Commits (commitlint with `config-conventional`, husky) refuses: `plan` is not one of its types, the lot failed before any session and the next one
151
+ was handed back with the plan left staged (GitHub issue #15). `plan.commit` in `cadence.yaml` sets the subject of **all** of them:
152
+
153
+ ```yaml
154
+ plan:
155
+ commit: 'chore({lot}): {action} (orchestrate {vague})'
156
+ ```
157
+
158
+ - `{lot}` is required (a commit must cite its lot); `{action}` is what was done (`démarré`, `revue de code enregistrée`, `revue de code notée`, `hypothèses de la fiche <sujet> notées`, `mineurs de la revue de <revue> notés`, `règles R1, R2 sourcées par la réponse du lead`, `test d'acceptation rescellé (<test>)`);
159
+ `{vague}` is the wave id. A word between braces that is none of these, an empty template or one on several lines is refused when the config is read.
160
+ - Outside a wave (`spec reseal`) there is no wave: the parenthesised group that holds `{vague}` is dropped, so the template above gives `chore(L17): test d'acceptation rescellé (…)`.
161
+ - Absent, the subject is the one above, unchanged. The key needs `plan:` to be an object (`plan: .cadence/raf.yaml` as a plain path has nowhere to put it: write `plan: { path: …, commit: … }`).
162
+ - `cadence spec green` (L366) commits its `acceptation-<lot>.json` with the template too when there is one (`{action}` = `acceptation verte, vert: true posé par cadence spec green`); without one its subject stays `chore(<lot>): …`. The tests it reseals are listed in the commit body, never in the subject, which therefore does not grow with them.
163
+ - The commit of the fiche sourced by the lead's answer takes the template too (its default, `docs(<lot>): règle R3 sourcée par la réponse du lead (orchestrate <wave>)`, is already Conventional but can exceed `header-max-length` with several rules and a wave id). The `test(<lot>): …` and `chore(<lot>): …` commits of the cycle (acceptation sealed, `vert: true` set) and the work commits are already Conventional and are not templated.
164
+ - `cadence orchestrate … --dry-run` runs every plan subject each lot would write through the repository's real `commit-msg` hook (found as git finds it: `core.hooksPath` and husky included), the sheet subjects measured with a subject of real length (the lot's sheet, or the longest of the folder) and the lead's answer with the costly hypotheses it would source, and names the one it would refuse — see [docs/orchestrate.md](orchestrate.md#the-subject-of-the-plan-commits-plancommit-l343).
165
+
145
166
  ## Moving the plan: `raf migrate`
146
167
 
147
168
  `raf migrate [--dry-run]` moves a legacy `docs/plan/raf.yaml` to `.cadence/raf.yaml` and updates what reads the old path, so the
@@ -213,6 +234,31 @@ plan:
213
234
  files: [frontend/public/plan-data/plan.json]
214
235
  ```
215
236
 
237
+ ### Regenerate the published copy on every write (`plan.onWrite`, L346)
238
+
239
+ Declaring the published file is not enough when it is generated: a hand-regenerated
240
+ `plan.json` that drifts from the plan (a flag forgotten) turns the project's
241
+ « published plan up to date » check red. `plan.onWrite` makes the program run the
242
+ generator itself, after **every** write of the plan:
243
+
244
+ ```yaml
245
+ plan:
246
+ onWrite: { command: npm run plan, files: [frontend/public/plan-data/plan.json] }
247
+ ```
248
+
249
+ - `command` runs through `sh -c` at the repository root (the folder of `cadence.yaml`, whatever folder `raf` is run from), with a
250
+ 2-minute limit. It runs after `raf add|start|done|drop|todo|note|did|title|uses|public|screen|visible|spec|ux|review|ignore|docs-ok`, after the writes of `cadence orchestrate`
251
+ (start of the lot, notes of the review and of the sheet, review recorded), after the plan note of `cadence spec reseal`, after `cadence deliver` opens an article lot, and after the config lot
252
+ of `cadence config set --all`. There is one code path for all of them, so a new command that writes the plan gets it for free.
253
+ - `files` (optional list of files, relative to the repository, never outside it; a folder is refused with `plan.onWrite.files « <path> » est un dossier`) are the files the command produces. They are **kept with the plan**, like `plan.files`: a commit that touches only
254
+ them and the plan is plan upkeep, and they go **in the same commit as the plan** everywhere the program commits the plan (`cadence orchestrate`, `cadence config set`, `cadence spec reseal`, the
255
+ `hook.autostart: start` pre-commit). A produced file that is not tracked yet is added. `raf` itself never commits: for its commands, `git add` the plan and the produced files together.
256
+ - A command that fails (non-zero code, cannot start, time limit) **refuses the write**: the plan and the declared files are put back as they were, the command stops with
257
+ `plan.onWrite en échec (<command>, code 7) : <last lines of its output> — écriture refusée, plan inchangé`, and in a wave the lot is handed back with that message. With `hook.autostart: start`, the pre-commit hook says `démarrage automatique refusé : plan.onWrite en échec …`, the lot stays todo and the commit goes through without the plan (never the « raf start fait par le hook » line).
258
+ `cadence spec reseal` commits the reseal before writing its note: if `plan.onWrite` then fails, the error says `le rescellé a eu lieu (commit <sha>) mais la note du lot n’a pas été écrite` (exit 2, plan unchanged); running the command again answers « rien à resceller », so add the note by hand with `raf note` once the generator works.
259
+ A command that succeeds without producing a declared file (a wrong path in `files`) does not refuse the write, but says `raf: plan.onWrite a réussi mais le fichier déclaré est absent : <path>` on stderr — the file it really wrote would otherwise stay out of the plan's commit.
260
+ - A read-only plan (kept by another tool) never runs the command: the project's tool owns the write.
261
+
216
262
  Until the file is declared, such a commit is reported as a "commit without a
217
263
  lot" when it cites none, and counts as work on the lots it cites.
218
264
 
@@ -244,6 +290,7 @@ plan:
244
290
  repos: depots # the lot's neighbouring repositories (a list of paths and/or { path, cite }, or one of them)
245
291
  parent: parent
246
292
  add: 'mytool add {title}' # optional: the command that creates a lot here (L342: `cadence config set --all` uses it; {title} is replaced by the quoted title)
293
+ commit: 'chore({lot}): {action}' # optional (L343): the subject of the plan commits cadence writes — see « The subject of the plan commits » below
247
294
  statuses: # raf status: their states
248
295
  todo: [prevu, specifie]
249
296
  doing: [en_cours, teste]
package/docs/ref.md CHANGED
@@ -7,7 +7,7 @@ tables drawn as pictures, message templates, figures of a norm (WMO, ICAO…). `
7
7
  turns such a document into files a session can read without losing the pictures. The whole
8
8
  design is in
9
9
  [`docs/superpowers/specs/2026-10-23-ref-documents-de-reference-design.md`](superpowers/specs/2026-10-23-ref-documents-de-reference-design.md)
10
- (issue #14); this page describes what is delivered so far: **`cadence ref add`** (lot 1 of 10), **`cadence ref measure`** (lot 2 of 10) **`cadence ref check`** with the inventory schema (lot 3 of 10), **`cadence ref eval`** (lot 4 of 10, with the synthetic corpus `cadence ref corpus`) **`cadence ref analyse`** (lot 5 of 10: the `reference-analyst` agent, one single reading) and **`cadence ref diff`** with the double reading (lot 6 of 10). **The series stops at lot 6**: the stopping point of the double reading (hypothesis H1) was not met, so lots 7 to 10 are dropped and the double reading stays an option, `--double` ([measurement and conclusion](mesures/ref-double-lecture-2026-10-25.md)).
10
+ (issue #14); this page describes what is delivered so far: **`cadence ref add`** (lot 1 of 10), **`cadence ref measure`** (lot 2 of 10) **`cadence ref check`** with the inventory schema (lot 3 of 10), **`cadence ref coverage`** (the coverage of the document by the lots of the plan), **`cadence ref eval`** (lot 4 of 10, with the synthetic corpus `cadence ref corpus`) **`cadence ref analyse`** (lot 5 of 10: the `reference-analyst` agent, one single reading) and **`cadence ref diff`** with the double reading (lot 6 of 10). **The series stops at lot 6**: the stopping point of the double reading (hypothesis H1) was not met, so lots 7 to 10 are dropped and the double reading stays an option, `--double` ([measurement and conclusion](mesures/ref-double-lecture-2026-10-25.md)).
11
11
 
12
12
  ## `cadence ref add <document.pdf> [--sections 3-5,9] [--slug name]`
13
13
 
@@ -290,7 +290,7 @@ text of a `texte`, role and content of a `composant`, figure of a `lien`…), si
290
290
  | **silence** | a question, in either reading | gathered (one question once), never sent to a third look and not counted below: a silence is not a reading that someone else contradicts |
291
291
 
292
292
  More than **30 %** of the paired-or-single elements unpaired (silences left out, `--seuil` changes it, in percent) means the two readings do not describe the same document: no reconciliation, the question goes to the lead (exit 1, nothing written).
293
- The ids of the result are renumbered `<slug>#<type>-<n>` (reading A in its order, then what B alone carries) and the references (`composant.ecran`, `etat.cible`, `statut.couvre`) follow; an element that referred to a discarded one is discarded with it, **except a paired element** (both readings carry it): it holds while one of its two versions points to a kept element, and is then `incertain` with the cause when only one version does; it is discarded only when both versions point to discarded elements (see `ref analyse`, « Reconciliation »).
293
+ The ids of the result are renumbered `<slug>#<type>-<n>` (reading A in its order, then what B alone carries), except that an element read again keeps the id it had in the inventory `--out` replaces (see `ref coverage`) and the references (`composant.ecran`, `etat.cible`, `statut.couvre`) follow; an element that referred to a discarded one is discarded with it, **except a paired element** (both readings carry it): it holds while one of its two versions points to a kept element, and is then `incertain` with the cause when only one version does; it is discarded only when both versions point to discarded elements (see `ref analyse`, « Reconciliation »).
294
294
 
295
295
  ```
296
296
  $ cadence ref diff a.json b.json --out inventaire.json
@@ -334,7 +334,7 @@ and output tokens of the session (the count of the orchestrate budget; cache rea
334
334
  in the list counts as **no answer**: the element stays `incertain` (`troisième regard sans réponse`), never a `lu` by default. No element carried by one reading only: no third look, no session;
335
335
  - the **silences** of both readings are gathered, once per question, and never sent to a third look.
336
336
 
337
- The result is **`<references.dir>/<slug>/inventaire.json`**: `genre: "inventaire"`, valid for `ref check`, ids renumbered, `usage` = `{ tokens, lectures: { a, b, regard } }` (the total of the three sessions and each one's tokens and model, what `ref eval` reads for the tokens per page)
337
+ The result is **`<references.dir>/<slug>/inventaire.json`**: `genre: "inventaire"`, valid for `ref check`, ids renumbered (an element already in the previous `inventaire.json` keeps its id), `usage` = `{ tokens, lectures: { a, b, regard } }` (the total of the three sessions and each one's tokens and model, what `ref eval` reads for the tokens per page)
338
338
  and `reconciliation` = the counts, the unpaired ratio and, for each element the third look ruled on, `{ lecture, id (in its reading), verdict, raison }`.
339
339
 
340
340
  A session cut by a signal with nothing written is started again once in a fresh session (like a review); cut twice, it is a failure (exit 2). Without `--double` the single reading is written to `lectures/a.json` and checked as above; `--model-b` and `--seuil` are refused then (exit 2).
@@ -372,6 +372,64 @@ the same number for a corpus case read whole. Without `--double` the output is t
372
372
  `docs/mesures/ref-lecture-unique-2026-10-24.md` states what the single reading is measured on and with which commands (recall, false « read », tokens per page against the corpus of `ref corpus`, the declared ceiling), and what was measured.
373
373
  The double reading is measured the same way, `ref eval` on `inventaire.json` instead of `lectures/a.json`, and compared with that baseline: `docs/mesures/ref-double-lecture-2026-10-25.md` (hypothesis H1, the stopping point of lot 6: not met, which is why `--double` is an option).
374
374
 
375
+ ## `cadence ref coverage <inventaire.json>`
376
+
377
+ Answers one question without a model: **what of the reference document is covered by a lot?** (L367, GitHub issue #19). It takes the
378
+ inventory of `ref analyse --double` / `ref diff --out`, or the reading `lectures/a.json` that `ref analyse` writes by default (a reading carries
379
+ the same elements and ids). Each element has an **id** `<slug>#<type>-<n>`, and the id **stays the element's address from one reading of the
380
+ document to the next**: `ref diff --out` reads the inventory it replaces, and every `ref analyse` (single reading or `--double`) reads what it replaces
381
+ (the `inventaire.json` of the document, else its `lectures/a.json`); they give an element read again (same type, same page, same identity, the zone that overlaps most)
382
+ its old id, the references (`ecran`, `cible`, `couvre`) following; a new element takes a number above the highest of its type, a number of a vanished
383
+ element is never reused, so a lot that quotes `doc#texte-3` keeps pointing at the same element when one is inserted before it. The written `exclusions`
384
+ come along. (`ref diff` on two readings that were never reconciled before, or a `ref analyse` of a document with no valid previous file, numbers by order.) `ref check` refuses two elements with the same id, and
385
+ `source` says the page and zone of the element. A lot **covers** an element by quoting its id in its title, its goal (`goal:`) or a note
386
+ the lead wrote on it; the sub-tasks' notes and the notes the program writes itself (title replaced, minors of a review, hypotheses, put back on hold) do not count.
387
+
388
+ ```
389
+ $ cadence ref coverage inventaire.json
390
+ ✗ exemple#texte-3 (texte p.1 zone 120,600,240,64 « Valider ») : aucun lot, aucune exclusion écrite
391
+ ✗ exemple#composant-1 (composant p.2 zone 80,40,300,60 « Layers ») : lot non fait (L12 doing)
392
+ ✗ exemple#texte-5 (texte p.1 zone 10,10,90,20 « Aide ») : justifié non pris par la fiche de L12 : service absent, sans exclusion écrite
393
+ 3 élément(s) sans lot fait ni exclusion écrite sur 11
394
+ $ echo $?
395
+ 1
396
+ ```
397
+
398
+ An element is **covered** when a lot **done** quotes its id and its sheet does not justify it as not taken, or when the file carries a **written exclusion** with its reason:
399
+
400
+ ```json
401
+ "exclusions": [{ "id": "exemple#texte-7", "raison": "décor du document, pas du produit" }]
402
+ ```
403
+
404
+ A line `<id> : <reason>` in `sujet.perimetre.hors_perimetre` of the sheet of a lot (`docs/specs/<sujet>/fiche.yaml`, `sujet.lot`) is how the spec-writer justifies an element it
405
+ does not take: that lot does not cover it, it stays a gap until the lead writes its exclusion or another done lot quotes it. (A justification written
406
+ only in the report, or in a sheet with no back part, is not read: the lead removes the id from the lot or writes the exclusion.)
407
+
408
+ `exclusions` is a key of the root of the file (`ref check` ignores it, as it ignores any unknown key). An exclusion without a
409
+ reason, or of an id that is not in the file, excludes nothing and is listed (`✗ exclusions[0].raison : …`). A lot `dropped` covers
410
+ nothing, a lot `todo` or `doing` is named in the line of the element but does not cover it. **The exclusions are written in a file that `ref analyse`
411
+ replaces** (`references.dir` is outside the repository): before it removes or rewrites `inventaire.json`, `lectures/a.json` or `lectures/b.json`, `ref analyse` copies the
412
+ `exclusions` they carry into `exclusions-<date>.json` next to them and says so (`attention : …`); `ref analyse --double` and `ref diff --out` carry those of the inventory they replace into the new one (an id that no
413
+ longer exists is then listed by this command).
414
+
415
+ Exit codes: **0** every element is covered (`couverture complète : N éléments (n par un lot fait, k exclus avec leur raison)`), **1** at
416
+ least one is not (the `✗` lines, then the count, **on the standard output**), **2** wrong call, unreadable file, a file that does not pass `ref check` or no plan. It launches no model and no system tool, reads the plan like `raf` does (`cadence.yaml` `plan:`) and the sheets under `orchestrate.spec.dir`, and writes nothing.
417
+
418
+ ### Planning the coverage: the `reference-planner` agent
419
+
420
+ The lead runs `cadence ref coverage` to see the gaps, then has the [`reference-planner`](../agents/reference-planner.md) agent read the inventory
421
+ and the plan. It **only proposes**: lots that cite the ids they cover, each **feasible** or **blocked** (`bloque_par` names the missing datum or
422
+ service, never « missing information »), what each depends on, the elements it would exclude and the silences that are questions for a human. It creates
423
+ no lot and writes no file; the lead creates the lots (`raf add "… exemple#texte-3 …"`) and writes the exclusions. Run
424
+ `cadence ref coverage` again until it exits 0.
425
+
426
+ ### Briefs of a wave
427
+
428
+ When the title, the goal or a note of a lot quotes ids of an inventory, `cadence orchestrate` puts them in two briefs (and in `--dry-run`): the
429
+ **spec-writer** takes each into its sheet (the field, rule or state that shows it quotes the id in its `source`) and **justifies each element it does not
430
+ take** (`<id> : <reason>` under « choix » of its report, and in `perimetre.hors_perimetre` when the sheet has a back part); the **ux-reviewer**
431
+ checks each of them on the finished screen and reports a missing or different one as a finding naming its id. A small visible lot has no `ux` pass: its single `review-small` pass carries the same instruction and checks the ids on the finished screen. A lot that quotes no id has the same briefs as before.
432
+
375
433
  ## `cadence ref corpus <dossier> [--sans-rendu]`
376
434
 
377
435
  Writes the **synthetic corpus with a known truth** that `ref eval` scores against (design §« Évaluation »): seven small documents,
package/docs/releasing.md CHANGED
@@ -30,7 +30,7 @@ A version exists in three places and is published in two; a release does all of
30
30
  while an existing one is left alone. If the package is on npm but the release is still missing, re-run the job;
31
31
  the by-hand fallback is `gh release create v<version> --title v<version> --notes-file <the section of CHANGELOG.md> --verify-tag`.
32
32
 
33
- `.github/workflows/tests.yml` runs the type-check and the test suite on the same runner image, Node 24 and `npm ci` as the publish job, and never publishes: start it with `gh workflow run tests.yml --ref <branch>` or by pushing a `diag/**` branch, to see on the runner what a red publish run saw.
33
+ `.github/workflows/tests.yml` runs the type-check and the test suite on the same runner image and `npm ci` as the publish job, in a matrix of Node 22 and 24 (the minimum `engines.node` allows, and the Node the publish job uses), and never publishes: start it with `gh workflow run tests.yml --ref <branch>` or by pushing a `diag/**` branch, to see on the runner what a red publish run saw.
34
34
 
35
35
  The Claude Code plugin is read from the repository, so pushing `main` is what updates it; npm is what
36
36
  `npx @sylad/cadence` and a global install read, and only the tag publishes there. A missing tag, or a red
@@ -71,8 +71,10 @@ version files are written, the commit is not ready. `--dry-run` reads the forms
71
71
 
72
72
  It prints the lead's steps, in order, and runs none: the `git add` of exactly the files of the version commit and the commit,
73
73
  `npm run eval:plugin -- --trust-plugin` (only when `package.json` has that script), `git tag v<x.y.z>`, `git push origin <branch> v<x.y.z>`,
74
- `gh run watch`, `npm view <package> version` (not for a private package) and `claude plugin update <plugin>@<marketplace>`
75
- (only with a `.claude-plugin/plugin.json`). The 2026-10-25 releases (1.10.0 to 1.12.1) were this chain done four times by hand,
74
+ `gh run watch`, `npm view <package> version` (not for a private package) and, only with a `.claude-plugin/plugin.json`,
75
+ the plugin update: `claude plugin marketplace update <marketplace>` first (without it the installed plugin stays at the old
76
+ version; skipped when the marketplace has no name), then `claude plugin update <plugin>@<marketplace>`, then `/reload-plugins`
77
+ in the open Claude Code session. The 2026-10-25 releases (1.10.0 to 1.12.1) were this chain done four times by hand,
76
78
  once with the long paragraph inserted at the wrong place of `docs/whats-new.md`.
77
79
 
78
80
  ## Evaluating the plugin (before a release)
package/docs/session.md CHANGED
@@ -42,6 +42,14 @@ loop over the sub-folders. A project that is not the root of its own git reposit
42
42
  (and exit 2) instead of the parent repository's plan. `--all` and `--depth` are refused outside `session` (`--depth` is also read by `cadence lead tour`),
43
43
  and `--all` refuses `--file`, `--config` and `RAF_FILE`: each project reads its own plan. For a one-line-per-project overview, `cadence lead tour`.
44
44
 
45
+ A note that quotes a lot already `done` or `dropped` is not relayed as it stands (L347): `session start`
46
+ prints it with the lot beside it, `- L217 à faire ⚠ note périmée : L217 done le 24-10` (`dropped` the same
47
+ way; the date is the lot's `finished`, left out when it has none; an id is read whole, so `L2` is not found in
48
+ `L217`). `session close` lists those notes under `Notes de la dernière clôture à réécrire` — nothing
49
+ changes in the verdict — and the `session-close` skill offers to rewrite them with `cadence session next`.
50
+ Generic: the check reads the ids of the project's plan, whatever their form. A note that
51
+ quotes no finished lot is shown unchanged.
52
+
45
53
  `cadence session next` without a line refuses (exit 2) and leaves the notes of the
46
54
  last close as they are — it used to erase them silently; erasing is `--clear`.
47
55
 
package/docs/skills.md CHANGED
@@ -74,6 +74,7 @@ repository with `cadence skills install` (to `.claude/skills/cadence-*` and
74
74
  `Grep`, `Glob`: it writes only its reading, never the tokens (the program counts them), never an expected inventory, and it never opens
75
75
  another file of `lectures/` (L266: with `--double`, `cadence ref analyse` launches it twice, readings A and B, which must stay independent).
76
76
  `cadence ref analyse` launches it ([ref](ref.md#cadence-ref-analyse-documentpdf---sections-3-59---slug-name---model-sonnetopushaiku---model-b-sonnetopushaiku---seuil-30---double)).
77
+ - **reference-planner** (agent, L367): reads an inventory and the plan, starts from `cadence ref coverage`, and **proposes** lots that cite the ids they cover, each feasible or blocked (the missing datum or service named) with its dependencies. Its tools are `Read`, `Grep`, `Glob` and `Bash`: it creates no lot and writes no file, the lead decides ([ref](ref.md#planning-the-coverage-the-reference-planner-agent)).
77
78
  - **reference-verifier** (agent, L266): the third look of `cadence ref analyse`. It receives only the elements that one of the two readings wrote and the
78
79
  other did not, each with the rendered page and a crop of its zone, and answers `confirme` or `ecarte` with a reason, measuring with
79
80
  `cadence ref measure` rather than by eye and never guessing (what it cannot tell is `ecarte`). Its tools are `Read` and `Bash`: it writes nothing and never sees the readings