task-pipeline-skill 1.10.3 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/CONTRIBUTING.md +35 -1
  3. package/README.md +2 -1
  4. package/SKILL-CARD.md +3 -3
  5. package/cursor/rules/task-pipeline.mdc +50 -1
  6. package/evals/RESULTS.md +4 -1
  7. package/package.json +1 -1
  8. package/plugins/task-pipeline/.claude-plugin/plugin.json +1 -1
  9. package/plugins/task-pipeline/skills/task-pipeline/SKILL.md +11 -1
  10. package/plugins/task-pipeline/skills/task-pipeline/pipeline.example.json +4 -0
  11. package/plugins/task-pipeline/skills/task-pipeline/pipeline.schema.json +30 -1
  12. package/plugins/task-pipeline/skills/task-pipeline/references/artifacts.md +1 -0
  13. package/plugins/task-pipeline/skills/task-pipeline/references/build.md +34 -1
  14. package/plugins/task-pipeline/skills/task-pipeline/references/continuity.md +214 -0
  15. package/plugins/task-pipeline/skills/task-pipeline/references/grill.md +1 -0
  16. package/plugins/task-pipeline/skills/task-pipeline/references/portability.md +3 -0
  17. package/plugins/task-pipeline/skills/task-pipeline/references/stages.md +13 -3
  18. package/plugins/task-pipeline/skills/task-pipeline/templates/README.md +1 -0
  19. package/plugins/task-pipeline/skills/task-pipeline/templates/adr.md +1 -1
  20. package/plugins/task-pipeline/skills/task-pipeline/templates/brief.md +1 -0
  21. package/plugins/task-pipeline/skills/task-pipeline/templates/carryover.md +1 -1
  22. package/plugins/task-pipeline/skills/task-pipeline/templates/hygiene.sh +236 -0
  23. package/plugins/task-pipeline/skills/task-pipeline/templates/retro.md +7 -1
  24. package/plugins/task-pipeline/skills/task-pipeline/templates/routing-rule.md +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,119 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.12.0 — 2026-08-05
4
+
5
+ ### Added — a gate for the defects an agent leaves behind
6
+
7
+ Every other check in this repository asks whether the work is *right*. None of
8
+ them asked whether the text is *intact* — whether a merge was left half-resolved,
9
+ a stub outlived its task, a generation stopped mid-fence, a file was "shortened"
10
+ while being rewritten, a retried edit duplicated a block instead of replacing it,
11
+ or a section was opened and abandoned. That class has an author, and the author is
12
+ an agent.
13
+
14
+ `templates/hygiene.sh` is the answer: a seeded, executable gate, sibling of
15
+ `docgate.sh`, carrying the same contracts — a `SCOPE:` header that names its own
16
+ false-positive surfaces, a VERDICT block that must be last, portability to macOS
17
+ bash 3.2, progressive arming, and a final line of computed numbers. It runs in two
18
+ modes: the run's diff at **zero tolerance**, and the whole tree behind per-check
19
+ **ratchet floors**, so an existing repository can adopt it without starting red
20
+ while nothing new is forgiven.
21
+
22
+ **It never edits.** None of the six defects is safely machine-fixable — deleting a
23
+ "duplicated block" sometimes deletes a legitimate repetition, and deleting a `TODO`
24
+ erases a reminder instead of discharging it. `references/build.md` makes fixing the
25
+ agent's obligation, at the point it is cheapest: after each task reports `DONE`,
26
+ before the reviewer sees it. Found one task later, a defect costs a re-dispatch;
27
+ found eight tasks later it is fixed by an agent that no longer remembers the code.
28
+
29
+ **Two of the six definitions were wrong until they were measured**, which is the
30
+ point of the rule that says measure a detector before shipping it. Matching the
31
+ word `TODO` anywhere found 33 hits here, 28 of them ordinary English — this is a
32
+ repository whose own doctrine is *"a ratchet, never a TODO"*. Anchored as a
33
+ line-leading marker: 0. Flagging a heading followed by any heading found 62
34
+ legitimate nestings; restricted to same-or-higher level it found 4, and the
35
+ detector still had a bug — fenced lines were skipped entirely, so a section whose
36
+ body is one code block looked empty. Counting fenced content as body: 2, both real.
37
+ Check 4 then repeated check 2's mistake in its sibling and was caught the same way.
38
+
39
+ Those last 2 were fixed rather than absorbed into a floor, so all six ship at 0.
40
+ One of them was a shape mistake in the retro template every project copies: a
41
+ one-line retirement record written as a heading, when a heading promises a section.
42
+
43
+ ### Fixed — a guard that had been decorative since it shipped
44
+
45
+ Writing the first probe for the VERDICT-last contract revealed that the guard split
46
+ each gate script on the *word* `VERDICT`, which also appears in the header sentence
47
+ forbidding anything after it. Its "tail after the verdict" was therefore most of the
48
+ script, exit lines included, and it passed on anything. It now splits on the
49
+ `# ---------- VERDICT` marker and requires that marker to exist. A green from a
50
+ check nobody has watched fail is not evidence; this one was green for nine releases.
51
+
52
+ The template contract guards now iterate a `GATE_SCRIPTS` list instead of naming one
53
+ file, so a third gate costs one line rather than a copied block that drifts.
54
+
55
+ Negative self-tests: 68 → 75.
56
+
57
+ ## v1.11.0 — 2026-08-04
58
+
59
+ ### Added — run continuity: the pacing rules the operator was repeating by hand
60
+
61
+ Two rules that had to be said out loud at the start of every run. One of them was
62
+ already written down, which is the interesting half.
63
+
64
+ **The loop mode is now config, not a request.** `references/build.md` has carried
65
+ *"Continuous execution: don't check in between tasks"* since early on, and it did
66
+ not work — for two compounding reasons. It lived inside stage 5, so an agent
67
+ running any other stage never saw it; and on a harness where a turn ends, prose
68
+ cannot carry a run across the boundary at all. Only a scheduled re-invocation
69
+ can. So the decision moved to `pipeline.json` → `run.loop`, read at preflight in
70
+ the same block as the model: recorded means armed and never re-asked, absent means
71
+ **off**. `build.md`'s rule keeps its full force and gains one sentence naming its
72
+ scope, because the two govern different things and a reader was going to take one
73
+ for the other.
74
+
75
+ **The context rule fires on evidence or not at all.** The opposite failure was
76
+ live: runs announcing that context was nearly exhausted against a mostly-empty
77
+ window, because an estimate from transcript length was being presented as a
78
+ measurement. Nothing returns the remaining percentage, so the rule now admits
79
+ exactly two signals — the harness saying so, or the operator saying so — and
80
+ forbids the announcement without one. When it does fire: finish the item in
81
+ flight, start no new one, make the ledgers true, continue. Not stop.
82
+
83
+ There is deliberately **no config field** for the context half. The one number
84
+ anyone would put in it is the one that can never be honoured, and the schema's own
85
+ `description` says so — a blank space in a contract gets filled back in by the
86
+ next contributor as an oversight.
87
+
88
+ Both halves live in one new file, `references/continuity.md`, because they are one
89
+ mechanism: the loop is what makes the context rule necessary. Without a loop a run
90
+ stops at the turn boundary anyway; with one, it will start an item it cannot
91
+ finish.
92
+
93
+ ### Fixed — three templates whose links were broken everywhere they are read
94
+
95
+ `templates/adr.md`, `carryover.md` and `routing-rule.md` carried relative links
96
+ that resolved from `templates/` — where the files are stored — and from nowhere
97
+ they are ever actually seeded. `carryover.md` had shipped one since v1.1.0 and the
98
+ link checker was green the whole time, because it resolves from the file's home.
99
+ No run had seeded the template verbatim, so nobody hit it until one did.
100
+
101
+ The rule is now the one the Cursor rule already follows: a document that travels
102
+ is self-contained, and names files in code spans rather than linking to them.
103
+
104
+ ### Guards — 63 → 68
105
+
106
+ `run.loop.mode` must be set explicitly in the shipped example; five surfaces must
107
+ name `continuity.md`; the two load-bearing clauses in `continuity.md` must survive
108
+ an edit (matched after whitespace normalisation, since both wrap at 80 columns and
109
+ a line-oriented search would reject correct prose); and no seeded template may
110
+ carry a relative link. Four invariants added to `CONTRIBUTING.md`, each citing the
111
+ guard that enforces it.
112
+
113
+ `CLAUDE.md`'s claim of *sixteen* invariants — the list held twenty-four — was
114
+ deleted rather than corrected. A hand-written count drifts; that is what the rule
115
+ about computing numbers is for, and this file was breaking it.
116
+
3
117
  ## v1.10.3 — 2026-08-03
