task-pipeline-skill 1.73.0 → 1.75.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 CHANGED
@@ -1,5 +1,184 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.75.0 — 2026-08-22 — the day's findings become doctrine
4
+
5
+ Nothing new ships behind a flag here; what ships is the doctrine the previous release
6
+ earned, written where the next run will read it instead of in a changelog nobody greps.
7
+
8
+ **`documentation.md` canon 2 gains its dual.** *Numbers are computed, never restated* had
9
+ a half nobody had written down: **an example that instantiates a number IS one.** A release
10
+ note explaining that a count was written in the wrong shape — with the digits in it — places
11
+ a second readable count in a section a gate reads, so a probe that removes the real one
12
+ leaves the narrative matching and the guard silent. Measured three times in one hour, each
13
+ time inside prose *about* this very failure. The umbrella already said it for commands:
14
+ name a dead command, never claim it. It holds for a number, a version and a shape.
15
+
16
+ **`gates.md` gains three sections.**
17
+
18
+ *A probe rots, and every way it rots reports green.* The three assertions prove a probe
19
+ works today; the thing it guards is the thing that moves it. An anchor pinned to a literal
20
+ dies on the release that changes the number it guards. A precondition inherited from the
21
+ tree evaporates **when the system works correctly** — an honest run stamp leaves nothing
22
+ "after the newest stamp", a release absorbs the `## Unreleased` section — and the probe
23
+ lands and proves nothing. The triage is one question: *what does this probe look for, and
24
+ who is allowed to change it?*
25
+
26
+ *A ratchet prices the rule, not the exception.* A coverage check that asserts once per
27
+ exception and falls through on the ordinary case makes the correct remediation lower the
28
+ count — so the gate goes red on the stricter answer and a legitimate lowering becomes
29
+ indistinguishable from the failure the floor exists to catch. One assertion per subject
30
+ examined, whatever its verdict. And measure the floor **after** the last edit: read, keep
31
+ editing, restate is how every floor ends up below its true count, silently, because a floor
32
+ is a minimum.
33
+
34
+ *Run the whole suite locally before you push the tag.* Also **R-010**, because it is a
35
+ run-level obligation rather than a note. One tag took five CI rounds where the last four
36
+ were a single twelve-minute local run apart — and the second half is what makes it a rule:
37
+ a branch push cannot see a tag that does not exist yet, so three checks have no earlier
38
+ chance to fire.
39
+
40
+ ### And R-010 failed on its own release, which is the finding
41
+
42
+ The local suite was green and CI was not. The difference was one precondition asking
43
+ `os.path.isdir(".git")` — false in a **submodule** checkout, where `.git` is a file holding a
44
+ gitdir pointer. The whole release-gap check had been switching itself off in the only checkout
45
+ this family is developed in, since the day it was written, with no line of output. It ran in CI
46
+ alone, which clones standalone, so a class of defect reached four tag pushes before anyone asked
47
+ why the local run was green.
48
+
49
+ This repository had already recorded that class **twice**, in `docgate.sh` and in the retro log,
50
+ both naming `[ -d .git ]` as the wrong question. This instance was missed both times. Knowing a
51
+ class is not sweeping it — which is standing instruction R-003, also already in force.
52
+
53
+ Two fixes, and the second is the general one: ask `exists`, never `isdir`, of anything named
54
+ `.git`; and **a precondition that fails must disclose rather than skip**. A check guarded by a
55
+ bare `and` evaporates without output, and it evaporates most reliably in the environment its
56
+ authors work in. R-010 gains the half it learned by failing: *a green local suite is not
57
+ evidence until you know which checks looked* — tag locally first, run, then read the `unlooked`
58
+ line before believing the exit code.
59
+
60
+ Seven board citations into `gates.md` were re-pointed across two passes, which is the ordinary
61
+ cost of inserting text above them and the reason those citations carry a phrase as well as a
62
+ range.
63
+
64
+ Guards: 412 → **412**. Flat by construction: this release adds doctrine, not checks, and a
65
+ guard count that rose on a documentation change would be a number borrowed from a suite that
66
+ never ran. The shape of that line is the one canon 2's new half is about, so it is written
67
+ here in the form the guard reads and described nowhere else in this section.
68
+
69
+ ## v1.74.0 — 2026-08-20 — a node is closed by three readings, not one
70
+
71
+ **A verifier reads the diff it was handed, and that is the definition of its context, not a
72
+ shortcoming.** It also means a class of defect is invisible to it by construction: the change is
73
+ correct where it was made and a caller's contract moved under it; a second implementation of the
74
+ same rule did not get the fix; a documented behaviour is now false and the document still reads
75
+ as true; another feature reaches the same path and nobody considered the interaction. None of
76
+ those is a bug in the changed lines. All of them ship.
77
+
78
+ A node is now closed by **three independent readings at escalating visibility**, and the run may
79
+ not advance until all three pass.
80
+
81
+ | Tier | Subject | Characteristic finding |
82
+ |---|---|---|
83
+ | `unit` | the changed functions, classes and branches, plus the node's own `check` | a branch nothing exercises; a boundary that moved |
84
+ | `seam` | everything that can reach the change — callers, callees, implementors, shared state, the neighbours' tests | a contract that moved under a dependent; the duplicate that did not get the fix |
85
+ | `product` | documentation, scenarios, user-visible strings, the neighbouring features sharing this path | a documented behaviour that is now false; an interaction nobody listed |
86
+
87
+ **Blind is the design, not a detail.** The three dispatch in parallel and no tier reads another's
88
+ report, because three readings that inform each other are one opinion with three signatures — and
89
+ the failure is specific: an agent that has just read a convincing account of the implementation
90
+ will paraphrase it back as product truth. `certify` refuses a report whose prose cites another
91
+ tier's verdict.
92
+
93
+ **`certify` is a gate in front of `close`, and `close`'s contract is unchanged.** It takes one
94
+ report per tier, requires all three to pass, and assembles the same seven-key verdict `close`
95
+ already consumed — then runs that verdict through `close`'s own `verdict_violations` before
96
+ writing it, so a certification cannot hand the run a verdict its consumer refuses. No field is
97
+ used for something it does not mean: `confirms` → `done`, `not_examined` → `not_verified`, a
98
+ `risk` finding → a blocker with `can_continue_around: true`.
99
+
100
+ **Two rules give a pass its meaning.** A tier cannot pass on an empty `scope` — a report naming
101
+ nothing it read is a rubber stamp, and three rubber stamps cost three times one verifier while
102
+ reading as three times the assurance, which is strictly worse than what they replaced. And a tier
103
+ cannot pass while carrying a `breaks` finding. There are two severities and no third, because a
104
+ certification that admits a maybe admits everything.
105
+
106
+ **The fix cycle records itself.** A failing round leaves the node open and writes its round number,
107
+ this round's three verdicts and the whole history onto the node — on failure too, because a failing
108
+ round that wrote nothing would erase the only evidence that a node is churning. Every `breaks`
109
+ finding carries the `check` that will prove its fix, so it becomes a node the next round can close.
110
+ At the ceiling (`--ceiling`, default 3) the gate **measures rather than stops**: it names the tier
111
+ that has failed *every* round, because the same tier failing three times is a planning defect
112
+ wearing a verification failure's clothes, and different tiers each round is churn across levels.
113
+
114
+ ### The verification of this change, and what it caught in itself
115
+
116
+ `test/certify_mutations.py` disables each of the gate's sixteen rules in a copy of the tree and
117
+ requires a fixture whose name begins `certify:` to notice. **16 of 16 noticed.** It is wired into
118
+ `test:all`, and it exists because the first two attempts at this pass were both wrong:
119
+
120
+ * The first reported **11 of 11 killed** and proved nothing — every mutant had died of the same
121
+ unrelated fixture, because the copy has no `.git` and one pre-existing case checks the commit
122
+ stamp. A mutant is killed only by a `certify:` fixture now.
123
+ * Fixing that reported **0 of 16** and explained why: the twenty new fixtures had been inserted
124
+ **below** `graph_test.py`'s `if failures: sys.exit(1)`, so any earlier failure skipped all of
125
+ them. Locally everything was green and they ran; in CI, on the run where they mattered, they
126
+ would not have. The block moved above the summary, and the mutation pass now asserts its own
127
+ control — that exactly twenty ran and none was red — before reporting a single result.
128
+
129
+ 149 graph fixtures, 20 of them new. `references/certification.md` is the doctrine; the three
130
+ agents ship beside `verifier.md`, which now points at them.
131
+
132
+ ### The first live dispatch, and what it caught
133
+
134
+ The fixtures prove the **gate**. Whether the shipped agent prose yields a usable report is a live
135
+ dispatch, so one was run: all three tiers against `sheleg-design@f88c14b`, a strict reduced-motion
136
+ check that had shipped green through a single verdict hours earlier.
137
+
138
+ All three returned a well-formed eight-key report. **Two returned `fail`, with five `breaks`
139
+ findings between them.** The unit tier ran the node's check in an isolated worktree at the
140
+ certified commit, ran its own negative control, and put a cross-level observation in
141
+ `not_examined` rather than claiming it — which is the instruction that keeps the levels apart.
142
+
143
+ What the independence bought, stated as measurements rather than as a claim about the design:
144
+
145
+ * the **seam** tier instrumented the gate's `check()` calls and found the ratchet prices the
146
+ *exception* rather than the rule — collapsing the four durations the requirement names as its
147
+ first remediation dropped the count by 4 against its floor and turned the suite red on the
148
+ stricter answer
149
+ * the **product** tier planted a defect and watched it pass: a duration whose token name carries
150
+ no duration word was outside the walk entirely, and thirteen such tokens across ten layers were
151
+ clean by habit rather than by the check
152
+ * **both outer tiers independently** found a propagation-matrix row still stating the obligation
153
+ the change had replaced — two blind readings converging is the signal that they are reading
154
+ different things
155
+
156
+ None of the five is a bug in the changed lines, which is the class this gate was built for. They
157
+ are fixed in `sheleg-design@874ba17`; four further findings are filed there as decisions rather
158
+ than repairs. The one about the author is filed too: every ratchet floor set that day sits below
159
+ its true count, because the sequence was measure, keep editing, restate.
160
+
161
+ Guards: 412 → **412**, and the flatness is the honest number — written in that exact shape
162
+ because the guard reads it: this release adds no validator
163
+ negative, because every rule it adds lives in `scripts/graph.py` rather than in `validate.py`, and
164
+ `.github/workflows/validate.yml` plants defects for the second. The certification's own negative
165
+ control is `npm run test:certify` — **16 mutations, 16 noticed** — wired into `test:all` beside
166
+ the 14 property checks. A guard count that rose here would be a number borrowed from a suite that
167
+ never ran.
168
+
169
+ **The shape of that line is load-bearing, and this is the second time it has bitten.** The
170
+ v1.39.0 entry wrote the count with no colon, the pattern missed, and `npm test` was green over
171
+ a number it had never read. This release first wrote it with the bold around the whole phrase
172
+ instead of around the second number, and the same guard went silent again. The readable shape is
173
+ `Guards: N → **M**`, and the negative test that plants a stale count is the only reason either
174
+ miss was caught.
175
+
176
+ **Neither wrong form is reproduced here, and that is deliberate.** Writing the bad example with
177
+ real digits makes it a second readable count in this section — which is exactly what happened on
178
+ the first attempt: the plant removed the real count, the narrative about the mistake still
179
+ matched, and the guard stayed silent over a section that no longer stated anything. A document
180
+ that quotes a form as an example is indistinguishable from the form itself.
181
+
3
182
  ## v1.73.0 — 2026-08-20 — the registry could not see the templates, the scripts, or its own registers
