task-pipeline-skill 1.56.0 → 1.58.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,198 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.58.0 — a fan-out is not finished when its branches are
4
+
5
+ The graph model this pipeline was audited against in v1.57.0 named one defect and fixed
6
+ it in one place. Applying the same model to the rest of the skill found the same defect
7
+ in **three more**, and they are the same sentence each time: work fans out, the branches
8
+ each go green, and the node that consumes them trusts them because they arrived.
9
+
10
+ ### Added
11
+
12
+ - **The harvest is a convergence, and now it has a check.** Stage 0 queries the code, the
13
+ graph, `CLAUDE.md`, the ADRs, the docs, past briefs, the wiki, the board — all
14
+ independently — and lands them in one brief the interview then treats as a single
15
+ answer. Phase 2 checks each *answer* against the harvest; **nothing compared the sources
16
+ with each other**, so a doc contradicting the code produced two rows that each looked
17
+ fine and the run followed whichever it read last. The ledger now carries a
18
+ `Contradictions:` line with four things to look for, and the stage-0 gate reads it.
19
+ `Contradictions: none` is the answer most runs write, and writing it is the point.
20
+
21
+ - **Stage 3's COPY and VISUAL tracks are a parallel layer, and their convergence has a
22
+ check.** Neither consumes the other — copy is written from the scenarios and the brand
23
+ pack, the visual from the frame and the style pack — so the order they were written in
24
+ was a fake edge teaching a run to wait for a result that never arrives. What they do
25
+ share is the screen, which is where the real failure lives: **each track is right alone
26
+ and they disagree together.** A label the layout has no room for, a state one drew and
27
+ the other never wrote, two names for one component, a tone the motion contradicts.
28
+
29
+ - **Stage 9's three artifacts are named as a convergence**, and the graph↔docs divergence
30
+ check as its gate rather than a nicety — it is the only thing that compares two of the
31
+ three outputs against each other.
32
+
33
+ Guards: 339 → **344**. Property checks: 9 → 9. Five new plants, one per branch of the
34
+ three new checks, each anchored on a heading and each asserting it changed something.
35
+ Two of the five were broken on their first run and both failures were mine: a grep
36
+ pattern one character short of the message it looked for — **the same class fixed hours
37
+ earlier in this programme and not swept into the new plants**, which is R-003 — and an
38
+ assertion looking for the doctrine in the file that does not hold it.
39
+
40
+ ### Changed
41
+
42
+ - **The stage-4 gate reads both halves of what it requires**, and the id/version
43
+ allocation this repository actually uses is written down. `.claude/agent-sync.json`
44
+ declares three id registers over an `fs` backend whose `reserve` refuses by design —
45
+ correctly, since *pretending would hand two agents the same id* — and the declaration
46
+ read as a capability, so nobody wrote the manual procedure it was hiding. On 2026-08-15
47
+ two sessions filed a different `B-073` and two branches claimed one version number.
48
+ `CLAUDE.md` now carries the three-step allocation (lease first, compute from the
49
+ **committed** file, commit before releasing) and the version rule (`git ls-remote --tags`,
50
+ because a local checkout is not where the answer lives), the shipped doctrine in
51
+ `documentation.md` carries the generalisable half, and a guard requires both.
52
+
53
+ ### Fixed
54
+
55
+ - **Two concurrent runs of `test/negatives.py` no longer corrupt each other** (`B-075`).
56
+ Every step copies the repo to a **fixed** `/tmp` name, which is right in CI — one runner
57
+ per job — and wrong on a machine where a second suite is already running. The runner now
58
+ serialises the runs instead: an exclusive lock for the duration of the suite, so a second
59
+ run **waits** rather than corrupting the first and says so instead of producing a number
60
+ nobody can trust. The 344 workflow steps are untouched — they are the CI contract, one
61
+ runner per job, and they were never the ones colliding.
62
+
63
+ **The first fix was wrong and is worth recording.** It rewrote every `/tmp/...` path in a
64
+ step's script to a per-run name, and it broke two plants whose payload **is the workflow
65
+ text** — they search the copied workflow for a literal path in order to duplicate it. A
66
+ mechanical rewrite cannot tell a path being *used* from a path being *discussed*, which is
67
+ the umbrella's standing instruction #7 met for the second time in two days, both times by
68
+ the same author. The suite caught it; reading did not.
69
+
70
+ **Watched both ways, under real overlap.** Before, two concurrent runs of one selector
71
+ returned `1 guard did not fire, 7 broken` and `8 guards did not fire` — two different
72
+ wrong answers about a tree that was not changing. After, both return `all 8 guards
73
+ provably reject their planted defect`, exit 0, with the second printing that it waited.
74
+
75
+ ## v1.57.0 — an arrow that carries nothing is not an arrow, and two green diffs can still contradict each other
76
+
77
+ The pipeline has drawn a dependency graph at stage 4 since it had a stage 4, and grouped
78
+ tasks topologically off it. What it never said was how to tell a **real** edge from one
79
+ that only records the order somebody typed the tasks in. The self-review asked the right
80
+ question in a checklist line — *does this `depends:` point at a task that really produces
81
+ what's consumed* — and no gate read the answer, so a plan could serialise itself entirely
82
+ and pass every check.
83
+
84
+ Audited against *Graph Engineering with Claude*
85
+ (`https://x.com/Mahaximus_/status/2082442856417956173`), findings and both rejections in
86
+ `docs/evidence/specs/2026-08-15-graph-audit.md`. **Nine of the ten macro stage edges carry
87
+ data**, which is the audit's first result and the reason nothing was reordered.
88
+
89
+ ### Added
90
+
91
+ - **The fake-edge test, as a numbered procedure** (`references/planning.md`). Six steps
92
+ over the graph you just drew: for each arrow, does output from A actually enter B, and if
93
+ you cannot name what crosses it, delete it. Expect two or three per plan.
94
+
95
+ - **A `Carries` column in the *Execution order* table**, and it is the whole mechanisation:
96
+ a cell you cannot fill **is** the finding. The fake-edge test stops being a thing an agent
97
+ remembers to do and becomes a column a reviewer can see is empty.
98
+
99
+ - **`Edges: <n> declared, <n> carry data, <n> removed`** in the stage-4 self-review, and
100
+ the gate reads it. Computed, like every other line in that block.
101
+
102
+ - **The group convergence check** (`references/build.md` §4.2a). A per-task review reads
103
+ **one diff**; a fanned-out group produces several, and the defect that exists only
104
+ *between* two of them passes both. One check over the group's reports and diffs together,
105
+ after the last task and **before the first worktree is integrated** — the only moment all
106
+ of them exist and none has landed. Five things it looks for, each a real defect invisible
107
+ in a single diff: an empty deliverable, two outputs that cannot both be true, off-brief
108
+ work, one REQ satisfied twice differently, a Global Constraint only one task applied.
109
+ **A clean group logs a line too**, because a check whose silence is indistinguishable from
110
+ not having run is not evidence.
111
+
112
+ - **A statement that this pipeline is a static graph, and why** (`references/planning.md`).
113
+ Auditability: a graph that decides its own shape produces a shape nobody drew, and then
114
+ *"here is the pipeline"* and *"here is what this run did"* stop being the same document.
115
+ The two places the run **does** discover structure — the module map and the carry-over
116
+ ledger — are named, and both land in a committed artifact, which is what separates
117
+ discovery from a dynamic graph.
118
+
119
+ - **A preference for a harness-native fan-out primitive**, on the same reasoning §1 already
120
+ applies to worktrees: the harness owns the concurrency cap, the isolation and the resume.
121
+ Stated **without naming a product** — the keyword for one host's fan-out was renamed six
122
+ weeks after the article documenting it, and doctrine pinned to a vendor's noun rots on
123
+ that schedule.
124
+
125
+ ### Fixed
126
+
127
+ - **`test/negatives.py` could not restore `.git` in a submodule checkout, so two guards
128
+ silently never fired.** The restore was gated on `os.path.isdir(.git)`. In every checkout
129
+ of this repository **as a submodule** — which is how the `sshlg-skills` umbrella ships it,
130
+ and therefore how most work on it happens — `.git` is a 48-byte file holding a `gitdir:`
131
+ pointer, so the branch was skipped and both git-dependent guards reported `fatal: not a
132
+ git repository` and were counted as *did not fire*. CI clones normally and was green,
133
+ which is why it survived. Measured before: exit `1`, `2 guard(s) did not fire`. After:
134
+ exit `0`, **all 318 guards**, twice consecutively.
135
+
136
+ The copy resolves the `gitdir:` pointer and **copies** the directory rather than pointing
137
+ at it, for two reasons that both bite: a plant that commits would otherwise move the real
138
+ branch, and the module's config carries `core.worktree` aimed back at the live checkout,
139
+ which would make every git command inside the snapshot operate on the tree the snapshot
140
+ exists to protect. That one key is stripped from the copy.
141
+
142
+ - **The fan-out rule was stated with three conditions in `build.md` and one in
143
+ `stages.md`.** The summary kept *own worktree* and dropped *same group* and *exclusive
144
+ file ownership*, so a reader who took the summary as the rule would fan out two tasks that
145
+ share a file, in separate worktrees, and meet the conflict at integration. Both surfaces
146
+ now state all three.
147
+
148
+ - **A property check's plant had gone narrow one column over, and CI is what found it.**
149
+ *The Human column survives a header reorder* rewrites the ledger's header and then swaps
150
+ each row's two last cells — matching on `| <auto> | <human> | — |`, with the `—` a
151
+ literal. Seven rows added by this run carry a finding id in `Note` instead, went
152
+ unmatched, stayed in the old order under a reordered header, and the check reported the
153
+ **doctrine** broken when the **plant** was what had aged. It had already been widened once
154
+ for exactly this reason one column to the left, which is what makes it a class rather than
155
+ an incident: a plant anchored on the CONTENT of a cell describes the table it was written
156
+ against. It now matches any note.
157
+
158
+ Guards: 322 → **339**. Property checks: 9 → 9. Seventeen new plants, because a rule a check
159
+ can decide is written as the check and not as prose somebody remembers: the fake-edge test
160
+ renamed away, the `Carries` column dropped, the stage-4 gate no longer reading it, §4.2a
161
+ deleted outright, §4.2a losing its *before integration*, and `stages.md` dropping the
162
+ convergence check from its stage-5 summary — which is the exact drift F-5 found, now
163
+ guarded in the direction it drifted. The other eleven came from **an independent reviewer
164
+ across five rounds on the PR**, which is the mechanism standing instruction R-005 exists to buy: it found four
165
+ branches of the new guards with no plant behind them, a `_section()` site with no
166
+ `is None` arm (the shape every other site in the file has), a stage-5 guard checking one
167
+ of the three preconditions its own message claims, and an uncached read of a memoised
168
+ document. It also found the two gaps that were not nits — see below. Every plant is
169
+ anchored on a heading or a token, and every one asserts it changed something before the
170
+ validator is asked.
171
+
172
+ **Two of the reviewer's findings were defects, not nits, and both were fixed before
173
+ merge.** First: the convergence check was written into narrative prose and into
174
+ `SKILL.md`'s stage table, and **into neither GATE bullet** — so a fanned-out group could
175
+ reach stage 6 having never run it. `build.md`'s and `stages.md`'s gates now require it,
176
+ and a guard requires the gate to require it. Second: the `.git` restore resolved a
177
+ `gitdir:` pointer by hand, which is right for a submodule and **wrong for a linked
178
+ worktree** — the shape `build.md` itself tells every run to work in, where `objects`,
179
+ `refs` and `config` live wherever `commondir` points. It now asks
180
+ `git rev-parse --git-common-dir`, which answers correctly for all three shapes. A fix that
181
+ covers one of two shapes of the same defect is half a fix.
182
+
183
+ ### Not changed, deliberately
184
+
185
+ - **Stage 8 → 9.** The audit's F-4 asked whether docs depend on the post-deploy check or
186
+ only on the version stage 7 produced. Rejected with reasoning rather than left open: a
187
+ single agent session runs serially so removing the edge buys no wall-clock, and a
188
+ post-deploy check can change what stage 9 must write. Weak is not the same as fake.
189
+ - **The stage list, the stage count and every gate type.** Gate *criteria* moved at stage 4
190
+ and stage 5; nothing was renumbered, reordered or retyped.
191
+
192
+ **Released as 1.57.0, not 1.56.0.** This work was branched, reviewed over five rounds and tagged in its own tree while a concurrent session merged a different 1.56.0 — the
193
+ browser-channel release below. Both branches claimed the number; the id register that would have prevented it is declared in `.claude/agent-sync.json` and cannot allocate
194
+ against an `fs` backend, which is the umbrella's open row **B-45**. The same collision took a board id: `B-073` here was renumbered to **B-075**.
195
+
3
196
  ## v1.56.0 — the stages demanded a look and named no way to take one
