task-pipeline-skill 1.93.3 → 1.94.1

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 (49) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/README.md +42 -9
  3. package/SKILL-CARD.md +1 -1
  4. package/bin/task-pipeline.js +64 -33
  5. package/cursor/rules/task-pipeline.mdc +10 -5
  6. package/evals/RESULTS.md +1 -1
  7. package/evals/task-pipeline.evals.json +16 -0
  8. package/package.json +3 -3
  9. package/plugins/task-pipeline/.claude-plugin/plugin.json +1 -1
  10. package/plugins/task-pipeline/agents/verifier.md +4 -1
  11. package/plugins/task-pipeline/commands/task-pipeline.md +22 -8
  12. package/plugins/task-pipeline/hooks/memory-nudge.sh +45 -3
  13. package/plugins/task-pipeline/skills/evidence-docs/references/gates.md +6 -3
  14. package/plugins/task-pipeline/skills/evidence-docs/references/hooks.md +2 -1
  15. package/plugins/task-pipeline/skills/evidence-docs/references/retrospective.md +26 -1
  16. package/plugins/task-pipeline/skills/task-pipeline/SKILL.md +13 -11
  17. package/plugins/task-pipeline/skills/task-pipeline/graph.schema.json +89 -4
  18. package/plugins/task-pipeline/skills/task-pipeline/pipeline.schema.json +7 -0
  19. package/plugins/task-pipeline/skills/task-pipeline/references/backlog.md +25 -0
  20. package/plugins/task-pipeline/skills/task-pipeline/references/certification.md +45 -1
  21. package/plugins/task-pipeline/skills/task-pipeline/references/continuity.md +94 -25
  22. package/plugins/task-pipeline/skills/task-pipeline/references/deploy-targets.md +30 -4
  23. package/plugins/task-pipeline/skills/task-pipeline/references/doctrine-map.md +1 -0
  24. package/plugins/task-pipeline/skills/task-pipeline/references/gates.md +6 -3
  25. package/plugins/task-pipeline/skills/task-pipeline/references/grill.md +1 -1
  26. package/plugins/task-pipeline/skills/task-pipeline/references/hooks.md +2 -1
  27. package/plugins/task-pipeline/skills/task-pipeline/references/iteration.md +387 -0
  28. package/plugins/task-pipeline/skills/task-pipeline/references/knowledge-sources.md +1 -0
  29. package/plugins/task-pipeline/skills/task-pipeline/references/loop-guard.md +4 -1
  30. package/plugins/task-pipeline/skills/task-pipeline/references/portability.md +1 -0
  31. package/plugins/task-pipeline/skills/task-pipeline/references/retrospective.md +26 -1
  32. package/plugins/task-pipeline/skills/task-pipeline/references/stages.md +35 -11
  33. package/plugins/task-pipeline/skills/task-pipeline/references/waves.md +76 -36
  34. package/plugins/task-pipeline/skills/task-pipeline/references/work-graph.md +54 -5
  35. package/plugins/task-pipeline/skills/task-pipeline/scripts/backlog_sources.py +54 -13
  36. package/plugins/task-pipeline/skills/task-pipeline/scripts/context_packets.py +3 -3
  37. package/plugins/task-pipeline/skills/task-pipeline/scripts/graph.py +205 -22
  38. package/plugins/task-pipeline/skills/task-pipeline/scripts/lessons.py +1137 -0
  39. package/plugins/task-pipeline/skills/task-pipeline/scripts/stage_checkpoint.py +108 -11
  40. package/plugins/task-pipeline/skills/task-pipeline/scripts/waves.py +546 -0
  41. package/plugins/task-pipeline/skills/task-pipeline/templates/README.md +6 -0
  42. package/plugins/task-pipeline/skills/task-pipeline/templates/backlog-sources.schema.json +96 -0
  43. package/plugins/task-pipeline/skills/task-pipeline/templates/canvas.md +97 -0
  44. package/plugins/task-pipeline/skills/task-pipeline/templates/dossier.md +82 -0
  45. package/plugins/task-pipeline/skills/task-pipeline/templates/inventory.schema.json +94 -0
  46. package/plugins/task-pipeline/skills/task-pipeline/templates/lesson.schema.json +221 -0
  47. package/plugins/task-pipeline/skills/task-pipeline/templates/questions.md +45 -0
  48. package/plugins/task-pipeline/skills/task-pipeline/templates/retro.md +4 -0
  49. package/plugins/task-pipeline/skills/task-pipeline/templates/run.md +2 -2
