task-pipeline-skill 1.39.0 → 1.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,99 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.40.0 — the loop had a cadence and no queue
4
+
5
+ `run.loop` said how **often** to continue. It never said **what the next item is**, so an
6
+ armed mode still left the run choosing its next move by recollection — `learned.md` rule
7
+ 16, once per fire. And nothing scheduled the next turn at all: on 2026-08-10 a run of
8
+ this pipeline wrote *«продолжаю без остановки»* and the turn ended, because a sentence
9
+ about future behaviour is not a wakeup. That run is this release's occasion and its
10
+ evidence.
11
+
12
+ ### The queue is stage 2's
13
+
14
+ The **module map** when the brief was a platform, the plan's task list otherwise. Both
15
+ already existed, both were already ordered, and neither had ever been named as the thing
16
+ the loop walks. `run.loop.queue` names it; `run.loop.arm` says where the mode is armed,
17
+ and the default for a queue-bearing run is **after decomposition** — arming at preflight
18
+ arms a loop with nothing to walk.
19
+
20
+ Arming is a consequence, not a request, for the same reason the mode is recorded rather
21
+ than asked for: a capability the operator must remember to switch on is one they forget
22
+ on exactly the run that needed it.
23
+
24
+ **What arming does not change is stated where it could be missed.** The four stops are
25
+ the four stops; a `manual` gate still waits; an outward act still needs its own specific
26
+ authorization. *A generic flag is not a specific authorization*, and arming a queue is
27
+ the most generic flag there is — guarded, because that sentence is what the deploy floor
28
+ rests on.
29
+
30
+ ### `mode: dynamic`
31
+
32
+ `interval` was the only mode while a fixed tick was the only primitive. A harness that
33
+ can schedule its own next turn picks each delay from what it is waiting for, and **prints
34
+ the delay it chose** — the disclosure that replaces an interval run's job id. A run
35
+ silent about its pacing cannot be told apart from one that quietly stopped, which is the
36
+ claim this file already forbade for harnesses with no primitive at all.
37
+
38
+ ### The goal is re-read between items, not only the board
39
+
40
+ Each iteration already re-measured the work-list, which answers *what is open*. It did
41
+ not answer *whether the open thing still serves what this run was for*. A queue built at
42
+ stage 2 outlives the reason it was built, because the operator learns things between
43
+ items and says so. So the bottom of an iteration now quotes the goal, states whether the
44
+ next item still serves it, and re-orders or re-scopes when it does not — a row that stops
45
+ serving the goal leaves for the board with its reason.
46
+
47
+ A queue re-derived only by `age` and `sev` is honest about priority and silent about
48
+ purpose. Both numbers can be right while the run finishes something the operator stopped
49
+ wanting two items ago.
50
+
51
+ ### The reader found the contradiction, not the bug
52
+
53
+ R-005's reader defeated all five guards and then read the doctrine as a reader rather
54
+ than its author. Part 1a said *"arming is a consequence, not a request"* and stated its
55
+ trigger as a fact about the **work** — a queue with more than one item — with no
56
+ antecedent about configuration. Two sections above, the same file says **Default off.
57
+ Silence arms nothing, exactly as silence authorizes no deploy**, and `grill.md`'s deploy
58
+ floor is explicitly said to rest on that distinction. Read cold, Part 1a arms a loop in a
59
+ project with no `pipeline.json` at all.
60
+
61
+ The contradiction was in the phrasing, not the intent — `stages.md` had already restated
62
+ the rule with the antecedent intact. **Part 1a gave way**, and a guard now requires it to
63
+ state its precondition, because five guards checked for the presence of strings and not
64
+ one would have noticed either reading.
65
+
66
+ What the reader took apart in the guards, all verified by planting the defect and
67
+ watching `PASS`:
68
+
69
+ - **a deleted contract was a skip, not a failure.** Removing the whole `run.loop` block
70
+ left CI green with two tidy `unlooked` lines — and `run` allows additional properties,
71
+ so the example still conformed while meaning nothing;
72
+ - **`_loop_block` searched instead of addressing.** A deprecated top-level `loop` earlier
73
+ in file order answered for the real contract;
74
+ - **presence tests let the release's own thesis be reverted.** `arm` existed, so setting
75
+ the example back to `preflight` passed;
76
+ - **`if _qv and …` short-circuited itself** — an open string in place of the queue enum
77
+ passed, which is precisely the failure the guard was written for;
78
+ - **the floor guard had never tested its own rule.** Its phrase entered the file in
79
+ v1.11.0, twenty-nine releases earlier; both doctrinal statements could be deleted and a
80
+ Rationalizations row kept it green;
81
+ - **the dynamic-disclosure regex was content-blind** — a sentence keeping the words and
82
+ inverting the obligation passed — and it triggered off the prose word, so renaming the
83
+ mode switched the guard off;
84
+ - **`arm the mode` matched a bullet forbidding arming**, and `"loop"` matched any
85
+ sentence about any loop. Both now key on the schema's own tokens.
86
+
87
+ Guards 233 → **248**: seven more probes than the first pass shipped with, six of them for
88
+ fail sites that did not exist until the reader's findings were fixed.
89
+
90
+ ### Also
91
+
92
+ - **A guard was listing the legal modes instead of reading them.** Adding `dynamic`
93
+ failed the guard on a correct example — a check enforcing its own staleness. It now
94
+ reads the enum out of the schema.
95
+ - Guards: 233 → **248**, one per new fail site, each with its planted defect.
96
+
3
97
  ## v1.39.0 — the skill could not be reached by the word "audit"