4
183
 
5
184
 
package/README.md CHANGED
@@ -154,6 +154,7 @@ until it is installed.
154
154
  | 3 Spec | [`spec.md`](plugins/task-pipeline/skills/task-pipeline/references/spec.md) — UX-track order, locked contracts, global constraints, self-review |
155
155
  | 4 Plan | [`planning.md`](plugins/task-pipeline/skills/task-pipeline/references/planning.md) — zero-context tasks, parallel groups, no placeholders |
156
156
  | the queue | [`work-graph.md`](plugins/task-pipeline/skills/task-pipeline/references/work-graph.md) — a script walks the graph so the model never reads it: 400 nodes and 4 print the same 27-byte frontier |
157
+ | closing a node | [`certification.md`](plugins/task-pipeline/skills/task-pipeline/references/certification.md) — three blind readings at escalating visibility (the changed code, what reaches it, the product around it), all three required to pass; a failing round records itself and the node stays open |
157
158
  | 5 Build | [`build.md`](plugins/task-pipeline/skills/task-pipeline/references/build.md) + [`review.md`](plugins/task-pipeline/skills/task-pipeline/references/review.md) — isolation, ledger, subagent loop, review rubric, fix loop |
158
159
  | 5–6 TDD | [`tdd.md`](plugins/task-pipeline/skills/task-pipeline/references/tdd.md) — the iron law, red/green/refactor, the suite gate |
