task-pipeline-skill 1.80.0 → 1.82.4

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,303 @@
1
+ ## v1.82.4 — the plant the harness could not see
2
+
3
+ `v1.82.3` is a dead tag, and its cause is the **fourth distinct one**. Its stamp was
4
+ correct and in the right order: the payload merged as `9e2ab3e`, the stamp cited it and
5
+ landed as `e4e0eb4`, the tag was cut there, and the gate had accepted that tree.
6
+
7
+ What failed was a negative self-test added in the same programme. `test/negatives.py`
8
+ locates a plant's scratch directory by searching its script for a literal
9
+ `cp -R . <dir>`, then diffs that copy against the repository to tell a real defect from a
10
+ no-op. The duplicate-id plant copied **one file** and mutated the live tree, restoring it
11
+ afterwards — correct as a workflow step, invisible to the harness. With no scratch
12
+ directory it could not confirm the plant had landed, and reported the guard as one that
13
+ **did not fire**. Correctly, by its own rule.
14
+
15
+ The plant now follows the convention every other one uses: copy the tree, mutate the
16
+ copy, run the validator from the copy, leave it mutated. Verified with the harness itself
17
+ before shipping — `PASS: a board id naming two rows must fail`, the step that had never
18
+ been run locally across four attempts.
19
+
20
+ **`npm test` cannot see this**, because `negatives.py` is a CI step and not part of the
21
+ suite. Four dead tags, four different mechanisms — a stamp in the wrong shape, a tag cut
22
+ before the stamp existed twice, and now a plant the harness cannot verify — and one
23
+ shared shape behind all of them: a green local gate that does not cover what the release
24
+ runs. `B-134` carries it, and now asks for a preflight that runs the plant harness too.
25
+
26
+ Guards: 424 → **424** — the plant is repaired, not added.
27
+
28
+ **Landed 2026-09-02, and the release-gap check refused this tree first.** The repair above
29
+ was green on every branch check and still declared no stamp for the release it was
30
+ creating — a fifth instance of the same shape, caught because the tag was cut locally and
31
+ `npm run test:all` was run against the tag's own tree before anything was pushed. The
32
+ preflight `B-134` asks for is therefore not a new check: it is `test:all` on that tree,
33
+ which is the only tree where four of the five failures can be seen at all. `npm test` runs
34
+ three of its thirteen.
35
+
36
+ ## v1.82.3 — the release protocol is two merges, and their order is written down now
37
+
38
+ **`v1.82.2` is a dead tag for the same reason `v1.82.1` was, and the run that burned it
39
+ had just written the rule into this file.** A release here needs a stamp on the tag's own
40
+ tree; the declaration v1.82.2 carried was for v1.82.1, the tag already dead. The payload
41
+ merged as `9195590`, the tag was cut there, and nothing in that tree stamped the run.
42
+
43
+ **The mechanism, stated so nobody pays for it a fourth time.** A stamp must cite a commit
44
+ inside the NEW tag's own range, and that commit does not exist until the payload merges —
45
+ the ruleset rebase-merges and mints every SHA at merge time. So a release is **two merges
46
+ and their order is load-bearing**: the payload lands as `X`, a second change stamps the
47
+ run citing `X` and lands as `Y`, and the tag is cut on `Y`.
48
+
49
+ **Nothing refuses the wrong order before the tag exists.** `validate.yml` ignores tag
50
+ pushes; `release.yml` is the first thing to run on the tag's own tree. So the branch is
51
+ green either way and the cost is always a burned version, because the ruleset forbids
52
+ deleting a tag. Three in one day: `v1.82.0` (stamp written in the sibling repository's
53
+ bullet shape into a section this gate reads as a table), `v1.82.1` and `v1.82.2` (tag cut
54
+ on the payload commit). That gap is now `B-134`, which asks for a preflight that resolves
55
+ the range the tag would create and asserts a stamp falls inside it — a check that can run
56
+ *before* anything is spent.
57
+
58
+ Guards: 424 → **424** — this release records a protocol and adds no mechanism; the
59
+ mechanism it asks for is the board row.
60
+
61
+ ## v1.82.2 — one id, one finding, and a status read from the wrong place
62
+
63
+ **A board id identified two findings for the whole of v1.82.x.** `B-114` and `B-115`
64
+ each named two rows: the pair filed on 2026-08-31 from the harness-engineering
65
+ articles, and the pair this programme appended on 2026-09-01 without reading what was
66
+ already there. Four rows, two ids — so a run stamp saying *"B-115 filed"*, a CHANGELOG
67
+ naming the range `B-114…B-117`, and a sibling board citing either one all pointed at
68
+ two different findings at once. The newer pair is renumbered `B-119` and `B-120`, and
69
+ the stamp that cited them is repointed in the same change.
70
+
71
+ **The guard, because renumbering fixes the case and not the class.** `test/validate.py`
72
+ collected board ids into a **set**, where a repeat collapses leaving no trace — which
73
+ is precisely why this shipped and stayed invisible. The new check reads row-leading ids
74
+ only (`| B-NNN |`, first cell), so a prose citation of another row stays a citation
75
+ rather than becoming a second definition, and it names both line numbers in its
76
+ refusal. Watched failing before it shipped: a planted collision returns
77
+ *"docs/evidence/backlog.md:110: id B-119 identifies a second row (the first is line
78
+ 109)"*. The umbrella has carried this guard for its own board; this repository is where
79
+ the defect actually landed, and it had none. Guards: 423 → **424**.
80
+
81
+ **Shipped as 1.82.2 because `v1.82.1` is a dead tag.** Its tree carried no stamp for its
82
+ own run, so the release suite — which runs on the tag's own tree, where `validate.yml`
83
+ never looks — refused it. The round was not lost to skipping the check: the gate **was**
84
+ run against the merge commit, and its exit status was read from a trailing `echo` in the
85
+ same command rather than from the suite, so a zero belonging to the `echo` was taken for
86
+ the suite's verdict. An explicit re-run returned 1 seconds later. The rule from v1.82.0
87
+ therefore gains its second half: verify with the gate rather than a grep of your own
88
+ prose, **read the gate's own exit status**, and cut the tag locally, run the suite on the
89
+ tag's tree, and push only then — a local tag can be moved, a pushed one cannot.
90
+
91
+ ## v1.82.1 — the second axis of blindness, and the scope the two-severity rule never had
92
+
93
+ **This release is 1.82.0 and not 1.81.0, and the reason is worth recording.** 1.81.0 was
94
+ already in flight in a concurrent session — the anchors-are-derived release — and this run
95
+ picked the same number by reading `package.json` at branch time without asking whether
96
+ anyone else held it. Worse, the tag was pushed **immediately after a `gh pr merge` that had
97
+ printed a conflict and not merged**, so `v1.81.0` landed on the other session's commit and
98
+ started its release before its run stamp was written. That release failed on exactly the
99
+ guard that exists for it — *a release named nowhere in `Releases that carry no stamp`* — and
100
+ published nothing. The tag cannot be deleted (a repository rule refuses it), so 1.81.0 stays
101
+ spent and this work moves up. Standing instruction #8 in the umbrella's retro covers the
102
+ mechanism: a wrapper's exit status is not the verdict, and it was not read.
103
+
104
+ **Shipped as 1.82.1 because `v1.82.0` is a dead tag, and the reason is a check that read the
105
+ wrong thing.** The run stamp for this release was written as a **list item** — the shape the
106
+ sibling `sshlg-skills` uses — into a section this repository keeps as a **table**. The release
107
+ suite therefore found no stamp covering the tag, refused it, and published nothing. Worse than
108
+ the format slip: the verification run before tagging grepped for the stamp's own title and
109
+ reported it present. **That confirmed the text existed, not that the gate could read it.** The
110
+ tag cannot be deleted, so it is declared in `retro.md`'s no-stamp section and the payload moves
111
+ one patch up.
112
+
113
+ Guards: 423 → **423** — this release adds doctrine and no mechanism. Both findings argue
114
+ against a rule rather than against a missing check, and the vertical blindness rule is
115
+ explicitly one `certify` cannot verify from a report.
116
+
117
+ **`certification.md` decoupled the three readers from each other and never decoupled the
118
+ fixer from the grader.** Tier blindness is horizontal — no tier reads another's report, and
119
+ `certify` refuses a report whose prose cites another tier. Nothing said the *vertical* half:
120
+ the agent that produced a change may not be the agent that certifies it.
121
+
122
+ That gap matters because the optimisation which closes it is attractive. The fixer already
123
+ holds the context; a fresh reader has to re-derive it. Taking that saving converts
124
+ certification into self-assessment **while every report it writes stays sincere** — an
125
+ optimiser that grades itself learns to game the metric rather than improve the work, and
126
+ `certify` cannot detect the collapse from a report. It is now a dispatch rule beside the
127
+ one it belongs with.
128
+
129
+ **And the two-severity rule shipped without its scope.** *There are two severities and no
130
+ third, because a certification that admits a maybe admits everything* is correct — for a
131
+ **finding**, which is a claim a reader makes about a diff and either blocks or does not.
132
+ It is the wrong shape for the **verdict of a check** whose subject is non-deterministic,
133
+ where binary pass/fail has been measured at **0% detection** of regressions and trace
134
+ fingerprinting at **86%**. A third value there is not laxity: `INCONCLUSIVE`, grounded in
135
+ hypothesis testing, is how a run says *this sample cannot decide* instead of flipping a coin
136
+ and reporting the result as a severity. Sequential testing cuts the trials it needs by
137
+ **78%**.
138
+
139
+ So the rule now carries its boundary: **two severities for a finding, three verdicts for a
140
+ stochastic check**, with the second axis pointed at `agent-stack`'s `agent-evals` —
141
+ `references/statistics.md` for how many runs make a difference real — and at the skill's
142
+ `references/gates.md` → *Axis A* for where such a gate belongs.
143
+
144
+ **A fourth invisible class, with a measurement.** *Why one verifier is not enough* listed
145
+ three defects a diff-reader cannot see; the fourth is the one a blended score cannot see
146
+ either: **the internal state is right and the message about it is stale.** The value was
147
+ stored correctly and the final sentence to the user echoed the old one — passing on state,
148
+ failing on truth, so any grader that averages the two calls it mostly fine. It was caught by
149
+ a rubric with categorical outcomes rather than a number, and closing it moved the violation
150
+ rate from **21% to 4%**. Its general form is this pipeline's own worst case: work that
151
+ **looks like it is working** while quietly missing what was asked — which is the product
152
+ tier's whole job, and why the tier that reads no code is not the soft one.
153
+
1
154
  # Changelog