4
98
 
5
99
  `references/audit.md` has said since v0.1.0 that an audit may be **the whole task** —
package/CONTRIBUTING.md CHANGED
@@ -191,7 +191,7 @@ reads those files, and a partial read shows whichever sections come first.
191
191
  `run` block in `pipeline.schema.json` carries the loop mode; the shipped example
192
192
  sets `run.loop.mode` explicitly rather than omitting it, because the example is
193
193
  what gets copied and an absent field reads as an oversight instead of a decision.
194
- *(guard: `no explicit run.loop.mode`)*
194
+ *(guard: `run.loop.mode is` … `and the schema's legal set is`)*
195
195
 
196
196
  **25. The run-wide mode is named by every stage that could be misled by it.**
197
197
  `SKILL.md`, `references/grill.md`, `references/build.md`, `references/stages.md`
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.39.0 |
15
+ | **Version** | 1.40.0 |
16
16
  | **Surface** | Claude Code (filesystem skill + plugin) and the vercel `skills` CLI. **Not** uploaded to the Skills API; custom Skills do not sync across surfaces |
17
17
  | **Dependencies** | None required. Optional: `context7` (MCP), `figma` (MCP), super-ux, agent-sync, graphify, obsidian-wiki. Every stage's doctrine ships in-repo; the one conditional requirement is super-ux for the stage-3 UX track on a user-facing task |
18
18
  | **Evaluation status** | Suite authored, 5 categories. One recorded run, **self-observed by the author**; **zero blind runs on zero of three models** — the split, and the numbers, live in [`evals/RESULTS.md`](evals/RESULTS.md) and are computed by `evals/run.py` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-pipeline-skill",
3
- "version": "1.39.0",
3
+ "version": "1.40.0",
4
4
  "description": "Full-cycle delivery pipeline for coding agents: a mandatory built-in intake grill, then 10 gated stages (docs, brainstorm+decompose, spec, plan, build, tests, lint/deploy, post-deploy, docs/wiki, acceptance). Every stage's doctrine ships inside the skill — no companion plugin required. This package is the installer CLI.",
