task-pipeline-skill 1.96.0 → 1.98.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 +90 -0
- package/HOW-IT-WORKS.md +17 -13
- package/README.md +23 -17
- package/SKILL-CARD.md +2 -2
- package/cursor/rules/task-pipeline.mdc +33 -21
- package/evals/task-pipeline.evals.json +3 -3
- package/package.json +1 -1
- package/plugins/task-pipeline/.claude-plugin/plugin.json +2 -2
- package/plugins/task-pipeline/commands/task-pipeline.md +14 -219
- package/plugins/task-pipeline/skills/evidence-docs/SKILL.md +8 -1
- package/plugins/task-pipeline/skills/evidence-docs/references/GENERATED.md +1 -1
- package/plugins/task-pipeline/skills/evidence-docs/references/learned.md +24 -1
- package/plugins/task-pipeline/skills/evidence-docs/references/retrospective.md +21 -576
- package/plugins/task-pipeline/skills/project-audit/SKILL.md +8 -1
- package/plugins/task-pipeline/skills/task-pipeline/SKILL.md +162 -230
- package/plugins/task-pipeline/skills/task-pipeline/graph.schema.json +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/pipeline.example.json +9 -9
- package/plugins/task-pipeline/skills/task-pipeline/pipeline.schema.json +10 -3
- package/plugins/task-pipeline/skills/task-pipeline/references/acceptance.md +2 -2
- package/plugins/task-pipeline/skills/task-pipeline/references/brainstorm.md +3 -3
- package/plugins/task-pipeline/skills/task-pipeline/references/browser.md +48 -11
- package/plugins/task-pipeline/skills/task-pipeline/references/build.md +7 -7
- package/plugins/task-pipeline/skills/task-pipeline/references/certification.md +49 -44
- package/plugins/task-pipeline/skills/task-pipeline/references/companion-skills.md +32 -24
- package/plugins/task-pipeline/skills/task-pipeline/references/doctrine-map.md +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/finish-line.md +13 -8
- package/plugins/task-pipeline/skills/task-pipeline/references/iteration.md +20 -23
- package/plugins/task-pipeline/skills/task-pipeline/references/knowledge-sources.md +5 -3
- package/plugins/task-pipeline/skills/task-pipeline/references/learned.md +24 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/loop-guard.md +17 -7
- package/plugins/task-pipeline/skills/task-pipeline/references/retrospective.md +47 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/spec.md +5 -4
- package/plugins/task-pipeline/skills/task-pipeline/references/stage-00-intake.md +39 -25
- package/plugins/task-pipeline/skills/task-pipeline/references/stage-05-dev.md +3 -3
- package/plugins/task-pipeline/skills/task-pipeline/references/stage-08-post-deploy.md +3 -2
- package/plugins/task-pipeline/skills/task-pipeline/references/stages.md +34 -22
- package/plugins/task-pipeline/skills/task-pipeline/references/tdd.md +3 -2
- package/plugins/task-pipeline/skills/task-pipeline/references/waves.md +6 -4
- package/plugins/task-pipeline/skills/task-pipeline/scripts/graph.py +77 -21
- package/plugins/task-pipeline/skills/task-pipeline/scripts/retro_budget.py +305 -0
- package/plugins/task-pipeline/skills/task-pipeline/scripts/short_path.py +70 -0
- package/plugins/task-pipeline/skills/task-pipeline/scripts/waves.py +22 -10
- package/plugins/task-pipeline/skills/task-pipeline/templates/browser-claims.json +5 -5
- package/plugins/task-pipeline/skills/task-pipeline/templates/canvas.md +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/templates/retro-archive.md +9 -0
- package/plugins/task-pipeline/skills/task-pipeline/templates/retro.md +10 -1
|
@@ -32,12 +32,14 @@ reviewed; a table and a re-run can. This script is that re-run:
|
|
|
32
32
|
(a plan that spans several repositories' graphs).
|
|
33
33
|
waves.py finish --inventory FILE --max-rounds N --max-releases M (--rounds K | --ledger RUN.md)
|
|
34
34
|
--checks K/M --artefact NAME@SHA [--releases R] [--operator-go]
|
|
35
|
+
[--clean-rounds 1|2]
|
|
35
36
|
the finish-line verdict (`references/finish-line.md`) from the triaged items, and the
|
|
36
37
|
finish block printed. Only `triage: blocking` items count, less those closed as
|
|
37
38
|
duplicate, stale or not-reproduced; deferred and noise never change the verdict. The
|
|
38
39
|
round count is explicit: --rounds, or the ledger's `round: <N> — <reading> — new
|
|
39
|
-
blocking <k>` lines, whose counts must match the inventory. Exit 0 done (the last
|
|
40
|
-
rounds found no new blocking item
|
|
40
|
+
blocking <k>` lines, whose counts must match the inventory. Exit 0 done (the last
|
|
41
|
+
--clean-rounds rounds found no new blocking item: one by default, two when the
|
|
42
|
+
iteration carries an R3 or flagship node; none is open, --checks is all green, and the rounds
|
|
41
43
|
are within --max-rounds or --operator-go is passed); 5 the budget reached before done
|
|
42
44
|
(rounds at --max-rounds, releases at --max-releases with a blocker open, or done past
|
|
43
45
|
the round budget without --operator-go); 6 diverging (the blocking count did not fall
|
|
@@ -598,7 +600,7 @@ def ledger_rounds(path: pathlib.Path) -> dict[int, int]:
|
|
|
598
600
|
|
|
599
601
|
def finish_verdict(items: list[dict], rounds: int, max_rounds: int, max_releases: int,
|
|
600
602
|
releases: int, checks: tuple[int, int] | None,
|
|
601
|
-
operator_go: bool = False) -> tuple[int, str, dict]:
|
|
603
|
+
operator_go: bool = False, clean_rounds: int = 1) -> tuple[int, str, dict]:
|
|
602
604
|
"""(exit code, word, facts) — the finish line computed from the triaged items alone.
|
|
603
605
|
Deferred and noise items are counted for the block and never read for the verdict."""
|
|
604
606
|
triaged = [x for x in items if "triage" in x]
|
|
@@ -612,7 +614,9 @@ def finish_verdict(items: list[dict], rounds: int, max_rounds: int, max_releases
|
|
|
612
614
|
open_b = [x for x in blocking if _needs_node(x)]
|
|
613
615
|
facts = {"rounds": rounds, "new": new, "open": open_b, "fixed": len(blocking) - len(open_b),
|
|
614
616
|
"self_inflicted": selfinf}
|
|
615
|
-
clean
|
|
617
|
+
# One clean round ends an iteration; two only where it carries an R3 or flagship node
|
|
618
|
+
# (`--clean-rounds 2`). Two for everything was the loop the 2026-10-20 cost audit measured.
|
|
619
|
+
clean = rounds >= clean_rounds and all(n == 0 for n in new[-clean_rounds:]) and not open_b
|
|
616
620
|
green = checks is not None and checks[0] == checks[1]
|
|
617
621
|
if clean and green:
|
|
618
622
|
if rounds <= max_rounds:
|
|
@@ -625,7 +629,7 @@ def finish_verdict(items: list[dict], rounds: int, max_rounds: int, max_releases
|
|
|
625
629
|
if rounds >= max_rounds:
|
|
626
630
|
owed = (f"{len(open_b)} blocker(s) open" if open_b else
|
|
627
631
|
"exit checks not supplied" if checks is None else
|
|
628
|
-
"exit checks not all green" if not green else "
|
|
632
|
+
"exit checks not all green" if not green else "a clean round still owed")
|
|
629
633
|
return 5, "budget", dict(facts, why=f"rounds {rounds}/{max_rounds} reached before done — {owed}")
|
|
630
634
|
if open_b and releases >= max_releases:
|
|
631
635
|
return 5, "budget", dict(facts, why=f"releases {releases}/{max_releases} reached before "
|
|
@@ -640,7 +644,8 @@ def finish_verdict(items: list[dict], rounds: int, max_rounds: int, max_releases
|
|
|
640
644
|
why = (f"{len(open_b)} blocker(s) open" if open_b else
|
|
641
645
|
"exit checks not supplied — pass --checks K/M read on the artefact" if checks is None else
|
|
642
646
|
"exit checks not all green" if not green else
|
|
643
|
-
"a second round with no new blocking finding is owed"
|
|
647
|
+
("a second round with no new blocking finding is owed" if clean_rounds > 1 else
|
|
648
|
+
"a round with no new blocking finding is owed"))
|
|
644
649
|
return 3, "continue", dict(facts, why=why)
|
|
645
650
|
|
|
646
651
|
|
|
@@ -649,11 +654,14 @@ def cmd_finish(a) -> int:
|
|
|
649
654
|
_, items = load_inventory(pathlib.Path(a.inventory))
|
|
650
655
|
except Fail as exc:
|
|
651
656
|
raise Fail(1, exc.reasons) # a malformed inventory is malformed here, never usage
|
|
652
|
-
|
|
657
|
+
if a.clean_rounds not in (1, 2):
|
|
658
|
+
raise Fail(1, [f"--clean-rounds {a.clean_rounds} is not 1 or 2"])
|
|
659
|
+
for flag, v, least in (("--max-rounds", a.max_rounds, a.clean_rounds),
|
|
660
|
+
("--max-releases", a.max_releases, 1),
|
|
653
661
|
("--releases", a.releases, 0), ("--rounds", a.rounds, 0)):
|
|
654
662
|
if v is not None and v < least:
|
|
655
663
|
raise Fail(1, [f"{flag} {v} is below {least}" + (
|
|
656
|
-
" — done needs
|
|
664
|
+
f" — done needs {a.clean_rounds} clean rounds, so a smaller budget can never be met"
|
|
657
665
|
if flag == "--max-rounds" else "")])
|
|
658
666
|
# THE ROUND COUNT IS EXPLICIT. A clean round leaves no item, so a count derived from the
|
|
659
667
|
# items can never see the two clean rounds `done` needs: the verdict would stay
|
|
@@ -684,7 +692,7 @@ def cmd_finish(a) -> int:
|
|
|
684
692
|
if a.artefact and not ARTEFACT.match(a.artefact):
|
|
685
693
|
raise Fail(1, [f"--artefact {a.artefact!r} is not NAME@SHA (a commit of 7-40 hex digits)"])
|
|
686
694
|
code, word, f = finish_verdict(items, rounds, a.max_rounds, a.max_releases, a.releases, checks,
|
|
687
|
-
a.operator_go)
|
|
695
|
+
a.operator_go, a.clean_rounds)
|
|
688
696
|
deferred = sorted((x for x in items if x.get("triage") == "deferred"), key=_priority)
|
|
689
697
|
noise = [x for x in items if x.get("triage") == "noise"]
|
|
690
698
|
risks = [x for x in deferred if x.get("blast") == 3 or x.get("rung") in ("I4", "I5")]
|
|
@@ -738,7 +746,11 @@ def main(argv: list[str]) -> int:
|
|
|
738
746
|
help="ignore plan nodes this graph does not hold")
|
|
739
747
|
f = sub.add_parser("finish", help="the finish-line verdict from the triaged items, and the block")
|
|
740
748
|
f.add_argument("--inventory", required=True)
|
|
741
|
-
f.add_argument("--max-rounds", type=int, required=True,
|
|
749
|
+
f.add_argument("--max-rounds", type=int, required=True,
|
|
750
|
+
help="run.finish.maxRounds (default 3, at least --clean-rounds)")
|
|
751
|
+
f.add_argument("--clean-rounds", type=int, default=1,
|
|
752
|
+
help="run.finish.cleanRounds: clean rounds that end the iteration, 1 by "
|
|
753
|
+
"default, 2 when it carries an R3 or flagship node")
|
|
742
754
|
f.add_argument("--max-releases", type=int, required=True, help="run.finish.maxReleases (default 2)")
|
|
743
755
|
f.add_argument("--releases", type=int, default=0, help="releases this iteration made so far")
|
|
744
756
|
f.add_argument("--rounds", type=int, help="rounds run, clean ones included")
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"route": "/",
|
|
76
76
|
"motion": "reduced",
|
|
77
77
|
"captured_at": "2026-10-07T00:00:00Z",
|
|
78
|
-
"source": "the capability that took it, e.g.
|
|
78
|
+
"source": "the capability and engine that took it, e.g. playwright-cli screenshot (webkit)"
|
|
79
79
|
},
|
|
80
80
|
"figma_frame": "https://www.figma.com/design/<fileKey>/<file>?node-id=<frame>",
|
|
81
81
|
"baseline": null,
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"route": "/",
|
|
121
121
|
"motion": "reduced",
|
|
122
122
|
"captured_at": "2026-10-07T00:00:00Z",
|
|
123
|
-
"source": "the capability that took it, e.g.
|
|
123
|
+
"source": "the capability and engine that took it, e.g. playwright-cli screenshot (webkit)"
|
|
124
124
|
},
|
|
125
125
|
"figma_frame": "https://www.figma.com/design/<fileKey>/<file>?node-id=<frame>",
|
|
126
126
|
"baseline": null,
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
"route": "/",
|
|
166
166
|
"motion": "reduced",
|
|
167
167
|
"captured_at": "2026-10-07T00:00:00Z",
|
|
168
|
-
"source": "the capability that took it, e.g.
|
|
168
|
+
"source": "the capability and engine that took it, e.g. playwright-cli screenshot (webkit)"
|
|
169
169
|
},
|
|
170
170
|
"figma_frame": "https://www.figma.com/design/<fileKey>/<file>?node-id=<frame>",
|
|
171
171
|
"baseline": null,
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
"route": "/",
|
|
211
211
|
"motion": "reduced",
|
|
212
212
|
"captured_at": "2026-10-07T00:00:00Z",
|
|
213
|
-
"source": "the capability that took it, e.g.
|
|
213
|
+
"source": "the capability and engine that took it, e.g. playwright-cli screenshot (webkit)"
|
|
214
214
|
},
|
|
215
215
|
"figma_frame": null,
|
|
216
216
|
"baseline": null,
|
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
"route": "/",
|
|
252
252
|
"motion": "reduced",
|
|
253
253
|
"captured_at": "2026-10-07T00:00:00Z",
|
|
254
|
-
"source": "the capability that took it, e.g.
|
|
254
|
+
"source": "the capability and engine that took it, e.g. playwright-cli screenshot (webkit)"
|
|
255
255
|
},
|
|
256
256
|
"figma_frame": null,
|
|
257
257
|
"baseline": null,
|
|
@@ -79,7 +79,7 @@ attached.
|
|
|
79
79
|
Cut per `references/waves.md` → *Cutting the waves*: a wave holds only items closable in one
|
|
80
80
|
pass; wave 1 is the walking skeleton; lanes run in parallel inside a wave and respect file
|
|
81
81
|
ownership. Each node carries its own risk class (`references/iteration.md` → *Verification
|
|
82
|
-
starts when a node lands*): R1 one passing round, R2
|
|
82
|
+
starts when a node lands*): R1 one passing round on one tier, R2 one, R3 two, one of them recorded adversarial.
|
|
83
83
|
|
|
84
84
|
**Counts:** <from `waves.py count` and the graph: nodes by risk, by wave, out of waves>.
|
|
85
85
|
|
|
@@ -25,6 +25,8 @@ resolve — the documentation gate checks it.
|
|
|
25
25
|
one, and it produces no fix)
|
|
26
26
|
- **Fix:** grade 1 (mechanical) — … / grade 2 → `R-00N` / grade 3, expires in 2 runs
|
|
27
27
|
- **The check:** … (what catches this the first time, from now on)
|
|
28
|
+
- **Class:** one of the classes in `references/retrospective.md` · **Cost:** rounds,
|
|
29
|
+
burned tags, time
|
|
28
30
|
- **Commit:** `<sha>` — the change that fixed it (a real short SHA, and it must
|
|
29
31
|
resolve: the gate runs `git rev-parse --verify` over every one)
|
|
30
32
|
- **Upstream?** … (a lesson true in any repository belongs in the skill's own
|
|
@@ -40,6 +42,13 @@ history attached**, which is what this section is for.
|
|
|
40
42
|
|---|---|---|---|---|
|
|
41
43
|
| <YYYY-MM-DD> | R-000 | … | became a check (`npm run lint:paths`) | `<sha>` |
|
|
42
44
|
|
|
45
|
+
## Moved by `retro_budget.py migrate`
|
|
46
|
+
|
|
47
|
+
Narrative the floor's byte budget moved out of `retro.md`: each block is the original
|
|
48
|
+
text, verbatim, under a `<!-- sha256:… -->` line that hashes it, and the floor keeps a
|
|
49
|
+
pointer (`retro/YYYY-QN.md sha256:<prefix>`). `retro_budget.py verify` proves every
|
|
50
|
+
pointer. Blocks are appended at the end of this file.
|
|
51
|
+
|
|
43
52
|
## Run stamps
|
|
44
53
|
|
|
45
54
|
Rotated out of `retro.md` when its live table passes ten. Append-only, whole rows —
|
|
@@ -13,6 +13,11 @@ not negotiable. The **Recent log** and the **archive**
|
|
|
13
13
|
read end to end: nothing caps the log's length, and an uncapped section read in
|
|
14
14
|
full is the volume that stops the ten binding rows being read.
|
|
15
15
|
|
|
16
|
+
**The floor has a byte budget:** standing instructions plus run stamps under 12 000
|
|
17
|
+
bytes, a standing row at most 600 characters (the story goes to the archive), a stamp
|
|
18
|
+
one line of at most 300. `scripts/retro_budget.py check` measures it at stage 0 and at
|
|
19
|
+
gate 10; `migrate --write` moves narrative to the archive verbatim, hash-proved.
|
|
20
|
+
|
|
16
21
|
## Standing instructions (max 10 — in force right now)
|
|
17
22
|
|
|
18
23
|
Every row is a rule an agent must follow, that **no check can decide**. A rule a
|
|
@@ -49,6 +54,10 @@ stays short precisely so that reading it in full at stage 0 stays cheap.
|
|
|
49
54
|
- **Fix:** grade 1 (mechanical) — … / grade 2 → `R-00N` / grade 3, expires in
|
|
50
55
|
2 runs
|
|
51
56
|
- **The check:** … (what catches this the first time, from now on)
|
|
57
|
+
- **Class:** `exit-code` | `unwatched-check` | `release` | `collision` |
|
|
58
|
+
`destructive-undo` | `claim-by-say-so` | `stale-doc` | `wrong-copy` | `sibling` |
|
|
59
|
+
`truncated-output` | `shell-instrument` | `handback` | `loop` (recurrence is counted
|
|
60
|
+
by class) · **Cost:** … (rounds, burned tags, time)
|
|
52
61
|
- **Lesson:** `<class-key>` — <the class, one sentence> · **When:** <trigger> · **Nouns:** <a>, <b>, <c>
|
|
53
62
|
(optional, and on ONE line: write it only when the class would be true in another
|
|
54
63
|
project — it makes the entry a lesson for `scripts/lessons.py add`, lesson/1. Without
|
|
@@ -74,7 +83,7 @@ this section is read in full at stage 0.
|
|
|
74
83
|
| Date | Topic | Commit | Verdict | Retro |
|
|
75
84
|
|---|---|---|---|---|
|
|
76
85
|
| 2026-05-04 | `<topic>` | `<sha>` | 14/14 verified | 1 entry · 7 standing (was 9) · retired 3 · added 1 |
|
|
77
|
-
| 2026-05-02 | `<topic>` | `<sha>` | 9/9 verified | no divergence · 9 standing ·
|
|
86
|
+
| 2026-05-02 | `<topic>` | `<sha>` | 9/9 verified | no divergence · 9 standing · prune: 0 retired |
|
|
78
87
|
|
|
79
88
|
The `Commit` column is what turns a stamp from a date into a navigable point in
|
|
80
89
|
history: `git show <sha>` is the run, and `git log <sha>..HEAD` is everything that
|