task-pipeline-skill 1.29.0 → 1.31.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,270 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.31.0 — the board, and the pointer that was never the one dangling
4
+
5
+ The carry-over ledger has always offered `backlog` as a home for a deferred row — a
6
+ place the pipeline **named and did not own**. The obvious fix was to build that
7
+ backlog. Measuring first changed the target: across ten ledgers in this repository,
8
+ **not one row has ever used that value.** The dangling pointer was never `backlog`. It
9
+ was `open` — **sixteen rows across six ledgers** (later re-measured: **24 rows across eight**), deferred out loud and filed nowhere.
10
+
11
+ `docs/superpowers/backlog.md` is the board: one per project, **mutable** where the
12
+ ledger is append-only, because a queue is re-ranked and a history is not. Rows leave
13
+ only into a *Closed* list, with the commit.
14
+
15
+ **Priority is computed, never assigned:** `prio = sev × blast + age_bonus`, inputs in
16
+ the row and the formula in the doctrine, so a ranking can be **checked** rather than
17
+ trusted. Two consequences are the point rather than side effects: an old small thing
18
+ eventually outranks a new medium one, and a row's priority moves without anyone
19
+ touching it — which is what makes the re-derivation at the end of an iteration real
20
+ work instead of ceremony.
21
+
22
+ **Ten ledgers, six different column shapes.** `#` or `id`; the status column named
23
+ *Home*, *Where it lives now*, *Resolution*, *Status* or *State*. So the guard finds
24
+ columns **by name** — a positional read would have silently checked the wrong cell in
25
+ five files out of six, and passed.
26
+
27
+ Both directions, because they are different failures: a ledger row pointing at an id
28
+ nobody issued, and a board row traceable to nothing. Neither is visible from the other
29
+ side.
30
+
31
+ The seam was closed rather than deferred: all sixteen rows carry a board id, so the
32
+ floor is **zero** and the next unhomed row fails the build instead of joining a debt.
33
+ This repo's own board opens seeded from those ledgers, with several rows that were the
34
+ same finding written separately by different runs — collapsed into one each, which is the
35
+ job the board exists for. The count is deliberately not restated here: it moved twice
36
+ while this entry was being written, which is exactly why this repository deletes
37
+ restated numbers rather than chasing them.
38
+
39
+ **The review found the guard reading the wrong cell in half the corpus.** The status
40
+ column was taken by position — and five of ten ledgers here carry *two* status-ish
41
+ columns (`status`+`home`, `resolution`+`state`), so "take the last one" read a different
42
+ cell per file and passed genuinely open rows in silence. Three more rows carry more
43
+ cells than their header and were skipped outright.
44
+
45
+ The test is now **position-free**: a row is open if any of its cells says so, and homed
46
+ if a board id appears anywhere in it. Neither question asks which column it came from,
47
+ so neither can be defeated by a shape nobody anticipated. It immediately found **eight
48
+ more open rows** — the true count was 24, not 16, and my own measurement was a third
49
+ low. Three of the eight were the same *"evals never run"* finding written by three
50
+ different runs; the board collapses them into one, which is the job it exists for.
51
+
52
+ Two more from the same round: the new stage-0 bullet was spliced into the middle of the
53
+ word *"the"* and `npm test` did not see it, and `templates/backlog.md` — the file seeded
54
+ verbatim into every host project — shipped a worked example that **contradicted the
55
+ formula printed two lines below it**. The arithmetic is now a guard, over both boards.
56
+
57
+ **The class the board logs hit the board itself.** A blank line inside its table split
58
+ three rows off into prose — and row **B-004** on that same board reads *"a blank line
59
+ silently splits a markdown table, and the documentation gate does not catch it."* Second
60
+ instance of a class this repository already had written down, so it became a script
61
+ rather than a third ledger row, swept across the whole corpus. B-004 is closed by the
62
+ check it asked for.
63
+
64
+ **A promise is not a check.** Three doctrine passages described stage-10 resolution as
65
+ keyed on a ledger row homed `backlog`, and the seeded template said outright that *the
66
+ gate refuses it* — while the shipped guard only ever looked for `open`. A reader seeded
67
+ a scratch ledger with exactly that row and watched it pass. Both triggers are enforced
68
+ now, and the doctrine describes what runs.
69
+
70
+ The split-table guard, one round old, could not tell *"table split"* from *"table ends,
71
+ table begins"* — it never checked whether the line after the blank opens its own table,
72
+ which its own comment claimed it did. A **property check** now proves it stays quiet on
73
+ the valid pattern, because a checker with false positives is worse than none.
74
+
75
+ **A tightening that turned the guard off.** Fixing the false positive above, the
76
+ separators were hand-listed — and the list omitted the arrow this repo's own annotations
77
+ use (`open → B-001`), so **all twenty-four resolved rows became invisible** and the
78
+ check passed by seeing nothing at all rather than by finding everything homed. The one
79
+ negative test covering that path caught it, which is the entire argument for the suite
80
+ in a sentence. The separator is now *"not a word character"* and the predicate is proven
81
+ against eight concrete cases instead of one.
82
+
83
+ **The detector reversed three times, and the third answer was in between.** Positional
84
+ read the wrong cell wherever a ledger carried two status columns. Pure-text then broke in
85
+ both directions with one regex — too strict for a live row worded *"open as a printed
86
+ exclusion"*, too loose for a description reading *"Open-source …"*, because a hyphen is
87
+ punctuation exactly like the arrow. What ships reads the header for **candidate columns**
88
+ — all of them, never just the last — and matches a status on a word boundary **inside**
89
+ them. A description cannot masquerade as a status because it is never looked at.
90
+
91
+ Every reversal was found by a reader. None by a probe.
92
+
93
+ **Two surfaces did not know the board exists.** `cursor/rules/task-pipeline.mdc` and
94
+ the command restate stage 0 and stage 10 in detail, and neither mentioned it — the same
95
+ one-rule-in-one-file-of-nine class this repository has a guard for on the rotation axes
96
+ and none for a new mechanism. Both carry it now, the Cursor rule by restatement because
97
+ it is self-contained by contract.
98
+
99
+ **The class that ran through six rounds is closed by computation, not by a sixth fix.**
100
+ Every one of those rounds found the same shape: the doctrine promised a resolution
101
+ trigger the check did not enforce — `open` alone while the prose said `backlog`,
102
+ `backlog` added while `unresolved` was still only promised, *"two triggers"* written in
103
+ a file whose code checked three. `audit.md` says a class seen twice becomes a script, so
104
+ the enumeration is now **extracted from the regex** and required to appear wherever the
105
+ doctrine lists it, in both directions. A guard that loses its own source fails rather
106
+ than passing.
107
+
108
+ **And the seam's origin was the template nobody opened.**
109
+ `templates/carryover.md` — the first ledger every host project ever sees — showed a bare
110
+ `backlog` home as a *settled* outcome, with a worked example carrying no board id. Six
111
+ rounds went into the doctrine, the guard, the board and three consuming surfaces before
112
+ anyone read the file the value came from. It names three unsettled values now, its
113
+ example carries a real id, and the guard checks the template beside the live ledgers.
114
+
115
+ **The guard written to close a false-success class had the class.** It verified that a
116
+ trigger word appeared *somewhere on the page* rather than that it was presented as an
117
+ enabled trigger — so prose reading *"those are the only two triggers"* would pass on the
118
+ strength of the third word appearing in a later paragraph. Scoped to the enumerating
119
+ paragraph now, and what it still cannot decide is written into the code rather than
120
+ implied by its silence.
121
+
122
+ Guards: 156 → **175**, property checks 1 → 4.
123
+
124
+ ## v1.30.0 — the cap that would have measured the wrong axis
125
+
126
+ The audit's last finding was that `references/learned.md` has no retirement rule while
127
+ `retro.md` caps its standing instructions at ten. Four measurements, each with a
128
+ differently-shaped command — the rotation axis shipped one release ago, turned on the
129
+ backlog that proposed it:
130
+
131
+ | The belief | The command | Result |
132
+ |---|---|---|
133
+ | rules accumulate | table rows per tag: 15 → 18 → 21 → 21 | **flat for four releases** |
134
+ | growth *is* rules | words per section, v1.23.0 → HEAD | **+223, every word in the binding map** |
135
+ | the long incidents duplicate the retro | each incident's distinctive tokens against the whole retro corpus | **zero hits** — they are other projects' events, held nowhere else here |
136
+
137
+ A cap of N would have squeezed the axis that had not moved in four releases. A word
138
+ budget would have cut the incidents, which are the only record of those events anywhere
139
+ in this repository. **The finding was right that something was missing and wrong about
140
+ what.**
141
+
142
+ **Why the retro is capped and this file is not**, now written down where the proposal
143
+ keeps arriving: a cap belongs to a file you must *finish reading*. The retro's standing
144
+ instructions are read in full at stage 0 — bounded by construction, or the last one is
145
+ never reached. `learned.md` is entered by citation from twenty-three surfaces and its
146
+ binding map is that entrance. **A file you enter through an index needs its index to be
147
+ right, not its length to be short.**
148
+
149
+ **Two triggers retire a rule, and neither is a count:** the conditions can no longer
150
+ occur in any project the skill runs on, or the rule is **subsumed** — a merge, where the
151
+ absorbing rule names the absorbed one and every binding-map row is repointed in the same
152
+ change. Explicitly *not* "it became a check": that trigger is right for a standing
153
+ instruction, whose job is to be read until the machine takes over, but here the rule is
154
+ the **reason** the check exists, and a check whose reason was deleted is the next thing
155
+ somebody removes as noise.
156
+
157
+ Numbers are never reused and never closed up, so a departure shows as a **gap**, and a
158
+ gap the `### Retired` log does not name now fails the build. The log exists while empty
159
+ and says so — an absent log and an empty one are indistinguishable from outside, and
160
+ only one of them means nothing has been retired.
161
+
162
+ And the file's **shape** is now printed beside the validator's verdict — rules,
163
+ incidents, incident words, binding rows — as a disclosure: computed, no floor, no
164
+ direction, **never a target**. Growth becomes visible without becoming a lever.
165
+
166
+ **The review found a guard that could not catch the thing it was built for.** Deleting
167
+ the **highest-numbered** rule shrinks `max()` with it, so no gap ever opens — reproduced
168
+ by deleting rule 21 and getting a clean pass. My own probe had planted in the middle of
169
+ the list, which is the shape the author imagines. The mark now comes from the file
170
+ (`Numbers issued so far: N`, the one number this file states about itself), and the log
171
+ match is anchored to each line's leading number, because a bare digit anywhere in the
172
+ body — *"subsumed by 9"* — masked a genuine deletion of rule 9.
173
+
174
+ The disclosure was also wrong before it was right: one write-up covers two rules
175
+ (*"4 and 5 · Probes"*), so it printed 18 where the file has 19 write-ups covering 20
176
+ rules. It now says **rules with an incident**, because a label that does not say what
177
+ it counts is a number nobody can check.
178
+
179
+ **Swept the same class one level out.** The cold-retirement guard's corpus was a
180
+ hand-written list of seven files. Made **discovered** instead — any shipped surface that
181
+ states the condition must state both units — and it immediately found **thirteen**,
182
+ including `README.md`, the Cursor rule, `gates.md` and `learned.md` itself. A narration
183
+ of the old wording is exempt by the repository's existing convention: a double-quoted
184
+ span is a citation, and rule 21's incident is left as it happened.
185
+
186
+ **The class turned out to be the module.** Three guards in this file held
187
+ hand-written corpora; all three had missed a shipped surface, and each miss was found
188
+ by a reader or by a sweep rather than by the guard. All three are now **discovered**:
189
+ the cold trigger (7 listed → 13 found), the disclosure verdicts (5 listed → README's
190
+ worked `GATE 10` block found), and the claim registry (widened in v1.29.0 after the
191
+ same shape). *Nobody notices a corpus that is too small, because everything inside it
192
+ passes.*
193
+
194
+ **A high-water mark the same change can lower is not one.** A reader found the residual
195
+ hole and named why a file-only validator cannot see it: the evidence is in the previous
196
+ commit. Deleting rule 21 *and* editing the mark to 20 makes both numbers agree and no
197
+ gap opens. The check now reads the mark at `HEAD` through git — and where git cannot
198
+ answer, it **prints** the skip as an `unlooked` disclosure rather than going quiet,
199
+ because a check that falls silent outside a checkout reads exactly like a check that
200
+ passed. That assertion is a property, not a rejection, so it lives outside the negative
201
+ suite: the suite requires each of its tests to watch a guard *reject* something, and it
202
+ reported the mismatch itself.
203
+
204
+ **A citation naming two guards was checked as zero.** The extractor matched a single
205
+ backtick span followed by `)*`, so invariant 43 — the one this release added, naming
206
+ two — contributed nothing to the checked set and sat silently exempt from the invariant
207
+ that every citation must resolve. Found by a reader, on the invariant whose subject is
208
+ exactly that. Every literal inside a `*(guard: …)*` parenthetical is now checked, and
209
+ one branch that had shipped unprobed (a rule numbered above the mark) now has its plant.
210
+
211
+ The two discovered corpora also collapsed into one `_discover_md(skip, predicate)` —
212
+ second occurrence of the walk, and this file's own rule promotes a class at the second,
213
+ not the third.
214
+
215
+ **A check that only worked in the window its own test used.** The cross-commit
216
+ comparison read the mark at `HEAD` — identical to the working tree on a *committed*
217
+ checkout, which is what CI runs. It fired only in the local pre-commit window, and the
218
+ self-test exercised exactly that window. A reader committed the coordinated edit and
219
+ watched it pass. The mark is now compared against **every value this file's history has
220
+ held**, and the test commits its plant.
221
+
222
+ Two more from the same round: emptying the rules table wholesale made the list falsy and
223
+ skipped the entire check (`rules 0`, PASS), and `docs/superpowers/plans/` was missing
224
+ from the frozen-record exclusions its two siblings carry.
225
+
226
+ **CI caught what the local gate could not see.** Renaming the skip's message left the
227
+ property check grepping for the old string — and that step lives only in the workflow,
228
+ because it asserts something *is printed* rather than watching a guard reject. The local
229
+ runner had no way to run it, so `npm run test:all` was green while CI failed on a string
230
+ this same change had renamed. `test/negatives.py` now runs property checks too: a step
231
+ that exists only in CI is a step the author's gate is blind to.
232
+
233
+ **Both directions, on the guard about both directions.** The check found gaps the log
234
+ did not name, and never the reverse: a number *listed as retired* whose row is still in
235
+ the table — the log naming the wrong rule, or a retired rule quietly back. A gap has one
236
+ side and so does a resurrection; only the second pass sees it. This is `learned.md`'s own
237
+ rule 2 applied to the guard that guards `learned.md`.
238
+
239
+ **And the number the exclusion moved.** Adding `docs/superpowers/plans/` to the frozen
240
+ records took the discovered corpus from fourteen to thirteen, and two documents kept the
241
+ old number — so the corpus's own size is now a claim-registry class, computed from the
242
+ walk. The registry's evaluation moved below the walks that feed it: declared early,
243
+ computed late.
244
+
245
+ The local runner also learned to treat property checks as their own category — selected
246
+ by `-k`, shown by `--list`, and reported under their own heading rather than as *"the
247
+ validator accepted a planted defect"*, which is false for a check that plants nothing.
248
+ Its verdict now names what ran: `PASS: all 0 guards` over an empty selection is the
249
+ refused measurement this repository has a rule about.
250
+
251
+ **A probe that could no longer fail for its own reason.** The anchor test planted a log
252
+ line whose *body* held the digit it was about — and the high-water mark lives in that
253
+ same section, so a bare-digit scan picked up `21`, tripped a different check, and the
254
+ test failed either way. It could not tell a working anchor from a regressed one, which a
255
+ reader demonstrated by reverting the anchor. The mark is now stripped before the log is
256
+ parsed (it was never a log entry), the plant uses a number that is not a live row, and
257
+ reverting the anchor makes the test pass again — which is what "this test measures that"
258
+ means.
259
+
260
+ Two more of the same family: the rule-row regex existed twice, byte-identical, one for
261
+ the registry and one for the disclosure — rule 8's class inside the file that enforces
262
+ it, now one pattern with two readers. And property checks got the floor the negative
263
+ suite has had for releases: rename the only one and the category empties in silence,
264
+ which is the failure property checks were added to close, one level up.
265
+
266
+ Guards: 144 → **156**, plus 1 property check now covered locally.
267
+
3
268
  ## v1.29.0 — a sixth axis, and three summaries that each read as complete
