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,645 @@
1
+ # Gates — the three axes, and how to build one that cannot lie
2
+
3
+ **One job: turn a rule into something that can say no.** [`audit.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/audit.md)
4
+ says a class seen twice *belongs in a script*; this file is where that script comes
5
+ from — how it is written, where it runs, how it is armed, how it is proven, and
6
+ what you must know **before** you quote its green as evidence.
7
+
8
+ **Boundary, so this file does not become a second source.** The *law* lives
9
+ elsewhere and is not restated here:
10
+
11
+ | The law | Lives in | This file adds |
12
+ |---|---|---|
13
+ | A check must be watched failing before it is trusted | [`audit.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/audit.md) *Exit criterion*, [`learned.md`](learned.md) 4–5 | the executable recipe |
14
+ | Ratchet, never TODO | [`audit.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/audit.md) §3, [`learned.md`](learned.md) 7 | floor variables, and where the count is printed |
15
+ | A gate's exit code is part of its output | [`learned.md`](learned.md) 11 | where the verdict block goes |
16
+ | A checker with false positives is worse than none | [`learned.md`](learned.md) 10 | how to measure before shipping |
17
+ | A generator seeds green | [`learned.md`](learned.md) 9 | progressive arming |
18
+ | A class that repeats twice becomes a gate | [`audit.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/audit.md) §1 | the six-step recipe |
19
+
20
+ ---
21
+
22
+ ## Contents
23
+
24
+ - Axis A — the stage gate type
25
+ - The judgment gate — a ruling is not a measurement
26
+ - Axis B — the enforcement mechanism
27
+ - Axis C — degrees of freedom
28
+ - Progressive arming
29
+ - Before you run a check
30
+ - False success — when a mechanism reports a win it never checked
31
+ - Anatomy of a project gate
32
+ - Writing the check itself
33
+ - A ratchet prices the rule, not the exception
34
+ - Run the whole suite locally before you push the tag
35
+ - A ratchet's matcher is itself a check, and it needs a near-miss
36
+ - The false-positive budget
37
+ - Ratchets
38
+ - The result is the goal; the check is how you know
39
+ - Disclosures — counted like a ratchet, and deliberately not monotone
40
+ - Where a gate runs
41
+ - Adding a check to an existing gate
42
+ - Rationalizations
43
+ - Cross-cutting, at every stage
44
+
45
+ ## Axis A — the stage gate type
46
+
47
+ From [`../pipeline.schema.json`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/pipeline.schema.json), one per stage:
48
+
49
+ | Type | Meaning | Failure to respect it |
50
+ |---|---|---|
51
+ | `auto` | the orchestrator verifies the `check` itself, pass/fail, and stops on fail | advancing on an unverified check |
52
+ | `judgment` | somebody **rules** on it, because no complete deterministic check exists; the gate names its `judge` | recording the ruling in the slot reserved for what a machine established |
53
+ | `manual` | wait for the operator's **explicit** go | treating an auto verification as the approval |
54
+
55
+ **An auto gate never substitutes for a required manual approval.** A green table is
56
+ not the operator confirming it is what they asked for, and no amount of checking
57
+ makes it one. Which stages are manual is the **operator's** decision, recorded in
58
+ their `pipeline.json`; the framework fixes no stage count and no gate assignment.
59
+
60
+ **That sentence is about the pipeline's SHAPE, and nothing else.** It says a project
61
+ chooses how many stages it runs and which of them wait for a person. It does not say the
62
+ criteria inside a gate are per-run negotiable: where a project keeps stage 10 manual, what
63
+ that gate asks is [`acceptance.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/acceptance.md)'s policy **`AP-1`**, which is versioned
64
+ and has an owner. The two rules stood side by side unscoped until 2026-08-20 (`B-091`), and
65
+ a reader could take either as the whole rule — *the framework fixes nothing* and *the ladder
66
+ is fixed* are both in the shipped doctrine, which is how an acceptance standard becomes
67
+ something every run re-argues.
68
+
69
+ ## The judgment gate — a ruling is not a measurement
70
+
71
+ Two types were not enough, and the gap was not cosmetic. A reviewer's ruling, a check that
72
+ the scenarios are coherent, a verdict that a mockup is good — none has a complete
73
+ deterministic check, and all three rode in `auto`, **indistinguishable from an exit code**.
74
+ A coverage table then cannot tell a measured row from an opinion, and the role-agent
75
+ programme multiplies the problem: `reviewer`, `ux`, `ui` and `market-analyst` produce
76
+ judgement by design.
77
+
78
+ `auto` now means only what a machine established.
79
+
80
+ **The precedent already existed in miniature, and this generalises it rather than
81
+ inventing it.** [`templates/verification.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/templates/verification.md) already turns a
82
+ coverage verdict of `review` into `none` in the `Auto` column — because that column records
83
+ what a machine established, and a review is not that. That rule, applied to one column, is
84
+ the `judgment` type in one instance.
85
+
86
+ Three obligations, and the third is the one that bites:
87
+
88
+ 1. **It names its `judge`** — a role, an agent, a person. The schema refuses the gate
89
+ without one. A ruling with no author cannot be weighed for independence, and
90
+ independence is not a property of *having* a reviewer: this pipeline's own `R-005` reader
91
+ shares a model, instructions and repository with the author it reviews, differing only in
92
+ context. That is a real second reading and it is **not** a deterministic runner, a
93
+ contract at another boundary, or an external system. Naming the judge is what makes the
94
+ difference visible instead of assumed.
95
+ 2. **The verdict is recorded as judgement**, in the artifact that quotes it — never
96
+ promoted to a pass in a column that means *a machine established this*.
97
+ 3. **It may not stand in for a `manual` gate.** A judgement can be rendered by an agent; an
98
+ *authorisation* cannot. Anything outward, irreversible, or costing money stays `manual`
99
+ however confident the judge.
100
+
101
+ **Which of this pipeline's own gates are judgement is deliberately not decided here.**
102
+ Gate assignment is the operator's call and the framework fixes none — so shipping a
103
+ reclassified stage list would contradict the sentence above it. The type exists; the
104
+ project chooses where it applies.
105
+
106
+ **The rubric steers the builder before it judges the work.** A judgment gate's
107
+ criteria are read twice — by the judge at the gate, and earlier by whoever builds
108
+ toward it — and the second reading is absorbed as direction: Anthropic reports
109
+ agents converging on one aesthetic because a rubric's adjective ("museum
110
+ quality") was obeyed as a target rather than weighed as a bar (read 2026-08-30).
111
+ So write criteria as the direction you want taken, not only as the bar to clear;
112
+ the words will be obeyed either way, and a rubric written carelessly is an
113
+ instruction issued accidentally.
114
+
115
+ | Rationalization | Why it is wrong |
116
+ |---|---|
117
+ | *"The reviewer approved it, so the gate passed."* | It did — as a judgement. Type it as one, or the table claims a machine agreed |
118
+ | *"A second agent checked it, so it is independent."* | Independence is a different **evidence path**, not a second reader. Name the judge and the difference is visible |
119
+ | *"There is no check for this, so it has to be `manual`."* | `manual` waits for a person's authority. `judgment` records a ruling. Collapsing them puts a human in the loop for everything that is merely hard to measure, which is how an operator learns to route around the pipeline |
120
+
121
+ ## Axis B — the enforcement mechanism
122
+
123
+ Where a rule actually lives. A rule climbs this ladder; it does not start at the top.
124
+
125
+ | Rung | Mechanism | Costs | Promote when |
126
+ |---|---|---|---|
127
+ | 1 | **Doctrine line** in a reference file | reading attention | it was violated once |
128
+ | 2 | **Review question** at a named gate | a person's time, every run | no check can decide it — and say *why*, in one line |
129
+ | 3 | **Script check** in the project's gate | writing it once | the class has occurred **twice** |
130
+ | 4 | **CI step** | minutes per push | it must hold for people who never run it locally |
131
+ | 5 | **Hook** ([`hooks.md`](hooks.md)) | latency on every tool call | the failure is cheaper to prevent than to detect, and the target is an edit an agent is making now |
132
+
133
+ A rule may sit on several rungs. What it may never do is **pretend** to be on a
134
+ higher one: a doctrine line that reads as if it were enforced is the same failure as
135
+ a gate that prints `FAIL` and exits `0` — both report a world they are not looking
136
+ at.
137
+
138
+ **Rung 2 is where honesty is bought.** "No check can decide this" is a legitimate,
139
+ common answer. Written down with its reason, it is a finding somebody can later
140
+ disprove. Left unwritten, it is indistinguishable from an omission.
141
+
142
+ ---
143
+
144
+ ## Axis C — degrees of freedom
145
+
146
+ Axis B says how hard a rule bites. This one says how much latitude the *instruction*
147
+ leaves, and it is a separate choice: a low-freedom instruction guarded by nothing is
148
+ a wish, and a high-freedom instruction behind a blocking hook is a bottleneck.
149
+
150
+ Match the level to how **fragile** the step is, not to how important it feels:
151
+
152
+ | Level | Shape | Use when | Example here |
153
+ |---|---|---|---|
154
+ | **high** | prose direction, no prescribed sequence | many routes reach a good answer and context decides | stage 2 — the design conversation |
155
+ | **medium** | a named order with room inside each step | the sequence is fixed, the content is judgement | stage 0 — two phases, adaptive questions |
156
+ | **low** | run exactly this, in this order, no variation | the operation is fragile, irreversible, or must be identical every time | stage 5's TDD order · stage 7's deploy · stage 9's matrix walk |
157
+
158
+ The picture worth keeping is an **open field versus a narrow bridge**. In the field,
159
+ say where to go and let the agent find the route. On the bridge there is one safe way
160
+ across, and the guardrails are the instruction.
161
+
162
+ **Over-constraining costs as much as under-constraining and is harder to see.** A
163
+ high-freedom step written as low freedom produces an agent that follows the letter
164
+ past the point where the letter stopped fitting — and reports success, because it did
165
+ what it was told. Where a step is genuinely open, say so out loud; that sentence is
166
+ what stops the next reader from hardening it.
167
+
168
+ Every stage in [`stages.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/stages.md) declares its level and its reason, on the
169
+ line under its heading.
170
+
171
+ ## Progressive arming
172
+
173
+ A gate seeded into a young project has almost nothing to check yet, and a gate that
174
+ starts red teaches everyone on day one that it is noise ([`learned.md`](learned.md)
175
+ rule 9). So each section reports one of four states and only one of them fails:
176
+
177
+ | State | Means | Fails? |
178
+ |---|---|---|
179
+ | `ok` | the check ran and passed | no |
180
+ | `dormant: … — no <artefact> yet` | the input does not exist yet | no |
181
+ | `skip: … — <why>` | the input exists, the check could not run here | no |
182
+ | `ERR` | the check ran and found something | **yes** |
183
+
184
+ `dormant` and `skip` are **printed, never silent** — that is the whole reason they do
185
+ not quietly become permanent.
186
+
187
+ They also force one more obligation on the verdict line: it must report **what the
188
+ run actually looked at**. Every section dormant is indistinguishable from a gate
189
+ blind to the shape in front of it, and exit 0 alone cannot tell those two apart.
190
+
191
+ ## Before you run a check
192
+
193
+ Four preconditions. Skipping any of them turns a run into a claim.
194
+
195
+ 1. **The base is green** — or its known-red baseline is *recorded*. A new guard
196
+ added to an already-red base passes for the wrong reason and proves nothing.
197
+ 2. **The check has been probed.** Green from a check nobody has watched fail is
198
+ worth nothing. If you did not plant the defect, you do not know what the green
199
+ means. How to plant, what a probe owes, and every way one rots is
200
+ [`probing.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/probing.md) — the harness is `test/probe.py`
201
+ (`npm run test:probe`), and hand-rolling a fourth copy of its loop is how
202
+ three probes in one day proved the wrong guard.
203
+ 3. **You have read its scope header** and know what it does **not** cover. A gate
204
+ is evidence for exactly the surface it walks; quoting it beyond that is how
205
+ "the gate is green" becomes a false statement made in good faith.
206
+ 4. **You have read the ratchet floors.** A pass with a floor that was quietly
207
+ raised is a pass over the thing the floor was hiding.
208
+
209
+ **And one rule for the moment after.** A new check that goes red on the day it is
210
+ written invites one reflex — *the check must be wrong, relax it*. Sometimes the
211
+ check **is** wrong, **and the red is still the finding.** A guard asserted two
212
+ constants describing "the price of one unit per year" were equal; they belong to
213
+ two different products, so the premise was wrong and the obvious move was to
214
+ delete the assertion. Reading further showed the disagreement was **visible to
215
+ customers**: both products still sell from one page, and the note under the volume
216
+ table promised a discount computed from the header's price while the table's own
217
+ numbers gave roughly half of it. Nothing miscalculated — only the claim on the page
218
+ was false.
219
+
220
+ So: **separate the premise from the observation before touching either.** Ask what
221
+ the check *saw*, not whether it was entitled to look. Relax or delete the assertion
222
+ only after the thing it surfaced has its own record — otherwise the finding leaves
223
+ with the check that found it, and nothing remembers it was ever seen.
224
+
225
+ ---
226
+
227
+ ## False success — when a mechanism reports a win it never checked
228
+
229
+ The four preconditions above protect a *check*. The same law binds an **action**:
230
+
231
+ > **An actor's own reply is not evidence about the world. Confirm an effect by
232
+ > re-reading the state it changed.**
233
+
234
+ A failure is loud and gets fixed on the pass that finds it. A false success is
235
+ silent and **removes the reason to look**, which is why every shape below survived
236
+ at least one release in this repository:
237
+
238
+ | Shape | What reported success | What was actually true |
239
+ |---|---|---|
240
+ | Fail-open hook | any exit code but `2` is non-blocking, so a **crashed** guard *allows* the action | the guard never ran |
241
+ | Teardown by reply | a cancel accepted an id that was never scheduled and returned success | the job was still armed |
242
+ | Presence instead of absence | a counter asserted the new number was present, not that the old one was gone | four surfaces still printed the old number, green for three releases |
243
+ | Half-applied batch | a batch of edits reported done while one edit never applied (R-002) | the file was unchanged |
244
+ | Silence read as a pass | a section with no input printed nothing, and the caller counted it as checked | nothing was looked at |
245
+ | **Read through a pipe** | the caller read the **formatter's** exit code, not the gate's | `npm test 2>&1 \| tee ../test.log` under `bash -e` without `pipefail` concluded `success` over its own `# fail 55`; `check-docs.sh \| grep FAIL \| tail && git commit` committed over a `FAIL` printed to the author's screen |
246
+ | **An absence with no subject** | an assertion that a thing is gone, about a thing that exists nowhere | a viewport test asserting a column leaves the tree at 1279px passed at **every** width — the column had been deleted from the product months earlier |
247
+
248
+ **The test.** For any mechanism you are about to trust, ask:
249
+ *what does it print when it did not look?* If that is indistinguishable from what
250
+ it prints when it looked and found nothing wrong, it is not evidence — give it a distinct `dormant`
251
+ or `skip` state (→ *Progressive arming*), or verify the effect independently.
252
+
253
+ Four rules follow. Elsewhere in this bundle they are **cited, never restated**:
254
+
255
+ 1. **Verify by re-reading, not by the reply.** After a teardown, cancel, delete,
256
+ disable, publish or migrate: query the authoritative state and assert the item's
257
+ new condition.
258
+ 2. **Assert the absence of the old, not the presence of the new.** A check that only
259
+ proves the new value exists stays green while the old one is still shipping.
260
+ 3. **Read a gate's own exit code, never a pipeline's.** `set -o pipefail`, or
261
+ `${PIPESTATUS[0]}`, or do not pipe. GitHub Actions runs `run:` under `bash -e`
262
+ **without** `pipefail`, so `gate | tee` reports `tee`. This is the least visible
263
+ entry in the table because the command reads as diligence: `check.sh | grep FAIL`
264
+ looks like someone being careful, and it is the shape that reports success while
265
+ printing failure to the screen of the person who wrote it.
266
+ 4. **An absence assertion needs a subject that exists somewhere.** Before pinning
267
+ "X must not appear here", prove X appears *somewhere* — otherwise the assertion is
268
+ true for a reason unrelated to what it claims, and the complement of *watch the
269
+ green fail against a planted defect* is what catches it: that rule finds a check
270
+ that **cannot fail**, this one finds a check that **cannot succeed meaningfully**.
271
+ Both are invisible to every mechanical signal — the test is green, its name is
272
+ accurate, its code reads correctly.
273
+
274
+ ---
275
+
276
+ ## Anatomy of a project gate
277
+
278
+ Ten properties. Each one is here because its absence has shipped.
279
+
280
+ | Property | Rule | The failure it prevents |
281
+ |---|---|---|
282
+ | **Exit code** | non-zero on **any** failure | a gate that appended a check *after* its verdict block printed `FAIL` and returned `0`; CI was green over it for an unknown period |
283
+ | **Verdict last** | nothing may run after the verdict block | the same failure, from the other end |
284
+ | **Scope header** | states what the gate does **not** cover | a green quoted as proof of a surface nobody walked |
285
+ | **Portability** | POSIX + bash 3.2: no `grep -P`, no `sed -i`, no `readarray` | BSD `sed -i` needs an argument GNU refuses, and `0,/re/` does not exist there — it silently edits nothing and the check reads as a guard that failed to fire |
286
+ | **Ratchet floors** | `<NAME>_FLOOR` variables at the top; counts printed beside `OK` | a backlog that grows back without anyone explaining why |
287
+ | **Skips are printed** | a check that could not run says so | a submodule not checked out silently removing coverage |
288
+ | **Progressive arming** | a section with no input artefact prints `dormant: … — no <artefact> yet` and does **not** fail | a freshly seeded project starting red, which teaches everyone on day one that the gate is noise |
289
+ | **Computed, never restated** | derive every count at check time | two documents quoting a total that went stale |
290
+ | **Both directions** | any two-layer mapping is checked each way | four fully-specified entities with no schema anywhere — found only by the direction that felt redundant |
291
+ | **Named location** | every error prints file **and** line | a finding nobody can act on |
292
+
293
+ Shape:
294
+
295
+ ```bash
296
+ #!/usr/bin/env bash
297
+ # check-docs.sh — the documentation gate for <project>.
298
+ # SCOPE: walks <what>. Does NOT check <what>.
299
+ # Portable to macOS bash 3.2: no grep -P, no sed -i, no readarray.
300
+ set -u
301
+ FAIL=0
302
+ PROP_FLOOR=${PROP_FLOOR:-1} # ratchet: raising it is a decision
303
+
304
+ # ---------- 1. <name> ----------
305
+ ... # ok: / ERR: / skip: / dormant:
306
+
307
+ # ---------- VERDICT — nothing runs after this block ----------
308
+ if [ "$FAIL" -ne 0 ]; then echo "FAIL: <gate>"; exit 1; fi
309
+ echo "OK: <gate> — backlog: $BACKLOG (floor $PROP_FLOOR) · registers: $DECS decisions · $OQS open"
310
+ exit 0
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Writing the check itself
316
+
317
+ - **Pick the unit and say what it costs.** A check that scopes to a table *row*
318
+ will let one marker in that row exempt everything else in it. That is a real
319
+ blind spot; measured and accepted beats unmeasured and denied, so write it in the
320
+ comment.
321
+ - **Prefer a deterministic rule to a heuristic.** A parity-based check for
322
+ unbalanced markup produced six false positives out of six on a real corpus and
323
+ was discarded. If the rule cannot be stated exactly, that is information.
324
+ - **Never infer from strings the environment also produces.** Matching `"claude"`
325
+ in a process command line matched the throwaway shell of every tool call.
326
+ - **Compute the count you print.** A number restated in prose is a number that will
327
+ be wrong; derive it from the source at check time so the two cannot disagree.
328
+ - **Normalise the corpus's own formatting before you match, and say which unit you
329
+ chose.** A predicate written against the sentence you have in mind meets the sentence
330
+ as the file actually stores it: wrapped at some column, with emphasis, inside a table
331
+ cell. Three separate guards in this bundle were defeated that way and none of them by
332
+ its content —
333
+
334
+ | What defeated it | The guard | The fix |
335
+ |---|---|---|
336
+ | a citation wrapped across two lines | the section-citation check | normalise whitespace, match over the paragraph |
337
+ | a marker split by the ~80-column wrap | the distrust-marker check | same |
338
+ | `**five run stamps**` — bold inside the phrase | the cold-retirement check | strip emphasis too |
339
+
340
+ Each was silent, which is the expensive part: the guard reported green over a file it
341
+ had never read. Pick the unit deliberately — **line, paragraph, or whole file** — write
342
+ down which, and probe the shape the corpus actually contains rather than the shape you
343
+ typed into the regex. And plant the defect **in the file that defines the thing**, not
344
+ in the most convenient one: a probe against a surface with none of the formatting is a
345
+ probe that cannot fail for this reason.
346
+
347
+ ---
348
+
349
+ ## A ratchet prices the rule, not the exception
350
+
351
+ A floor that counts assertions is a floor that can be lowered by improving the code, if
352
+ the assertions are attached to the wrong things.
353
+
354
+ Measured: a coverage check called `check()` once per *exception* — a silent value, a kept
355
+ value, a promise — and simply `continue`d on the ordinary case. Collapsing four kept
356
+ values, **the remediation the requirement names first**, therefore dropped the count by
357
+ four against its floor and turned the suite red on the stricter answer. The only way
358
+ through was lowering a ratchet whose own reason says a falling count is how a deleted
359
+ requirement hides — so a legitimate lowering and the failure the floor exists to catch
360
+ became indistinguishable.
361
+
362
+ **One assertion per subject examined, whatever its verdict.** The ordinary case asserts
363
+ too. Then the floor is a function of how large the corpus is, not of how many exceptions
364
+ it happens to contain, and doing the right thing can never lower it.
365
+
366
+ **And measure the floor after the last edit, not before it.** Read the count, keep
367
+ editing, restate the count you read — every floor set that way sits below the true one,
368
+ and a floor is a minimum, so nothing ever says so. Where the gate cannot enforce
369
+ equality — it must not, or the ratchet stops allowing growth — it can still **print the
370
+ gap**: `floor 4026, ran 4027: 1 check is not pinned` turns a silent difference into a
371
+ visible one for the cost of one line.
372
+
373
+ ## Run the whole suite locally before you push the tag
374
+
375
+ Not a preference: an arithmetic. A release workflow that runs the full suite takes
376
+ twenty-five to forty minutes per round, and it reports one failure at a time. The same
377
+ suite on the machine that wrote the change takes twelve and reports all of them at once.
378
+
379
+ On 2026-08-22 one tag took **five CI rounds** — a stray key in a `run:` block, a missing
380
+ run stamp, a stamp cap, and then four rotted probes — where a single local `test:all`
381
+ before the first push would have found the last four together. Every refusal was correct.
382
+ The cost was entirely in asking the wrong machine.
383
+
384
+ **A green local suite is not evidence until you know which checks LOOKED.** This
385
+ instruction failed on its own release: the local run was green, CI was not, and the
386
+ difference was a precondition asking `os.path.isdir(".git")` — false in a submodule
387
+ checkout, where `.git` is a *file* holding a gitdir pointer. One check switched itself
388
+ off in the only checkout the family is developed in, silently, and had been doing so
389
+ since it was written. The repository had recorded that class **twice** already, in two
390
+ other files, and this instance was missed both times: knowing a class is not sweeping
391
+ it. Two consequences, and the second is the general one:
392
+
393
+ - ask `exists`, never `isdir`, of anything named `.git`;
394
+ - **a precondition that fails must disclose, not skip.** Where a check cannot run, it
395
+ appends to the unlooked list and the run prints it. A check guarded by a bare `and`
396
+ evaporates without a line of output, which is the one thing this file's own canon
397
+ forbids — and it evaporates most reliably in the environment its authors use.
398
+
399
+ The rule has a second half, and it is the one that makes it stick: **a tag is the only
400
+ thing that runs some checks.** A branch push cannot see a tag that does not exist yet, so
401
+ the tag-ancestry check, the version-sync check and the run-stamp check have no earlier
402
+ opportunity to fire. Locally, run them the way the release does — against the tree you are
403
+ about to tag, with the suite the release claims.
404
+
405
+ ## A ratchet's matcher is itself a check, and it needs a near-miss
406
+
407
+ Reported from another project through `retro.publish`, and it is the neighbour
408
+ probe's own class ([`probing.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/probing.md) → *The neighbour probe*) arrived at
409
+ independently — which is the strongest evidence either has.
410
+
411
+ A run built a ratchet to hold a coverage debt: a list of units with no test, a guard that
412
+ fails when the list grows, a count printed at the gate. Exactly the shape
413
+ [`audit.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/audit.md) asks for instead of a deferred TODO. The guard decided whether a
414
+ unit was covered by asking whether its identifier appeared **anywhere** in the test
415
+ corpus. The identifiers were path-like and many were prefixes of longer ones, so every
416
+ unit that happened to be the parent of another was credited with its child's coverage.
417
+
418
+ **A ratchet whose matcher is looser than its subject shrinks itself.** It reports progress
419
+ for work nobody did, and because a ratchet is trusted precisely so that nobody re-derives
420
+ it, the error compounds for as long as the ratchet exists.
421
+
422
+ Both existing rules were satisfied. The ratchet was printed. The guard had been seen going
423
+ red when the list grew. Neither asks whether the matcher can tell its subject from a near
424
+ neighbour, and that is the only question that would have caught it.
425
+
426
+ **So before a ratchet is kept, feed its matcher a near-miss it must reject** — the prefix,
427
+ the parent, the same name in a comment or an import, the longer extension. Seeing a guard
428
+ go red on a real change proves it **reacts**; seeing it stay green on a look-alike proves
429
+ it **discriminates**. Only the second makes its number worth trusting.
430
+
431
+ **And when a matcher is corrected, re-derive the whole ratchet and print both numbers with
432
+ the reason.** In the reporting project the corrected count was *identical* to the old one
433
+ and the composition was not: rows credited falsely came back in as rows genuinely paid off
434
+ went out. A single number with no delta reads as a run where nothing happened.
435
+
436
+ ## The false-positive budget
437
+
438
+ Run a new heuristic over the **real corpus** before shipping it and count the false
439
+ positives. Zero, or replace the heuristic with a deterministic rule.
440
+
441
+ The budget is not perfectionism. A gate that cries wolf is switched off by the
442
+ third person who hits it, and after that it protects nothing while still appearing
443
+ in the pipeline as a control. A noisy check is worse than no check, because it also
444
+ consumes the credibility of the checks beside it.
445
+
446
+ ---
447
+
448
+ ## Ratchets
449
+
450
+ A **ratchet** is a named, counted set that may only shrink, printed on every run.
451
+
452
+ - Its floor is a **variable at the top of the script**, so raising it is a visible
453
+ edit and a decision.
454
+ - Its count is printed **beside the verdict**, so `PASS` never reads as *verified*
455
+ — it reads as *"green, and here is exactly what was not looked at"*.
456
+ - A ratchet that grew needs a sentence in the run log saying why.
457
+
458
+ ```
459
+ GATE 9 docs: PASS — propagation backlog: 121 (was 162) · unmarked residue: 0
460
+ abstained: 0 · unlooked: 4 (3 dormant · 1 skip — no submodules in this repo) · holds: 0
461
+ ```
462
+
463
+ A ratchet nobody prints is a TODO with a better name.
464
+
465
+ ## The result is the goal; the check is how you know
466
+
467
+ Everything else in this file pushes one way: prove more, assume less. Read alone it
468
+ has an obvious failure mode — a run that spends its afternoon proving a
469
+ one-character change and never ships the thing it was asked for. **The deliverable
470
+ is the working result, as described in the brief. A check is how the run knows it
471
+ has one. A check that is not buying that knowledge is not diligence; it is the run
472
+ optimising the wrong thing.**
473
+
474
+ **Scale the check to what breaking costs, and the project already computes that.**
475
+ The board ranks by `sev × blast` ([`backlog.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/backlog.md)). The same two inputs
476
+ size the verification:
477
+
478
+ | What breaking costs | What the check has to be |
479
+ |---|---|
480
+ | an outward, irreversible or shared-state effect — deploy, publish, a lease, another agent's file | proven: watched failing against a planted defect, and re-read rather than trusted from the reply |
481
+ | a contract other code depends on | an executed test, named in the REQ row |
482
+ | behaviour a person will see | observed on the surface — a browser, the actual output — not inferred from a diff |
483
+ | a typo, a comment, a rename the compiler checks | the compiler, the suite already running, and nothing more |
484
+
485
+ **Four things that mean you have crossed over**, and each has cost this project a
486
+ run:
487
+
488
+ - **A third pass over the same axis finds mostly what the last pass's fixes broke.**
489
+ The axis is exhausted — rotate it or stop ([`audit.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/audit.md)).
490
+ - **The check is being widened after it went green**, with no failure in hand. A
491
+ check widened by imagination is a check whose scope nobody has measured.
492
+ - **The evidence is being gathered for a claim nobody made.** If no REQ row and no
493
+ gate criterion asks for it, it is not evidence — it is browsing.
494
+ - **The run is on its second measurement of the same number.** One measurement plus
495
+ what it does not cover, stated, beats two measurements and no decision.
496
+
497
+ **This never licenses skipping a gate.** The gates are the floor, and the floor is
498
+ not proportionate to anything — a `manual` gate waits, `unknown` fails stage 10, and
499
+ a green nobody watched fail is not evidence at any blast radius. What is
500
+ proportionate is the work *above* the floor: how many axes, how many passes, how
501
+ much of the corpus. Cutting the floor to go faster is not speed; it is the failure
502
+ this whole file exists to prevent, arriving on schedule.
503
+
504
+ **Where it is recorded.** Stages 3 and 4 carry a `Cost:` line —
505
+ `<surfaces>/<guards>/<REQ> now, <…> at stage 2 — <proportionate | grown, and why>`.
506
+ *Grown, and why* is the honest answer often enough that it is written into the form.
507
+
508
+ ## Disclosures — counted like a ratchet, and deliberately not monotone
509
+
510
+ A ratchet may only shrink. **Some numbers must not be**, and printing them under a
511
+ ratchet's discipline inverts the thing they measure.
512
+
513
+ **Abstention is the case that matters.** This bundle has eight vocabularies for declining
514
+ to claim — `partial`, `unknown`, `cannot verify from diff`, `review`, `dormant`, `skip`,
515
+ `recalled`, `ungated` — and until they were counted, none of them appeared beside a
516
+ verdict. So `PASS` read as *verified* rather than as *"green, and here is what nobody
517
+ claimed"*.
518
+
519
+ The obvious fix is a ratchet, and it is wrong. A count of abstentions that may only shrink
520
+ puts pressure on exactly one thing: **claiming more**. A run reaching `abstained: 0` is not
521
+ more careful; it is a run that stopped saying *I don't know*, which is the cheapest way to
522
+ make the number fall. Refusals and wrong answers are communicating vessels — squeeze one
523
+ column and it reappears in the other, silently, because a wrong claim looks like a claim.
524
+
525
+ So a **disclosure** is printed beside the verdict like a ratchet and carries the opposite
526
+ rule: **no floor, no direction, and a movement in either direction wants one sentence.**
527
+
528
+ The disclosures below are kept separate because they are different facts — the list is the count:
529
+
530
+ | Disclosure | Counts | Reading it |
531
+ |---|---|---|
532
+ | `abstained: N` | claims the run **declined to make** — `partial`, `unknown`, `cannot verify from diff` | a *choice*. Rising can mean the work got harder or the run got honest; falling can mean either the reverse |
533
+ | `unlooked: N` | checks that **did not look** — `dormant`, `skip` | a *state of the corpus*, not a decision. It falls as the project grows the inputs those checks need |
534
+ | `holds: N` | what the run left **running or lying about** — the eight classes in `references/residue.md` | a *state of the environment*, not of the corpus or of the run's claims. A legitimate 2 during a build beats a manufactured 0; only stage 10 requires it to reach zero or name an owner per item |
535
+
536
+ Three are deliberately **not** counted, and saying which is part of the disclosure:
537
+
538
+ - **`review`** — *no check can decide this* — is an abstention, and it is the one this
539
+ section first listed and then forgot, which is exactly the failure it exists to catch.
540
+ It stays out of `abstained` because it is not a claim the run declined: it is a rule
541
+ that **declined to be mechanical**, recorded once at rung 2 with its reason (→ *Axis B*).
542
+ Counted per run it would report the same standing number every time and say nothing
543
+ about the run.
544
+ - **`recalled`** — a property of one claim, already carried in the ledger beside the
545
+ command that would re-derive it.
546
+ - **`ungated`** — a property of the whole run, said once, in words.
547
+
548
+ A vocabulary that is named and then left out of every bucket is the one that goes
549
+ uncounted forever. So each gets its line, including the one that got missed here.
550
+
551
+ **What makes a disclosure honest rather than decorative** is the same thing that makes a
552
+ ratchet honest: it is *computed*, and it is printed whether or not anyone likes the
553
+ number. What makes it different is that **nobody may set a target for it.** A target on an
554
+ abstention count is an instruction to guess.
555
+
556
+ ---
557
+
558
+ ## Where a gate runs
559
+
560
+ | Place | Good at | Limit |
561
+ |---|---|---|
562
+ | **Local pre-commit** | fast feedback for the author | skippable, and skipped exactly when someone is in a hurry |
563
+ | **CI** | authoritative; holds for people who never run it locally | minutes late, and it checks out the repo in a shape the author's machine never has — rehearse that shape |
564
+ | **Hook** ([`hooks.md`](hooks.md)) | stops the edit *before* it happens | Claude Code only; a crashing hook **fails open** |
565
+ | **Stage gate** (this pipeline) | judgement, and the things only a person can answer | it is the run's own memory, not the repository's |
566
+
567
+ The four are not alternatives. The same rule can be a hook for the agent, a
568
+ pre-commit for the human and a CI step for the record — what it must never be is
569
+ *declared* in one place and *enforced* in none.
570
+
571
+ ---
572
+
573
+ ## Adding a check to an existing gate
574
+
575
+ 1. **Name the class** — the shape, not the instance. "This id is undefined" is an
576
+ instance; "an id referenced and never defined" is a class.
577
+ 2. **Find the unit** the check will parse: a line, a table row, a paragraph, a
578
+ file. Write down what that unit will miss.
579
+ 3. **Write the predicate deterministically**, with the file and line in the error.
580
+ 4. **Measure it** over the real corpus; zero false positives or rewrite it.
581
+ 5. **Plant, run, restore** — both directions observed, and recorded.
582
+ 6. **Wire its count into the verdict line**, with a floor if it cannot be zero yet.
583
+
584
+ Step 6 is the one that gets skipped, and it is the one that makes the check
585
+ survive: a number beside `OK` is read every run, and a check nobody sees the output
586
+ of is deleted in the next refactor by someone who assumed it was dead.
587
+
588
+ ---
589
+
590
+ ## Rationalizations
591
+
592
+ | Excuse | Reality |
593
+ |---|---|
594
+ | "The check is green, that's evidence" | Only if you have seen it red. An unproven check is a decoration that reports success. |
595
+ | "It printed FAIL, so it failed" | CI reads `$?`. A gate has shipped that printed `FAIL` and exited `0`, and nobody noticed for an unknown number of runs. |
596
+ | "I'll write the check later, the rule is documented" | Then it is on rung 1 and behaves like rung 3 in everyone's head. That gap is the whole failure. |
597
+ | "It's one occurrence, a note is enough" | It is. On the second, the note becomes a script — that is the rule, and the third occurrence is proof it was ignored. |
598
+ | "The heuristic mostly works" | Measure it. Six false positives out of six on a real corpus is what "mostly" felt like from inside. |
599
+ | "The gate would be red on day one, so I'll add it later" | Make the section dormant instead. Dormant is visible and green; "later" is neither. |
600
+ | "I raised the floor to get the build green" | Then say so in the log, in the same commit. A floor raised silently is a ratchet running backwards. |
601
+ | "A hook is overkill, CI catches it" | CI catches it after the edit, the commit and the push. If the point is to stop the edit, CI is the wrong rung — and if it is not, do not pay the latency. |
602
+
603
+ ---
604
+
605
+ ## Cross-cutting, at every stage
606
+
607
+ **When anything is settled — scope, a contract, a
608
+ name, a policy, a vocabulary — run the Doc Loop
609
+ (`references/documentation.md`) before the run moves on**: reserve the id,
610
+ record it, resolve the question it answers, propagate by the matrix, commit
611
+ with the ids. A decision that lives only in the spec dies with the spec, and one
612
+ that lives only in the conversation was never made;
613
+ **answer from the brief's autonomy section rather
614
+ than asking again** — it was grilled precisely so you wouldn't have to;
615
+ **anything deferred, dropped or left half-done goes into the carry-over ledger
616
+ the moment it's said** — deferred out loud is forgotten; **never narrow the task
617
+ silently** — the REQ list is frozen, adding is free, removing needs the
618
+ operator's explicit agreement; **when a loop starts undoing an earlier pass —
619
+ the same file edited twice for the same reason, a closed finding coming back, a
620
+ third entry into one stage — stop and run the loop guard**
621
+ (`references/loop-guard.md`): name the two shapes, escalate to the layer that
622
+ owns the conflict, re-plan the check as an ordered list, then go through it one
623
+ item at a time; **when a pass is *searching* rather than editing and starts
624
+ finding mostly what the previous pass's own fixes broke, the axis is exhausted —
625
+ rotate it, don't look harder** (`references/audit.md`); **every gate
626
+ prints `holds: N` — what this run left running** across the eight classes
627
+ (background shells, monitors, scheduled loops, coordination leases, worktrees,
628
+ containers, scratch files, remote state), enumerated **by class and never by a
629
+ single tool**, and stage 10 does not close while this run's residue is live and
630
+ unaccounted (`references/residue.md`); and remember that a
631
+ green from a check nobody has watched fail is not evidence; task
632
+ tracker + conventional commits per host conventions; worktree isolation for the
633
+ build, integrated back per the brief's branch policy before stage 7; honest
634
+ degradation (never claim a failed/skipped step succeeded);
635
+ outward/irreversible actions (deploy, publish, repo create, opening a PR,
636
+ **editing a shared design file — frames are read by designers and stakeholders,
637
+ so drawing in one is publishing — and above all *creating* one, which needs a
638
+ named team and never happens while a recorded file resolves**) need explicit
639
+ operator go — or a **specific** standing authorization recorded in the brief
640
+ (named target + preconditions; a vague "do everything" is not one).
641
+
642
+ Moved out of `SKILL.md` on 2026-08-16 for the same budget reason as the
643
+ multi-repository block: these fire at any stage, so they belong with the gate
644
+ doctrine rather than inside step 5 of the run order.
645
+