task-pipeline-skill 1.91.0 → 1.92.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 (30) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/HOW-IT-WORKS.md +7 -3
  3. package/README.md +30 -4
  4. package/SKILL-CARD.md +1 -1
  5. package/cursor/rules/task-pipeline.mdc +11 -2
  6. package/evals/RESULTS.md +1 -1
  7. package/evals/task-pipeline.evals.json +16 -1
  8. package/package.json +1 -1
  9. package/plugins/task-pipeline/.claude-plugin/plugin.json +2 -2
  10. package/plugins/task-pipeline/commands/task-pipeline.md +23 -8
  11. package/plugins/task-pipeline/hooks/build-gate.sh +6 -0
  12. package/plugins/task-pipeline/hooks/release-gate.sh +15 -0
  13. package/plugins/task-pipeline/skills/project-audit/SKILL.md +2 -1
  14. package/plugins/task-pipeline/skills/task-pipeline/SKILL.md +9 -10
  15. package/plugins/task-pipeline/skills/task-pipeline/graph.schema.json +8 -2
  16. package/plugins/task-pipeline/skills/task-pipeline/pipeline.example.json +1 -1
  17. package/plugins/task-pipeline/skills/task-pipeline/references/artifacts.md +4 -0
  18. package/plugins/task-pipeline/skills/task-pipeline/references/brainstorm.md +6 -3
  19. package/plugins/task-pipeline/skills/task-pipeline/references/build.md +2 -2
  20. package/plugins/task-pipeline/skills/task-pipeline/references/continuity.md +12 -2
  21. package/plugins/task-pipeline/skills/task-pipeline/references/decomposition.md +4 -0
  22. package/plugins/task-pipeline/skills/task-pipeline/references/doctrine-map.md +1 -0
  23. package/plugins/task-pipeline/skills/task-pipeline/references/grill.md +14 -5
  24. package/plugins/task-pipeline/skills/task-pipeline/references/planning.md +8 -0
  25. package/plugins/task-pipeline/skills/task-pipeline/references/portability.md +1 -0
  26. package/plugins/task-pipeline/skills/task-pipeline/references/stages.md +10 -6
  27. package/plugins/task-pipeline/skills/task-pipeline/references/waves.md +225 -0
  28. package/plugins/task-pipeline/skills/task-pipeline/references/work-graph.md +3 -1
  29. package/plugins/task-pipeline/skills/task-pipeline/scripts/graph.py +89 -5
  30. package/plugins/task-pipeline/skills/task-pipeline/templates/run.md +2 -2
