@sylad/cadence 1.6.0 → 1.8.0

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 (49) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +47 -1734
  4. package/agents/acceptance-writer.md +1 -0
  5. package/bin/cadence.js +4 -1
  6. package/dist/audit.js +4 -3
  7. package/dist/cli.js +68 -7
  8. package/dist/config.js +46 -0
  9. package/dist/corrections.js +57 -22
  10. package/dist/deliver.js +8 -1
  11. package/dist/orchestrate/accept.js +8 -3
  12. package/dist/orchestrate/briefs.js +1 -1
  13. package/dist/orchestrate/command.js +3 -0
  14. package/dist/orchestrate/cycle.js +68 -6
  15. package/dist/perf.js +559 -0
  16. package/dist/state.js +29 -0
  17. package/docs/cadence-hud.md +50 -0
  18. package/docs/corrections.md +24 -0
  19. package/docs/cycle-vague-dark.svg +42 -0
  20. package/docs/cycle-vague-light.svg +42 -0
  21. package/docs/deliver.md +204 -0
  22. package/docs/fonctionnement-lead-dark.svg +101 -0
  23. package/docs/fonctionnement-lead-light.svg +101 -0
  24. package/docs/fonctionnement-lot-dark.svg +38 -0
  25. package/docs/fonctionnement-lot-light.svg +38 -0
  26. package/docs/fonctionnement-service-dark.svg +23 -0
  27. package/docs/fonctionnement-service-light.svg +23 -0
  28. package/docs/fonctionnement-wave-dark.svg +66 -0
  29. package/docs/fonctionnement-wave-light.svg +66 -0
  30. package/docs/fonctionnement.md +106 -0
  31. package/docs/lead-workflow-dark.svg +200 -0
  32. package/docs/lead-workflow-light.svg +200 -0
  33. package/docs/lead.md +149 -0
  34. package/docs/news.md +245 -0
  35. package/docs/orchestrate.md +639 -0
  36. package/docs/overview.md +27 -0
  37. package/docs/perf.md +84 -0
  38. package/docs/raf.md +192 -0
  39. package/docs/releasing.md +59 -0
  40. package/docs/session.md +107 -0
  41. package/docs/skills.md +76 -0
  42. package/docs/spec.md +60 -0
  43. package/docs/stabilite.md +35 -0
  44. package/docs/whats-new.md +83 -0
  45. package/package.json +29 -1
  46. package/skills/lead/SKILL.md +7 -0
  47. package/templates/acceptation-runner.mjs +1 -1
  48. package/templates/fiche-back.yaml +1 -1
  49. package/templates/fiche.yaml +1 -1
