task-pipeline-skill 1.85.2 → 1.86.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/CONTRIBUTING.md +2 -2
  3. package/README.md +2 -1
  4. package/SKILL-CARD.md +1 -1
  5. package/bin/task-pipeline.js +70 -9
  6. package/evals/cases/evidence-docs.json +188 -0
  7. package/evals/cases/project-audit.json +188 -0
  8. package/evals/cases/task-pipeline.json +191 -0
  9. package/package.json +5 -4
  10. package/plugins/task-pipeline/.claude-plugin/plugin.json +1 -1
  11. package/plugins/task-pipeline/skills/evidence-docs/SKILL.md +16 -11
  12. package/plugins/task-pipeline/skills/evidence-docs/references/GENERATED.md +8 -0
  13. package/plugins/task-pipeline/skills/evidence-docs/references/documentation.md +472 -0
  14. package/plugins/task-pipeline/skills/evidence-docs/references/gates.md +645 -0
  15. package/plugins/task-pipeline/skills/evidence-docs/references/hooks.md +274 -0
  16. package/plugins/task-pipeline/skills/evidence-docs/references/learned.md +292 -0
  17. package/plugins/task-pipeline/skills/evidence-docs/references/retrospective.md +551 -0
  18. package/plugins/task-pipeline/skills/evidence-docs/references/setup.md +149 -0
  19. package/plugins/task-pipeline/skills/evidence-docs/templates/decisions.md +50 -0
  20. package/plugins/task-pipeline/skills/evidence-docs/templates/docgate.sh +537 -0
  21. package/plugins/task-pipeline/skills/project-audit/SKILL.md +66 -25
  22. package/plugins/task-pipeline/skills/project-audit/scripts/audit.py +11 -0
  23. package/plugins/task-pipeline/skills/task-pipeline/SKILL.md +71 -54
  24. package/plugins/task-pipeline/skills/task-pipeline/execution-attempt.schema.json +68 -0
  25. package/plugins/task-pipeline/skills/task-pipeline/execution-packet.example.json +42 -0
  26. package/plugins/task-pipeline/skills/task-pipeline/execution-packet.schema.json +217 -0
  27. package/plugins/task-pipeline/skills/task-pipeline/execution-result.example.json +49 -0
  28. package/plugins/task-pipeline/skills/task-pipeline/execution-result.schema.json +261 -0
  29. package/plugins/task-pipeline/skills/task-pipeline/graph.example.json +10 -1
  30. package/plugins/task-pipeline/skills/task-pipeline/graph.schema.json +172 -2
  31. package/plugins/task-pipeline/skills/task-pipeline/pipeline.schema.json +50 -1
  32. package/plugins/task-pipeline/skills/task-pipeline/references/acceptance.md +7 -0
  33. package/plugins/task-pipeline/skills/task-pipeline/references/artifacts.md +23 -0
  34. package/plugins/task-pipeline/skills/task-pipeline/references/audit.md +6 -0
  35. package/plugins/task-pipeline/skills/task-pipeline/references/backlog.md +8 -1
  36. package/plugins/task-pipeline/skills/task-pipeline/references/browser.md +8 -0
  37. package/plugins/task-pipeline/skills/task-pipeline/references/build.md +32 -0
  38. package/plugins/task-pipeline/skills/task-pipeline/references/companion-skills.md +14 -3
  39. package/plugins/task-pipeline/skills/task-pipeline/references/decomposition.md +83 -2
  40. package/plugins/task-pipeline/skills/task-pipeline/references/doctrine-map.md +53 -0
  41. package/plugins/task-pipeline/skills/task-pipeline/references/documentation.md +3 -0
  42. package/plugins/task-pipeline/skills/task-pipeline/references/grill.md +27 -8
  43. package/plugins/task-pipeline/skills/task-pipeline/references/model-tiering.md +19 -0
  44. package/plugins/task-pipeline/skills/task-pipeline/references/planning.md +203 -26
  45. package/plugins/task-pipeline/skills/task-pipeline/references/portability.md +1 -0
  46. package/plugins/task-pipeline/skills/task-pipeline/references/retrospective.md +26 -8
  47. package/plugins/task-pipeline/skills/task-pipeline/references/work-graph.md +7 -1
  48. package/plugins/task-pipeline/skills/task-pipeline/scripts/context_packets.py +686 -0
  49. package/plugins/task-pipeline/skills/task-pipeline/scripts/execution_authority.py +271 -0
  50. package/plugins/task-pipeline/skills/task-pipeline/scripts/graph.py +415 -18
  51. package/plugins/task-pipeline/skills/task-pipeline/scripts/packet.py +400 -0
  52. package/plugins/task-pipeline/skills/task-pipeline/templates/README.md +2 -0
  53. package/plugins/task-pipeline/skills/task-pipeline/templates/browser-claims.json +54 -0
  54. package/plugins/task-pipeline/skills/task-pipeline/templates/finding-evidence.json +42 -0
  55. package/plugins/task-pipeline/skills/task-pipeline/templates/run.md +2 -2
