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.
Files changed (46) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/HOW-IT-WORKS.md +17 -13
  3. package/README.md +23 -17
  4. package/SKILL-CARD.md +2 -2
  5. package/cursor/rules/task-pipeline.mdc +33 -21
  6. package/evals/task-pipeline.evals.json +3 -3
  7. package/package.json +1 -1
  8. package/plugins/task-pipeline/.claude-plugin/plugin.json +2 -2
  9. package/plugins/task-pipeline/commands/task-pipeline.md +14 -219
  10. package/plugins/task-pipeline/skills/evidence-docs/SKILL.md +8 -1
  11. package/plugins/task-pipeline/skills/evidence-docs/references/GENERATED.md +1 -1
  12. package/plugins/task-pipeline/skills/evidence-docs/references/learned.md +24 -1
  13. package/plugins/task-pipeline/skills/evidence-docs/references/retrospective.md +21 -576
  14. package/plugins/task-pipeline/skills/project-audit/SKILL.md +8 -1
  15. package/plugins/task-pipeline/skills/task-pipeline/SKILL.md +162 -230
  16. package/plugins/task-pipeline/skills/task-pipeline/graph.schema.json +1 -1
  17. package/plugins/task-pipeline/skills/task-pipeline/pipeline.example.json +9 -9
  18. package/plugins/task-pipeline/skills/task-pipeline/pipeline.schema.json +10 -3
  19. package/plugins/task-pipeline/skills/task-pipeline/references/acceptance.md +2 -2
  20. package/plugins/task-pipeline/skills/task-pipeline/references/brainstorm.md +3 -3
  21. package/plugins/task-pipeline/skills/task-pipeline/references/browser.md +48 -11
  22. package/plugins/task-pipeline/skills/task-pipeline/references/build.md +7 -7
  23. package/plugins/task-pipeline/skills/task-pipeline/references/certification.md +49 -44
  24. package/plugins/task-pipeline/skills/task-pipeline/references/companion-skills.md +32 -24
  25. package/plugins/task-pipeline/skills/task-pipeline/references/doctrine-map.md +1 -1
  26. package/plugins/task-pipeline/skills/task-pipeline/references/finish-line.md +13 -8
  27. package/plugins/task-pipeline/skills/task-pipeline/references/iteration.md +20 -23
  28. package/plugins/task-pipeline/skills/task-pipeline/references/knowledge-sources.md +5 -3
  29. package/plugins/task-pipeline/skills/task-pipeline/references/learned.md +24 -1
  30. package/plugins/task-pipeline/skills/task-pipeline/references/loop-guard.md +17 -7
  31. package/plugins/task-pipeline/skills/task-pipeline/references/retrospective.md +47 -1
  32. package/plugins/task-pipeline/skills/task-pipeline/references/spec.md +5 -4
  33. package/plugins/task-pipeline/skills/task-pipeline/references/stage-00-intake.md +39 -25
  34. package/plugins/task-pipeline/skills/task-pipeline/references/stage-05-dev.md +3 -3
  35. package/plugins/task-pipeline/skills/task-pipeline/references/stage-08-post-deploy.md +3 -2
  36. package/plugins/task-pipeline/skills/task-pipeline/references/stages.md +34 -22
  37. package/plugins/task-pipeline/skills/task-pipeline/references/tdd.md +3 -2
  38. package/plugins/task-pipeline/skills/task-pipeline/references/waves.md +6 -4
  39. package/plugins/task-pipeline/skills/task-pipeline/scripts/graph.py +77 -21
  40. package/plugins/task-pipeline/skills/task-pipeline/scripts/retro_budget.py +305 -0
  41. package/plugins/task-pipeline/skills/task-pipeline/scripts/short_path.py +70 -0
  42. package/plugins/task-pipeline/skills/task-pipeline/scripts/waves.py +22 -10
  43. package/plugins/task-pipeline/skills/task-pipeline/templates/browser-claims.json +5 -5
  44. package/plugins/task-pipeline/skills/task-pipeline/templates/canvas.md +1 -1
  45. package/plugins/task-pipeline/skills/task-pipeline/templates/retro-archive.md +9 -0
  46. 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 two
40
- rounds found no new blocking item, none is open, --checks is all green, and the rounds
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 = rounds >= 2 and new[-1] == 0 and new[-2] == 0 and not open_b
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 "the second clean round still owed")
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
- for flag, v, least in (("--max-rounds", a.max_rounds, 2), ("--max-releases", a.max_releases, 1),
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 two clean rounds, so a budget of one can never be met"
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, help="run.finish.maxRounds (default 3, at least 2)")
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. chrome-devtools take_screenshot"
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. chrome-devtools take_screenshot"
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. chrome-devtools take_screenshot"
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. chrome-devtools take_screenshot"
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. chrome-devtools take_screenshot"
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 two, R3 three, one of them recorded adversarial.
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 · retired 0 |
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