task-pipeline-skill 1.38.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,176 @@
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
+
97
+ ## v1.39.0 — the skill could not be reached by the word "audit"
98
+
99
+ `references/audit.md` has said since v0.1.0 that an audit may be **the whole task** —
100
+ stages 3–5 producing findings and fixes instead of a feature. No routing surface named
101
+ it. Every trigger noun was build-shaped, and the exclusion clause read *"Not for:
102
+ answering a question, explaining or reading code"* — which is the opening move of an
103
+ audit, a bug hunt, a production check and a PR review alike.
104
+
105
+ ### Measured, not supposed
106
+
107
+ Ten routing queries, one fresh agent each, holding nothing but the competing skill
108
+ descriptions and one user sentence (`evals/routing/render.py`, results in
109
+ `evals/routing/RESULTS.md`). **7 / 10 before.** The three misses were the three the board
110
+ row predicted, and none of them was a failure to find a match — each agent **quoted this
111
+ skill's own exclusion clause back as the reason it refused**:
112
+
113
+ - «проверь, нет ли ошибок в обработчике вебхуков» → `none`
114
+ - «проверь, всё ли живо в проде после вчерашнего релиза» → `none`
115
+ - «посмотри PR #24 и скажи, что там не так» → `none`
116
+
117
+ A fourth result only the reasoning shows: «сделай аудит модуля оплат» *did* route, and
118
+ justified it by stretching the build verb `hardening`. A right answer resting on a
119
+ stretch is one rewording from a miss.
120
+
121
+ ### The boundary is what a request ends in
122
+
123
+ Not whether it reads. An answer stops in the conversation; a change **or a finding**
124
+ lands in the tree, and the pipeline is what carries REQ rows, board rows and fixes
125
+ there. The description, the portable routing rule and the Cursor rule now all say so,
126
+ and `reading` is gone from the exclusions — guarded, so it cannot come back quietly.
127
+
128
+ ### Two things the harvest found before the first grill question
129
+
130
+ - **`перевести` was locked into the verb list by the v1.9.0 design and never shipped.**
131
+ It existed in exactly one place in this repository: the design that locked it.
132
+ - **`REQ-003` was accepted `verified` anyway.** The evidence recorded was the clause's
133
+ *shape* and its character count — neither of which can see a missing member of the
134
+ list the REQ locked. An L1→L2 absence that passed an L5 check. A guard now reads the
135
+ locked list out of that design and compares it to the shipped surface, so the next
136
+ dropped verb is a failure rather than a week.
137
+
138
+ ### The reader earned its standing instruction
139
+
140
+ R-005 exists because an author's probes only exercise the shapes the author already
141
+ thought of. Dispatched on the nine new guards, an independent reader defeated them
142
+ **fifteen ways**, each verified by planting the text and watching the validator still
143
+ print `PASS`. The three worth naming:
144
+
145
+ - **A presence test over a whole file proves a word exists, not that the rule says it.**
146
+ The reader deleted the entire boundary clause from the Cursor rule, put the old
147
+ exclusion back, added one unrelated sentence elsewhere containing the same four class
148
+ names — and the guard passed. Both cross-surface checks are now scoped to the
149
+ `## Routing` section.
150
+ - **A `should_not_trigger` control counted as coverage.** The eval-coverage guard joined
151
+ every query regardless of category, so deleting all four findings evals and mentioning
152
+ the words in one negative control certified *named and untested* — the exact state it
153
+ cites `B-046` for — as green. It now reads `should_trigger` queries only.
154
+ - **The anti-dormancy sentinel was itself one synonym from dormant**, and the
155
+ locked-verb guard read a regex out of a document this repo forbids maintaining, so
156
+ relabelling one heading in a superseded design would have silenced the check that
157
+ exists because a verb was silently dropped. Both now fail loudly instead.
158
+
159
+ Nine of the fifteen were in checks written that same hour. The tenth fail site had no
160
+ probe at all — ten branches, nine tests — which is the invariant the repository states
161
+ about itself and did not keep.
162
+
163
+ ### Also
164
+
165
+ - **False-positive controls, because widening a vocabulary can steal work.** Three
166
+ competitors already claim the word *audit* — `seo-aeo-audit`, `ux-audit`,
167
+ `make-skill`. All three are eval cases now, and all three still won their query.
168
+ - **R-003 sweep** turned up the same word-map ceiling that produced R-006, in the
169
+ redaction-rule count: past ten it compared against the digit alone. It now accepts
170
+ either form and names both when it fails.
171
+ - Guards: 218 → **233**; eval cases 21 → **28**; description 956 → 1004 of 1024, paid for
172
+ by cutting mechanism prose that could not affect routing.
173
+
3
174
  ## v1.38.0 — the wall came down, and a green started meaning something