4
118
 
5
119
  ### Fixed — the cause behind five audits, rather than a sixth symptom
package/CONTRIBUTING.md CHANGED
@@ -117,6 +117,13 @@ found**, and run a minimum of live checks — because every section can go `dorm
117
117
  and a gate blind to a shape passes exactly like one that reads it. Change a template
118
118
  → run `npm test`, not just your eyes.
119
119
 
120
+ **The same law now covers `templates/hygiene.sh`.** It is run over a clean scratch
121
+ project, must exit `0`, and must report all six of its check counts — exit `0` alone
122
+ proves nothing, because every check can go `dormant` and dormant is green. Both gate
123
+ scripts are validated by one iterated block, not two copies, and both must keep a
124
+ `# ---------- VERDICT` marker with nothing after it.
125
+ *(guard: `the VERDICT block must be last and must `)*
126
+
120
127
  **11. Every reference over 100 lines carries a `## Contents` list**, and the list is
121
128
  compared against that file's own `##` headings. The guidance asks for it because a
122
129
  long file gets previewed with a partial read; the comparison is because a hand-kept
@@ -169,7 +176,34 @@ with the bundle instead of being hand-installed into an operator's config.
169
176
  reads those files, and a partial read shows whichever sections come first.
170
177
  *(guard: `needs the same partial-read protection references get`)*
171
178
 
172
- **24. Every invariant above names the guard that enforces it, and that guard exists.**
179
+ **24. Run-wide pacing is config, and the example demonstrates its default.** The
180
+ `run` block in `pipeline.schema.json` carries the loop mode; the shipped example
181
+ sets `run.loop.mode` explicitly rather than omitting it, because the example is
182
+ what gets copied and an absent field reads as an oversight instead of a decision.
183
+ *(guard: `no explicit run.loop.mode`)*
184
+
185
+ **25. The run-wide mode is named by every stage that could be misled by it.**
186
+ `SKILL.md`, `references/grill.md`, `references/build.md`, `references/stages.md`
187
+ and `templates/brief.md` each name `continuity.md`. The continuous-execution rule
188
+ sat inside `build.md` for nine releases and did not work, because an agent running
189
+ one stage never re-reads the orchestrator to discover a run-wide rule exists.
190
+ *(guard: `a run-wide rule no stage has heard of`)*
191
+
192
+ **26. `references/continuity.md` keeps its two load-bearing clauses.** One forbids
193
+ announcing that context is nearly spent without a harness signal; the other names
194
+ the harness limit on the loop primitive. Both are one edit away from softening into
195
+ nothing. The guard normalises whitespace first — the clauses wrap at 80 columns,
196
+ and a line-oriented search would reject correctly formatted prose.
197
+ *(guard: `missing the contractual clause`)*
198
+
199
+ **27. A seeded template carries no relative markdown links.** A template is copied
200
+ somewhere else by definition, so a link that resolves from `templates/` is broken
201
+ everywhere the file is actually read. `carryover.md` shipped one for nine minor
202
+ releases and the link checker stayed green throughout, because it resolves from the
203
+ file's home. Name the file in a code span instead — the same rule the Cursor rule
204
+ follows, for the same reason. *(guard: `resolves only from`)*
205
+
206
+ **28. Every invariant above names the guard that enforces it, and that guard exists.**
173
207
  This list claims to be *what the validator enforces*; it was eight guards behind when
174
208
  an audit measured it. A claim of enforcement is now checked like any other claim.
175
209
  *(guard: `whose message does not appear in`)* — and a cited literal must lie inside
package/README.md CHANGED
@@ -127,6 +127,7 @@ until it is installed.
127
127
  | 10 Acceptance | [`acceptance.md`](plugins/task-pipeline/skills/task-pipeline/references/acceptance.md) — REQ coverage table, evidence rules, the closing question |
128
128
  | 10 + any audit | [`audit.md`](plugins/task-pipeline/skills/task-pipeline/references/audit.md) — the L0→L7 ladder and its seams, axis rotation, ratchets, proven checks |
129
129
  | any loop | [`loop-guard.md`](plugins/task-pipeline/skills/task-pipeline/references/loop-guard.md) — churn detection, caps, the break protocol |
130
+ | run-wide pacing | [`continuity.md`](plugins/task-pipeline/skills/task-pipeline/references/continuity.md) — the loop mode (`run.loop`, default off, never collapses a manual gate) and the context budget (fires on a harness signal, never on an estimate) |
130
131
  | 0 + 9 + any settled decision | [`documentation.md`](plugins/task-pipeline/skills/task-pipeline/references/documentation.md) — the inventory, registers and ids, SSOT, the Doc Loop, supersede semantics, the propagation matrix, intent vs as-built |
131
132
  | 6–10 + any check you write | [`gates.md`](plugins/task-pipeline/skills/task-pipeline/references/gates.md) — the two axes, the promotion ladder, gate anatomy, the probe recipe, ratchet floors |
132
133
  | any agent-time enforcement | [`hooks.md`](plugins/task-pipeline/skills/task-pipeline/references/hooks.md) — the `PreToolUse` contract, the fail-open hazard, the Claude-Code-only limit |
@@ -775,7 +776,7 @@ recommendation, so you arm the whole run in one exchange. Detail:
775
776
  | [`references/knowledge-graph.md`](plugins/task-pipeline/skills/task-pipeline/references/knowledge-graph.md) | the code graph: install line, stage-0 reach queries, the stage-9 refresh, the graph↔docs divergence check |
776
777
  | [`references/retrospective.md`](plugins/task-pipeline/skills/task-pipeline/references/retrospective.md) | the project retro: the three grades of fix, the mandatory prune, the cap of ten |
777
778
  | [`references/model-tiering.md`](plugins/task-pipeline/skills/task-pipeline/references/model-tiering.md) | model policy, the `/model` reminder, overrides |
778
- | [`templates/`](plugins/task-pipeline/skills/task-pipeline/templates/README.md) | brief, carry-over ledger, `CONTEXT.md` and ADR skeletons, the doc map, both registers, the retro and its archive, the seeded `docgate.sh`, a worked hook |
779
+ | [`templates/`](plugins/task-pipeline/skills/task-pipeline/templates/README.md) | brief, carry-over ledger, `CONTEXT.md` and ADR skeletons, the doc map, both registers, the retro and its archive, the seeded `docgate.sh` and `hygiene.sh`, a worked hook |
779
780
  | [`references/adoption.md`](plugins/task-pipeline/skills/task-pipeline/references/adoption.md) | the first run in a project: greenfield seeding, and the brownfield walkthrough |
780
781
  | [`references/setup.md`](plugins/task-pipeline/skills/task-pipeline/references/setup.md) | the entry audit: seven passes over the docs a project already has, offered once, output as a fix plan |
781
782
  | [`references/portability.md`](plugins/task-pipeline/skills/task-pipeline/references/portability.md) | the manifest of workflow decisions and their homes in the bundle, and the boundary against a project's own answers |
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.10.3 |
15
+ | **Version** | 1.12.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. 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 (15 evals, 5 categories). **Never executed** — see [`evals/RESULTS.md`](evals/RESULTS.md) |
@@ -38,7 +38,7 @@ apply.
38
38
  instruction surface, and every one is linked directly from `SKILL.md`.
39
39
  2. Read `templates/docgate.sh` before seeding it; it is the only shipped script a
40
40
  host project will run on its own repository.
41
- 3. Run `npm run test:all` — 63 guards, each with a negative self-test that plants a
41
+ 3. Run `npm run test:all` — 76 guards, each with a negative self-test that plants a
42
42
  defect and requires rejection.
43
43
  4. Run `python3 evals/run.py` for the behavioural protocol, and read
44
44
  `evals/RESULTS.md` for what has actually been observed.
@@ -54,7 +54,7 @@ apply.
54
54
  - **Versions are pinned by git tag** and mirrored into `sshlg-skills`'s catalogue.
55
55
  Rollback is `git checkout v<previous>` or pinning the previous plugin version;
56
56
  the previous version is never deleted.