2
155
 
156
+ ## v1.81.1 — the anchors are derived, and a dormant plant stops reading as a pass
157
+
158
+ > **Why 1.81.1 and not 1.81.0.** A `v1.81.0` tag was cut from outside this run, against a tree that did not yet carry the run stamp, and its release round failed on the release-gap check. Nothing published; npm never left 1.80.0. The tag ruleset forbids deletion, so `v1.81.0` is declared dead in `docs/evidence/retro.md` → *Releases that carry no stamp* and this version carries the payload.
159
+
160
+ Board row **B-113**, filed 2026-08-22 and confirmed four times since: *negative-test
161
+ anchors are pinned to literals that the releases they guard move, so a release
162
+ disarms its own checks.* Every confirmation had the same cause and the same reason
163
+ nobody noticed — the plants were disarmed by the repository getting **healthier**.
164
+ The board re-derived its ages and `bd5`'s needle stopped existing; the first blind
165
+ eval runs shipped and `pf1`'s guard went dormant; a release finally carried an honest
166
+ run stamp and `gap1`'s precondition emptied. All four were found by the 35-minute
167
+ negatives suite at release time, one release after the damage, and until it ran
168
+ everything was green.
169
+
170
+ **The census, measured rather than estimated.** `test/anchors.py` reads all 422
171
+ negative self-tests out of the workflow and answers the row's own question — *is the
172
+ number the plant WRITES, or the number it LOOKS FOR?* — from the AST rather than by
173
+ grep. A needle is only what a plant reads out of a file, before it writes that path:
174
+ provenance is tracked from `open(...).read()` through derived names, read-backs are
175
+ tracked per path (a plant-wide "after the first write" rule exempts `pf1`'s second
176
+ file, which is a genuine anchor), regex shape is stripped (`[0-9a-f]{7,40}` is a
177
+ shape, `\u2192` in a raw string is an escape and not the year 2192), and a
178
+ `validate.py | grep 'message'` is assertion 3 rather than a needle. Counted over the
179
+ corpus at the commit this release branched from: **26 of 419 plants pinned a value a
180
+ release can move.** The row's own count of 27 came from a grep that both over- and
181
+ under-reported — it missed `bd5`'s single-digit `| 1 | **6** |` entirely — and this
182
+ release's own first figure of 21 was wrong for the same reason one level in: the
183
+ detector could not see a needle read through `enumerate()`, `pathlib` or a compiled
184
+ pattern. The R-005 reader found that by fixture, and it is why the number moved twice
185
+ before it was true.
186
+
187
+ **25 of the 26 are derived now**, one declared. Each reads its anchor out of the tree at run
188
+ time: the high-water mark by shape, the rule number the stage map binds, the seeded
189
+ templates' open row and worked example, the carry-over ledgers' board pointers, the
190
+ live board's rows, the criterion numbers, the caps, the container count and its date,
191
+ the recipe fence. The live-board plants were the sharpest: `bd10` pinned `| B-008 |`
192
+ and `tbl`/`tbl2` pinned `| B-015 |`, so all three would have stopped landing the day
193
+ those rows closed. One literal stays and says so: `res15` pins `^13\.` because the
194
+ criterion NUMBER is the *guard's* contract — `test/validate.py` locates the teardown
195
+ criterion as `^13\.` and its refusal names 13 — and deriving it would prove some other
196
+ criterion's guard fired instead.
197
+
198
+ **Dormant is no longer counted as passing.** `test/negatives.py` folded every plant
199
+ that printed `SKIP` into *all N guards provably reject their planted defect*: a check
200
+ that could not construct its precondition, reported inside the claim that every check
201
+ rejected one. Skips now have their own bucket, print with the reason they gave, and
202
+ the aggregate says *N of M · K DORMANT, named above — not counted as passing*. In CI
203
+ each plant is its own step and a green step carrying SKIP is invisible, so the half
204
+ that is checkable everywhere is the declaration: every plant that can decline to run
205
+ carries `# dormant-when: <the state it cannot construct, and what that costs>`, and
206
+ all four now do.
207
+
208
+ **The class is self-reporting, in the cheap gate rather than the 35-minute one.**
209
+ `test/validate.py` refuses a plant whose needle pins a moving value unless it declares
210
+ why, refuses a declaration that resolves to nothing the plant reads, refuses a skip
211
+ branch with no dormancy declaration, and refuses an empty census — because "no anchors
212
+ found" and "the parser matched nothing" are otherwise the same sentence. Four checks
213
+ ride into the suite with it, each watched red before shipping, and `test/anchors_test.py`
214
+ is **47 whole-workflow fixtures, 27 of them watched firing** — a number that itself moved
215
+ three times as the reader found what the census could not see. **Eighteen of the 45 cases assert SILENCE, and 16 of
216
+ those 18 are retractions** — the other two are positive controls (`a derived needle passes`,
217
+ `a declared anchor is accepted`) (the other two of the 47 checks are the empty-corpus
218
+ census and a source check, which assert neither) — a `{7,40}` quantifier read as a year, `\u2192` in a raw
219
+ string read as 2192, a payload built from local strings, a `validate.py | grep` read as a
220
+ needle, a plant echoing `B-008` into its own replacement, `json.dump(d, open(p, "w"))`
221
+ unrecognised as a write, a comprehension variable outliving its comprehension, and an
222
+ unquoted `grep` taking a filename for its pattern. The other 25 fire, and four of them
223
+ name blind spots the R-005 reader found: `enumerate()`, `pathlib.Path.read_text()`,
224
+ `re.compile` and a heredoc spelling.
225
+
226
+ **The R-005 reader found five blocking defects the author's own fixtures did not, and
227
+ this is the part worth keeping.** A dispatched independent reader wrote twenty
228
+ fixtures against the census and nineteen of them slipped past it. Provenance was lost
229
+ through `enumerate()`, and **two live plants — `wv1` and `wv2` — were still pinned to
230
+ `B-005` behind that blind spot** while the census called the corpus clean; the same for
231
+ `pathlib.Path(...).read_text()` (seven plants) and `re.compile` (one). `bd2` and `bd13`
232
+ had collapsed onto one row, so the dangling-id guard's well-formed branch went unprobed
233
+ and `bd13` no longer proved what its name claims. And three documents restated a census
234
+ of 21 that the detector itself put at 24. All five are closed, each with its own
235
+ fixture; the true figure moved **21 → 24 → 26** as the detector stopped being blind, and
236
+ that is the whole argument for the rule the reader exists to serve.
237
+
238
+ Consequently the census now reads provenance through builtin wrappers, `pathlib`,
239
+ `with`-handles, compiled patterns, `json.load`/`yaml.safe_load` and their structured
240
+ looks, `sed`, `awk`, an unquoted `grep` and a `subprocess` argv; it keys read-backs by
241
+ path SOURCE TEXT so a computed path matches itself; it refuses a heredoc spelling it
242
+ cannot read rather than reporting silence — while ignoring one quoted inside a payload,
243
+ because a guard reads what would RUN; and it judges a declaration on characters **and**
244
+ distinct words, because sixty dots satisfy a length floor.
245
+
246
+ **Round two of the same reader found four more, and one of them is this row's own
247
+ class for the third time.** `json.dump(d, open(p, "w"))` was not recorded as a write, so
248
+ the own-read-back of every plant that writes that way was one string literal from being
249
+ refused — 39 plants write that way and **none was flagged yet**, which is a latent false
250
+ positive and is said as one — the write side had been extended to
251
+ `Path().write_text()` and not to `json.dump`, two halves of one rule landing unmatched in
252
+ one commit. A loop or comprehension variable kept its file provenance for the rest of the
253
+ body, so an unrelated later local read as file text. Needles 891 → **832**, and most of that
254
+ drop is a second defect: a comprehension was dispatched twice, so needles inside its `iter`
255
+ and `ifs` were counted twice over. The size of that share is **not stated here** — two
256
+ methods of counting it disagreed (50 and 88) and neither has a command written beside it,
257
+ which is the same defect one level up. 891 shipped as a measurement in a ledger row.
258
+ The no-needle disclosure explained 39 plants as *a JSON key, which raises rather than
259
+ passing*, and by the classifier that now ships exactly **1 of those 39** was a JSON or
260
+ dict key: 13 were shell-only, 11 read no file, 10 compared whole-file bytes and 4 lost
261
+ provenance at a helper function — so the breakdown is computed now — **five**
262
+ reasons at this tree, none of them a gloss, and the fifth (`provenance stopped at a helper
263
+ function`) is the one the gloss was hiding. And the fixture count was **stated as 18/8 in one sentence
264
+ and 35/20 in the next**, which is B-113 itself, third occurrence inside its own fix
265
+ (21 → 24 → 26, then 18/8 → 44/25 → 47/27). Every number in this entry and in the ledger rows is
266
+ now pasted from the command that prints it.
267
+
268
+ **A pure module for the arithmetic nobody can reach.** The dormant branch cannot fire on
269
+ a healthy machine — all four skip-capable plants ran and their guards fired when the
270
+ reader drove them end to end, because the runner rebuilds `.git` in its snapshot and CI
271
+ fetches tags. So the two declarations that claimed otherwise were corrected after
272
+ measurement, and `verdict()` and `claim()` became pure functions with
273
+ `test/runner_test.py` calling them directly: 14 cases, two of them the empty-set branch
274
+ that used to print `PASS: K DORMANT` over nothing at all.
275
+
276
+ **One defect this change surfaced in a neighbour, fixed in the same commit.**
277
+ `CONTRIBUTING.md`'s invariant list cites the guard literal that enforces each entry, and
278
+ `test/validate.py` looked for those literals in its own source only — so the first
279
+ citation of an anchors-census refusal was reported as *an enforcement that does not
280
+ exist* while the check printing it sat in the same suite. The corpus is now this file
281
+ plus the sibling modules it imports, discovered rather than listed. Invariant **64**
282
+ carries the rule.
283
+
284
+ **The loop guard tripped, and the trip is the release's own best evidence.** The fixture
285
+ count returned twice with the same stale figure — inside the paragraph that narrates a
286
+ stale figure as the row's lesson — because each fix APPENDED a corrected sentence instead
287
+ of correcting the one already there. `build.md:417` says a tripped guard is not another
288
+ round: stop, name the two shapes, escalate to the layer that owns the conflict. The layer
289
+ is canon 8, and the escalation is mechanical: **`anchor-census fixtures` and
290
+ `anchor-census fixtures watched firing` are registered claim classes now**, computed from
291
+ the fixture file, with a plant watching them fail. Registered classes 13 → 15. Three
292
+ figures this entry cannot compute are **deleted rather than corrected** — the share of the
293
+ 891 that was double-counted, the number of plants narrowing by order, and one the reader
294
+ retracted itself — because a number whose definition is not written beside it is the
295
+ defect this whole release is about.
296
+
297
+ Guards: 419 → **423** — the re-pinned needle, the undeclared dormancy and the
298
+ declaration that resolves to nothing, plus a property check that the census prints its
299
+ disclosure beside the verdict.
300
+
3
301
  ## v1.80.0 — the doctrine pack: one home per rule, and the boundaries said out loud