4
197
 
5
198
  Since v1.36.0 three stages have required the rendered surface to be checked in a
package/README.md CHANGED
@@ -68,8 +68,8 @@ Every gate is **typed**: `auto` — the orchestrator verifies it itself, pass/fa
68
68
  | 1 | Docs study | contracts grounded on current docs | auto |
69
69
  | 2 | Brainstorm + decompose | design approved; UI verdict recorded; every REQ answered; platform: module map approved | manual |
70
70
  | 3 | Spec | committed + reviewed; UI: super-ux chain validated, linter green | manual |
71
- | 4 | Plan | parallel-ready, DoD per task | auto |
72
- | 5 | Dev | tasks DONE (three review verdicts each), TDD green per task | auto |
71
+ | 4 | Plan | parallel-ready, DoD per task; **every edge names what it carries** — an arrow whose payload nobody can fill is a fake edge | auto |
72
+ | 5 | Dev | tasks DONE (three review verdicts each), TDD green per task; **a fanned-out group gets one convergence check over all its diffs together before the first worktree lands** | auto |
73
73
  | 6 | Tests | full suite green, new code covered; **on a web front end the surface is checked in a browser, not in the diff** | auto |
74
74
  | 7 | Lint + deploy | lint clean + suite green before deploy | manual |
75
75
  | 8 | Post-deploy | clean boot / honest degradation, **and the CI verdict read rather than assumed**, and **a deployed web page is opened rather than curled** — a `200` is not a rendered page | auto |
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.56.0 |
15
+ | **Version** | 1.58.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` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-pipeline-skill",
3
- "version": "1.56.0",
3
+ "version": "1.58.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,7 +2,7 @@
2
2
  "name": "task-pipeline",
3
3
  "displayName": "Task Pipeline",
4
4
  "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/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.",
5
- "version": "1.56.0",
5
+ "version": "1.58.0",
6
6
  "author": {
7
7
  "name": "ssheleg",
8
8
  "url": "https://x.com/sshlg93"
@@ -115,8 +115,12 @@ log and archive are **queried** by the task's nouns, because nothing caps them
115
115
  outranks any document — **but only out loud**, so an override is a recorded decision
116
116
  rather than an undetected divergence. That ledger is also stage 9's work list.
117
117
 
118
- **Three artifacts close a run, not two.** Stage 9 syncs the docs, the wiki **and the
119
- code graph** (`/graphify . --update`). The graph is what the next run's harvest queries
118
+ **Three artifacts close a run, not two and they are a convergence, not a sequence.**
119
+ Stage 9 syncs the docs, the wiki **and the code graph** (`/graphify . --update`). None of
120
+ the three consumes another; all three consume the same change, and the **graph↔docs
121
+ divergence check is the gate over their convergence** rather than an extra nicety. That is
122
+ why it is not optional where a graph exists: it is the only thing that compares two of the
123
+ three outputs against each other. The graph is what the next run's harvest queries
120
124
  first, so a stale one is a false premise **carrying the authority of a machine** —
121
125
  a wrong doc gets argued with, a wrong graph gets believed. Refreshing it buys the
122
126
  graph↔docs divergence check; doc-side findings are fixed at stage 9, absences become
@@ -248,12 +252,12 @@ capable available — see `references/model-tiering.md`).
248
252
 
249
253
  | # | Stage | Gate | Type |
250
254
  |---|---|---|---|
251
- | 0 | Intake grill — **mandatory** | source ledger written; **the documentation inventory answered into `docs/DOCMAP.md`** — registers, single homes, the propagation matrix, the gate command — and **intent reconciled against as-built**, every divergence resolved ([`references/documentation.md`](references/documentation.md)); the retro read in full and its archive queried; shared understanding reached; autonomy sweep covered; brief locked + confirmed | manual |
255
+ | 0 | Intake grill — **mandatory** | source ledger written **with its `Contradictions:` line** — the harvest converges on one brief and nothing else compares the sources with each other; **the documentation inventory answered into `docs/DOCMAP.md`** — registers, single homes, the propagation matrix, the gate command — and **intent reconciled against as-built**, every divergence resolved ([`references/documentation.md`](references/documentation.md)); the retro read in full and its archive queried; shared understanding reached; autonomy sweep covered; brief locked + confirmed | manual |
252
256
  | 1 | Docs study | contracts grounded on fetched docs | auto |
253
257
  | 2 | Brainstorm + decompose | design approved; UI verdict recorded; every REQ answered; platform: module map approved | manual |
254
- | 3 | Spec | committed + reviewed; UI: chain validated, linter green, scenarios/`SCR-` traced | manual |
255
- | 4 | Plan | parallel-ready, DoD per task | auto |
256
- | 5 | Dev | tasks DONE, TDD green per task, branch integrated per the brief; **anything generated passes its own checks, and local infrastructure does not publish the host's default ports** ([`references/learned.md`](references/learned.md)) | auto |
258
+ | 3 | Spec | committed + reviewed; UI: chain validated, linter green, scenarios/`SCR-` traced; **COPY and VISUAL are a parallel layer after UX, and where both ran their convergence check is recorded** — a label the layout has no room for is right in each track and wrong on the screen ([`references/stages.md`](references/stages.md)) | manual |
259
+ | 4 | Plan | parallel-ready, DoD per task; **every edge names what it carries** — the fake-edge test run, its `Edges:` count computed, and no arrow left whose payload nobody can name ([`references/planning.md`](references/planning.md)) | auto |
260
+ | 5 | Dev | tasks DONE, TDD green per task, branch integrated per the brief; **a fanned-out group gets one convergence check over all its diffs together before the first worktree lands** — a per-task review cannot see a contradiction that exists only between two of them ([`references/build.md`](references/build.md) §4.2a); **anything generated passes its own checks, and local infrastructure does not publish the host's default ports** ([`references/learned.md`](references/learned.md)) | auto |
257
261
  | 6 | Tests | full suite green; new/changed code covered; **every new check probed both ways and asserted on its exit code**, and the suite run once against a cold environment ; **on a web front end the surface is checked in a browser, not in the diff** — a green suite cannot see a component that renders under a fixed header, a request that 404s past its mock, or a console error — and a browser **test suite** is the other half of the pair, never a substitute for the look (`playwright` or `chrome-devtools`, either one — **how**: [`references/browser.md`](references/browser.md), which channel: [`references/companion-skills.md`](references/companion-skills.md); absent → say *verified by reading the diff* and record it as the weaker claim it is) | auto |
258
262
  | 7 | Lint + deploy | lint clean + suite green before deploy; deploy needs a go (or the brief's specific standing authorization) | manual |
259
263
  | 8 | Post-deploy | clean boot or honest degradation report; **a deployed web target is opened, not curled** — a `200` proves the server answered and says nothing about a 404'd bundle or a console full of errors on load (`playwright` or `chrome-devtools`, either one, [`references/browser.md`](references/browser.md); absent → call it an HTTP response, which is its honest name) | auto |
@@ -78,7 +78,7 @@
78
78
  ],
79
79
  "gate": {
80
80
  "type": "auto",
81
- "check": "SET EQUALITY: the REQ ids in the brief equal the union of Implements: across plan tasks — a non-empty difference fails the gate and is reported as the explicit list of dropped requirements. Plus: every spec requirement maps to a task; no placeholders; names and types consistent across tasks; every task carries a verifiable DoD; parallel-group tasks share no files; UI tasks name the scenario ID(s) and SCR- screen(s) they implement in their DoD"
81
+ "check": "SET EQUALITY: the REQ ids in the brief equal the union of Implements: across plan tasks — a non-empty difference fails the gate and is reported as the explicit list of dropped requirements. Plus: every spec requirement maps to a task; no placeholders; names and types consistent across tasks; every task carries a verifiable DoD; parallel-group tasks share no files; UI tasks name the scenario ID(s) and SCR- screen(s) they implement in their DoD. Every edge in the plan's Execution order table carries a non-empty Carries cell and the self-review's Edges: line is computed — an arrow whose payload nobody can name is a fake edge and the wait behind it is free to give away."
82
82
  }
83
83
  },
84
84
  {
@@ -92,7 +92,7 @@
92
92
  ],
93
93
  "gate": {
94
94
  "type": "auto",
95
- "check": "all plan tasks DONE — the per-task review returns three verdicts (spec compliance, REQ satisfied, code quality); every finding fixed or parked with a written ruling; no task left BLOCKED; full test suite green; the branch integrated per the brief's branch policy (base synced, suite green on the result, worktree removed) — or the operator's explicit 'leave it unmerged' recorded. Every parked finding and implementer concern is harvested into the carry-over ledger before the scratch workspace is deleted."
95
+ "check": "all plan tasks DONE — the per-task review returns three verdicts (spec compliance, REQ satisfied, code quality); every finding fixed or parked with a written ruling; no task left BLOCKED; full test suite green; the branch integrated per the brief's branch policy (base synced, suite green on the result, worktree removed) — or the operator's explicit 'leave it unmerged' recorded. Every parked finding and implementer concern is harvested into the carry-over ledger before the scratch workspace is deleted. Every group that actually fanned out ran its convergence check over all its diffs together BEFORE its first worktree was integrated, and logged a line either way (findings with their ruling, or 'convergence check clean')."
96
96
  }
97
97
  },
98
98
  {
@@ -275,10 +275,52 @@ physical: **two implementers writing one working tree corrupt each other's state
275
275
  **each implementer gets its own isolated worktree**. Then dispatch them together,
276
276
  review each one against its own diff, and integrate the worktrees back to the
277
277
  build branch one at a time, running the suite after each merge.
278
+ - **Prefer a native fan-out primitive where the harness has one**, for the same
279
+ reason `git worktree` defers to a native worktree tool in §1: the harness owns the
280
+ concurrency cap, the per-agent isolation and the resume, and hand-rolling those
281
+ creates state it cannot see or clean up. Where there is none, dispatch by hand
282
+ under the three conditions above. **Never name a specific product here** — the
283
+ keyword for one host's fan-out was renamed six weeks after the article that
284
+ documented it, and doctrine pinned to a vendor's noun rots on that schedule.
278
285
  - **Any conflict on integration** means the plan's file ownership was wrong: stop
279
286
  fanning out, finish the group sequentially, and record it in the ledger.
280
287
  - Never fan out the fix loop — a task under repair belongs to one implementer.
281
288
 
289
+ ### 4.2a The group convergence check — after the group, before integration
290
+
291
+ A per-task review reads **one diff**. A fanned-out group produces several, and the
292
+ defect this stage cannot otherwise see is the one that exists only *between* them:
293
+ each task passes its own review and the group is still incoherent.
294
+
295
+ So after the last task in a fanned-out group reports, and **before** the first
296
+ worktree is integrated, run one check over the group's reports and diffs together.
297
+ It is the only moment all of them exist and none of them has landed.
298
+
299
+ Five things it looks for. Each has been a real defect, and none is visible in a
300
+ single diff:
301
+
302
+ 1. **An empty deliverable** — a task that reported DONE and changed nothing that
303
+ satisfies its REQ.
304
+ 2. **Two outputs that cannot both be true** — the same helper renamed by one task
305
+ and called by its old name in another; two tasks adding the same config key with
306
+ different defaults; two migrations claiming the same version.
307
+ 3. **Off-brief work** — a task that solved a neighbouring problem instead of the one
308
+ its brief named. Its own review passes, because its own diff is coherent.
309
+ 4. **A REQ satisfied twice, differently** — two tasks each implementing the same
310
+ requirement in incompatible ways, which the per-task REQ verdict cannot see
311
+ because each is looking at one task.
312
+ 5. **A shared assumption that only one task acted on** — a Global Constraint one
313
+ implementer applied and another did not.
314
+
315
+ **A finding here is not a per-task fix loop.** It is a group-level decision: pick
316
+ which task is right, send the other back with the ruling, or park both and escalate.
317
+ Log it once, on the group:
318
+ `Group <G>: convergence check — <n> findings (<one-liners>); ruling: <what governs>`.
319
+
320
+ **A clean group logs a line too** — `Group <G>: convergence check clean (<n> tasks)`.
321
+ A check whose silence is indistinguishable from not having run is not evidence, and
322
+ this is the check most likely to be skipped because every task already went green.
323
+
282
324
  ### 4.3 Handle the report
283
325
 
284
326
  | Status | Action |
@@ -520,8 +562,10 @@ stages 7–9 run against an unintegrated branch.
520
562
  ## GATE (auto)
521
563
 
522
564
  All plan tasks DONE with all three review verdicts (spec compliance, REQ satisfied,
523
- code quality); the full test suite green; every open finding either fixed or parked
524
- with a ruling; **every parked finding and implementer concern harvested into the
565
+ code quality); **every group that actually fanned out has run its convergence check
566
+ (§4.2a) before its first worktree was integrated, and logged a line either way** —
567
+ findings with their ruling, or `convergence check clean`; the full test suite green;
568
+ every open finding either fixed or parked with a ruling; **every parked finding and implementer concern harvested into the
525
569
  carry-over ledger** — the workspace is deleted, so nothing may stay only there;
526
570
  no task left BLOCKED; the branch integrated per the brief's policy — or the
527
571
  operator explicitly told you to leave it, and that is recorded. **Every decision a
@@ -541,5 +585,7 @@ a red suite or an unresolved BLOCKED does not advance to stage 6.
541
585
  | "This finding is obviously wrong, drop it" | You adjudicate at the cap, in writing. Silent discards are forbidden. |
542
586
  | "Ledger bookkeeping is overhead" | The ledger is what survives compaction. Without one, controllers re-run entire completed task sequences. |
543
587
  | "Two implementers in parallel will be faster" | One working tree, two writers = corrupted state. Parallel needs one worktree each. |
588
+ | "Every task in the group passed its review, so the group is fine" | A per-task review reads one diff. The defect between two diffs — a rename one task made and another calls by its old name — passes both and lands at integration. §4.2a. |
589
+ | "The convergence check found nothing, no need to log it" | A check whose silence looks identical to not having run is not evidence. The clean line is the record that it ran. |
544
590
  | "I'll paste the earlier tasks so it has context" | A fresh subagent needs its task, its interfaces and the constraints. Pasted history is pure cost. |
545
591
  | "Stage 7 can merge the branch" | Stage 7 lints and deploys what is integrated. An unmerged branch means lint, deploy and docs all ran against something that is not what ships. |
@@ -353,6 +353,23 @@ A register is the one file two agents will write in the same minute.
353
353
  a real state, not a formality. Describing a project as protected while nothing
354
354
  enforces it is worse than having no protection, because everyone downstream
355
355
  believes the guarantee.
356
+ - **A register declared against a backend that cannot reserve is a register in name
357
+ only, and its presence is what stops the manual procedure being written.** Measured
358
+ 2026-08-15: a project declared three id registers over a local-filesystem backend
359
+ whose `reserve` correctly refuses — *pretending would hand two agents the same id* —
360
+ and two sessions filed a different `B-073` on the same afternoon. The tool was honest;
361
+ the *declaration* was the problem, because a capability that appears to exist is one
362
+ nobody replaces. Where reservation is not available, write the three-step manual
363
+ allocation down where an agent reads it, and make it follow the lease:
364
+ 1. take the lease on the register's file **first**;
365
+ 2. compute the next id from the **committed** file — `git show HEAD:<file>` — never
366
+ from a working copy, which holds your own unpushed row and hides somebody else's;
367
+ 3. write and **commit** before releasing the lease. An id held only in an uncommitted
368
+ file is an id nobody else can see you took.
369
+ - **A version number is the same class with no register at all.** Two branches both
370
+ claimed one version on that same afternoon because each incremented from what its own
371
+ checkout knew. Read the remote — `git ls-remote --tags` — because that list is the only
372
+ place the answer lives.
356
373
 
357
374
  ---
358
375
 
@@ -17,6 +17,7 @@ that goes stale when the answer changes.
17
17
  - The knowledge wiki — recommended
18
18
  - How to harvest — retrieval, not reading
19
19
  - Record it — the source ledger
20
+ - The harvest is a convergence, so it needs a check across its sources
20
21
  - The source is not the copy you have
21
22
  - Carried-in claims — measured or recalled
22
23
  - Phase 2 — validate the answers against the harvest
@@ -177,6 +178,38 @@ source nobody will update.
177
178
  **"No sources found" is a valid, recorded outcome.** Write the row. An empty ledger
178
179
  tells the next run that the search happened and came back empty — silence doesn't.
179
180
 
181
+ ## The harvest is a convergence, so it needs a check across its sources
182
+
183
+ Every row above was produced independently — the code does not know what the ADR says,
184
+ the wiki does not know what the graph found. Then all of them arrive at one place, the
185
+ brief, and the interview treats that place as a single answer. **That is a fan-out with
186
+ a convergence, and a convergence trusts its inputs because they arrived**
187
+ (`agent-stack` → `agent-orchestrator/references/graph-engineering.md`, the checker node).
188
+
189
+ Phase 2 below checks each *answer* against the harvest. Nothing checks the **sources
190
+ against each other**, and that is the gap: a doc that contradicts the code produces two
191
+ rows that each look fine, and the run picks whichever it read last.
192
+
193
+ So before the first question, walk the ledger once more and write a **`Contradictions:`**
194
+ line under it. Four things to look for, and they are the checker's five catches with the
195
+ one that cannot apply here dropped:
196
+
197
+ 1. **Empty** — a source that was consulted and said nothing about this task. Already a
198
+ valid row; the point is that it must be *written*, not omitted.
199
+ 2. **Mutually contradictory** — two sources that cannot both be true. Name both rows and
200
+ which one this run will follow, **out loud**, because the operator outranks a document
201
+ only when the override is recorded.
202
+ 3. **Off-topic** — a row that answers a neighbouring question. It inflates the ledger and
203
+ makes the next reader trust a source that was never about this.
204
+ 4. **Stale against a fresher neighbour** — the graph built a week before the code it
205
+ describes, an ADR superseded by a decision nobody linked. This is the one the `Fresh?`
206
+ column exists for and nobody compares.
207
+
208
+ `Contradictions: none` is the answer most runs will write, and writing it is the point:
209
+ a check whose silence is indistinguishable from not having run is not evidence. Anything
210
+ found here is a stage-9 obligation — the ledger's row is what stage 9 goes back and
211
+ fixes.
212
+
180
213
  ## The source is not the copy you have
181
214
 
182
215
  `learned.md` rule 17. The harvest reads the project's own files, and one property of them is
@@ -52,10 +52,53 @@ what each one owns. This is where decomposition gets locked in:
52
52
  into **parallel groups** in topological order, and tag each task
53
53
  `depends: [task ids]`.
54
54
 
55
+ **Then run the fake-edge test over what you just drew.** An edge is a dependency
56
+ that carries data — not two tasks that happen to be written one after the other.
57
+ Six steps, five minutes, and it is the difference between a graph and a list:
58
+
59
+ 1. Write every task as a box.
60
+ 2. Draw an arrow between each pair you were about to order.
61
+ 3. For each arrow ask: **does output from A actually enter B?** Not *"does B come
62
+ after A"* — does B consume a file, a signature, a decision or a value that A
63
+ produced?
64
+ 4. Yes → keep it, and **write the payload in the `Carries` cell**.
65
+ 5. No → delete it. That wait was free to give away, and you were paying for it.
66
+ 6. Everything left with no incoming arrow is group A and starts at once.
67
+
68
+ **The payload is the test, not the answer to it.** An arrow whose `Carries` cell
69
+ you cannot fill is a fake edge, and the empty cell is what makes that visible to
70
+ a reviewer instead of leaving an arrow in place because it looked orderly. Expect
71
+ two or three per plan; the classic is *"review file A, then review file B"*,
72
+ which reads as a sequence and never once passes anything between them.
73
+
55
74
  **File ownership is exclusive within a group.** No two tasks in the same parallel
56
75
  group write the same file — that is the rule that makes stage-5 fan-out safe.
57
76
  Sequential integration/glue tasks sit *between* groups.
58
77
 
78
+ **Distinct is not the same as independent.** Two tasks with different names, in
79
+ different directories, that both write one shared registry — or both take the
80
+ same fixed scratch path — are one task with a race in it. The check is what they
81
+ *touch*, never what they are called.
82
+
83
+ ### This pipeline is a static graph, and that is a decision
84
+
85
+ The stage list is fixed before the run starts, and stays fixed. It could have
86
+ been otherwise: a run that reads its own findings and invents its next stage is
87
+ a real design, and it is the one this pipeline refuses.
88
+
89
+ **The reason is auditability.** A graph that decides its own shape while running
90
+ produces a shape nobody drew, so *"here is the pipeline"* and *"here is what this
91
+ run did"* stop being the same document — and every claim stage 10 makes becomes
92
+ unfalsifiable from the outside. That is the same failure the evidence canons name
93
+ when a number is restated rather than computed.
94
+
95
+ Two places the run *does* discover structure, both bounded and both recorded:
96
+ the **module map** cut at stage 2 ([`decomposition.md`](decomposition.md)), which
97
+ is committed as a file before any module is built, and the **carry-over ledger**,
98
+ which grows but never reorders a stage. Discovery that lands in a committed
99
+ artifact is not a dynamic graph; discovery that changes what runs next, silently,
100
+ is.
101
+
59
102
  ## Task right-sizing
60
103
 
61
104
  A task is the smallest unit that carries its own test cycle and is worth a fresh
@@ -94,14 +137,19 @@ verbatim from the spec. Every task's requirements implicitly include this sectio
94
137
 
95
138
  ## Execution order
96
139
 
97
- | Group | Tasks | Runs after |
98
- |---|---|---|
99
- | A | 1, 2 | — |
100
- | B | 3 | A |
140
+ | Group | Tasks | Runs after | Carries |
141
+ |---|---|---|---|
142
+ | A | 1, 2 | — | — |
143
+ | B | 3 | A | <what crosses this edge: the file, signature, decision or value B consumes> |
101
144
 
102
145
  ---
103
146
  ```