@@ -0,0 +1,24 @@
1
+ [← cadence](../README.md)
2
+
3
+ # corrections
4
+
5
+ `cadence corrections [folder] [--classify [--model m]] [--cache file] [--sample n] [--until date] [--since date] [--json]` measures how often a wave
6
+ has to correct itself. From the folder a wave was launched from (default: the current one) it reads the journals
7
+ `<folder>/.cadence/runs/<wave>/<project>--<lot>.json` of **every project** and prints:
8
+
9
+ - the **lots with a correction**: of the lots that went through a successful write session, the share with at least one
10
+ and at least two `fix` passes, per project and for visible versus non-visible lots (`61 %` and `29 %` of 263 lots in the 10-10 baseline);
11
+ - the **passes by cause**, in total, per project and **per visibility** (visible lots apart from the others, with the share of `spec-floue`, `regle-inventee` and `test-manquant` among each one's passes — the reference an evolution aimed at screens is compared to is that of the visible lots, not « all lots »: 16 % of 45 passes against 31 % overall in the 10-10 baseline). The cause of a pass is read from the findings of the review that
12
+ precedes it, among: unclear spec or missing criterion (`spec-floue`), invented business rule (`regle-inventee`),
13
+ architecture drift (`derive-architecture`), data / SQL (`donnees-sql`), code bug (`bug-code`), missing test
14
+ (`test-manquant`), false review finding (`faux-constat`). One cause per pass: the root of what forced the fix. A pass with no review finding to read (triggered by red tests right after the write session) is counted apart as `sans-constat` and never sent to the model.
15
+
16
+ Without `--classify` the command calls no model: the passes not yet classified count as `non classé`. With `--classify`
17
+ each pass is classified by a **local model served by Ollama** (`OLLAMA_HOST`, default `http://localhost:11434`;
18
+ `--model`, default `mistral-small3.2:24b`; temperature 0, output constrained to the seven causes), off the Anthropic quota.
19
+ The classification is cached in `<folder>/.cadence/corrections.json` (or `--cache file`): a re-run classifies only the
20
+ new passes, and a pass the model could not answer stays out of the cache. `--sample n` prints `n` passes with findings spread
21
+ evenly over the sorted keys, with their findings and the model's cause, to be read by hand; a `manual` field written
22
+ beside a pass in the cache file (one of the seven causes, else the command refuses the file) overrides the model's cause in every table and gives the **agreement rate** of the model
23
+ on the reviewed sample. `--until date` (ISO, e.g. `2026-10-10T18:31Z`) reads only the waves created up to then (`created` in the wave's `wave.json`) and, in them, only the steps ended by then (`ended`), so that neither later waves nor a wave still running at that time change a published baseline; a wave or a step with no readable date is left out. `--since date` (ISO, same format) keeps only the lots of the waves **created after** that date (`created` in the wave's `wave.json`), **with all their passes**: a wave renders every brief from the copy of the tool frozen when it is created (`<wave>/tool/`), so the creation date of the wave, not the start of a lot, tells which brief the lot was given. A `--continue` wave created before a change of brief therefore does not give it to its lots, even those that start after the change (a difference of two unbounded runs is wrong as soon as a wave straddles the date); the exception is a wave resumed (`--resume`) without a snapshot (older than L61) or, with `--budget project:lot=+N`, from a snapshot older than L223: it runs with the current `dist/` and templates, so it does receive the new brief. A wave with no readable `created` is left out, by `--since` as by `--until`. `--since` is strict and `--until` inclusive, so a wave created exactly at the date is on the `--until` side only and the two sets never overlap; `--since` and `--until` can be combined (a window of creation dates). Together `--until d` and `--since d` split the waves, and they add up to the unbounded total only when `d` is set while no wave is running (between two sessions, as `npm run service` requires; `cadence orchestrate --status` shows none, **and** no wave created before `d` is still resumable: `--status` lists only the live waves, an interrupted or suspended one is not shown and `--resume` would replay passes after `d`; list them with `jq -r 'select(.status != "done") | "\(.id) \(.status) \(.created)"' <folder>/.cadence/runs/*/wave.json`, and finish each before setting `d` or never resume it): a wave created before `d` that ends a pass after `d` leaves that pass in neither (`--until` does not read it, so that a published baseline does not move), and a gap between the sum and the unbounded total means `d` fell inside a wave. `--json` prints the measure as data. The command reads the journals and writes nothing but the cache.
24
+ The first baseline is in [docs/mesures/corrections-2026-10-10.md](mesures/corrections-2026-10-10.md); `test/corrections-mesure.test.ts` replays its command on the journals and checks the document's results word for word (skipped when the journals are not there). The measure of the screen-sheet step against the visible lots of this baseline (L176: 8 lots with a sheet, verdict per row of the success table, token cost beside the passes avoided) is in [docs/mesures/etape-spec-2026-10-12.md](mesures/etape-spec-2026-10-12.md); the target of its third row is ≤ 8 %, half the 16 % reference. The measure of the back-end spec step against the non-visible lots (L190: 10 lots with a back-end sheet, passes by cause, token cost beside the passes avoided) is in [docs/mesures/etape-spec-back-2026-10-16.md](mesures/etape-spec-back-2026-10-16.md).
@@ -0,0 +1,42 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 250" width="1100" height="250" role="img" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif" font-size="13" fill="#e6edf3">
2
+ <title>Cycle of a wave and role of the lead</title>
3
+ <desc>A lot goes through spec, mockup, accept, implement, review, ux and deliver; the lead picks the lots, answers the questions, re-verifies and delivers.</desc>
4
+ <defs><marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#8b949e"/></marker></defs>
5
+ <rect width="1100" height="250" fill="#0d1117"/>
6
+ <rect x="20" y="16" width="1060" height="52" rx="6" fill="#0c2d6b" stroke="#58a6ff" stroke-width="1.5"/>
7
+ <text x="40" y="38" font-weight="600" font-size="13.5">The lead (you, or the lead skill)</text>
8
+ <text x="40" y="57" font-size="12" fill="#8b949e">picks the lots · answers the questions · re-verifies each lot · delivers one project at a time</text>
9
+ <rect x="20" y="130" width="122" height="62" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5" stroke-dasharray="5 4"/>
10
+ <text x="81" y="157" text-anchor="middle" font-weight="600" font-size="14" font-family="ui-monospace,SFMono-Regular,Menlo,monospace">spec</text>
11
+ <text x="81" y="176" text-anchor="middle" font-size="11.5" fill="#8b949e">sheet + rules</text>
12
+ <line x1="144" y1="161" x2="167" y2="161" stroke="#8b949e" stroke-width="1.5" marker-end="url(#a)"/>
13
+ <rect x="170" y="130" width="122" height="62" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5" stroke-dasharray="5 4"/>
14
+ <text x="231" y="157" text-anchor="middle" font-weight="600" font-size="14" font-family="ui-monospace,SFMono-Regular,Menlo,monospace">mockup</text>
15
+ <text x="231" y="176" text-anchor="middle" font-size="11.5" fill="#8b949e">page review</text>
16
+ <line x1="294" y1="161" x2="317" y2="161" stroke="#8b949e" stroke-width="1.5" marker-end="url(#a)"/>
17
+ <rect x="320" y="130" width="122" height="62" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5" stroke-dasharray="5 4"/>
18
+ <text x="381" y="157" text-anchor="middle" font-weight="600" font-size="14" font-family="ui-monospace,SFMono-Regular,Menlo,monospace">accept</text>
19
+ <text x="381" y="176" text-anchor="middle" font-size="11.5" fill="#8b949e">red tests, sealed</text>
20
+ <line x1="444" y1="161" x2="467" y2="161" stroke="#8b949e" stroke-width="1.5" marker-end="url(#a)"/>
21
+ <rect x="470" y="130" width="122" height="62" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
22
+ <text x="531" y="157" text-anchor="middle" font-weight="600" font-size="14" font-family="ui-monospace,SFMono-Regular,Menlo,monospace">implement</text>
23
+ <text x="531" y="176" text-anchor="middle" font-size="11.5" fill="#8b949e">test first</text>
24
+ <line x1="594" y1="161" x2="617" y2="161" stroke="#8b949e" stroke-width="1.5" marker-end="url(#a)"/>
25
+ <rect x="620" y="130" width="122" height="62" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
26
+ <text x="681" y="157" text-anchor="middle" font-weight="600" font-size="14" font-family="ui-monospace,SFMono-Regular,Menlo,monospace">review</text>
27
+ <text x="681" y="176" text-anchor="middle" font-size="11.5" fill="#8b949e">code review</text>
28
+ <line x1="744" y1="161" x2="767" y2="161" stroke="#8b949e" stroke-width="1.5" marker-end="url(#a)"/>
29
+ <rect x="770" y="130" width="122" height="62" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
30
+ <text x="831" y="157" text-anchor="middle" font-weight="600" font-size="14" font-family="ui-monospace,SFMono-Regular,Menlo,monospace">ux</text>
31
+ <text x="831" y="176" text-anchor="middle" font-size="11.5" fill="#8b949e">usability, once</text>
32
+ <line x1="894" y1="161" x2="917" y2="161" stroke="#8b949e" stroke-width="1.5" marker-end="url(#a)"/>
33
+ <rect x="920" y="130" width="122" height="62" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
34
+ <text x="981" y="157" text-anchor="middle" font-weight="600" font-size="14" font-family="ui-monospace,SFMono-Regular,Menlo,monospace">deliver</text>
35
+ <text x="981" y="176" text-anchor="middle" font-size="11.5" fill="#8b949e">CI + proof</text>
36
+ <line x1="831" y1="68" x2="831" y2="127" stroke="#58a6ff" stroke-width="1.2" stroke-dasharray="3 3" marker-end="url(#a)"/>
37
+ <line x1="81" y1="68" x2="81" y2="127" stroke="#58a6ff" stroke-width="1.2" stroke-dasharray="3 3" marker-end="url(#a)"/>
38
+ <line x1="981" y1="127" x2="981" y2="68" stroke="#58a6ff" stroke-width="1.2" stroke-dasharray="3 3" marker-end="url(#a)"/>
39
+ <text x="73" y="216" font-size="11.5" fill="#8b949e">dashed: only for a lot with a page or a behaviour to pin down (screen: page, spec: true)</text>
40
+ <text x="661" y="216" font-size="11.5" fill="#8b949e">each step in a fresh session, driven by cadence orchestrate</text>
41
+ <text x="20" y="236" font-size="11.5" fill="#8b949e">Every verdict is written to the plan (docs/plan/raf.yaml); a delivery counts only when its checks pass.</text>
42
+ </svg>
@@ -0,0 +1,42 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 250" width="1100" height="250" role="img" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif" font-size="13" fill="#1f2328">
2
+ <title>Cycle of a wave and role of the lead</title>
3
+ <desc>A lot goes through spec, mockup, accept, implement, review, ux and deliver; the lead picks the lots, answers the questions, re-verifies and delivers.</desc>
4
+ <defs><marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#57606a"/></marker></defs>
5
+ <rect width="1100" height="250" fill="#ffffff"/>
6
+ <rect x="20" y="16" width="1060" height="52" rx="6" fill="#ddf4ff" stroke="#0969da" stroke-width="1.5"/>
7
+ <text x="40" y="38" font-weight="600" font-size="13.5">The lead (you, or the lead skill)</text>
8
+ <text x="40" y="57" font-size="12" fill="#57606a">picks the lots · answers the questions · re-verifies each lot · delivers one project at a time</text>
9
+ <rect x="20" y="130" width="122" height="62" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5" stroke-dasharray="5 4"/>
10
+ <text x="81" y="157" text-anchor="middle" font-weight="600" font-size="14" font-family="ui-monospace,SFMono-Regular,Menlo,monospace">spec</text>
11
+ <text x="81" y="176" text-anchor="middle" font-size="11.5" fill="#57606a">sheet + rules</text>
12
+ <line x1="144" y1="161" x2="167" y2="161" stroke="#57606a" stroke-width="1.5" marker-end="url(#a)"/>
13
+ <rect x="170" y="130" width="122" height="62" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5" stroke-dasharray="5 4"/>
14
+ <text x="231" y="157" text-anchor="middle" font-weight="600" font-size="14" font-family="ui-monospace,SFMono-Regular,Menlo,monospace">mockup</text>
15
+ <text x="231" y="176" text-anchor="middle" font-size="11.5" fill="#57606a">page review</text>
16
+ <line x1="294" y1="161" x2="317" y2="161" stroke="#57606a" stroke-width="1.5" marker-end="url(#a)"/>
17
+ <rect x="320" y="130" width="122" height="62" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5" stroke-dasharray="5 4"/>
18
+ <text x="381" y="157" text-anchor="middle" font-weight="600" font-size="14" font-family="ui-monospace,SFMono-Regular,Menlo,monospace">accept</text>
19
+ <text x="381" y="176" text-anchor="middle" font-size="11.5" fill="#57606a">red tests, sealed</text>
20
+ <line x1="444" y1="161" x2="467" y2="161" stroke="#57606a" stroke-width="1.5" marker-end="url(#a)"/>
21
+ <rect x="470" y="130" width="122" height="62" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
22
+ <text x="531" y="157" text-anchor="middle" font-weight="600" font-size="14" font-family="ui-monospace,SFMono-Regular,Menlo,monospace">implement</text>
23
+ <text x="531" y="176" text-anchor="middle" font-size="11.5" fill="#57606a">test first</text>
24
+ <line x1="594" y1="161" x2="617" y2="161" stroke="#57606a" stroke-width="1.5" marker-end="url(#a)"/>
25
+ <rect x="620" y="130" width="122" height="62" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
26
+ <text x="681" y="157" text-anchor="middle" font-weight="600" font-size="14" font-family="ui-monospace,SFMono-Regular,Menlo,monospace">review</text>
27
+ <text x="681" y="176" text-anchor="middle" font-size="11.5" fill="#57606a">code review</text>
28
+ <line x1="744" y1="161" x2="767" y2="161" stroke="#57606a" stroke-width="1.5" marker-end="url(#a)"/>
29
+ <rect x="770" y="130" width="122" height="62" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
30
+ <text x="831" y="157" text-anchor="middle" font-weight="600" font-size="14" font-family="ui-monospace,SFMono-Regular,Menlo,monospace">ux</text>
31
+ <text x="831" y="176" text-anchor="middle" font-size="11.5" fill="#57606a">usability, once</text>
32
+ <line x1="894" y1="161" x2="917" y2="161" stroke="#57606a" stroke-width="1.5" marker-end="url(#a)"/>
33
+ <rect x="920" y="130" width="122" height="62" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
34
+ <text x="981" y="157" text-anchor="middle" font-weight="600" font-size="14" font-family="ui-monospace,SFMono-Regular,Menlo,monospace">deliver</text>
35
+ <text x="981" y="176" text-anchor="middle" font-size="11.5" fill="#57606a">CI + proof</text>
36
+ <line x1="831" y1="68" x2="831" y2="127" stroke="#0969da" stroke-width="1.2" stroke-dasharray="3 3" marker-end="url(#a)"/>
37
+ <line x1="81" y1="68" x2="81" y2="127" stroke="#0969da" stroke-width="1.2" stroke-dasharray="3 3" marker-end="url(#a)"/>
38
+ <line x1="981" y1="127" x2="981" y2="68" stroke="#0969da" stroke-width="1.2" stroke-dasharray="3 3" marker-end="url(#a)"/>
39
+ <text x="73" y="216" font-size="11.5" fill="#57606a">dashed: only for a lot with a page or a behaviour to pin down (screen: page, spec: true)</text>
40
+ <text x="661" y="216" font-size="11.5" fill="#57606a">each step in a fresh session, driven by cadence orchestrate</text>
41
+ <text x="20" y="236" font-size="11.5" fill="#57606a">Every verdict is written to the plan (docs/plan/raf.yaml); a delivery counts only when its checks pass.</text>
42
+ </svg>
@@ -0,0 +1,204 @@
1
+ [← cadence](../README.md)
2
+
3
+ # deliver
4
+
5
+ A delivery is done when its checks pass, not when a tool says "success".
6
+
7
+ ```yaml
8
+ # cadence.yaml, at the repository root
9
+ deliver:
10
+ ci: github # github | none | { command: "…" } (default: none)
11
+ ciTimeout: 1800 # seconds
12
+ deploy: # sh commands, in order, at the repo root
13
+ - ./scripts/deploy.sh "$CADENCE_SHORT"
14
+ verify: # at least one; retried every 10 s until verifyTimeout
15
+ - url: https://app.example.com/api/health
16
+ status: 200 # default 200
17
+ - url: https://app.example.com/version.txt
18
+ contains: "${SHORT}"
19
+ - command: kubectl rollout status deploy/app --timeout=60s
20
+ verifyTimeout: 300
21
+ deployTimeout: 1800 # seconds, per deploy command
22
+ ```
23
+
24
+ A public title (`public:`) longer than the site's Plan page accepts fails its build. Declare the limit
25
+ in `cadence.yaml` and it is caught before the push:
26
+
27
+ ```yaml
28
+ news:
29
+ publicTitleMax: 80 # characters; without the key, `raf check` warns at 80 in a project that has a news directory
30
+ ```
31
+
32
+ `raf check` also warns (`⚠`, not an error: exit code 0 if nothing else is wrong) about every visible lot, dropped ones excepted, that has no public title: the site would show its technical title (a done lot cited by a News entry is spared: the site reuses that entry's title). Fix it with `raf public <id> "…"`. On a plan kept by another tool (read-only), the warning only applies if `plan.fields` maps a `public` field, or if some lot already carries a literal `public:` key (a field left out is read under its own name): without either there is no public title to set, so it would come back at every session.
33
+
34
+ With the key, `raf public`, `raf add --public`, `raf done` (on a lot whose public title is too long) and
35
+ `cadence news new` (its title) refuse above it; without it, only `raf check` warns.
36
+
37
+ ```sh
38
+ cadence deliver --dry-run # preconditions, then the resolved steps; nothing runs
39
+ cadence deliver # 0 delivered and verified · 1 a step failed · 2 refused before acting
40
+ ```
41
+
42
+ - **Preconditions**: no modified tracked file; `HEAD` is on a remote branch (the
43
+ CI can only build what was pushed); no other delivery running (a lock whose
44
+ process died is removed with a warning); with `ci: github`, `gh` installed and
45
+ logged in.
46
+ - Every command is killed when it exceeds its budget (CI, `deployTimeout`, what is
47
+ left of `verifyTimeout`) and reported as "délai dépassé". Killed means the
48
+ whole tree, followed while the command runs: every 200 ms cadence lists the
49
+ command's descendants and remembers each process it has seen with its start
50
+ time, so a process whose parent exits (re-parented to init — a background
51
+ child of a script, `sh`'s fork for each command) stays known. To kill, cadence
52
+ takes every remembered process still alive with the same start time (never a
53
+ recycled pid) plus all its current descendants, stops each one first
54
+ (`SIGSTOP`, the tree is re-read until nothing new appears), then kills them
55
+ (`SIGKILL`). This happens on a delay overrun, and at once when cadence
56
+ receives `SIGTERM`. On Ctrl-C or a hangup the tree has already received the
57
+ signal from the terminal: cadence gives the whole followed set, not just the
58
+ command, up to 2 seconds to finish (a script's `trap`, git removing its
59
+ `index.lock`), carries on the moment none of it is alive, and kills what is
60
+ left after the 2 seconds, a background child that ignores Ctrl-C included. If
61
+ the command was never listed (process list unreadable from the start), the
62
+ grace lasts as long as the command itself is alive, and what remains after
63
+ 2 seconds is killed through the same fallback (the command itself only). The
64
+ same fallback applies when the process list fails while the command runs and
65
+ is still failing when the kill comes (the last listing is not trusted after
66
+ 1 second): only the command is killed, its descendants survive, and cadence
67
+ says so once on stderr.
68
+ A delay overrun and `SIGTERM` give no grace: a script's own `trap` does not
69
+ get to finish there. The lock is held until the followed set is empty: when
70
+ cadence dies of the signal it dies after the tree, and its stale lock, which
71
+ the next delivery removes, leaves nothing of the first delivery running.
72
+ What can still outlive cadence and run concurrently with the next delivery:
73
+ a process that leaves the tree before cadence first sees it (a daemon's
74
+ double fork, `setsid`, `nohup … &` from a shell that exits, all within less
75
+ than 200 ms), a process of another user that cadence may not signal (`sudo`),
76
+ a background process still running when the command returns normally (it is
77
+ neither waited for nor killed), and everything if cadence itself is killed
78
+ with `SIGKILL` (`kill -9`, the OOM killer).
79
+ Where the process list cannot be read (no `/proc` and `ps` failing), cadence
80
+ can only kill the root command (`sh`): it leads no group of its own, since it
81
+ is deliberately kept in cadence's group, so its descendants are not killed and
82
+ survive it. A list that fails for more than a second is no longer
83
+ trusted (a followed pid may have been recycled): nothing is signalled from it.
84
+ cadence says so once on stderr when the list is unavailable.
85
+ - Commands run in cadence's own process group and session, attached to the
86
+ terminal: `ssh`, `sudo` or `pinentry` can prompt on `/dev/tty`, and Ctrl-C or
87
+ closing the terminal stops the running command together with cadence (within
88
+ the limits above).
89
+ - **CI** `github`: polls `gh run list --commit <sha>` every 15 s; no run after
90
+ 5 minutes is a failure (you probably pushed another commit than the one you
91
+ deliver); every run must end `success`, `skipped` or `neutral`. `gh` errors
92
+ are retried, and reported with their cause after 5 minutes.
93
+ - Commands get `CADENCE_SHA`, `CADENCE_SHORT` (7 characters) and `CADENCE_BRANCH`;
94
+ `${SHA}` and `${SHORT}` are replaced in `url` and `contains`.
95
+ - On success the lots cited by the commits since the previous delivery are
96
+ listed, so you can `raf done` those whose effect you have seen. With a
97
+ read-only plan, only the lots that were in progress when the delivery started
98
+ are listed: an id quoted in a message for context (a finished lot, a
99
+ reservation number that looks like one) is not a delivered lot. Plan upkeep
100
+ commits (see "Plan upkeep needs no lot") are skipped in that list: they cite
101
+ lots without delivering anything.
102
+ - With [`docs.articles`](news.md#an-article-elsewhere-that-follows-the-delivery), a green delivery of lots with a
103
+ public title also opens the lot `Article <project> à rafraîchir` in the plan.
104
+
105
+ ## A project with its own delivery script
106
+
107
+ A project that already delivers with its own script (CI wait, deploy, business
108
+ checks) plugs it in instead of rewriting it as `ci` / `deploy` / `verify`:
109
+
110
+ ```yaml
111
+ deliver:
112
+ script: ./scripts/ship.sh "$CADENCE_SHORT" # replaces ci and deploy
113
+ allowDirty: true # optional: a modified tree is reported, not refused
114
+ deployTimeout: 3600 # seconds, for the whole script
115
+ verify: [] # optional here: the script's own checks count
116
+ ```
117
+
118
+ ```sh
119
+ cadence deliver -- api frontend --news docs/changelog/x.md -- map # everything after the first « -- » goes to the script
120
+ cadence deliver --dry-run -- api # shows the full command, runs nothing
121
+ cadence deliver --sha 6b0d9aa -- api # an earlier pushed commit instead of HEAD
122
+ ```
123
+
124
+ `--sha` (any mode) delivers a pushed commit other than `HEAD` — for a CI that
125
+ builds each service only on the commit that touched it. `allowDirty` suits a
126
+ working tree shared by several sessions when the delivery starts from a pushed
127
+ sha and never from local files; without it a modified tree is refused.
128
+
129
+ cadence keeps what the script usually lacks: the preconditions (clean tree, pushed
130
+ `HEAD`), the lock (never two deliveries at once), the delivery log and the lots
131
+ delivered. Arguments are quoted for `sh`, so spaces and quotes reach the script
132
+ intact. If the script commits and pushes during the delivery (stamping a
133
+ changelog entry, say), the new `HEAD` is the sha recorded as delivered. Exit code
134
+ 0 of the script means delivered; `verify` checks, if any, run after it.
135
+
136
+ ## verify: replay the effect checks, outside a delivery
137
+
138
+ A green delivery says the effect was right *then*. `cadence verify` replays the
139
+ `deliver.verify` checks of `cadence.yaml` at any time — no CI, no deploy, no lock,
140
+ no journal entry, nothing written.
141
+
142
+ ```sh
143
+ cadence verify # one pass, one line per check, then a one-line summary
144
+ cadence verify --retry 60 # retry each failing check for up to 60 s (deliver's 300 s is not applied)
145
+ cadence verify --sha 6b0d9aa # the sha that replaces ${SHA} / ${SHORT} (default: see below)
146
+ ```
147
+
148
+ ```
149
+ ✓ GET https://ol.example/api/health → 200
150
+ ✗ GET https://ol.example/api/lineup → 200, contient « "starters" » — « "starters" » absent de la réponse
151
+ verify : 1 effet rouge sur 2 vérifications
152
+ ```
153
+
154
+ Exit code: **0** every check green · **1** at least one red effect · **2** nothing
155
+ to verify or invalid configuration.
156
+
157
+ **Which sha is expected.** By default `${SHA}` / `${SHORT}` is the last recorded
158
+ delivery (else `HEAD`). One case differs: a project with `ci: none` (the default) and
159
+ **neither** deploy commands (`deliver.deploy`) nor a delivery script — a host that
160
+ builds every push, like Cloudflare Pages — delivers by pushing, and every push counts,
161
+ plan-maintenance commits included. The expected sha is then the head of the tracked
162
+ upstream branch (`origin/<branch>`, as last fetched — `verify` does no network git),
163
+ **provided that upstream is the remote's production branch**: read without network from
164
+ `refs/remotes/origin/HEAD` (set by `git clone` or `git remote set-head origin <branch>`);
165
+ when that reference does not exist locally (repository created with `git init` + push),
166
+ `main` then `master` are taken as the production branch. A work branch pushed with `-u`
167
+ is not published by such a host, so it keeps the default rule. A project with `ci: github`
168
+ or a `ci.command` and no deploy also keeps the last delivery. When local `HEAD` is ahead
169
+ of the upstream, the report says so first — `2 commit(s) non poussé(s) — l'effet vérifié
170
+ est celui de origin/main` — so a red there reads "waiting for a push", not "broken
171
+ effect". `session start` uses the same rule; `--sha` always wins.
172
+
173
+ Time limits differ from deliver's. `verify` runs all the checks **in parallel**,
174
+ each try with the whole 120 s limit (a `url` request gives up after 20 s; a slow
175
+ check is killed at its limit, "délai dépassé", and takes nothing from the others); results are printed in the order
176
+ of `cadence.yaml`. Retries repeat every 10 s (deliver's interval) up to `--retry`
177
+ seconds for each check on its own, so a run lasts at most `--retry` + 120 s.
178
+ deliver, by contrast, runs its checks one after the other, retries each until
179
+ `verifyTimeout` (300 s by default) is spent, and stops at the first check that
180
+ never turns green. The check code is deliver's own (`url` / `status` /
181
+ `contains` / `command`, same messages). A project with
182
+ a delivery script (`deliver.script`) and no `verify` declares no effect checks —
183
+ `verify` says so and exits 2 (its script's own checks stay its business); add
184
+ `deliver.verify` to replay some.
185
+
186
+ Each `command` check of `verify` (and of `session start`) runs in a process group
187
+ of its own, detached from the terminal (no `/dev/tty`: a check must not prompt).
188
+ At its time limit the whole group is killed, so no child survives it; Ctrl-C,
189
+ `SIGTERM` or a closed terminal is passed on to the running checks before cadence
190
+ exits. As the checks run together, the output of their commands may interleave;
191
+ the result lines come after it, in order.
192
+
193
+ Make `verify` count: a health endpoint stays green while the data is wrong (a
194
+ lineup served empty for 38 h behind a green `/api/health`). Add a check on the
195
+ content that matters, e.g. `url: …/api/lineup` with `contains: '"starters"'`.
196
+
197
+ `cadence session start` runs the same checks in parallel (one try each, command
198
+ output muted), each with a 10 s limit, so the whole step takes about 10 s at worst;
199
+ it is not deliver's `verifyTimeout`, and there is no retry. It adds an
200
+ **"Effets en production"** section to the morning report: a single `✓` line when
201
+ everything is green, the red effects and the summary otherwise. It is a fact like
202
+ the others: it never changes the exit code, and an unreachable network shows up
203
+ as red lines ("erreur réseau") without blocking the session. No section for a
204
+ project without `deliver.verify`.
@@ -0,0 +1,101 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1290 372" width="1290" height="372" role="img" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif" font-size="13" fill="#e6edf3">
2
+ <title>The lead drives the waves</title>
3
+ <desc>Three lanes: the human chooses and decides, the lead starts, relays, re-verifies and delivers, the orchestrate program runs the lots and chains the next wave while the context stays under 60 percent.</desc>
4
+ <defs><marker id="arr3" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#8b949e"/></marker></defs>
5
+ <rect width="1290" height="372" fill="#0d1117"/>
6
+ <rect x="0" y="30" width="1290" height="90" fill="#161b22"/>
7
+ <rect x="0" y="230" width="1290" height="90" fill="#161b22"/>
8
+ <text x="12" y="71" text-anchor="start" font-weight="600" font-size="13.5" fill="#e6edf3">Human</text>
9
+ <text x="12" y="88" text-anchor="start" font-size="11.5" fill="#8b949e">chooses, decides</text>
10
+ <text x="12" y="171" text-anchor="start" font-weight="600" font-size="13.5" fill="#e6edf3">Lead</text>
11
+ <text x="12" y="188" text-anchor="start" font-size="11.5" fill="#8b949e">short session</text>
12
+ <text x="12" y="271" text-anchor="start" font-weight="600" font-size="13.5" fill="#e6edf3">orchestrate</text>
13
+ <text x="12" y="288" text-anchor="start" font-size="11.5" fill="#8b949e">fresh sessions</text>
14
+ <text x="200" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#8b949e">1 TOUR</text>
15
+ <text x="344" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#8b949e">2 WAVE</text>
16
+ <text x="488" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#8b949e">3 RELAY</text>
17
+ <text x="632" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#8b949e">4 TABLE</text>
18
+ <text x="776" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#8b949e">5 RE-CHECK</text>
19
+ <text x="920" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#8b949e">6 DELIVERY</text>
20
+ <text x="1064" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#8b949e">7 THRESHOLD</text>
21
+ <text x="1208" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#8b949e">8 CLOSE</text>
22
+ <rect x="136" y="46" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
23
+ <text x="200" y="71.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Chooses</text>
24
+ <text x="200" y="86.5" text-anchor="middle" font-size="11.5" fill="#8b949e">lots + order</text>
25
+ <rect x="424" y="46" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5" stroke-dasharray="6 4"/>
26
+ <text x="488" y="71.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Answers</text>
27
+ <text x="488" y="86.5" text-anchor="middle" font-size="11.5" fill="#8b949e">on a question</text>
28
+ <rect x="712" y="46" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5" stroke-dasharray="6 4"/>
29
+ <text x="776" y="71.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">UX verdict</text>
30
+ <text x="776" y="86.5" text-anchor="middle" font-size="11.5" fill="#8b949e">or lead (lead.ux)</text>
31
+ <rect x="1144" y="46" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
32
+ <text x="1208" y="71.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Report</text>
33
+ <text x="1208" y="86.5" text-anchor="middle" font-size="11.5" fill="#8b949e">1 line / project</text>
34
+ <rect x="136" y="146" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
35
+ <text x="200" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">lead tour</text>
36
+ <text x="200" y="186.5" text-anchor="middle" font-size="11.5" fill="#8b949e">3 proposals</text>
37
+ <rect x="280" y="146" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
38
+ <text x="344" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Starts</text>
39
+ <text x="344" y="186.5" text-anchor="middle" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#8b949e">--wave</text>
40
+ <rect x="424" y="146" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
41
+ <text x="488" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Relays</text>
42
+ <text x="488" y="186.5" text-anchor="middle" font-size="11.5" fill="#8b949e">1 sentence / line</text>
43
+ <rect x="568" y="146" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
44
+ <text x="632" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Triages</text>
45
+ <text x="632" y="186.5" text-anchor="middle" font-size="11.5" fill="#8b949e">observable bug → lot</text>
46
+ <rect x="712" y="146" width="128" height="58" rx="6" fill="#0c2d6b" stroke="#58a6ff" stroke-width="2"/>
47
+ <text x="776" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Re-verifies</text>
48
+ <text x="776" y="186.5" text-anchor="middle" font-size="11.5" fill="#8b949e">tests · build · check</text>
49
+ <rect x="856" y="146" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
50
+ <text x="920" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Delivers</text>
51
+ <text x="920" y="186.5" text-anchor="middle" font-size="11.5" fill="#8b949e">push + deliver</text>
52
+ <rect x="1000" y="146" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
53
+ <text x="1064" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">&lt; 60 % ?</text>
54
+ <text x="1064" y="186.5" text-anchor="middle" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#8b949e">session context</text>
55
+ <rect x="1144" y="146" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
56
+ <text x="1208" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Close</text>
57
+ <text x="1208" y="186.5" text-anchor="middle" font-size="11.5" fill="#8b949e">if ≥ 60 %</text>
58
+ <rect x="280" y="246" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
59
+ <text x="344" y="271.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Lots</text>
60
+ <text x="344" y="286.5" text-anchor="middle" font-size="11.5" fill="#8b949e">figure 2, per lot</text>
61
+ <rect x="424" y="246" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
62
+ <text x="488" y="271.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">journal.log</text>
63
+ <text x="488" y="286.5" text-anchor="middle" font-size="11.5" fill="#8b949e">1 line / transition</text>
64
+ <rect x="568" y="246" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
65
+ <text x="632" y="271.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">Final table</text>
66
+ <text x="632" y="286.5" text-anchor="middle" font-size="11.5" fill="#8b949e">ready · handed back</text>
67
+ <rect x="856" y="246" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
68
+ <text x="920" y="271.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">qa-reviewer</text>
69
+ <text x="920" y="286.5" text-anchor="middle" font-size="11.5" fill="#8b949e">read only</text>
70
+ <rect x="1000" y="246" width="128" height="58" rx="6" fill="#0d1117" stroke="#8b949e" stroke-width="1.5"/>
71
+ <text x="1064" y="271.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#e6edf3">--continue</text>
72
+ <text x="1064" y="286.5" text-anchor="middle" font-size="11.5" fill="#8b949e">next ready lot</text>
73
+ <polyline points="180,146 180,106" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
74
+ <polyline points="264,75 344,75 344,144" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
75
+ <text x="300" y="68" text-anchor="middle" font-size="11.5" fill="#8b949e">priority</text>
76
+ <polyline points="408,175 422,175" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
77
+ <polyline points="552,175 566,175" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
78
+ <polyline points="696,175 710,175" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
79
+ <polyline points="840,175 854,175" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
80
+ <polyline points="984,175 998,175" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
81
+ <polyline points="1128,175 1142,175" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
82
+ <polyline points="344,204 344,244" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
83
+ <text x="350" y="228" text-anchor="start" font-size="11.5" fill="#8b949e">starts</text>
84
+ <polyline points="408,275 422,275" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
85
+ <polyline points="552,275 566,275" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
86
+ <polyline points="488,246 488,206" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
87
+ <text x="494" y="230" text-anchor="start" font-size="11.5" fill="#8b949e">journal</text>
88
+ <polyline points="488,146 488,106" fill="none" stroke="#8b949e" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arr3)"/>
89
+ <text x="494" y="130" text-anchor="start" font-size="11.5" fill="#8b949e">question</text>
90
+ <polyline points="632,246 632,206" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
91
+ <text x="638" y="230" text-anchor="start" font-size="11.5" fill="#8b949e">wave ends</text>
92
+ <polyline points="776,146 776,106" fill="none" stroke="#8b949e" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arr3)"/>
93
+ <text x="782" y="130" text-anchor="start" font-size="11.5" fill="#8b949e">captures</text>
94
+ <polyline points="920,204 920,244" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
95
+ <text x="926" y="228" text-anchor="start" font-size="11.5" fill="#8b949e">if a page changes</text>
96
+ <polyline points="1064,204 1064,244" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
97
+ <text x="1070" y="228" text-anchor="start" font-size="11.5" fill="#8b949e">yes</text>
98
+ <polyline points="1208,146 1208,106" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
99
+ <polyline points="1064,304 1064,346 344,346 344,306" fill="none" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr3)"/>
100
+ <text x="704" y="340" text-anchor="middle" font-size="11.5" fill="#8b949e">next wave, without asking the human again</text>
101
+ </svg>
@@ -0,0 +1,101 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1290 372" width="1290" height="372" role="img" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif" font-size="13" fill="#1f2328">
2
+ <title>The lead drives the waves</title>
3
+ <desc>Three lanes: the human chooses and decides, the lead starts, relays, re-verifies and delivers, the orchestrate program runs the lots and chains the next wave while the context stays under 60 percent.</desc>
4
+ <defs><marker id="arr3" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#57606a"/></marker></defs>
5
+ <rect width="1290" height="372" fill="#ffffff"/>
6
+ <rect x="0" y="30" width="1290" height="90" fill="#f6f8fa"/>
7
+ <rect x="0" y="230" width="1290" height="90" fill="#f6f8fa"/>
8
+ <text x="12" y="71" text-anchor="start" font-weight="600" font-size="13.5" fill="#1f2328">Human</text>
9
+ <text x="12" y="88" text-anchor="start" font-size="11.5" fill="#57606a">chooses, decides</text>
10
+ <text x="12" y="171" text-anchor="start" font-weight="600" font-size="13.5" fill="#1f2328">Lead</text>
11
+ <text x="12" y="188" text-anchor="start" font-size="11.5" fill="#57606a">short session</text>
12
+ <text x="12" y="271" text-anchor="start" font-weight="600" font-size="13.5" fill="#1f2328">orchestrate</text>
13
+ <text x="12" y="288" text-anchor="start" font-size="11.5" fill="#57606a">fresh sessions</text>
14
+ <text x="200" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#57606a">1 TOUR</text>
15
+ <text x="344" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#57606a">2 WAVE</text>
16
+ <text x="488" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#57606a">3 RELAY</text>
17
+ <text x="632" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#57606a">4 TABLE</text>
18
+ <text x="776" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#57606a">5 RE-CHECK</text>
19
+ <text x="920" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#57606a">6 DELIVERY</text>
20
+ <text x="1064" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#57606a">7 THRESHOLD</text>
21
+ <text x="1208" y="20" text-anchor="middle" font-weight="600" font-size="12" letter-spacing="0.7" fill="#57606a">8 CLOSE</text>
22
+ <rect x="136" y="46" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
23
+ <text x="200" y="71.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Chooses</text>
24
+ <text x="200" y="86.5" text-anchor="middle" font-size="11.5" fill="#57606a">lots + order</text>
25
+ <rect x="424" y="46" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5" stroke-dasharray="6 4"/>
26
+ <text x="488" y="71.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Answers</text>
27
+ <text x="488" y="86.5" text-anchor="middle" font-size="11.5" fill="#57606a">on a question</text>
28
+ <rect x="712" y="46" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5" stroke-dasharray="6 4"/>
29
+ <text x="776" y="71.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">UX verdict</text>
30
+ <text x="776" y="86.5" text-anchor="middle" font-size="11.5" fill="#57606a">or lead (lead.ux)</text>
31
+ <rect x="1144" y="46" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
32
+ <text x="1208" y="71.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Report</text>
33
+ <text x="1208" y="86.5" text-anchor="middle" font-size="11.5" fill="#57606a">1 line / project</text>
34
+ <rect x="136" y="146" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
35
+ <text x="200" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">lead tour</text>
36
+ <text x="200" y="186.5" text-anchor="middle" font-size="11.5" fill="#57606a">3 proposals</text>
37
+ <rect x="280" y="146" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
38
+ <text x="344" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Starts</text>
39
+ <text x="344" y="186.5" text-anchor="middle" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#57606a">--wave</text>
40
+ <rect x="424" y="146" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
41
+ <text x="488" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Relays</text>
42
+ <text x="488" y="186.5" text-anchor="middle" font-size="11.5" fill="#57606a">1 sentence / line</text>
43
+ <rect x="568" y="146" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
44
+ <text x="632" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Triages</text>
45
+ <text x="632" y="186.5" text-anchor="middle" font-size="11.5" fill="#57606a">observable bug → lot</text>
46
+ <rect x="712" y="146" width="128" height="58" rx="6" fill="#ddf4ff" stroke="#0969da" stroke-width="2"/>
47
+ <text x="776" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Re-verifies</text>
48
+ <text x="776" y="186.5" text-anchor="middle" font-size="11.5" fill="#57606a">tests · build · check</text>
49
+ <rect x="856" y="146" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
50
+ <text x="920" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Delivers</text>
51
+ <text x="920" y="186.5" text-anchor="middle" font-size="11.5" fill="#57606a">push + deliver</text>
52
+ <rect x="1000" y="146" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
53
+ <text x="1064" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">&lt; 60 % ?</text>
54
+ <text x="1064" y="186.5" text-anchor="middle" font-size="11.5" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" fill="#57606a">session context</text>
55
+ <rect x="1144" y="146" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
56
+ <text x="1208" y="171.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Close</text>
57
+ <text x="1208" y="186.5" text-anchor="middle" font-size="11.5" fill="#57606a">if ≥ 60 %</text>
58
+ <rect x="280" y="246" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
59
+ <text x="344" y="271.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Lots</text>
60
+ <text x="344" y="286.5" text-anchor="middle" font-size="11.5" fill="#57606a">figure 2, per lot</text>
61
+ <rect x="424" y="246" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
62
+ <text x="488" y="271.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">journal.log</text>
63
+ <text x="488" y="286.5" text-anchor="middle" font-size="11.5" fill="#57606a">1 line / transition</text>
64
+ <rect x="568" y="246" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
65
+ <text x="632" y="271.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">Final table</text>
66
+ <text x="632" y="286.5" text-anchor="middle" font-size="11.5" fill="#57606a">ready · handed back</text>
67
+ <rect x="856" y="246" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
68
+ <text x="920" y="271.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">qa-reviewer</text>
69
+ <text x="920" y="286.5" text-anchor="middle" font-size="11.5" fill="#57606a">read only</text>
70
+ <rect x="1000" y="246" width="128" height="58" rx="6" fill="#ffffff" stroke="#57606a" stroke-width="1.5"/>
71
+ <text x="1064" y="271.5" text-anchor="middle" font-weight="600" font-size="13.5" fill="#1f2328">--continue</text>
72
+ <text x="1064" y="286.5" text-anchor="middle" font-size="11.5" fill="#57606a">next ready lot</text>
73
+ <polyline points="180,146 180,106" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
74
+ <polyline points="264,75 344,75 344,144" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
75
+ <text x="300" y="68" text-anchor="middle" font-size="11.5" fill="#57606a">priority</text>
76
+ <polyline points="408,175 422,175" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
77
+ <polyline points="552,175 566,175" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
78
+ <polyline points="696,175 710,175" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
79
+ <polyline points="840,175 854,175" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
80
+ <polyline points="984,175 998,175" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
81
+ <polyline points="1128,175 1142,175" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
82
+ <polyline points="344,204 344,244" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
83
+ <text x="350" y="228" text-anchor="start" font-size="11.5" fill="#57606a">starts</text>
84
+ <polyline points="408,275 422,275" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
85
+ <polyline points="552,275 566,275" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
86
+ <polyline points="488,246 488,206" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
87
+ <text x="494" y="230" text-anchor="start" font-size="11.5" fill="#57606a">journal</text>
88
+ <polyline points="488,146 488,106" fill="none" stroke="#57606a" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arr3)"/>
89
+ <text x="494" y="130" text-anchor="start" font-size="11.5" fill="#57606a">question</text>
90
+ <polyline points="632,246 632,206" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
91
+ <text x="638" y="230" text-anchor="start" font-size="11.5" fill="#57606a">wave ends</text>
92
+ <polyline points="776,146 776,106" fill="none" stroke="#57606a" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arr3)"/>
93
+ <text x="782" y="130" text-anchor="start" font-size="11.5" fill="#57606a">captures</text>
94
+ <polyline points="920,204 920,244" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
95
+ <text x="926" y="228" text-anchor="start" font-size="11.5" fill="#57606a">if a page changes</text>
96
+ <polyline points="1064,204 1064,244" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
97
+ <text x="1070" y="228" text-anchor="start" font-size="11.5" fill="#57606a">yes</text>
98
+ <polyline points="1208,146 1208,106" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
99
+ <polyline points="1064,304 1064,346 344,346 344,306" fill="none" stroke="#57606a" stroke-width="1.5" marker-end="url(#arr3)"/>
100
+ <text x="704" y="340" text-anchor="middle" font-size="11.5" fill="#57606a">next wave, without asking the human again</text>
101
+ </svg>