57
- - **Behavioural evidence is missing, not merely thin.** 63 structural guards prove
57
+ - **Behavioural evidence is missing, not merely thin.** 76 structural guards prove
58
58
  the skill is well-formed. Until `evals/RESULTS.md` carries a dated run, nothing in
59
59
  this repository proves it *behaves* — triggers correctly, stays quiet on a
60
60
  question, or performs the steps it documents.
@@ -24,6 +24,47 @@ you recommend and what they're on, let them confirm or override (per-stage
24
24
  overrides welcome), record the answer — then **stop asking at every stage**. If the
25
25
  recommended tier isn't available, say which one you're using and continue.
26
26
 
27
+ ## Pacing — the run mode, and the context budget
28
+
29
+ Two rules about how a run keeps going. Settle the first at stage 0, in the same
30
+ block as the model; the second is unconditional and needs no setting.
31
+
32
+ **The run mode.** On, the run advances one item per iteration and does not ask
33
+ between items — the plan's tasks, the boundary between stages whose gate is
34
+ automatic, the per-module loop, the acceptance tail. It stops for exactly four
35
+ things: a manual gate, a block it can't resolve, a genuine ambiguity, completion.
36
+
37
+ - **Off unless recorded.** Read the project's pipeline config → `run.loop`. A
38
+ recorded mode is the answer and is never re-asked; absent, the mode is **off**.
39
+ Silence arms nothing, the same floor deploy authorization uses.
40
+ - **It buys pacing, never authorization.** It never collapses a manual gate, and
41
+ never a deploy, publish, PR or shared-design-file write. A generic flag is not a
42
+ specific authorization.
43
+ - **Prose does not survive a turn boundary.** Where the harness offers a loop
44
+ primitive, arm it and say the interval, the job id and how to cancel; where it
45
+ does not, the mode degrades to discipline plus the build ledger — and you say
46
+ which of the two you are actually running rather than implying the stronger one.
47
+ - **Pick an interval longer than a typical item**, and one that divides its unit
48
+ cleanly. If the job auto-expires, say when: a loop that quietly stops is worse
49
+ than one never armed.
50
+ - **Parked at a manual gate, cancel the loop** and print the re-arm command beside
51
+ the gate. A loop firing into a gate every few minutes is a nag, and a nagged
52
+ operator stops reading.
53
+
54
+ **The context budget.** Near the end of the window: finish the item in flight,
55
+ **start no new one**, make the ledgers true, then continue. Compaction is a normal
56
+ event, not the end of the run — what loses work is crossing it mid-item.
57
+
58
+ - **It fires on evidence only:** a harness signal (a compaction warning, a
59
+ pre-compaction hook) or the user saying so. Nothing returns the remaining
60
+ percentage, so an estimate from transcript length is a guess — and announcing
61
+ that context is nearly spent without a signal is presenting a guess as a
62
+ measurement. A run that keeps volunteering it against a mostly-empty window
63
+ trains the user to disregard the one time it is true.
64
+ - **Flushing updates what already exists** — the build ledger, the carry-over
65
+ ledger, requirement statuses, the task list. Not a summary written for the
66
+ compactor: that is a fourth copy of the truth that nobody maintains.
67
+
27
68
  ## Stage 0 — intake grill (MANDATORY, do this FIRST)
28
69
 
29
70
  Never skipped, and nothing to install — the grill is part of this rule. No "the
@@ -166,7 +207,15 @@ not authorize an outward, irreversible action — stage 7 stops and asks.
166
207
  commit), then a review with **all three** verdicts — spec compliance, **REQ
167
208
  satisfied** (judged against the requirement's own statement, not the task's
168
209
  instructions), and code quality. Findings loop back to the implementer (max 5
169
- rounds, then adjudicate in writing); minor findings are recorded, never blocking. Keep a progress ledger in
210
+ rounds, then adjudicate in writing); minor findings are recorded, never blocking.
211
+ **After each task reports done, before the review, scan what that task changed
212
+ for the defects an agent leaves behind** — a half-resolved merge, a stub that
213
+ outlived the task, a code fence left open, a file "shortened" while being
214
+ rewritten, a block duplicated by a retried edit, a section opened and abandoned.
215
+ Zero tolerance on a diff: that task wrote it, that task fixes it. A subagent's own
216
+ report is not evidence about the text it left behind, and a duplicated block eight
217
+ tasks later is repaired by an agent that no longer remembers the code.
218
+ Keep a progress ledger in
170
219
  a file so a lost context can resume instead of redoing finished work. Tasks in
171
220
  the same parallel group run concurrently **only** if each worker has its own
172
221
  worktree — otherwise sequential. Close the stage by integrating: sync with the
package/evals/RESULTS.md CHANGED
@@ -37,13 +37,16 @@ or it belongs in a check.
37
37
  | Dated runs recorded | **0** | 2026-08-03 |
38
38
 
39
39
  The bottom two numbers are the honest state of this skill's behavioural evidence.
40
- Everything else in this repository is proven by 63 structural guards that check the
40
+ Everything else in this repository is proven by 76 structural guards that check the
41
41
  *form*; these are the only checks that would speak to the *behaviour*, and they have
42
42
  not been run yet. Printed here so "63 of 63 green" is never read as "the skill is
43
43
  known to work".
44
44
 
45
45
  ## Runs
46
46
 
47
+ Newest first. Each entry states the model, the task, and how the evidence was
48
+ obtained — a self-observed run and a blind one are not the same claim.
49
+
47
50
  ## 2026-08-03 · opus · self-observed, not a blind run
48
51
 
49
52
  **Scope of this evidence.** The author ran the skill on a real task
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-pipeline-skill",
3
- "version": "1.10.3",
3
+ "version": "1.12.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"
@@ -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 must close with evidence, a loop guard that breaks churn, one provider-agnostic model confirmed up front, and an optional super-ux UX track for user-facing work.",
5
- "version": "1.10.3",
5
+ "version": "1.12.0",
6
6
  "author": {
7
7
  "name": "ssheleg",
8
8
  "url": "https://x.com/sshlg93"
@@ -65,6 +65,7 @@ gate stops until it is installed.
65
65
  | **first run · the entry audit** (offered once) | [`references/setup.md`](references/setup.md) |
66
66
  | **what travels with the bundle vs stays in a project** | [`references/portability.md`](references/portability.md) |
67
67
  | any repeating loop | [`references/loop-guard.md`](references/loop-guard.md) |
68
+ | run-wide · how a run keeps going (the loop mode + the context budget) | [`references/continuity.md`](references/continuity.md) |
68
69
 
69
70
  **Optional bridge.** If the operator already runs an equivalent skill set (e.g.
70
71
  `superpowers:brainstorming` / `writing-plans` / `subagent-driven-development` /
@@ -181,7 +182,12 @@ Three things the grill does beyond clarifying the request:
181
182
  tasks, context7, wiki-update, graphify) and emits ONE block covering them
182
183
  **and the model decision** (`references/model-tiering.md`): recommend
183
184
  the most capable model available, let the operator confirm or override, record
184
- it. Ask once, here.
185
+ it. Ask once, here. **The same block carries the run mode**
186
+ (`references/continuity.md`): read `pipeline.json` → `run.loop`; where it is
187
+ recorded, arm it and print the job id and the cancel command — the config is
188
+ the authorization, so re-asking rebuilds the habit it exists to retire. Where
189
+ it is **absent, the mode is off**; recommend it in one line and move on.
190
+ Silence arms nothing, and the mode never collapses a `manual` gate.
185
191
  2. **Run stage 0 — always, no exceptions.** It opens with the **knowledge harvest**
186
192
  (`references/knowledge-sources.md`): query the project's own sources — repo docs,
187
193
  ADRs, `docs/ux/`, past briefs, the wiki if installed, any doc repo the project
@@ -341,6 +347,10 @@ automation is on — `pipeline.schema.json` is the only contract.
341
347
  inside the bundle, and the boundary against a project's own answers
342
348
  - `references/adoption.md` — the first run in a project: greenfield seeding, and the
343
349
  brownfield walkthrough whose third step baselines the ratchets at today
350
+ - `references/continuity.md` — run-wide pacing: the loop mode (`run.loop`, default
351
+ off, and it never collapses a manual gate), how it is armed on Claude Code and
352
+ what that costs, and the context budget — which fires on a harness signal, never
353
+ on an estimate
344
354
  - `references/conventions.md` — how stages 6–10 read the host project's CLAUDE.md
345
355
  - `references/companion-skills.md` — companion skills, install lines, preflight recommendation
346
356
  - `references/artifacts.md` — the canonical document/artifact layout per stage
@@ -170,6 +170,10 @@
170
170
  }