@@ -50,12 +50,14 @@ From [`../pipeline.schema.json`](https://github.com/ssheleg/task-pipeline/blob/m
50
50
  |---|---|---|
51
51
  | `auto` | the orchestrator verifies the `check` itself, pass/fail, and stops on fail | advancing on an unverified check |
52
52
  | `judgment` | somebody **rules** on it, because no complete deterministic check exists; the gate names its `judge` | recording the ruling in the slot reserved for what a machine established |
53
- | `manual` | wait for the operator's **explicit** go | treating an auto verification as the approval |
53
+ | `manual` | the operator's **explicit** go, or specific standing authorization naming the act | treating an auto verification as the approval |
54
54
 
55
55
  **An auto gate never substitutes for a required manual approval.** A green table is
56
56
  not the operator confirming it is what they asked for, and no amount of checking
57
57
  makes it one. Which stages are manual is the **operator's** decision, recorded in
58
58
  their `pipeline.json`; the framework fixes no stage count and no gate assignment.
59
+ A standing authorization is cited with its source and scope, not inferred from a generic
60
+ loop flag ([`continuity.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/continuity.md) → *The limit, before the capability*).
59
61
 
60
62
  **That sentence is about the pipeline's SHAPE, and nothing else.** It says a project
61
63
  chooses how many stages it runs and which of them wait for a person. It does not say the
@@ -495,8 +497,9 @@ run:
495
497
  what it does not cover, stated, beats two measurements and no decision.
496
498
 
497
499
  **This never licenses skipping a gate.** The gates are the floor, and the floor is
498
- not proportionate to anything — a `manual` gate waits, `unknown` fails stage 10, and
499
- a green nobody watched fail is not evidence at any blast radius. What is
500
+ not proportionate to anything — a `manual` gate waits without an explicit go or a
501
+ specific standing authorization naming its act, cited with source and scope;
502
+ `unknown` fails stage 10, and a green nobody watched fail is not evidence at any blast radius. What is
500
503
  proportionate is the work *above* the floor: how many axes, how many passes, how
501
504
  much of the corpus. Cutting the floor to go faster is not speed; it is the failure
502
505
  this whole file exists to prevent, arriving on schedule.
@@ -219,7 +219,8 @@ Since v1.50.0 the plugin carries hooks of its own. `hooks/release-gate.sh`
219
219
  tag push, `gh release create`, `npm publish` — while the run says the suite has not
220
220
  passed. `hooks/gate-observer.sh` (`PostToolUse` and `PostToolUseFailure`) records
221
221
  what the declared gate command actually did. `hooks/memory-nudge.sh` (`PostToolUse` on `Bash|Edit|Write|MultiEdit`) reminds,
222
- once per returned gate, that no workflow-memory line follows it
222
+ once per returned gate, that no workflow-memory line follows it — unless the run recorded
223
+ memory unavailable with `--for-run`
223
224
  ([`continuity.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/continuity.md), Part 3).
224
225
 
225
226
  **Two lessons from v1.50.0, both shipped as defects and both fixed in v1.51.0.**
@@ -44,6 +44,7 @@ file exists to stop.
44
44
  - A fix to one call site closes a call site, not a class
45
45
  - The loop closes at stage 0
46
46
  - Where a lesson goes when it is not about this project
47
+ - Recording to a shared lesson store, where the host has one
47
48
  - Publishing the insight — the skill learns from every project that runs it
48
49
  - The improvement iteration — what happens to a published insight next
49
50
  - What may leave the project — the redaction list
@@ -416,6 +417,28 @@ file that accumulates universal rules is a fork of the skill that nobody named.
416
417
  trigger and no authorization** — an instruction on rung 1 that everybody read as done.
417
418
  The rest of this section is the mechanism.
418
419
 
420
+ ## Recording to a shared lesson store, where the host has one
421
+
422
+ **One job: let the next agent on another project read this lesson at its stage 0.** An
423
+ entry whose class would be true in another project carries a `**Lesson:**` line — the class
424
+ key, the class in one sentence, when it applies, 3–8 search nouns. That line makes it a
425
+ *lesson* ([`lesson.schema.json`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/templates/lesson.schema.json), lesson/1): with task-pipeline installed, its `scripts/lessons.py add` puts it in the
426
+ local index every install has, and where the session lists a shared lesson store (an
427
+ Observatory) it is also recorded there, as a proposal. An entry without the line stays
428
+ private to this project.
429
+
430
+ **A lesson is not a publication, but the redaction list below binds it too — with one
431
+ relaxation.** On this machine or estate (the local index, a shared store), rule 2 is relaxed
432
+ for exactly two fields: the evidence commit and the pseudonymous project key (`p-` and 12 hex
433
+ of the root commit, the same key in the store, never the repository's name or a
434
+ `project:<slug>` id). A private repository's SHA names nothing; a public repository's SHA is
435
+ searchable, so it stays in its field and never in text. Rules 1, 3, 4 and 5 still bind every
436
+ text field: no path at all, no code or value, no person's, company's, customer's or
437
+ product's name, a title that states the class. `retro.publish` strips the whole origin.
438
+ The read side is source 7b of [`knowledge-sources.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/knowledge-sources.md). Where no store exists the run
439
+ says `shared lessons: absent` rather than nothing.
440
+ Mechanics and the read and write recipes: [`iteration.md`](https://github.com/ssheleg/task-pipeline/blob/main/plugins/task-pipeline/skills/task-pipeline/references/iteration.md) → *The learning loop*.
441
+
419
442
  ## Publishing the insight — the skill learns from every project that runs it
420
443
 
421
444
  **One job: stop a lesson dying in the repository that learned it.**
@@ -507,7 +530,9 @@ is what you want them to do.
507
530
  ## What may leave the project — the redaction list
508
531
 
509
532
  An issue is a **public artefact in someone else's repository**. What travels is the
510
- *class*; what stays is everything that identifies where it happened.
533
+ *class*; what stays is everything that identifies where it happened. This list binds
534
+ whatever leaves the estate, and every lesson too; *Recording to a shared lesson store*
535
+ above names the one relaxation a lesson kept on this machine or estate gets.
511
536
 
512
537
  | Goes | Stays |
513
538
  |---|---|
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: task-pipeline
3
- description: "Use when work changes the repository — feature, fix, bug, refactor, migration, integration, rewrite, adoption or hardening; фича, фикс, баг, рефактор, миграция, интеграция, доработать, починить, внедрить, перевести — or 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. Runs a substantial task through gated stages: intake grill, docs, brainstorm, spec, plan, build, tests, deploy, post-deploy, docs/wiki, acceptance. 'waves' / 'волнами' closes the whole board; 'checkup' / 'чекап' lists unconfirmed releases; 'setup' audits docs. Not for: answering a question, explaining code, a typo, a one-line edit, a mechanical rename, reconnaissance that lands nothing — say 'no pipeline' / 'без пайплайна' to opt out."
3
+ description: "Use when work changes the repository — feature, fix, bug, refactor, migration, integration, rewrite, adoption or hardening; фича, фикс, баг, рефактор, миграция, интеграция, доработать, починить, внедрить, перевести — or 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. Runs a substantial task through gated stages: intake grill, docs, brainstorm, spec, plan, build, tests, deploy, post-deploy, docs/wiki, acceptance. 'waves'/'волнами'/'release iteration'/'итерация релиза' closes the board; 'checkup'/'чекап' lists unconfirmed releases; 'setup' audits docs. Not for: answering a question, explaining, a typo, a one-line edit, a rename, recon that lands nothing — say 'no pipeline'/'без пайплайна' to opt out."
4
4
  license: MIT
5
5
  compatibility: "Doctrine runs on any agent. The bundled scripts need python3; the run needs git. Missing either degrades, never blocks — the graph verbs and seeded gates go unused, and the run says so."
6
6
  ---
@@ -46,7 +46,7 @@ kernel field. Each gate has a **type**: `auto` (the
46
46
  orchestrator verifies the `check` itself, pass/fail), `judgment` (no complete
47
47
  deterministic check exists — a named judge rules, and the ruling is recorded as
48
48
  judgement, never as a measurement; `references/gates.md` → *The judgment gate*)
49
- or `manual` (wait for an explicit operator go); which stages are manual is the
49
+ or `manual` (an explicit go, including specific standing authorization); which stages are manual is the
50
50
  operator's call. In the
51
51
  example's `skills[]`, `task-pipeline:<name>` denotes this skill's own built-in
52
52
  doctrine (`references/<name>.md`) and `host:<name>` denotes the host project's own
@@ -122,11 +122,10 @@ Stage 0 reads those standing instructions in full, which is why the prune is a g
122
122
  criterion: a rule nobody reads to the end is worse than no rule, because everyone
123
123
  believes it is covered.
124
124
 
125
- Three things the grill does beyond clarifying the request, each in full in
126
- [`references/grill.md`](references/grill.md):
127
- - **Domain awareness** — it reads the project's `CONTEXT.md` / `docs/adr/` and holds the operator to them, writing resolved terms back as they land.
128
- - **The autonomy sweep** — it pre-resolves what would otherwise stop stages 1→10 mid-flight. Autonomy is bought here or not at all; an unasked question is a scheduled interruption.
129
- - **The design destination** with Figma on — *which* file per surface, in which team, decided at stage 0. Left to drawing time it is answered by whoever holds the brush, and the answer is usually *create a new file*.
125
+ **At stage 0, read [grill.md](references/grill.md)** for domain awareness
126
+ (`CONTEXT.md` / ADRs; write resolved terms back), the autonomy sweep (pre-resolve
127
+ stages 1→10), and the Figma design destination (file and team per surface, settled
128
+ before drawing, never invented by the drawing agent).
130
129
 
131
130
  ## How to run
132
131
 
@@ -176,13 +175,16 @@ Three things the grill does beyond clarifying the request, each in full in
176
175
  build order, one brick at a time — stages 0–2 run once, and the module map's
177
176
  status column is the resume point (`references/stages.md` → *The program loop*).
178
177
  **The whole board?** `/task-pipeline waves` (`references/waves.md`): stages
179
- 0–4 once, 5–8 per wave, 9–10 to close.
178
+ 0–4 once, 5–8 per wave, 9–10 to close. **The project moves in releases**: the loop
179
+ around the waves — survey, module dossiers, one design, parallel build, checks that
180
+ start as each node lands, release, post-release check, next pool — is
181
+ `references/iteration.md`. A finding joins the inventory; it is not fixed alone.
180
182
  4. Do **not** advance until the stage **gate** passes (`references/stages.md`).
181
183
  Honor the gate **type**: for `auto`, verify the gate's `check` yourself and
182
184
  stop/return on fail; for `judgment`, record the named judge's ruling as
183
- judgement, never as a measurement; for `manual`, present the result and
184
- **wait for the operator's explicit "continue"/go** — neither of the other
185
- two ever substitutes for a required manual approval.
185
+ judgement, never as a measurement; for `manual`, present the result and cite the
186
+ **operator's explicit go or standing authorization naming this act**. Other gate
187
+ types and generic loop flags cannot supply that authority (`references/continuity.md`).
186
188
  5. **The cross-cutting rules fire at any stage**, not only here — the Doc Loop, the
187
189
  loop guard, the audit's exit, the frozen REQ list, the carry-over ledger, and
188
190
  what counts as evidence, and **every gate prints `holds: N`** — what this run left
@@ -91,9 +91,10 @@
91
91
  "running",
92
92
  "done",
93
93
  "blocked",
94
- "parked"
94
+ "parked",
95
+ "waiting"
95
96
  ],
96
- "description": "`blocked` is waiting on an edge; `parked` is the verifier's deliberate *«this is a blocker, continue around it»*. The two are different facts and collapsing them loses the one a person needs."
97
+ "description": "`blocked` is waiting on an edge; `parked` is the verifier's deliberate *«this is a blocker, continue around it»*. The two are different facts and collapsing them loses the one a person needs. `waiting` records an external event and a watcher locator; it is never runnable."
97
98
  },
98
99
  "claim": {
99
100
  "type": [
@@ -205,6 +206,10 @@
205
206
  ],
206
207
  "description": "The class of the user-facing surface this node builds, copied from the brief's `surface_class` (stage 0). It selects the gate profile, and `graph.py certify` reads it: on `flagship` or `product` the fourth, `visual` tier report is REQUIRED, because none of unit, seam or product opens the contact sheet; on any other class a `visual` report is accepted when given. Absent on a node that builds no user-facing surface. `references/certification.md` → *The fourth reading*."
207
208
  },
209
+ "risk": {
210
+ "enum": ["R1", "R2", "R3"],
211
+ "description": "The node's risk class (`references/iteration.md` → *Verification starts when a node lands*), computed from what it touches and never lowered to save time. It sets how many PASSING certification rounds the node owes (a failed round counts for nothing): R1 one, R2 two, R3 three, one of them recorded adversarial (`certify --kind adversarial`). A round is the three blind tiers plus one fix cycle. Absent outside wave mode or a release iteration. `graph.py close` refuses a node until the rounds its class owes have passed."
212
+ },
208
213
  "wave": {
209
214
  "type": "integer",
210
215
  "minimum": 1,
@@ -264,6 +269,11 @@
264
269
  "type": "string",
265
270
  "minLength": 1
266
271
  },
272
+ "kinds": {
273
+ "type": "array",
274
+ "description": "One entry per round: `blind` (the default) or `adversarial` (`graph.py certify --kind adversarial`). An R3 node owes three passing rounds, one of them adversarial (`references/certification.md` → *Rounds owed by risk class*).",
275
+ "items": {"enum": ["blind", "adversarial"]}
276
+ },
267
277
  "history": {
268
278
  "type": "array",
269
279
  "minItems": 1,
@@ -392,6 +402,9 @@
392
402
  }
393
403
  },
394
404
  "description": "The IMMUTABLE dispatch packet this node was built from (FIX-PF-05.01): address + digest of the compiled packet (REQ refs, constraints, interfaces, artifact digests, base, scope, budget, profile, skill lock — no ephemeral secret). Links the node to the exact context it ran under, so a graph in a git-ignored workspace still points at a durable, content-addressed packet."
405
+ },
406
+ "waiting_on": {
407
+ "$ref": "#/definitions/wait_record"
395
408
  }
396
409
  },
397
410
  "allOf": [
@@ -406,6 +419,9 @@
406
419
  },
407
420
  {
408
421
  "$ref": "#/definitions/rule_running_needs_claim"
422
+ },
423
+ {
424
+ "$ref": "#/definitions/rule_waiting_needs_watcher"
409
425
  }
410
426
  ]
411
427
  },
@@ -450,9 +466,12 @@
450
466
  "close",
451
467
  "invalidate",
452
468
  "waive",
453
- "wave"
469
+ "wave",
470
+ "risk",
471
+ "wait",
472
+ "resume"
454
473
  ],
455
- "description": "Which mutation made this entry. `close` joined the two on 2026-08-17 with T-5 — and it was caught by a probe rather than by the fixture asserting *the graph after a close still validates*, because `violations()` never reaches an enum. The same disagreement B-084 records, one field over. `invalidate` joined with PF-02.02: a REQ/interface/brief change supersedes a node and resets its downstream proofs. `wave` joined with wave mode: the re-cut after every wave moves nodes between waves, and a move nobody recorded is the quiet re-plan `references/planning.md` refuses."
474
+ "description": "Which mutation made this entry. `close` joined the two on 2026-08-17 with T-5 — and it was caught by a probe rather than by the fixture asserting *the graph after a close still validates*, because `violations()` never reaches an enum. The same disagreement B-084 records, one field over. `invalidate` joined with PF-02.02: a REQ/interface/brief change supersedes a node and resets its downstream proofs. `wave` joined with wave mode: the re-cut after every wave moves nodes between waves, and a move nobody recorded is the quiet re-plan `references/planning.md` refuses. `risk` joined with the release iteration: `waves.py sync-graph` sets a node's risk class from the plan, and a lowering carries its own reason."
456
475
  },
457
476
  "node": {
458
477
  "type": "string",
@@ -468,6 +487,9 @@
468
487
  "precondition": {
469
488
  "type": "string",
470
489
  "description": "The tree the mutation was made against — the proven HEAD for a `close` (FIX-PF-02.01). A revision then carries a VERSION, so a brief/REQ/interface change that moves the tree is a visibly different precondition, which is what invalidates a proof taken before it."
490
+ },
491
+ "waiting_on": {
492
+ "$ref": "#/definitions/wait_record"
471
493
  }
472
494
  }
473
495
  },
@@ -575,6 +597,69 @@
575
597
  }
576
598
  }
577
599
  }
600
+ },
601
+ "rule_waiting_needs_watcher": {
602
+ "if": {
603
+ "properties": {
604
+ "status": {
605
+ "const": "waiting"
606
+ }
607
+ },
608
+ "required": [
609
+ "status"
610
+ ]
611
+ },
612
+ "then": {
613
+ "required": [
614
+ "waiting_on"
615
+ ],
616
+ "properties": {
617
+ "claim": {
618
+ "type": "null"
619
+ }
620
+ }
621
+ },
622
+ "else": {
623
+ "not": {
624
+ "required": [
625
+ "waiting_on"
626
+ ]
627
+ }
628
+ }
629
+ },
630
+ "wait_record": {
631
+ "type": "object",
632
+ "additionalProperties": false,
633
+ "required": [
634
+ "id",
635
+ "kind",
636
+ "watcher"
637
+ ],
638
+ "properties": {
639
+ "id": {
640
+ "type": "string",
641
+ "pattern": "^[a-f0-9]{32}$",
642
+ "minLength": 32,
643
+ "maxLength": 32
644
+ },
645
+ "kind": {
646
+ "enum": [
647
+ "ci",
648
+ "review",
649
+ "peer",
650
+ "release",
651
+ "time",
652
+ "lease"
653
+ ]
654
+ },
655
+ "watcher": {
656
+ "type": "string",
657
+ "pattern": "\\S",
658
+ "not": {
659
+ "pattern": "[\\r\\n]"
660
+ }
661
+ }
662
+ }
578
663
  }
579
664
  }
580
665
  }
@@ -205,6 +205,13 @@
205
205
  "type": "string",
206
206
  "description": "Free prose beside `goal`."
207
207
  },
208
+ "authorized_by": {
209
+ "type": "string",
210
+ "minLength": 1,
211
+ "pattern": "\\S",
212
+ "not": {"pattern": "[\\r\\n]"},
213
+ "description": "Optional single-line source locator (for example docs/DECISIONS.md:42) for the specific standing authorization covering the tag and its publication. The locator is not authorization: read the source and verify its authority, currentness and scope at stage 7. Absent: inspect existing recorded authority before asking; do not infer a grant from checks approval, loop mode or this field's mere presence (references/deploy-targets.md)."
214
+ },
208
215
  "approval": {
209
216
  "enum": [
210
217
  "checks",
@@ -21,6 +21,7 @@ exactly one seam, and that seam already existed as a dangling pointer.
21
21
  - A row that says work exists names where it lives
22
22
  - Priority is computed, not assigned
23
23
  - The shape is not fixed, so nothing may assume it
24
+ - A declared source list — `docs/backlog-sources.json`
24
25
  - A decision is not debt — `waived`
25
26
  - What a loop iteration does with it
26
27
  - Rationalizations
@@ -161,6 +162,30 @@ The same rule governs the verification ledger, where five shapes exist across th
161
162
  and only one carries a column that can say a *person* looked
162
163
  ([`exposure.md`](exposure.md)).
163
164
 
165
+ ## A declared source list — `docs/backlog-sources.json`
166
+
167
+ A project whose open work lives in several files — a board, a roadmap, a lane per product —
168
+ can declare them, and then the board is exactly those files. Wave 0's harvest reads only the
169
+ sources the manifest lists, with the id, title and status columns each one names
170
+ ([`waves.md`](waves.md) → *Wave 0*). A source of open work the manifest does not list is
171
+ reported as a finding, never silently added; a status change lands only in the owning file,
172
+ under a lease.
173
+
174
+ **The shape is [`templates/backlog-sources.schema.json`](../templates/backlog-sources.schema.json)**
175
+ (backlog-sources/1), and it is the rule for anyone using this skill:
176
+ - `schema` is 1, `repository` the repository's canonical GitHub URL, `sources` 1–100 entries;
177
+ - each source is a relative `path` (no hidden, `.` or `..` segment), a `goal` anchor, and a
178
+ `kind` — `tasks` (the default) or `context`;
179
+ - a `tasks` source has a `format`: `table`, `checklist` or `json-tasks`. A table names its
180
+ `idColumn` and `titleColumn`, and exactly one of `statusColumn` or `defaultStatus: "open"`;
181
+ - `idPattern` is a literal prefix, optionally `[0-9]+`, anchored at both ends; `excludeIds`
182
+ a unique list.
183
+
184
+ Two rules a schema cannot state are the harvest's to check: each path is a regular file
185
+ reached through no symlink, and no two declarations are identical. The Fabric workspace keeps
186
+ a source contract this one is compatible with; that page is provenance, not authority, and a
187
+ public reader never needs it.
188
+
164
189
  ## A decision is not debt — `waived`
165
190
 
166
191
  `open` is work not done. `dropped` is an idea abandoned. Neither fits a deliberate **no**
@@ -21,6 +21,7 @@ queue is — graph or plan — is what decides, not the mood of the closer.
21
21
  - The report, and where each field lands
22
22
  - The commands
23
23
  - The fix cycle, and its ceiling
24
+ - Rounds owed by risk class
24
25
  - What this costs, said out loud
25
26
  - Rationalizations
26
27
 
@@ -217,10 +218,53 @@ has failed **every** round. A run spinning on one level needs the operator to se
217
218
  *which* level:
218
219
 
219
220
  - the same tier every round → the level is being misread, or the node is the wrong
220
- shape. Not one fix away. Re-plan the node, do not attempt round four.
221
+ shape. Not one fix away. Re-plan the node, do not attempt another failing round. (An R3
222
+ node's owed rounds are not this count — *Rounds owed by risk class*, below.)
221
223
  - different tiers each round → churn across levels. Usually one requirement that
222
224
  was never decided, surfacing at whichever distance looks at it.
223
225
 
226
+ ## Rounds owed by risk class
227
+
228
+ In a release iteration a node carries `risk` (R1–R3, [`work-graph.md`](work-graph.md);
229
+ [`iteration.md`](iteration.md) → *Verification starts when a node lands*), and the class
230
+ decides how many **passing** rounds the node owes before `close` accepts it: **R1 one, R2
231
+ two, R3 three, of which one is adversarial.** A round is the three blind tiers (four on a
232
+ flagship or product surface) plus one fix cycle. `graph.py certify` prints `round N: k of M
233
+ owed passing rounds — not closable yet` until the count is met (`k of M owed passing rounds —
234
+ closable` once it is), and `graph.py close` refuses
235
+ the verdict until then. **Only passing rounds count** — a round passes when every tier
236
+ passes, read from `certification.history` — and, for R3, one of the passing rounds must be
237
+ one recorded with `--kind adversarial`
238
+ (`certification.kinds`, one entry per round). A node with a `risk` and no certification at
239
+ all is refused by `close` too: its class owes at least one round.
240
+
241
+ **What a round after a clean round reads.** The node's **whole diff at the new HEAD** — not
242
+ the fix diff alone, and not the previous round's reports. A clean round proves the tree it
243
+ read; the next owed round asks the same three distances again of the whole change as it
244
+ stands, so a defect the first reading accepted has a second chance to be seen from a fresh
245
+ context.
246
+
247
+ **Owed rounds and the ceiling are different counts.** `--ceiling` measures churn: how many
248
+ rounds a node has *failed*, and which tier keeps failing. An R3 node that owes three rounds
249
+ and passes them is not "at round four" of anything; its owed rounds are the plan, and the
250
+ ceiling's warning is about failing rounds beyond what the class owes. Read the ceiling's
251
+ output only when rounds fail.
252
+
253
+ **The adversarial round** (one of R3's three, on another model where one is available):
254
+ 1. **Plant defects in the change** — off-by-one in a limit, an inverted check, a dropped
255
+ error path — and require the node's own checks to fail on each. A plant that passes is a
256
+ finding against the check, not against the plant.
257
+ 2. **Model the external system's state across runs** — a policy edited between two runs, a
258
+ key or resource renamed, two writers at once, a provider that echoes a request back or
259
+ answers twice. The defects of a money or credential path live in the second run, not
260
+ the first.
261
+ 3. **Try the abuse cases** a caller outside the trust boundary would: inputs at and past
262
+ every bound, replays, a revoked identity, an out-of-order sequence.
263
+
264
+ It is a different question, not a stricter reading: on 2026-10-12 one iteration's third
265
+ round on a spend-limits node found three breaks of a money invariant that two blind rounds
266
+ had passed. Record it with `certify --kind adversarial`.
267
+
224
268
  ## What this costs, said out loud
225
269
 
226
270
  Three agents per node instead of one — four on a flagship or product surface. That is
@@ -27,19 +27,63 @@ almost no window left, loses the middle of it, and re-derives what it already di
27
27
 
28
28
  **The loop mode collapses discretionary check-ins. It collapses nothing else.**
29
29
 
30
- - A `manual` gate still waits for an explicit operator go. Always.
31
- - An outward or irreversible act — deploy, publish, opening a PR, creating or
32
- editing a shared design file — still needs its own authorization. A recorded
33
- loop mode is a **generic** flag, and a generic flag is not a specific
34
- authorization; that floor is set in [`grill.md`](grill.md) and this file does
35
- not lower it.
36
- - **Default off.** Absent config and absent instruction, the mode is off. Silence
37
- arms nothing, exactly as silence authorizes no deploy.
30
+ - A `manual` gate requires the operator's explicit go. A specific standing authorization
31
+ naming that gate's act is such a go; cite its source and scope in the gate record.
32
+ Without it, the gate waits. A generic instruction to continue is not authorization.
33
+ - An outward or irreversible act — deploy, publish, a tag or a release, sending to a
34
+ person, spending money, creating or editing a shared design file — still needs its own
35
+ **specific** authorization. A recorded loop mode is a **generic** flag, and a generic
36
+ flag is not a specific authorization; that floor is set in [`grill.md`](grill.md) and
37
+ this file does not lower it.
38
+ - **Pushing this repository's work and opening its pull request** is armed by a specific
39
+ standing authorization that names it — a recorded instruction to push the requested work
40
+ to the repository's own remote ([`build.md`](build.md) → *6. Integrate, then finish*).
41
+ Without one it waits for the go like the acts above. Another repository's PR is outward
42
+ every time ([`conventions.md`](conventions.md)).
43
+
44
+ **What arms the loop — exactly three sources, and the ledger says which.**
45
+ 1. `run.loop` in `pipeline.json` (Part 1);
46
+ 2. the operator's answer at preflight;
47
+ 3. **a standing instruction the host already carries** — an organisation's principle that
48
+ a free agent takes the next task, a global instruction file, a recorded decision.
49
+
50
+ The run cites its source once, in the ledger: `loop: armed by <source:line>`.
51
+ **Silence still arms nothing**: absent all three, the mode is off, exactly as silence authorizes no
52
+ deploy. A project with no such instruction behaves as it always did.
53
+
54
+ **What a watcher may do unasked.** A wait is not a stop: while a review, CI, a peer's
55
+ answer or a release run is pending, the run starts a watcher and takes the next independent
56
+ item (*Waits outlive turns*, below). When the wait clears, the watcher may take — **only
57
+ where a standing instruction names that class of act** — these steps: merge under the
58
+ repository's own integration policy, release a lease, rebase a stacked branch onto what
59
+ landed, notify. Deploy, publish, tag and sending to people stay gated unless a *specific*
60
+ standing authorization names them. A recorded "releases are the agent's" decision is one:
61
+ it names the act class — the tag, and the publish its release run performs — and, when it
62
+ names no repositories, it covers every repository its owner governs. It does not stretch to
63
+ a deploy or a message it does not name.
64
+
65
+ **Work that depends on a pending change is not blocked by it**: start it on a branch
66
+ stacked on the pending one, open its PR against that branch, and rebase or merge when the
67
+ pending change lands. **A watcher polls at the pace its wait changes** — a PR or a peer's
68
+ answer every one to two minutes, a release run while it runs, a scheduled time at that time
69
+ — and that pace is separate from the loop's `/loop` interval below, which paces items, not
70
+ waits.
38
71
 
39
72
  The mode's whole content is the removal of *"should I continue?"* between items
40
73
  the operator already asked for. Anything beyond that is a different decision,
41
74
  taken separately.
42
75
 
76
+ ### Waits outlive turns
77
+
78
+ A watcher started inside a session — a background loop, a monitor, a session cron —
79
+ dies with the session. **A wait that can outlive the session goes on a durable trigger**: a
80
+ launchd or systemd job, an `at` one-shot, a CI schedule, or a board row with a due time that
81
+ a session-start hook surfaces, where the host has one. Its one line — what it waits on and which trigger acts on it —
82
+ goes into the handoff or the board ([`backlog.md`](backlog.md)), not only into the chat,
83
+ because the chat is the one place the next session will not read. On Claude Code the
84
+ session-bound primitives are a `run_in_background` loop, a Monitor and `/loop`; on other
85
+ harnesses, a background process or the host's own scheduler.
86
+
43
87
  ## Part 1 — the loop
44
88
 
45
89
  **On, the run advances one item per iteration and does not ask between items.**
@@ -52,15 +96,21 @@ This spans the whole run, not one stage:
52
96
  - the acceptance → retro tail, which is where runs most often stall one step from
53
97
  finished.
54
98
 
55
- It stops for exactly four things: a `manual` gate, a block it cannot resolve, a
99
+ It stops for exactly four things: an unmet `manual` gate, a block it cannot resolve, a
56
100
  genuine ambiguity the brief does not answer, and completion.
57
101
 
58
- **A block or an ambiguity stops its node, not the run.** The node is parked with the
59
- question as its reason, marked `needs-operator` and answerable without context
60
- ([`prioritisation.md`](prioritisation.md) → *5. Where the human goes*), and the run
61
- continues on every node it does not block. The run itself stops only when the whole
62
- frontier is blocked — `graph.py next` exits `4` — and the questions are then asked
63
- together. **When the board is empty, say so once**, and do not invent work to fill it.
102
+ **A block or an ambiguity stops its node, not the run.** An ambiguity whose answer is
103
+ internal and reversible is not a stop at all: the run takes the recommended answer, records
104
+ it as **assumed** on the node, and continues ([`iteration.md`](iteration.md) → *Question
105
+ rounds*). Otherwise park the node with its actual reason and owner: only human-owned
106
+ questions are marked `needs-operator` and made answerable without context
107
+ ([`prioritisation.md`](prioritisation.md) → *5. Where the human goes*); external waits
108
+ get watchers, and project-owned questions go to the owning Project under
109
+ [`iteration.md`](iteration.md)'s board rule. The run continues on every node it does
110
+ not block. When the whole frontier is blocked — `graph.py next` exits `4` — classify the
111
+ recorded reasons: external waits go on watchers, human decisions go into the question
112
+ batch. Exit 4 does not prove a human gate or completion. A watcher keeps the recorded
113
+ next action alive while no independent node is runnable. **When the board is empty, say so once**, and do not invent work to fill it.
64
114
 
65
115
  **This extends [`build.md`](build.md); it does not replace it.** That file's
66
116
  *Continuous execution* rule governs the inside of a stage-5 execution — do not
@@ -84,8 +134,13 @@ and still leave the run picking its next move by recollection — which is the f
84
134
  nodes and nothing else. It is preferred over the two below for one measured reason — a
85
135
  400-node graph and a 4-node graph produce the same 27-byte frontier, so the cost of knowing
86
136
  what is next does not grow with the programme. `run.loop.queue: work-graph` records it, and
87
- `next`'s exit codes are what the loop branches on: `3` is *every node is done*, `4` is
88
- *what remains is blocked or parked*, and those are different endings.
137
+ `next`'s exit codes are what the loop branches on: `3` is *every node is terminal*
138
+ (including deliberately parked nodes), `4` retains another unresolved barrier, and
139
+ `6` means recorded watched waits hold every remaining dependency leaf. On 6, keep
140
+ the recorded watchers armed and take independent work; it is neither completion
141
+ nor a new human gate. `graph.py wait` records the locator and `resume --wait-id`
142
+ resolves that exact wait; neither executes a watcher or grants authority. See
143
+ [`work-graph.md`](work-graph.md) → *Watched external waits*.
89
144
 
90
145
  **Otherwise the queue is the module map** ([`decomposition.md`](decomposition.md)) when the brief
91
146
  was a platform, and the plan's task list otherwise. Both already exist and both are
@@ -97,6 +152,10 @@ stage 2** and the run says so in one line. It is not asked for at that point, fo
97
152
  same reason the mode is recorded rather than requested: re-asking would rebuild the habit
98
153
  the config exists to retire.
99
154
 
155
+ **A standing instruction the host carries counts as recorded** (*The limit, before the
156
+ capability*, source 3): it arms at the close of stage 2 like `run.loop`, unless the
157
+ instruction itself says otherwise, and the ledger cites it.
158
+
100
159
  **Where nothing is recorded, nothing arms.** Silence arms nothing here too — this section
101
160
  moves *where* a recorded mode is armed, from preflight to the close of stage 2. It does
102
161
  not make arming unconditional, and a reader who takes it that way would arm a loop in a
@@ -104,7 +163,8 @@ project with no `pipeline.json` at all. That reading was in this section's first
104
163
  an independent reader found it before it shipped.
105
164
 
106
165
  **What arming does NOT change, and this is load-bearing:** the four stops are the four
107
- stops. A `manual` gate still waits. An outward or irreversible act still needs its own
166
+ stops. A `manual` gate waits unless a specific standing authorization already names
167
+ its act, cited with its source and scope. An outward or irreversible act still needs its own
108
168
  specific authorization — *a generic flag is not a specific authorization*, and arming a
109
169
  queue is the most generic flag there is. Arming decides only that the run does not stop
110
170
  to ask *"shall I take the next one?"*.
@@ -293,10 +353,12 @@ true.
293
353
 
294
354
  ## What happens at the signal
295
355
 
296
- The four acts above, in order, and one prohibition: **do not begin an item you
297
- cannot finish inside what is left.** When the next item is plainly too large,
298
- say so and take the smaller thing that fits — closing a ledger, writing a decision
299
- that is already settled, updating a REQ status.
356
+ The four acts above, in order, and one prohibition: **no new item starts.** This is the one
357
+ carve-out from "take the next task in the same turn": **at the compaction signal, finish the
358
+ current unit and write the next task into the handoff** — the ledger's next line, the board
359
+ row — **instead of starting it.** Until compaction, the only writes are to what already
360
+ exists: the ledger, the plan, the decisions already settled, the REQ statuses. The next
361
+ turn, after compaction, takes the written task from there.
300
362
 
301
363
  If the project has recorded that it must not cross a compaction mid-stage, stop at
302
364
  the stage boundary instead and hand off. That is a project answer, not a default:
@@ -318,6 +380,11 @@ right costs nothing extra and pays twice.
318
380
 
319
381
  ## Part 3 — workflow memory at the stage boundary
320
382
 
383
+ Project Observatory is a separately installed, public engine:
384
+ [passioncode-ai/project-observatory-dashboard](https://github.com/passioncode-ai/project-observatory-dashboard)
385
+ — its README's *Install* and *MCP* sections say how to run it and connect it. Without it,
386
+ everything below degrades to one ledger line and the run continues.
387
+
321
388
  Which of the five handoff mechanisms carries what — the Git record, Observatory's lease
322
389
  and checkpoint, a host's launch context, the inbound queue — and the order a successor
323
390
  reads them in, has one home:
@@ -333,9 +400,9 @@ checkpoint**, and the next executor continues from the last finished stage.
333
400
  - **How.** `scripts/stage_checkpoint.py emit` prints the tool's arguments, built from the ledger alone: the topic, the verdicts, the next stage, the operator's constraints and key names. Pass them to `observatory_checkpoint_write`, or to `memory.checkpoint.write` under memory/0.1. Then hand the answer to `stage_checkpoint.py record --answer -`.
334
401
  - **The constraints.** Give the brief's restrictive rules once, as `--constraint`. They are carried to every later boundary, and a successor reads them first.
335
402
  - **Keys.** Pass keys by name, as `--credential PROJECT/ENV/NAME`, never as a value.
336
- - **Without the tools.** `stage_checkpoint.py record --unavailable "<why>"` appends one `event: memory — unavailable` line, and the run continues exactly as before. Missing memory is a state the ledger names, not a failure.
403
+ - **Without the tools.** `stage_checkpoint.py record --unavailable "<why>"` appends one `event: memory — unavailable` line, and the run continues exactly as before. Missing memory is a state the ledger names, not a failure. When the tools are absent for the whole run — no Observatory on this machine — say it **once**: `record --unavailable "<why>" --for-run` appends one `unavailable for this run` line and leaves a marker in `.task-pipeline/memory.json`; no later gate is nudged or needs a line. Any later `record --answer` (the tools came back) clears the marker.
337
404
  - **A planned change of executor** — a quota running out, a route moving the work to another model or account. `stage_checkpoint.py handoff --to-provider <p> [--to-model <m>] --reason limit|plan_route|operator` prints `observatory_handoff_create`'s arguments; `record --answer -` keeps the offer as one ledger line, and the lease stays with this run until the successor accepts. The successor runs `stage_checkpoint.py accept --handoff-id <id>` → `observatory_handoff_accept` → `record --answer -` **before its first write**, and obeys the constraints the answer carries first. `LeaseLost` is then left for the race nobody planned. A retried offer keeps its idempotency key until a memory event (a checkpoint, a refusal, a recorded offer — never `unavailable`) is appended, so a `gate:` or `stage:` line between retries does not turn it into a new offer that replaces the unaccepted one.
338
- - **The reminder.** `hooks/memory-nudge.sh` (PostToolUse) reads the ledger after each tool call and, once per `stage:` line with no `event: memory` line after it, puts one sentence in front of the model naming the two commands above. It decides nothing and blocks nothing; adoption measured before it was one write in 293 sessions.
405
+ - **The reminder.** `hooks/memory-nudge.sh` (PostToolUse) reads the ledger after each tool call and, once per `stage:` line with no `event: memory` line after it, puts one sentence in front of the model naming the two commands above — unless the run-level marker says memory is unavailable for this run, in which case it stays silent. The first reminder of a run also says where Observatory comes from, once. It decides nothing and blocks nothing; adoption measured before it was one write in 293 sessions.
339
406
  - **A refusal.** `LeaseLost` means another executor holds the workflow now. The script drops the token. Read the workflow (`observatory_checkpoint_latest` with `stage_checkpoint.py state`'s id) before doing anything else. A stale writer stops after one refusal.
340
407
  - **The lease token.** It lives in `.task-pipeline/memory.json` (mode 0600, git-ignored) and never in the ledger, a reply or a commit.
341
408
  - **What this is not.** Not a second ledger and not the narrative. The checkpoint is the work's state for the next executor. The wiki and the ledger keep everything else.
@@ -344,7 +411,9 @@ checkpoint**, and the next executor continues from the last finished stage.
344
411
 
345
412
  | Excuse | Reality |
346
413
  |---|---|
347
- | "The operator obviously wants it to keep going, I'll just loop" | Default off. Silence arms nothing — the same floor deploy authorization uses. Ask once at preflight, or read `run.loop`. |
414
+ | "The operator obviously wants it to keep going, I'll just loop" | Default off. Silence arms nothing — the same floor deploy authorization uses. Arm from `run.loop`, the preflight answer, or a standing instruction the host carries, and cite which in the ledger. |
415
+ | "The watcher can push the release through when CI goes green" | A watcher takes only the act classes a standing instruction names — merge, release a lease, rebase, notify. A tag or a deploy needs its own specific authorization. |
416
+ | "My background loop will catch it tomorrow" | It dies with the session. A wait that can outlive it goes on a durable trigger, and its line goes into the handoff. |
348
417
  | "The mode is on, so I can push the deploy through" | The mode collapses check-ins, never gates or outward acts. A generic flag is not a specific authorization. |
349
418
  | "I'll tell it to keep going and it will" | On Claude Code, prose does not survive the turn boundary. Either a job is scheduled or the run stops; say which. |
350
419
  | "A one-minute interval is closest to no pause" | The interval must be longer than a typical item, or every fire lands on work already in progress and buys nothing. |