4
269
 
5
270
  **Re-derivation** joins the rotation axes in `references/audit.md`: take a number the
package/CONTRIBUTING.md CHANGED
@@ -349,7 +349,44 @@ claim-registry classes, separated by the qualifier, so a guard cannot report dri
349
349
  between two things that were never the same list.
350
350
  *(guard: `gates.md's own axes`)*
351
351
 
352
- **41. Every invariant above names the guard that enforces it, and that guard exists.**This list claims to be *what the validator enforces*; it was eight guards behind when
352
+ **41. A rule leaves `learned.md` only on a logged line, and the log exists while
353
+ empty.** Numbers are never reused and never closed up, so a departure shows as a gap in
354
+ the table; a gap the `### Retired` log does not name is a rule that vanished with its
355
+ incident, and the next run re-learns it at full price. The log is present and says
356
+ *none yet*, because an absent log and an empty one look identical from outside.
357
+ *(guard: `named in no line of`)*
358
+
359
+ **42. `learned.md` has no cap, and the reason is written down where the cap keeps being
360
+ proposed.** A cap belongs to a file read *in full* every run — that is the retro's
361
+ standing instructions. This one is entered by citation from twenty-three surfaces.
362
+ Measured before deciding: rules flat at 21 across four releases while the file grew, and
363
+ every word of the growth in the binding map. Its shape prints beside the verdict as a
364
+ **disclosure** — computed, no floor, no direction, never a target.
365
+ *(guard: `learned.md — rules `)*
366
+
367
+ **43. A guard's corpus is discovered, not hand-listed.** Three in `test/validate.py`
368
+ held written lists, and all three had missed a shipped surface: the cold trigger named
369
+ seven files where thirteen state the condition, the disclosure check named five where
370
+ `README.md` also prints a worked verdict, and the claim registry excluded the Cursor
371
+ rule and the command. Every miss was found by a reader or a sweep, never by the guard —
372
+ **nobody notices a corpus that is too small, because everything inside it passes.**
373
+ Exclusions are allowed and must each carry a reason in the code: a changelog narrates
374
+ old formats, `docs/superpowers/specs/` are point-in-time records.
375
+ *(guard: `a worked GATE verdict omits ` and `states the cold-retirement condition as` — both now run over corpora walked from disk, so a new surface joins by existing)*
376
+
377
+ **44. A carry-over row still `open` names a board id, and the board row names it back.**
378
+ `docs/superpowers/backlog.md` is the project's queue between runs; the ledger's `open`
379
+ was a home that pointed nowhere, and rows across eight ledgers sat in it — sixteen by the first, positional count, twenty-four once the check stopped reading by column. Both
380
+ directions are checked because they are different failures — an id nobody issued, and a
381
+ row traceable to nothing. The test is **position-free**: a row is open if any of its cells
382
+ says so, and homed if a board id appears anywhere in it. Neither question asks which
383
+ column the value came from — ten ledgers here carry six header shapes and **five of them
384
+ have two status-ish columns**, so both a positional read and a by-name read pick a
385
+ different cell per file and pass open rows in silence. Reading by name was the first
386
+ design and it was wrong for the same reason.
387
+ *(guard: `with no board id` and `names no Source`)*
388
+
389
+ **45. Every invariant above names the guard that enforces it, and that guard exists.**This list claims to be *what the validator enforces*; it was eight guards behind when
353
390
  an audit measured it. A claim of enforcement is now checked like any other claim.