4
175
 
5
176
  An audit of this skill measured nineteen problems and asked one question of all of them:
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.38.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` |
@@ -605,11 +605,17 @@ track that ran look identical afterwards.
605
605
 
606
606
  ## Routing — when this applies at all
607
607
 
608
- **Work that CHANGES THE REPOSITORY goes through the pipeline** a feature, fix,
609
- refactor, migration, integration, rewrite, adoption or hardening pass, in any
610
- phrasing. **Not** for a question, an explanation, reading code, a typo or a one-line
611
- edit. The operator opts out of a qualifying task by saying "без пайплайна" or
612
- "quick"; when they do, say out loud that the cycle was skipped at their request.
608
+ **Work that LANDS IN THE REPOSITORY goes through the pipeline**, in two shapes: a
609
+ **change** — a feature, fix, refactor, migration, integration, rewrite, adoption or
610
+ hardening pass and a **finding that lands** an audit, a bug hunt, a production
611
+ check, a PR review, whose output is REQ rows, board rows and fixes rather than an
612
+ answer in the chat. In any phrasing.
613
+
614
+ **Not** for a question, an explanation, mapping code for a person to read, a typo or a
615
+ one-line edit. **Reading is not the test**: an audit, a bug hunt and a PR review all
616
+ begin by reading and all three end in the tree. The operator opts out of a qualifying
617
+ task by saying "без пайплайна" or "quick"; when they do, say out loud that the cycle
618
+ was skipped at their request.
613
619
 
614
620
  A borderline case is named, not silently chosen: state which route you are taking and
615
621
  why, in one line.
@@ -0,0 +1,79 @@
1
+ # Routing measurement — results
2
+
3
+ Method: `evals/routing/render.py`. Ten queries, **one fresh agent per query**, holding
4
+ nothing but the competing skill descriptions and one user sentence.
5
+
6
+ > **Limits, stated before the numbers.** This measures the **descriptions'**
7
+ > discriminating power, not Claude Code's selection machinery, which sees more than a
8
+ > description. A subagent is fresh in *context*, not blind in *disposition* — this is
9
+ > **not** the multi-model blind run board row `B-002` asks for and no line here may be
10
+ > quoted as one. And see §3: **one sample per cell is too few**, which this measurement
11
+ > found out about itself by being run twice.
12
+
13
+ ## 1. The three runs
14
+
15
+ | id | query | expected | **A** before | **B** after | **C** after + prod pair |
16
+ |---|---|---|---|---|---|
17
+ | AUD-01 | сделай аудит модуля оплат | task-pipeline | ✓ *(stretch)* | ✓ | ✓ |
18
+ | BUG-01 | проверь, нет ли ошибок в обработчике вебхуков | task-pipeline | **none** | ✓ | ✓ |
19
+ | PRD-01 | проверь, всё ли живо в проде после вчерашнего релиза | task-pipeline | **none** | **none** | **none** |
20
+ | PRR-01 | посмотри PR #24 и скажи, что там не так | task-pipeline | **none** | ✓ | ✓ |
21
+ | FEA-01 | добавь экспорт в CSV на страницу отчётов | task-pipeline | ✓ | ✓ | **none** |
22
+ | QST-01 | объясни, как наш auth middleware решает… | none | ✓ | ✓ | ✓ |
23
+ | TYP-01 | поправь опечатку в заголовке README | none | ✓ | ✓ | ✓ |
24
+ | SEO-01 | сделай аудит лендинга — почему упал трафик | seo-aeo-audit | ✓ | ✓ | ✓ |
25
+ | UXA-01 | проверь, что код соответствует UX-сценариям | ux-audit | ✓ | ✓ | ✓ |
26
+ | SKL-01 | проверь, соответствует ли этот скил стандарту | make-skill | ✓ | ✓ | ✓ |
27
+ | | | | **7 / 10** | **9 / 10** | **8 / 10** |
28
+
29
+ - **A** — `9f67dcd`, description 956/1024, before any edit.
30
+ - **B** — the findings clause added, description 1004/1024.
31
+ - **C** — `production check` given its Russian pair, description 1008/1024.
32
+
33
+ ## 2. What is robust, and what is one sample
34
+
35
+ **Robust — the same result in both after-runs, for the stated reason:**
36
+
37
+ - **BUG-01 and PRR-01: `none` → `task-pipeline`, 2/2.** In A both agents *quoted this
38
+ skill's own exclusion clause* as the reason they refused — *"matching task-pipeline's
39
+ own exclusion 'Not for: … explaining or reading code'"*, *"task-pipeline's is scoped to
40
+ 'work changes the repository', not 'explaining or reading code'"*. In B and C both
41
+ quote the new clause instead. The mechanism named in the refusal is the mechanism the
42
+ change removed.
43
+ - **AUD-01's *reason* changed, 2/2.** It routed in A too — by stretching the build verb
44
+ `hardening`, with nothing in the description naming an audit. In B and C it quotes
45
+ `audit/аудит`. The answer did not move; the ground under it did.
46
+ - **PRD-01 never routed: 0/2 after.** Adding `/проверь прод` did not fix it. In C the
47
+ agent said the request has *"no repository change and no matching trigger"* and
48
+ reached for `checkup` before rejecting it. The production-check class is **named and
49
+ still unreachable** — the vocabulary was necessary and is not sufficient.
50
+
51
+ **One sample, and therefore not a result:**
52
+
53
+ - **FEA-01 flipped to `none` in C** on reasoning that does not parse — *"no such change
54
+ applies here because none of the listed skills is task-pipeline's own trigger"*. This
55
+ is a control that passed in A and B with the same description clause C did not touch.
56
+ Read as run-to-run variance, **not** as a regression caused by the edit — and the
57
+ measurement as built cannot prove that reading, which is the point of §3.
58
+
59
+ ## 3. The measurement's own defect, found by running it twice
60
+
61
+ **One agent per query cannot separate an effect from noise.** Three runs produced three
62
+ totals — 7, 9, 8 — and one of the moves between them (FEA-01) is almost certainly the
63
+ sampler, not the surface. Had the run stopped at B, this file would have reported
64
+ **9/10 and a clean win**, and the claim would have rested on single samples exactly like
65
+ the one that flipped.
66
+
67
+ What a next version needs, in order of value:
68
+
69
+ 1. **N ≥ 3 per cell, majority-scored**, so a flip is visible as a split rather than a
70
+ result. Cost scales linearly and the queries are tiny.
71
+ 2. **The controls scored separately from the targets.** A false-positive control that
72
+ flips is a different fact from a target that does not move.
73
+ 3. **The reason recorded, not only the answer.** Every real finding in this file came
74
+ from *why* an agent chose, not from what it chose — AUD-01 passed in A for a reason
75
+ that was one rewording from failing.
76
+
77
+ Filed as a board row rather than fixed here: the run that needed the harness is the run
78
+ that found the harness too thin, and rebuilding it mid-run would leave the before-numbers
79
+ measured by a different instrument than the after-numbers.
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env python3
2
+ """Render the routing-measurement prompt for one query.
3
+
4
+ The measurement asks a fresh agent, holding nothing but competing skill
5
+ descriptions and one user sentence, which skill it would use. Run before a
6
+ description change and after it; the only thing that may differ between the two
7
+ runs is the text of the descriptions themselves — this file renders everything
8
+ else identically, which is the whole reason it is a file and not a paste.
9
+
10
+ python3 evals/routing/render.py <query-id> # prompt to stdout
11
+ python3 evals/routing/render.py --list # the queries and expectations
12
+
13
+ Honest limits, stated here because a reader of the results will not see them:
14
+ * this measures the DESCRIPTIONS' discriminating power, not Claude Code's own
15
+ selection machinery, which sees more than a description;
16
+ * a subagent carries its host's system prompt, so it is fresh in CONTEXT, not
17
+ in disposition — this is not the blind multi-model run board row B-002 asks
18
+ for, and no result here may be quoted as one.
19
+ """
20
+ import json
21
+ import os
22
+ import re
23
+ import subprocess
24
+ import sys
25
+
26
+ ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
27
+
28
+ # task-pipeline's description is read from the WORKING TREE, so the after-run
29
+ # picks up the edit; the competitors are read from the installed plugins, so an
30
+ # unrelated release of theirs cannot silently move the baseline between runs.
31
+ SELF = os.path.join(ROOT, "plugins/task-pipeline/skills/task-pipeline/SKILL.md")
32
+ MARKETPLACES = os.path.expanduser("~/.claude/plugins/marketplaces")
33
+ COMPETITORS = ["super-ux", "sheleg-design", "make-skill", "agent-sync", "seo-aeo-audit"]
34
+
35
+ QUERIES = [
36
+ # id, the sentence, what should win, why it is here
37
+ ("AUD-01", "сделай аудит модуля оплат", "task-pipeline",
38
+ "findings on code — audit.md:350 says an audit may be the whole task"),
39
+ ("BUG-01", "проверь, нет ли ошибок в обработчике вебхуков", "task-pipeline",
40
+ "bug hunt — findings that land in the repo"),
41
+ ("PRD-01", "проверь, всё ли живо в проде после вчерашнего релиза", "task-pipeline",
42
+ "production check"),
43
+ ("PRR-01", "посмотри PR #24 и скажи, что там не так", "task-pipeline",
44
+ "PR review — review.md owns the reader contract"),
45
+ ("FEA-01", "добавь экспорт в CSV на страницу отчётов", "task-pipeline",
46
+ "CONTROL+ — build work already routes; must not regress"),
47
+ ("QST-01", "объясни, как наш auth middleware решает, какие роуты публичные", "none",
48
+ "CONTROL− — a question; the exclusion must survive the rewording"),
49
+ ("TYP-01", "поправь опечатку в заголовке README", "none",
50
+ "CONTROL− — a one-line edit; the exclusion must survive"),
51
+ ("SEO-01", "сделай аудит лендинга — почему упал трафик из поиска", "seo-aeo-audit",
52
+ "FALSE-POSITIVE test — 'аудит' must not be stolen from its owner"),
53
+ ("UXA-01", "проверь, что код соответствует UX-сценариям", "ux-audit",
54
+ "FALSE-POSITIVE test — the scenario audit has an owner"),
55
+ ("SKL-01", "проверь, соответствует ли этот скил стандарту", "make-skill",
56
+ "FALSE-POSITIVE test — skill-shaped audit has an owner"),
57
+ ]
58
+
59
+
60
+ def _desc(path):
61
+ """The description as the platform reads it: the frontmatter field, unwrapped."""
62
+ t = open(path, encoding="utf-8").read()
63
+ n = re.search(r"^name:\s*(.+)$", t, re.M)
64
+ d = re.search(r"^description:\s*(.+?)(?=^[a-z_-]+:\s|^---)", t, re.M | re.S)
65
+ if not (n and d):
66
+ return None
67
+ return n.group(1).strip(), " ".join(d.group(1).split()).strip("\"' ")
68
+
69
+
70
+ def choices():
71
+ out = []
72
+ s = _desc(SELF)
73
+ if s:
74
+ out.append(s)
75
+ for m in COMPETITORS:
76
+ base = os.path.join(MARKETPLACES, m)
77
+ if not os.path.isdir(base):
78
+ continue
79
+ found = subprocess.run(["find", base, "-name", "SKILL.md"],
80
+ capture_output=True, text=True).stdout.split()
81
+ for f in sorted(found):
82
+ got = _desc(f)
83
+ if got and got[0] not in {o[0] for o in out}:
84
+ out.append(got)
85
+ return out
86
+
87
+
88
+ PROMPT = """You are choosing which skill, if any, to invoke for one user request.
89
+
90
+ Below are the skills available, each with the description its author wrote. You have
91
+ nothing else — no repository, no history, no knowledge of these tools beyond what is
92
+ written here.
93
+
94
+ {catalogue}
95
+
96
+ The user says:
97
+
98
+ {query}
99
+
100
+ Answer with exactly two lines and nothing else:
101
+
102
+ SKILL: <the skill name, or the word none>
103
+ WHY: <one sentence, quoting the phrase in the description that decided it>
104
+ """
105
+
106
+
107
+ def main():
108
+ if "--list" in sys.argv:
109
+ for qid, q, exp, why in QUERIES:
110
+ print(f"{qid:<8}{exp:<16}{q}\n{'':8}{'':16}# {why}")
111
+ return 0
112
+ if "--choices" in sys.argv:
113
+ for name, d in choices():
114
+ print(f"{name:<18}{len(d):>5} chars")
115
+ return 0
116
+ if len(sys.argv) < 2:
117
+ print(__doc__)
118
+ return 2
119
+ qid = sys.argv[1]
120
+ row = next((r for r in QUERIES if r[0] == qid), None)
121
+ if row is None:
122
+ print(f"unknown query id {qid}; --list to see them", file=sys.stderr)
123
+ return 2
124
+ cat = "\n".join(f"- **{name}** — {d}" for name, d in choices())
125
+ print(PROMPT.format(catalogue=cat, query=row[1]))
126
+ return 0
127
+
128
+
129
+ if __name__ == "__main__":
130
+ raise SystemExit(main())
@@ -307,6 +307,92 @@
307
307
  "Does NOT invoke task-pipeline, does not open a grill and does not write a brief"
308
308
  ],
309
309
  "why": "Answering a question is on the skill's own not-for list. The description carries that boundary and nothing has ever measured whether a model honours it."
310
+ },
311
+ {
312
+ "id": "TRIG-07",
313
+ "category": "should_trigger",
314
+ "skills": [
315
+ "task-pipeline"
316
+ ],
317
+ "query": "сделай аудит модуля оплат",
318
+ "expected_behavior": [
319
+ "Invokes the skill: the output is a finding that lands in the repository, which the description names",
320
+ "Runs audit.md's L0->L7 ladder as the whole task — stages 3-5 produce findings and fixes rather than a feature",
321
+ "Does NOT justify the routing by stretching a build verb such as 'hardening'"
322
+ ],
323
+ "why": "Measured 2026-08-10: before the findings clause this routed, but its stated reason was the build-verb list. A right answer resting on a stretch is one rewording from a miss."
324
+ },
325
+ {
326
+ "id": "TRIG-08",
327
+ "category": "should_trigger",
328
+ "skills": [
329
+ "task-pipeline"
330
+ ],
331
+ "query": "проверь, нет ли ошибок в обработчике вебхуков",
332
+ "expected_behavior": [
333
+ "Invokes the skill rather than answering in the chat",
334
+ "Findings become REQ or board rows with file:line and a minimal fix, ordered by seam"
335
+ ],
336
+ "why": "Measured before the change: answered `none`, quoting the exclusion clause 'explaining or reading code'. Reading is the opening move, not the boundary."
337
+ },
338
+ {
339
+ "id": "TRIG-09",
340
+ "category": "should_trigger",
341
+ "skills": [
342
+ "task-pipeline"
343
+ ],
344
+ "query": "проверь, всё ли живо в проде после вчерашнего релиза",
345
+ "expected_behavior": [
346
+ "Invokes the skill; the verification trio, not one of three",
347
+ "Reads the CI run and the platform's own log command rather than assuming a verdict"
348
+ ],
349
+ "why": "Measured before the change: answered `none` on the same exclusion. A production check ends in a verification row, which lands."
350
+ },
351
+ {
352
+ "id": "TRIG-10",
353
+ "category": "should_trigger",
354
+ "skills": [
355
+ "task-pipeline"
356
+ ],
357
+ "query": "посмотри PR #24 и скажи, что там не так",
358
+ "expected_behavior": [
359
+ "Invokes the skill and applies review.md's reviewer contract, including the three reader states",
360
+ "Prints NO READER rather than staying silent when no independent reader is available"
361
+ ],
362
+ "why": "Measured before the change: answered `none` because no description offered a phrase for reviewing a PR, while review.md owns exactly that contract."
363
+ },
364
+ {
365
+ "id": "NOTRIG-06",
366
+ "category": "should_not_trigger",
367
+ "skills": [],
368
+ "query": "сделай аудит лендинга — почему упал трафик из поиска",
369
+ "expected_behavior": [
370
+ "Routes to seo-aeo-audit, whose description names 'почему упал трафик' verbatim",
371
+ "The widened findings vocabulary does not claim an audit that has an owner"
372
+ ],
373
+ "why": "False-positive control. Widening 'audit' must not steal work: gates.md sets the false-positive budget at zero."
374
+ },
375
+ {
376
+ "id": "NOTRIG-07",
377
+ "category": "should_not_trigger",
378
+ "skills": [],
379
+ "query": "проверь, что код соответствует UX-сценариям",
380
+ "expected_behavior": [
381
+ "Routes to ux-audit, which owns the scenario audit",
382
+ "The findings vocabulary in task-pipeline's description does not override an owner that names the surface exactly"
383
+ ],
384
+ "why": "False-positive control against the second competing claim on the word audit."
385
+ },
386
+ {
387
+ "id": "NOTRIG-08",
388
+ "category": "should_not_trigger",
389
+ "skills": [],
390
+ "query": "проверь, соответствует ли этот скил стандарту",
391
+ "expected_behavior": [
392
+ "Routes to make-skill, which owns skill-shaped audits",
393
+ "The findings vocabulary in task-pipeline's description does not override an owner that names the artefact exactly"
394
+ ],
395
+ "why": "False-positive control against the third competing claim on the word audit."
310
396
  }
311
397
  ]
312
398
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-pipeline-skill",
3
- "version": "1.38.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.38.0",
5
+ "version": "1.40.0",
6
6
  "author": {
7
7
  "name": "ssheleg",
8
8
  "url": "https://x.com/sshlg93"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: task-pipeline
3
- description: "Runs a substantial task through a full delivery pipeline: an intake grill that expands the request into a locked brief, then docs study, brainstorm, spec, plan, subagent build, tests, lint/deploy, post-deploy check, docs/wiki sync and acceptance — gated stages whose doctrine ships inside this skill (no required companions). Use when work changes the repository — a feature, fix, refactor, migration, integration, rewrite, adoption or hardening; фича, фикс, рефактор, миграция, интеграция, доработать, починить, внедрить — or on 'run this through the pipeline' / 'прогони по конвейеру', 'the full cycle' / 'полный цикл', /task-pipeline. Two modes need no task at all: 'checkup' / 'чекап' reports what has shipped without a person confirming it and what to look at first; 'setup' audits the documentation a project already has. Not for: answering a question, explaining or reading code, a typo or a one-line edit — say 'без пайплайна' / 'quick' to opt out."
3
+ description: "Runs a substantial task through a full delivery pipeline: an intake grill that locks the request into a brief, then docs study, brainstorm, spec, plan, subagent build, tests, lint/deploy, post-deploy check, docs/wiki sync and acceptance. Use when work changes the repository — a feature, fix, refactor, migration, integration, rewrite, adoption or hardening; фича, фикс, рефактор, миграция, интеграция, доработать, починить, внедрить, перевести — or when the output is a finding that lands in it: audit/аудит, bug hunt/проверь ошибки, production check/проверь прод, PR review/ревью PR — or on 'run this through the pipeline' / 'прогони по конвейеру', 'the full cycle' / 'полный цикл', /task-pipeline. Two modes need no task at all: 'checkup' / 'чекап' reports what has shipped without a person confirming it and what to look at first; 'setup' audits the documentation a project already has. Not for: answering a question, explaining code, a typo or a one-line edit — say 'без пайплайна' / 'quick' to opt out."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -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*).
@@ -17,20 +17,32 @@ than no boundary.
17
17
 
18
18
  ---
19
19
 
20
- ## Routing — repo-changing work goes through the pipeline
20
+ ## Routing — work that lands in the repository goes through the pipeline
21
21
 
22
- **When `task-pipeline` is installed, any work that CHANGES THE REPOSITORY goes
23
- through it** — without being asked for. A feature, a fix, a refactor, a migration, an
24
- integration, a rewrite, an adoption, a hardening pass; in any language and any
25
- phrasing. Saying *"run this through the pipeline"* is an accelerator, not a
26
- precondition.
22
+ **When `task-pipeline` is installed, any work that LANDS IN THE REPOSITORY goes
23
+ through it** — without being asked for. Two shapes, not one:
27
24
 
28
- **The boundary is "changes the repository", and it cuts both ways.** Not through the
29
- pipeline:
25
+ - **a change** a feature, a fix, a refactor, a migration, an integration, a rewrite,
26
+ an adoption, a hardening pass;
27
+ - **a finding that lands** — an audit, a bug hunt, a production check, a PR review. Its
28
+ output is not an answer in the chat: it is REQ rows, board rows, ledger rows and
29
+ fixes, and the pipeline is what carries them there.
30
30
 
31
- - a question and its answer, an explanation, reading or mapping code;
31
+ In any language and any phrasing. Saying *"run this through the pipeline"* is an
32
+ accelerator, not a precondition.
33
+
34
+ **The boundary is what the request ENDS IN, and it cuts both ways.** An answer stops in
35
+ the conversation; a change or a finding lands in the tree. Not through the pipeline:
36
+
37
+ - a question and its answer, an explanation, mapping code so a person can read it;
32
38
  - a typo, a one-line edit, a mechanical rename;
33
- - reconnaissance or measurement that commits nothing.
39
+ - reconnaissance or measurement that lands nothing.
40
+
41
+ **Reading is not the test.** An audit, a bug hunt and a PR review all *begin* by reading,
42
+ and all three end in the repository. Excluding them because they read is how a skill with
43
+ a whole doctrine file for audits (`references/audit.md`) never gets reached by the word
44
+ *audit* — measured, not supposed: three of ten routing queries were refused with this
45
+ rule's own exclusion quoted back as the reason.
34
46
 
35
47
  Running ten gated stages for one character is the fastest way to teach an agent to
36
48
  route around the pipeline entirely.