5
5
  "bin": {
6
6
  "task-pipeline": "bin/task-pipeline.js"
@@ -2,7 +2,7 @@
2
2
  "name": "task-pipeline",
3
3
  "displayName": "Task Pipeline",
4
4
  "description": "Runs a substantial task through a mandatory built-in intake grill, then 10 gated stages (docs, brainstorm+decompose, spec, plan, subagent build, tests, lint/deploy, post-deploy, docs/wiki, acceptance). Every stage's doctrine is built into the skill — no companion plugin required — with typed auto/manual gates, a frozen requirement spine that 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.",
5
- "version": "1.39.0",
5
+ "version": "1.40.0",
6
6
  "author": {
7
7
  "name": "ssheleg",
8
8
  "url": "https://x.com/sshlg93"
@@ -46,7 +46,7 @@
46
46
  ],
47
47
  "gate": {
48
48
  "type": "manual",
49
- "check": "the user approves the design AND the UI verdict is recorded (does the task touch a user-facing surface — web/mobile/CLI/TUI? this arms the stage-3 UX track). Every REQ is answered by the design, or explicitly dropped by the operator into the carry-over ledger. For a platform (several independent capabilities or shippable surfaces): the module map specs/<topic>-modules.md is committed and approved — brick criteria met or excepted in writing, dependency graph acyclic, build order topological with the walking skeleton first, every REQ mapped to exactly one module, cross-module contracts named with their owner. Single-module work records 'single module: <name>' instead — a skipped decomposition is a recorded decision, never an omission"
49
+ "check": "the user approves the design AND the UI verdict is recorded (does the task touch a user-facing surface — web/mobile/CLI/TUI? this arms the stage-3 UX track). Every REQ is answered by the design, or explicitly dropped by the operator into the carry-over ledger. For a platform (several independent capabilities or shippable surfaces): the module map specs/<topic>-modules.md is committed and approved — brick criteria met or excepted in writing, dependency graph acyclic, build order topological with the walking skeleton first, every REQ mapped to exactly one module, cross-module contracts named with their owner. Single-module work records 'single module: <name>' instead — a skipped decomposition is a recorded decision, never an omission. THE LOOP'S ARMING STATE IS PRINTED — armed with its queue and pacing, or not armed with the reason; the queue exists at this stage and a loop armed earlier has nothing to walk."
50
50
  }
51
51
  },
52
52
  {
@@ -172,7 +172,12 @@
172
172
  ],
173
173
  "_run_note": "Run-wide pacing. Shipped explicitly OFF so this example DEMONSTRATES the default rather than relying on its absence — see references/continuity.md. Set mode to 'interval' and a project stops being asked whether to run item-by-item; it still stops at every manual gate. There is no context-budget field here on purpose: the threshold is not measurable, so it is unconditional doctrine, not config.",
174
174
  "run": {
175
- "loop": { "mode": "off", "interval": "5m", "command": "/loop" }
175
+ "loop": {
176
+ "mode": "dynamic",
177
+ "queue": "module-map",
178
+ "arm": "after-decomposition",
179
+ "command": "/loop"
180
+ }
176
181
  },
177
182
  "release": {
178
183
  "enabled": true,
@@ -48,18 +48,34 @@
48
48
  "mode": {
49
49
  "enum": [
50
50
  "off",
51
- "interval"
51
+ "interval",
52
+ "dynamic"
52
53
  ],
53
- "description": "off (the default when absent) = the run pauses between items as it always did. interval = the run is armed with the harness's own loop primitive and advances one item per fire, stopping only at a manual gate, an unresolvable block, a genuine ambiguity, or completion."
54
+ "description": "off (the default when absent) = the run pauses between items as it always did. interval = armed with the harness's fixed-tick primitive, one item per fire. dynamic = the harness schedules its own next turn and the run picks each delay from what it is waiting for; a wait on nothing is minutes, not a tick nobody chose. interval and dynamic stop at the same four things — a manual gate, an unresolvable block, a genuine ambiguity, completion — and neither collapses an outward act: a generic flag is not a specific authorization."
54
55
  },
55
56
  "interval": {
56
57
  "type": "string",
57
58
  "pattern": "^[0-9]+[smhd]$",
58
- "description": "Required when mode is 'interval'. Must divide its unit cleanly (5m, 10m, 2h — not 7m or 90m); a value that does not is rounded to the nearest that does, and the rounding is stated out loud. Pick the shortest interval that is longer than a typical item."
59
+ "description": "Required when mode is 'interval', and meaningless when it is 'dynamic' — a self-pacing run chooses each delay. Must divide its unit cleanly (5m, 10m, 2h — not 7m or 90m); a value that does not is rounded to the nearest that does, and the rounding is stated out loud. Pick the shortest interval that is longer than a typical item."
59
60
  },
60
61
  "command": {
61
62
  "type": "string",
62
63
  "description": "How this harness arms it, e.g. '/loop'. Harness-specific and therefore project-recorded rather than assumed: on a harness with no loop primitive, omit it — the mode then degrades to prose discipline plus the build ledger, and the run says so instead of implying it is armed."
64
+ },
65
+ "queue": {
66
+ "enum": [
67
+ "module-map",
68
+ "plan-tasks",
69
+ "none"
70
+ ],
71
+ "description": "What the loop walks. A loop with no queue is a timer: it says how often to continue and never what the next item is, so the run picks its next move by recollection. module-map = stage 2's decomposition (a platform); plan-tasks = stage 4's task list; none = the mode is armed for the stage boundaries only. Doctrine: references/continuity.md -> Part 1a."
72
+ },
73
+ "arm": {
74
+ "enum": [
75
+ "preflight",
76
+ "after-decomposition"
77
+ ],
78
+ "description": "When the mode is armed. preflight = at the top of the run, the original behaviour. after-decomposition = at the close of stage 2, once the queue exists and has more than one item — the default for a queue-bearing run, because a capability the operator must remember to switch on is one they forget on the run that needed it. Arming never collapses a manual gate or an outward action."
63
79
  }
64
80
  }