104
147
 
148
+ **The `Carries` cell is required on every edge and empty only on group A.** A cell
149
+ you cannot fill means the arrow carries nothing, which means it is a fake edge:
150
+ delete it and let the task start in the earlier group. This is the fake-edge test
151
+ made a column, so its result is committed rather than remembered.
152
+
105
153
  ## Task structure — required
106
154
 
107
155
  ````markdown
@@ -190,8 +238,11 @@ A checklist you run yourself, inline. No subagent:
190
238
  4. **Name and type consistency:** signatures, property names and types used in
191
239
  later tasks match what earlier tasks defined. `clearLayers()` in Task 3 and
192
240
  `clearFullLayers()` in Task 7 is a bug, not a style difference.
193
- 5. **Parallel safety:** no two tasks in the same group write the same file; every
194
- `depends:` points at a task that really produces what's consumed.
241
+ 5. **Parallel safety and the fake-edge test:** no two tasks in the same group write
242
+ the same file **or share any other mutable target**; every `depends:` points at a
243
+ task that really produces what's consumed, and every edge's `Carries` cell is
244
+ filled. Count the edges you deleted — that number is the line below, and a plan
245
+ that deletes none on its first pass has almost certainly not run the test.
195
246
  6. **DoD present and verifiable** on every task.