171
171
  }
172
172
  ],
173
+ "_run_note": "Run-wide pacing. Shipped explicitly OFF so this example DEMONSTRATES the default rather than relying on its absence — see references/continuity.md. Set mode to 'interval' and a project stops being asked whether to run item-by-item; it still stops at every manual gate. There is no context-budget field here on purpose: the threshold is not measurable, so it is unconditional doctrine, not config.",
174
+ "run": {
175
+ "loop": { "mode": "off", "interval": "5m", "command": "/loop" }
176
+ },
173
177
  "release": {
174
178
  "enabled": true,
175
179
  "trigger": "tag",
@@ -14,9 +14,38 @@
14
14
  "description": "The pipeline stages, in order. Any number, any names — your project's real stages.",
15
15
  "items": { "$ref": "#/definitions/stage" }
16
16
  },
17
- "release": { "$ref": "#/definitions/release" }
17
+ "release": { "$ref": "#/definitions/release" },
18
+ "run": { "$ref": "#/definitions/run" }
18
19
  },
19
20
  "definitions": {
21
+ "run": {
22
+ "type": "object",
23
+ "additionalProperties": true,
24
+ "description": "Optional run-wide pacing. Omit the object and every field defaults OFF: silence arms nothing and authorises nothing — the same floor deploy authorization uses. There is deliberately NO context-budget field: the remaining context is not measurable from inside a run, so a field accepting a percentage would validate a number that can never be honoured. That rule is unconditional doctrine in references/continuity.md instead.",
25
+ "properties": {
26
+ "loop": {
27
+ "type": "object",
28
+ "required": ["mode"],
29
+ "additionalProperties": true,
30
+ "description": "Whether the run advances item by item without a discretionary check-in. It NEVER collapses a manual gate or an outward action — a generic flag is not a specific authorization.",
31
+ "properties": {
32
+ "mode": {
33
+ "enum": ["off", "interval"],
34
+ "description": "off (the default when absent) = the run pauses between items as it always did. interval = the run is armed with the harness's own loop primitive and advances one item per fire, stopping only at a manual gate, an unresolvable block, a genuine ambiguity, or completion."
35
+ },
36
+ "interval": {
37
+ "type": "string",
38
+ "pattern": "^[0-9]+[smhd]$",
39
+ "description": "Required when mode is 'interval'. Must divide its unit cleanly (5m, 10m, 2h — not 7m or 90m); a value that does not is rounded to the nearest that does, and the rounding is stated out loud. Pick the shortest interval that is longer than a typical item."
40
+ },
41
+ "command": {
42
+ "type": "string",
43
+ "description": "How this harness arms it, e.g. '/loop'. Harness-specific and therefore project-recorded rather than assumed: on a harness with no loop primitive, omit it — the mode then degrades to prose discipline plus the build ledger, and the run says so instead of implying it is armed."
44
+ }
45
+ }
46
+ }
47
+ }
48
+ },
20
49
  "release": {
21
50
  "type": "object",
22
51
  "additionalProperties": true,
@@ -156,6 +156,7 @@ plugins/task-pipeline/
156
156
  conventions.md artifacts.md # host conventions, this layout
157
157
  companion-skills.md # optional companions + preflight
158
158
  templates/ # skeletons seeded into a host project
159
+ hygiene.sh # -> scripts/check-hygiene.sh (stages 5, 6, 9)
159
160
  README.md brief.md carryover.md context.md adr.md retro.md
160
161
  cursor/rules/task-pipeline.mdc # Cursor channel (self-contained rule)
161
162
  bin/task-pipeline.js # npx installer (package task-pipeline-skill)
@@ -18,6 +18,15 @@ session's history, so it stays focused; your context stays free for coordination
18
18
  plan to be executed — execute it. Stop only for BLOCKED you can't resolve, a
19
19
  genuine ambiguity, or completion. "Should I continue?" between tasks is noise.
20
20
 
21
+ **Scope, so this is not mistaken for the run-wide mode:** the rule above governs
22
+ the *inside* of this stage and is unconditional — it holds whether or not a loop
23
+ is armed. What it cannot do is reach the other nine stages, or survive the
24
+ boundary between one agent turn and the next; on a harness where prose does not
25
+ carry across that boundary, only a scheduled re-invocation does.
26
+ [`continuity.md`](continuity.md) owns that half, is recorded in `pipeline.json` →
27
+ `run.loop`, and is **off unless recorded**. The two never disagree: one is a
28
+ stage's internal discipline, the other is the run's pacing.
29
+
21
30
  **Narration:** at most one short line between tool calls. The ledger and the tool
22
31
  results are the record.
23
32
 
@@ -238,11 +247,35 @@ physical: **two implementers writing one working tree corrupt each other's state
238
247
 
239
248
  | Status | Action |
240
249
  |---|---|
241
- | `DONE` | Build the review package, dispatch the task review ([`review.md`](review.md)). |
250
+ | `DONE` | **Run the hygiene gate in diff mode first** (below), then build the review package and dispatch the task review ([`review.md`](review.md)). |
242
251
  | `DONE_WITH_CONCERNS` | Read the concerns first. Correctness or scope → resolve before review. Observations ("this file is getting large") → **append to the carry-over ledger**, then proceed. A concern that stays only in the report dies with the workspace. |
243
252
  | `NEEDS_CONTEXT` | Supply exactly what's missing, re-dispatch. |
244
253
  | `BLOCKED` | Diagnose: missing context → re-dispatch with it; needs more reasoning → a more capable model; too large → split the task; the plan itself is wrong → escalate to the operator. |
245
254
 
255
+ ### The hygiene gate — after every task, before the review
256
+
257
+ A subagent's own report is not evidence about the text it left behind. Run the
258
+ seeded gate over **what this task changed**, before the reviewer sees it:
259
+
260
+ ```bash
261
+ HYGIENE_BASE=<the commit recorded before dispatching> bash scripts/check-hygiene.sh
262
+ ```
263
+
264
+ Diff mode has **no floor**: this task wrote it, this task fixes it. Six checks — a
265
+ half-resolved merge, a stub that outlived the task, a fence left open, a file
266
+ "shortened" while being rewritten, a **block duplicated by a retried edit**, and a
267
+ section opened and abandoned.
268
+
269
+ The fifth is why the gate runs here rather than only at stage 6. A batch of edits
270
+ where one applied twice, or never applied at all, is the incident behind standing
271
+ instruction R-002 — and it is invisible in a status report. Found one task later it
272
+ costs a re-dispatch; found eight tasks later it is fixed by an agent that no longer
273
+ remembers the code.
274
+
275
+ **The gate never edits. Fixing is yours.** A finding is repaired inside the same
276
+ task, or it becomes a carry-over row with a reason — never a silent pass. A gate
277
+ whose findings nobody acts on is a slower way of ignoring them.
278
+
246
279
  **Never** ignore an escalation, and never re-dispatch the same model with the same
247
280
  prompt after a BLOCKED. If the implementer says it's stuck, something must change.
248
281
  If the implementer asks a question — before or mid-task — answer it completely; do
@@ -0,0 +1,214 @@
1
+ # Continuity — how a run keeps going
2
+
3
+ Two rules about pacing, in one file because they are two halves of one mechanism.
4
+ The loop is what lets a run walk a task list without asking permission between
5
+ items. The context budget is what stops that loop from starting an item it cannot
6
+ finish. **Neither is useful without the other**, and a run that has the first
7
+ without the second fails in a specific, expensive way: it begins task nine with
8
+ almost no window left, loses the middle of it, and re-derives what it already did.
9
+
10
+ ## Contents
11
+
12
+ - The limit, before the capability
13
+ - Part 1 — the loop
14
+ - Arming it on Claude Code
15
+ - Other harnesses, and honest degradation
16
+ - What one iteration means
17
+ - Parked at a manual gate
18
+ - Part 2 — the context budget
19
+ - The evidence rule
20
+ - What happens at the signal
21
+ - The flush is not a new document
22
+ - Rationalizations
23
+
24
+ ## The limit, before the capability
25
+
26
+ **The loop mode collapses discretionary check-ins. It collapses nothing else.**
27
+
28
+ - A `manual` gate still waits for an explicit operator go. Always.
29
+ - An outward or irreversible act — deploy, publish, opening a PR, creating or
30
+ editing a shared design file — still needs its own authorization. A recorded
31
+ loop mode is a **generic** flag, and a generic flag is not a specific
32
+ authorization; that floor is set in [`grill.md`](grill.md) and this file does
33
+ not lower it.
34
+ - **Default off.** Absent config and absent instruction, the mode is off. Silence
35
+ arms nothing, exactly as silence authorizes no deploy.
36
+
37
+ The mode's whole content is the removal of *"should I continue?"* between items
38
+ the operator already asked for. Anything beyond that is a different decision,
39
+ taken separately.
40
+
41
+ ## Part 1 — the loop
42
+
43
+ **On, the run advances one item per iteration and does not ask between items.**
44
+ This spans the whole run, not one stage:
45
+
46
+ - the plan's tasks at stage 5;
47
+ - the boundary between stages whose gate is `auto`;
48
+ - the per-module program loop when the brief was a platform;
49
+ - the acceptance → retro tail, which is where runs most often stall one step from
50
+ finished.
51
+
52
+ It stops for exactly four things: a `manual` gate, a block it cannot resolve, a
53
+ genuine ambiguity the brief does not answer, and completion.
54
+
55
+ **This extends [`build.md`](build.md); it does not replace it.** That file's
56
+ *Continuous execution* rule governs the inside of a stage-5 execution — do not
57
+ check in between the tasks of a plan you were told to execute. It is unconditional
58
+ and stays that way. What it cannot do is reach the other nine stages, or survive
59
+ the boundary between one agent turn and the next. This file is that reach.
60
+
61
+ **Where it is recorded:** `pipeline.json` → `run.loop`. A project sets it once and
62
+ is never asked again — which is the entire point. A mode that has to be requested
63
+ every run is not a mode, it is a habit the operator maintains by hand.
64
+
65
+ ## Arming it on Claude Code
66
+
67
+ ```
68
+ /loop <interval> <the invocation that continues the run>
69
+ ```
70
+
71
+ The interval **must divide its unit cleanly** — `5m`, `10m`, `2h` are fine; `7m`
72
+ gives uneven gaps and `90m` cannot be expressed at all. A value that does not
73
+ divide cleanly is rounded to the nearest one that does, **and the rounding is said
74
+ out loud** before it is used.
75
+
76
+ Pick **the shortest interval that is longer than a typical item.** Shorter buys
77
+ nothing: the scheduler cannot deliver faster than the item finishes.
78
+
79
+ Two properties of the armed job must be stated when arming it, because both are
80
+ silent failures otherwise:
81
+
82
+ - **The job is session-only.** It is not written to disk and it dies with the
83
+ session.
84
+ - **It auto-expires after seven days**, firing one last time. A loop that quietly
85
+ stops on day eight is worse than one that was never armed, because the operator
86
+ believes work is still moving.
87
+
88
+ Where `run.loop.mode` is `interval`, the **preflight arms it** and prints the job
89
+ id and the cancel command. Arming is not a new decision at that point — the config
90
+ is the recorded authorization, and re-asking would rebuild the habit the config
91
+ exists to retire.
92
+
93
+ ## Other harnesses, and honest degradation
94
+
95
+ `/loop` is **Claude Code only**, in the same way the `PreToolUse` contract in
96
+ [`hooks.md`](hooks.md) is. This file names the limit rather than pretending the
97
+ mechanism is universal.
98
+
99
+ On a harness with no loop primitive, the mode degrades to what prose can do: the
100
+ discipline in [`build.md`](build.md) plus the build ledger, which is genuinely
101
+ useful and genuinely weaker. **Say which one you are running.** A run that reports
102
+ itself as looping while nothing is scheduled is claiming a capability it does not
103
+ have, and the operator finds out by discovering that nothing happened.
104
+
105
+ ## What one iteration means
106
+
107
+ One item, taken to its gate.
108
+
109
+ **A fixed interval cannot interrupt an unfinished item.** The scheduler enqueues
110
+ only while the harness is idle — never mid-query. This is the reason the interval
111
+ form is safe, and it is worth stating precisely, because the plausible-sounding
112
+ alternative explanation is wrong.
113
+
114
+ **The build ledger is the second line, not the first.** It covers a different
115
+ case: a fire that lands after a context loss, where the controller no longer
116
+ remembers what it finished. There, `Task <N>: complete` in
117
+ `.task-pipeline/build/<plan>/progress.md` is the only DONE marker that counts, and
118
+ a task carrying one is never re-dispatched ([`build.md`](build.md)).
119
+
120
+ Do not write, and do not believe, that the ledger is what makes the interval safe.
121
+ Someone will eventually remove the ledger on the strength of that sentence, and
122
+ the protection they think they are keeping is in the scheduler.
123
+
124
+ ## Parked at a manual gate
125
+
126
+ A fixed interval firing into a `manual` gate is a nag. Five minutes later it fires
127
+ again, and again, and the operator — who is reading the thing the gate asked them
128
+ to read — learns to ignore the loop. That is the same failure as a rule nobody
129
+ reads to the end: everyone believes it is covered.
130
+
131
+ **So the run cancels its own loop job when it parks**, and prints the re-arm
132
+ command beside the gate. The go and the re-arm are then one act. This is also the
133
+ honest accounting: while the run waits for a person, nothing is looping, and the
134
+ job list should agree with that.
135
+
136
+ **Verify the cancel by listing the jobs, never by the cancel's own reply.** A
137
+ teardown call will happily accept an id that was never scheduled and report
138
+ success, which leaves the real job running while the transcript says it stopped.
139
+ List afterwards and read the list. This is the same law the rest of this pipeline
140
+ applies to every gate — a green nobody watched is not evidence — and a loop is
141
+ exactly where breaking it goes unnoticed, because the symptom is a message
142
+ arriving on time.
143
+
144
+ ## Part 2 — the context budget
145
+
146
+ Near the end of the context window, the run does **not** stop and it does not
147
+ start anything new:
148
+
149
+ 1. **Finish the item in flight.** A half-finished item is the expensive state.
150
+ 2. **Start no new item.** An item begun in this window loses its own middle.
151
+ 3. **Make the ledgers true** (below).
152
+ 4. **Continue.** Compaction is a normal event, not the end of the run.
153
+
154
+ The failure this prevents is not running out of room — that is survivable. It is
155
+ crossing the boundary *in the middle of something*, so the work either vanishes or
156
+ gets done twice.
157
+
158
+ ## The evidence rule
159
+
160
+ **The rule fires only on evidence**, and there are exactly two admissible kinds:
161
+
162
+ - **A signal from the harness** — a warning that compaction is near, a `PreCompact`
163
+ hook firing, an explicit low-context notice.
164
+ - **The operator saying so.**
165
+
166
+ Absent both, the threshold has not been observed, and you **never announce that
167
+ the context is nearly spent without one of those signals**. No tool returns the
168
+ remaining percentage. An estimate from transcript length or how much has been read
169
+ is a guess, and presenting a guess as a measurement is precisely the failure
170
+ [`learned.md`](learned.md) names — compute, never restate.
171
+
172
+ This cuts both ways, and the second direction is the one that actually goes wrong:
173
+ a run that keeps volunteering *"context is nearly exhausted, start a new session"*
174
+ against a mostly-empty window trains the operator to disregard the one time it is
175
+ true.
176
+
177
+ ## What happens at the signal
178
+
179
+ The four acts above, in order, and one prohibition: **do not begin an item you
180
+ cannot finish inside what is left.** When the next item is plainly too large,
181
+ say so and take the smaller thing that fits — closing a ledger, writing a decision
182
+ that is already settled, updating a REQ status.
183
+
184
+ If the project has recorded that it must not cross a compaction mid-stage, stop at
185
+ the stage boundary instead and hand off. That is a project answer, not a default:
186
+ the default is to continue.
187
+
188
+ ## The flush is not a new document
189
+
190
+ Flushing means **making the artifacts that already exist true**:
191
+
192
+ - the build ledger — every completed task carrying its `complete` line;
193
+ - the carry-over ledger — everything deferred, dropped or half-done;
194
+ - the brief's REQ statuses;
195
+ - the task list.
196
+
197
+ It does **not** mean writing a summary for the compactor. A summary is a fourth
198
+ copy of the truth, it is written once, nobody updates it, and the next run reads
199
+ it as current. The artifacts above are read by later stages anyway; making them
200
+ right costs nothing extra and pays twice.
201
+
202
+ ## Rationalizations
203
+
204
+ | Excuse | Reality |
205
+ |---|---|
206
+ | "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`. |
207
+ | "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. |
208
+ | "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. |
209
+ | "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. |
210
+ | "The ledger protects me from mid-task fires" | The scheduler not firing mid-query protects you. The ledger covers context loss. Confusing them is how the real protection gets deleted. |
211
+ | "It's still looping while we wait for approval" | A loop firing into a manual gate is a nag, and a nagged operator stops reading. Cancel on parking, re-arm with the go. |
212
+ | "Context feels tight, I should warn them" | Feels is not evidence. Without a harness signal or the operator's word, the warning is a guess wearing the clothes of a measurement. |
213
+ | "I'll write a handoff summary before compaction" | Update the ledgers instead. A summary is a copy of the truth that nobody maintains. |
214
+ | "One more task will fit" | If it does not fit, its middle is what you lose — and half a task is the most expensive state there is. |
@@ -140,6 +140,7 @@ explicit "stop and ask me here":
140
140
  | Stage | What to settle up front |
141
141
  |---|---|
142
142
  | run-wide | the model decision ([`model-tiering.md`](model-tiering.md)); what to decide autonomously vs escalate |
143
+ | run-wide Pacing | the **run mode** ([`continuity.md`](continuity.md)): does the run advance item-by-item with no check-in between items, and on what interval? Read `pipeline.json` → `run.loop` first — a recorded mode is the answer and is not re-asked. **Absent, it is off**: recommend it, take the answer, record it. It never collapses a `manual` gate or an outward act, so this row buys pacing, not authorization |
143
144
  | 0 Harvest | doc sources beyond this repo — other repos, hosted doc systems, the knowledge wiki, **the code graph** ([`knowledge-graph.md`](knowledge-graph.md): built / installed-not-built / absent) — and whether stage 9 may write to them (another repo is outward: propose + PR, never a direct push) |
144
145
  | 0 Setup audit | doc map absent or stale: run the entry audit over the existing documentation before building on it ([`setup.md`](setup.md))? Asked once; a refusal is recorded and never re-asked |
145
146
  | run-wide Escalation | cost of being wrong: decide alone while it stays inside the repository and reversible; escalate price, legal posture, promise, money, reputation, irreversible outward acts. Project exceptions? |
@@ -51,10 +51,13 @@ a row pointing outside the bundle is the defect this file exists to catch.
51
51
  | The entry audit and what it inspects | `references/setup.md` |
52
52
  | The ladder, seams, axis rotation, ratchets | `references/audit.md` |
53
53
  | Loop detection and its caps | `references/loop-guard.md` |
54
+ | **The run mode** — item-by-item pacing, default off, what it never collapses | `references/continuity.md` |
55
+ | **The context budget** — the evidence rule and what a flush actually updates | `references/continuity.md` |
54
56
  | The retro: prune, cap, commits, archive | `references/retrospective.md` |
55
57
  | Rules earned by failure | `references/learned.md` |
56
58
  | **The routing default and its boundary** | `templates/routing-rule.md` |
57
59
  | The seeded doc map, registers and gate | `templates/docmap.md`, `templates/decisions.md`, `templates/open-questions.md`, `templates/docgate.sh` |
60
+ | Which agent-introduced defects are found, and that the agent fixes them rather than the script | `templates/hygiene.sh`, `references/build.md` |
58
61
  | What a stage reads, and which host files bind it | `references/artifacts.md` |
59
62
  | The design conversation, its hard gate, UI detection, user paths | `references/brainstorm.md` |
60
63
  | Cutting a platform into modules, brick criteria, build order | `references/decomposition.md` |
@@ -131,6 +131,13 @@ never that the work was skipped quietly.
131
131
  calls) and the **autonomy sweep** that pre-resolves every stage-1→10 blocker.
132
132
  Deploy authorization has a hard floor there: a standing go counts only when it
133
133
  names the target and the preconditions.
134
+ - **The run mode is settled here too** ([`continuity.md`](continuity.md)): does
135
+ the run advance item-by-item with no check-in between items, and on what
136
+ interval? Read `pipeline.json` → `run.loop` first — a recorded mode is the
137
+ answer and is not re-asked; **absent, it is off**. It buys pacing, never
138
+ authorization: no loop mode collapses a `manual` gate or an outward act. The
139
+ same file carries the other half of pacing — the context budget, which fires
140
+ only on a harness signal or the operator's word, never on an estimate.
134
141
  - **UI early-detect:** one branch of the grill is always "does this touch a
135
142
  user-facing surface (web/mobile/CLI/TUI)?". If yes → surface **super-ux**
136
143
  now (use it if installed; otherwise give the install line — see SKILL.md
@@ -289,7 +296,9 @@ never that the work was skipped quietly.
289
296
  on the result, land it the project's way (merge, or a PR — outward, so it needs a
290
297
  go), remove the worktree. Stages 7–9 act on the integrated result, so a branch the
291
298
  operator chose to leave unmerged is recorded as such.
292
- - **GATE (auto):** all plan tasks DONE (three review verdicts per task: spec
299
+ - **GATE (auto):** **the hygiene gate green in diff mode after every task**
300
+ (`references/build.md`) — six checks over what that task changed, no floor, and a
301
+ finding fixed in-task or carried over with a reason; all plan tasks DONE (three review verdicts per task: spec
293
302
  compliance, **REQ satisfied**, code quality); every finding fixed or parked with a
294
303
  ruling; **every parked finding and implementer concern harvested into the
295
304
  carry-over ledger** — nothing stays only in the scratch workspace, which is
@@ -304,7 +313,8 @@ never that the work was skipped quietly.
304
313
  - **Invoke:** the host test runner (see `conventions.md` → *Lint + test*); the
305
314
  built-in [`tdd.md`](tdd.md) cycle for any uncovered gap — failing test first,
306
315
  same as stage 5.
307
- - **GATE (auto):** the **full** suite is green (not just the new tests); new/changed code
316
+ - **GATE (auto):** **the hygiene gate green over the whole tree**, its six counts
317
+ printed beside their floors; the **full** suite is green (not just the new tests); new/changed code
308
318
  is covered; no `skip`/`xfail` smuggling a red suite past the gate. Never advance
309
319
  to deploy on a red or partial run. **The carry-over count is printed beside this
310
320
  verdict** — a ratchet nobody prints is a TODO with a better name
@@ -374,7 +384,7 @@ never that the work was skipped quietly.
374
384
  - **Docs living in another repository** are outward: propose the edit, get an
375
385
  explicit go, then open a PR there. No go → the exact edit goes in the carry-over
376
386
  ledger.
377
- - **GATE (auto):** **the propagation matrix walked for every change type this run
387
+ - **GATE (auto):** **the hygiene gate green**, counts printed; **the propagation matrix walked for every change type this run
378
388
  produced**, with every settled thing recorded under an id and every answered
379
389
  question resolved; **the documentation gate green, its ratchet counts printed and
380
390
  any skip stated** — this is what replaced the unfalsifiable *"docs in sync with
@@ -17,6 +17,7 @@ from `super-ux`.
17
17
  | `decisions.md` | `docs/DECISIONS.md` — the decision register | 0 seeds it, the Doc Loop appends |
18
18
  | `open-questions.md` | `docs/OPEN_QUESTIONS.md` | 0 seeds it, the Doc Loop resolves rows |
19
19
  | `docgate.sh` | `scripts/check-docs.sh` | 0 seeds it · 9 runs it · 10 proves it |
20
+ | `hygiene.sh` | `scripts/check-hygiene.sh` | 0 seeds it · **5 runs it after every task** · 6 and 9 run it · 10 proves it |
20
21
  | `hooks.example.json` | the project's `.claude/settings.json` | 0 — offered, never installed silently |
21
22
  | `routing-rule.md` | the operator's `CLAUDE.md` — **offered by `setup`, never written silently** | 0 / `setup` |
22
23
  | `retro.md` | `docs/superpowers/retro.md` — **one per project, not per run** | 10 writes (prune → stamp → entry), 0 reads it in full |
@@ -21,7 +21,7 @@ decision was made and *why* — not filling out sections.
21
21
  ## When this directory IS the register
22
22
 
23
23
  An ADR set and `docs/DECISIONS.md` are **two shapes of one decision home**, and a
24
- project has exactly one ([`../references/documentation.md`](../references/documentation.md)).
24
+ project has exactly one (the skill's own `references/documentation.md`).
25
25
  If `docs/adr/` already holds an `NNNN-*.md`, that is the register — record it in
26
26
  `docs/DOCMAP.md` and never seed a second home beside it.
27
27
 
@@ -117,6 +117,7 @@ is not neutral — it is a scheduled interruption.
117
117
  | run-wide | Model for this run | … (most capable available unless overridden; per-stage overrides here) |
118
118
  | run-wide Escalation | … | cost of being wrong: decide alone while it stays inside the repository and reversible; escalate price, legal posture, promise, money, reputation, irreversible outward acts. Project exceptions? |
119
119
  | run-wide | Decide autonomously vs escalate to me | … |
120
+ | run-wide Pacing | Run mode: item-by-item with no check-in between items, and on what interval? (the skill's `references/continuity.md`; read `pipeline.json` → `run.loop` first) | … (**absent ⇒ off**; it never collapses a manual gate or an outward act) |
120
121
  | 0 Harvest | Doc sources beyond this repo — other repos, hosted docs, the knowledge wiki, the code graph; and may stage 9 write to them? | … (another repo is outward: propose + PR, never a direct push; graph built / not built) |
121
122
  | 0 Setup audit | … (yes / no — recorded either way) | doc map absent or stale: run the entry audit over the existing documentation before building on it (the skill's `references/setup.md`)? Asked once; a refusal is recorded and never re-asked |
122
123
  | 0 Docs regime | Where settled things live (register or ADR set — one home, never both); who may write it; lease mechanism present, or is this run `ungated`? Gate command + ratchet floors; may this run raise a floor? | … |
@@ -47,7 +47,7 @@ is the true statement.
47
47
  - **The set may only shrink.** If it grew, the run log gets one sentence saying why.
48
48
  A ratchet that grows silently is a TODO with a better name.
49
49
  - **A finding class that appears twice stops belonging here** and becomes a check in
50
- the host's lint or CI ([`audit.md`](../references/audit.md) → *A class that
50
+ the host's lint or CI (the skill's own `references/audit.md` → *A class that
51
51
  repeats twice becomes a gate*). This ledger is for what cannot be automated, not
52
52
  for what nobody automated.
53
53
 
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env bash
2
+ # hygiene.sh — the artifact hygiene gate for <project>.
3
+ #
4
+ # Seeded by task-pipeline (references/gates.md). IT IS YOURS NOW: extend it here,
5
+ # section by section. Each section is independent and removable.
6
+ #
7
+ # WHAT IT IS FOR: the defect class an AGENT produces and no other check looks for —
8
+ # a half-resolved merge, a stub that outlived its task, a generation cut off in the
9
+ # middle, a file "shortened" while being rewritten, a batch of edits where one
10
+ # applied twice, a section opened and abandoned.
11
+ #
12
+ # SCOPE: walks tracked files. In diff mode it walks only what this run changed; in
13
+ # tree mode it walks everything, behind per-check floors.
14
+ # It does NOT check: prose meaning, code correctness, style, formatting, spelling,
15
+ # another repository, or generated output. It is not a linter — your language has
16
+ # one, and this is not it.
17
+ # KNOWN FALSE-POSITIVE SURFACES, one per check, because a gate that hides them
18
+ # gets disabled the first time it is wrong:
19
+ # 1 conflict markers — a document explaining merge conflicts
20
+ # 2 placeholders — only a LINE-LEADING marker counts. The word TODO in the
21
+ # middle of a sentence is prose, not a stub. This
22
+ # distinction is the whole check: measured on the project
23
+ # that ships it, 28 of 33 hits were ordinary English.
24
+ # 3 unterminated fence — a fence deliberately shown unclosed as an example
25
+ # 4 truncation stubs — a document quoting one as an example of what not to do
26
+ # 5 duplicated block — a legitimately repeated stanza 3+ lines long
27
+ # 6 empty section — a heading used as a one-line record. If you have those,
28
+ # they want to be list items: a heading promises a section.
29
+ # Read this header before quoting a green from here as evidence.
30
+ #
31
+ # IT NEVER EDITS. It reports file:line and exits non-zero. None of the six is safely
32
+ # machine-fixable: deleting a "duplicated block" sometimes deletes a legitimate
33
+ # repetition, and deleting a TODO erases a reminder instead of discharging it.
34
+ # Fixing is the agent's job, and task-pipeline's references/build.md makes it one.
35
+ #
36
+ # EXIT CODE IS THE OUTPUT: non-zero on any failure. Nothing may run after the
37
+ # VERDICT block at the bottom — a gate that appended a check after its verdict
38
+ # printed FAIL and returned 0, and CI was green over it for an unknown period.
39
+ #
40
+ # PORTABLE to macOS bash 3.2: no grep -P, no sed -i, no readarray, no mapfile.
41
+ #
42
+ # PROGRESSIVE ARMING: a check whose input does not exist prints
43
+ # "dormant: … — no <input>" and does NOT fail. Dormant is visible so it is not
44
+ # forgotten, and green so a freshly seeded project does not start red.
45
+ #
46
+ # TWO MODES, AND THE DIFFERENCE IS THE POINT:
47
+ # diff mode (HYGIENE_BASE set) — ZERO tolerance. This run wrote it, this run fixes
48
+ # it. There is no floor, because a floor for work you just did is a licence.
49
+ # tree mode (default) — per-check RATCHET FLOORS. A count at or below its
50
+ # floor passes; above it fails. A floor may only fall. This is what lets an
51
+ # existing repository adopt the gate without starting red, while forgiving
52
+ # nothing new.
53
+
54
+ set -u
55
+
56
+ FAIL=0
57
+ HYGIENE_BASE=${HYGIENE_BASE:-}
58
+ HYGIENE_EXCLUDE=${HYGIENE_EXCLUDE:-graphify-out/}
59
+ HYGIENE_FLOOR_1=${HYGIENE_FLOOR_1:-0}
60
+ HYGIENE_FLOOR_2=${HYGIENE_FLOOR_2:-0}
61
+ HYGIENE_FLOOR_3=${HYGIENE_FLOOR_3:-0}
62
+ HYGIENE_FLOOR_4=${HYGIENE_FLOOR_4:-0}
63
+ HYGIENE_FLOOR_5=${HYGIENE_FLOOR_5:-0}
64
+ HYGIENE_FLOOR_6=${HYGIENE_FLOOR_6:-0}
65
+
66
+ TMP=${TMPDIR:-/tmp}/hygiene.$$
67
+ mkdir -p "$TMP" || exit 2
68
+ trap 'rm -rf "$TMP"' EXIT
69
+
70
+ err() { echo " FAIL: $*"; FAIL=1; }
71
+ ok() { echo " ok: $*"; }
72
+ dormant() { echo " dormant: $*"; }
73
+
74
+ # ---------- the file list ----------
75
+ # git is the source of truth for "authored": generated and ignored files are not
76
+ # this gate's business, and scanning them is how a gate becomes noise.
77
+ if git rev-parse --git-dir >/dev/null 2>&1; then
78
+ if [ -n "$HYGIENE_BASE" ]; then
79
+ MODE=diff
80
+ { git diff --name-only "$HYGIENE_BASE" 2>/dev/null
81
+ git diff --name-only 2>/dev/null
82
+ git diff --name-only --cached 2>/dev/null
83
+ } | sort -u > "$TMP/all"
84
+ else
85
+ MODE=tree
86
+ git ls-files > "$TMP/all"
87
+ fi
88
+ else
89
+ MODE=tree
90
+ find . -type f -not -path './.git/*' | sed 's|^\./||' > "$TMP/all"
91
+ fi
92
+
93
+ : > "$TMP/files"
94
+ while read -r f; do
95
+ [ -n "$f" ] || continue
96
+ [ -f "$f" ] || continue
97
+ case "$f" in
98
+ $HYGIENE_EXCLUDE*) continue ;;
99
+ esac
100
+ echo "$f" >> "$TMP/files"
101
+ done < "$TMP/all"
102
+
103
+ NFILES=$(wc -l < "$TMP/files" | tr -d ' ')
104
+ : > "$TMP/md"
105
+ while read -r f; do
106
+ case "$f" in *.md) echo "$f" >> "$TMP/md" ;; esac
107
+ done < "$TMP/files"
108
+ NMD=$(wc -l < "$TMP/md" | tr -d ' ')
109
+
110
+ echo "hygiene gate — mode $MODE · $NFILES file(s), $NMD markdown"
111
+
112
+ # The floor a check is judged against. Diff mode has none: zero tolerance.
113
+ floor_for() {
114
+ if [ "$MODE" = diff ]; then echo 0; return; fi
115
+ eval "echo \${HYGIENE_FLOOR_$1}"
116
+ }
117
+
118
+ judge() { # judge <n> <count> <label>
119
+ _f=$(floor_for "$1")
120
+ if [ "$2" -gt "$_f" ]; then
121
+ err "check $1 — $3: $2 finding(s), floor $_f"
122
+ sed 's/^/ /' "$TMP/hits$1"
123
+ else
124
+ ok "check $1 — $3: $2 (floor $_f)"
125
+ fi
126
+ }
127
+
128
+ if [ "$NFILES" -eq 0 ]; then
129
+ dormant "every check — no files in scope"
130
+ C1=0; C2=0; C3=0; C4=0; C5=0; C6=0
131
+ else
132
+
133
+ # ---------- 1. conflict markers ----------
134
+ : > "$TMP/hits1"
135
+ while read -r f; do
136
+ grep -n -E '^(<<<<<<< |=======$|>>>>>>> )' "$f" 2>/dev/null |
137
+ sed "s|^|$f:|" >> "$TMP/hits1"
138
+ done < "$TMP/files"
139
+ C1=$(wc -l < "$TMP/hits1" | tr -d ' ')
140
+ judge 1 "$C1" "conflict markers"
141
+
142
+ # ---------- 2. surviving placeholders ----------
143
+ # A LINE-LEADING marker, optionally behind a comment introducer. Not the word.
144
+ : > "$TMP/hits2"
145
+ while read -r f; do
146
+ grep -n -E '^[[:space:]]*([#/*<!-]+[[:space:]]*)*(TODO|TBD|FIXME|XXX)\b' "$f" 2>/dev/null |
147
+ sed "s|^|$f:|" >> "$TMP/hits2"
148
+ done < "$TMP/files"
149
+ C2=$(wc -l < "$TMP/hits2" | tr -d ' ')
150
+ judge 2 "$C2" "surviving placeholders"
151
+
152
+ # ---------- 3. unterminated code fence ----------
153
+ : > "$TMP/hits3"
154
+ if [ "$NMD" -eq 0 ]; then
155
+ dormant "check 3 — unterminated fence: no markdown in scope"
156
+ C3=0
157
+ else
158
+ while read -r f; do
159
+ # grep -c exits 1 on zero matches, so a `|| echo 0` would append a SECOND zero
160
+ # and make the test below see "0\n0". Swallow the status instead.
161
+ n=$(grep -c '^[[:space:]]*```' "$f" 2>/dev/null) || n=0
162
+ [ -z "$n" ] && n=0
163
+ if [ "$n" -gt 0 ] && [ "$(expr "$n" % 2)" -ne 0 ]; then
164
+ echo "$f:1: odd fence count ($n) — a code block is not closed" >> "$TMP/hits3"
165
+ fi
166
+ done < "$TMP/md"
167
+ C3=$(wc -l < "$TMP/hits3" | tr -d ' ')
168
+ judge 3 "$C3" "unterminated fence"
169
+ fi
170
+
171
+ # ---------- 4. truncation stubs ----------
172
+ # LINE-LEADING, exactly like check 2, and for exactly the same reason: the first
173
+ # version matched the phrase anywhere and fired three times on the very documents
174
+ # that DEFINE these patterns. A stub is a line standing in for content; a phrase in
175
+ # the middle of a sentence is prose about stubs.
176
+ : > "$TMP/hits4"
177
+ while read -r f; do
178
+ grep -n -E '^[[:space:]]*([#/*<!-]+[[:space:]]*)*(\.\.\. existing code|\[TRUNC|rest of (the )?file unchanged|unchanged\.\.\.)' "$f" 2>/dev/null |
179
+ sed "s|^|$f:|" >> "$TMP/hits4"
180
+ done < "$TMP/files"
181
+ C4=$(wc -l < "$TMP/hits4" | tr -d ' ')
182
+ judge 4 "$C4" "truncation stubs"
183
+
184
+ # ---------- 5. duplicated adjacent block ----------
185
+ # The mechanical form of "re-verify every edit in a batch": a retry that duplicated
186
+ # instead of replacing leaves two identical blocks back to back. Three lines is the
187
+ # floor because two-line repeats occur legitimately (table rows, list pairs).
188
+ : > "$TMP/hits5"
189
+ while read -r f; do
190
+ awk -v F="$f" '
191
+ NF { blk = blk "\n" $0; n++; next }
192
+ {
193
+ if (n >= 3) { if (blk == prev) print F ":" NR ": duplicated " n "-line block"; prev = blk }
194
+ else prev = ""
195
+ blk = ""; n = 0
196
+ }
197
+ END { if (n >= 3 && blk == prev) print F ":" NR ": duplicated " n "-line block at end of file" }
198
+ ' "$f" >> "$TMP/hits5" 2>/dev/null
199
+ done < "$TMP/files"
200
+ C5=$(wc -l < "$TMP/hits5" | tr -d ' ')
201
+ judge 5 "$C5" "duplicated adjacent block"
202
+
203
+ # ---------- 6. empty section ----------
204
+ # A heading of level N followed by a heading of level <= N with no body between.
205
+ # FENCED CONTENT COUNTS AS BODY — the first version of this check skipped fenced
206
+ # lines entirely and so reported every section whose body is one code block.
207
+ : > "$TMP/hits6"
208
+ if [ "$NMD" -eq 0 ]; then
209
+ dormant "check 6 — empty section: no markdown in scope"
210
+ C6=0
211
+ else
212
+ while read -r f; do
213
+ awk -v F="$f" '
214
+ /^[[:space:]]*(```|~~~)/ { fence = !fence; prev = 0; next }
215
+ fence { prev = 0; next }
216
+ /^#{1,6} / {
217
+ match($0, /^#+/); lvl = RLENGTH
218
+ if (prev && lvl <= prev) print F ":" NR ": empty section — h" prev " then h" lvl " with no body"
219
+ prev = lvl; next
220
+ }
221
+ NF { prev = 0 }
222
+ ' "$f" >> "$TMP/hits6" 2>/dev/null
223
+ done < "$TMP/md"
224
+ C6=$(wc -l < "$TMP/hits6" | tr -d ' ')
225
+ judge 6 "$C6" "empty section"
226
+ fi
227
+
228
+ fi
229
+
230
+ # ---------- VERDICT — nothing may run after this block ----------
231
+ if [ "$FAIL" -ne 0 ]; then
232
+ echo "FAIL: hygiene gate — mode $MODE · conflict ${C1} · placeholder ${C2} · fence ${C3} · truncation ${C4} · duplicate ${C5} · empty-section ${C6}"
233
+ exit 1
234
+ fi
235
+ echo "OK: hygiene gate — mode $MODE · $NFILES file(s) · conflict ${C1} · placeholder ${C2} · fence ${C3} · truncation ${C4} · duplicate ${C5} · empty-section ${C6}"
236
+ exit 0
@@ -48,7 +48,13 @@ stays short precisely so that reading it in full at stage 0 stays cheap.
48
48
  - **Upstream?** … (a lesson true in any repo belongs in the skill's own
49
49
  `references/learned.md` — open an issue and say so here)
50
50
 
51
- ### 2026-05-04 · retired R-000 — became a check (`npm run lint:paths`) · `<sha>`
51
+ ### Retirements
52
+
53
+ One line each, newest first. A retirement is a record, not a section — so it is a
54
+ list item. A heading promises a body, and a heading with nothing under it is the
55
+ shape the hygiene gate's check 6 exists to find.
56
+
57
+ - 2026-05-04 · retired R-000 — became a check (`npm run lint:paths`) · `<sha>`
52
58
 
53
59
  ## Run stamps
54
60
 
@@ -8,7 +8,7 @@ operator's configuration silently.
8
8
  raises the odds the model selects it and cannot make selection mandatory — the choice
9
9
  stays with the model, case by case. Only an instruction makes routing binding. That
10
10
  instruction therefore has to be *installed*, which is exactly how a workflow decision
11
- ends up living outside the bundle ([`../references/portability.md`](../references/portability.md)).
11
+ ends up living outside the bundle (the skill's own `references/portability.md`).
12
12
  Shipping it as a template is what lets it travel.
13
13
 
14
14
  **Keep the exclusions identical** to the skill's `description` and to the