65
81
  },
@@ -11,6 +11,7 @@ almost no window left, loses the middle of it, and re-derives what it already di
11
11
 
12
12
  - The limit, before the capability
13
13
  - Part 1 — the loop
14
+ - Part 1a — the queue is stage 2's, and the loop arms on it
14
15
  - Arming it on Claude Code
15
16
  - Other harnesses, and honest degradation
16
17
  - What one iteration means
@@ -62,6 +63,62 @@ the boundary between one agent turn and the next. This file is that reach.
62
63
  is never asked again — which is the entire point. A mode that has to be requested
63
64
  every run is not a mode, it is a habit the operator maintains by hand.
64
65
 
66
+ ## Part 1a — the queue is stage 2's, and the loop arms on it
67
+
68
+ **A loop with no queue is a timer.** Until this section existed, `run.loop` said *how
69
+ often* to continue and never said *what the next item is*, so the mode could be armed
70
+ and still leave the run picking its next move by recollection — which is the failure
71
+ [`learned.md`](learned.md) rule 16 is about, running once per fire.
72
+
73
+ **The queue is the module map** ([`decomposition.md`](decomposition.md)) when the brief
74
+ was a platform, and the plan's task list otherwise. Both already exist and both are
75
+ already ordered; neither was ever named as the thing the loop walks.
76
+
77
+ **Arming is the execution of a recorded decision, not a fresh request.** Where the mode
78
+ is **recorded** and the queue has more than one item, the loop arms **at the close of
79
+ stage 2** and the run says so in one line. It is not asked for at that point, for the
80
+ same reason the mode is recorded rather than requested: re-asking would rebuild the habit
81
+ the config exists to retire.
82
+
83
+ **Where nothing is recorded, nothing arms.** Silence arms nothing here too — this section
84
+ moves *where* a recorded mode is armed, from preflight to the close of stage 2. It does
85
+ not make arming unconditional, and a reader who takes it that way would arm a loop in a
86
+ project with no `pipeline.json` at all. That reading was in this section's first draft;
87
+ an independent reader found it before it shipped.
88
+
89
+ **What arming does NOT change, and this is load-bearing:** the four stops are the four
90
+ stops. A `manual` gate still waits. An outward or irreversible act still needs its own
91
+ specific authorization — *a generic flag is not a specific authorization*, and arming a
92
+ queue is the most generic flag there is. Arming decides only that the run does not stop
93
+ to ask *"shall I take the next one?"*.
94
+
95
+ **`mode: dynamic` — when the harness paces itself.** `interval` was the only mode while
96
+ the only primitive was a fixed tick. A harness that can schedule its own next turn
97
+ should: the run picks the delay from what it is waiting for, and a wait on nothing is a
98
+ wait of minutes rather than a fixed tick nobody chose. On a harness with neither
99
+ primitive the mode degrades to prose discipline plus the build ledger, and **the run
100
+ says which one it is running** — the rule below about claiming a capability you do not
101
+ have is unchanged and applies to `dynamic` first.
102
+
103
+ ### The goal is re-read between items, not only the board
104
+
105
+ Each iteration already re-measures the work-list. That answers *what is open*. It does
106
+ not answer *whether the open thing still serves what this run was for* — and a queue
107
+ built at stage 2 outlives the reason it was built, because the operator learns things
108
+ between items and says so.
109
+
110
+ So at the bottom of an iteration, beside the re-derived `prio`:
111
+
112
+ 1. re-read the brief's goal — one line, quoted, not recalled;
113
+ 2. state whether the next item still serves it;
114
+ 3. if it does not, **re-order or re-scope the queue and say what moved and why.** A row
115
+ that stops serving the goal leaves for the board with its reason
116
+ ([`backlog.md`](backlog.md)), it does not get worked because it was next.
117
+
118
+ A queue re-derived only by `age` and `sev` is a queue that is honest about priority and
119
+ silent about purpose. Both numbers can be right while the run is finishing something the
120
+ operator stopped wanting two items ago.
121
+
65
122
  ## Arming it on Claude Code