354
391
  *(guard: `whose message does not appear in`)* — and a cited literal must lie inside
355
392
  a **single** string in `test/validate.py`: the check reads that file as text, so a
package/README.md CHANGED
@@ -129,6 +129,7 @@ until it is installed.
129
129
  | 10 + any audit | [`audit.md`](plugins/task-pipeline/skills/task-pipeline/references/audit.md) — the L0→L7 ladder and its seams, axis rotation, ratchets, proven checks |
130
130
  | any loop | [`loop-guard.md`](plugins/task-pipeline/skills/task-pipeline/references/loop-guard.md) — churn detection, caps, the break protocol |
131
131
  | run-wide pacing | [`continuity.md`](plugins/task-pipeline/skills/task-pipeline/references/continuity.md) — the loop mode (`run.loop`, default off, never collapses a manual gate) and the context budget (fires on a harness signal, never on an estimate) |
132
+ | the board between runs | [`backlog.md`](plugins/task-pipeline/skills/task-pipeline/references/backlog.md) — the work-list that outlives a run: seeded at stage 0 or picked up, priority **computed** from three stated inputs rather than assigned, and the seam where the carry-over ledger's `backlog` pointer finally resolves |
132
133
  | 0 + 9 + any settled decision | [`documentation.md`](plugins/task-pipeline/skills/task-pipeline/references/documentation.md) — the inventory, registers and ids, SSOT, the Doc Loop, supersede semantics, the propagation matrix, intent vs as-built |
133
134
  | 3 + 4 · every spec and plan | the self-review reads its rules back — is every named check real, does anything contradict a locked decision or a rejected option, and what does this cost now versus at design time. Answers land as a committed `## Self-review` of computed numbers |
134
135
  | 6–10 + any check you write | [`gates.md`](plugins/task-pipeline/skills/task-pipeline/references/gates.md) — the three axes, the promotion ladder, gate anatomy, the probe recipe, ratchet floors |
@@ -576,8 +577,10 @@ The last act of stage 10 is therefore a **retrospective**, written to
576
577
  `docs/superpowers/retro.md` — **one file per project, not per run**
577
578
  ([`retrospective.md`](plugins/task-pipeline/skills/task-pipeline/references/retrospective.md)).
578
579
  Every run **stamps and prunes** — in that order, because one retirement trigger
579
- counts firings across the last five run stamps and a prune ahead of the stamp reads a
580
- counter its own stage writes afterwards. Only a run that *diverged* writes an entry:
580
+ counts firings across the last five run stamps **or sixty days**, whichever comes
581
+ first, and a prune ahead of the stamp reads a counter its own stage writes afterwards.
582
+ The calendar is the unit that keeps moving when the stamp counter has stopped, which is
583
+ exactly when a stale rule matters most. Only a run that *diverged* writes an entry:
581
584
  symptom with evidence, the stage it surfaced at, the stage that **owned** it, the
582
585
  root cause, the fix, and the check that catches it the first time from now on.
583
586
 
@@ -593,7 +596,7 @@ root cause, the fix, and the check that catches it the first time from now on.
593
596
  Every standing
594
597
  instruction is checked against three retirement triggers — *it became a check* ·
595
598
  *every path or command it names is gone* · *it has not fired in the last five run
596
- stamps* — and the list is held to a **hard cap of ten**. At eleven, the oldest
599
+ stamps, or in sixty days* — and the list is held to a **hard cap of ten**. At eleven, the oldest
597
600
  never-fired rule goes; "but they all matter" is exactly the state in which the list
598
601
  stopped being read, and the ninth stale rule is what discredits the two that are
599
602
  load-bearing.
@@ -606,6 +609,7 @@ visible where it happened:
606
609
  ```
607
610
  GATE 10 acceptance: PASS — 14/14 REQ verified
608
611
  carry-over: 0 unresolved · retro: 7 standing (was 9) · retired 3 · added 1
612
+ abstained: 1 (1 cannot-verify) · unlooked: 0
609
613
  ```
610
614
 