4
302
 
5
303
  The largest doctrine reconciliation since the corpus was audited whole. The
package/CONTRIBUTING.md CHANGED
@@ -592,6 +592,21 @@ file about itself. Checked in the live retro and in `templates/retro.md`, which
592
592
  false bound into every host project.
593
593
  *(guard: `declares the bound` and `and nothing enforces it`)*
594
594
 
595
+ **64. A negative self-test may not promise that the tree will not change, and a plant that
596
+ declines to run is not a plant that passed.** B-113, confirmed four times: a needle pinned to a
597
+ value a release moves stops landing, and reports green while it does — and three of the four
598
+ instances were caused by the repository getting *healthier* (a re-derived board, the first blind
599
+ eval runs, the first honest run stamp), so nothing about them looked like a regression. Every
600
+ needle a plant reads off disk before writing that path is extracted from the AST by
601
+ `test/anchors.py`; one carrying a value a release can move must be derived at run time or
602
+ declared in the plant's own body with what would falsify the declaration. Separately, any check
603
+ that can print `SKIP` — a negative self-test **or a property check** — declares the state it
604
+ cannot construct and what that costs, and `test/negatives.py` reports it by name instead of
605
+ counting it inside *all N guards provably reject* or *N property check(s) printed what they
606
+ assert*. Where nothing that ran proved anything the runner reports INCONCLUSIVE, because a
607
+ pass over an empty set is a refused measurement.
608
+ *(guard: `is anchored on a value a release can move` and `can print SKIP and declares no` and `the anchor census found no negative self-tests`)*
609
+
595
610
  ## Adding or changing doctrine