66
123
 
67
124
  ```
@@ -85,10 +142,22 @@ silent failures otherwise:
85
142
  stops on day eight is worse than one that was never armed, because the operator
86
143
  believes work is still moving.
87
144
 
88
- Where `run.loop.mode` is `interval`, the **preflight arms it** and prints the job
89
- id and the cancel command. Arming is not a new decision at that point — the config
90
- is the recorded authorization, and re-asking would rebuild the habit the config
91
- exists to retire.
145
+ **Where it arms is `run.loop.arm`,** and there are two points because there are two
146
+ kinds of run:
147
+
148
+ - `preflight` — the top of the run. Right when the loop walks stage boundaries rather
149
+ than a list, and the only option before Part 1a existed.
150
+ - `after-decomposition` — the close of stage 2, once the queue exists and holds more
151
+ than one item. Arming earlier would arm a loop with nothing to walk.
152
+
153
+ Either way the run **prints the job id and the cancel command**, and arming is not a new
154
+ decision at that point — the config is the recorded authorization, and re-asking would
155
+ rebuild the habit the config exists to retire.
156
+
157
+ Under `mode: dynamic` there is no job id: the run schedules its own next turn each time
158
+ and prints **the delay it chose and why**, which is the same disclosure in the form that
159
+ mode has. A dynamic run that says nothing about its pacing is indistinguishable from a
160
+ run that quietly stopped.
92
161
 
93
162
  ## Other harnesses, and honest degradation
94
163
 
@@ -258,13 +258,21 @@ never that the work was skipped quietly.
258
258
  its status, in build order with the walking skeleton first. Single-module work
259
259
  records `single module: <name>` in the design and moves on — a skipped
260
260
  decomposition is a decision, never an omission.
261
+ - **The queue exists here, so the loop arms here** ([`continuity.md`](continuity.md) →
262
+ *Part 1a*). Where `run.loop.arm` is `after-decomposition` and the map holds more than
263
+ one module, arm the mode at the close of this stage and print one line: the mode, and
264
+ either the job id and its cancel command (`interval`) or the delay chosen and why
265
+ (`dynamic`). Arming collapses no gate and authorizes no outward act; it decides only
266
+ that the run does not stop to ask *"shall I take the next one?"*. Single-module work
267
+ arms nothing and says so — a loop with one item is a timer.
261
268
  - **GATE (manual):** the user approves the design, the UI verdict is recorded,
262
269
  **every REQ is answered by the design** — a requirement the design doesn't
263
270
  address is either covered now or explicitly dropped by the operator, with the
264
271
  drop recorded in the carry-over ledger — **and, for a platform, the module map is
265
272
  approved**: brick criteria met or excepted in writing, dependency graph acyclic,
266
273
  build order topological, every REQ mapped to exactly one module, cross-module
267
- contracts named with their owner.
274
+ contracts named with their owner — **and the loop's arming state is printed**:
275
+ armed with its queue and pacing, or not armed with the reason.
268
276
 
269
277
  ## 3 — Spec — with UX track for user-facing tasks
270
278
  - **Freedom: medium** — what the contract says is judgement; which contracts must be locked is a list ([`gates.md`](gates.md) → *Axis C*).