196
247
  7. **Every command, path and file a DoD names resolves.** Walk each task's
197
248
  *Definition of done* and its steps and check the targets exist — a DoD that says
@@ -219,6 +270,7 @@ before the gate; every line a **computed number, not a tick**.
219
270
  - Decisions: checked against <the brief's D-table> and <stage 2's rejected options> — <verdict>
220
271
  - Cost: <surfaces>/<guards>/<REQ> now, <…> at stage 2 — <proportionate | grown, and why>
221
272
  - Hygiene: <n> checks, <n> findings, <n> open
273
+ - Edges: <n> declared, <n> carry data, <n> removed
222
274
  - Placeholders: <n> · Ambiguity: <n> found, <n> resolved inline
223
275
  ```
224
276
 
@@ -243,6 +295,9 @@ the explicit list of dropped (or invented) requirements — this seam is where s
243
295
  leaks, so the check is mechanical, never a judgement call.
244
296
 
245
297
  Then: every spec requirement maps to a task; no placeholders; names and types
246
- consistent across tasks; parallel-group tasks share no files; each task has a
247
- verifiable DoD. UI tasks carry their scenario IDs and `SCR-` screens. Verify all of
248
- it yourself and stop on failure this gate has no operator in it.
298
+ consistent across tasks; parallel-group tasks share no files **or other mutable
299
+ target**; each task has a verifiable DoD. **Every edge in the *Execution order*
300
+ table has a non-empty `Carries` cell, and the `Edges:` line of the self-review is
301
+ computed** — an unfillable cell is a fake edge and the gate does not pass with one
302
+ left in the table. UI tasks carry their scenario IDs and `SCR-` screens. Verify all
303
+ of it yourself and stop on failure — this gate has no operator in it.
@@ -194,7 +194,11 @@ never that the work was skipped quietly.
194
194
  an updated `CONTEXT.md` (terms written as they resolved) and any ADRs under
195
195
  `docs/adr/` — see `grill.md` → *Domain awareness*.
196
196
  - **GATE (manual):** shared understanding reached — **the source ledger is written
197
- (every source consulted, or an explicit "none found")**, **where a code graph
197
+ (every source consulted, or an explicit "none found")** and **its `Contradictions:`
198
+ line is written under it** — the harvest is a fan-out that converges on one brief, and
199
+ nothing else compares the sources *with each other*; `Contradictions: none` is the
200
+ answer most runs give and writing it is the point
201
+ ([`knowledge-sources.md`](knowledge-sources.md)), **where a code graph
198
202
  exists its row carries the measured lag and the signal it was measured with — a
199
203
  bare build date does not satisfy this, because it is the graph's own reply rather
200
204
  than a measurement of it** ([`knowledge-graph.md`](knowledge-graph.md) →
@@ -338,6 +342,29 @@ never that the work was skipped quietly.
338
342
  `sheleg-design` how it **looks**. Until 2026-08-10 this stage named only the first,
339
343
  so a run designed a flow, then wrote its strings by taste and picked its values at the
340
344
  keyboard — and every gate in the pipeline reported green over both.
345
+ - **Two of the three are a parallel layer, and the third is their only real dependency.**
346
+ COPY and VISUAL both consume the UX track's scenarios; **neither consumes the other**.
347
+ Copy is written against the brand pack and the scenarios, not against tokens; the visual
348
+ is built from the frame and the style pack, not from strings. Writing them in a line —
349
+ which this file did until 2026-08-15 — teaches a run to wait for a result that never
350
+ arrives. The order is `UX → { COPY ∥ VISUAL }`, and the only thing crossing each of
351
+ those two arrows is **the scenario set**.
352
+ - **Their convergence needs a check, and it has a real contradiction to catch.** Both land
353
+ on the same screen, so the failure is not that one is wrong: it is that each is right
354
+ alone and they disagree together. Before the spec is committed, compare the two outputs
355
+ and record the answer:
356
+ 1. **A string the layout has no room for** — a label, an error or an empty state longer
357
+ than the frame's element, at the frame's own width.
358
+ 2. **A state one track has and the other does not** — copy for an empty state the design
359
+ never drew, or a loading state drawn with no string.
360
+ 3. **Two names for one thing** — the design system's component name against the
361
+ terminology file's noun, where a user reads both.
362
+ 4. **A tone the visual contradicts** — a calm, plain register on a screen whose motion
363
+ and colour say urgency.
364
+ `Tracks converge: clean` is the answer most runs write, and writing it is the point —
365
+ a check whose silence is indistinguishable from not having run is not evidence. This is
366
+ the same rule the harvest applies at stage 0 and the build applies to a fanned-out group
367
+ at stage 5 ([`build.md`](build.md) §4.2a); one shape, three places.
341
368
  - **Spec:** write the approved design to
342
369
  `<artifacts>/specs/YYYY-MM-DD-<topic>-design.md` and commit it. Lock all
343
370
  shared contracts (types, schemas, signatures, file layout). For UI tasks the
@@ -360,6 +387,9 @@ never that the work was skipped quietly.
360
387
  through the COPY track or the refusal is recorded**, and **the visual layer went
361
388
  through the VISUAL track or the refusal is recorded** — a recorded refusal passes
362
389
  this gate and an unmentioned one does not, which is the only difference that matters.
390
+ **Where both tracks ran, their convergence check is recorded** — findings with the
391
+ ruling, or `Tracks converge: clean`; a screen where each track is right alone and they
392
+ disagree together is the defect neither track's own review can see.
363
393
  No plan (stage 4)
364
394
  starts before this — the chain comes BEFORE interface.
365
395
 
@@ -378,7 +408,11 @@ never that the work was skipped quietly.
378
408
  scope leaks silently, so the check is mechanical, not a judgement call. Plus:
379
409
  every spec requirement maps to a task; no placeholders; names and
380
410
  types consistent across tasks; every task carries a verifiable DoD; parallel-group
381
- tasks share no files. For UI tasks: every task building user-facing behavior
411
+ tasks share no files **or other mutable target**; **every edge in the *Execution
412
+ order* table carries a non-empty `Carries` cell and the self-review's `Edges:`
413
+ line is computed** — an arrow whose payload nobody can name is a fake edge and
414
+ the wait behind it is free to give away ([`planning.md`](planning.md)).
415
+ For UI tasks: every task building user-facing behavior
382
416
  names the scenario ID(s) and `SCR-` screen(s) it implements, and its DoD
383
417
  includes satisfying them **and** updating the affected super-ux layers in the
384
418
  same change (super-ux *same-change* rule).
@@ -393,7 +427,12 @@ never that the work was skipped quietly.
393
427
  with an explicit breaker. TDD per task ([`tdd.md`](tdd.md)): failing test →
394
428
  watch it fail → minimal impl → watch it pass → commit. Pin subagents to the
395
429
  run's confirmed model (`model-tiering.md`). The plan's parallel groups fan out
396
- **only** when each implementer gets its own worktree; otherwise sequential.
430
+ **only when all three hold** the tasks share no `depends:`, their file ownership
431
+ is exclusive per the plan, and each implementer gets its own worktree; otherwise
432
+ sequential. A group that did fan out gets **one convergence check over all its
433
+ reports and diffs together, before the first worktree is integrated**
434
+ ([`build.md`](build.md) §4.2a) — a per-task review reads one diff and cannot see a
435
+ contradiction that exists only between two of them.
397
436
  - **Web front end? The task's own surface is checked in a browser, not in the diff.**
398
437
  Where a browser channel is connected — `playwright` **or** `chrome-devtools`, either
399
438
  one, whichever answers first (**how**: [`browser.md`](browser.md); which:
@@ -416,7 +455,9 @@ never that the work was skipped quietly.
416
455
  - **GATE (auto):** **the hygiene gate green in diff mode after every task**
417
456
  (`references/build.md`) — six checks over what that task changed, no floor, and a
418
457
  finding fixed in-task or carried over with a reason; all plan tasks DONE (three review verdicts per task: spec
419
- compliance, **REQ satisfied**, code quality); every finding fixed or parked with a
458
+ compliance, **REQ satisfied**, code quality); **every group that fanned out ran its
459
+ convergence check before its first worktree was integrated, and logged a line either
460
+ way** ([`build.md`](build.md) §4.2a); every finding fixed or parked with a
420
461
  ruling; **every parked finding and implementer concern harvested into the
421
462
  carry-over ledger** — nothing stays only in the scratch workspace, which is
422
463
  deleted; no task left BLOCKED; full test suite green; branch integrated per the brief's policy (or the operator's