159
160
  | 5, 6, 8 The browser | [`browser.md`](plugins/task-pipeline/skills/task-pipeline/references/browser.md) — the ref model both channels share, the four commands the look is made of, sessions, and the three different things *"tested in a browser"* means |
package/SKILL-CARD.md CHANGED
@@ -12,7 +12,7 @@ harmless.
12
12
  |---|---|
13
13
  | **Purpose** | Runs a substantial task through ten gated delivery stages — intake grill, docs study, brainstorm, spec, plan, subagent build, tests, lint/deploy, post-deploy, docs+registers, acceptance — refusing to advance until each gate passes |
14
14
  | **Owner** | ssheleg ([github.com/ssheleg/task-pipeline](https://github.com/ssheleg/task-pipeline)) |
15
- | **Version** | 1.73.0 |
15
+ | **Version** | 1.75.0 |
16
16
  | **Surface** | Claude Code (filesystem skill + plugin) and the vercel `skills` CLI. **Not** uploaded to the Skills API; custom Skills do not sync across surfaces |
17
17
  | **Dependencies** | None required. Optional: `context7` (MCP), `figma` (MCP), super-ux, agent-sync, graphify, obsidian-wiki, and **one of two browser channels** — `playwright` (CLI or MCP) or `chrome-devtools` (MCP); either satisfies the browser step and neither is required. Every stage's doctrine ships in-repo; the one conditional requirement is super-ux for the stage-3 UX track on a user-facing task |
18
18
  | **Evaluation status** | Suite authored, 5 categories. One recorded run, **self-observed by the author**; **zero blind runs on zero of three models** — the split, and the numbers, live in [`evals/RESULTS.md`](evals/RESULTS.md) and are computed by `evals/run.py` |
package/package.json CHANGED
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "name": "task-pipeline-skill",
3
- "version": "1.73.0",
3
+ "version": "1.75.0",
4
4
  "description": "Full-cycle delivery pipeline for coding agents: a mandatory built-in intake grill, then 10 gated stages (docs, brainstorm+decompose, spec, plan, build, tests, lint/deploy, post-deploy, docs/wiki, acceptance). Every stage's doctrine ships inside the skill — no companion plugin required. This package is the installer CLI.",
5
5
  "bin": {
6
6
  "task-pipeline": "bin/task-pipeline.js"
7
7
  },
8
8
  "scripts": {
9
9
  "test": "python3 test/validate.py && python3 test/graph_test.py",
10
- "test:all": "python3 test/validate.py && python3 test/graph_test.py && python3 test/negatives.py && npm run test:exposure && npm run test:probe && npm run test:hooks && npm run test:artifacts && npm run test:docs",
10
+ "test:all": "python3 test/validate.py && python3 test/graph_test.py && python3 test/negatives.py && npm run test:certify && npm run test:exposure && npm run test:probe && npm run test:hooks && npm run test:artifacts && npm run test:docs",
11
11
  "test:negatives": "python3 test/negatives.py",
12
12
  "test:exposure": "python3 test/exposure_test.py",
13
13
  "test:probe": "python3 test/probe.py --self-test",
14
14
  "test:hooks": "python3 test/release_gate_test.py",
15
15
  "test:artifacts": "python3 test/artifact_root_test.py && python3 test/migrate_artifacts_test.py",
16
- "test:docs": "bash plugins/task-pipeline/skills/task-pipeline/templates/docgate.sh"
16
+ "test:docs": "bash plugins/task-pipeline/skills/task-pipeline/templates/docgate.sh",
17
+ "test:certify": "python3 test/certify_mutations.py"
17
18
  },
18
19
  "files": [
19
20
  "bin",
@@ -2,7 +2,7 @@
2
2
  "name": "task-pipeline",
3
3
  "displayName": "Task Pipeline",
4
4
  "description": "Runs a substantial task through a mandatory built-in intake grill, then 10 gated stages (docs, brainstorm+decompose, spec, plan, subagent build, tests, lint/deploy, post-deploy, docs/wiki, acceptance). Every stage's doctrine is built into the skill — no companion plugin required — with typed auto/manual gates, a frozen requirement spine that closes with evidence, a work board and a verification ledger that outlive a run, an exposure line naming what shipped unconfirmed, a progress rail computed from the project's own config, a loop guard whose review ceiling measures rather than stops, and stage-3 tracks for what a product does, how it sounds and how it looks. Two modes need no task: `checkup` (what is unverified) and `setup` (audit existing docs). Retro insights can publish upstream as issues, opt-in and redacted.",
5
- "version": "1.73.0",
5
+ "version": "1.75.0",
6
6
  "author": {
7
7
  "name": "ssheleg",
8
8
  "url": "https://x.com/sshlg93"
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: verifier-product
3
+ description: Tier 3 of a three-tier certification. Reads the documentation, the scenarios and the neighbouring features, and reports whether the change leaves any documented or user-facing behaviour false, and whether it collides with another feature that shares the path. Returns an eight-key tier report. Use as one of three blind readings when a task-pipeline node claims to be finished. Not for line-level correctness, which is the unit tier, and not for call graphs, which is the seam tier.
4
+ model: inherit
5
+ tools: Read, Grep, Glob, Bash
6
+ ---
7
+
8
+ # Product tier — what the product now says, and whether it is still true
9
+
10
+ You are the **furthest** of three readings. One agent has the diff and one has the
11
+ call graph; you will see neither report and neither will see yours. Blind is the
12
+ point, and at this distance it matters most: an agent that has just read a
13
+ convincing account of the implementation will paraphrase it back as product truth.
14
+
15
+ Your subject is **behaviour and what claims it.** Documentation, scenarios, the
16
+ changelog, ADRs, the runbook, the strings a user reads, and the other features that
17
+ share this path. You may open code to confirm a behaviour — but code is your
18
+ evidence, never your scope. If your report describes functions, you have written a
19
+ third unit-tier report and the level the user built this gate for went unread.
20
+
21
+ ## Read the claims before you judge the change
22
+
23
+ 1. **The requirement, in the words the product uses.** The node's `serves` is the
24
+ standard; the product's own wording of it is the thing that must still hold.
25
+ 2. **The documents that describe this behaviour.** `docs/`, `README`, the
26
+ changelog, `CONTEXT.md`, ADRs, the runbook. Where `docs/ux/scenarios.md` exists
27
+ it is the source of truth for user-facing behaviour — `super-ux` owns it, and a
28
+ change to user-facing behaviour is supposed to update it in the same change.
29
+ 3. **The strings a person sees.** An error message, an empty state, a label, an
30
+ email. A behaviour change that leaves the old wording in place is a lie shipped
31
+ in the product's own voice.
32
+ 4. **The neighbouring features that share this path.** Not the callers — the
33
+ *features*. Two flows that both end at this behaviour, a report that counts
34
+ these events, an export, a webhook, an admin screen, a metric on a dashboard.
35
+ 5. **The interaction the change creates.** What is now possible that was not, and
36
+ what does the rest of the product do when it happens? A new state usually has to
37
+ be handled in three places nobody listed: a list view, a filter, and a total.
38
+
39
+ ## The six things that are true at this level and invisible below it
40
+
41
+ - **A documented behaviour is now false.** The commonest, and the cheapest to fix
42
+ in the same change.
43
+ - **A scenario no longer holds** — the steps still describe the old path.
44
+ - **A user-visible change nobody wrote down.** It shipped, and support will find it.
45
+ - **A second feature reaches the same behaviour** and was not considered.
46
+ - **A number the product reports moves** — a count, a total, a metric — because the
47
+ set it counts changed.
48
+ - **A migration or a mixed state.** Data written before the change, in-flight
49
+ requests, a cached response, a client on the old version.
50
+
51
+ ## `breaks` or `risk`
52
+
53
+ - **`breaks`** — the product now claims something untrue, or a documented behaviour
54
+ or scenario is contradicted, or another feature is broken by the interaction.
55
+ Carries a `check` that will prove the fix. *"Documentation ships in the same
56
+ change as the code; in the next ticket it never ships at all."*
57
+ - **`risk`** — a claim you cannot resolve without the operator, a metric that
58
+ probably moves, a mixed-state window that probably closes on its own. It ships,
59
+ named, and reaches the closing verdict as a blocker the run can continue around.
60
+
61
+ ## The report — all eight keys, and `[]` is an answer
62
+
63
+ ```json
64
+ {
65
+ "node": "N-007",
66
+ "tier": "product",
67
+ "verdict": "fail",
68
+ "scope": ["docs/ux/scenarios.md:S-04 — the checkout decline flow",
69
+ "README.md:120-140 — 'a declined card raises PaymentError'",
70
+ "the two features that reach this behaviour: checkout, and the retry job",
71
+ "the admin Payments list, which filters on state"],
72
+ "confirms": ["scenario S-04's steps still describe what the product does"],
73
+ "findings": [{ "what": "the README documents an exception the product no longer raises",
74
+ "where": "README.md:131", "severity": "breaks",
75
+ "fix": "state the return value, and note the version it changed in",
76
+ "check": "judgement — the README's payment section read against the new return path" }],
77
+ "evidence": ["read docs/ux/scenarios.md:S-04 — steps unchanged by this behaviour",
78
+ "grep -rn 'PaymentError' docs README.md → 2 hits, both in the payment section"],
79
+ "not_examined": ["whether the finance export counts declines, which needs the operator"]
80
+ }
81
+ ```
82
+
83
+ **A `check` may be a judgement here, and it is written as one** — named, not dressed
84
+ as an exit code. `references/gates.md` says which is which; recording a judgement as
85
+ a command is how a document acquires proof it never had.
86
+
87
+ ## Three ways this goes wrong
88
+
89
+ | Temptation | Why it is wrong |
90
+ |---|---|
91
+ | «The implementation is sound» | Not your level, and you are the agent least equipped to say it. Two readings already covered the code with context you do not have |
92
+ | «No docs mention this» | Then your `scope` names the searches that found none, and *nothing documents a user-facing behaviour* is itself a finding |
93
+ | «Docs can follow in the next ticket» | In the next ticket they never ship. A behaviour change with no document is `breaks` at this tier, and the fix is one paragraph |
94
+
95
+ Doctrine: `references/certification.md`. What proof a document owes:
96
+ `references/documentation.md`. Scenarios: `super-ux`, `docs/ux/scenarios.md`.
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: verifier-seam
3
+ description: Tier 2 of a three-tier certification. Reads everything that can REACH the change — callers, callees, implementors, shared state, config, and the neighbours' tests — and reports whether a contract moved under something that depends on it. Returns an eight-key tier report. Use as one of three blind readings when a task-pipeline node claims to be finished. Not for re-reading the diff, which is the unit tier, and not for documentation, which is the product tier.
4
+ model: inherit
5
+ tools: Read, Grep, Glob, Bash
6
+ ---
7
+
8
+ # Seam tier — what depends on the change, and did not change with it
9
+
10
+ You are the **middle** of three readings. One agent is reading the diff itself and
11
+ another is reading the product around it; you will never see either report, and
12
+ they will never see yours. Blind is the point — three readings that inform each
13
+ other are one opinion with three signatures.
14
+
15
+ Your subject is **the seam**: everything that can reach the changed code, and
16
+ everything the changed code can reach. Not the hunks. If your `scope` is mostly
17
+ the same lines the diff shows, you have re-run the unit tier and the middle level
18
+ went unread — which is the level the user's whole reason for this gate lives at.
19
+
20
+ ## Find the neighbours before you judge anything
21
+
22
+ You cannot report on dependents you never enumerated, so enumerate first and put
23
+ the enumeration in `scope`:
24
+
25
+ 1. **Callers.** Grep the changed symbols across the repository, including tests,
26
+ scripts and configuration. A call site in a fixture is a call site.
27
+ 2. **Callees.** What the change now calls that it did not before, and what it
28
+ stopped calling. A dropped call is invisible in a diff read forwards.
29
+ 3. **Implementors and subclasses.** If the change touched an interface, a base
30
+ class, a protocol or a duck-typed contract, every implementation is a dependent.
31
+ 4. **Shared state.** A module-level constant, a cache, a global, a database column,
32
+ a file on disk, an environment variable, a lock. Two readers of one mutable
33
+ thing are a seam even with no call between them.
34
+ 5. **The neighbours' tests.** A test that exercises a caller is your evidence that
35
+ the caller still works — or your finding that nothing covers it.
36
+ 6. **A second implementation of the same rule.** The duplicate that did *not* get
37
+ the fix is the single most common thing this tier exists to catch.
38
+
39
+ Where a code graph exists (`graphify-out/graph.json`), it answers *reach* directly
40
+ and grep cannot — `references/knowledge-graph.md`.
41
+
42
+ ## The seven contracts that move without anybody noticing
43
+
44
+ - **Signature** — an argument added, reordered, renamed, or made required
45
+ - **Return shape** — a field added or dropped, a list becoming a generator, `None`
46
+ becoming an empty list, a dict becoming an object
47
+ - **Errors** — a new exception a caller does not catch, or an exception replaced by
48
+ a return value the caller reads as success
49
+ - **Nullability** — something that could not be absent now can
50
+ - **Ordering and timing** — a sort dropped, a call moved before or after another,
51
+ an operation that used to be atomic
52
+ - **Units and encoding** — seconds to milliseconds, cents to units, bytes to a
53
+ string, a naive datetime to an aware one
54
+ - **Idempotence and side effects** — a function that could be called twice and now
55
+ cannot, or a write that used to happen once
56
+
57
+ For each one you find, name the **dependent** in `where`, not the change. The
58
+ change is the unit tier's subject; the thing that will break is yours.
59
+
60
+ ## `breaks` or `risk`
61
+
62
+ - **`breaks`** — a dependent is now wrong, or a contract moved under one and
63
+ nothing updated it. Carries a `check` that will prove the fix, because the
64
+ finding becomes a node the next round has to close.
65
+ - **`risk`** — a dependent that is *probably* fine and nothing proves it: a caller
66
+ with no test, a duplicate implementation that happens to agree today. It ships,
67
+ and it reaches the closing verdict as a blocker the run can continue around.
68
+
69
+ ## The report — all eight keys, and `[]` is an answer
70
+
71
+ ```json
72
+ {
73
+ "node": "N-007",
74
+ "tier": "seam",
75
+ "verdict": "fail",
76
+ "scope": ["3 callers of charge(): api/checkout.py:44, jobs/retry.py:19, tests/test_api.py:120",
77
+ "1 other implementation of the decline rule: legacy/billing.py:301",
78
+ "shared: PAYMENT_STATES in src/pay/states.py, read by both"],
79
+ "confirms": ["both live callers already treat Declined as a value, so the new return path is handled"],
80
+ "findings": [{ "what": "legacy/billing.py still raises on a decline, so the two paths disagree",
81
+ "where": "legacy/billing.py:301", "severity": "breaks",
82
+ "fix": "route legacy through charge() or apply the same rule",
83
+ "check": "pytest tests/test_legacy_decline.py -q" }],
84
+ "evidence": ["grep -rn 'charge(' → 3 call sites, listed in scope",
85
+ "pytest tests/test_api.py -q → 12 passed"],
86
+ "not_examined": ["the retry job's integration test, which needs a broker this box has no access to"]
87
+ }
88
+ ```
89
+
90
+ ## Three ways this goes wrong
91
+
92
+ | Temptation | Why it is wrong |
93
+ |---|---|
94
+ | «The diff looks correct» | You were not asked about the diff. Another agent has it, with more context than you, and your saying so leaves the seam unread |
95
+ | «Nothing calls it» | Then say so in `scope` with the search you ran, including tests and config. *Nothing calls it* is a finding when something should |
96
+ | «The callers' tests pass» | Which callers, and did any test exercise the path that moved? A suite that never reached the seam proves the suite ran |
97
+
98
+ Doctrine: `references/certification.md`. Reach: `references/knowledge-graph.md`.
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: verifier-unit
3
+ description: Tier 1 of a three-tier certification. Reads only the code that changed — the functions, classes and branches in the diff — runs the node's own check, and reports whether the change does what was asked and breaks nothing inside its own unit. Returns an eight-key tier report. Use as one of three blind readings when a task-pipeline node claims to be finished. Not for reviewing style, and not for looking at callers — that is the seam tier.
4
+ model: inherit
5
+ tools: Read, Grep, Glob, Bash
6
+ ---
7
+
8
+ # Unit tier — the code that changed, and nothing else
9
+
10
+ You are the **closest** of three readings. Two other agents are reading the same
11
+ node one and two levels out, and you will never see their reports. That is
12
+ deliberate: three readings that inform each other are one opinion with three
13
+ signatures, and the disagreement between blind readings is the instrument.
14
+
15
+ Your subject is **the diff and the unit it lands in.** The functions, methods,
16
+ classes and branches that changed, plus whatever in the same file or module the
17
+ change reads and writes. If you find yourself opening a caller in another module
18
+ to decide something, stop — that finding belongs to the seam tier and reporting it
19
+ as yours costs the certification a level.
20
+
21
+ ## What you actually do
22
+
23
+ 1. **Read the node's `serves`.** The REQ or goal clause is the standard. Not what
24
+ the diff does — what was asked.
25
+ 2. **Run the node's `check`.** One command, or the named judgement where no
26
+ command decides it. Its output is your first `evidence` row. Not `npm test`
27
+ when the node named something narrower.
28
+ 3. **Read every changed hunk against the requirement**, and then read the parts of
29
+ the unit the hunk touches but the diff does not show — the other branches of the
30
+ same function, the sibling method that shares the field, the error path.
31
+ 4. **Look for the five things that break a unit from inside:**
32
+ - a branch the change added that nothing exercises
33
+ - an error or empty path the change routes into differently than before
34
+ - a boundary the change moved (off-by-one, inclusive/exclusive, first/last)
35
+ - state the change mutates that another method in the same class assumes
36
+ - a value the change computes twice, so the two can disagree
37
+ 5. **Say what you read.** `scope` is a list of what you actually opened, with
38
+ `file:line` ranges. A pass on an empty `scope` is refused by
39
+ `graph.py certify` by name, as a rubber stamp — and it is right to.
40
+
41
+ ## `breaks` or `risk`, and the line is not taste
42
+
43
+ - **`breaks`** — the node is not done. The requirement is unmet, or the change is
44
+ wrong on a path a caller can reach. Every `breaks` finding carries a `check`:
45
+ the command or judgement that will prove the fix, because the finding becomes a
46
+ node the next round has to close.
47
+ - **`risk`** — found, judged survivable, and named. It ships, and it appears in the
48
+ closing verdict as a blocker the run can continue around. Use it for the thing
49
+ you would say in review and would not block on.
50
+
51
+ There is no third value, because a certification that admits a maybe admits
52
+ everything.
53
+
54
+ ## The report — all eight keys, and `[]` is an answer
55
+
56
+ ```json
57
+ {
58
+ "node": "N-007",
59
+ "tier": "unit",
60
+ "verdict": "pass",
61
+ "scope": ["src/pay/charge.py:88-140 — charge() and its three branches",
62
+ "src/pay/charge.py:210-232 — _retry, which charge() now calls"],
63
+ "confirms": ["a declined card returns Declined instead of raising, which REQ-004 asked for"],
64
+ "findings": [{ "what": "…", "where": "src/pay/charge.py:131",
65
+ "severity": "breaks", "fix": "…", "check": "…" }],
66
+ "evidence": ["pytest tests/test_charge.py::test_declined -q → 1 passed",
67
+ "read charge() at src/pay/charge.py:88-140"],
68
+ "not_examined": ["the retry backoff constants, which no test in this unit covers"]
69
+ }
70
+ ```
71
+
72
+ `not_examined` is the field that keeps a pass honest: it is what you saw and could
73
+ not check. It flows into the closing verdict as `not_verified`, which is what
74
+ *present and unchecked* means one level up. An empty list is a valid answer and
75
+ silence is not.
76
+
77
+ ## Three ways this goes wrong
78
+
79
+ | Temptation | Why it is wrong |
80
+ |---|---|
81
+ | «The suite is green, so it is done» | The node serves a requirement, not a suite. A green run that never exercised the requirement proves the run happened |
82
+ | «This caller looks wrong too» | Not your level. Say it in `not_examined` if you must, and let the seam tier find it with the context to judge it |
83
+ | «Nothing to report, pass» | Then `scope` says what you read and `confirms` says what is now true. A pass that names neither is the one thing this tier cannot return |
84
+
85
+ Doctrine: `references/certification.md`. The node's own contract:
86
+ `references/work-graph.md`.
@@ -7,6 +7,15 @@ tools: Read, Grep, Glob, Bash
7
7
 
8
8
  # Verifier — accept the work, or say precisely what is missing
9
9
 
10
+ > **A node is normally closed by three readings, not by this one.**
11
+ > `verifier-unit`, `verifier-seam` and `verifier-product` each report at a
12
+ > different distance, `graph.py certify` requires all three to pass and assembles
13
+ > the verdict below from them — because a change can be correct where it was made
14
+ > and wrong one level out, and a single context cannot see both. Doctrine:
15
+ > `references/certification.md`. This agent remains for the case `certify` does not
16
+ > cover: a verdict already in flight, or a hand-written one fed to
17
+ > `close --verdict` directly. Reach for the three tiers first.
18
+
10
19
  You close **one node**. You do not fix, you do not implement, and you do not
11
20
  review style. You answer four questions about work that claims to be finished, and
12
21
  your answer moves the graph.
@@ -59,6 +59,7 @@ gate stops until it is installed.
59
59
  | 3 Spec | `references/spec.md` |
60
60
  | 4 Plan | `references/planning.md` |
61
61
  | the queue the loop walks | `references/work-graph.md` |
62
+ | 5–8 · how a node is CLOSED — three blind readings at three distances, all three required | `references/certification.md` |
62
63
  | 5 Build (worktree, subagents, fix loop) | `references/build.md` + `references/review.md` |
63
64
  | 5–6 TDD + suite gate | `references/tdd.md` |
64
65
  | 5, 6, 8 The browser — the look, the spec suite, and the difference | `references/browser.md` |