596
611
 
597
612
  - **Change one idea per PR.** These files are read by agents under load; a PR that
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.80.0 |
15
+ | **Version** | 1.82.4 |
16
16
  | **Surface** | Claude Code (filesystem skill + plugin) and the vercel `skills` CLI. **Not** uploaded to the Skills API; custom Skills do not sync across surfaces |
17
17
  | **Dependencies** | None required. Optional: `context7` (MCP), `figma` (MCP), super-ux, agent-sync, graphify, obsidian-wiki, and **one of two browser channels** — `playwright` (CLI or MCP) or `chrome-devtools` (MCP); either satisfies the browser step and neither is required. Every stage's doctrine ships in-repo; the one conditional requirement is super-ux for the stage-3 UX track on a user-facing task |
18
18
  | **Evaluation status** | Suite authored, 5 categories. One recorded run, **self-observed by the author**; **zero blind runs on zero of three models** — the split, and the numbers, live in [`evals/RESULTS.md`](evals/RESULTS.md) and are computed by `evals/run.py` |
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "task-pipeline-skill",
3
- "version": "1.80.0",
3
+ "version": "1.82.4",
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"
7
7
  },
8
8
  "scripts": {
9
9
  "test": "python3 test/validate.py && python3 test/graph_test.py && python3 test/project_audit_test.py",
10
- "test:all": "python3 test/validate.py && python3 test/graph_test.py && python3 test/project_audit_test.py && python3 test/negatives.py && npm run test:certify && npm run test:exposure && npm run test:probe && npm run test:hooks && npm run test:artifacts && npm run test:docs",
10
+ "test:all": "python3 test/validate.py && python3 test/graph_test.py && python3 test/project_audit_test.py && python3 test/negatives.py && npm run test:certify && npm run test:exposure && npm run test:probe && npm run test:anchors && npm run test:runner && npm run test:hooks && npm run test:artifacts && npm run test:docs",
11
11
  "test:negatives": "python3 test/negatives.py",
12
12
  "test:exposure": "python3 test/exposure_test.py",
13
13
  "test:probe": "python3 test/probe.py --self-test",
14
+ "test:anchors": "python3 test/anchors_test.py && python3 test/anchors.py",
15
+ "test:runner": "python3 test/runner_test.py",
14
16
  "test:hooks": "python3 test/release_gate_test.py",
15
17
  "test:artifacts": "python3 test/artifact_root_test.py && python3 test/migrate_artifacts_test.py",
16
18
  "test:docs": "bash plugins/task-pipeline/skills/task-pipeline/templates/docgate.sh",
@@ -3,7 +3,7 @@
3
3
  "name": "task-pipeline",
4
4
  "displayName": "Task Pipeline",
5
5
  "description": "Runs a substantial task through a mandatory built-in intake grill, then 10 gated stages (docs, brainstorm+decompose, spec, plan, subagent build, tests, lint/deploy, post-deploy, docs/wiki, acceptance). Every stage's doctrine is built into the skill — no companion plugin required — with typed auto/judgment/manual gates, a frozen requirement spine that closes with evidence, a work board and a verification ledger that outlive a run, an exposure line naming what shipped unconfirmed, a progress rail computed from the project's own config, a loop guard whose review ceiling measures rather than stops, and stage-3 tracks for what a product does, how it sounds and how it looks. Two modes need no task: `checkup` (what is unverified) and `setup` (audit existing docs). Retro insights can publish upstream as issues, opt-in and redacted.",
6
- "version": "1.80.0",
6
+ "version": "1.82.4",
7
7
  "author": {
8
8
  "name": "ssheleg",
9
9
  "url": "https://x.com/sshlg93"
@@ -33,11 +33,23 @@ to it by construction:
33
33
  - a **second implementation of the same rule** did not get the fix
34
34
  - a **documented behaviour** is now false, and the document still reads as true
35
35
  - **another feature** reaches the same path and nobody considered the interaction
36
+ - the **internal state is right and the message about it is stale** — the value was
37
+ stored correctly and the final sentence to the user echoed the old one
36
38
 
37
39
  None of these is a bug in the changed lines. All of them ship. Each is found by
38
40
  looking one level further out than the change — which is a different reading, not a
39
41
  longer one, because the context that finds it is the context that excludes the diff.
40
42
 
43
+ **The last one is worth its own sentence, because it is the shape a blended score cannot
44
+ see.** A run that stores the right value and then reports the stale one passes on state
45
+ and fails on truth, so any grader that averages the two calls it mostly fine. It was
46
+ caught by a purpose-built rubric with categorical outcomes — *honoured / ignored /
47
+ partial / none* — rather than a number, and closing it moved the violation rate from
48
+ **21% to 4%**. The general form is the dangerous one for this pipeline: work that **looks
49
+ like it is working** — confident answers, a plan that reads fine — while quietly missing
50
+ what was actually asked. That is the product tier's whole job, and it is why the tier
51
+ that reads no code is not the soft one.
52
+
41
53
  ## The three tiers
42
54
 
43
55
  | Tier | Subject | Characteristic finding |
@@ -62,6 +74,16 @@ tier's verdict.
62
74
  Dispatch all three in one message so they run concurrently. Give each the node id,
63
75
  its `serves`, and the diff — nothing else, and never another tier's output.
64
76
 
77
+ **The second axis, and it is the one an optimisation removes first: whoever produced
78
+ the fix never grades it.** Tier blindness is horizontal — no tier reads another's
79
+ report. This one is vertical: the agent that wrote the change, and the agent that
80
+ certifies it, are different agents. The reason is not tidiness — *an optimizer that
81
+ grades itself learns to game the metric instead of improving the work*, and it does so
82
+ while every report it writes stays sincere. The saving on offer is real (the fixer
83
+ already holds the context, a fresh reader must re-derive it) and it is the saving that
84
+ converts certification into self-assessment. `certify` cannot detect the collapse from
85
+ a report, so it is stated here as a dispatch rule.
86
+
65
87
  ## A pass has to mean something, so two rules have teeth
66
88
 
67
89
  **A tier cannot pass on an empty `scope`.** `scope` is what the tier actually
@@ -81,6 +103,24 @@ and no third, because a certification that admits a maybe admits everything:
81
103
  closing verdict as a blocker with `can_continue_around: true`, which is exactly
82
104
  what a named survivable finding is.
83
105
 
106
+ **And the scope of that rule, because it is not universal.** These are severities of a
107
+ **finding**, and a finding is a claim a reader makes about a diff — it either blocks or
108
+ it does not. That is a different object from the **verdict of a check**, and where the
109
+ check's subject is non-deterministic the two-valued form is not strictness, it is
110
+ blindness: binary pass/fail has been measured at **0% detection** of regressions in a
111
+ non-deterministic workflow, where mapping execution traces to compact vectors and
112
+ testing them multivariately reaches **86%**. A third value there — `INCONCLUSIVE`,
113
+ grounded in hypothesis testing rather than in a reader's judgement — is what lets a
114
+ run say *this sample cannot decide* instead of flipping a coin and reporting it as a
115
+ severity. Sequential testing cuts the trials such a verdict needs by **78%**.
116
+
117
+ So: **two severities for a finding, three verdicts for a stochastic check.** A
118
+ deterministic command exits 0 or it does not, and admitting a maybe there does admit
119
+ everything. The instrument for the other case is `agent-stack`'s `agent-evals` —
120
+ `references/statistics.md` for how many runs make a difference real — and a gate whose
121
+ subject is non-deterministic belongs on that axis rather than this one
122
+ ([`gates.md`](gates.md) → *Axis A*).
123
+
84
124
  ## The report, and where each field lands
85
125
 
86
126
  Eight keys, all required, `[]` a valid answer and silence not one. On a pass