@@ -0,0 +1,551 @@
1
+ # Retrospective — the run teaches the next run, and the list stays short
2
+
3
+ The last act of stage 10, after the coverage table and before the run is called
4
+ done. It exists because the pipeline's gates are good at *this* run and blind
5
+ across runs: the same class of failure can be caught, fixed and forgotten five
6
+ times, and nothing in the flow notices it is the same one.
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
+
15
+ **Two artifacts, and the split is the point.** A file that is read *in full* every
16
+ run may not contain anything that grows without limit — otherwise the cap that
17
+ justifies reading it protects one section while the file below it doubles.
18
+
19
+ | Artifact | Parts | How it is read |
20
+ |---|---|---|
21
+ | `docs/evidence/retro.md` — **one per project** | **Standing instructions** (max **10**) · **Run stamps** (max **10**, oldest rotate out) | stage 0, **in full** — both are bounded by a **cap**, which *one line each* never was |
22
+ | the same file's **Recent log** | narrative entries, **uncapped by design** — the heading said *entries from the last five run stamps* until 2026-08-20 while the section held 25 going back nine days, a bound in a heading that nothing enforced | stage 0, **queried** by the task's nouns. It said *in full* until 2026-08-10, when it measured **74%** of the file: an uncapped section inside a binding source is what makes the capped part get skimmed |
23
+ | `docs/evidence/retro/YYYY-QN.md` — the archive | every entry and every retirement ever written, append-only | **queried** by the task's nouns; never read end to end |
24
+
25
+ Seed the archive from [`../templates/retro-archive.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/templates/retro-archive.md).
26
+
27
+ Every run writes a **stamp** and runs the **prune**. Only a run that *diverged*
28
+ writes an entry. A retro that is empty after a messy run is the exact failure this
29
+ file exists to stop.
30
+
31
+ ## Contents
32
+
33
+ - Write the entry only for a divergence — and name the layer that owned it
34
+ - Every lesson carries its commit
35
+ - Never amend a commit a record already names
36
+ - A release stamp names the TAG, and the merge commit — never the branch's own SHA
37
+ - The stamp table is capped at ten, and *one line per run* was never a cap
38
+ - `publish:` is a line in the verdict, not a silence
39
+ - Rotation — the archive is how pruning stops losing things
40
+ - Three grades of fix — take the highest one that can work
41
+ - Stamp first, then prune, then write
42
+ - The prune — mandatory, and it runs after the stamp
43
+ - When the prune cannot run, say so — it is not the same as nothing to prune
44
+ - A fix to one call site closes a call site, not a class
45
+ - The loop closes at stage 0
46
+ - Where a lesson goes when it is not about this project
47
+ - Publishing the insight — the skill learns from every project that runs it
48
+ - The improvement iteration — what happens to a published insight next
49
+ - What may leave the project — the redaction list
50
+ - Rationalizations
51
+
52
+ ## Write the entry only for a divergence — and name the layer that owned it
53
+
54
+ An entry is owed when the run did not go as planned: a gate reopened, a stage was
55
+ re-entered, a fix broke something it wasn't touching, an estimate was wrong by a
56
+ factor, the operator had to intervene where the brief said they wouldn't.
57
+
58
+ Required fields, and none of them is optional:
59
+
60
+ | Field | The rule |
61
+ |---|---|
62
+ | **Symptom** | what actually happened, in one line, with the evidence (a command, a `file:line`, the gate that reopened) |
63
+ | **Surfaced at** | the stage where it became visible |
64
+ | **Owned by** | the stage that *let it through* — usually an earlier one. A finding belongs to the layer that owns it; recording it against the stage that tripped over it is how the same defect returns |
65
+ | **Root cause** | why the pipeline permitted it. "The agent was careless" is not a cause — it is the absence of one, and it produces no fix |
66
+ | **Fix** | one of the three grades below |
67
+ | **The check** | what would have caught this the first time. If the honest answer is "nothing yet", that is the fix, and it is grade 1 |
68
+ | **Commit** | the short SHA of the change that fixed it |
69
+
70
+ ## Every lesson carries its commit
71
+
72
+ A standing instruction carries **two** SHAs — `Commit` (the change that introduced
73
+ it) and `Fired at` (the last run in which it fired) — and every log entry and every
74
+ retirement carries one.
75
+
76
+ **Why a SHA and not a `file:line`.** A line number rots at the next edit, and then
77
+ the evidence points at something that has moved or gone; the reader is left with a
78
+ claim and no way to check it. A commit is immutable and carries the diff, the
79
+ message and the parent, so `git show <sha>` reconstructs the entire incident two
80
+ months later — which is exactly when the same class comes back and somebody needs
81
+ to know whether this was already understood.
82
+
83
+ **Every SHA must resolve — and resolving is not enough.** This is
84
+ [`learned.md`](learned.md) rule 14 — *a document may not send a reader to something
85
+ absent* — applied to history, and it is mechanical: the documentation gate runs
86
+ `git rev-parse --verify --quiet <sha>^{commit}` over every backticked SHA in the retro
87
+ and its archive ([`gates.md`](gates.md)).
88
+
89
+ **But a commit that was amended away still resolves on the machine that amended it**,
90
+ and exists in no clone. The gate therefore also requires each SHA to be **reachable
91
+ from `HEAD`** — `git merge-base --is-ancestor <sha> HEAD` — because that is the
92
+ question a reader two months from now is actually asking, and the weaker one passes
93
+ for as long as the object survives locally.
94
+
95
+ ## Never amend a commit a record already names
96
+
97
+ Measured 2026-08-16, twice in one close-out and twenty minutes apart. The sequence is
98
+ seductive because each step is right on its own: stamp the run with its commit → the
99
+ stamp is part of the run, so fold it in with `--amend` → the amend mints a new SHA →
100
+ the stamp now names a commit that will never reach the remote.
101
+
102
+ The rule is one line and it is absolute: **once a file names a SHA, that commit is
103
+ frozen.** A correction goes in a *follow-up commit*, never a second amend — amending to
104
+ repair a stamp is the loop that produced the problem, and the second attempt lands in the
105
+ same place as the first.
106
+
107
+ Practically, that makes the order:
108
+
109
+ 1. commit the work;
110
+ 2. **then** stamp, in a commit of its own, naming the commit from step 1;
111
+ 3. prune and write the entry in that same second commit, or a third.
112
+
113
+ The stamp costs one line and one commit. A run that folds it back into the work to keep
114
+ the history tidy is trading a reader's ability to find the incident for the appearance of
115
+ tidiness — and the reader is the entire reason the stamp exists.
116
+
117
+ ## A release stamp names the TAG, and the merge commit — never the branch's own SHA
118
+
119
+ The sibling of the amend rule, one level up, and it recurred three times after the amend
120
+ rule was written because each step is right on its own: stamp the run on the PR branch →
121
+ the stamp cites that branch's HEAD → `main` is **rebase-merge-only** by ruleset → the
122
+ rebase mints new SHAs → the object the stamp names dies with the branch.
123
+
124
+ Measured across `v1.79.1` (twice) and `v1.80.0`, and again on 2026-09-05 in the umbrella
125
+ repository, where it burned **two tags** in one afternoon. Each recurrence costs the same
126
+ two refusals, both of them correct:
127
+
128
+ 1. the **unstamped-release guard** reads the tag's own commit range, finds no stamp that
129
+ resolves, and refuses the release;
130
+ 2. the **documentation gate** resolves every backticked commit reference against a *fresh
131
+ clone*, where the branch SHA has never existed, and refuses again.
132
+
133
+ Then it costs one extra PR and one full CI round — measured at 57–97 minutes here.
134
+
135
+ **The shape that survives a rebase-merge is the tag.** A SHA the author can know before
136
+ the merge is precisely the SHA the merge is about to destroy, so the rule cannot be
137
+ "write a better SHA". Two spellings are allowed and nothing else:
138
+
139
+ - **cite the release tag** — `v1.84.1` is immutable, it is the thing being released, and
140
+ it resolves in any clone;
141
+ - **or write the stamp AFTER the merge**, in a second PR, naming the **merge commit**.
142
+ This is the default, because it is what the amend rule already forces one level down:
143
+ once a file names a SHA, that commit is frozen, so the SHA has to exist first.
144
+
145
+ The order for a release run is therefore:
146
+
147
+ 1. merge the work; the merge commit is now a real object on `main`;
148
+ 2. **then** stamp, in a second PR, naming that merge commit;
149
+ 3. cut the tag **locally**, run `npm run test:all` against the tag's own tree, and push
150
+ the tag only if that run is green — `git merge-base --is-ancestor <stamp-sha> HEAD`
151
+ is what the release gate will ask, so ask it first.
152
+
153
+ Step 3 is not optional and it is not the same as a green branch build: the branch tree
154
+ and the tag's tree differ by whatever the merge did, and **a tag cannot be repaired** —
155
+ deleting or re-pointing one is refused by repository rule, correctly, because a tag is a
156
+ promise about a tree. A tag pushed on a green branch build is a promise about a tree
157
+ nobody ran.
158
+
159
+ ## The stamp table is capped at ten, and *one line per run* was never a cap
160
+
161
+ Measured 2026-08-10: standing instructions **~1 234 tok** behind a cap of ten, run stamps
162
+ **~2 099 tok over 27 rows** behind nothing. Both are read in full at stage 0 and both
163
+ were described as *bounded by construction*. One line per run is a **slope**: at a
164
+ hundred runs the stamp table alone is ~7 800 tokens of a floor the doctrine believes is
165
+ bounded.
166
+
167
+ This is the same shape the 2026-08-10 audit found in the narrative log and moved out of
168
+ the floor — and it left the neighbour in the same file, with the same property, because
169
+ the neighbour's growth is *tidy*. A tidy slope is still a slope.
170
+
171
+ **The cap is ten and the trigger is why.** The cold rule reads *the last five run
172
+ stamps*; ten is that with a margin, so a stamp rotating out can never be one the trigger
173
+ needed. At the eleventh, the oldest row moves — whole, with its verdict and its retro
174
+ column — into `docs/evidence/retro/YYYY-QN.md` under `## Run stamps`, append-only,
175
+ like every other rotation. **The count is printed at the prune**, beside the standing
176
+ instructions' own count, so a table that stops rotating is visible rather than merely
177
+ large.
178
+
179
+ ## `publish:` is a line in the verdict, not a silence
180
+
181
+ Reported from another project, and the report is about this file: an operator asked, after
182
+ many runs, why nothing had ever been published.
183
+
184
+ The floor is right and does not move — publishing is opt-in per project, off by default,
185
+ because an outward act taken from a generic flag is an outward act nobody authorized. The
186
+ gap is what happens next. A project with no configuration produces, run after run,
187
+ retrospective entries carrying lessons about **the skill**, and the mechanism reports
188
+ nothing, because it was never armed. **An unarmed mechanism and a mechanism with nothing
189
+ to say are indistinguishable from the outside** — [`gates.md`](gates.md)'s false success,
190
+ applied to this bundle's own learning path. In the reporting project several runs had
191
+ produced skill-level lessons over months; the count of published insights was zero and no
192
+ gate had ever mentioned it.
193
+
194
+ Stage 10 prints ratchets and two disclosures — what the run declined to claim, and what a
195
+ check never looked at. Publishing is neither, so its absence is not observable at the only
196
+ moment anyone is reading.
197
+
198
+ **So stage 10's block carries one line for publication:**
199
+
200
+ ```
201
+ publish: <issue url> — opened this run
202
+ publish: 0 (configured, nothing insight-grade)
203
+ publish: not configured (N insight-grade entries stayed local)
204
+ ```
205
+
206
+ It arms nothing and authorizes nothing; it makes the silence legible. A count of zero
207
+ beside *configured* is a fine answer. A **blank** where configuration is absent is how an
208
+ instruction went unread for eight releases.
209
+
210
+ **The failure mode survived its own fix, in a quieter form.** This section once said
211
+ *"open an issue upstream"* while naming no repository, no trigger and no authorization,
212
+ and every reader took it as done. The mechanism that replaced it is correct — and until
213
+ this line existed it still had no way to say it never ran.
214
+
215
+ ## Rotation — the archive is how pruning stops losing things
216
+
217
+ At the prune, entries older than the last five run stamps **move** to
218
+ `docs/evidence/retro/YYYY-QN.md`. Moving is not deleting.
219
+
220
+ - The archive is **append-only**, and a retirement writes its line **there**, with
221
+ the trigger that retired it and the commit.
222
+ - A retired rule that comes back as a real failure is a grade-1 fix — **with its
223
+ history attached**, which is the whole return on having archived it.
224
+ - Nothing is ever removed from the archive to keep it tidy. It is not read in full,
225
+ so its size costs nothing; its completeness is what it is for.
226
+
227
+ ## Three grades of fix — take the highest one that can work
228
+
229
+ **Grade 1 — mechanical.** A test, a lint rule, a gate criterion, a CI step, a hook.
230
+ The check *is* the memory: nothing has to be read, remembered or pruned later. Log
231
+ it as `landed` and move on — it never becomes a standing instruction and never
232
+ costs a slot.
233
+
234
+ **Grade 2 — a standing instruction.** A rule an agent must read, for the cases no
235
+ check can decide (a judgement, a precedence, a "ask before X"). It costs one of the
236
+ ten slots and it is **only accepted with its retirement trigger written at birth**
237
+ (below).
238
+
239
+ **Grade 3 — a note with an expiry.** For something still being understood. Maximum
240
+ **two runs**. At the second stamp it is promoted to grade 1 or 2, or deleted. A note
241
+ with no expiry is how a file becomes unreadable one honest line at a time.
242
+
243
+ Prefer grade 1 whenever a check can decide it. This is the same law as
244
+ [`audit.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/audit.md) → *A class that repeats twice becomes a gate, not a note*: a
245
+ rule that could have been a check gets read twice and obeyed once.
246
+
247
+ ## Stamp first, then prune, then write
248
+
249
+ `learned.md` rule 21. This order used to be *prune first*, and that was a **deadlock**, not a
250
+ preference: the cold trigger below reads *the last five run stamps*, and the stamp was written after
251
+ the prune. The trigger read a counter the same stage produced later, so on any list it had never run
252
+ on real data — and it stays unreadable for exactly as long as nobody stamps.
253
+
254
+ Measured on a real project: last entry five days old; stamps per day 33, 20, 26, **3, 0** — the zero
255
+ on a day with 107 commits — and the list sitting at **10 of 10**. Every run arrived at a stage that
256
+ opened with a full list, an unusable trigger and a mandatory deletion. It was not skipped out of
257
+ laziness; its first step could not be performed, and the cheap step that would have made it
258
+ performable was queued behind it.
259
+
260
+ **The stamp is one line and costs nothing.** It is also the only thing that makes the prune
261
+ computable, which is why it goes first:
262
+
263
+ ```bash
264
+ printf '%s · %s\n' "$(date +%F)" "$(git rev-parse --short HEAD)" >> docs/evidence/retro.md
265
+ ```
266
+
267
+ ## The prune — mandatory, and it runs after the stamp
268
+
269
+ A lesson that lands in a cluttered file is a lesson nobody will reach — so the prune still runs
270
+ before the entry is written. It runs *after* the stamp, because it reads it.
271
+
272
+ Every row carries its own trigger in a **`Retire when`** column AND a **class**,
273
+ both written at birth — a rule whose retirement condition is decided later is a
274
+ rule the prune can only argue about. Three classes, because coldness means
275
+ three different things:
276
+
277
+ - **permanent** — a safety invariant, a contract, a recovery procedure. The
278
+ absence of the rare event it guards is not absence of value: **coldness never
279
+ retires a permanent rule** — only *became a check* or *surface gone* do.
280
+ - **situational** — fires when a named EXPOSURE occurs (a payment run, a
281
+ migration, a release). Its row names the exposure, and the prune counts
282
+ **exposure opportunities, not raw runs**: five runs that never touched
283
+ payments say nothing about a payment rule.
284
+ - **temporary** — a workaround with a **TTL** and the replacing mechanism named
285
+ at birth. TTL only exists on this class.
286
+
287
+ Check **every** standing instruction against three triggers:
288
+
289
+ | Trigger | Test | Then |
290
+ |---|---|---|
291
+ | **It became a check** | the rule is now enforced by a test, lint, gate or hook | delete it — the check is the memory, and keeping both means it is read twice and obeyed once |
292
+ | **Its surface is gone** | resolve every path, command, stage and tool it names; any that no longer exists | delete it — it now describes a system nobody is running |
293
+ | **It went cold** | *situational only*: it fired in none of the last **five exposure opportunities** — or no exposure occurred in **sixty days** | mark **review-needed** and put the question to the operator with the exposure counts — cold is evidence worth reviewing, never an automatic deletion. *temporary*: TTL expired → archive it **once the replacing mechanism is verified present** (its check resolves and runs); an expired workaround whose replacement is absent is a live defect, not a retirement. *permanent*: not applicable |
294
+
295
+ Every removal stays explainable: the archive line names the class, the trigger
296
+ and the evidence (the exposure counts, or the replacing mechanism's check).
297
+
298
+ **Each trigger is a command, not a judgement.** A retirement condition nobody can run is a
299
+ condition nobody applies, which is how a list reaches ten and stops being read:
300
+
301
+ ```bash
302
+ # became a check — the rule's own words appear in something that runs
303
+ grep -rl "$RULE_KEYWORD" scripts/ test/ .github/workflows/ Makefile* 2>/dev/null
304
+
305
+ # surface is gone — every path, command and tool it names, resolved
306
+ grep -oE '`[^`]+`' <<<"$RULE_TEXT" | tr -d '`' | while read -r t; do
307
+ [ -e "$t" ] || command -v "$t" >/dev/null || echo "MISSING: $t"; done
308
+
309
+ # went cold — fired in none of the last five stamps
310
+ tail -n 200 docs/evidence/retro.md | grep -c "$RULE_ID"
311
+
312
+ # ...OR in the last 60 days, whichever comes first — see below for why both
313
+ git log -1 --format=%cd --date=short -S"$RULE_ID" -- docs/evidence/retro.md
314
+ ```
315
+
316
+ Anything the first two print is a deletion. A zero from the third — counted against
317
+ **exposure opportunities** for a situational rule (`grep -c "$EXPOSURE_MARK"` over the same
318
+ stamps, so the denominator is runs that COULD have fired it) — marks **review-needed**, and the
319
+ run states the counts rather than the conclusion (`learned.md` rule 19 — an empty result and an
320
+ unrun command look identical). Only the operator's answer, or a temporary rule's verified
321
+ replacement, turns review-needed into a removal.
322
+
323
+ **Why the cold trigger needs two units, and it is not belt-and-braces.** A run stamp is written by
324
+ a run *of this pipeline*. Where a project ships some of its work another way, the stamp counter
325
+ stops while the work does not — so "the last five stamps" can span an arbitrary amount of change,
326
+ and a rule sits unexamined for exactly as long as the pipeline goes unused. Measured on this
327
+ repository: **ten consecutive releases, `v1.16.0` through `v1.23.0`, carry no stamp at all.** Over
328
+ that stretch the trigger was not strict or lenient; it was **unreadable**, and a list capped at ten
329
+ with an unreadable retirement condition fills up and stops being pruned.
330
+
331
+ The wall-clock alternative fires on elapsed time, which nothing can stall. Keep both: the stamp
332
+ count is the better signal when the pipeline is in use, and the date is the one that still works
333
+ when it is not.
334
+
335
+ **The stamp gap is itself a number worth printing.** A retro whose newest stamp is far behind the
336
+ repository's newest release is telling you the retro is describing a smaller world than the one
337
+ that shipped — the same failure `learned.md` rule 16 records for a work-list. State it beside the
338
+ retro counts:
339
+
340
+ ```bash
341
+ git tag --sort=-v:refname | head -1 # newest release
342
+ grep -m1 -oE '`[0-9a-f]{7,}`' docs/evidence/retro.md # newest stamped commit
343
+ ```
344
+
345
+ Then the cap: **ten standing instructions, hard.** At eleven you do not get to keep
346
+ them all — the oldest never-fired one goes. "But all of them matter" is precisely
347
+ the state in which the list stopped being read, and the ninth stale rule is what
348
+ discredits the two that are load-bearing.
349
+
350
+ **Every deletion writes one line in the archive** — id, date, which trigger fired,
351
+ and the commit. Silent deletion is forbidden: the record is what survives, the
352
+ instruction is what leaves.
353
+
354
+ **Print the counts beside the gate verdict**, the same way the carry-over ledger
355
+ does ([`audit.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/audit.md) → *What can't be fixed now becomes a ratchet, never a TODO*):
356
+
357
+ ```
358
+ GATE 10 acceptance: PASS — 14/14 REQ verified
359
+ carry-over: 0 unresolved · retro: 7 standing (was 9) · retired 3 · added 1
360
+ abstained: 2 · unlooked: 3
361
+ holds: 10 — none — enumerated 8/8 classes
362
+ ```
363
+
364
+ A pruned list that nobody prints is a list that quietly grows back.
365
+
366
+ ## When the prune cannot run, say so — it is not the same as nothing to prune
367
+
368
+ The prune retires a standing instruction against its triggers, and the cold trigger counts
369
+ run stamps. Meeting a list whose rows carry **no id and no retirement condition**, the
370
+ honest behaviour was undefined — so a careful run printed counts and stopped while a
371
+ careless one could delete nine rules, and both looked like *the prune ran*.
372
+
373
+ - **Backfill first, once.** On meeting rows with no id, the prune's first act is to mint
374
+ ids and propose a retirement condition per row **from the row's own text**, printed for
375
+ the operator to accept. One pass, and the trigger works forever after.
376
+ - **`blocked` is a named outcome**, printed like any other: what could not be evaluated
377
+ and why. An undefined case is where the spread between two runs lives.
378
+ - **A hard cap plus an unrunnable retirement condition is a deadlock.** Either the cap
379
+ yields until the backfill is done, or the backfill is a precondition of enforcing it —
380
+ but not both, and the file says which.
381
+
382
+ ## A fix to one call site closes a call site, not a class
383
+
384
+ - **A fix other call sites will need is exported before it is used.** The test is
385
+ mechanical: if the same defect could exist in a second file, the fix does not stay
386
+ private to the first.
387
+ - **Closing a defect includes a census of its siblings, by BEHAVIOUR rather than by a list
388
+ of names** — *every script that spawns a browser*, found by searching for the spawn, not
389
+ by remembering the five. A list of names is how the previous pass missed four of them.
390
+ - **The entry states how many call sites the class had and how many were fixed.** *Fixed*
391
+ and *fixed in one of five* currently read identically, and only one of them closes a row.
392
+
393
+ ## The loop closes at stage 0
394
+
395
+ The standing instructions are an **instruction source**, not background reading:
396
+ [`knowledge-sources.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/knowledge-sources.md) reads them in full at the harvest —
397
+ they are short by construction — and records the file as a ledger row. Every
398
+ instruction that actually *fires* during the run gets its **last-fired date and
399
+ commit stamped** as it fires. That stamp is the only thing that makes the cold-rule
400
+ honest; without it "five runs without firing" is a guess, and the prune becomes a
401
+ mood.
402
+
403
+ **The archive is queried at the same moment**, by the task's own nouns. It is the
404
+ one source that answers *"have we been bitten by this class before?"* — and that
405
+ question is worth asking precisely when the in-force list says nothing, because a
406
+ rule that was retired for going cold is exactly the rule about to be re-learned.
407
+
408
+ ## Where a lesson goes when it is not about this project
409
+
410
+ A lesson that would be true in any repository does not belong in one project's
411
+ retro — it belongs in the pipeline's own doctrine
412
+ ([`learned.md`](learned.md), which is exactly that list, earned the same way). A local
413
+ file that accumulates universal rules is a fork of the skill that nobody named.
414
+
415
+ **This said *"open an issue upstream"* for eight releases and named no repository, no
416
+ trigger and no authorization** — an instruction on rung 1 that everybody read as done.
417
+ The rest of this section is the mechanism.
418
+
419
+ ## Publishing the insight — the skill learns from every project that runs it
420
+
421
+ **One job: stop a lesson dying in the repository that learned it.**
422
+
423
+ A retro entry is written per project and read by that project's next stage 0. The
424
+ pipeline itself never sees it. So a defect in the **skill** — a gate that loops, a
425
+ doctrine promising what nothing enforces, a rule firing on the wrong shape — is
426
+ rediscovered independently in every project and fixed in none of them.
427
+
428
+ **Opt-in, per project, off by default** — `pipeline.json` → `retro.publish`:
429
+
430
+ ```json
431
+ "retro": {
432
+ "publish": {
433
+ "repo": "ssheleg/task-pipeline",
434
+ "label": "retro-insight",
435
+ "redact": "strict"
436
+ }
437
+ }
438
+ ```
439
+
440
+ Absent, nothing is published, and nothing is asked. **Silence arms nothing** — the same
441
+ floor deploy authorization uses, and for the same reason: this is an **outward act**,
442
+ and an outward act taken from a generic flag is an outward act nobody authorized
443
+ ([`continuity.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/continuity.md) → *The limit, before the capability*).
444
+
445
+ **The body is printed in full before the issue is opened, every time.** Not a summary
446
+ of it, not its title — the string that will be sent. The operator standing at stage
447
+ 10's manual gate is already reading; showing them what leaves the machine costs one
448
+ block and is the only moment anyone can stop it.
449
+
450
+ ```
451
+ ── would open issue ──────────────────────────────────────────
452
+ repo: ssheleg/task-pipeline
453
+ label: retro-insight
454
+ title: [retro] a queue is not a diagnosis
455
+ <the whole body, verbatim>
456
+ ──────────────────────────────────────────────────────────────
457
+ opening… → #24
458
+ ```
459
+
460
+ **No `gh`, no network, no permission?** Print the body, say the issue was **not**
461
+ opened, and carry the exact text in the carry-over ledger. That is the honest
462
+ degradation; a second transport is not.
463
+
464
+ ## The improvement iteration — what happens to a published insight next
465
+
466
+ Publishing is half a loop. An issue that is opened and never triaged is a lesson
467
+ that cost a run and bought nothing, and a tracker full of those teaches everyone
468
+ that publishing is where findings go to be filed.
469
+
470
+ **The loop, and every arrow in it is somebody's obligation:**
471
+
472
+ ```
473
+ a run diverges → a retro entry → retro.publish → an issue on the skill
474
+
475
+ a board row ← measured against the tree ← triaged in a later cycle
476
+
477
+ doctrine + the guard that proves it → a release → the next run reads it
478
+ ```
479
+
480
+ **Resolve what you worked, let the rest accumulate — visibly.** At the close of a
481
+ cycle that consumed published issues:
482
+
483
+ - **An issue closes when the behaviour changed**, not when the lesson was
484
+ understood (that is R-006, and it applies to issues exactly as it applies to
485
+ findings). The closing comment names **where** it landed — a file and a line —
486
+ and the guard that now holds it. A close with no address is a close nobody can
487
+ audit.
488
+ - **An issue nobody worked stays open.** It is not triaged into silence, not
489
+ relabelled, not closed as stale. The pile is the queue, and its depth is the
490
+ honest measure of how far behind the doctrine is.
491
+ - **Nothing is deleted.** A closed issue keeps its number, and the number is what
492
+ the CHANGELOG and the retro entry point at. Deleting one severs both, and the
493
+ cost lands on whoever next asks *why is this rule here*.
494
+
495
+ **Where the queue comes from at the start of a cycle**, in this order: open issues
496
+ published by any project running this skill, then the board's open rows by computed
497
+ priority, then the open-questions register. A run that finishes its brief early
498
+ takes the top of that list rather than inventing work — and a run that *cannot*
499
+ take it says so, which is a fuller queue rather than a quiet one.
500
+
501
+ **The measurement that keeps this honest.** Print, at the close: issues consumed,
502
+ issues resolved with an address, issues left open. Three numbers, no floor, no
503
+ target, and the third one rising is information rather than a failure — it means
504
+ the projects running this skill are finding more than one cycle can absorb, which
505
+ is what you want them to do.
506
+
507
+ ## What may leave the project — the redaction list
508
+
509
+ An issue is a **public artefact in someone else's repository**. What travels is the
510
+ *class*; what stays is everything that identifies where it happened.
511
+
512
+ | Goes | Stays |
513
+ |---|---|
514
+ | the class of failure, stated in the abstract | the file, the function, the line it happened in |
515
+ | which stage owned it and which stage surfaced it | the repository, organisation, branch or commit |
516
+ | the doctrine or guard that missed it, by its name **in this skill** | any host path, absolute or relative |
517
+ | the fix by grade, and the check that would catch it | the code, the config values, the data |
518
+ | whether an existing standing instruction fired | any person, company, customer or product name |
519
+
520
+ Five rules, numbered so a reader can point at one:
521
+
522
+ 1. **No host paths.** Only paths inside task-pipeline itself — `references/…`,
523
+ `templates/…`, `test/validate.py`. An absolute path names a machine.
524
+ 2. **No host identifiers**: repository, organisation, branch, commit, tag, issue or PR
525
+ number belonging to the project the run happened in.
526
+ 3. **No code, no configuration values, no data** — not a snippet, not a redacted
527
+ snippet. A shape can be described in a sentence.
528
+ 4. **No names**: person, company, customer, employer, product.
529
+ 5. **The title states the class, not the incident** — *"a queue is not a diagnosis"*,
530
+ never *"our export job looped"*.
531
+
532
+ **The printed text and the sent text are one string.** Redacting after the print, or
533
+ printing a cleaned-up version of what is actually sent, is the false-success shape this
534
+ bundle names outright ([`gates.md`](gates.md) → *False success*): a mechanism reporting
535
+ on itself rather than on what it did.
536
+
537
+ **When in doubt the rule is subtraction, not judgement.** An insight that survives
538
+ losing a detail is still an insight; a detail that leaks cannot be recalled from an
539
+ index. If removing it makes the entry incomprehensible, the entry was about the project
540
+ and not about the skill — keep it local.
541
+
542
+ ## Rationalizations
543
+
544
+ | Excuse | Reality |
545
+ |---|---|
546
+ | "Nothing really went wrong this run" | Then the stamp says so in one line and you are done in ten seconds. The runs that "went fine" are where a repeated class hides — it never cost enough to remember. |
547
+ | "I'll write the retro later, with a clear head" | Later is when you remember the outcome and not the seam. The cause is legible for about an hour after the run. |
548
+ | "Don't delete it, it might still be useful" | That sentence is the entire failure mode. Every rule kept "just in case" spends attention that the load-bearing ones needed, and the file stops being read at all. |
549
+ | "Pruning loses knowledge" | The Log keeps the incident forever; only the *instruction* leaves. If it recurs you get a grade-1 fix with its own history attached. |
550
+ | "The list is at eleven but they're all important" | Then one of them is doctrine and belongs in `CLAUDE.md`, one has become a check, and one has not fired in a year. Ten is the budget precisely because ranking is uncomfortable. |
551
+ | "I'll note it as a reminder for next time" | A note is grade 3 and expires in two runs. If it is worth remembering it is worth a check or a slot; if it is worth neither, it was never going to be read. |