611
615
  Stage 0 reads those standing instructions **in full** on the next run — which is
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.29.0 |
15
+ | **Version** | 1.31.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. 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` |
@@ -174,6 +174,13 @@ Seed a **carry-over ledger** next to the brief, append-only. Anything deferred,
174
174
  dropped, or left half-done goes in the moment it's said — including implementer
175
175
  concerns and non-blocking review findings. **Deferred out loud is forgotten.**
176
176
 
177
+ Read **the board** — `docs/superpowers/backlog.md`, the project's work-list *between*
178
+ runs — and quote its open count in the brief; seed it from the template when absent. Its
179
+ priority is **computed, never assigned**: `prio = sev × blast + age_bonus`, with the
180
+ three inputs in the row so a ranking can be checked rather than trusted, and `age` moving
181
+ on its own is why the re-derivation at an iteration's end is real work. Where the ledger
182
+ records what *this run* deferred, the board is what the project still owes.
183
+
177
184
  Stop when shared understanding is reached (every branch answered or explicitly
178
185
  deferred) and lock the answers into a short brief the user confirms. Only then
179
186
  start. Later stages read that brief instead of asking again.
@@ -334,11 +341,20 @@ not authorize an outward, irreversible action — stage 7 stops and asks.
334
341
  per project, not per run**. Every gate above is good at *this* run and blind
335
342
  across runs: the same class of failure gets caught, fixed and forgotten five
336
343
  times and nothing notices it is the same one. So, in this order —
344
+ **0. Resolve the ledger onto the board first.** Every carry-over row still
345
+ `open`, `unresolved`, or homed `backlog` leaves with a `B-NNN` id on
346
+ `docs/superpowers/backlog.md`, and the ledger row is updated to name it — both
347
+ directions, because a pointer to an id nobody issued and a board row traceable to
348
+ nothing are different failures. Then re-derive the board's priorities: `age` moved
349
+ while the run was happening.
350
+
337
351
  **1. Stamp the run first** — date, topic, commit, verdict, counts. It costs one
338
352
  line and it is the only thing that makes the next step computable: one retirement
339
- trigger counts firings across the last five run stamps, so a prune placed ahead of
340
- the stamp reads a counter its own stage writes afterwards and can never run on real
341
- data.
353
+ trigger counts firings across the last five run stamps **or sixty days**, whichever
354
+ comes first, so a prune placed ahead of the stamp reads a counter its own stage
355
+ writes afterwards and can never run on real data. The calendar unit exists because
356
+ the stamp counter only moves when this pipeline runs — ten consecutive releases of
357
+ this repository carry no stamp at all.
342
358
  **2. Then prune.** Check every standing instruction against three
343
359
  retirement triggers: it became a check (a test, a lint rule, a gate, a hook →
344
360
  delete it; the check is the memory, and keeping both means it is read twice and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-pipeline-skill",
3
- "version": "1.29.0",
3
+ "version": "1.31.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 must close with evidence, a loop guard that breaks churn, one provider-agnostic model confirmed up front, and an optional super-ux UX track for user-facing work.",
5
- "version": "1.29.0",
5
+ "version": "1.31.0",
6
6
  "author": {
7
7
  "name": "ssheleg",
8
8
  "url": "https://x.com/sshlg93"
@@ -30,8 +30,8 @@ grill's **domain awareness** (challenge terms against `CONTEXT.md`, sharpen fuzz
30
30
  language, ADRs for hard-to-reverse calls) and covering the **autonomy sweep** (what
31
31
  would otherwise stop stages 1→10: docs sources incl. doc repos, the wiki and the code graph, branch/tracker
32
32
  policy, test and lint commands, deploy target and authorization, log locations, docs/wiki/graph targets, and for UI tasks whether the design is done visually in Figma or text-only, whether the Figma MCP is connected, and — if it isn't — whether to ship text-only or stop and connect it, since the UX chain degrades on its own and never blocks; **and with Figma on, the design destination: which team/org by name and which file** — the recorded one, a URL the operator gives, or creation in that named team explicitly authorized, written into the project's canonical record before the first frame, because a destination decided at drawing time is how a project ends up with three design files and no way to tell which is real. **Never create while a recorded file resolves; if it doesn't resolve, stop and ask — never create a replacement**) —
33
- until the brief is locked — including the **REQ table**, the request as an addressable list where every row names how it is verified — so the rest runs autonomously and the final stage can account for all of it. The list is frozen: adding is free, removing needs the operator's agreement. Anything deferred goes into the carry-over ledger the moment it's said. For any user-facing task, recommend/use
34
- **super-ux**. **If the brief describes a platform rather than a change**, stage 2 also cuts it into modules (`references/decomposition.md`) — module map committed, walking skeleton first, every REQ in exactly one module — and stages 3→10 then run per module, one brick at a time. **If any loop starts undoing an earlier pass** (same file edited twice for the same reason, a closed finding returning, a third entry into one stage), stop and run the loop guard (`references/loop-guard.md`): name both shapes, escalate to the layer that owns the conflict, re-plan the check as an ordered list, then go item by item. **The closing stage opens with the ladder walk** (`references/audit.md`): the REQ table finds what was named and lost, but a comparison needs two sides and an absence has one — so walk each REQ bottom-up through its rungs (decision → spec section → contract *and its failure behavior* → task → change → executed test → surface/docs), check the seam at each step, order findings by seam rather than by file, and turn every absence into a new REQ row **before** the coverage table is written. A green from a check nobody has watched fail against a planted defect is not evidence; a finding class seen twice becomes a script rather than a third ledger row; and the carry-over ledger's counts are printed beside every gate verdict, so "green" never reads as "verified". If a searching pass starts finding mostly what the previous pass's own fixes broke, the axis is exhausted — rotate it, don't look harder. **The docs stage closes three artifacts, not two:** module docs, the wiki, **and the code graph** (`/graphify . --update` where `graphify-out/` exists — `references/knowledge-graph.md`), because the graph is what the next run's harvest queries first and a stale one is a false premise carrying the authority of a machine. Then check the graph against the docs: a hub `graphify god-nodes` reports that no document names is an undocumented seam; an edge the docs deny is a leak in the code or a lie in the docs; a doc naming a module the graph no longer has is stale. Doc-side findings are fixed there, absences become REQ rows in the closing stage. **In a project of several repositories, stage 10 closes on the parent too:** a parent records each submodule as a pointer to one commit, and moving the submodule does not move the pointer — so the work can be committed, pushed and green while a clone of the parent still gets the commit before it. Neither repo looks wrong alone, which is why it survives every check that runs inside one. Require `git submodule status` with no line starting `+`, and every repo clean and pushed (`git -C <repo> status --porcelain`, `git -C <repo> log @{u}..HEAD`). The fix is two commands and the second gets forgotten: push the submodule, then `git add <submodule> && git commit`. **The run's last act is the retrospective** (`references/retrospective.md` → `docs/superpowers/retro.md`, one file per project): **stamp the run first** (its commit is what makes the cold-retirement trigger computable), **then prune** — every standing instruction against its three retirement triggers (it became a check; the paths/commands it names are gone; it hasn't fired in five run stamps, or in sixty days — the calendar is the unit that still moves when the stamp counter has stopped), the list held to a hard cap of ten, every deletion logged as one line and never silent — then, only if the run diverged, write the entry: symptom with evidence, the stage it surfaced at, the stage that *owned* it, the root cause, the fix by grade (mechanical check > standing instruction > note that expires in two runs), and the check that catches it next time. Stage 0 reads those standing instructions in full, so the prune is a gate criterion, not a good intention: a rule nobody reads to the end is worse than no rule, because everyone believes it is covered. Honor every stage gate by its type (`auto` = verify yourself;
33
+ until the brief is locked — including the **REQ table**, the request as an addressable list where every row names how it is verified — so the rest runs autonomously and the final stage can account for all of it. The list is frozen: adding is free, removing needs the operator's agreement. Anything deferred goes into the carry-over ledger the moment it's said, and **the board** (`docs/superpowers/backlog.md`, `references/backlog.md`) — the work-list between runs — is read at stage 0 with its open count quoted in the brief, or seeded when absent. For any user-facing task, recommend/use
34
+ **super-ux**. **If the brief describes a platform rather than a change**, stage 2 also cuts it into modules (`references/decomposition.md`) — module map committed, walking skeleton first, every REQ in exactly one module — and stages 3→10 then run per module, one brick at a time. **If any loop starts undoing an earlier pass** (same file edited twice for the same reason, a closed finding returning, a third entry into one stage), stop and run the loop guard (`references/loop-guard.md`): name both shapes, escalate to the layer that owns the conflict, re-plan the check as an ordered list, then go item by item. **The closing stage opens with the ladder walk** (`references/audit.md`): the REQ table finds what was named and lost, but a comparison needs two sides and an absence has one — so walk each REQ bottom-up through its rungs (decision → spec section → contract *and its failure behavior* → task → change → executed test → surface/docs), check the seam at each step, order findings by seam rather than by file, and turn every absence into a new REQ row **before** the coverage table is written. A green from a check nobody has watched fail against a planted defect is not evidence; a finding class seen twice becomes a script rather than a third ledger row; and every ledger row still `open`, `unresolved` or homed `backlog` leaves stage 10 with a board id and the board's priorities are re-derived (`references/backlog.md`); the carry-over ledger's counts are printed beside every gate verdict, so "green" never reads as "verified". If a searching pass starts finding mostly what the previous pass's own fixes broke, the axis is exhausted — rotate it, don't look harder. **The docs stage closes three artifacts, not two:** module docs, the wiki, **and the code graph** (`/graphify . --update` where `graphify-out/` exists — `references/knowledge-graph.md`), because the graph is what the next run's harvest queries first and a stale one is a false premise carrying the authority of a machine. Then check the graph against the docs: a hub `graphify god-nodes` reports that no document names is an undocumented seam; an edge the docs deny is a leak in the code or a lie in the docs; a doc naming a module the graph no longer has is stale. Doc-side findings are fixed there, absences become REQ rows in the closing stage. **In a project of several repositories, stage 10 closes on the parent too:** a parent records each submodule as a pointer to one commit, and moving the submodule does not move the pointer — so the work can be committed, pushed and green while a clone of the parent still gets the commit before it. Neither repo looks wrong alone, which is why it survives every check that runs inside one. Require `git submodule status` with no line starting `+`, and every repo clean and pushed (`git -C <repo> status --porcelain`, `git -C <repo> log @{u}..HEAD`). The fix is two commands and the second gets forgotten: push the submodule, then `git add <submodule> && git commit`. **The run's last act is the retrospective** (`references/retrospective.md` → `docs/superpowers/retro.md`, one file per project): **stamp the run first** (its commit is what makes the cold-retirement trigger computable), **then prune** — every standing instruction against its three retirement triggers (it became a check; the paths/commands it names are gone; it hasn't fired in five run stamps, or in sixty days — the calendar is the unit that still moves when the stamp counter has stopped), the list held to a hard cap of ten, every deletion logged as one line and never silent — then, only if the run diverged, write the entry: symptom with evidence, the stage it surfaced at, the stage that *owned* it, the root cause, the fix by grade (mechanical check > standing instruction > note that expires in two runs), and the check that catches it next time. Stage 0 reads those standing instructions in full, so the prune is a gate criterion, not a good intention: a rule nobody reads to the end is worse than no rule, because everyone believes it is covered. Honor every stage gate by its type (`auto` = verify yourself;
35
35
  `manual` = wait for explicit go). Confirm the **model once at preflight** —
36
36
  recommend the most capable one the environment offers, never a hardcoded id — then
37
37
  run the whole pipeline on it without re-asking.
@@ -68,6 +68,7 @@ gate stops until it is installed.
68
68
  | **what travels with the bundle vs stays in a project** | [`references/portability.md`](references/portability.md) |
69
69
  | any repeating loop | [`references/loop-guard.md`](references/loop-guard.md) |
70
70
  | run-wide · how a run keeps going (the loop mode + the context budget) | [`references/continuity.md`](references/continuity.md) |
71
+ | run-wide · the work-list **between** runs, and how its priority is computed | [`references/backlog.md`](references/backlog.md) |
71
72
 
72
73
  **Optional bridge.** If the operator already runs an equivalent skill set (e.g.
73
74
  `superpowers:brainstorming` / `writing-plans` / `subagent-driven-development` /
@@ -76,7 +76,7 @@ whatever the context happens to hold.
76
76
 
77
77
  | Stage | Reads | From where |
78
78
  |---|---|---|
79
- | **0 Harvest** | the project's own knowledge about this task | code · the code graph (`graphify-out/`) · `CLAUDE.md`/`AGENTS.md` · `CONTEXT.md`/`docs/adr/` · `docs/` + `docs/ux/` · past briefs and carry-over ledgers · the retro's standing instructions **in full** · the wiki · any doc repo or hosted system the project names |
79
+ | **0 Harvest** | the project's own knowledge about this task | code · the code graph (`graphify-out/`) · `CLAUDE.md`/`AGENTS.md` · `CONTEXT.md`/`docs/adr/` · `docs/` + `docs/ux/` · past briefs and carry-over ledgers · **the board** (`docs/superpowers/backlog.md`, open count quoted in the brief) · the retro's standing instructions **in full** · the wiki · any doc repo or hosted system the project names |
80
80
  | **0 Inventory (1b)** | the documentation regime | `docs/DOCMAP.md` — registers, single homes, propagation matrix, gate commands, ratchet floors. Absent ⇒ seeded ([`adoption.md`](adoption.md)) |
81
81
  | **0 Reconcile (1c)** | intent vs as-built | git (how it *should* be) against the run record (how it *turned out*) |
82
82
  | **0 Grill** | the operator | the interview — every answer checked against the harvest, which is what makes it checkable rather than confident |
@@ -100,6 +100,7 @@ that has not read them is running the pipeline's defaults, not this project's.
100
100
  |---|---|---|---|
101
101
  | `CLAUDE.md` / `AGENTS.md` | commands, deploy path, house rules, which docs exist and where | 0 | 6–10 |
102
102
  | `docs/DOCMAP.md` | the decision home, each fact's single home, the propagation matrix, the gate and its ratchet floors | 0 (1b) | 9 |
103
+ | `docs/superpowers/backlog.md` | the project's work-list **between** runs — ids, the three priority inputs, state. Mutable; rows leave only into its *Closed* list ([`backlog.md`](backlog.md)) | 0 | re-derived at every iteration's end; resolved at 10 |
103
104
  | `docs/superpowers/retro.md` | standing instructions — the rules no check can decide. Capped at ten, **read in full**, stamped the moment one fires | 0 | pruned at 10 |
104
105
  | `specs/<topic>-brief.md` → *Autonomy* | every pre-resolved decision; stages 1→10 **answer from it instead of asking** | 0 | 1–10 |
105
106
  | `specs/<topic>-carryover.md` | everything deferred, parked or half-done; appended the moment it is said | all | read in full at 10 |
@@ -124,6 +125,7 @@ them is a finding, not a tie-break ([`knowledge-sources.md`](knowledge-sources.m
124
125
  | 0 Inventory | `docs/DOCMAP.md` + the registers + `scripts/check-docs.sh` — seeded **only when absent**, and the seeding is the register's first entry ([`documentation.md`](documentation.md)) | every later stage; **stage 9** walks the matrix, **stage 10** proves the gate |
125
126
  | 0 Grill (domain) | `CONTEXT.md`, `docs/adr/NNNN-<slug>.md` — created **lazily**, only when a term resolves or a decision qualifies. Where `docs/adr/` **is** the register, entries carry the register's field set | stages 2–4 + the repo |
126
127
  | any stage | a register entry per settled thing, via the **Doc Loop** — recorded, resolved, propagated, committed with its id | the next run's harvest |
128
+ | 10 Board resolution | `docs/superpowers/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 |
127
129
  | 10 Retro rotation | `docs/superpowers/retro/YYYY-QN.md` — entries older than five stamps, plus every retirement, each with its commit | queried by a later run's harvest |
