task-pipeline-skill 1.80.0 → 1.81.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,150 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.81.1 — the anchors are derived, and a dormant plant stops reading as a pass
4
+
5
+ > **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.
6
+
7
+ Board row **B-113**, filed 2026-08-22 and confirmed four times since: *negative-test
8
+ anchors are pinned to literals that the releases they guard move, so a release
9
+ disarms its own checks.* Every confirmation had the same cause and the same reason
10
+ nobody noticed — the plants were disarmed by the repository getting **healthier**.
11
+ The board re-derived its ages and `bd5`'s needle stopped existing; the first blind
12
+ eval runs shipped and `pf1`'s guard went dormant; a release finally carried an honest
13
+ run stamp and `gap1`'s precondition emptied. All four were found by the 35-minute
14
+ negatives suite at release time, one release after the damage, and until it ran
15
+ everything was green.
16
+
17
+ **The census, measured rather than estimated.** `test/anchors.py` reads all 422
18
+ negative self-tests out of the workflow and answers the row's own question — *is the
19
+ number the plant WRITES, or the number it LOOKS FOR?* — from the AST rather than by
20
+ grep. A needle is only what a plant reads out of a file, before it writes that path:
21
+ provenance is tracked from `open(...).read()` through derived names, read-backs are
22
+ tracked per path (a plant-wide "after the first write" rule exempts `pf1`'s second
23
+ file, which is a genuine anchor), regex shape is stripped (`[0-9a-f]{7,40}` is a
24
+ shape, `\u2192` in a raw string is an escape and not the year 2192), and a
25
+ `validate.py | grep 'message'` is assertion 3 rather than a needle. Counted over the
26
+ corpus at the commit this release branched from: **26 of 419 plants pinned a value a
27
+ release can move.** The row's own count of 27 came from a grep that both over- and
28
+ under-reported — it missed `bd5`'s single-digit `| 1 | **6** |` entirely — and this
29
+ release's own first figure of 21 was wrong for the same reason one level in: the
30
+ detector could not see a needle read through `enumerate()`, `pathlib` or a compiled
31
+ pattern. The R-005 reader found that by fixture, and it is why the number moved twice
32
+ before it was true.
33
+
34
+ **25 of the 26 are derived now**, one declared. Each reads its anchor out of the tree at run
35
+ time: the high-water mark by shape, the rule number the stage map binds, the seeded
36
+ templates' open row and worked example, the carry-over ledgers' board pointers, the
37
+ live board's rows, the criterion numbers, the caps, the container count and its date,
38
+ the recipe fence. The live-board plants were the sharpest: `bd10` pinned `| B-008 |`
39
+ and `tbl`/`tbl2` pinned `| B-015 |`, so all three would have stopped landing the day
40
+ those rows closed. One literal stays and says so: `res15` pins `^13\.` because the
41
+ criterion NUMBER is the *guard's* contract — `test/validate.py` locates the teardown
42
+ criterion as `^13\.` and its refusal names 13 — and deriving it would prove some other
43
+ criterion's guard fired instead.
44
+
45
+ **Dormant is no longer counted as passing.** `test/negatives.py` folded every plant
46
+ that printed `SKIP` into *all N guards provably reject their planted defect*: a check
47
+ that could not construct its precondition, reported inside the claim that every check
48
+ rejected one. Skips now have their own bucket, print with the reason they gave, and
49
+ the aggregate says *N of M · K DORMANT, named above — not counted as passing*. In CI
50
+ each plant is its own step and a green step carrying SKIP is invisible, so the half
51
+ that is checkable everywhere is the declaration: every plant that can decline to run
52
+ carries `# dormant-when: <the state it cannot construct, and what that costs>`, and
53
+ all four now do.
54
+
55
+ **The class is self-reporting, in the cheap gate rather than the 35-minute one.**
56
+ `test/validate.py` refuses a plant whose needle pins a moving value unless it declares
57
+ why, refuses a declaration that resolves to nothing the plant reads, refuses a skip
58
+ branch with no dormancy declaration, and refuses an empty census — because "no anchors
59
+ found" and "the parser matched nothing" are otherwise the same sentence. Four checks
60
+ ride into the suite with it, each watched red before shipping, and `test/anchors_test.py`
61
+ is **47 whole-workflow fixtures, 27 of them watched firing** — a number that itself moved
62
+ three times as the reader found what the census could not see. **Eighteen of the 45 cases assert SILENCE, and 16 of
63
+ those 18 are retractions** — the other two are positive controls (`a derived needle passes`,
64
+ `a declared anchor is accepted`) (the other two of the 47 checks are the empty-corpus
65
+ census and a source check, which assert neither) — a `{7,40}` quantifier read as a year, `\u2192` in a raw
66
+ string read as 2192, a payload built from local strings, a `validate.py | grep` read as a
67
+ needle, a plant echoing `B-008` into its own replacement, `json.dump(d, open(p, "w"))`
68
+ unrecognised as a write, a comprehension variable outliving its comprehension, and an
69
+ unquoted `grep` taking a filename for its pattern. The other 25 fire, and four of them
70
+ name blind spots the R-005 reader found: `enumerate()`, `pathlib.Path.read_text()`,
71
+ `re.compile` and a heredoc spelling.
72
+
73
+ **The R-005 reader found five blocking defects the author's own fixtures did not, and
74
+ this is the part worth keeping.** A dispatched independent reader wrote twenty
75
+ fixtures against the census and nineteen of them slipped past it. Provenance was lost
76
+ through `enumerate()`, and **two live plants — `wv1` and `wv2` — were still pinned to
77
+ `B-005` behind that blind spot** while the census called the corpus clean; the same for
78
+ `pathlib.Path(...).read_text()` (seven plants) and `re.compile` (one). `bd2` and `bd13`
79
+ had collapsed onto one row, so the dangling-id guard's well-formed branch went unprobed
80
+ and `bd13` no longer proved what its name claims. And three documents restated a census
81
+ of 21 that the detector itself put at 24. All five are closed, each with its own
82
+ fixture; the true figure moved **21 → 24 → 26** as the detector stopped being blind, and
83
+ that is the whole argument for the rule the reader exists to serve.
84
+
85
+ Consequently the census now reads provenance through builtin wrappers, `pathlib`,
86
+ `with`-handles, compiled patterns, `json.load`/`yaml.safe_load` and their structured
87
+ looks, `sed`, `awk`, an unquoted `grep` and a `subprocess` argv; it keys read-backs by
88
+ path SOURCE TEXT so a computed path matches itself; it refuses a heredoc spelling it
89
+ cannot read rather than reporting silence — while ignoring one quoted inside a payload,
90
+ because a guard reads what would RUN; and it judges a declaration on characters **and**
91
+ distinct words, because sixty dots satisfy a length floor.
92
+
93
+ **Round two of the same reader found four more, and one of them is this row's own
94
+ class for the third time.** `json.dump(d, open(p, "w"))` was not recorded as a write, so
95
+ the own-read-back of every plant that writes that way was one string literal from being
96
+ refused — 39 plants write that way and **none was flagged yet**, which is a latent false
97
+ positive and is said as one — the write side had been extended to
98
+ `Path().write_text()` and not to `json.dump`, two halves of one rule landing unmatched in
99
+ one commit. A loop or comprehension variable kept its file provenance for the rest of the
100
+ body, so an unrelated later local read as file text. Needles 891 → **832**, and most of that
101
+ drop is a second defect: a comprehension was dispatched twice, so needles inside its `iter`
102
+ and `ifs` were counted twice over. The size of that share is **not stated here** — two
103
+ methods of counting it disagreed (50 and 88) and neither has a command written beside it,
104
+ which is the same defect one level up. 891 shipped as a measurement in a ledger row.
105
+ The no-needle disclosure explained 39 plants as *a JSON key, which raises rather than
106
+ passing*, and by the classifier that now ships exactly **1 of those 39** was a JSON or
107
+ dict key: 13 were shell-only, 11 read no file, 10 compared whole-file bytes and 4 lost
108
+ provenance at a helper function — so the breakdown is computed now — **five**
109
+ reasons at this tree, none of them a gloss, and the fifth (`provenance stopped at a helper
110
+ function`) is the one the gloss was hiding. And the fixture count was **stated as 18/8 in one sentence
111
+ and 35/20 in the next**, which is B-113 itself, third occurrence inside its own fix
112
+ (21 → 24 → 26, then 18/8 → 44/25 → 47/27). Every number in this entry and in the ledger rows is
113
+ now pasted from the command that prints it.
114
+
115
+ **A pure module for the arithmetic nobody can reach.** The dormant branch cannot fire on
116
+ a healthy machine — all four skip-capable plants ran and their guards fired when the
117
+ reader drove them end to end, because the runner rebuilds `.git` in its snapshot and CI
118
+ fetches tags. So the two declarations that claimed otherwise were corrected after
119
+ measurement, and `verdict()` and `claim()` became pure functions with
120
+ `test/runner_test.py` calling them directly: 14 cases, two of them the empty-set branch
121
+ that used to print `PASS: K DORMANT` over nothing at all.
122
+
123
+ **One defect this change surfaced in a neighbour, fixed in the same commit.**
124
+ `CONTRIBUTING.md`'s invariant list cites the guard literal that enforces each entry, and
125
+ `test/validate.py` looked for those literals in its own source only — so the first
126
+ citation of an anchors-census refusal was reported as *an enforcement that does not
127
+ exist* while the check printing it sat in the same suite. The corpus is now this file
128
+ plus the sibling modules it imports, discovered rather than listed. Invariant **64**
129
+ carries the rule.
130
+
131
+ **The loop guard tripped, and the trip is the release's own best evidence.** The fixture
132
+ count returned twice with the same stale figure — inside the paragraph that narrates a
133
+ stale figure as the row's lesson — because each fix APPENDED a corrected sentence instead
134
+ of correcting the one already there. `build.md:417` says a tripped guard is not another
135
+ round: stop, name the two shapes, escalate to the layer that owns the conflict. The layer
136
+ is canon 8, and the escalation is mechanical: **`anchor-census fixtures` and
137
+ `anchor-census fixtures watched firing` are registered claim classes now**, computed from
138
+ the fixture file, with a plant watching them fail. Registered classes 13 → 15. Three
139
+ figures this entry cannot compute are **deleted rather than corrected** — the share of the
140
+ 891 that was double-counted, the number of plants narrowing by order, and one the reader
141
+ retracted itself — because a number whose definition is not written beside it is the
142
+ defect this whole release is about.
143
+
144
+ Guards: 419 → **423** — the re-pinned needle, the undeclared dormancy and the
145
+ declaration that resolves to nothing, plus a property check that the census prints its
146
+ disclosure beside the verdict.
147
+
3
148
  ## v1.80.0 — the doctrine pack: one home per rule, and the boundaries said out loud
4
149
 
5
150
  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.81.1 |
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.81.1",
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.81.1",
7
7
  "author": {
8
8
  "name": "ssheleg",
9
9
  "url": "https://x.com/sshlg93"