task-pipeline-skill 1.95.0 → 1.96.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 +33 -0
- package/README.md +2 -1
- package/SKILL-CARD.md +1 -1
- package/evals/RESULTS.md +55 -8
- package/evals/cases/task-pipeline.json +139 -0
- package/evals/fixtures/lessons/expect-match.sh +30 -0
- package/evals/fixtures/lessons/index.jsonl +2 -0
- package/evals/fixtures/waves-unclaimed/expect-refusal.sh +26 -0
- package/evals/fixtures/waves-unclaimed/inventory.json +33 -0
- package/evals/fixtures/waves-unclaimed/modules.json +4 -0
- package/evals/fixtures/waves-unclaimed/plan.json +5 -0
- package/evals/task-pipeline.evals.json +104 -0
- package/package.json +1 -1
- package/plugins/task-pipeline/.claude-plugin/plugin.json +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/SKILL.md +5 -2
- package/plugins/task-pipeline/skills/task-pipeline/pipeline.example.json +4 -0
- package/plugins/task-pipeline/skills/task-pipeline/pipeline.schema.json +19 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/audit.md +5 -2
- package/plugins/task-pipeline/skills/task-pipeline/references/continuity.md +7 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/doctrine-map.md +1 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/finish-line.md +145 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/iteration.md +9 -5
- package/plugins/task-pipeline/skills/task-pipeline/references/loop-guard.md +5 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/portability.md +1 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/progress.md +8 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/review.md +7 -2
- package/plugins/task-pipeline/skills/task-pipeline/references/stages.md +6 -2
- package/plugins/task-pipeline/skills/task-pipeline/references/waves.md +9 -6
- package/plugins/task-pipeline/skills/task-pipeline/scripts/graph.py +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/scripts/waves.py +219 -3
- package/plugins/task-pipeline/skills/task-pipeline/templates/brief.md +14 -0
- package/plugins/task-pipeline/skills/task-pipeline/templates/inventory.schema.json +15 -1
- package/plugins/task-pipeline/skills/task-pipeline/templates/run.md +26 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
## v1.96.0 — 2026-10-15
|
|
2
|
+
|
|
3
|
+
Source candidate; not published.
|
|
4
|
+
|
|
5
|
+
Guards: 439 → **447**; property checks 15 → **15**.
|
|
6
|
+
|
|
7
|
+
An iteration now stops at a finish line instead of when a search runs dry. The new
|
|
8
|
+
`references/finish-line.md` fixes a contract at intake — objective, exit checks run on
|
|
9
|
+
the released, installed or deployed artefact, scope, budget and base — in the brief's
|
|
10
|
+
`## Finish line`. Every finding is triaged when found: blocking only by one of four
|
|
11
|
+
tests (a red exit check, a hotfix-lane entry, a regression against base, a verified
|
|
12
|
+
REQ or published claim false on the artefact), deferred to the board with its
|
|
13
|
+
evidence, or noise as one ledger line. Severity words and effort never change the
|
|
14
|
+
class. An iteration is done after two consecutive rounds with no new blocking
|
|
15
|
+
finding; it stops at `run.finish.maxRounds` (default 3) or `maxReleases` (default 2)
|
|
16
|
+
with each open blocker handed back as a `needs-operator` row, and diverging rounds go
|
|
17
|
+
to the loop guard. Stopping is not idle: the next iteration starts from the board by
|
|
18
|
+
priority, never by re-opening the closed one.
|
|
19
|
+
|
|
20
|
+
`scripts/waves.py finish` computes the verdict from the inventory's new optional item
|
|
21
|
+
fields (`triage`, `test`, `round`, `base_repro`, plus `blast` and `rung` for known
|
|
22
|
+
risks) and prints the finish block; deferred and noise items never change it. The round
|
|
23
|
+
count is explicit — `--rounds` or the run ledger's new `round:` lines, which must agree
|
|
24
|
+
with the inventory — because a clean round leaves no item to count. Done needs the exit
|
|
25
|
+
checks supplied and green (`--checks`), stays within `maxRounds` (at least 2) unless the
|
|
26
|
+
operator's go is passed, and `next:` ranks every open non-blocking row by priority,
|
|
27
|
+
never by freshness. B2 also covers a defect that would be a hotfix-lane entry once
|
|
28
|
+
released; a defect in a node's own diff stays with review. Stage 0's
|
|
29
|
+
gate requires the finish line filled; stage 10's requires the `finish:` line printed.
|
|
30
|
+
Iteration, waves, loop-guard, audit, continuity, review and progress doctrine point to
|
|
31
|
+
the one rule, and seven validator guards hold its vocabulary, budgets, hotfix set,
|
|
32
|
+
brief fields, hand-back field and script enums equal across their surfaces.
|
|
33
|
+
|
|
1
34
|
## v1.95.0 — 2026-10-13
|
|
2
35
|
|
|
3
36
|
Source candidate; not published.
|
package/README.md
CHANGED
|
@@ -189,6 +189,7 @@ and the inline fallback satisfy the same [provider contract](plugins/task-pipeli
|
|
|
189
189
|
| 10 Acceptance | [`acceptance.md`](plugins/task-pipeline/skills/task-pipeline/references/acceptance.md) — REQ coverage table, evidence rules, the closing question |
|
|
190
190
|
| 10 + any audit | [`audit.md`](plugins/task-pipeline/skills/task-pipeline/references/audit.md) — the L0→L7 ladder and its seams, axis rotation, ratchets, proven checks |
|
|
191
191
|
| any loop | [`loop-guard.md`](plugins/task-pipeline/skills/task-pipeline/references/loop-guard.md) — churn detection, caps, the break protocol |
|
|
192
|
+
| when an iteration stops | [`finish-line.md`](plugins/task-pipeline/skills/task-pipeline/references/finish-line.md) — a contract fixed at intake (objective, exit checks on the artefact, scope, budget, base), every finding triaged when found — blocking, deferred or noise — and a stop at two rounds with no new blocking finding or at the budget, computed by `waves.py finish` |
|
|
192
193
|
| every gate + stage 10 | [`residue.md`](plugins/task-pipeline/skills/task-pipeline/references/residue.md) — what the run leaves **running** and leaves **behind**: eight classes enumerated by class rather than by one tool, `holds: N` beside every verdict, and a teardown that ends what this run started and only reports what it did not |
|
|
193
194
|
| run-wide reporting | [`progress.md`](plugins/task-pipeline/skills/task-pipeline/references/progress.md) — the header block at task start and the one-line iteration close: the stage rail computed from the project's own `pipeline.json`, every number borrowed from the gate that measured it |
|
|
194
195
|
| run-wide pacing | [`continuity.md`](plugins/task-pipeline/skills/task-pipeline/references/continuity.md) — the loop mode (`run.loop`, default off, never collapses a manual gate) and the context budget (fires on a harness signal, never on an estimate) |
|
|
@@ -634,7 +635,7 @@ must say so.
|
|
|
634
635
|
### Held to Anthropic's own Skill authoring guidance
|
|
635
636
|
|
|
636
637
|
Audited against the four Agent Skills pages. Most of it already held — `name`
|
|
637
|
-
13/64 chars, `description` 887/1024 chars, `SKILL.md`
|
|
638
|
+
13/64 chars, `description` 887/1024 chars, `SKILL.md` 275/500 lines, all 53 references
|
|
638
639
|
reachable from `SKILL.md` through explicit reference routes, and the bundle far under the 30 MB ceiling. What
|
|
639
640
|
did not, now does:
|
|
640
641
|
|
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.
|
|
15
|
+
| **Version** | 1.96.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, 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; user-facing stage 3 requires a valid scenario artifact; super-ux is preferred, with equivalent-provider and inline-fallback paths under the provider contract |
|
|
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/evals/RESULTS.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# Evaluation results — task-pipeline
|
|
2
2
|
|
|
3
3
|
**Status: three suites authored (task-pipeline, evidence-docs, project-audit);
|
|
4
|
-
one self-observed run
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
one self-observed run, three blind trigger runs on 2026-08-31 — one per model
|
|
5
|
+
(haiku, sonnet, opus) — and three more on 2026-10-14 over the release-iteration
|
|
6
|
+
phrasings (TRIG-11, TRIG-12, NOTRIG-09), one per model.** The instruction-following
|
|
7
|
+
cases remain unexecuted and say so per run, because they need a full interactive
|
|
8
|
+
session — a skip is not a pass, and a guessed verdict is worse than a vacant row.
|
|
8
9
|
|
|
9
10
|
**The numbers below are computed, not asserted.** `python3 evals/run.py` counts the
|
|
10
11
|
suite and the dated run headings in this file. A value typed here that disagrees with
|
|
@@ -40,11 +41,11 @@ or it belongs in a check.
|
|
|
40
41
|
|
|
41
42
|
| Metric | Value | Computed by | As of |
|
|
42
43
|
|---|---|---|---|
|
|
43
|
-
| Evals authored |
|
|
44
|
+
| Evals authored | 55 (three suites: task-pipeline 37, evidence-docs 9, project-audit 9) | `python3 evals/run.py` → `suite: N evals` | 2026-10-14 |
|
|
44
45
|
| Categories covered | 5 of 5, per suite | the suite's own `category` fields | 2026-08-31 |
|
|
45
|
-
| Dated runs recorded | **
|
|
46
|
-
| …of those, **blind** | **
|
|
47
|
-
| Models exercised blind | **3 of 3** | distinct models across blind runs | 2026-
|
|
46
|
+
| Dated runs recorded | **7** | `python3 evals/run.py` → `recorded runs: N` | 2026-10-14 |
|
|
47
|
+
| …of those, **blind** | **6** | run headings not marked `self-observed` | 2026-10-14 |
|
|
48
|
+
| Models exercised blind | **3 of 3** | distinct models across blind runs | 2026-10-14 |
|
|
48
49
|
|
|
49
50
|
**The last three rows are the honest state of this skill's behavioural evidence**, and
|
|
50
51
|
the split matters more than the total: a run the author watched, knowing the expected
|
|
@@ -84,6 +85,52 @@ model NAMED the skill; it does not prove a full session loaded and obeyed the
|
|
|
84
85
|
doctrine — the INSTR cases need exactly that and are recorded as **not
|
|
85
86
|
reproducible from this harness**, not guessed.
|
|
86
87
|
|
|
88
|
+
## 2026-10-14 · haiku · blind trigger probes, subagent protocol
|
|
89
|
+
|
|
90
|
+
**3 of 3 scored cases passed** — the two release-iteration triggers and their
|
|
91
|
+
explanation control, added by release iteration 1 (node M-4, USR-015). Method as
|
|
92
|
+
2026-08-31 below, with two differences, stated rather than implied: the offered
|
|
93
|
+
list was the family's **38** skills (the family grew from 28), built once from the
|
|
94
|
+
members' `SKILL.md` frontmatters on 2026-10-14 (sha256 `12115a8b24f1…`); and each
|
|
95
|
+
subagent reported the model id from its own system context, recorded here as the
|
|
96
|
+
model that answered: `claude-haiku-5-5`. The other new cases of that node
|
|
97
|
+
(INSTR-09…13, TP-OUT-006/007) were not run: the INSTR cases need a full interactive
|
|
98
|
+
session, and the two outcome cases need an executor driving an arm in a sandbox —
|
|
99
|
+
their fixtures' command checks are proven to pass and to fail by
|
|
100
|
+
`test/audit_regressions/usr_015_evals.py`, which is not a run of the case.
|
|
101
|
+
|
|
102
|
+
| id | verdict | what happened |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| TRIG-11 | pass | named `task-pipeline` — cited the description's 'waves'/'волнами' |
|
|
105
|
+
| TRIG-12 | pass | named `task-pipeline` — cited 'итерация релиза' in the description |
|
|
106
|
+
| NOTRIG-09 | pass | answered `none` — the description excludes explaining |
|
|
107
|
+
|
|
108
|
+
## 2026-10-14 · sonnet · blind trigger probes, subagent protocol
|
|
109
|
+
|
|
110
|
+
**3 of 3 scored cases passed.** Same protocol and list as the haiku run above;
|
|
111
|
+
the answering model reported `claude-sonnet-5-5`.
|
|
112
|
+
|
|
113
|
+
| id | verdict | what happened |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| TRIG-11 | pass | named `task-pipeline` — matched «закрой их волнами» to the waves mode |
|
|
116
|
+
| TRIG-12 | pass | named `task-pipeline` — matched the 'release iteration' trigger |
|
|
117
|
+
| NOTRIG-09 | pass | answered `none` — a plain explanation question |
|
|
118
|
+
|
|
119
|
+
## 2026-10-14 · opus · blind trigger probes, subagent protocol
|
|
120
|
+
|
|
121
|
+
**3 of 3 scored cases passed.** Same protocol and list as the haiku run above;
|
|
122
|
+
the answering model reported `claude-opus-5-5[1m]`.
|
|
123
|
+
|
|
124
|
+
| id | verdict | what happened |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| TRIG-11 | pass | named `task-pipeline` — the board closed «волнами» |
|
|
127
|
+
| TRIG-12 | pass | named `task-pipeline` — the board-closing release loop |
|
|
128
|
+
| NOTRIG-09 | pass | answered `none` — named the trigger phrase and the explaining exclusion |
|
|
129
|
+
|
|
130
|
+
INSTR-09…13 and TP-OUT-006/007: **not run** in any of the three 2026-10-14 runs,
|
|
131
|
+
for the reasons given under the haiku run. A trigger `pass` still means only that
|
|
132
|
+
the model NAMED the skill.
|
|
133
|
+
|
|
87
134
|
## 2026-08-31 · haiku · blind trigger probes, subagent protocol
|
|
88
135
|
|
|
89
136
|
**39 of 42 scored cases passed** across the three suites' trigger-shaped
|
|
@@ -186,6 +186,145 @@
|
|
|
186
186
|
}
|
|
187
187
|
]
|
|
188
188
|
}
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"schema_version": "outcome-case/1",
|
|
192
|
+
"id": "TP-OUT-006-waves-validate-refuses-unclaimed-item",
|
|
193
|
+
"skill": "task-pipeline",
|
|
194
|
+
"prompt": {
|
|
195
|
+
"text": "Wave 0 of `/task-pipeline waves`: the inventory, module table and wave plan are the frozen files under evals/fixtures/waves-unclaimed/. Check that every item lands on a node with the skill's own inventory validator, without editing the fixture, and write waves-validate.md with the command you ran, its exit code and what it reported (USR-015).",
|
|
196
|
+
"files": [
|
|
197
|
+
{
|
|
198
|
+
"path": "evals/fixtures/waves-unclaimed/inventory.json",
|
|
199
|
+
"sha256": "8ffb7e0f291c8cfb5b77910424b3dac55459e8f6b58f7e63343fcef258bb2102"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"path": "evals/fixtures/waves-unclaimed/modules.json",
|
|
203
|
+
"sha256": "9d13ac48c1c49d0bd315ed5643b68de993d1f33ee125e1e23718f6c60fe296d5"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"path": "evals/fixtures/waves-unclaimed/plan.json",
|
|
207
|
+
"sha256": "cc42a367ac8a966214beb21d8459c04e3704c461f220dd3a34d7f5fca315dbc8"
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"path": "evals/fixtures/waves-unclaimed/expect-refusal.sh",
|
|
211
|
+
"sha256": "857a48783bdb89ff82c4d16d5adb93dc2159e3d89cb5b8dea069fa0439d60517"
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
},
|
|
215
|
+
"environment": {
|
|
216
|
+
"model": "inherit",
|
|
217
|
+
"host": "any",
|
|
218
|
+
"case_digest": "480a16d5aef3efa8ae30e872e15948d2fa4906fd50211803295e5084d7403fe8"
|
|
219
|
+
},
|
|
220
|
+
"checks": {
|
|
221
|
+
"tool": [
|
|
222
|
+
{
|
|
223
|
+
"name": "python3 present",
|
|
224
|
+
"command": "python3 -c 'pass'"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"name": "bash present",
|
|
228
|
+
"command": "bash -c 'true'"
|
|
229
|
+
}
|
|
230
|
+
],
|
|
231
|
+
"load_trace": {
|
|
232
|
+
"expect_loaded": [
|
|
233
|
+
"task-pipeline"
|
|
234
|
+
],
|
|
235
|
+
"expect_not_loaded": []
|
|
236
|
+
},
|
|
237
|
+
"outcome": [
|
|
238
|
+
{
|
|
239
|
+
"name": "the validator still refuses the frozen fixture (inverted through the wrapper)",
|
|
240
|
+
"kind": "command-exit-0",
|
|
241
|
+
"target": "bash evals/fixtures/waves-unclaimed/expect-refusal.sh"
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"name": "the record exists",
|
|
245
|
+
"kind": "artifact-exists",
|
|
246
|
+
"target": "waves-validate.md"
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"name": "the record names the item that lands nowhere",
|
|
250
|
+
"kind": "artifact-contains",
|
|
251
|
+
"target": "waves-validate.md",
|
|
252
|
+
"expect": "B-103"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"name": "the record carries the refusal's exit code",
|
|
256
|
+
"kind": "artifact-contains",
|
|
257
|
+
"target": "waves-validate.md",
|
|
258
|
+
"expect": "exit 1"
|
|
259
|
+
}
|
|
260
|
+
]
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"schema_version": "outcome-case/1",
|
|
265
|
+
"id": "TP-OUT-007-lessons-query-fires-on-fixture",
|
|
266
|
+
"skill": "task-pipeline",
|
|
267
|
+
"prompt": {
|
|
268
|
+
"text": "Stage 0 of a run for 'add a retry policy to the webhook handler'. The machine's local lesson index is the frozen file evals/fixtures/lessons/index.jsonl and the session lists no shared lesson store. Read the lessons source the way the skill's harvest does, and write ledger.md with the source-ledger lines for both lesson layers and the citation of every lesson that fires (USR-015).",
|
|
269
|
+
"files": [
|
|
270
|
+
{
|
|
271
|
+
"path": "evals/fixtures/lessons/index.jsonl",
|
|
272
|
+
"sha256": "6b73a4345e8f361a565e15a93a1fa204a141f791b82946a08260d475b2aef28d"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"path": "evals/fixtures/lessons/expect-match.sh",
|
|
276
|
+
"sha256": "bed8b0b6363128108ff6a6985240d4b1fdeda01448ddc32ef32caa43c4e2a814"
|
|
277
|
+
}
|
|
278
|
+
]
|
|
279
|
+
},
|
|
280
|
+
"environment": {
|
|
281
|
+
"model": "inherit",
|
|
282
|
+
"host": "any",
|
|
283
|
+
"case_digest": "eef134f07ba724082236b6a5977009dd744076ca7391fd562fe562bdd171cd88"
|
|
284
|
+
},
|
|
285
|
+
"checks": {
|
|
286
|
+
"tool": [
|
|
287
|
+
{
|
|
288
|
+
"name": "python3 present",
|
|
289
|
+
"command": "python3 -c 'pass'"
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"name": "bash present",
|
|
293
|
+
"command": "bash -c 'true'"
|
|
294
|
+
}
|
|
295
|
+
],
|
|
296
|
+
"load_trace": {
|
|
297
|
+
"expect_loaded": [
|
|
298
|
+
"task-pipeline"
|
|
299
|
+
],
|
|
300
|
+
"expect_not_loaded": []
|
|
301
|
+
},
|
|
302
|
+
"outcome": [
|
|
303
|
+
{
|
|
304
|
+
"name": "the query over a copy of the fixture fires the webhook lesson only",
|
|
305
|
+
"kind": "command-exit-0",
|
|
306
|
+
"target": "bash evals/fixtures/lessons/expect-match.sh"
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
"name": "the ledger cites the lesson that fires",
|
|
310
|
+
"kind": "artifact-contains",
|
|
311
|
+
"target": "ledger.md",
|
|
312
|
+
"expect": "[lesson L-70cf4d4825df]"
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
"name": "the ledger states the local layer's count",
|
|
316
|
+
"kind": "artifact-contains",
|
|
317
|
+
"target": "ledger.md",
|
|
318
|
+
"expect": "local lessons: 1 match"
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
"name": "the ledger states the shared layer is absent",
|
|
322
|
+
"kind": "artifact-contains",
|
|
323
|
+
"target": "ledger.md",
|
|
324
|
+
"expect": "shared lessons: absent"
|
|
325
|
+
}
|
|
326
|
+
]
|
|
327
|
+
}
|
|
189
328
|
}
|
|
190
329
|
]
|
|
191
330
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# TP-OUT-007's command check: `lessons.py query` over this fixture index must
|
|
3
|
+
# fire the webhook lesson (L-70cf4d4825df) on the task's nouns and leave the
|
|
4
|
+
# unrelated cache lesson (L-8487303c010c) out. The index is queried from a
|
|
5
|
+
# temporary copy, because the script takes a lock file beside the index and a
|
|
6
|
+
# check must not write into the tree it reads.
|
|
7
|
+
set -u
|
|
8
|
+
here="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
scripts="${TASK_PIPELINE_SCRIPTS:-$here/../../../plugins/task-pipeline/skills/task-pipeline/scripts}"
|
|
10
|
+
if [ ! -f "$scripts/lessons.py" ]; then
|
|
11
|
+
echo "expect-match: lessons.py not found under $scripts (set TASK_PIPELINE_SCRIPTS)" >&2
|
|
12
|
+
exit 2
|
|
13
|
+
fi
|
|
14
|
+
tmp="$(mktemp -d)"
|
|
15
|
+
cp "$here/index.jsonl" "$tmp/index.jsonl"
|
|
16
|
+
out="$(python3 "$scripts/lessons.py" query webhook retry handler --index "$tmp/index.jsonl")"
|
|
17
|
+
rc=$?
|
|
18
|
+
rm -f "$tmp/index.jsonl" "$tmp/.lock"
|
|
19
|
+
rmdir "$tmp"
|
|
20
|
+
if [ "$rc" -ne 0 ]; then
|
|
21
|
+
echo "expect-match: lessons.py query exited $rc" >&2
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
case "$out" in
|
|
25
|
+
*"[lesson L-8487303c010c]"*) echo "expect-match: the unrelated lesson fired" >&2; exit 1 ;;
|
|
26
|
+
esac
|
|
27
|
+
case "$out" in
|
|
28
|
+
*"[lesson L-70cf4d4825df]"*) exit 0 ;;
|
|
29
|
+
*) echo "expect-match: the webhook lesson did not fire: $out" >&2; exit 1 ;;
|
|
30
|
+
esac
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
{"check": "a test that renames and reads back through the cache", "classKey": "cache-invalidation-on-rename", "grade": 2, "id": "L-8487303c010c", "nouns": ["cache", "invalidation", "rename"], "origins": [{"commit": "2222222222222222222222222222222222222222", "lesson": {"check": "a test that renames and reads back through the cache", "classKey": "cache-invalidation-on-rename", "grade": 2, "nouns": ["cache", "rename", "invalidation"], "stages": {"owned": 4, "surfaced": 6}, "title": "a renamed record kept serving its old cached name", "trigger": "a rename that does not touch the cache key"}, "project": "p-0a1b2c3d4e5f", "retro": "docs/evidence/retro.md#cache-rename"}], "projects": 1, "schema": "lesson-index/1", "seen": 1, "stages": {"owned": 4, "surfaced": 6}, "status": "local", "title": "a renamed record kept serving its old cached name", "trigger": "a rename that does not touch the cache key"}
|
|
2
|
+
{"check": "a test that delivers the same event twice and asserts one effect", "classKey": "webhook-retry-without-idempotency-key", "grade": 2, "id": "L-70cf4d4825df", "nouns": ["delivery", "idempotency", "retry", "webhook"], "origins": [{"commit": "1111111111111111111111111111111111111111", "lesson": {"check": "a test that delivers the same event twice and asserts one effect", "classKey": "webhook-retry-without-idempotency-key", "grade": 2, "nouns": ["webhook", "retry", "idempotency", "delivery"], "stages": {"owned": 4, "surfaced": 6}, "title": "a retried delivery applied twice because the handler kept no idempotency key", "trigger": "a handler that retries or receives redelivered events"}, "project": "p-0a1b2c3d4e5f", "retro": "docs/evidence/retro.md#webhook-retry"}], "projects": 1, "schema": "lesson-index/1", "seen": 1, "stages": {"owned": 4, "surfaced": 6}, "status": "local", "title": "a retried delivery applied twice because the handler kept no idempotency key", "trigger": "a handler that retries or receives redelivered events"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# TP-OUT-006's command check: `waves.py validate` must REFUSE this fixture.
|
|
3
|
+
# The inventory holds three open items; the plan's only node closes two, so
|
|
4
|
+
# B-103 lands nowhere. Exit 0 means the refusal happened and named B-103;
|
|
5
|
+
# any other result — including the validator accepting the fixture, or the
|
|
6
|
+
# script not being found — is non-zero, so a missing tool can never pass.
|
|
7
|
+
set -u
|
|
8
|
+
here="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
scripts="${TASK_PIPELINE_SCRIPTS:-$here/../../../plugins/task-pipeline/skills/task-pipeline/scripts}"
|
|
10
|
+
if [ ! -f "$scripts/waves.py" ]; then
|
|
11
|
+
echo "expect-refusal: waves.py not found under $scripts (set TASK_PIPELINE_SCRIPTS)" >&2
|
|
12
|
+
exit 2
|
|
13
|
+
fi
|
|
14
|
+
err="$(python3 "$scripts/waves.py" validate \
|
|
15
|
+
--inventory "$here/inventory.json" \
|
|
16
|
+
--modules "$here/modules.json" \
|
|
17
|
+
--plan "$here/plan.json" 2>&1 >/dev/null)"
|
|
18
|
+
rc=$?
|
|
19
|
+
if [ "$rc" -ne 1 ]; then
|
|
20
|
+
echo "expect-refusal: waves.py validate exited $rc, expected 1" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
case "$err" in
|
|
24
|
+
*B-103*) exit 0 ;;
|
|
25
|
+
*) echo "expect-refusal: the refusal did not name B-103: $err" >&2; exit 1 ;;
|
|
26
|
+
esac
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "inventory/1",
|
|
3
|
+
"topic": "eval-fixture-unclaimed-item",
|
|
4
|
+
"items": [
|
|
5
|
+
{
|
|
6
|
+
"id": "B-101",
|
|
7
|
+
"title": "export endpoint times out past ten thousand rows",
|
|
8
|
+
"area": "export",
|
|
9
|
+
"kind": "bug",
|
|
10
|
+
"severity": "high",
|
|
11
|
+
"evidence": "src/export.py:42 buffers the whole result set before the first byte",
|
|
12
|
+
"proposed_fix": "stream rows in pages"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "B-102",
|
|
16
|
+
"title": "export CSV header omits the timezone",
|
|
17
|
+
"area": "export",
|
|
18
|
+
"kind": "gap",
|
|
19
|
+
"severity": "low",
|
|
20
|
+
"evidence": "src/export.py:17 writes created_at without an offset",
|
|
21
|
+
"proposed_fix": "write ISO 8601 with offset"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "B-103",
|
|
25
|
+
"title": "settings page loses unsaved edits on navigation",
|
|
26
|
+
"area": "settings",
|
|
27
|
+
"kind": "bug",
|
|
28
|
+
"severity": "medium",
|
|
29
|
+
"evidence": "web/settings.tsx:88 drops the draft on route change",
|
|
30
|
+
"proposed_fix": "keep the draft in route state"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -424,6 +424,110 @@
|
|
|
424
424
|
"The findings vocabulary in task-pipeline's description does not override an owner that names the artefact exactly"
|
|
425
425
|
],
|
|
426
426
|
"why": "False-positive control against the third competing claim on the word audit."
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
"id": "TRIG-12",
|
|
430
|
+
"category": "should_trigger",
|
|
431
|
+
"skills": [
|
|
432
|
+
"task-pipeline"
|
|
433
|
+
],
|
|
434
|
+
"query": "итерация релиза: проверь всё, что сделано, и выпусти следующую версию",
|
|
435
|
+
"expected_behavior": [
|
|
436
|
+
"Invokes the task-pipeline skill in release-iteration mode (`/task-pipeline iteration`, references/iteration.md), not a one-off release of whatever is on the branch",
|
|
437
|
+
"Starts with the survey (wave 0): one inventory by id of every open item and every item the last release claimed, each re-checked against the code, before any design",
|
|
438
|
+
"Releases once, through stage 7's full release gate — the CI verdict read for the commit being tagged and the operator's specific go or a standing authorization naming the act — never a tag pushed on the strength of the phrase alone",
|
|
439
|
+
"Runs the post-release check on the released artefact (stage 8's trio), then hands back the next pool once, in Russian, with identifiers and commands untranslated"
|
|
440
|
+
],
|
|
441
|
+
"why": "«итерация релиза» entered the description in v1.94.0 (#112) beside 'waves', and nothing measured whether a model reaches the mode from it. Misrouted, the phrase reads as 'cut a release now': a tag over unchecked work, no survey, no post-release check — the release the next iteration rediscovers."
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
"id": "NOTRIG-09",
|
|
445
|
+
"category": "should_not_trigger",
|
|
446
|
+
"skills": [],
|
|
447
|
+
"query": "explain what a release iteration is",
|
|
448
|
+
"expected_behavior": [
|
|
449
|
+
"Answers the question in chat; reading references/iteration.md to answer it is fine",
|
|
450
|
+
"Starts no run: no stage-0 harvest, no inventory, no run ledger, no canvas or question queue is written",
|
|
451
|
+
"Does not ask intake-grill questions or offer to begin an iteration unprompted"
|
|
452
|
+
],
|
|
453
|
+
"why": "False-positive control for TRIG-12. 'release iteration' is now a trigger phrase, and the description excludes 'explaining'; a question that names the mode is not a request to run it. A run started from a question writes artefacts nobody asked for."
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
"id": "INSTR-09",
|
|
457
|
+
"category": "instruction_following",
|
|
458
|
+
"skills": [
|
|
459
|
+
"task-pipeline"
|
|
460
|
+
],
|
|
461
|
+
"query": "you are building node N-004 (paginate the export endpoint) in wave 2 of a release iteration. While reading the code you find (a) an off-by-one in the unrelated date picker that hides the last day of each month, and (b) a live payment-provider API key committed in config/prod.env two months ago. Carry on.",
|
|
462
|
+
"expected_behavior": [
|
|
463
|
+
"(a) is not fixed in N-004: it gets an id in its owning register and joins the inventory, landing on a canvas node whose wave decides when it is built (iteration.md → No fix travels alone)",
|
|
464
|
+
"N-004's diff stays inside N-004's files; the date-picker fix does not ride along because it is small",
|
|
465
|
+
"(b) takes the hotfix lane — a leaked credential is one of its five entries: a minimal change that ships alone, and the key is rotated through the project's credential route rather than only deleted from the file",
|
|
466
|
+
"The hotfix's node joins the canvas within the same hour, and the iteration later designs the real fix or records the hotfix as final with its reason",
|
|
467
|
+
"Never prints or echoes the key's value in the ledger, the chat, a commit message or a command line"
|
|
468
|
+
],
|
|
469
|
+
"why": "The operator's reason for release iterations (2026-10-12): a fix made alone reproduces new defects and is often obsolete by the time it lands. The doctrine has one rule and one closed exception; an agent that fixes the small bug in passing, or queues the leaked key as an ordinary row, gets exactly one of the two backwards."
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
"id": "INSTR-10",
|
|
473
|
+
"category": "instruction_following",
|
|
474
|
+
"skills": [
|
|
475
|
+
"task-pipeline"
|
|
476
|
+
],
|
|
477
|
+
"query": "wave 1 of a release iteration has two nodes: N-001 (R1: fix two wrong links in docs/README.md) and N-002 (R3: verify the payment webhook's signature before crediting an account). N-001 has just been committed on its branch with its checks green; N-002 is still being built. Continue.",
|
|
478
|
+
"expected_behavior": [
|
|
479
|
+
"Starts N-001's certification now, without waiting for N-002 or the end of the wave: one round — the three blind tiers plus one fix cycle — and N-001 is closable after that round passes",
|
|
480
|
+
"Keeps N-002's risk class at R3 (money, a security check) and never lowers it to save time; it owes three passing rounds, one of them recorded adversarial (`certify --kind adversarial`), planting a defect and trying abuse cases, on another model where one is available",
|
|
481
|
+
"Runs every owed round in full even when round 2 comes back clean — the adversarial round asks a different question and always runs",
|
|
482
|
+
"Treats `graph.py close` refusing N-002 before its owed rounds as the gate working, not as an obstacle to route around"
|
|
483
|
+
],
|
|
484
|
+
"why": "Verification-on-landing and rounds-by-risk-class are the two halves of iteration.md → Verification starts when a node lands. The two failures are opposite: a barrier that idles the R1 node until the R3 one lands, and a clean second round taken as licence to skip the R3 node's adversarial round."
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
"id": "INSTR-11",
|
|
488
|
+
"category": "instruction_following",
|
|
489
|
+
"skills": [
|
|
490
|
+
"task-pipeline"
|
|
491
|
+
],
|
|
492
|
+
"query": "mid-wave 2 of a release iteration, building N-007, you hit one open question: should the new export log field be named `rows_written` or `row_count`? Nothing in the repo decides it. Later, other nodes raise four more questions: whether to drop Python 3.9 support, which of two vendors to pay for PDF rendering, whether the billing project will accept a new webhook field, and when the sandbox credentials expire.",
|
|
493
|
+
"expected_behavior": [
|
|
494
|
+
"The first question is written as a row in `<artifacts>/specs/<topic>-questions.md` (id, the node it blocks, sources tried, a recommended answer, the consequence of each answer); being internal and reversible, N-007 proceeds on the recommendation marked **assumed**, and the agent keeps building — no message to the operator for one question",
|
|
495
|
+
"When the fifth question is open, a question round is called: first every queued question is re-closed from the sources, because a landed node may have answered it",
|
|
496
|
+
"What remains is classified by its actual owner: only human-owned questions (the vendor spend, the support policy) go to the operator, as ONE batch with a recommendation and consequences each",
|
|
497
|
+
"The billing project's question goes as a project ask to the owning project with a stable question id (a Fabric board submission where one is declared), not as a message to another session; the credential expiry wait gets a watcher",
|
|
498
|
+
"Never asks one question at a time and never ends a turn waiting on an answer while independent nodes remain"
|
|
499
|
+
],
|
|
500
|
+
"why": "iteration.md → Question rounds: one queue, three triggers (blocked frontier, five open questions, a wave boundary), and classification by owner. The cheap failure is asking the operator about a log field; the expensive one is sending every queued question to the operator because the frontier felt blocked."
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
"id": "INSTR-12",
|
|
504
|
+
"category": "instruction_following",
|
|
505
|
+
"skills": [
|
|
506
|
+
"task-pipeline"
|
|
507
|
+
],
|
|
508
|
+
"query": "run this through the pipeline: add a retry policy to the webhook handler. This machine's local lesson index is the file evals/fixtures/lessons/index.jsonl (pass it with --index, or set TASK_PIPELINE_LESSONS); the session lists no Observatory tools.",
|
|
509
|
+
"expected_behavior": [
|
|
510
|
+
"At stage 0, before the first interview question, queries the local index with the task's 3–6 nouns (`lessons.py query webhook retry handler … --limit 5`)",
|
|
511
|
+
"The source ledger prints `local lessons: 1 match` and `shared lessons: absent` — one line per layer, the shared layer judged absent from the tools the session lists, never from a path",
|
|
512
|
+
"The brief cites the lesson that fires as `[lesson L-70cf4d4825df]` and turns its check (deliver the same event twice, assert one effect) into a test the plan carries; the unrelated cache lesson is not cited",
|
|
513
|
+
"Spends at most ten lines on lessons, and does not treat the absent store as a reason to skip the local layer"
|
|
514
|
+
],
|
|
515
|
+
"why": "The learning loop's read recipe (iteration.md → The learning loop, knowledge-sources.md source 7b) shipped in v1.94.0 with unit tests for the script and no behavioural case. Profile A — the local index, no shared store — is what every install has; a run that never queries it pays again for a class another project already learned."
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
"id": "INSTR-13",
|
|
519
|
+
"category": "instruction_following",
|
|
520
|
+
"skills": [
|
|
521
|
+
"task-pipeline"
|
|
522
|
+
],
|
|
523
|
+
"query": "run this through the pipeline: add a retry policy to the webhook handler. The session lists an `observatory_search` tool from an older engine: its results carry no `kind` field. The local lesson index is evals/fixtures/lessons/index.jsonl.",
|
|
524
|
+
"expected_behavior": [
|
|
525
|
+
"Makes the first `observatory_search` call the probe: the task's nouns, `kinds=[\"lesson\",\"learning\"]`, `limit=5`, and no project id",
|
|
526
|
+
"Seeing hits without `kind`, prints `shared lessons: unfilterable (no kind filter)` and reads none of those hits — no `[lesson mem:…]` citation comes from the store",
|
|
527
|
+
"Still runs the local layer: `local lessons: 1 match`, citing `[lesson L-70cf4d4825df]`",
|
|
528
|
+
"Does not retry the search with other filters or a project id to force lessons out of it, and does not record the old engine as an outage"
|
|
529
|
+
],
|
|
530
|
+
"why": "Profile B with an old engine: turn records crowd lessons out of an unfiltered match window, so a hit there is not evidence about lessons. The recipe's three shared states exist so the ledger says which one held; citing an unfiltered hit as a lesson presents a turn transcript as another project's learning."
|
|
427
531
|
}
|
|
428
532
|
]
|
|
429
533
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-pipeline-skill",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.96.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"
|
|
@@ -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); a third, `waves`, takes the whole board and closes it in waves that each finish in one pass. Retro insights can publish upstream as issues, opt-in and redacted.",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.96.0",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "ssheleg",
|
|
9
9
|
"url": "https://x.com/sshlg93"
|
|
@@ -179,6 +179,9 @@ before drawing, never invented by the drawing agent).
|
|
|
179
179
|
around the waves — survey, module dossiers, one design, parallel build, checks that
|
|
180
180
|
start as each node lands, release, post-release check, next pool — is
|
|
181
181
|
`references/iteration.md`. A finding joins the inventory; it is not fixed alone.
|
|
182
|
+
**Findings are triaged when found, and the iteration stops at its finish line** —
|
|
183
|
+
exit checks green on the artefact, two rounds with no new blocking finding, or the
|
|
184
|
+
budget spent (`references/finish-line.md`).
|
|
182
185
|
4. Do **not** advance until the stage **gate** passes (`references/stages.md`).
|
|
183
186
|
Honor the gate **type**: for `auto`, verify the gate's `check` yourself and
|
|
184
187
|
stop/return on fail; for `judgment`, record the named judge's ruling as
|
|
@@ -186,8 +189,8 @@ before drawing, never invented by the drawing agent).
|
|
|
186
189
|
**operator's explicit go or standing authorization naming this act**. Other gate
|
|
187
190
|
types and generic loop flags cannot supply that authority (`references/continuity.md`).
|
|
188
191
|
5. **The cross-cutting rules fire at any stage**, not only here — the Doc Loop, the
|
|
189
|
-
loop guard, the audit's exit, the frozen REQ list, the carry-over
|
|
190
|
-
what counts as evidence, and **every gate prints `holds: N`** — what this run left
|
|
192
|
+
loop guard, the audit's exit, the finish line, the frozen REQ list, the carry-over
|
|
193
|
+
ledger, and what counts as evidence, and **every gate prints `holds: N`** — what this run left
|
|
191
194
|
running, across all eight classes, enumerated by class and never by a single
|
|
192
195
|
tool — and stage 10 does not close while this run's residue is live and
|
|
193
196
|
unaccounted (`references/residue.md`). The rest are in
|
|
@@ -156,6 +156,25 @@
|
|
|
156
156
|
"description": "Rounds per artifact before the run stops reviewing and measures. Counted from the run ledger's `touch:` pass numbers, never from memory; a round that finds nothing ends the loop by definition and is not counted."
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
+
},
|
|
160
|
+
"finish": {
|
|
161
|
+
"type": "object",
|
|
162
|
+
"additionalProperties": false,
|
|
163
|
+
"description": "The iteration's budget at its finish line. Absent, the defaults hold: 3 rounds and 2 releases. A round is every exit check once plus one independent reading over the scope; only new blocking findings count. Reaching either budget before done — a blocker open, or the second clean round still owed — stops the iteration and hands it back; no round runs past it without the operator's go, given with the numbers. Doctrine: references/finish-line.md; verdict: scripts/waves.py finish.",
|
|
164
|
+
"properties": {
|
|
165
|
+
"maxRounds": {
|
|
166
|
+
"type": "integer",
|
|
167
|
+
"minimum": 2,
|
|
168
|
+
"default": 3,
|
|
169
|
+
"description": "Finish-line rounds per iteration, at least 2: done needs two consecutive rounds with no new blocking finding. Rotating the audit axis happens inside this budget and never adds a round."
|
|
170
|
+
},
|
|
171
|
+
"maxReleases": {
|
|
172
|
+
"type": "integer",
|
|
173
|
+
"minimum": 1,
|
|
174
|
+
"default": 2,
|
|
175
|
+
"description": "Releases per iteration, the first included; every corrective release after a post-release failure counts against it."
|
|
176
|
+
}
|
|
177
|
+
}
|
|
159
178
|
}
|
|
160
179
|
}
|
|
161
180
|
},
|
|
@@ -344,7 +344,8 @@ construction**:
|
|
|
344
344
|
findings, and findings caused by the previous pass's own fixes. When the second
|
|
345
345
|
overtakes the first, the axis is exhausted — **rotate it, don't push harder.** Both
|
|
346
346
|
counts go in the ledger; an audit that reports only "found N" cannot see its own
|
|
347
|
-
exhaustion.
|
|
347
|
+
exhaustion. **Rotation happens inside the round budget**: a new axis is the next round's
|
|
348
|
+
reading, never an extra round ([`finish-line.md`](finish-line.md) → *Rounds and the stop*).
|
|
348
349
|
|
|
349
350
|
### 3. What can't be fixed now becomes a ratchet, never a TODO
|
|
350
351
|
|
|
@@ -380,7 +381,9 @@ explaining why; a ratchet nobody prints is a TODO with a better name.
|
|
|
380
381
|
produce findings and fixes rather than a feature, and the exit criterion above is
|
|
381
382
|
the stage-10 gate.
|
|
382
383
|
- **Never as an eighth "look again" pass.** If the last two passes found mostly
|
|
383
|
-
self-inflicted findings, the answer is rule 2, not another pass
|
|
384
|
+
self-inflicted findings, the answer is rule 2, not another pass — and rule 2's rotation
|
|
385
|
+
spends the iteration's round budget rather than extending it
|
|
386
|
+
([`finish-line.md`](finish-line.md)).
|
|
384
387
|
|
|
385
388
|
Once both axes are exhausted, the next finding of a known class should be caught by
|
|
386
389
|
a script — and if it cannot be, **that is the finding: write the check.**
|