128
130
  | 2 Decompose | `specs/<topic>-modules.md` — module map, build order, contracts, per-module status (platforms only) | stages 3–10, every module's run |
129
131
  | 3 Spec | `specs/<topic>-design.md` — module dossier for a decomposed platform (+ links `docs/ux/*` for UI) | stage 4 |
@@ -0,0 +1,109 @@
1
+ # The board — the work-list that outlives a run
2
+
3
+ **One job: give the thing every loop already talks about a file to be.**
4
+ [`continuity.md`](continuity.md) says each iteration must re-measure the work-list, and
5
+ that *"next up is X"* at the end of an iteration is a claim about the board that no
6
+ gate reads. Until this file existed, there was no board — the claim had nothing to be
7
+ checked against.
8
+
9
+ **Boundary, so this does not become a second ledger.** The carry-over ledger
10
+ ([`templates/carryover.md`](../templates/carryover.md)) records what **one run**
11
+ deferred and closes at that run's stage 10. The board is the **project's queue**: it
12
+ outlives runs, it is re-prioritised, and rows leave it only by closing. They meet at
13
+ exactly one seam, and that seam already existed as a dangling pointer.
14
+
15
+ ---
16
+
17
+ ## Contents
18
+
19
+ - The seam the ledger left open
20
+ - Seeded or picked up
21
+ - Priority is computed, not assigned
22
+ - What a loop iteration does with it
23
+ - Rationalizations
24
+
25
+ ## The seam the ledger left open
26
+
27
+ The ledger's last column is *Where it lives now*, and one of its legal values is
28
+ `backlog` — a place the pipeline named and did not own. So the honest reading of a
29
+ finished run was: *"deferred, and filed somewhere nobody here can point at."*
30
+
31
+ **Stage 10 resolves it, on three triggers.** A row is unresolved if its home reads
32
+ `backlog` — the value that pointed nowhere — or `open`, which is what the rows in this
33
+ repository actually said, or `unresolved`, which
34
+ [`templates/carryover.md`](../templates/carryover.md) calls the canonical not-done value
35
+ and says outright blocks this gate. Each of the three was, at some point, described in
36
+ the doctrine and absent from the check; a reader found each by seeding the shape and
37
+ watching it pass. **The trigger list is the one part of this file that must be read
38
+ against the code, not beside it.**
39
+
40
+ - every unresolved ledger row has a board id, and that id exists on the board;
41
+ - every board row sourced from a ledger names the ledger row it came from.
42
+
43
+ One direction alone is not enough, and this repository has a rule about that
44
+ ([`learned.md`](learned.md) rule 2): a ledger row pointing at a board id that was never
45
+ created and a board row invented with no source are **different failures**, and only a
46
+ pass in each direction finds both.
47
+
48
+ ## Seeded or picked up
49
+
50
+ Stage 0's harvest reads `docs/superpowers/backlog.md` when it exists — it is a source
51
+ in the ledger like any other, and its **open count is quoted in the brief**, because a
52
+ run that begins without knowing what is already queued will cheerfully re-discover it.
53
+
54
+ When the file is absent, stage 0 seeds it from
55
+ [`templates/backlog.md`](../templates/backlog.md) and says so in the brief. Seeding is
56
+ not a decision that needs asking: an empty board and no board are the same thing to
57
+ work on, and only one of them can be appended to.
58
+
59
+ **A picked-up board is re-measured, not recalled.** Its open count comes from a command
60
+ at the top of the run, not from what the last run's report said — the previous run is
61
+ precisely what invalidated that number ([`learned.md`](learned.md) rule 16).
62
+
63
+ ## Priority is computed, not assigned
64
+
65
+ ```
66
+ prio = sev × blast + age_bonus sev, blast ∈ 1..3
67
+ age_bonus = 1 past 14 days, 2 past 30
68
+ ```
69
+
70
+ **The inputs live in the row and the formula lives here**, so a ranking can be checked
71
+ rather than trusted. That is the whole reason the number is built this way: a
72
+ hand-assigned priority is an opinion wearing a number's clothes, and the one thing
73
+ nobody can audit is the number that was simply typed.
74
+
75
+ Two consequences worth stating, because both are the point:
76
+
77
+ - **An old small thing eventually outranks a new medium thing.** That is deliberate. A
78
+ row that has sat for a month is a row the queue has been lying about.
79
+ - **A row's priority changes without anyone touching it**, because age moves. The
80
+ re-derivation at the end of an iteration is therefore not busywork — it is the only
81
+ moment the board stops being stale.
82
+
83
+ `sev` and `blast` are judgement, and they are written down *as* judgement: two small
84
+ integers a reader can disagree with, rather than a ranking they can only accept.
85
+
86
+ ## What a loop iteration does with it
87
+
88
+ At the **top** of an iteration, one command: count the open rows and read the top few.
89
+ At the **bottom**, re-derive `age` and `prio`, apply anything the iteration itself
90
+ added, and take the highest-priority row.
91
+
92
+ **A row added mid-iteration may outrank the row being worked on.** That is the case the
93
+ board exists for, and it is not a reason to abandon the current item: finish the item at
94
+ hand to its gate — `continuity.md` defines an iteration as *one item taken to its gate*
95
+ — then take the new top. Switching mid-item is how a loop ends with three half-finished
96
+ things and no gate passed on any of them.
97
+
98
+ **The report cites the file.** *"Next up: B-014"* is checkable; *"next up: the export
99
+ fix"* is not, and it is the one sentence in an iteration that no gate reads.
100
+
101
+ ## Rationalizations
102
+
103
+ | The excuse | What is actually true |
104
+ |---|---|
105
+ | "The tracker is the backlog, this duplicates it" | Then the `Home` column carries the tracker id and this file is an index with the priority inputs visible. What it replaces is nothing; what it adds is a board an agent can read at 3am without credentials. |
106
+ | "I'll re-prioritise when it matters" | Age moves on its own. A board re-prioritised only when someone remembers is ranked by when they last remembered. |
107
+ | "The row is obvious, it doesn't need a source" | Six weeks later nobody can tell a finding from a passing thought, and the row is either done twice or dropped by whoever trusts it least. |
108
+ | "Priority is a judgement call, a formula can't capture it" | The formula does not replace the judgement — it *exposes* it. `sev` and `blast` are exactly where the judgement lives, and writing them down is what lets somebody argue with it. |
109
+ | "I'll drop the row, it's not worth a line in Closed" | Then the next run re-discovers it, re-decides it, and the decision is made twice by people who never met. A closed row costs one line. |
@@ -20,6 +20,7 @@ to be enforced and is not is the same failure as a gate that prints `FAIL` and e
20
20
  - The two that are not in the table, and why
