task-pipeline-skill 1.67.0 → 1.69.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 +856 -0
- package/README.md +25 -0
- package/SKILL-CARD.md +1 -1
- package/bin/task-pipeline.js +30 -0
- package/package.json +4 -3
- package/plugins/task-pipeline/.claude-plugin/plugin.json +1 -1
- package/plugins/task-pipeline/agents/verifier.md +88 -0
- package/plugins/task-pipeline/commands/task-pipeline.md +22 -0
- package/plugins/task-pipeline/skills/task-pipeline/SKILL.md +84 -136
- package/plugins/task-pipeline/skills/task-pipeline/graph.example.json +73 -0
- package/plugins/task-pipeline/skills/task-pipeline/graph.schema.json +253 -0
- package/plugins/task-pipeline/skills/task-pipeline/pipeline.schema.json +46 -3
- package/plugins/task-pipeline/skills/task-pipeline/references/acceptance.md +69 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/audit.md +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/continuity.md +9 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/documentation.md +17 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/gates.md +91 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/portability.md +1 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/progress.md +41 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/stages.md +11 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/verification.md +52 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/work-graph.md +121 -0
- package/plugins/task-pipeline/skills/task-pipeline/scripts/graph.py +1113 -0
- package/plugins/task-pipeline/skills/task-pipeline/templates/README.md +1 -0
- package/plugins/task-pipeline/skills/task-pipeline/templates/carryover.md +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/templates/convergence.sh +146 -0
- package/plugins/task-pipeline/skills/task-pipeline/templates/exposure.sh +104 -1
- package/plugins/task-pipeline/skills/task-pipeline/templates/hooks.example.json +13 -1
- package/plugins/task-pipeline/skills/task-pipeline/templates/run.md +32 -0
- package/plugins/task-pipeline/skills/task-pipeline/templates/verification.md +67 -5
|
@@ -22,6 +22,7 @@ elsewhere and is not restated here:
|
|
|
22
22
|
## Contents
|
|
23
23
|
|
|
24
24
|
- Axis A — the stage gate type
|
|
25
|
+
- The judgment gate — a ruling is not a measurement
|
|
25
26
|
- Axis B — the enforcement mechanism
|
|
26
27
|
- Axis C — degrees of freedom
|
|
27
28
|
- Progressive arming
|
|
@@ -40,6 +41,7 @@ elsewhere and is not restated here:
|
|
|
40
41
|
- Where a gate runs
|
|
41
42
|
- Adding a check to an existing gate
|
|
42
43
|
- Rationalizations
|
|
44
|
+
- Cross-cutting, at every stage
|
|
43
45
|
|
|
44
46
|
## Axis A — the stage gate type
|
|
45
47
|
|
|
@@ -48,6 +50,7 @@ From [`../pipeline.schema.json`](../pipeline.schema.json), one per stage:
|
|
|
48
50
|
| Type | Meaning | Failure to respect it |
|
|
49
51
|
|---|---|---|
|
|
50
52
|
| `auto` | the orchestrator verifies the `check` itself, pass/fail, and stops on fail | advancing on an unverified check |
|
|
53
|
+
| `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 |
|
|
51
54
|
| `manual` | wait for the operator's **explicit** go | treating an auto verification as the approval |
|
|
52
55
|
|
|
53
56
|
**An auto gate never substitutes for a required manual approval.** A green table is
|
|
@@ -55,6 +58,49 @@ not the operator confirming it is what they asked for, and no amount of checking
|
|
|
55
58
|
makes it one. Which stages are manual is the **operator's** decision, recorded in
|
|
56
59
|
their `pipeline.json`; the framework fixes no stage count and no gate assignment.
|
|
57
60
|
|
|
61
|
+
## The judgment gate — a ruling is not a measurement
|
|
62
|
+
|
|
63
|
+
Two types were not enough, and the gap was not cosmetic. A reviewer's ruling, a check that
|
|
64
|
+
the scenarios are coherent, a verdict that a mockup is good — none has a complete
|
|
65
|
+
deterministic check, and all three rode in `auto`, **indistinguishable from an exit code**.
|
|
66
|
+
A coverage table then cannot tell a measured row from an opinion, and the role-agent
|
|
67
|
+
programme multiplies the problem: `reviewer`, `ux`, `ui` and `market-analyst` produce
|
|
68
|
+
judgement by design.
|
|
69
|
+
|
|
70
|
+
`auto` now means only what a machine established.
|
|
71
|
+
|
|
72
|
+
**The precedent already existed in miniature, and this generalises it rather than
|
|
73
|
+
inventing it.** [`templates/verification.md`](../templates/verification.md) already turns a
|
|
74
|
+
coverage verdict of `review` into `none` in the `Auto` column — because that column records
|
|
75
|
+
what a machine established, and a review is not that. That rule, applied to one column, is
|
|
76
|
+
the `judgment` type in one instance.
|
|
77
|
+
|
|
78
|
+
Three obligations, and the third is the one that bites:
|
|
79
|
+
|
|
80
|
+
1. **It names its `judge`** — a role, an agent, a person. The schema refuses the gate
|
|
81
|
+
without one. A ruling with no author cannot be weighed for independence, and
|
|
82
|
+
independence is not a property of *having* a reviewer: this pipeline's own `R-005` reader
|
|
83
|
+
shares a model, instructions and repository with the author it reviews, differing only in
|
|
84
|
+
context. That is a real second reading and it is **not** a deterministic runner, a
|
|
85
|
+
contract at another boundary, or an external system. Naming the judge is what makes the
|
|
86
|
+
difference visible instead of assumed.
|
|
87
|
+
2. **The verdict is recorded as judgement**, in the artifact that quotes it — never
|
|
88
|
+
promoted to a pass in a column that means *a machine established this*.
|
|
89
|
+
3. **It may not stand in for a `manual` gate.** A judgement can be rendered by an agent; an
|
|
90
|
+
*authorisation* cannot. Anything outward, irreversible, or costing money stays `manual`
|
|
91
|
+
however confident the judge.
|
|
92
|
+
|
|
93
|
+
**Which of this pipeline's own gates are judgement is deliberately not decided here.**
|
|
94
|
+
Gate assignment is the operator's call and the framework fixes none — so shipping a
|
|
95
|
+
reclassified stage list would contradict the sentence above it. The type exists; the
|
|
96
|
+
project chooses where it applies.
|
|
97
|
+
|
|
98
|
+
| Rationalization | Why it is wrong |
|
|
99
|
+
|---|---|
|
|
100
|
+
| *"The reviewer approved it, so the gate passed."* | It did — as a judgement. Type it as one, or the table claims a machine agreed |
|
|
101
|
+
| *"A second agent checked it, so it is independent."* | Independence is a different **evidence path**, not a second reader. Name the judge and the difference is visible |
|
|
102
|
+
| *"There is no check for this, so it has to be `manual`."* | `manual` waits for a person's authority. `judgment` records a ruling. Collapsing them puts a human in the loop for everything that is merely hard to measure, which is how an operator learns to route around the pipeline |
|
|
103
|
+
|
|
58
104
|
## Axis B — the enforcement mechanism
|
|
59
105
|
|
|
60
106
|
Where a rule actually lives. A rule climbs this ladder; it does not start at the top.
|
|
@@ -447,7 +493,7 @@ A **ratchet** is a named, counted set that may only shrink, printed on every run
|
|
|
447
493
|
|
|
448
494
|
```
|
|
449
495
|
GATE 9 docs: PASS — propagation backlog: 121 (was 162) · unmarked residue: 0
|
|
450
|
-
abstained: 0 · unlooked: 4 (3 dormant · 1 skip — no submodules in this repo)
|
|
496
|
+
abstained: 0 · unlooked: 4 (3 dormant · 1 skip — no submodules in this repo) · holds: 0
|
|
451
497
|
```
|
|
452
498
|
|
|
453
499
|
A ratchet nobody prints is a TODO with a better name.
|
|
@@ -589,3 +635,47 @@ of is deleted in the next refactor by someone who assumed it was dead.
|
|
|
589
635
|
| "The gate would be red on day one, so I'll add it later" | Make the section dormant instead. Dormant is visible and green; "later" is neither. |
|
|
590
636
|
| "I raised the floor to get the build green" | Then say so in the log, in the same commit. A floor raised silently is a ratchet running backwards. |
|
|
591
637
|
| "A hook is overkill, CI catches it" | CI catches it after the edit, the commit and the push. If the point is to stop the edit, CI is the wrong rung — and if it is not, do not pay the latency. |
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
## Cross-cutting, at every stage
|
|
642
|
+
|
|
643
|
+
5. Cross-cutting, every stage: **when anything is settled — scope, a contract, a
|
|
644
|
+
name, a policy, a vocabulary — run the Doc Loop
|
|
645
|
+
(`references/documentation.md`) before the run moves on**: reserve the id,
|
|
646
|
+
record it, resolve the question it answers, propagate by the matrix, commit
|
|
647
|
+
with the ids. A decision that lives only in the spec dies with the spec, and one
|
|
648
|
+
that lives only in the conversation was never made;
|
|
649
|
+
**answer from the brief's autonomy section rather
|
|
650
|
+
than asking again** — it was grilled precisely so you wouldn't have to;
|
|
651
|
+
**anything deferred, dropped or left half-done goes into the carry-over ledger
|
|
652
|
+
the moment it's said** — deferred out loud is forgotten; **never narrow the task
|
|
653
|
+
silently** — the REQ list is frozen, adding is free, removing needs the
|
|
654
|
+
operator's explicit agreement; **when a loop starts undoing an earlier pass —
|
|
655
|
+
the same file edited twice for the same reason, a closed finding coming back, a
|
|
656
|
+
third entry into one stage — stop and run the loop guard**
|
|
657
|
+
(`references/loop-guard.md`): name the two shapes, escalate to the layer that
|
|
658
|
+
owns the conflict, re-plan the check as an ordered list, then go through it one
|
|
659
|
+
item at a time; **when a pass is *searching* rather than editing and starts
|
|
660
|
+
finding mostly what the previous pass's own fixes broke, the axis is exhausted —
|
|
661
|
+
rotate it, don't look harder** (`references/audit.md`); **every gate
|
|
662
|
+
prints `holds: N` — what this run left running** across the eight classes
|
|
663
|
+
(background shells, monitors, scheduled loops, coordination leases, worktrees,
|
|
664
|
+
containers, scratch files, remote state), enumerated **by class and never by a
|
|
665
|
+
single tool**, and stage 10 does not close while this run's residue is live and
|
|
666
|
+
unaccounted (`references/residue.md`); and remember that a
|
|
667
|
+
green from a check nobody has watched fail is not evidence; task
|
|
668
|
+
tracker + conventional commits per host conventions; worktree isolation for the
|
|
669
|
+
build, integrated back per the brief's branch policy before stage 7; honest
|
|
670
|
+
degradation (never claim a failed/skipped step succeeded);
|
|
671
|
+
outward/irreversible actions (deploy, publish, repo create, opening a PR,
|
|
672
|
+
**editing a shared design file — frames are read by designers and stakeholders,
|
|
673
|
+
so drawing in one is publishing — and above all *creating* one, which needs a
|
|
674
|
+
named team and never happens while a recorded file resolves**) need explicit
|
|
675
|
+
operator go — or a **specific** standing authorization recorded in the brief
|
|
676
|
+
(named target + preconditions; a vague "do everything" is not one).
|
|
677
|
+
|
|
678
|
+
Moved out of `SKILL.md` on 2026-08-16 for the same budget reason as the
|
|
679
|
+
multi-repository block: these fire at any stage, so they belong with the gate
|
|
680
|
+
doctrine rather than inside step 5 of the run order.
|
|
681
|
+
|
|
@@ -70,6 +70,7 @@ a row pointing outside the bundle is the defect this file exists to catch.
|
|
|
70
70
|
| Cutting a platform into modules, brick criteria, build order | `references/decomposition.md` |
|
|
71
71
|
| What a spec must lock, the UX-track order, the module dossier | `references/spec.md` |
|
|
72
72
|
| The zero-context plan format, parallel groups, set equality | `references/planning.md` |
|
|
73
|
+
| The work graph: its fields, the verbs and their exit codes, and the three invariants a schema cannot state | `references/work-graph.md`, `scripts/graph.py`, `graph.schema.json` |
|
|
73
74
|
| Workspace isolation, the subagent loop, who may write the register | `references/build.md` |
|
|
74
75
|
| The review rubric, diff packages, the three verdicts | `references/review.md` |
|
|
75
76
|
| **False success** — the class, its known shapes and its two rules | `references/gates.md` |
|
|
@@ -163,11 +163,31 @@ PROGRESS where the run stands against that request: gates passed,
|
|
|
163
163
|
DONE what was solved this iteration, each with its evidence
|
|
164
164
|
SURFACED what came up that nobody asked for — findings, corrections,
|
|
165
165
|
things that turned out to be other than assumed
|
|
166
|
+
SCOPE what this claim covers: the commit, the environment, the REQ
|
|
167
|
+
ids, the surfaces — the validity domain, not the ambition
|
|
168
|
+
NOT VERIFIED what was built and NOT checked, or could not be. The literal
|
|
169
|
+
`none within the stated scope` is a valid answer; an empty
|
|
170
|
+
field is not
|
|
166
171
|
|
|
167
172
|
DECISIONS WAITING <n> each as a question with options, asked HERE
|
|
168
173
|
AMBIGUITIES <n> computed, below
|
|
169
174
|
```
|
|
170
175
|
|
|
176
|
+
**`SCOPE` and `NOT VERIFIED` are the two the block lacked, and their absence had a
|
|
177
|
+
shape.** Every gate already computes exactly what `NOT VERIFIED` needs — `abstained` for
|
|
178
|
+
claims the run declined to make, `unlooked` for checks that did not look — and none of it
|
|
179
|
+
reached the artifact an operator actually reads. So a run could hand back a report honest
|
|
180
|
+
sentence by sentence and still be **indistinguishable from a run whose checks never
|
|
181
|
+
looked**, which is the failure this file names three separate times.
|
|
182
|
+
|
|
183
|
+
`NOT VERIFIED` is **populated from those disclosures rather than composed**: it is the
|
|
184
|
+
`abstained` and `unlooked` sets in words, plus anything built this iteration that no check
|
|
185
|
+
touched. Composing it by hand is how it becomes a summary of the parts somebody remembered.
|
|
186
|
+
|
|
187
|
+
And `none within the stated scope` is a claim with a subject — it says *nothing inside
|
|
188
|
+
what SCOPE names is unverified*. Leaving the field empty says nothing at all, which reads
|
|
189
|
+
as the same thing and is not: canon 9a, one artifact over.
|
|
190
|
+
|
|
171
191
|
**Where there is no brief** — `checkup`, `setup`, a short path — TASK quotes the
|
|
172
192
|
operator's own sentence instead, marked as such. An unquotable TASK is a run that cannot
|
|
173
193
|
say what it was asked, which is worth its own line.
|
|
@@ -338,6 +358,27 @@ of the command the project declared in `pipeline.json` → the tests stage's
|
|
|
338
358
|
`gate.command`. The rail does not read it — a glyph still comes from the verdict —
|
|
339
359
|
but the stage-7 release gate requires the claim and the observation to agree.
|
|
340
360
|
|
|
361
|
+
**The same shape, one axis over — `read:`.** Since v1.69.0 a `PostToolUse` hook on `Read`
|
|
362
|
+
appends
|
|
363
|
+
|
|
364
|
+
```
|
|
365
|
+
read: references/<file>.md
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
deduplicated, and always exiting 0 — a hook that can fail a `Read` breaks every turn in
|
|
369
|
+
every session. `scripts/graph.py doctrine` reports how many of the bundle's reference files
|
|
370
|
+
a run opened and lists the rest.
|
|
371
|
+
|
|
372
|
+
Why it is hook-written is the same reason as above: a claim about what somebody read,
|
|
373
|
+
written by the party the claim is about, is not evidence. And why the verb prints
|
|
374
|
+
`unmeasured` rather than `0` when there are no such lines is the same reason again — the
|
|
375
|
+
hook being absent and the run reading nothing are **opposite facts** the ledger cannot
|
|
376
|
+
separate, so it claims neither. A `0` there would be the reassuring answer to a question
|
|
377
|
+
nobody asked, over 34 files nobody checked.
|
|
378
|
+
|
|
379
|
+
It is a disclosure: no floor, no direction, never a target. The moment the number becomes
|
|
380
|
+
something to raise, a run will open files to raise it.
|
|
381
|
+
|
|
341
382
|
The reason is the one this whole file is about, arriving one level down. A rail
|
|
342
383
|
written from memory is a summary that is confidently wrong exactly when it matters;
|
|
343
384
|
a gate that reads a verdict typed by the agent it constrains is the same shape
|
|
@@ -262,6 +262,14 @@ never that the work was skipped quietly.
|
|
|
262
262
|
its status, in build order with the walking skeleton first. Single-module work
|
|
263
263
|
records `single module: <name>` in the design and moves on — a skipped
|
|
264
264
|
decomposition is a decision, never an omission.
|
|
265
|
+
- **Where the queue is a work graph, it is WRITTEN here**
|
|
266
|
+
([`work-graph.md`](work-graph.md)): `.task-pipeline/graph.json`, carrying the frozen REQ
|
|
267
|
+
ids so `serves` resolves, one node per unit of work with its owner and what it touches,
|
|
268
|
+
and an edge per dependency **naming what it hands over**. Then
|
|
269
|
+
`python3 scripts/graph.py validate` — a graph that does not validate is not a queue, and
|
|
270
|
+
`next` refuses to walk one. The reason to prefer it over a prose plan is measured, not
|
|
271
|
+
aesthetic: a 400-node graph and a 4-node graph produce the same 27-byte frontier, so the
|
|
272
|
+
cost of knowing what is next does not grow with the programme.
|
|
265
273
|
- **The queue exists here, so the loop arms here** ([`continuity.md`](continuity.md) →
|
|
266
274
|
*Part 1a*). Where `run.loop.arm` is `after-decomposition` and the map holds more than
|
|
267
275
|
one module, arm the mode at the close of this stage and print one line: the mode, and
|
|
@@ -269,7 +277,9 @@ never that the work was skipped quietly.
|
|
|
269
277
|
(`dynamic`). Arming collapses no gate and authorizes no outward act; it decides only
|
|
270
278
|
that the run does not stop to ask *"shall I take the next one?"*. Single-module work
|
|
271
279
|
arms nothing and says so — a loop with one item is a timer.
|
|
272
|
-
- **GATE (manual):** the user approves the design, the UI verdict is recorded,
|
|
280
|
+
- **GATE (manual):** the user approves the design, the UI verdict is recorded, **the
|
|
281
|
+
queue is an artifact rather than a recollection** — where it is a work graph,
|
|
282
|
+
`graph.py validate` exits 0 and `graph.py coverage` names any requirement no node serves,
|
|
273
283
|
**every REQ is answered by the design** — a requirement the design doesn't
|
|
274
284
|
address is either covered now or explicitly dropped by the operator, with the
|
|
275
285
|
drop recorded in the carry-over ledger — **and, for a platform, the module map is
|
|
@@ -22,6 +22,7 @@ the run*. Three things it does not say, and each is why this file exists:
|
|
|
22
22
|
|
|
23
23
|
- Why it keys to the brief, not to the coverage table
|
|
24
24
|
- `never` is a fact
|
|
25
|
+
- Staleness — the other end of `never`
|
|
25
26
|
- A ledger records two different things, and most record only one
|
|
26
27
|
- What stage 8 writes and what stage 10 refuses
|
|
27
28
|
- Rationalizations
|
|
@@ -54,6 +55,57 @@ So the count has **no floor, no direction, and may never be given a target**, ex
|
|
|
54
55
|
like the disclosures in [`gates.md`](gates.md). A project with forty `never` rows is not
|
|
55
56
|
failing; it is a project that now knows something it could not previously ask.
|
|
56
57
|
|
|
58
|
+
## Staleness — the other end of `never`
|
|
59
|
+
|
|
60
|
+
This file tracked rows nobody had ever confirmed and had **no notion of a row whose
|
|
61
|
+
confirmation the tree has overtaken.** A row verified at commit A read `verified` after
|
|
62
|
+
commit B, forever. Those are the same failure from two ends, and only one end was
|
|
63
|
+
instrumented — which is why a ledger could read fully green over a tree where every check
|
|
64
|
+
ran against code that has since moved.
|
|
65
|
+
|
|
66
|
+
`Observed at` is the commit the check ran against. `scripts/exposure.sh` reports four
|
|
67
|
+
states from it, and the whole thing is a **port**: `references/knowledge-graph.md` already
|
|
68
|
+
gives the code graph a stamp, a distance and three states, and every non-current one ends
|
|
69
|
+
in a marker. The same contract, applied to the ledger.
|
|
70
|
+
|
|
71
|
+
| State | Condition | Reads |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| **current** | 0 commits behind `HEAD` | still speaks about this tree |
|
|
74
|
+
| **behind** | N commits / M days behind | *not trusted for the current tree until re-observed* |
|
|
75
|
+
| **unresolvable** | the commit does not resolve here | rebase, squash, shallow clone — same marker |
|
|
76
|
+
| **unanchored** | no commit recorded | nothing can say what it saw |
|
|
77
|
+
|
|
78
|
+
**It is a disclosure: no floor, no direction, never a target** — the same contract the
|
|
79
|
+
`Human` column has, and for the same reason. A threshold on staleness teaches a run to
|
|
80
|
+
re-observe rows for the counter rather than for the question.
|
|
81
|
+
|
|
82
|
+
**`behind` means unproven for this tree, never wrong.** The section knows the distance and
|
|
83
|
+
does not know whether the commits in between touched anything the row covers. Claiming
|
|
84
|
+
more than that would be the estimate-printed-as-measurement this pipeline refuses
|
|
85
|
+
elsewhere.
|
|
86
|
+
|
|
87
|
+
**State zero prints out loud.** `current 12 · behind 0 · unresolvable 0 · unanchored 0` is
|
|
88
|
+
a measurement; printing nothing when everything is fresh makes freshness indistinguishable
|
|
89
|
+
from a check that never looked.
|
|
90
|
+
|
|
91
|
+
**Invalidation is not deletion.** An overtaken row is not wrong — it is true about the tree
|
|
92
|
+
it observed, and it stays. Re-observing **appends** a row; it never edits the old one. Four
|
|
93
|
+
things overtake a row, and the note says which applies: a **code** change in what it
|
|
94
|
+
covers, a **dependency** change, an **environment** change, and a **policy** change — the
|
|
95
|
+
rule under which the evidence was accepted.
|
|
96
|
+
|
|
97
|
+
**Where the section prints matters as much as what it prints.** It runs *before* the
|
|
98
|
+
check-list, because `exposure.sh` exits early when nothing is unverified — and that is
|
|
99
|
+
exactly the state where these counts matter most. `0 unverified` is the sentence most
|
|
100
|
+
likely to be read as *nothing to look at*, and a staleness section invisible behind it
|
|
101
|
+
would have been dormant precisely when it was needed.
|
|
102
|
+
|
|
103
|
+
| Rationalization | Why it is wrong |
|
|
104
|
+
|---|---|
|
|
105
|
+
| *"The row says `pass`, so it passes."* | It passed against a tree. Name which one, or the claim has no subject |
|
|
106
|
+
| *"Everything is behind, so the number is noise."* | Then it is a large honest number. A threshold would make it a small dishonest one |
|
|
107
|
+
| *"I re-ran the check, so I will update the row."* | Append. The old row is true about what it saw, and overwriting it destroys the only record of when the claim was earned |
|
|
108
|
+
|
|
57
109
|
## A ledger records two different things, and most record only one
|
|
58
110
|
|
|
59
111
|
**What confirmed it** and **whether a person looked** are separate facts. The first is
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# The work graph — the queue a script walks so the model never reads it
|
|
2
|
+
|
|
3
|
+
**One job: hold what is left to do, in a form whose cost does not grow with the
|
|
4
|
+
programme.**
|
|
5
|
+
|
|
6
|
+
A plan is prose. A prose plan for a real release is hundreds of lines, and a model that
|
|
7
|
+
re-reads it every iteration spends its context on ground it has already walked — so the
|
|
8
|
+
cost of knowing *what is next* rises with the size of the work, which is exactly backwards.
|
|
9
|
+
|
|
10
|
+
`.task-pipeline/graph.json` is the same information as a typed graph, and
|
|
11
|
+
[`scripts/graph.py`](../scripts/graph.py) answers one question against it: **which nodes are
|
|
12
|
+
runnable right now.** What enters a context each iteration is then bounded by the
|
|
13
|
+
**frontier's width**, not by the graph's size.
|
|
14
|
+
|
|
15
|
+
Measured on this build: a 400-node graph (51 KB) and a 4-node graph produce the same
|
|
16
|
+
**27-byte** frontier. That property is the whole reason the file exists, and it is why
|
|
17
|
+
`next` prints the frontier and nothing else — anything else printed there is paid on every
|
|
18
|
+
turn of every loop.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Contents
|
|
23
|
+
|
|
24
|
+
- What is in it, and what each field is for
|
|
25
|
+
- The verbs, and their exit codes
|
|
26
|
+
- The three things a schema cannot state
|
|
27
|
+
- What it deliberately does NOT do
|
|
28
|
+
- Rationalizations
|
|
29
|
+
|
|
30
|
+
## What is in it, and what each field is for
|
|
31
|
+
|
|
32
|
+
`graph.schema.json` is the contract; this is why each part is there.
|
|
33
|
+
|
|
34
|
+
| Field | Why |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `goal` | echoed from `pipeline.json` → `release.goal`, so the graph carries the thing it serves rather than pointing at a file that may have moved on |
|
|
37
|
+
| `requirements` | the REQ ids the brief froze. **`serves` resolves against these** — without them it was a non-empty string and nothing more, and that field is the one edge joining the intent graph to this one |
|
|
38
|
+
| `goal_clauses` | release work no requirement names. Enumerated, never matched against the goal's prose: substring-matching a sentence produces confidence without correctness |
|
|
39
|
+
| `nodes[].owner` | which role does it. A node nobody can dispatch never leaves the frontier and nothing says why |
|
|
40
|
+
| `nodes[].serves` | the REQ or goal clause it exists for. A node serving neither is **parked with that as the reason** |
|
|
41
|
+
| `nodes[].blocked_by` | what must close first. This is what the frontier obeys |
|
|
42
|
+
| `nodes[].touches` | what it **mutates**. Two runnable nodes writing one file is the false parallelism [`planning.md`](planning.md) refuses — *distinct is not the same as independent, and the check is what they touch, never what they are called* |
|
|
43
|
+
| `nodes[].evidence` | required when `status` is `done`. A node called done by assertion is what evidence exists to prevent |
|
|
44
|
+
| `nodes[].parked_reason` | required when `status` is `parked`. A park with no reason is indistinguishable, a week later, from work quietly dropped |
|
|
45
|
+
| `edges[].payload` | what the dependency hands over. **An edge carrying no named artifact is chronology drawn as architecture** |
|
|
46
|
+
| `revisions` | why the graph is not the graph stage 2 wrote. A graph that changed for reasons nobody recorded can explain its own completion by appealing to a plan that existed only at the end |
|
|
47
|
+
|
|
48
|
+
## The verbs, and their exit codes
|
|
49
|
+
|
|
50
|
+
Exit codes are the contract, per standing instruction `R-004`: **the next command is
|
|
51
|
+
conditional on the code, never merely sequenced after it.**
|
|
52
|
+
|
|
53
|
+
| Verb | Prints | Codes |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `validate` | every violation, in a stable order | `0` clean · `1` any |
|
|
56
|
+
| `next` | the frontier, ordered by how much each node unblocks — **and nothing else** | `0` printed · `3` all done · `4` nothing runnable |
|
|
57
|
+
| `coverage` | each requirement with the nodes serving it | `0` covered · `1` a gap, named |
|
|
58
|
+
| `goal` | the release goal | `0` · `3` unstated |
|
|
59
|
+
| `add` | the id it allocated | `0` · `1` refused |
|
|
60
|
+
| `park` | the id and the reason | `0` · `1` refused |
|
|
61
|
+
| `close` | the goal, the new frontier count, and what was not verified | `0` · `1` refused **or the verdict stops the run** |
|
|
62
|
+
| `producer` | what produced this proof — actor, model, runtime, skill, config, commit, trace | `0` |
|
|
63
|
+
| `doctrine` | how many of the bundle's reference files this run opened | `0` |
|
|
64
|
+
|
|
65
|
+
**`next` is ordered by what each node unblocks, transitively, and the number is computed.**
|
|
66
|
+
A `priority` field is something somebody typed once and nobody revisits; this one moves when
|
|
67
|
+
the graph does. Ties break on declaration order, so the frontier is stable between runs —
|
|
68
|
+
an unstable one costs more than it looks, because an agent calling `next` twice starts the
|
|
69
|
+
other node.
|
|
70
|
+
|
|
71
|
+
**`close` stamps the commit; the verifier never supplies it.** A verdict written after the
|
|
72
|
+
tree moved is evidence about a different tree, and an agent cannot name the wrong commit if
|
|
73
|
+
it is never the one naming one.
|
|
74
|
+
|
|
75
|
+
**A stop closes its node and refuses the next step.** `replan.possible: false` says the run
|
|
76
|
+
cannot continue *around* what it found — not that the work just verified did not happen.
|
|
77
|
+
|
|
78
|
+
## The three things a schema cannot state
|
|
79
|
+
|
|
80
|
+
The split is where the format actually puts it, which is not where the first draft drew it.
|
|
81
|
+
`graph.schema.json` states everything JSON Schema can, including `done → evidence` and
|
|
82
|
+
`parked → parked_reason` — the first version claimed those were beyond it and was wrong.
|
|
83
|
+
|
|
84
|
+
What genuinely cannot be expressed is cross-document and cross-node, and lives in
|
|
85
|
+
`violations()`:
|
|
86
|
+
|
|
87
|
+
1. **Whether `owner` names a role that exists** — with a near-miss hint, because an agent
|
|
88
|
+
told only *no* will try a synonym.
|
|
89
|
+
2. **Whether `serves` resolves**, and whether every `blocked_by` and edge end is in the
|
|
90
|
+
graph at all.
|
|
91
|
+
3. **Whether the edges cycle** — the one failure of this design that looks exactly like slow
|
|
92
|
+
progress: a frontier can be non-empty forever while nothing is runnable.
|
|
93
|
+
|
|
94
|
+
And one more that is not about expressiveness: **the schema is never applied to a live
|
|
95
|
+
graph.** `graph.py` is stdlib-only by design, so `violations()` also enforces the rules the
|
|
96
|
+
format states — because a rule checked only against the shipped example is a rule the run
|
|
97
|
+
does not have.
|
|
98
|
+
|
|
99
|
+
## What it deliberately does NOT do
|
|
100
|
+
|
|
101
|
+
- **It is not committed.** `.task-pipeline/graph.json` is a run artifact. That is a live
|
|
102
|
+
tension, not a settled question: a graph deleted with its run cannot be inspected
|
|
103
|
+
afterwards, and `docs/evidence/backlog.md` carries it rather than this file pretending it
|
|
104
|
+
is resolved.
|
|
105
|
+
- **It does not decide the module map.** Stage 2 does ([`decomposition.md`](decomposition.md));
|
|
106
|
+
the graph is where that decision becomes walkable.
|
|
107
|
+
- **It does not schedule.** `next` says what *may* run. Whether two runnable nodes are
|
|
108
|
+
dispatched together is the dispatcher's call, and the collision warning on stderr is what
|
|
109
|
+
that call is made against.
|
|
110
|
+
- **It does not read like a plan.** If you want to know why the work is shaped this way, the
|
|
111
|
+
brief and the spec say so. The graph says what is left.
|
|
112
|
+
|
|
113
|
+
## Rationalizations
|
|
114
|
+
|
|
115
|
+
| The excuse | Why it is wrong |
|
|
116
|
+
|---|---|
|
|
117
|
+
| *"I'll just read the graph, it's only forty nodes."* | Forty is the number today. The property being protected is that four hundred costs the same, and it stops being true the first time the model reads the file |
|
|
118
|
+
| *"The frontier is short; one extra line won't matter."* | It is paid on every iteration of every loop. That is what *the frontier and nothing else* means |
|
|
119
|
+
| *"`blocked_by` already says the dependency, the edge is bookkeeping."* | The edge carries the **payload**. A dependency handing over nothing named is the fake edge with a field around it |
|
|
120
|
+
| *"I'll add the node now and record why later."* | Later is the run that cannot say why its plan changed, which is the run whose completion claim cannot be falsified |
|
|
121
|
+
| *"The graph validates, so the mutation was fine."* | `validate` is stdlib and the format is richer. Both were made to agree once, and the disagreement returned within a day — check the two against each other, not the result against one |
|