package/CHANGELOG.md CHANGED
@@ -1,3 +1,94 @@
1
+ ## v1.92.0 — wave mode: the whole board, closed in its final form; the grill asks one batch
2
+
3
+ The operator's standard approach, decided on 2026-10-11 and codified here: when the request
4
+ is not one change but **the whole board** — every open item plus the new requests — gather
5
+ it all first, close every question the sources can close, ask what remains **once, as one
6
+ batch, at the end of planning**, lay the target state over the current logic on one canvas,
7
+ plan from the post-integration target, and cut the work into waves that each close in one
8
+ pass, with a verification, finishing, fixing and documentation pass after every wave. The
9
+ run takes the next wave without asking, says once when the board is empty, and leaves every
10
+ sign-in, consent, payment, message to a person and outward act to a human. The rule under
11
+ all of it: **build the system in its final form, never through intermediate states.**
12
+
13
+ - **`references/waves.md`, invoked as `/task-pipeline waves`** — a third mode beside `setup`
14
+ and `checkup`, and a program mode of the existing stages, not a parallel cycle: stages 0–4
15
+ once for the whole board, 5–8 per wave, 9–10 to close the release. Wave 0 is the survey
16
+ (with `project-audit` as a read-only sidecar for whole-project scope) and **one
17
+ machine-readable inventory**, every item by id with `id`, `source`, `status`, `area`,
18
+ `node`, `wave`, `release`, verified against the code — stale rows closed with their
19
+ receipt, duplicates clustered — and assigned to nodes and waves by a committed,
20
+ deterministic script. The **canvas** extends the module map: as-is → to-be per module and
21
+ contract, the integration seams, REQ → module → wave, and what the new version deletes.
22
+ No wave builds a shape a later wave undoes; a stub only at a declared contract with its
23
+ removal named. Wave 1 is the walking skeleton, and each wave names its exit criteria
24
+ before it starts. The close pass after each wave certifies, re-measures `audit.md`'s
25
+ exit, runs **one fix round** under the loop guard's caps, documents through the Doc Loop
26
+ scoped to the wave, marks the canvas, re-reads the goal, and re-cuts what is left. A board
27
+ bigger than one release becomes a program, carried on the canvas by node, never dropped.
28
+ The name is not "dynamic": that word is `run.loop.mode: dynamic`'s.
29
+ - **The grill's default changed — a decision by the operator, 2026-10-11.** It was *one
30
+ question per turn, never bundle*. It is now: close from the sources first, then ONE batch
31
+ of the material unknowns, ordered by dependency, each with a recommended answer and what
32
+ each answer changes; one question at a time only where answers depend on each other
33
+ (then depth-first) or when the operator asks for it. The reason is the operator's time: a
34
+ question asked alone costs a human round and a batch costs one, and
35
+ `prioritisation.md` → *5. Where the human goes* already said so for the rest of the run.
36
+ Mirrored in `grill.md`, `stages.md` (stages 0 and 2), `brainstorm.md`, the command, the
37
+ Cursor rule, `HOW-IT-WORKS.md`, the README, `pipeline.example.json`, this repository's
38
+ `pipeline.json`, and eval TRIG-01; TRIG-11 is the wave-mode trigger in Russian.
39
+ - **`continuity.md`: a block or an ambiguity stops its node, not the run.** The node is
40
+ parked `needs-operator` with the question written into it, the run continues on what it
41
+ does not block, and stops only when the whole frontier is blocked. An empty board is said
42
+ once. A goal re-read that moves the queue is a committed `revisions` entry.
43
+ - **`planning.md`: a wave re-cut keeps the static-graph decision** — every move is a
44
+ `revisions` entry plus a canvas edit, written before the next pass begins.
45
+ **`decomposition.md`**: the whole board runs one wave at a time, and a wave may hold several
46
+ bricks. **`build.md`**: "fix wave" is now "fix round", so *wave* means one thing.
47
+ **`project-audit`**: its rows and sidecar are wave mode's wave-0 input.
48
+ - **The work graph carries the wave.** `graph.schema.json` gains `nodes[].wave` (an integer
49
+ from 1) and the `wave` revision verb. `graph.py validate` refuses an edge from a later
50
+ wave's node into an earlier wave's node; `graph.py next --wave N` prints that wave's
51
+ frontier only, exiting `3` when the wave is done; `graph.py wave N-007 --to 3 --why …`
52
+ moves a node, writes the revision, and refuses a move that breaks the order, a `done`
53
+ node, or no reason. `test/graph_test.py` 158 → **166** cases.
54
+ - **The description names the mode** and paid for it: `'waves' / 'волнами' closes the whole
55
+ board`, while the stage list reads *through gated stages: …* instead of *… with explicit
56
+ gates*, `docs study` reads `docs`, and `docs/wiki sync` reads `docs/wiki`. 901 → 909 of
57
+ 1024 chars — inside the umbrella's 60-character reserve below the 970 working limit,
58
+ which a longer description would have broken. The `SKILL.md` body is 4736 tokens
59
+ (cl100k), one under where it was: the new sentence in *How to run* displaced a restated
60
+ kernel clause and the compiler sentence's tail.
61
+
62
+ Guards: 430 → **433**. The three new negatives live in `test/plant_wave.py` (the workflow
63
+ sits near GitHub's 512 000-byte limit) and disarm the order check, the `--wave` filter and
64
+ the re-cut's revision in turn; each was watched turning `test/graph_test.py` red before the
65
+ step was written.
66
+
67
+ ## v1.91.2 — v1.91.1's payload, released with its no-stamp declaration
68
+
69
+ `release.yml` refused the `v1.91.1` tag's own tree: the release carried no run stamp and was
70
+ not named in `## Releases that carry no stamp`. Nothing was published under it. This
71
+ release is the same payload with the declaration in `docs/evidence/retro.md`. What v1.91.1
72
+ ships is described in its section below.
73
+
74
+ ## v1.91.1 — the gates read the current run only, and a broken python3 stops only releases
75
+
76
+ - **The release gate read every run in the ledger.** `.task-pipeline/run.md` is
77
+ append-only, and a project that reuses it holds earlier runs above the current one.
78
+ The gate took the FIRST `stage: … tests … verdict pass` in the whole file, so a
79
+ previous release's pass admitted `git tag` while this run's tests were still PENDING
80
+ (reported 2026-10-11 from a live ledger: the 0.2.0 pass would have released 0.4.0).
81
+ Both gates now read only what follows the last `Run:` header — the line
82
+ `templates/run.md` writes once per run; a ledger with no header is one run.
83
+ - **The build gate had the same leak.** A previous run that had entered dev silenced
84
+ the ask for a new run still in planning. Same scoping, same fixture shape.
85
+ - **A failed python3 no longer blocks every command.** With the disk full, python3
86
+ could not start, and the gate refused `ls` in any project with a run in flight. A
87
+ shell pre-filter now lets a command through when it carries none of
88
+ `tag`/`push`/`release`/`publish`; anything that could release still fails closed.
89
+
90
+ Four fixtures in `test/release_gate_test.py`, each watched failing before its fix.
91
+
1
92
  ## v1.91.0 — the description says «баг», and a lightweight tag is refused before release
2
93
 
3
94
  v1.90.0 added `bug` to the description so "fix this bug" routes here by name. The Russian
package/HOW-IT-WORKS.md CHANGED
@@ -46,7 +46,9 @@ It is written in three parts, and the middle one was learned the hard way:
46
46
  the pipeline entirely.
47
47
 
48
48
  Two modes need no task at all. `checkup` reports what has shipped without anyone
49
- confirming it. `setup` audits the documentation a project already has.
49
+ confirming it. `setup` audits the documentation a project already has. A third,
50
+ `waves`, takes the whole board as the task: one inventory, one canvas of the target
51
+ state, one batch of questions, then waves that each close in one pass.
50
52
 
51
53
  **Honest limit:** this measures the *description's* discriminating power, not the
52
54
  host's selection machinery. Three measured runs scored 7/10, 9/10 and 8/10 — and
@@ -92,8 +94,10 @@ An absent source that is never named looks identical to a source that was read.
92
94
 
93
95
  ### Then the grill, and it is mandatory
94
96
 
95
- One question per turn, each with a recommended answer, exploring the codebase
96
- before asking, until every decision branch is resolved.
97
+ Close what the sources can answer, then ask the material unknowns in one batch,
98
+ ordered by dependency, each with a recommended answer — one at a time only where the
99
+ answers depend on each other or the operator asks — until every decision branch is
100
+ resolved.
97
101
 
98
102
  - **Every answer is validated against the harvested sources.** The operator
99
103
  outranks any document — but only *out loud*, and a document the run proves stale
package/README.md CHANGED
@@ -214,9 +214,11 @@ an external provider.
214
214
 
215
215
  ### The intake grill (stage 0) — mandatory
216
216
 
217
- Before any technical work, task-pipeline interviews you relentlessly — one question
218
- per turn, each with a recommended answer, exploring the codebase before asking —
219
- until every decision branch is resolved and locked into a **task brief**. There is
217
+ Before any technical work, task-pipeline closes every question the codebase and the
218
+ project's records can answer, then asks you the rest in **one batch**, ordered by
219
+ dependency, each with a recommended answer — one at a time only where the answers
220
+ depend on each other or you ask for it — until every decision branch is resolved and
221
+ locked into a **task brief**. There is
220
222
  no "clear enough task" exemption: no stage-1 work starts without a committed,
221
223
  confirmed brief.
222
224
 
@@ -444,6 +446,29 @@ stopped. Each module's spec is a full dossier: architecture, entities and
444
446
  ownership, contracts in and out with their failure behavior, business rules, edge
445
447
  and failure cases, UI/Figma chain, limits, open questions.
446
448
 
449
+ ### Wave mode — the whole board, closed in its final form
450
+
451
+ `/task-pipeline waves` takes every open item a project carries — the board, the
452
+ carry-over ledgers, plan rows, unverified rows, uncovered scenarios, other products'
453
+ asks — plus your new requests, and closes them **in the system's final form rather
454
+ than through intermediate states**
455
+ ([`waves.md`](plugins/task-pipeline/skills/task-pipeline/references/waves.md)). It is
456
+ a program mode of the same stages: 0–4 once for the whole board, 5–8 once per wave,
457
+ 9–10 to close.
458
+
459
+ Wave 0 is the survey: one machine-readable inventory of every item by id, each verified
460
+ against the code — stale rows closed with their receipt, duplicates clustered. Questions
461
+ are closed from the sources first; what is genuinely yours (access, consent, money,
462
+ direction, an outward act) reaches you **once, as one batch, at the end of planning**,
463
+ each with a recommendation. A committed **canvas** lays the target state over the
464
+ current logic — as-is → to-be per module and contract, the integration seams, REQ →
465
+ module → wave, and what the new version deletes. The work is then cut into waves that
466
+ each close in one pass, the walking skeleton first, each with its exit criteria named
467
+ before it starts, and no wave builds a shape a later wave undoes. After every wave a
468
+ close pass verifies, runs one fix round, updates the docs, re-measures the inventory
469
+ and re-cuts what is left. The run takes the next wave without asking; a board too big
470
+ for one release becomes a program, carried on the canvas, never dropped.
471
+
447
472
  ### Loop guard — churn is detected, not endured
448
473
 
449
474
  Any repeating pass can start undoing the previous one: two shapes alternating, the
@@ -587,7 +612,7 @@ must say so.
587
612
  ### Held to Anthropic's own Skill authoring guidance
588
613
 
589
614
  Audited against the four Agent Skills pages. Most of it already held — `name`
590
- 13/64 chars, `description` 901/1024 chars, `SKILL.md` 278/500 lines, all 39 references
615
+ 13/64 chars, `description` 909/1024 chars, `SKILL.md` 277/500 lines, all 40 references
591
616
  linked **directly** from `SKILL.md`, and the bundle far under the 30 MB ceiling. What
592
617
  did not, now does:
593
618
 
@@ -942,6 +967,7 @@ recommendation, so you arm the whole run in one exchange. Detail:
942
967
  | [`references/model-tiering.md`](plugins/task-pipeline/skills/task-pipeline/references/model-tiering.md) | model policy, the `/model` reminder, overrides |
943
968
  | [`templates/`](plugins/task-pipeline/skills/task-pipeline/templates/README.md) | brief, carry-over ledger, `CONTEXT.md` and ADR skeletons, the doc map, both registers, the retro and its archive, the seeded `docgate.sh` and `hygiene.sh`, a worked hook |
944
969
  | [`references/adoption.md`](plugins/task-pipeline/skills/task-pipeline/references/adoption.md) | the first run in a project: greenfield seeding, and the brownfield walkthrough |
970
+ | [`references/waves.md`](plugins/task-pipeline/skills/task-pipeline/references/waves.md) | wave mode: the whole board as one inventory, one canvas of the target state, one question batch, then waves that each close in one pass |
945
971
  | [`references/setup.md`](plugins/task-pipeline/skills/task-pipeline/references/setup.md) | the entry audit: seven passes over the docs a project already has, offered once, output as a fix plan |
946
972
  | [`references/portability.md`](plugins/task-pipeline/skills/task-pipeline/references/portability.md) | the manifest of workflow decisions and their homes in the bundle, and the boundary against a project's own answers |
947
973
  | [`references/learned.md`](plugins/task-pipeline/skills/task-pipeline/references/learned.md) | rules earned by failure on a real multi-repository build, each with its incident, its check and its exit criterion — the table is the count, and it grows |
package/SKILL-CARD.md CHANGED
@@ -12,7 +12,7 @@ harmless.
12
12
  |---|---|
13
13
  | **Purpose** | Runs a substantial task through ten gated delivery stages — intake grill, docs study, brainstorm, spec, plan, subagent build, tests, lint/deploy, post-deploy, docs+registers, acceptance — refusing to advance until each gate passes |
14
14
  | **Owner** | ssheleg ([github.com/ssheleg/task-pipeline](https://github.com/ssheleg/task-pipeline)) |
15
- | **Version** | 1.91.0 |
15
+ | **Version** | 1.92.0 |
16
16
  | **Surface** | Claude Code (filesystem skill + plugin) and the vercel `skills` CLI. **Not** uploaded to the Skills API; custom Skills do not sync across surfaces |
17
17
  | **Dependencies** | None required. Optional: `context7` (MCP), `figma` (MCP), super-ux, agent-sync, graphify, obsidian-wiki, and **one of two browser channels** — `playwright` (CLI or MCP) or `chrome-devtools` (MCP); either satisfies the browser step and neither is required. Every stage's doctrine ships in-repo; the one conditional requirement is super-ux for the stage-3 UX track on a user-facing task |
18
18
  | **Evaluation status** | Suite authored, 5 categories. One recorded run, **self-observed by the author**; **zero blind runs on zero of three models** — the split, and the numbers, live in [`evals/RESULTS.md`](evals/RESULTS.md) and are computed by `evals/run.py` |
@@ -108,7 +108,9 @@ built, recommend it once and continue: `uv tool install graphifyy` →
108
108
 
109
109
  **Phase 2 — grill the user against that harvest**, then run the rest without
110
110
  mid-flight questions:
111
- 1. One question per turn — never bundle.
111
+ 1. Close what the sources answer; ask the rest in ONE batch, ordered by
112
+ dependency. One at a time only when answers depend on each other or the user
113
+ asks.
112
114
  2. Give a recommended answer with every question (+ one-line rationale).
113
115
  3. Explore the codebase before asking — if a search/read answers it, do that.
114
116
  4. **Validate every answer against the harvest.** When what the user says
@@ -211,7 +213,14 @@ not authorize an outward, irreversible action — stage 7 stops and asks.
211
213
  first**, then topologically, no cycles (a cycle means the cut is wrong). Every REQ
212
214
  maps to exactly one module. Then stages 3→10 run **per module**, one brick at a
213
215
  time; stages 0–2 run once, and the map's status column is the resume point. Single
214
- module → record "single module: <name>" and move on.
216
+ module → record "single module: <name>" and move on. **If the request is the whole
217
+ board** (wave mode, `waves`) — every open item plus the new requests — stages 0–4
218
+ run once over one machine-readable inventory verified against the code and a
219
+ committed canvas of the target state; the work is cut into waves that each close
220
+ in one pass (wave 1 the walking skeleton, exit criteria named first); stages 5–8
221
+ run per wave with a close pass after each — verify, one fix round, docs,
222
+ re-measure, re-cut — and 9–10 close the release. No wave builds a shape a later
223
+ wave undoes.
215
224
  3. **Spec** (manual) — write and commit the design; lock every shared contract
216
225
  (types, schemas, signatures, file layout) plus a **Global Constraints** block of
217
226
  exact values every later task inherits. Self-review it for placeholders,
package/evals/RESULTS.md CHANGED
@@ -40,7 +40,7 @@ or it belongs in a check.
40
40
 
41
41
  | Metric | Value | Computed by | As of |
42
42
  |---|---|---|---|
43
- | Evals authored | 46 (three suites: task-pipeline 28, evidence-docs 9, project-audit 9) | `python3 evals/run.py` → `suite: N evals` | 2026-08-31 |
43
+ | Evals authored | 47 (three suites: task-pipeline 29, evidence-docs 9, project-audit 9) | `python3 evals/run.py` → `suite: N evals` | 2026-10-11 |
44
44
  | Categories covered | 5 of 5, per suite | the suite's own `category` fields | 2026-08-31 |
45
45
  | Dated runs recorded | **4** | `python3 evals/run.py` → `recorded runs: N` | 2026-08-31 |
46
46
  | …of those, **blind** | **3** | run headings not marked `self-observed` | 2026-08-31 |
@@ -17,7 +17,7 @@
17
17
  "expected_behavior": [
18
18
  "Invokes the task-pipeline skill rather than starting to design or code inline",
19
19
  "Runs the stage-0 knowledge harvest BEFORE the first interview question, and writes a source ledger",
20
- "Asks interview questions one at a time, each with a recommended answer",
20
+ "Asks the material interview questions in one batch ordered by dependency, each with a recommended answer, after closing what the sources answer",
21
21
  "Writes no implementation code before a brief is committed and confirmed"
22
22
  ],
23
23
  "why": "The explicit invocation phrase is the primary trigger; the failure it guards is jumping to code."
@@ -361,6 +361,21 @@
361
361
  ],
362
362
  "why": "Measured before the change: answered `none` because no description offered a phrase for reviewing a PR, while review.md owns exactly that contract."
363
363
  },
364
+ {
365
+ "id": "TRIG-11",
366
+ "category": "should_trigger",
367
+ "skills": [
368
+ "task-pipeline"
369
+ ],
370
+ "query": "собери все задачи с доски и новые запросы и закрой их волнами",
371
+ "expected_behavior": [
372
+ "Recognises this as wave mode and runs `/task-pipeline waves`, not one change at a time",
373
+ "Builds one machine-readable inventory of every open item by id, each verified against the code, before any design",
374
+ "Closes questions from the sources first and asks the operator the rest once, as one batch with a recommendation each, at the end of planning",
375
+ "Cuts the work into waves that each close in one pass, wave 1 the walking skeleton, each with its exit criteria named before it starts"
376
+ ],
377
+ "why": "Wave mode is the operator's standard approach for a whole board (2026-10-11). A model that cannot reach it from the natural Russian request runs the board item by item and builds the intermediate states the mode exists to refuse."
378
+ },
364
379
  {
365
380
  "id": "NOTRIG-06",
366
381
  "category": "should_not_trigger",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-pipeline-skill",
3
- "version": "1.91.0",
3
+ "version": "1.92.0",
4
4
  "description": "Full-cycle delivery pipeline for coding agents: a mandatory built-in intake grill, then 10 gated stages (docs, brainstorm+decompose, spec, plan, build, tests, lint/deploy, post-deploy, docs/wiki, acceptance). Every stage's doctrine ships inside the skill — no companion plugin required. This package is the installer CLI.",
5
5
  "bin": {
6
6
  "task-pipeline": "bin/task-pipeline.js"
@@ -2,8 +2,8 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "task-pipeline",
4
4
  "displayName": "Task Pipeline",
5
- "description": "Runs a substantial task through a mandatory built-in intake grill, then 10 gated stages (docs, brainstorm+decompose, spec, plan, subagent build, tests, lint/deploy, post-deploy, docs/wiki, acceptance). Every stage's doctrine is built into the skill — no companion plugin required — with typed auto/judgment/manual gates, a frozen requirement spine that closes with evidence, a work board and a verification ledger that outlive a run, an exposure line naming what shipped unconfirmed, a progress rail computed from the project's own config, a loop guard whose review ceiling measures rather than stops, and stage-3 tracks for what a product does, how it sounds and how it looks. Two modes need no task: `checkup` (what is unverified) and `setup` (audit existing docs). Retro insights can publish upstream as issues, opt-in and redacted.",
6
- "version": "1.91.0",
5
+ "description": "Runs a substantial task through a mandatory built-in intake grill, then 10 gated stages (docs, brainstorm+decompose, spec, plan, subagent build, tests, lint/deploy, post-deploy, docs/wiki, acceptance). Every stage's doctrine is built into the skill — no companion plugin required — with typed auto/judgment/manual gates, a frozen requirement spine that closes with evidence, a work board and a verification ledger that outlive a run, an exposure line naming what shipped unconfirmed, a progress rail computed from the project's own config, a loop guard whose review ceiling measures rather than stops, and stage-3 tracks for what a product does, how it sounds and how it looks. Two modes need no task: `checkup` (what is unverified) and `setup` (audit existing docs); a third, `waves`, takes the whole board and closes it in waves that each finish in one pass. Retro insights can publish upstream as issues, opt-in and redacted.",
6
+ "version": "1.92.0",
7
7
  "author": {
8
8
  "name": "ssheleg",
9
9
  "url": "https://x.com/sshlg93"
@@ -1,6 +1,6 @@
1
1
  ---
2
- description: "Run a task through task-pipeline's gated stages (intake grill → docs → brainstorm → spec → plan → build → tests → deploy → post-deploy → docs/wiki → acceptance). Also: `setup` — audit the docs you already have; `checkup` — what has shipped unverified, with no task running."
3
- argument-hint: "<one-line task> | setup | checkup"
2
+ description: "Run a task through task-pipeline's gated stages (intake grill → docs → brainstorm → spec → plan → build → tests → deploy → post-deploy → docs/wiki → acceptance). Also: `waves` — close the whole board in waves; `setup` — audit the docs you already have; `checkup` — what has shipped unverified, with no task running."
3
+ argument-hint: "<one-line task> | waves | setup | checkup"
4
4
  ---
5
5
  Run the task below through `task-pipeline`'s gated stages. **Every stage's doctrine
6
6
  ships inside the skill** — no companion plugin is required for any of it. The
@@ -8,9 +8,10 @@ reference files are indexed in `SKILL.md`; this page is the run order, not the i
8
8
 
9
9
  Task: $ARGUMENTS
10
10
 
11
- > **Two other modes.** `setup` audits the documentation this project already has;
12
- > `checkup` reports what has shipped without a person confirming it, with no task in
13
- > flight. Both are at the bottom of this page.
11
+ > **Three other modes.** `waves` takes the whole board instead of one task;
12
+ > `setup` audits the documentation this project already has; `checkup` reports what
13
+ > has shipped without a person confirming it, with no task in flight. All three are at
14
+ > the bottom of this page.
14
15
 
15
16
  **Idempotent entry — inspect state first, never restart blindly.** If a pipeline
16
17
  TaskList from a previous run exists for this task, **resume** from the first incomplete
@@ -59,9 +60,10 @@ Pull what the project already knows about *this task*:
59
60
 
60
61
  Write the **source ledger** into the brief: a row per source, or an explicit *none found*.
61
62
 
62
- **Then the grill, and it is mandatory** (`references/grill.md`). One question per turn,
63
- each with a recommended answer, exploring the codebase before asking, until every
64
- decision branch is resolved.
63
+ **Then the grill, and it is mandatory** (`references/grill.md`). Close what the sources
64
+ can, then ask the material unknowns in ONE batch, ordered by dependency, each with a
65
+ recommended answer — one at a time only where answers depend on each other or the
66
+ operator asks — until every decision branch is resolved.
65
67
 
66
68
  - **Validate every answer against the harvested sources.** The operator outranks any
67
69
  document — but only out loud, and a doc the run proves stale is logged for stage 9.
@@ -188,6 +190,19 @@ Where you ask it to file what it found, it appends board rows whose `Source` nam
188
190
  checkup and its date — printing what it would add first, never silently.
189
191
  Doctrine: `references/exposure.md`.
190
192
 
193
+ ## `/task-pipeline waves`
194
+
195
+ **The whole board, closed in its final form.** Every open item — board, ledgers, plan
196
+ rows, unverified rows, uncovered scenarios, other products' asks — plus the new requests,
197
+ in one machine-readable inventory verified against the code. Questions are closed from
198
+ the sources; what remains goes to the operator once, as one batch, at the end of
199
+ planning. A committed canvas lays the target state over the current logic, and the work
200
+ is cut into waves that each close in one pass. Stages 0–4 run once, 5–8 per wave with a
201
+ close pass after each (verify, one fix round, docs, re-measure, re-cut), 9–10 to close.
202
+ It takes the next wave without asking, says once when the board is empty, and leaves
203
+ every sign-in, consent, payment and outward act to a person.
204
+ Doctrine: `references/waves.md`.
205
+
191
206
  ## `/task-pipeline setup`
192
207
 
193
208
  **The entry audit instead of a feature.** Seven passes over the documentation this
@@ -54,6 +54,12 @@ try:
54
54
  except Exception:
55
55
  raise SystemExit(0)
56
56
 
57
+ # Only the CURRENT run speaks: everything after the last `Run:` header. A previous
58
+ # run that entered dev in a reused, append-only ledger silenced this gate (B-151).
59
+ _lines = text.splitlines()
60
+ _starts = [i for i, l in enumerate(_lines) if l.strip().startswith("Run:")]
61
+ text = "\n".join(_lines[_starts[-1]:] if _starts else _lines)
62
+
57
63
  stage_lines = [l.strip() for l in text.splitlines() if l.strip().startswith("stage:")]
58
64
  if not stage_lines:
59
65
  raise SystemExit(0)
@@ -155,6 +155,14 @@ except Exception:
155
155
  # gate cannot tell. Fail closed, and say which file to look at.
156
156
  print("block\t%s\tthe run ledger could not be read" % act); raise SystemExit(0)
157
157
 
158
+ # B-151: the ledger is append-only, and a project that reuses it holds earlier runs
159
+ # above this one. Only the CURRENT run speaks — everything after the last `Run:`
160
+ # header (templates/run.md writes one per run); a ledger with no header is one run.
161
+ # A previous release's `verdict pass` admitted a tag whose own tests were PENDING.
162
+ _lines = text.splitlines()
163
+ _starts = [i for i, l in enumerate(_lines) if l.strip().startswith("Run:")]
164
+ text = "\n".join(_lines[_starts[-1]:] if _starts else _lines)
165
+
158
166
  stage_lines = [l.strip() for l in text.splitlines() if l.strip().startswith("stage:")]
159
167
 
160
168
 
@@ -243,6 +251,13 @@ status=$?
243
251
  # A gate that cannot reach its own decision must not wave the release through.
244
252
  if [ $status -ne 0 ] || [ -z "$verdict" ]; then
245
253
  [ -f "$ledger" ] || exit 0
254
+ # B-152: without python3 the command cannot be classified, and refusing EVERY
255
+ # Bash call took the whole session down on a full disk. A word-level pre-filter
256
+ # keeps fail-closed exactly where it matters: anything that could tag, push,
257
+ # release or publish is still refused; `ls` is not.
258
+ if ! printf '%s' "$input" | grep -Eq '(^|[^[:alnum:]_])(tag|tags|push|release|publish)([^[:alnum:]_]|$)'; then
259
+ exit 0
260
+ fi
246
261
  echo "task-pipeline: the stage-7 gate could not run (python3 missing or failed), and this project has a run in flight ($ledger). Refusing an outward act rather than failing open." >&2
247
262
  exit 2
248
263
  fi
@@ -121,7 +121,8 @@ default is `Sev × Blast + age_bonus` ([`references/portable-method.md`](referen
121
121
  the local priority contract) — and the operator accepts them. Effort never ranks inside an
122
122
  audit: what a fix costs is the fixer's decision, not the finder's
123
123
  ([`references/portable-method.md`](references/portable-method.md)). An audit
124
- that edits while it reads cannot be re-run to check itself.
124
+ that edits while it reads cannot be re-run to check itself. In `task-pipeline`'s wave
125
+ mode these rows and the JSON sidecar are wave 0's input: they join its inventory by id.
125
126
 
126
127
  ## A finding keeps its axes apart, or it is a guess wearing a verdict
127
128
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: task-pipeline
3
- description: "Use when work changes the repository — feature, fix, bug, refactor, migration, integration, rewrite, adoption or hardening; фича, фикс, баг, рефактор, миграция, интеграция, доработать, починить, внедрить, перевести — or when the output is a finding that lands in it: audit/аудит, bug hunt/проверь ошибки, production check/проверь прод, PR review/ревью PR — or on 'run this through the pipeline' / 'прогони по конвейеру', 'the full cycle' / 'полный цикл', /task-pipeline. Runs a substantial task through an intake grill, docs study, brainstorm, spec, plan, build, tests, deploy, post-deploy, docs/wiki sync and acceptance with explicit gates. 'checkup' / 'чекап' reports unconfirmed releases; 'setup' audits existing docs. Not for: answering a question, explaining code, a typo or a one-line edit, a mechanical rename, reconnaissance that lands nothing — say 'no pipeline' / 'без пайплайна' to opt out."
3
+ description: "Use when work changes the repository — feature, fix, bug, refactor, migration, integration, rewrite, adoption or hardening; фича, фикс, баг, рефактор, миграция, интеграция, доработать, починить, внедрить, перевести — or when the output is a finding that lands in it: audit/аудит, bug hunt/проверь ошибки, production check/проверь прод, PR review/ревью PR — or on 'run this through the pipeline' / 'прогони по конвейеру', 'the full cycle' / 'полный цикл', /task-pipeline. Runs a substantial task through gated stages: intake grill, docs, brainstorm, spec, plan, build, tests, deploy, post-deploy, docs/wiki, acceptance. 'waves' / 'волнами' closes the whole board; 'checkup' / 'чекап' lists unconfirmed releases; 'setup' audits docs. Not for: answering a question, explaining code, a typo, a one-line edit, a mechanical rename, reconnaissance that lands nothing — say 'no pipeline' / 'без пайплайна' to opt out."
4
4
  license: MIT
5
5
  compatibility: "Doctrine runs on any agent. The bundled scripts need python3; the run needs git. Missing either degrades, never blocks — the graph verbs and seeded gates go unused, and the run says so."
6
6
  ---
@@ -17,8 +17,8 @@ GAP CHECK before it is an interview: the brief is built from the request, the
17
17
  files and the harvest first, and a question is asked only for a **material
18
18
  unknown** the sources cannot answer. A complete brief yields ZERO intake
19
19
  questions; what the request or the project already decided is recorded, never
20
- re-negotiated. The interview, where gaps remain, is one-question-at-a-time and
21
- sweeps stages 1→10 for anything that would stop the run later — then locks the
20
+ re-negotiated. The interview, where gaps remain, is ONE batch ordered by dependency
21
+ and sweeps stages 1→10 for anything that would stop the run later — then locks the
22
22
  answers into a brief. Autonomy is bought there or not at all; every MATERIAL
23
23
  question skipped at stage 0 comes back as an interruption at stage 6.
24
24
 
@@ -176,15 +176,16 @@ Three things the grill does beyond clarifying the request, each in full in
176
176
  stages and gates run** (FIX-TP-03.02): the numbers "1→10" name the DEFAULT
177
177
  profile, and a custom profile with three stages runs three — no hidden stage
178
178
  0, 7 or 10 is injected because the default profile has one. What a custom
179
- profile does NOT escape is the KERNEL (03.01): scope, evidence, deps and
180
- resume are owed by every stage of every profile, so dropping stages never
181
- drops the evidence gate. And an unknown MANDATORY capability blocks the
179
+ profile does NOT escape is the KERNEL (03.01): dropping stages never drops
180
+ the evidence gate. And an unknown MANDATORY capability blocks the
182
181
  compile — a profile whose `must_understand` names a capability this runtime
183
182
  does not have is refused, never run with the capability silently absent.
184
183
  **Is the brief a platform rather than a change?** Then stage 2 also cuts it into
185
184
  modules (`references/decomposition.md`) and stages 3→10 run **per module** in
186
185
  build order, one brick at a time — stages 0–2 run once, and the module map's
187
186
  status column is the resume point (`references/stages.md` → *The program loop*).
187
+ **The whole board?** `/task-pipeline waves` (`references/waves.md`): stages
188
+ 0–4 once, 5–8 per wave, 9–10 to close.
188
189
  4. Do **not** advance until the stage **gate** passes (`references/stages.md`).
189
190
  Honor the gate **type**: for `auto`, verify the gate's `check` yourself and
190
191
  stop/return on fail; for `judgment`, record the named judge's ruling as
@@ -272,7 +273,5 @@ substitute for, and a ResultEnvelope whose stale candidate (older revision, or
272
273
  a superseded fence) re-plans instead of landing as current
273
274
  (`scripts/packet.py validate-result`). The compiler's first stage maps an
274
275
  audit report onto parent tasks (`scripts/context_packets.py compile|verify`):
275
- ids derived from finding ids — never positional, so a shuffled report compiles
276
- byte-identically — evidence/limits/priority in their own fields separate from
277
- status, and any row it cannot map blocks the whole compile rather than being
278
- dropped silently.
276
+ ids derived from finding ids, never positional; evidence, limits and priority
277
+ kept apart from status; and any row it cannot map blocks the compile.
@@ -205,6 +205,11 @@
205
205
  ],
206
206
  "description": "The class of the user-facing surface this node builds, copied from the brief's `surface_class` (stage 0). It selects the gate profile, and `graph.py certify` reads it: on `flagship` or `product` the fourth, `visual` tier report is REQUIRED, because none of unit, seam or product opens the contact sheet; on any other class a `visual` report is accepted when given. Absent on a node that builds no user-facing surface. `references/certification.md` → *The fourth reading*."
207
207
  },
208
+ "wave": {
209
+ "type": "integer",
210
+ "minimum": 1,
211
+ "description": "Wave mode only (`references/waves.md`): the pass of stages 5–8 that closes this node, from 1, where wave 1 is the walking skeleton. `graph.py validate` refuses an edge from a later wave's node into an earlier wave's node, because an earlier wave waiting on a later one cannot close in its own pass; `graph.py next --wave N` prints that wave's frontier only; `graph.py wave` moves a node and records the re-cut as a revision. Absent outside wave mode."
212
+ },
208
213
  "certification": {
209
214
  "type": "object",
210
215
  "description": "What the tiered certification recorded for this node. Written by `graph.py certify` on every round, pass or fail: a failing round that wrote nothing would erase the only evidence that a node is churning, which is the number the loop-guard ceiling reads.",
@@ -444,9 +449,10 @@
444
449
  "park",
445
450
  "close",
446
451
  "invalidate",
447
- "waive"
452
+ "waive",
453
+ "wave"
448
454
  ],
449
- "description": "Which mutation made this entry. `close` joined the two on 2026-08-17 with T-5 — and it was caught by a probe rather than by the fixture asserting *the graph after a close still validates*, because `violations()` never reaches an enum. The same disagreement B-084 records, one field over. `invalidate` joined with PF-02.02: a REQ/interface/brief change supersedes a node and resets its downstream proofs."
455
+ "description": "Which mutation made this entry. `close` joined the two on 2026-08-17 with T-5 — and it was caught by a probe rather than by the fixture asserting *the graph after a close still validates*, because `violations()` never reaches an enum. The same disagreement B-084 records, one field over. `invalidate` joined with PF-02.02: a REQ/interface/brief change supersedes a node and resets its downstream proofs. `wave` joined with wave mode: the re-cut after every wave moves nodes between waves, and a move nobody recorded is the quiet re-plan `references/planning.md` refuses."
450
456
  },
451
457
  "node": {
452
458
  "type": "string",
@@ -18,7 +18,7 @@
18
18
  ],
19
19
  "gate": {
20
20
  "type": "manual",
21
- "check": "MANDATORY stage — never skipped (only sanctioned bypass: the entry-from-super-ux short-circuit). PHASE 1, before the first question: harvest the knowledge sources (references/knowledge-sources.md) — code, THE CODE GRAPH when one is built (references/knowledge-graph.md: graphify query/affected/god-nodes answer reach, which grep cannot; detect graphify-out/graph.json — recommended, never required), CLAUDE.md/AGENTS.md, CONTEXT.md + docs/adr, docs/ + docs/ux, past pipeline briefs and carry-over ledgers, THE RETRO'S STANDING INSTRUCTIONS (docs/evidence/retro.md — read IN FULL, not queried: they are capped at ten and they BIND this run; stamp each one the moment it fires, since that date is the only evidence behind stage 10's cold-retirement rule — references/retrospective.md), the knowledge wiki when installed (obsidian-wiki — recommended, never required; detect ~/.obsidian-wiki/config), and any other repo or hosted doc system the project names as its docs — queried by this task's own terms, with the SOURCE LEDGER written into the brief (a row per source consulted, or an explicit 'none found'; THE GRAPH'S ROW CARRIES ITS MEASURED LAG — commits and days behind HEAD, the signal it was measured with (built_at_commit exact / mtime approximate / unresolvable), and the marker '⚠ not trusted for reach until refreshed' — that exact string, so one marker is greppable across every ledger — on anything but current. A bare build date does NOT satisfy this: it is the graph's own reply about itself, true and self-reported and silent about whether the graph describes the tree this run is about to change — references/knowledge-graph.md -> Measure the lag, and references/gates.md -> False success for the class). PHASE 2, the grill, built into the skill (references/grill.md) — no companion to install. Per its contract: one question at a time, a recommended answer with each, explore the codebase/docs before asking, depth-first, contradictions reconciled; EVERY answer that touches a harvested source is validated against that source — the operator outranks any document, but only out loud, and the losing side is logged for the stage-9 doc update; domain awareness applied (terms challenged against CONTEXT.md, ADRs recorded for hard-to-reverse calls). The autonomy sweep is covered — every stage 1-10 has its blockers pre-resolved (docs sources, branch/tracker policy, test + lint commands, deploy target and authorization, log/health locations, docs+wiki targets, and for UI tasks the design surface: Figma on or text-only, is the Figma MCP connected, and if it is not — ship text-only or stop and connect it, since the UX chain degrades on its own and never blocks; plus, with Figma on, the DESIGN DESTINATION — which team/org by name and which file per surface, App / Web / ASO (the recorded one, a URL the operator gives, or creation in that named team with the creation explicitly authorized), written into the project's canonical record before the first frame, and never created while a recorded file resolves — an unreachable recorded file means stop and ask, never make a replacement) or is explicitly marked 'stop and ask here'. UI verdict recorded (arms super-ux), and for a user-facing task its SURFACE CLASS — surface_class: flagship | product | internal | ad — which selects the visual gate profile for stages 3, 6 and 10 (references/stages.md -> The surface class); model decision recorded. All of it locked into a committed task brief the operator confirms before stage 1. The REQ table is written — one row per independently verifiable deliverable, each naming how it is verified — and frozen: adding later is free, removing or narrowing needs the operator's explicit agreement. The carry-over ledger is seeded. PHASE 1b, THE DOCUMENTATION INVENTORY (references/documentation.md): four questions answered into docs/DOCMAP.md before the interview — where settled things live (the DECISION HOME, and there is exactly one per project: an existing docs/adr/ IS the register and is recorded as such, never duplicated), what each fact's single home is, what a change of type X obliges (THE PROPAGATION MATRIX, non-empty, every row naming the check that enforces it or the word 'review' with a one-line reason), and what proves it (the gate command). A project with no answers gets them seeded — registers, matrix and scripts/check-docs.sh from the skill's templates — and the seeding is recorded as the register's first entry; the seeded gate must exit 0 on its own seeds, because a project that starts red teaches everyone on day one that the gate is noise. The regime is recorded. PHASE 1c, RECONCILE: git says how it should be, the run record says how it turned out — read both for the area about to be touched and resolve every divergence (the document is stale, the record is wrong, or they genuinely disagree and that is a decision), because starting on an unresolved divergence means building against a system that does not exist. The retro's in-force sections are read IN FULL and its archive is QUERIED by the task's nouns."
21
+ "check": "MANDATORY stage — never skipped (only sanctioned bypass: the entry-from-super-ux short-circuit). PHASE 1, before the first question: harvest the knowledge sources (references/knowledge-sources.md) — code, THE CODE GRAPH when one is built (references/knowledge-graph.md: graphify query/affected/god-nodes answer reach, which grep cannot; detect graphify-out/graph.json — recommended, never required), CLAUDE.md/AGENTS.md, CONTEXT.md + docs/adr, docs/ + docs/ux, past pipeline briefs and carry-over ledgers, THE RETRO'S STANDING INSTRUCTIONS (docs/evidence/retro.md — read IN FULL, not queried: they are capped at ten and they BIND this run; stamp each one the moment it fires, since that date is the only evidence behind stage 10's cold-retirement rule — references/retrospective.md), the knowledge wiki when installed (obsidian-wiki — recommended, never required; detect ~/.obsidian-wiki/config), and any other repo or hosted doc system the project names as its docs — queried by this task's own terms, with the SOURCE LEDGER written into the brief (a row per source consulted, or an explicit 'none found'; THE GRAPH'S ROW CARRIES ITS MEASURED LAG — commits and days behind HEAD, the signal it was measured with (built_at_commit exact / mtime approximate / unresolvable), and the marker '⚠ not trusted for reach until refreshed' — that exact string, so one marker is greppable across every ledger — on anything but current. A bare build date does NOT satisfy this: it is the graph's own reply about itself, true and self-reported and silent about whether the graph describes the tree this run is about to change — references/knowledge-graph.md -> Measure the lag, and references/gates.md -> False success for the class). PHASE 2, the grill, built into the skill (references/grill.md) — no companion to install. Per its contract: close what the code/docs answer first, then ONE batch of the material unknowns ordered by dependency, a recommended answer with each (one at a time only where answers depend on each other or the operator asks), contradictions reconciled; EVERY answer that touches a harvested source is validated against that source — the operator outranks any document, but only out loud, and the losing side is logged for the stage-9 doc update; domain awareness applied (terms challenged against CONTEXT.md, ADRs recorded for hard-to-reverse calls). The autonomy sweep is covered — every stage 1-10 has its blockers pre-resolved (docs sources, branch/tracker policy, test + lint commands, deploy target and authorization, log/health locations, docs+wiki targets, and for UI tasks the design surface: Figma on or text-only, is the Figma MCP connected, and if it is not — ship text-only or stop and connect it, since the UX chain degrades on its own and never blocks; plus, with Figma on, the DESIGN DESTINATION — which team/org by name and which file per surface, App / Web / ASO (the recorded one, a URL the operator gives, or creation in that named team with the creation explicitly authorized), written into the project's canonical record before the first frame, and never created while a recorded file resolves — an unreachable recorded file means stop and ask, never make a replacement) or is explicitly marked 'stop and ask here'. UI verdict recorded (arms super-ux), and for a user-facing task its SURFACE CLASS — surface_class: flagship | product | internal | ad — which selects the visual gate profile for stages 3, 6 and 10 (references/stages.md -> The surface class); model decision recorded. All of it locked into a committed task brief the operator confirms before stage 1. The REQ table is written — one row per independently verifiable deliverable, each naming how it is verified — and frozen: adding later is free, removing or narrowing needs the operator's explicit agreement. The carry-over ledger is seeded. PHASE 1b, THE DOCUMENTATION INVENTORY (references/documentation.md): four questions answered into docs/DOCMAP.md before the interview — where settled things live (the DECISION HOME, and there is exactly one per project: an existing docs/adr/ IS the register and is recorded as such, never duplicated), what each fact's single home is, what a change of type X obliges (THE PROPAGATION MATRIX, non-empty, every row naming the check that enforces it or the word 'review' with a one-line reason), and what proves it (the gate command). A project with no answers gets them seeded — registers, matrix and scripts/check-docs.sh from the skill's templates — and the seeding is recorded as the register's first entry; the seeded gate must exit 0 on its own seeds, because a project that starts red teaches everyone on day one that the gate is noise. The regime is recorded. PHASE 1c, RECONCILE: git says how it should be, the run record says how it turned out — read both for the area about to be touched and resolve every divergence (the document is stale, the record is wrong, or they genuinely disagree and that is a decision), because starting on an unresolved divergence means building against a system that does not exist. The retro's in-force sections are read IN FULL and its archive is QUERIED by the task's nouns."
22
22
  }
23
23
  },
24
24
  {
@@ -33,6 +33,8 @@ docs/
33
33
  YYYY-MM-DD-<topic>-brief.md # stage 0 — locked intake brief (grill output)
34
34
  YYYY-MM-DD-<topic>-carryover.md # stage 0 seeds it; EVERY stage appends; stage 10 reads it
35
35
  YYYY-MM-DD-<topic>-modules.md # stage 2 — module map + build order (platforms only)
36
+ YYYY-MM-DD-<topic>-inventory.json # wave mode, stage 0 — every open item by id, verified
37
+ YYYY-MM-DD-<topic>-canvas.md # wave mode, stages 2–3 — the target state over the current logic
36
38
  YYYY-MM-DD-<topic>-design.md # stage 3 — the spec / module dossier (locks shared contracts)
37
39
  YYYY-MM-DD-<topic>-acceptance.md # stage 10 — REQ coverage table + evidence
38
40
  plans/
@@ -170,6 +172,8 @@ them is a finding, not a tie-break ([`knowledge-sources.md`](knowledge-sources.m
170
172
  | 10 Board resolution | `<artifacts>/backlog.md` — every unresolved ledger row — homed `backlog` or still `open` — arrives with a real id, and the ledger row is updated to name it; priority re-derived ([`backlog.md`](backlog.md)) | the next run's harvest, and every loop iteration |
171
173
  | 10 Retro rotation | `<artifacts>/retro/YYYY-QN.md` — entries older than five stamps, plus every retirement, each with its commit | queried by a later run's harvest |
172
174
  | 2 Decompose | `specs/<topic>-modules.md` — module map, build order, contracts, per-module status (platforms only) | stages 3–10, every module's run |
175
+ | 0 Wave mode | `specs/<topic>-inventory.json` — every open item: id, source, status, area, node, wave, release ([`waves.md`](waves.md)) | stages 2–10, every wave's re-measure |
176
+ | 2–3 Wave mode | `specs/<topic>-canvas.md` — as-is → to-be per module and contract, the seams, REQ → module → wave, what is deleted | every wave, and each re-cut |
173
177
  | 3 Spec | `specs/<topic>-design.md` — module dossier for a decomposed platform (+ links `docs/ux/*` for UI) | stage 4 |
174
178
  | 4 Plan | `plans/<topic>.md` | stage 5 |
175
179
  | 3 UX track | `docs/ux/{foundation,flows,screens,scenarios}.md` | stages 4–9 + `/ux-lint` |
@@ -56,9 +56,12 @@ not a question to re-open from scratch.
56
56
  the platform's shape — the pieces, how they relate, what order they land in —
57
57
  not the details of one corner; those belong to each module's own stage-3
58
58
  dossier. Don't refine something that needs splitting first.
59
- 3. **Questions one at a time.** Never bundle. Multiple choice where it fits, open
60
- where it doesn't. Purpose, constraints, success criteria — anything the brief
61
- left at design level.
59
+ 3. **Questions in one batch, after the sources.** What the brief, the code and the
60
+ recorded decisions cannot answer goes in one message, ordered by dependency, each
61
+ with a recommendation; one at a time only where an answer reshapes the next
62
+ question or the operator asks ([`grill.md`](grill.md)). Multiple choice where it
63
+ fits, open where it doesn't. Purpose, constraints, success criteria — anything the
64
+ brief left at design level.
62
65
  4. **Propose 2–3 approaches with trade-offs**, lead with your recommendation and
63
66
  the reason for it. **YAGNI ruthlessly** — strip anything the task doesn't need
64
67
  from every option before presenting.
@@ -610,9 +610,9 @@ merge.
610
610
 
611
611
  If it returns findings, dispatch **ONE** fix subagent with the complete list — not
612
612
  one fixer per finding; per-finding fixers each rebuild context and re-run suites.
613
- Then exactly **one** scoped re-review of the fix wave. Adjudicate residuals as in
613
+ Then exactly **one** scoped re-review of the fix round. Adjudicate residuals as in
614
614
  the breaker: park with rulings, or stop on load-bearing ones. There is no second
615
- fix wave.
615
+ fix round.
616
616
 
617
617
  ## 6. Integrate, then finish
618
618
 
@@ -47,13 +47,21 @@ This spans the whole run, not one stage:
47
47
 
48
48
  - the plan's tasks at stage 5;
49
49
  - the boundary between stages whose gate is `auto`;
50
- - the per-module program loop when the brief was a platform;
50
+ - the per-module program loop when the brief was a platform, and the waves when
51
+ the request was the whole board ([`waves.md`](waves.md));
51
52
  - the acceptance → retro tail, which is where runs most often stall one step from
52
53
  finished.
53
54
 
54
55
  It stops for exactly four things: a `manual` gate, a block it cannot resolve, a
55
56
  genuine ambiguity the brief does not answer, and completion.
56
57
 
58
+ **A block or an ambiguity stops its node, not the run.** The node is parked with the
59
+ question as its reason, marked `needs-operator` and answerable without context
60
+ ([`prioritisation.md`](prioritisation.md) → *5. Where the human goes*), and the run
61
+ continues on every node it does not block. The run itself stops only when the whole
62
+ frontier is blocked — `graph.py next` exits `4` — and the questions are then asked
63
+ together. **When the board is empty, say so once**, and do not invent work to fill it.
64
+
57
65
  **This extends [`build.md`](build.md); it does not replace it.** That file's
58
66
  *Continuous execution* rule governs the inside of a stage-5 execution — do not
59
67
  check in between the tasks of a plan you were told to execute. It is unconditional
@@ -122,7 +130,9 @@ So at the bottom of an iteration, beside the re-derived `prio`:
122
130
  2. state whether the next item still serves it;
123
131
  3. if it does not, **re-order or re-scope the queue and say what moved and why.** A row
124
132
  that stops serving the goal leaves for the board with its reason
125
- ([`backlog.md`](backlog.md)), it does not get worked because it was next.
133
+ ([`backlog.md`](backlog.md)), it does not get worked because it was next. On a work
134
+ graph the move is a committed `revisions` entry — `park`, `add`, or `wave` between
135
+ waves — never an untracked edit.
126
136
 
127
137
  A queue re-derived only by `age` and `sev` is a queue that is honest about priority and
128
138
  silent about purpose. Both numbers can be right while the run is finishing something the
@@ -137,6 +137,10 @@ Rules for the loop:
137
137
  lands, or build several and deploy once. Record it; don't decide it per module.
138
138
  - **Update the map's status column as each module closes**, in the same commit as
139
139
  that module's acceptance. The map is the resume point after a lost context.
140
+ - **The whole board is waves, not bricks** ([`waves.md`](waves.md)). When the request
141
+ is every open item rather than one platform, stages 0–2 still run once, the map
142
+ becomes the canvas, and the loop runs **one wave at a time**. A wave may hold several
143
+ bricks or nodes, provided they close together in one pass.
140
144
  - **Loop discipline:** a module re-entering the same stage a third time trips the
141
145
  loop guard ([`loop-guard.md`](loop-guard.md)) — stop, name the oscillation, and
142
146
  fix the layer that owns it instead of iterating.
@@ -27,6 +27,7 @@ UX track on a user-facing task.
27
27
  | 0 Intake grill | `references/grill.md` |
28
28
  | 2 Brainstorm | `references/brainstorm.md` |
29
29
  | 2 Decompose (platforms only) | `references/decomposition.md` |
30
+ | 0–10 · **wave mode** — the whole board: 0–4 once, 5–8 per wave, 9–10 to close | `references/waves.md` |
30
31
  | 3 Spec | `references/spec.md` |
31
32
  | 4 Plan | `references/planning.md` |
32
33
  | the queue the loop walks | `references/work-graph.md` |
@@ -63,16 +63,25 @@ not depend on it.
63
63
 
64
64
  Where material unknowns remain, interview the operator about THOSE until you
65
65
  reach a **shared understanding**. Walk down each unresolved branch of the
66
- decision tree, resolving dependencies between decisions one by one.
67
-
68
- 1. **One question per turn.** Never bundle. Wait for the answer before the next.
66
+ decision tree, resolving dependencies between decisions in order.
67
+
68
+ 1. **Close from the sources first, then ask ONE batch.** Every material unknown
69
+ the harvest, the code and the recorded decisions leave open goes into one
70
+ message, ordered by dependency, each with a recommended answer and what each
71
+ answer changes. **One question at a time only when the answers depend on each
72
+ other** — the next question cannot be written until this one is answered, and
73
+ then you go depth-first — **or when the operator asks for it.** Decided by the
74
+ operator on 2026-10-11: a question asked alone costs a human round, and a batch
75
+ costs one. [`prioritisation.md`](prioritisation.md) → *5. Where the human goes*
76
+ says the same about the rest of the run.
69
77
  2. **Recommend an answer with every question** (+ a one-line rationale). "What do
70
78
  you think?" is lazy — you have the codebase in front of you, they don't.
71
79
  3. **If the codebase can answer it, go read the codebase.** Spending the
72
80
  operator's turn on something `grep`/`Read`/context7 would have told you is the
73
81
  most common way to waste a grill.
74
- 4. **Depth-first.** Finish a branch before opening another; ask prerequisite
75
- decisions first, so later answers don't invalidate earlier ones.
82
+ 4. **Prerequisites first.** Order the batch so prerequisite decisions come before
83
+ the ones they shape; where an answer can invalidate a later question, ask the
84
+ prerequisite alone and go depth-first.
76
85
  5. **Reconcile contradictions immediately**, and chase dodges: "we'll decide
77
86
  later" → "what's the latest you can decide and still ship?"
78
87
  6. **Cover the autonomy sweep** (below). An unasked MATERIAL question is not
@@ -112,6 +112,14 @@ which grows but never reorders a stage. Discovery that lands in a committed
112
112
  artifact is not a dynamic graph; discovery that changes what runs next, silently,
113
113
  is.
114
114
 
115
+ **Wave mode's re-cut is the same kind of discovery** ([`waves.md`](waves.md)). After
116
+ each wave the remaining waves are re-cut from the re-measured inventory, and every move
117
+ lands twice in committed form: a `revisions` entry in the work graph (`graph.py wave`,
118
+ which refuses a move that breaks the wave order) and the canvas edit that says why. The
119
+ stage list does not change and no stage is invented; what changes is which nodes the
120
+ next pass of stages 5–8 takes, and that is written down before the pass begins. So the
121
+ static-graph decision holds.
122
+
115
123
  ## Task right-sizing
116
124
 
117
125
  A task is the smallest unit that carries its own test cycle and is worth a fresh
@@ -71,6 +71,7 @@ a row pointing outside the bundle is the defect this file exists to catch.
71
71
  | What a stage reads, and which host files bind it | `references/artifacts.md` |
72
72
  | The design conversation, its hard gate, UI detection, user paths | `references/brainstorm.md` |
73
73
  | Cutting a platform into modules, brick criteria, build order | `references/decomposition.md` |
74
+ | **Wave mode** — the whole board: the inventory, the canvas, the one question batch, the wave cut and the close pass | `references/waves.md` |
74
75
  | What a spec must lock, the UX-track order, the module dossier | `references/spec.md` |
75
76
  | The zero-context plan format, parallel groups, set equality | `references/planning.md` |
76
77
  | The work graph: its fields, the verbs and their exit codes, and the three invariants a schema cannot state | `references/work-graph.md`, `scripts/graph.py`, `graph.schema.json` |
@@ -166,9 +166,10 @@ never that the work was skipped quietly.
166
166
  ([`progress.md`](progress.md)), and a skip nobody can see is indistinguishable from a
167
167
  stage that was never entered.
168
168
  - **How it runs: [`grill.md`](grill.md)** — the full doctrine, built into this
169
- skill (nothing to install). In short: one question per turn, a recommended
170
- answer with each, explore the codebase before asking, depth-first through the
171
- decision tree, contradictions reconciled on the spot; **every answer that touches
169
+ skill (nothing to install). In short: close what the sources can, then ONE batch
170
+ of the material unknowns, ordered by dependency, a recommended answer with each —
171
+ one at a time only where answers depend on each other or the operator asks;
172
+ contradictions reconciled on the spot; **every answer that touches
172
173
  a harvested source is checked against it** — the operator outranks any document,
173
174
  but only out loud, and the losing side is logged for the stage-9 doc update; plus
174
175
  **domain awareness**
@@ -267,9 +268,9 @@ never that the work was skipped quietly.
267
268
  - **Freedom: high** — many designs are valid — this is the open field, and the only fixed thing is the gate ([`gates.md`](gates.md) → *Axis C*).
268
269
  - **How it runs: [`brainstorm.md`](brainstorm.md)** — built into this skill. Read
269
270
  the brief first (stage 0 already answered scope/constraints/done-criteria), then
270
- explore the codebase, scope-check for decomposition, one question at a time, 2–3
271
- approaches with a recommendation, design presented in sections and approved
272
- section by section. **Hard gate:** no code, no scaffolding, no implementation
271
+ explore the codebase, scope-check for decomposition, the open questions in one
272
+ batch, 2–3 approaches with a recommendation, design presented in sections and
273
+ approved section by section. **Hard gate:** no code, no scaffolding, no implementation
273
274
  skill before the operator approves the design — including on "obviously simple"
274
275
  tasks.
275
276
  - **UI detection (mandatory check):** decide whether the task touches any
@@ -859,6 +860,9 @@ module N → 3 spec (dossier) → 4 plan → 5 build → 6 tests → 7 lint+depl
859
860
  - **Program done** when every row is `done` or `deferred` with an agreed home, the
860
861
  cross-module contracts are covered by tests that cross the seam, and a final
861
862
  acceptance covers the platform's whole REQ table — not module by module.
863
+ - **The whole board, rather than one platform, is wave mode**
864
+ ([`waves.md`](waves.md)): stages 0–4 once over every open item, 5–8 per wave with a
865
+ close pass after each, 9–10 to close the release.
862
866
 
863
867
  ## Cross-cutting — the Doc Loop
864
868
 
@@ -0,0 +1,225 @@
1
+ # Wave mode — closing a whole board in its final form
2
+
3
+ `/task-pipeline waves`. The request is not one change but **the whole board**: every open
4
+ item a project carries — the board and its lanes, carry-over ledgers, plan rows,
5
+ verification rows still at `never`, scenarios with no coverage, open PRs and unlanded
6
+ branches, asks from other products' sessions — plus the operator's new requests.
7
+
8
+ **It is a program mode of the existing stages, not a parallel cycle.** Stages 0–4 run
9
+ once, for the whole board. Stages 5–8 run once per wave. Stages 9–10 close the release.
10
+ Every gate in [`stages.md`](stages.md) still holds where its stage runs; what the mode
11
+ changes is how many times each stage runs, and over what.
12
+
13
+ Decided by the operator on 2026-10-11 and first applied that day to a board of 701 items.
14
+
15
+ ## Contents
16
+
17
+ - The rule it exists for
18
+ - How the stages map
19
+ - Wave 0 — the survey and the inventory
20
+ - The questions — closed from sources, then one batch
21
+ - The canvas — the target state over the current logic
22
+ - Cutting the waves
23
+ - The wave loop
24
+ - The close pass, after every wave
25
+ - A board bigger than one release
26
+ - What stays human
27
+ - The graph carries the wave
28
+ - Rationalizations
29
+
30
+ ## The rule it exists for
31
+
32
+ **Build the system in its final form, never through intermediate states.** An
33
+ intermediate state that a later task undoes is the most expensive thing an agent
34
+ produces: the build, the review, the migration and the operator's attention are each
35
+ paid twice, and the second payment is for removing the first. So every item is designed
36
+ against the target state of the whole system first, and only then cut into waves.
37
+
38
+ **No wave builds a shape a later wave undoes.** A stub is allowed only at a contract the
39
+ canvas declares, and the wave that removes it is named beside it. A stub with no named
40
+ removal is the intermediate state this mode exists to refuse.
41
+
42
+ What the mode buys, in the order the operator named it: transparency (the inventory,
43
+ the canvas and the wave plan live in the repository, so any agent can resume from them),
44
+ stability, autonomy, predictability (each wave says before it starts what it closes and
45
+ how that is proved), minimum operator time, fast real results, and a strategy corrected
46
+ on real agents, tasks and users rather than on the first guess.
47
+
48
+ ## How the stages map
49
+
50
+ | Stage | In wave mode |
51
+ |---|---|
52
+ | 0 Intake | once, over the whole board: wave 0's survey is the harvest, the inventory is the work-list, and every in-scope item becomes or joins a REQ row in the operator's words |
53
+ | 1 Docs study | once, for every contract the canvas locks |
54
+ | 2 Brainstorm + decompose | once: the canvas extends the module map ([`decomposition.md`](decomposition.md)), and the work graph carries each node's wave |
55
+ | 3 Spec | once: the canvas is the committed target-state spec; a wave's dossier may deepen a section, never contradict it |
56
+ | 4 Plan | once: every wave's nodes, its exit criteria and the question batch. Re-cut at each wave boundary, as a recorded revision |
57
+ | 5–8 Build → post-deploy | per wave, then the close pass |
58
+ | 9 Docs + wiki · 10 Acceptance | once, to close the release, over everything the waves shipped |
59
+
60
+ **One planning gate, not three prompts.** The manual gates of stages 0, 2 and 3 are
61
+ presented together at the end of planning: the brief, the canvas, the wave plan and the
62
+ question batch in one message. Each gate's criteria are still checked and printed; the
63
+ operator is asked once, and the go names the gates it closes. Silence closes none.
64
+
65
+ ## Wave 0 — the survey and the inventory
66
+
67
+ **Harvest every source of open work, by id** ([`knowledge-sources.md`](knowledge-sources.md)
68
+ supplies the sources; [`backlog.md`](backlog.md) the board). For whole-project scope, run
69
+ `project-audit` beside it as a read-only sidecar: its HTML and JSON are measured
70
+ findings, and its proposed rows join the inventory like any other source. Where the
71
+ product has users, walk their real path in the built app on a fresh profile with
72
+ disposable data. A finding is a fact with a receipt, never an impression.
73
+
74
+ **One machine-readable inventory, nothing outside it.** A single committed file,
75
+ `<artifacts>/specs/YYYY-MM-DD-<topic>-inventory.json`, one entry per open item:
76
+
77
+ | Field | Carries |
78
+ |---|---|
79
+ | `id` | the item's id in its owning register; an item without one gets one there, in the same change |
80
+ | `source` | where it was found, as `file:line` |
81
+ | `status` | as measured, not as the register last said |
82
+ | `area` | the part of the system it touches |
83
+ | `node` | the canvas node it lands on |
84
+ | `wave` | the wave that closes it, or the release it is carried to |
85
+ | `release` | this release, or `program` |
86
+
87
+ `blocked_by`, `closable_in_one_pass` and `notes` are worth carrying beside them.
88
+
89
+ **Verify each open item against the code before trusting it.** A stale item is closed
90
+ in its owning register with the receipt that shows it done; duplicates are clustered,
91
+ both ids kept. A register that says *open* about shipped work is the commonest thing a
92
+ survey finds.
93
+
94
+ **Assign nodes and waves with a committed script, not by hand.** A deterministic script
95
+ reads the inventory and a short wave table and writes `node`, `wave` and `release` onto
96
+ every entry, then prints the counts per wave and per node. A re-cut is then a diff to a
97
+ table and a re-run, which a reviewer can read; seven hundred hand-edited rows are not.
98
+
99
+ ## The questions — closed from sources, then one batch
100
+
101
+ **Close every question from the sources first**, in this order: the operator's recorded
102
+ decisions, ADRs and principles, the knowledge base, the code, the neighbours' contracts.
103
+ A question answered there is recorded with its citation — *resolved from the sources,
104
+ not asked* — and closed. What remains is only what is genuinely the operator's: access,
105
+ consent, money, a business or product direction, an outward or irreversible act.
106
+
107
+ **Those go once, as one batch, at the end of planning.** Each carries a recommended
108
+ answer and the consequence of each answer, ordered so prerequisite decisions come first
109
+ ([`grill.md`](grill.md)). A go that leaves a question unanswered takes the
110
+ recommendation, recorded as **assumed** in the decisions table, and every wave that
111
+ depends on an assumption names it — so a different answer later re-plans exactly those
112
+ waves and no others. After planning, a question is asked only at a wave boundary, in the
113
+ hand-back's DECISIONS WAITING ([`progress.md`](progress.md)).
114
+
115
+ **An outward or irreversible act is never assumed.** Publishing, tagging, deploying
116
+ without a specific standing authorization, sending to a person, spending money,
117
+ deleting data: each waits for the operator's explicit go, asked with a verified direct
118
+ link. A recommendation is not an authorization.
119
+
120
+ ## The canvas — the target state over the current logic
121
+
122
+ One committed spec, `<artifacts>/specs/YYYY-MM-DD-<topic>-canvas.md`, extending the module
123
+ map. It is the current system with every change laid over it:
124
+
125
+ 1. **As-is → to-be per module and per contract** — data and events, screens and flows,
126
+ cross-product links.
127
+ 2. **The integration seams** — how each integration happens, who owns each side, what
128
+ crosses it.
129
+ 3. **REQ → module → wave**, one row per requirement.
130
+ 4. **Deleted or superseded** — what the new version removes (code, screens, contracts,
131
+ docs) and which wave removes it. A removal nobody wrote down survives the release.
132
+
133
+ **Every inventory item lands on a node.** An item that lands nowhere is either out of
134
+ scope — said, with a register row — or a gap in the canvas. **The canvas is reviewed for
135
+ coherence before any wave is cut:** two items that change one seam are designed together,
136
+ because designed apart they are the intermediate state the rule above forbids.
137
+
138
+ ## Cutting the waves
139
+
140
+ **A wave holds only items closable in one pass.** All four, for every item in it:
141
+
142
+ - its dependencies are done, or inside the same wave;
143
+ - it touches one seam, or a few adjacent ones;
144
+ - its verification can run together with the rest of the wave;
145
+ - the wave leaves the system working — landable, with no half-built shape exposed.
146
+
147
+ **Wave 1 is the walking skeleton**: the thinnest end-to-end path of the target that
148
+ proves the spine, for the reason [`decomposition.md`](decomposition.md) gives for a
149
+ platform's first brick. **Each wave names its exit criteria before it starts** — the
150
+ tests, the live walk, the gates and the documents that will prove it closed. Criteria
151
+ written after the wave are a description of what happened.
152
+
153
+ An item that cannot close in one pass because it needs a person stays on its node,
154
+ marked `needs-operator`, out of every wave until it is unblocked.
155
+
156
+ ## The wave loop
157
+
158
+ Each wave runs stages 5–8 as [`stages.md`](stages.md) states them, then the close pass.
159
+ Between waves the run does not ask whether to continue: it takes the next wave
160
+ ([`continuity.md`](continuity.md)). A genuine ambiguity parks its node `needs-operator`
161
+ and the wave goes on with the nodes it does not block; the run stops only when the whole
162
+ frontier is blocked. When the board is empty, it says so once.
163
+
164
+ Research, the audit and the blind readings run in parallel agents; builds stay sequential
165
+ wherever two of them share a seam ([`planning.md`](planning.md) → file ownership).
166
+
167
+ ## The close pass, after every wave
168
+
169
+ In this order, inside the wave rather than after it:
170
+
171
+ 1. **Verify.** Every node the wave closes is certified ([`certification.md`](certification.md)),
172
+ and [`audit.md`](audit.md)'s exit is re-measured over what the wave touched.
173
+ 2. **Finish and fix — one fix round**, inside [`loop-guard.md`](loop-guard.md)'s caps.
174
+ Every finding is fixed in the wave or parked with a board row and its reason; nothing
175
+ is dropped silently.
176
+ 3. **Document** — the Doc Loop and the stage-9 propagation matrix
177
+ ([`documentation.md`](documentation.md)), scoped to the wave: scenarios, decisions,
178
+ docs and registers in the same change as the code.
179
+ 4. **Mark the canvas.** Each node the wave closed carries its status and the commit.
180
+ 5. **Re-read the goal** ([`continuity.md`](continuity.md) → *The goal is re-read between
181
+ items, not only the board*). A re-plan is a committed `revisions` entry plus a canvas
182
+ edit, never a quiet change.
183
+ 6. **Re-measure the inventory and re-cut the remaining waves** from the updated target —
184
+ never from the original guess. New findings join the inventory with ids.
185
+
186
+ ## A board bigger than one release
187
+
188
+ **It becomes a program.** The release takes the first waves; the rest stays on the
189
+ canvas by node, `release: program`, never dropped. The next release resumes from the
190
+ canvas: it re-measures the inventory rather than re-running wave 0 from nothing, and its
191
+ waves are cut from the nodes still open.
192
+
193
+ ## What stays human
194
+
195
+ Sign-ins, consents, payments, sending to people, and every outward or irreversible act.
196
+ The mode removes *"shall I continue?"* between waves and *"what did you mean?"* where a
197
+ source already says; it never removes a person from a decision that is theirs. A
198
+ `manual` gate still waits for its go ([`continuity.md`](continuity.md) → *The limit,
199
+ before the capability*).
200
+
201
+ ## The graph carries the wave
202
+
203
+ The work graph ([`work-graph.md`](work-graph.md)) carries `wave` on a node, an integer from
204
+ 1. Three verbs read it:
205
+
206
+ - `graph.py validate` refuses an edge from a later wave's node into an earlier wave's
207
+ node: an earlier wave that waits on a later one cannot close in its own pass.
208
+ - `graph.py next --wave N` prints wave N's frontier only. Exit `3` says the wave is done,
209
+ `4` that nothing in it is runnable, `1` that no node is in wave N.
210
+ - `graph.py wave N-007 --to 3 --why "<reason>"` moves a node and writes the revision.
211
+ It refuses a move that would break the order above.
212
+
213
+ Without python3 the same three facts live in the wave plan's table, checked by hand and
214
+ said to be checked by hand.
215
+
216
+ ## Rationalizations
217
+
218
+ | Excuse | Reality |
219
+ |---|---|
220
+ | "Ship the simple version now, the real one later" | That is the intermediate state. Its removal is a second build nobody planned |
221
+ | "Ask each question as it comes up" | Each one is a human round. Close it from the sources, or put it in the batch |
222
+ | "The board is too big to inventory" | The inventory is the plan. Without it the waves are cut from memory |
223
+ | "Re-cutting waves makes the pipeline dynamic" | A re-cut is a committed revision and a canvas edit. The static-graph decision in [`planning.md`](planning.md) holds |
224
+ | "The recommendation is obviously right, so assume the deploy go" | An outward act is never assumed. Ask for the specific go once, in the batch |
225
+ | "Wave 3 can stub what wave 5 will build properly" | Only at a declared contract, with wave 5 named as the stub's removal |
@@ -41,6 +41,7 @@ turn of every loop.
41
41
  | `nodes[].blocked_by` | what must PRODUCE first. The frontier obeys a satisfaction predicate, and only `done` satisfies — a PARKED producer blocks its consumer, because a park is a decision not to produce, not a production. `next` names each held consumer with the park's reason; a valid alternative producer is an explicit, versioned edge change, never an implicit unblock |
42
42
  | `nodes[].touches` | what it **mutates**. Two runnable nodes writing one file is the false parallelism [`planning.md`](planning.md) refuses — *distinct is not the same as independent, and the check is what they touch, never what they are called* |
43
43
  | `nodes[].check` | **how this node will be closed** — one command, or the named judgement where no command can decide it. Required on every node except a `parked` one. The certification's `unit` tier runs it and reports its output as the evidence row ([`certification.md`](certification.md) — three blind tiers close a node, not one reader); before this field existed that instruction pointed at an absence, leaving a verifier the two things it forbids — invent a check, or run everything (B-080) |
44
+ | `nodes[].wave` | wave mode only ([`waves.md`](waves.md)): the pass of stages 5–8 that closes the node, from 1. **An earlier wave may not wait on a later one** — `validate` refuses the edge, because a wave holds only what closes in its own pass |
44
45
  | `nodes[].evidence` | required when `status` is `done`. A node called done by assertion is what evidence exists to prevent |
45
46
  | `nodes[].parked_reason` | required when `status` is `parked`. A park with no reason is indistinguishable, a week later, from work quietly dropped |
46
47
  | `edges[].payload` | what the dependency hands over. **An edge carrying no named artifact is chronology drawn as architecture** |
@@ -54,11 +55,12 @@ conditional on the code, never merely sequenced after it.**
54
55
  | Verb | Prints | Codes |
55
56
  |---|---|---|
56
57
  | `validate` | every violation, in a stable order | `0` clean · `1` any |
57
- | `next` | the frontier, ordered by how much each node unblocks — **and nothing else** | `0` printed · `3` all done · `4` nothing runnable |
58
+ | `next` | the frontier, ordered by how much each node unblocks — **and nothing else**. `--wave N` narrows it to one wave, and `3` then means *this wave is done* | `0` printed · `3` all done · `4` nothing runnable · `1` with `--wave`, a wave no node is in |
58
59
  | `coverage` | each requirement with the nodes serving it | `0` covered · `1` a gap, named |
59
60
  | `goal` | the release goal | `0` · `3` unstated |
60
61
  | `add` | the id it allocated | `0` · `1` refused |
61
62
  | `park` | the id and the reason | `0` · `1` refused |
63
+ | `wave` | wave mode's re-cut: the node, its old and new wave, and the reason, which enters the revision log. Refuses a move that would leave an earlier wave waiting on a later one, and a `done` node | `0` · `1` refused |
62
64
  | `certify` | the round, and on a failure every `breaks` finding with its fix and its check | `0` every owed tier passed — three, or four with `visual` on a flagship or product node · `1` a tier failed, a required one is missing, or a report is malformed |
63
65
  | `close` | the goal, the new frontier count, and what was not verified | `0` · `1` refused **or the verdict stops the run** |
64
66
  | `producer` | what produced this proof — actor, model, runtime, skill, config, commit, trace | `0` |
@@ -88,7 +88,7 @@ TERMINAL = {"done", "parked"}
88
88
  NO_GRAPH = {"producer", "doctrine"}
89
89
  # One place, and the schema enumerates the same three. Two homes for this set is
90
90
  # what let `close` write a verb the format forbade.
91
- REVISION_VERBS = {"add", "park", "close", "invalidate", "waive"}
91
+ REVISION_VERBS = {"add", "park", "close", "invalidate", "waive", "wave"}
92
92
  # Parking a node PROMOTES its dependents: `parked` is terminal, so anything
93
93
  # blocked on it becomes runnable even though the payload it waited on never
94
94
  # arrived. That is deliberate — `can_continue_around` in the verdict is the
@@ -276,6 +276,13 @@ def violations(graph):
276
276
  f"{', '.join(SURFACE_CLASSES)}. A class nobody recognises requires no "
277
277
  "visual reading, so a typo here would drop the tier that reads the pixels")
278
278
 
279
+ # Wave mode (`references/waves.md`). `True` is an int in Python, and a wave of
280
+ # `true` is a typo the ordering check below would read as wave 1.
281
+ if "wave" in n and not _is_wave(n["wave"]):
282
+ out.append(f"{nid}: wave is {n['wave']!r} — it must be an integer from 1. A wave "
283
+ "nobody can order is a node no `next --wave` ever prints, and the "
284
+ "order check below cannot see it")
285
+
279
286
  if n.get("status") == "done":
280
287
  ev = n.get("evidence")
281
288
  if not isinstance(ev, list) or not [e for e in ev
@@ -322,6 +329,17 @@ def violations(graph):
322
329
  "and a dependency handing over nothing is the one this check "
323
330
  "exists to refuse")
324
331
 
332
+ # An earlier wave may not wait on a later one: a wave holds only what closes in one
333
+ # pass, and a node whose producer is scheduled after it cannot close in its own.
334
+ wave_of = {n.get("id"): n.get("wave") for n in nodes if _is_wave(n.get("wave"))}
335
+ for e in graph.get("edges") or []:
336
+ src, dst = e.get("from"), e.get("to")
337
+ if src in wave_of and dst in wave_of and wave_of[src] > wave_of[dst]:
338
+ out.append(f"edge {src}→{dst}: {src} is in wave {wave_of[src]} and {dst} in "
339
+ f"wave {wave_of[dst]} — an earlier wave would wait on a later one, "
340
+ "so it cannot close in its own pass (references/waves.md). Move "
341
+ f"{dst} to wave {wave_of[src]} or later, or {src} earlier")
342
+
325
343
  for i, r in enumerate(graph.get("revisions") or []):
326
344
  if not isinstance(r, dict):
327
345
  out.append(f"revisions[{i}] is not an object")
@@ -603,6 +621,11 @@ TIERS = ("unit", "seam", "product")
603
621
  VISUAL_TIER = "visual"
604
622
  ALL_TIERS = TIERS + (VISUAL_TIER,)
605
623
  SURFACE_CLASSES = ("flagship", "product", "internal", "ad")
624
+
625
+
626
+ def _is_wave(w):
627
+ """A wave is an integer from 1 — and never a bool, which Python counts as an int."""
628
+ return isinstance(w, int) and not isinstance(w, bool) and w >= 1
606
629
  VISUAL_REQUIRED = ("flagship", "product")
607
630
  TIER_KEYS = ("node", "tier", "verdict", "scope", "confirms", "findings",
608
631
  "evidence", "not_examined")
@@ -746,13 +769,24 @@ def cmd_next(graph, args):
746
769
  if violations(graph):
747
770
  die("graph does not validate — run `validate` first", 1)
748
771
  nodes = graph.get("nodes") or []
772
+ wave = getattr(args, "wave", None)
773
+ if wave is not None:
774
+ # One wave's frontier, and its own endings: `3` is *this wave is done*, which is
775
+ # the close pass's cue, not the release's (references/waves.md).
776
+ if not _is_wave(wave):
777
+ die("--wave %r is not a wave — waves are integers from 1" % wave, 2)
778
+ nodes = [n for n in nodes if n.get("wave") == wave]
779
+ if not nodes:
780
+ die("no node is in wave %d — nothing to walk, and an empty wave is a wave plan "
781
+ "and a graph that disagree" % wave)
749
782
  if nodes and all(n.get("status") in TERMINAL for n in nodes):
750
783
  return 3
751
- ready = frontier(graph)
784
+ in_scope = {n.get("id") for n in nodes}
785
+ ready = [n for n in frontier(graph) if n.get("id") in in_scope]
752
786
  # A consumer whose producer is PARKED is not runnable and never will be by
753
787
  # itself (FIX-PF-04.01) — say so BEFORE the empty-frontier exit, or the one
754
788
  # moment the operator most needs the reason is the one moment it is silent.
755
- _by_id = {n.get("id"): n for n in nodes}
789
+ _by_id = {n.get("id"): n for n in graph.get("nodes") or []}
756
790
  for n in nodes:
757
791
  if n.get("status") in TERMINAL or n.get("status") == "running":
758
792
  continue
@@ -1123,6 +1157,46 @@ def cmd_park(graph, args):
1123
1157
  return 0
1124
1158
 
1125
1159
 
1160
+ def cmd_wave(graph, args):
1161
+ """Move one node to another wave, carrying the reason — wave mode's re-cut.
1162
+
1163
+ The re-cut after every wave is a plan revision, and a revision nobody recorded is
1164
+ the dynamic graph `references/planning.md` refuses. So the move is a verb: it
1165
+ writes the `revisions` entry, and it refuses a move that would leave an earlier
1166
+ wave waiting on a later one (`references/waves.md`).
1167
+ """
1168
+ guard(graph, args.graph)
1169
+ why = (args.why or "").strip()
1170
+ if not why:
1171
+ die("`wave` needs a --why with something in it: a re-cut nobody gave a reason for "
1172
+ "is the quiet edit the revision log exists to refuse — nothing was written")
1173
+ if any(c in why for c in "\n\r"):
1174
+ die("--why contains a line break — nothing was written")
1175
+ if not _is_wave(args.to):
1176
+ die("--to %r is not a wave — waves are integers from 1. Nothing was written"
1177
+ % args.to)
1178
+ node = next((n for n in graph.get("nodes") or [] if n.get("id") == args.node), None)
1179
+ if node is None:
1180
+ die("no node %s in this graph — nothing was written" % args.node)
1181
+ if node.get("status") == "done":
1182
+ die("%s is done, in wave %s — moving it would rewrite which pass closed it. "
1183
+ "Nothing was written" % (args.node, node.get("wave")))
1184
+ if node.get("wave") == args.to:
1185
+ die("%s is already in wave %d — nothing was written" % (args.node, args.to))
1186
+
1187
+ before = node.get("wave")
1188
+ node["wave"] = args.to
1189
+ revise(graph, "wave", args.node, why)
1190
+ bad = violations(graph)
1191
+ if bad:
1192
+ die("moving %s to wave %d would break the graph — nothing was written:\n %s"
1193
+ % (args.node, args.to, "\n ".join(bad)))
1194
+ save(args.graph, graph)
1195
+ print("%s: wave %s → %d: %s" % (args.node, before if before is not None else "none",
1196
+ args.to, why))
1197
+ return 0
1198
+
1199
+
1126
1200
  def next_id(known):
1127
1201
  """The next id, from the MAXIMUM rather than from the count.
1128
1202
 
@@ -1359,7 +1433,7 @@ def cmd_producer(graph, args):
1359
1433
  def cmd_doctrine(graph, args):
1360
1434
  """Which doctrine this run actually read — B-061.
1361
1435
 
1362
- The bundle is 39 reference files. A run reads some subset and nothing recorded which,
1436
+ The bundle is 40 reference files. A run reads some subset and nothing recorded which,
1363
1437
  so **a skipped file and a read one were indistinguishable** — the class every guard in
1364
1438
  this repository exists to catch, left standing over the doctrine itself.
1365
1439
 
@@ -1825,6 +1899,7 @@ VERBS = {
1825
1899
  "coverage": (cmd_coverage, "every requirement and the nodes serving it; exits 1 on a gap"),
1826
1900
  "add": (cmd_add, "add a node mid-run"),
1827
1901
  "park": (cmd_park, "park a node, carrying the reason"),
1902
+ "wave": (cmd_wave, "move a node to another wave, carrying the reason"),
1828
1903
  "certify": (cmd_certify, "require three independent tier reports (four on a "
1829
1904
  "flagship or product surface), then emit the verdict "
1830
1905
  "`close` consumes"),
@@ -1905,6 +1980,15 @@ def main(argv=None):
1905
1980
  made["waive"].add_argument("--by", required=True,
1906
1981
  help="the identity that authorized the exception")
1907
1982
 
1983
+ made["next"].add_argument("--wave", type=int, default=None,
1984
+ help="print only this wave's frontier; exit 3 when the wave "
1985
+ "is done (references/waves.md)")
1986
+ p_wave = made["wave"]
1987
+ p_wave.add_argument("node")
1988
+ p_wave.add_argument("--to", type=int, required=True, help="the wave to move it to")
1989
+ p_wave.add_argument("--why", required=True,
1990
+ help="why the re-cut moved it — it enters the revision log")
1991
+
1908
1992
  p_park = made["park"]
1909
1993
  p_park.add_argument("node")
1910
1994
  # `required=True` makes the MISSING flag a usage error (exit 2). The empty and
@@ -1916,7 +2000,7 @@ def main(argv=None):
1916
2000
  verbs = {k: v[0] for k, v in VERBS.items()}
1917
2001
  if args.verb in NO_GRAPH:
1918
2002
  return verbs[args.verb](None, args)
1919
- if args.verb in ("add", "park", "close", "certify", "invalidate", "waive"):
2003
+ if args.verb in ("add", "park", "wave", "close", "certify", "invalidate", "waive"):
1920
2004
  # The READ happens inside the lock too. Loading first and locking second is the
1921
2005
  # same lost update with an extra step: the stale copy is already in memory.
1922
2006
  with held(args.graph):
@@ -20,7 +20,7 @@ Run: `<topic>` · started `<YYYY-MM-DD>` · module map: `<path or "none">`
20
20
 
21
21
  ## `read:` — which doctrine this run actually opened
22
22
 
23
- The bundle is 39 reference files and nothing recorded which of them a run read, so **a
23
+ The bundle is 40 reference files and nothing recorded which of them a run read, so **a
24
24
  skipped file and a read one were indistinguishable** — the class every guard in this
25
25
  pipeline exists to catch, left standing over the doctrine itself.
26
26
 
@@ -48,7 +48,7 @@ same claim one level down.
48
48
  |---|---|---|
49
49
  | `unmeasured — no run ledger` | there is no ledger | nothing to read from |
50
50
  | `unmeasured — the ledger carries no read: lines` | the hook is absent, **or** the run opened no doctrine | two opposite facts, and the ledger cannot separate them, so neither is claimed |
51
- | `N of 39 reference files read — unattested`, then each unread one | the hook is installed and fired | the count alone says there is a gap, not where — and `unattested` says the ledger cannot name who wrote the lines |
51
+ | `N of 40 reference files read — unattested`, then each unread one | the hook is installed and fired | the count alone says there is a gap, not where — and `unattested` says the ledger cannot name who wrote the lines |
52
52
 
53
53
  **It is a disclosure: no floor, no direction, never a target.** A run that needs four files
54
54
  and reads four is not worse than one that reads thirty — and the moment the number becomes