21
21
  - The one instruction that would have prevented the most
22
22
  - Where these bind in the pipeline
23
+ - What leaves this file, and why there is no cap
23
24
 
24
25
  ## The table — trigger · check · exit criterion
25
26
 
@@ -137,7 +138,7 @@ run's belief about the work-list against the register; the claim only ever appea
137
138
 
138
139
  **20 · The copy that wins.** A service had **two Dockerfiles**. One was added at the repository root by a run that checked whether a Dockerfile existed by looking where it expected one; `docker/Dockerfile` had been there all along, and `.github/workflows/ci.yml` says `file: docker/Dockerfile`. They disagreed about the port — 8080 at the root, 8000 in `docker/` — and the disagreement surfaced two days later as a **deployed service that answered nothing**, while `docker ps` said `Up` and `systemctl` said `active`. The built one also ran as root and copied the whole context, including `.git` and any `.env`; the hardened one was the one nobody built. Comparing the two files would have found the difference and not the direction. Only the workflow line says which one ships, and it is one grep. The same session hit this three more times: an autonomy sweep row added to the file that ASKS and not the file that RECORDS, twice, caught by a validator that knew to look at both.
139
140
 
140
- **21 · The prune that could not run.** A retrospective's standing-instruction list has a hard cap of ten and three retirement triggers, one of which is *it has not fired in the last five run stamps*. The stage's own instruction was **prune first, then stamp**. So the trigger read a counter the same stage wrote afterwards: on a fresh list it is unreadable, and it stays unreadable for as long as nobody stamps. Measured on a real project: the last retro entry was five days old, stamps per day ran 33, 20, 26, **3, 0** — the zero on a day with 107 commits — and the list sat at exactly **10 of 10**, so every run arrived at a stage that opened with a full list, an unusable trigger and a mandatory deletion. It was not skipped out of laziness. It was skipped because its first step could not be performed, and the cheap step that would have made it performable was queued behind it.
141
+ **21 · The prune that could not run.** A retrospective's standing-instruction list has a hard cap of ten and three retirement triggers, one of which was, at the time of this incident, "it has not fired in the last five run stamps" — it has since gained a second unit, and the incident is left as it happened. The stage's own instruction was **prune first, then stamp**. So the trigger read a counter the same stage wrote afterwards: on a fresh list it is unreadable, and it stays unreadable for as long as nobody stamps. Measured on a real project: the last retro entry was five days old, stamps per day ran 33, 20, 26, **3, 0** — the zero on a day with 107 commits — and the list sat at exactly **10 of 10**, so every run arrived at a stage that opened with a full list, an unusable trigger and a mandatory deletion. It was not skipped out of laziness. It was skipped because its first step could not be performed, and the cheap step that would have made it performable was queued behind it.
141
142
 
142
143
  The same class had already bitten that project twice from the other side, and its roadmap names the
143
144
  property exactly: seven rows read `blocked` on producers the dependency board recorded as delivered,
@@ -209,3 +210,71 @@ project buys go in its retro ([`retrospective.md`](retrospective.md) →
209
210
  lesson there that would be true in any repository belongs here instead, as an issue
210
211
  upstream. A local file that accumulates universal rules is a fork of this one that
211
212
  nobody named.
213
+
214
+ ---
215
+
216
+ ## What leaves this file, and why there is no cap
217
+
218
+ `docs/superpowers/retro.md` caps its standing instructions at **ten** and retires them
219
+ on three triggers. Somebody proposes the same cap here about once a programme. It is
220
+ the wrong instrument, and the reason is worth more than the rule.
221
+
222
+ **A cap belongs to a file you must finish reading.** The retro's standing instructions
223
+ are read *in full* at stage 0 of every run — bounded by construction, or the last one
224
+ is never reached. This file is never read in full: it is entered by citation from the
225
+ stages, and *Where these bind in the pipeline* is that entrance. **A file you enter
226
+ through an index needs its index to be right, not its length to be short.**
227
+
228
+ And the cap would have measured the axis that is not moving. Re-derived across releases
229
+ rather than recalled — **measured at each tag**, so these rows are history and cannot
230
+ go stale; the live shape is printed by `npm test` beside its verdict, and this file
231
+ states no number about itself:
232
+
233
+ | | v1.14.1 | v1.20.0 | v1.23.0 | v1.29.0 |
234
+ |---|---|---|---|---|
235
+ | rules in the table | 15 | 18 | 21 | **21** |
236
+ | words in the file | 2165 | 2987 | 3696 | **3919** |
237
+
238
+ Rules have been flat for four releases while the file grew — and every word of that
239
+ growth is in the binding map, the section that makes a rule *reachable*. Cutting there
240
+ shortens the index. The largest section by far is the incidents, and those are the only
241
+ record of those events anywhere in this repository — checked by taking each incident's
242
+ distinctive tokens against the whole retro corpus, which returned nothing. They are not
243
+ a compression target; they are the thing the rules are made of.
244
+
245
+ **Two triggers retire a rule, and neither is a count:**
246
+
247
+ 1. **The conditions cannot occur.** The tool, the layer, or the failure mode it names
248
+ is gone from every project the skill runs on — not "we have not hit it lately".
249
+ 2. **It is subsumed.** Another rule covers it entirely. This is a **merge**, not a
250
+ delete: the absorbing rule names the absorbed one, and every binding-map row that
251
+ pointed at the old number is repointed in the same change, or the map now sends a
252
+ stage to a rule that is not there.
253
+
254
+ Never *"it became a check"* — that trigger is right for a standing instruction, whose
255
+ whole purpose is to be read until the machine takes over. Here the rule is the reason
256
+ the check exists, and a check whose reason has been deleted is the next thing somebody
257
+ removes as noise.
258
+
259
+ **Every deletion is logged as one line**, in the same change, the same discipline as
260
+ the retro's prune: the rule's number, its name, which trigger fired, and the commit.
261
+ A rule that vanishes silently takes its incident with it, and the next run re-learns it
262
+ at full price. Numbers are never reused and never closed up — a gap in the table is the
263
+ evidence that something left, and the log below says what.
264
+
265
+ ### Retired
266
+
267
+ **Numbers issued so far: 21.** This is the high-water mark, and it is the only number
268
+ this file states about itself — deliberately, because the gap that proves a rule left
269
+ cannot be computed from the table alone: **deleting the highest-numbered rule shrinks
270
+ the maximum with it, and no gap ever opens.** That false negative shipped in the first
271
+ draft of this very section's guard and was found by a reader, not by its probe, which
272
+ had planted in the middle of the list.
273
+
274
+ *None retired yet.* Stated rather than omitted: an empty log and a missing log look
275
+ identical from outside, and only one of them means nothing has been retired. Each
276
+ retirement is one line, starting with the rule's number:
277
+
278
+ <!-- - **N · Name** — trigger: subsumed by M | conditions gone; `<commit>` -->
279
+
280
+ ---
@@ -54,6 +54,7 @@ a row pointing outside the bundle is the defect this file exists to catch.
54
54
  | Loop detection and its caps | `references/loop-guard.md` |
55
55
  | **The run mode** — item-by-item pacing, default off, what it never collapses | `references/continuity.md` |
56
56
  | **The context budget** — the evidence rule and what a flush actually updates | `references/continuity.md` |
57
+ | **The board** — the work-list between runs, its computed priority, and the ledger seam it resolves | `references/backlog.md` |
57
58
  | The retro: prune, cap, commits, archive | `references/retrospective.md` |
58
59
  | Rules earned by failure | `references/learned.md` |
59
60
  | **The routing default and its boundary** | `templates/routing-rule.md` |
@@ -5,6 +5,13 @@ done. It exists because the pipeline's gates are good at *this* run and blind
5
5
  across runs: the same class of failure can be caught, fixed and forgotten five
6
6
  times, and nothing in the flow notices it is the same one.
7
7
 
8
+ **The cap here is not a general rule about doctrine files.** It applies because these
9
+ instructions are read *in full* every run. [`learned.md`](learned.md) →
10
+ *What leaves this file, and why there is no cap* is the other case: a file entered by
11
+ citation, where the index must be right rather than the length short, and where a rule
12
+ retires on two triggers that are not counts. Proposing this cap for that file is a
13
+ recurring idea, and it is answered there with the measurement rather than an opinion.
14
+
8
15
  **Two artifacts, and the split is the point.** A file that is read *in full* every
9
16
  run may not contain anything that grows without limit — otherwise the cap that
10
17
  justifies reading it protects one section while the file below it doubles.
@@ -173,6 +173,7 @@ never that the work was skipped quietly.
173
173
  seeded, the model decision is recorded, and the operator confirms the brief. Stop when a
174
174
  re-scan surfaces no new branches (don't grill past diminishing returns;
175
175
  reversible calls can be deferred with a note). Only then start stage 1.
176
+ - **The board is read, or seeded.** `docs/superpowers/backlog.md` ([`backlog.md`](backlog.md)) — its **open count is quoted in the brief**, measured by a command at the top of the run rather than inherited from the last run's report. Absent ⇒ seeded from the template and said so; an empty board and no board are the same thing to work on, and only one of them can be appended to.
176
177
 
177
178
  ## 1 — Docs study
178
179
  - **Freedom: medium** — which sources to fetch is judgement; grounding contracts on fetched docs is not ([`gates.md`](gates.md) → *Axis C*).
@@ -453,7 +454,16 @@ never that the work was skipped quietly.
453
454
  ## 10 — Acceptance
454
455
  - **Freedom: medium** — the walk and the evidence rule are fixed; whether it is what was asked for is the operator's ([`gates.md`](gates.md) → *Axis C*).
455
456
  - **What:** the closing stage — go back to the brief and account for **every**
456
- requirement. Doctrine: [`acceptance.md`](acceptance.md). Every earlier gate asks
457
+ requirement. Doctrine: [`acceptance.md`](acceptance.md).
458
+ - **The ledger's open rows are resolved onto the board.** Every carry-over row still
459
+ `open` leaves with a `B-NNN` id on `docs/superpowers/backlog.md`, and the ledger row
460
+ is updated to name it ([`backlog.md`](backlog.md)). Both directions, because they are
461
+ different failures: a ledger row pointing at an id nobody issued, and a board row
462
+ traceable to nothing. Measured before this was built: across the ledgers in
463
+ this repository, **twenty-four rows sat `open` with no home at all** — deferred out
464
+ loud and filed nowhere. The first count said sixteen across six ledgers; it read the
465
+ status column by position and was wrong wherever a ledger carried two of them. Then the board's priority is re-derived, because `age` moved while the
466
+ run was happening. Every earlier gate asks
457
467
  "is this artifact good?"; none asks "does this still contain everything that was
458
468
  asked for?" The loss happens on the seams between stages, and this is where it
459
469
  surfaces.
@@ -11,6 +11,7 @@ from `super-ux`.
11
11
  |---|---|---|
12
12
  | `brief.md` | `docs/superpowers/specs/YYYY-MM-DD-<topic>-brief.md` | 0 — intake grill |
13
13
  | `carryover.md` | `docs/superpowers/specs/YYYY-MM-DD-<topic>-carryover.md` | 0 seeds, all stages append, 10 reads |
14
+ | `backlog.md` | `docs/superpowers/backlog.md` | 0 seeds when absent, any stage appends, 10 resolves and re-derives |
14
15
  | `context.md` | `CONTEXT.md` at the repo root (or per context) | 0 — grill, domain awareness |
15
16
  | `adr.md` | `docs/adr/NNNN-<slug>.md` | 0 — grill, hard-to-reverse decisions |
16
17
  | `docmap.md` | `docs/DOCMAP.md` — **one per project** | 0 — the documentation inventory |
@@ -0,0 +1,58 @@
1
+ # Backlog — <project>
2
+
3
+ > **The board.** One per project, at `docs/superpowers/backlog.md`. Unlike the
4
+ > carry-over ledger, this file is **mutable**: priority is re-derived, state changes,
5
+ > rows close. What may never happen silently is a row *disappearing* — a closed row is
6
+ > marked closed, with the commit that closed it.
7
+ >
8
+ > **This is the file a loop iteration reads at the top and re-prioritises at the
9
+ > bottom.** `continuity.md` requires the work-list to be re-measured every iteration;
10
+ > this is the list.
11
+
12
+ | id | What | Source | Size | Sev | Blast | Age | Prio | State | Home |
13
+ |---|---|---|---|---|---|---|---|---|---|
14
+ | B-001 | `export.ts` has no size guard on the write path | `2026-07-28-export` / 5 Review | S | 2 | 1 | 12 | **2** | open | — |
15
+ | B-002 | XLSX export path | `2026-07-28-export` / 5 Dev | M | 1 | 2 | 12 | 2 | open | LIN-483 |
16
+ | B-003 | REQ-007 bulk export | `2026-07-28-export` / 2 Brainstorm | L | 1 | 1 | 12 | 1 | dropped | operator 2026-07-28 |
17
+
18
+ ## Columns
19
+
20
+ - **id** — `B-NNN`, issued in order, **never reused and never closed up**. A gap is
21
+ evidence a row left; the *Closed* section below says which and why.
22
+ - **What** — the concrete thing. *"Error handling"* is not a row; *"`export.ts`
23
+ swallows a failed write instead of surfacing it"* is. Same bar as the ledger.
24
+ - **Source** — the run and stage that surfaced it, so a reader can find the context
25
+ without asking. A row with no source is a wish somebody typed.
26
+ - **Size** — `S` / `M` / `L`. Not hours: an estimate in hours is a number that will be
27
+ quoted back as a commitment.
28
+ - **Sev**, **Blast**, **Age** — the three inputs to priority, each stated so the
29
+ priority can be recomputed by anyone. `Sev` 1–3 (annoyance / wrong behaviour /
30
+ data-or-money). `Blast` 1–3 (one file / one module / crosses a seam). `Age` in days
31
+ since the row was written.
32
+ - **Prio** — **computed, never assigned**: `Sev × Blast + age_bonus`, where
33
+ `age_bonus` is `1` past 14 days and `2` past 30. The formula is repeated here on
34
+ purpose — the inputs are in this table, so a reader checks the arithmetic rather than
35
+ trusting the ranking. Full doctrine: the skill's `references/backlog.md`.
36
+ - **State** — `open` · `in-flight` · `closed` · `dropped`. `dropped` needs the
37
+ operator's agreement and the date they gave it.
38
+ - **Home** — an issue id where a tracker exists, `—` where this file *is* the tracker.
39
+
40
+ ## Closed
41
+
42
+ Rows leave the table above only into this list, one line each, with the commit.
43
+
44
+ <!-- - **B-000 · What it was** — closed by `<commit>` on <date> -->
45
+
46
+ *None yet.* Stated rather than omitted: an empty list and a missing list look the same
47
+ from outside, and only one of them means nothing has closed.
48
+
49
+ ## How rows arrive
50
+
51
+ 1. **Stage 0** seeds this file when it is absent, and reads it when it is present.
52
+ 2. **Any stage, mid-run** — the moment something is said aloud and not done. Same rule
53
+ as the carry-over ledger: *deferred out loud, or lost.*
54
+ 3. **Stage 10** — every carry-over row that is unresolved arrives here with a real id,
55
+ and the ledger row is updated to name it. Unresolved means either home `backlog` —
56
+ the value that used to point nowhere — or still `open`. A row in either shape with no
57
+ id on this board is the dangling pointer this file exists to resolve, and the gate
58
+ refuses it.
@@ -12,7 +12,7 @@
12
12
  | # | Stage | What | Why it isn't done | REQ | Where it lives now |
13
13
  |---|---|---|---|---|---|
14
14
  | 1 | 5 Dev | XLSX export path | scope call — CSV first | REQ-004 | LIN-483 |
15
- | 2 | 5 Review | `export.ts` lacks a size guard | minor, non-blocking | — | backlog |
15
+ | 2 | 5 Review | `export.ts` lacks a size guard | minor, non-blocking | — | `B-014` |
16
16
  | 3 | 2 Brainstorm | REQ-007 dropped: bulk export | operator agreed 2026-07-28 | REQ-007 | dropped |
17
17
 
18
18
  ## Columns
@@ -23,10 +23,15 @@
23
23
  - **Why it isn't done** — scope call, blocked, deliberate deferral, out of budget.
24
24
  "Forgot" is a legitimate and useful answer here.
25
25
  - **REQ** — the requirement it belongs to, or `—` if it's outside the REQ spine.
26
- - **Where it lives now** — issue id, backlog, `dropped` (with the operator's
27
- agreement), or `unresolved`. **`unresolved` blocks the stage-10 gate**: an item
28
- with no home is exactly the thing that gets forgotten, so acceptance refuses to
29
- close on it.
26
+ - **Where it lives now** — an issue id, a **board id** (`B-NNN` on
27
+ `docs/superpowers/backlog.md`), or `dropped` with the operator's agreement. Those are
28
+ the three ways a row is *settled*.
29
+ **Three values are not settled and all three block the stage-10 gate:** `unresolved`,
30
+ `open`, and a bare `backlog` — the last one because it names a place without naming a
31
+ row in it, which is how a deferral reads as filed while being findable by nobody.
32
+ Each leaves stage 10 with a real `B-NNN`, and the board row names this ledger back.
33
+ An item with no home is exactly the thing that gets forgotten, so acceptance refuses
34
+ to close on it.
30
35
 
31
36
  ## This ledger is a ratchet, not a TODO list
32
37