cohorte 1.4.0 → 1.6.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 (44) hide show
  1. package/CHANGELOG.md +151 -0
  2. package/README.md +43 -12
  3. package/bin/cli.js +13 -2
  4. package/core/agents/review.md +23 -0
  5. package/core/commands/audit.md +9 -1
  6. package/core/commands/brainstorm.md +6 -0
  7. package/core/commands/build.md +93 -8
  8. package/core/commands/doctor.md +13 -8
  9. package/core/commands/drive.md +80 -0
  10. package/core/commands/fix.md +10 -5
  11. package/core/commands/review.md +94 -12
  12. package/core/commands/spec.md +20 -0
  13. package/core/commands/update-pipeline.md +6 -1
  14. package/core/hooks/gate.py +4 -4
  15. package/core/templates/decisions.template.md +42 -0
  16. package/core/templates/spec.template.md +4 -2
  17. package/core/templates/steps/init-pipeline/02-interview-gaps.md +1 -1
  18. package/core/templates/steps/init-pipeline/04-write-render.md +8 -4
  19. package/core/workflows/review.js +44 -2
  20. package/dashboard/dist/assets/{index-AFQnlfjO.css → index-BZ_LQlEj.css} +1 -1
  21. package/dashboard/dist/assets/index-DYyn4p93.js +43 -0
  22. package/dashboard/dist/index.html +2 -2
  23. package/dashboard/server/doctor.js +13 -3
  24. package/dashboard/server/index.js +7 -0
  25. package/dashboard/server/metrics.js +4 -4
  26. package/dashboard/server/usage.js +61 -0
  27. package/install.ps1 +12 -1
  28. package/install.sh +12 -2
  29. package/package.json +1 -1
  30. package/profile/PIPELINE.template.md +3 -3
  31. package/profile/SCHEMA.md +150 -12
  32. package/scripts/loop.sh +318 -0
  33. package/scripts/metrics/collect.mjs +11 -2
  34. package/scripts/preflight.sh +2 -2
  35. package/scripts/telemetry-send.sh +5 -2
  36. package/scripts/test-dashboard.mjs +22 -2
  37. package/scripts/test-gate.mjs +1 -2
  38. package/scripts/test-loop.mjs +227 -0
  39. package/scripts/test-metrics.mjs +12 -3
  40. package/scripts/test-workflows.mjs +28 -0
  41. package/scripts/validate-core.mjs +21 -6
  42. package/core/agents/smoke.md +0 -63
  43. package/core/commands/smoke.md +0 -55
  44. package/dashboard/dist/assets/index-DLBzciIC.js +0 -43
@@ -0,0 +1,80 @@
1
+ ---
2
+ model: sonnet
3
+ description: Autonomous /build → /review → /fix → /review loop for one feature, until no blocking finding remains.
4
+ argument-hint: <feature_id> [--max=N] [--no-build] [--rebuild] [--resume]
5
+ allowed-tools: Bash(bash ~/.claude/pipeline/scripts/loop.sh:*), Bash(bash .claude/pipeline/scripts/loop.sh:*), Bash(test:*), Read(specs/reports/**)
6
+ disable-model-invocation: true
7
+ ---
8
+
9
+ You are the **launcher**, not the loop. Run the driver for **$ARGUMENTS** and relay three lines.
10
+
11
+ > **This command was `/drive` until 1.6.0.** Claude Code ships its own built-in `/drive` (run a prompt on
12
+ > a recurring interval), which **shadowed** this one: typing `/drive <id>` started the interval runner
13
+ > with the feature id as its prompt, so the driver below never ran and the session reported a loop that
14
+ > did not exist. The shipped script keeps its `loop.sh` name — nothing about a user's install path
15
+ > changes, only what you type.
16
+ >
17
+ > This command exists because a slash command cannot `/clear` itself. Every phase of the loop runs
18
+ > as a **separate `claude -p` child session** with its own fresh context, driven by a bash script —
19
+ > so the diff, the N review reports and the N contracts never accumulate in YOUR history, which is
20
+ > re-sent at input price on every turn. Running the loop conversationally here would cost more than
21
+ > the loop saves.
22
+
23
+ ## 1. Launch
24
+
25
+ Probe the core, then run the script — ONE Bash call, and let it run to completion:
26
+
27
+ ```
28
+ test -f .claude/pipeline/scripts/loop.sh \
29
+ && bash .claude/pipeline/scripts/loop.sh $ARGUMENTS \
30
+ || bash ~/.claude/pipeline/scripts/loop.sh $ARGUMENTS
31
+ ```
32
+
33
+ Pass `$ARGUMENTS` through untouched — the script owns its own flag parsing (`--max=N`,
34
+ `--no-build`, `--rebuild`, `--resume`) and exits 64 on anything it doesn't know. Don't validate flags
35
+ yourself, don't rewrite them, don't add any.
36
+
37
+ **Resume is the human's call, not yours.** The loop records its position in the spec's front-matter
38
+ (`status: in-progress` · `loop_pass` · `loop_phase` — SCHEMA.md §Spec status), so a run killed by a
39
+ dead session, a ceiling or a `blocked` exit can continue with `--resume` instead of re-paying the
40
+ passes it already made. If the human types `/drive <id>` on a spec whose front-matter says
41
+ `status: in-progress` or `blocked` with `loop_pass` > 1, say so in one line and ask whether to resume
42
+ or restart — never silently add the flag, and never silently restart from pass 1.
43
+
44
+ **Never read `specs/reports/<id>.loop.log`.** It holds the full transcript of every child session —
45
+ the entire diff, every review report, every fix handoff. Pulling it into this session re-imports
46
+ exactly the context the loop was built to keep out, and it is the one mistake that turns this
47
+ command into the most expensive one in the pipeline. Point the human at the path instead; they can
48
+ open it in an editor for free. The same goes for the per-surface `.diff` and `.preflight.txt` files.
49
+
50
+ ## 2. Report — three lines, from the exit code
51
+
52
+ The script prints one line per phase and one closing line; that is your raw material. For exit
53
+ **1** or **3** only, also Read `specs/reports/<id>.verdict.json` (small, structured, safe) to name
54
+ the remaining findings — never the markdown report, which is the findings body in full. For exit
55
+ **4**, Read `specs/reports/<id>.readiness.json` instead (also small) and relay its `gaps`. On any
56
+ other exit the closing line already carries the deferred count, so read nothing.
57
+
58
+ | exit | meaning | what to say |
59
+ | ---- | ------- | ----------- |
60
+ | `0` | clean | no blocking findings left; the human can `/ship <id>` |
61
+ | `1` | ceiling hit | the fix was progressing but ran out of passes ⇒ re-run with a higher `--max` |
62
+ | `2` | no usable verdict | `/review` produced nothing, or aborted on a red preflight — the closing line says which; point at `specs/reports/<id>.preflight.txt` |
63
+ | `3` | non-convergent | the same blocking findings survived a fix pass; a higher `--max` will NOT help — the human needs to look at them (list them from the verdict) |
64
+ | `4` | not implementable | `/build`'s readiness gate returned `NOT-READY` — the frozen spec cannot be built and **no agent ran**; Read `specs/reports/<id>.readiness.json` (small, structured) and relay its `gaps`, then point at `/spec <id>`. More passes cannot fix this |
65
+ | `64` | usage | relay the script's own message verbatim |
66
+
67
+ Then print exactly three lines and nothing else — plus a fourth **only when the verdict carries
68
+ `deferred` > 0** (findings that were real but out of this feature's scope, parked in the backlog by
69
+ `/review` §3.5; they are not blocking and never cost a pass, but they are not nothing either):
70
+
71
+ ```
72
+ outcome: <one clause — clean / ceiling / no verdict / non-convergent / not implementable / usage>
73
+ iterations: <n> review pass(es)<, m fix pass(es) committed>
74
+ remaining: <blocking count + one short phrase per blocking item, or "none">
75
+ deferred: <n> parked in specs/refactor-backlog.md — /refactor <domain> when you want them
76
+ ```
77
+
78
+ Add at most one follow-up sentence: the next command to run. Never restate a finding's fix, never
79
+ summarize the log, never open the diff. Each fix pass is already committed
80
+ (`loop(<id>): fix pass <i>`) — say so on a non-zero exit, since those commits are the way back.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  model: sonnet
3
- description: Apply a REVIEW REPORT (or SMOKE failures) — append it to the spec's Remediation, then re-dispatch ONLY the surfaces that have findings.
3
+ description: Apply a REVIEW REPORT — append it to the spec's Remediation, then re-dispatch ONLY the surfaces that have findings.
4
4
  argument-hint: <feature_id> [paste REVIEW REPORT]
5
5
  ---
6
6
 
@@ -16,9 +16,9 @@ that change the *contract*; `/fix` is for everything else.
16
16
 
17
17
  ## 1. Ingest the report
18
18
 
19
- - The report is either pasted after the feature id, the REVIEW REPORT / SMOKE failures from this
20
- session's last `/review` / `/smoke`, or — if the context was cleared — read from
21
- `specs/reports/<id>.md`, where `/review` and `/smoke` stage their last report for exactly this reason.
19
+ - The report is either pasted after the feature id (a REVIEW REPORT, or failures the human hit by
20
+ hand), this session's last `/review` REVIEW REPORT, or — if the context was cleared — read from
21
+ `specs/reports/<id>.md`, where `/review` stages its last report for exactly this reason.
22
22
  If you have none of these, ask for it and wait.
23
23
  - Append each finding to `specs/<id>.md` **`## Remediation`** (same format as `/spec` Mode B, under a
24
24
  dated/numbered subheading): `- [ ] <severity> · <file:line> · <type> · <concrete fix>`. Set
@@ -50,6 +50,11 @@ that change the *contract*; `/fix` is for everything else.
50
50
 
51
51
  When the agents return:
52
52
 
53
+ - **Roll call first (SCHEMA.md §Dead agents).** A re-dispatched agent that returns nothing has fixed
54
+ nothing you can verify. Retry that surface ONCE with the byte-identical dispatch; still silent ⇒ leave
55
+ **every one of its items `- [ ]`** (a dead agent never ticks a box — that would hide an open finding
56
+ from the next round and from the driver), print `<key> · DEAD — items untouched`, and say the surface
57
+ is unverified.
53
58
  - **Tick the resolved items.** Each handoff's `## Remediation addressed` lists what that agent fixed
54
59
  (by `file:line`). For every Remediation item an agent reports fixed, flip its `- [ ]` → `- [x]` in
55
60
  `specs/<id>.md` and append a terse ` — fixed: <what/where>` note (the convention prior rounds already
@@ -65,7 +70,7 @@ When the agents return:
65
70
  handoff content — and append ONE metrics line for the batch to `pipeline-metrics.jsonl`
66
71
  (see `/build` §4, `phase: "fix"`), chaining the opt-in usage ping in the same Bash call
67
72
  (results = items fixed over items found across surfaces, e.g. `"5/6"`).
68
- - Tell the human: re-run `/smoke` if the failures were runtime ones, and `/review $ARGUMENTS` for the
73
+ - Tell the human: re-test by hand if the failures were runtime ones, and `/review $ARGUMENTS` for the
69
74
  re-verdict — the re-review is what *verifies* the ticked items actually hold (a regression simply
70
75
  reappears as a new finding in the next round). **Recommend a `/clear`** — all state (spec,
71
76
  checkboxes, staged report) is on disk, and the lead's history is re-sent at input price every turn.
@@ -28,9 +28,13 @@ with `test -x`); note the epoch (`date +%s`) in the same call — §3's metrics
28
28
 
29
29
  - **Non-zero exit** ⇒ the script already printed the raw last-40 lines. **STOP: relay them verbatim
30
30
  and spawn NO agent** — a compiler/test failure needs `/fix` (or the human), not a review that
31
- rediscovers it at agent prices. This abort is the whole point of the step.
31
+ rediscovers it at agent prices. This abort is the whole point of the step. Before stopping, write
32
+ the **aborted verdict** (§3's contract, degraded form) so an automated driver gets a diagnosis
33
+ rather than silence:
34
+ `{"id":"$ARGUMENTS","phase":"review","ts":"<ISO>","aborted":"preflight","verdict":"BLOCK","blocking":null}`
35
+ → `specs/reports/$ARGUMENTS.verdict.json`. One `printf`, in the same Bash call.
32
36
  - **Zero exit** ⇒ it stamped `.claude/preflight.ok`, which the gate hook checks before letting
33
- `review`/`smoke` dispatches through (SCHEMA.md §Preflight). Continue.
37
+ `review` dispatches through (SCHEMA.md §Preflight). Continue.
34
38
  - Script absent (older core) ⇒ run the three commands yourself, each redirected into
35
39
  `specs/reports/$ARGUMENTS.preflight.txt`, aborting on the first failure the same way.
36
40
 
@@ -82,25 +86,100 @@ metrics line needs it.
82
86
 
83
87
  ## 3. Merge & relay the verdict
84
88
 
85
- Merge the returned reports into **one** REVIEW REPORT (same template): findings concatenated and
89
+ **Roll call FIRST — a dead reviewer is not a clean surface.** Every surface you dispatched in §2 must
90
+ come back with a REVIEW REPORT. A reviewer that died (rate limit, transport error, exhausted context)
91
+ returns **nothing**, and zero findings from a dead reviewer is byte-identical to zero findings from a
92
+ genuinely clean one — which is how "every reviewer crashed" reads as the strongest possible verdict
93
+ from no evidence at all (SCHEMA.md §Dead agents). So:
94
+
95
+ - **Retry a silent surface ONCE**, byte-identical dispatch. Most deaths are transient, and the staged
96
+ diff is already on disk — the retry costs one agent, not a re-review.
97
+ - **Silent twice ⇒ that surface is `unreviewed`.** Name it in the report under
98
+ `## NOT reviewed (no verdict on these)`, list it in the verdict JSON's `unreviewed`, and **refuse to
99
+ score `SHIP`** — the merged verdict is at least `REVISE`. Absence of evidence is not evidence of
100
+ absence, and it must never reach `/ship` or tick a DoD box.
101
+ - **Never re-review the other surfaces** to compensate: their reports are valid and already on disk.
102
+
103
+ Then merge the returned reports into **one** REVIEW REPORT (same template): findings concatenated and
86
104
  re-ordered by severity, counts summed, duplicates collapsed, verdict = the worst returned
87
- (`BLOCK` > `REVISE` > `SHIP`). Append ONE metrics line for the batch to `pipeline-metrics.jsonl`
105
+ (`BLOCK` > `REVISE` > `SHIP`). The `## Deferred` sections merge the same way (dedupe by
106
+ `file` + problem) and stay **out of the severity table and out of the verdict** — see §3.5, which
107
+ routes them. Append ONE metrics line for the batch to `pipeline-metrics.jsonl`
88
108
  (main-checkout path + rules in `/build` §4): `{"ts":"<ISO>","feature":"$ARGUMENTS","phase":"review","seconds":<wall-clock>,"surfaces":{"<key>":"<verdict>:<finding count>",…}}`.
89
109
  In the same Bash call, chain the opt-in usage ping (`/build` §4, `phase: "review"`, results = the
90
110
  merged verdict + total finding count, e.g. `"REVISE:3"`).
91
111
  **Stage the full report to `specs/reports/$ARGUMENTS.md`** (overwrite) — a gitignored buffer so a
92
112
  `/fix` after a `/clear` can still read the findings; the `specs/reports/` subfolder is skipped by the
93
113
  non-recursive `specs/*.md` glob, so it's never mistaken for a spec (no phantom card, no bogus stage).
114
+ **Write the machine-readable verdict** to `specs/reports/$ARGUMENTS.verdict.json` (overwrite) — on
115
+ **every** run, including the small-diff fast path of §2 and a `SHIP`. This file is the ONLY contract
116
+ between the pipeline and an automated driver (`/drive`), which parses no prose:
117
+
118
+ ```json
119
+ { "id": "$ARGUMENTS", "phase": "review", "ts": "<ISO>", "verdict": "REVISE",
120
+ "findings": 7, "blocking": 2, "security": 1, "deferred": 3, "unreviewed": [],
121
+ "severity": {"CRITICAL": 1, "HIGH": 2, "MEDIUM": 3, "LOW": 1},
122
+ "surfaces": {"backend": {"verdict":"BLOCK","findings":4,"blocking":2}},
123
+ "blocking_items": ["backend|apps/api/src/routes/order.ts|missing authz on post"],
124
+ "fingerprint": "b3f1c2a90d4e5f67" }
125
+ ```
126
+
127
+ - **`blocking` = CRITICAL findings + `security` findings, deduplicated** (a finding that is both
128
+ counts once). That is exactly the agent's existing verdict rule restated as a number, so
129
+ `blocking == 0` ⟺ `verdict == SHIP`. HIGH/MEDIUM/LOW quality findings are **not** blocking —
130
+ they follow the `deferred:<id>` backlog route below, and must never cost a driver an iteration.
131
+ - **`blocking_items`** — one normalized string per blocking finding, `<surface>|<file>|<problem>`:
132
+ the file path **without the `:line`** (a fix that inserts lines shifts every line below it — a
133
+ line-bearing identity would change every pass and the drift detection would never fire), and the
134
+ **problem**, not the fix, cut to its first 8 words, lowercased, every run of non-alphanumerics
135
+ collapsed to one space. Identity of a finding, not its wording.
136
+ - **`fingerprint`** — computed in the same Bash call, never by hand:
137
+ `printf '%s\n' "<item>" … | LC_ALL=C sort | sha256sum | cut -c1-16` (`shasum -a 256` where there
138
+ is no `sha256sum`). Empty list ⇒ `""`. A driver comparing two consecutive fingerprints detects a
139
+ fix loop that is treading water.
140
+ - **`deferred`** — the count of merged `## Deferred` items §3.5 parked in the backlog. Informational:
141
+ it never enters `blocking`, so it can never cost a driver an iteration.
142
+ - **`unreviewed`** — the surface keys whose reviewer died twice, `[]` on a complete run. It is
143
+ **separate from `blocking` on purpose**: `blocking` counts real findings (CRITICAL + security), and
144
+ faking a number there to force a driver's hand would corrupt the one field the whole contract rests
145
+ on. A non-empty `unreviewed` means "this run does not cover everything" — a driver treats it as no
146
+ usable verdict, never as clean, whatever `blocking` says.
147
+
148
+ ## 3.5 Route the deferred findings — the backlog, not the fix loop
149
+
150
+ Do this on **every** run, before the verdict branch below, and whatever the verdict — a deferred
151
+ finding that is only routed on a `SHIP` is a deferred finding lost on every other verdict, which is
152
+ exactly the leak this step closes. Append each merged `## Deferred` item to
153
+ **`specs/refactor-backlog.md`**, under the `## <domain>` heading of the surface that owns its
154
+ `file:line` (create the file and/or heading if absent — same grouping `/audit` writes, so
155
+ `/refactor <domain>` picks them up with no extra plumbing):
156
+
157
+ ```
158
+ - [ ] <SEVERITY> · <file:line> · <quality|security|rule> · <concrete fix> · deferred:$ARGUMENTS
159
+ ```
160
+
161
+ - **Never into the spec's `## Remediation`** — that list is what `/fix` re-dispatches and what `/drive`
162
+ waits on, so a deferred item there would re-trigger the very loop it was deferred out of.
163
+ - **Dedupe before appending:** `grep -F` the backlog for the item's `<file>` + the first words of its
164
+ problem; already there (from a prior round or an `/audit`) ⇒ skip it, don't stack duplicates round
165
+ after round.
166
+ - Append with `>>` in ONE Bash call; never read the whole backlog into context to rewrite it (it grows
167
+ with every audit the repo has ever run).
168
+ - Report it as **one line** in chat: `deferred: <n> parked in specs/refactor-backlog.md (<domains>)`.
169
+
94
170
  In chat print ONLY: the verdict, the severity-count table, a one-line digest of each CRITICAL/security
95
171
  finding, and `Full report: specs/reports/$ARGUMENTS.md` — never echo the findings body into chat (it
96
172
  would sit in this session's history, re-sent every turn). Then:
97
173
 
98
- - **SHIP** → a SHIP verdict *is* the pipeline's statement that the feature meets its Definition of
174
+ - **SHIP** → only reachable with `unreviewed` empty (the roll call above forbids it otherwise). A SHIP
175
+ verdict *is* the pipeline's statement that the feature meets its Definition of
99
176
  Done, so **tick the DoD**: in `specs/$ARGUMENTS.md` §`Acceptance criteria / DoD`, flip each `- [ ]`
100
177
  → `- [x]` for the criteria the pipeline has actually verified — spec conformance + `ui_language`
101
- copy (this review), tests · lint · typecheck (a green `/build`), mobile-first + runtime flows (a
102
- prior `/smoke`). **Leave `- [ ]` (and say which) any item whose verifying stage didn't run this
103
- cycle** — e.g. no `/smoke` ⇒ the mobile-first / runtime item stays open. Ticking is the lead's job
178
+ copy (this review), tests · lint · typecheck (a green `/build`), mobile-first as far as the code
179
+ shows it (this review). **Leave `- [ ]` (and say which) any item no stage actually verified** —
180
+ nothing in the pipeline *runs* the feature, so any criterion that needs the app up (runtime flows,
181
+ a visual check against the design) stays open unless the human says they exercised it by hand and
182
+ it held. Ticking is the lead's job
104
183
  (the reviewer is read-only). **Then stamp the freshness gate** so `/ship` can refuse to ship code
105
184
  edited after this verdict: compute `BASE=$(git merge-base <default_branch> HEAD)` and write into the
106
185
  spec front-matter `reviewed_base: $BASE` plus
@@ -108,12 +187,15 @@ would sit in this session's history, re-sent every turn). Then:
108
187
  of exactly the source you just reviewed (specs excluded, so DoD ticks + the ship status flip don't
109
188
  trip it). Then tell the human they can `/ship` — **recommend a `/clear` first**, the handoff is
110
189
  fully on disk. **SHIP with leftover LOW findings** (or LOW+MEDIUM at the human's call) does NOT
111
- force a fix cycle for nits: park them in `specs/refactor-backlog.md` tagged `deferred:<id>` (NOT as
112
- open `## Remediation` items, which would re-trigger the fix loop), keep the SHIP verdict and the
113
- freshness stamp, and let the human ship.
190
+ force a fix cycle for nits: park them through §3.5's exact route (the backlog, under their surface's
191
+ domain heading, tagged `deferred:$ARGUMENTS` — never as open `## Remediation` items, which would
192
+ re-trigger the fix loop), keep the SHIP verdict and the freshness stamp, and let the human ship.
114
193
  - **REVISE / BLOCK**, or any CRITICAL/HIGH/security finding → tell the human to run
115
194
  **`/fix $ARGUMENTS`** — it appends the report to the spec's `## Remediation` and re-dispatches ONLY
116
- the surfaces with findings. The full path (`/spec` Mode B then `/build`) remains for findings that
195
+ the surfaces with findings. (If they'd rather automate the rounds, the autonomous driver is
196
+ `disable-model-invocation: true` on purpose: **you cannot start it, they must type it**. Name the
197
+ exact line for them to type rather than attempting it — an attempt that silently fails reads as a
198
+ loop that is running when nothing is.) The full path (`/spec` Mode B then `/build`) remains for findings that
117
199
  change the contract in ways that ripple into clean surfaces. _The report is staged to
118
200
  `specs/reports/$ARGUMENTS.md`, so you can `/clear` before `/fix` — it reads the findings back from
119
201
  disk._
@@ -13,6 +13,10 @@ You run the **spec** step in the main thread — interactive, with the human. Pa
13
13
  > Template paths below (`.claude/templates/…`) resolve to `~/.claude/templates/…` when the core is
14
14
  > installed globally — read whichever exists.
15
15
  >
16
+ > **Decision journal** (SCHEMA.md §Decisions): read `specs/_decisions.md` §Live if it exists — one
17
+ > line per standing decision, so it is cheap. It is the ONLY place the project's transverse rules
18
+ > live; a spec that contradicts one silently un-decides it. Absent ⇒ nothing to honour yet.
19
+ >
16
20
  > **Kanban** (SCHEMA.md §Kanban): when the spec opens, move card `#<feature_id>` → **Spec**; on freeze
17
21
  > (`status: frozen`, Mode A) → **Ready to build**. No-op silently if no board is configured.
18
22
 
@@ -53,6 +57,22 @@ Detect the mode from the pasted content:
53
57
  Ping only on a **landed** freeze, so the funnel counts specs that exist, not attempts. Mode B does
54
58
  not ping — it re-enters an already-counted spec, and `/fix` covers that loop. Silent no-op without
55
59
  consent; never ask about consent here.
60
+ 5b. **Record the transverse decisions — the journal, not a summary.** Walk what the interview settled
61
+ and keep ONLY the decisions that **outlive this feature**: a rule the next spec would otherwise
62
+ re-litigate or contradict (auth model, id/naming scheme, where a kind of state lives, an error
63
+ convention, a deliberate non-goal that binds future features). Typical yield: **0–3 lines**; zero
64
+ is a normal, healthy outcome for a feature that decided nothing new — never invent lines to fill
65
+ the section. Append them to `specs/_decisions.md` §Live (create the file from
66
+ `.claude/templates/decisions.template.md` on first use), each exactly:
67
+ `- <YYYY-MM-DD> · <area> · <decision> — because <reason> · <feature_id>`
68
+ - **Never** duplicate what §5, `PIPELINE.md` §Conventions or the code already states — the journal
69
+ carries the *non-obvious rule*, not the feature's content. A line that restates a spec section is
70
+ a line every future `/spec` pays for and learns nothing from.
71
+ - **Contradicting an existing line** is allowed but never silent: tell the human which line this
72
+ feature overrides, get their go-ahead, then append the new line with
73
+ `· supersedes <YYYY-MM-DD> <area>` and move the old one to `## Superseded`.
74
+ - Append with one `>>` Bash call, not a full-file rewrite (the file is append-only, and reading it
75
+ back to re-write it is the one way to make a bounded file expensive).
56
76
  6. Author the **design brief** — `specs/design/<id>.md`, rendered via
57
77
  `.claude/templates/design-brief.md` (resolves to `~/.claude/templates/…` on a global install).
58
78
  _Only if `design.enabled` / the feature has UI; skip entirely for a backend-only feature._
@@ -84,7 +84,12 @@ actually connected) and repair whatever fails — wiring that worked at
84
84
  init can rot (PATH changes, uninstalls, hand-edits). Report what was reconciled; if nothing was
85
85
  missing, say so. This is why `/init-pipeline` never needs re-running for a core upgrade.
86
86
 
87
- Two of the §Reconcile steps matter specifically here:
87
+ Three of the §Reconcile steps matter specifically here:
88
+
89
+ - **Spec-template top-up** (§Reconcile step 7): `specs/_template.md` was seeded at install and never
90
+ refreshed since, so add the front-matter fields the current `templates/spec.template.md` has and the
91
+ repo's copy lacks (1.6 added `loop_pass`/`loop_phase` and two states to the `status:` comment) —
92
+ front-matter only, never the body.
88
93
 
89
94
  - **Global config seed** (§Reconcile step 5): if `~/.claude/cohorte.config.yaml` is absent, seed it
90
95
  from the template so the kanban + shared-vault config has a home. Never clobber an existing filled
@@ -28,7 +28,7 @@ prompt that can never be answered.
28
28
  Two extra duties beyond Bash patterns:
29
29
 
30
30
  - Phase gate (`preflight` block in gate-config.json): a Task dispatch of a
31
- listed subagent_type (default review/smoke) requires a fresh
31
+ listed subagent_type (default review) requires a fresh
32
32
  `.claude/preflight.ok` stamp, written by pipeline/scripts/preflight.sh when
33
33
  typecheck+lint+tests are green. Stale/missing stamp => "ask" — dispatching
34
34
  reviewers onto code that doesn't compile burns their whole run.
@@ -70,7 +70,7 @@ def load_config() -> dict:
70
70
  # Patterns gated ONLY on the default branch — allowed freely on feature branches.
71
71
  "ask_on_default_branch": list(cfg.get("ask_on_default_branch", [])),
72
72
  "default_branch": cfg.get("default_branch", "main") or "main",
73
- # Phase gate: {"enabled": true, "agents": ["review","smoke"], "max_age_minutes": 30}
73
+ # Phase gate: {"enabled": true, "agents": ["review"], "max_age_minutes": 30}
74
74
  "preflight": preflight,
75
75
  }
76
76
 
@@ -130,11 +130,11 @@ def known_heads(cwd: str):
130
130
 
131
131
 
132
132
  def check_preflight(payload: dict, cfg: dict) -> int:
133
- """Phase gate on Task dispatches: review/smoke agents need a green preflight stamp."""
133
+ """Phase gate on Task dispatches: review agents need a green preflight stamp."""
134
134
  pf = cfg.get("preflight") or {}
135
135
  if not pf.get("enabled"):
136
136
  return 0
137
- agents = pf.get("agents") or ["review", "smoke"]
137
+ agents = pf.get("agents") or ["review"]
138
138
  subagent = (payload.get("tool_input") or {}).get("subagent_type", "") or ""
139
139
  if subagent not in agents:
140
140
  return 0
@@ -0,0 +1,42 @@
1
+ # Decisions
2
+
3
+ > The project's **transverse decision journal** — the non-obvious rules a stateless agent would
4
+ > otherwise re-discover or contradict, one feature at a time. `PIPELINE.md` says *how this repo is
5
+ > built* (surfaces, commands, conventions); this file says *what was decided and why*.
6
+ >
7
+ > **Append-only. One line per decision. No prose blocks, no code.** The bound is the point: this file
8
+ > is read by `/spec`, `/brainstorm` and `/audit` — the deciding stages — and by **nothing else**.
9
+ > Implementers and reviewers never read it: they work from the frozen contract, which already tells
10
+ > them what to do; handing them the rationale too would cost `surfaces × dispatches` tokens per
11
+ > feature for a fact they cannot act on.
12
+ >
13
+ > Line shape (≤ ~160 chars, `·`-separated, no wrapping):
14
+ >
15
+ > ```
16
+ > - <YYYY-MM-DD> · <area> · <the decision, imperative> — because <the reason> · <feature_id>
17
+ > ```
18
+ >
19
+ > `<area>` is a short tag, not a path: `auth`, `data`, `api`, `ui`, `deploy`, `naming`, `surfaces`…
20
+ >
21
+ > **Reversing a decision** never edits or deletes a line — append a superseding one and move the old
22
+ > one to `## Superseded`:
23
+ >
24
+ > ```
25
+ > - <YYYY-MM-DD> · <area> · <the new decision> — because <reason> · supersedes <YYYY-MM-DD> <area> · <feature_id>
26
+ > ```
27
+ >
28
+ > **Keep it bounded:** when `## Live` passes ~100 lines, move every superseded line into
29
+ > `## Superseded` (the audit trail survives; the section the deciding stages actually read stays
30
+ > short). Never summarize or merge live lines — a decision either holds or has been superseded.
31
+ >
32
+ > **What does NOT belong here:** anything the code, the tests, `PIPELINE.md` §Conventions or a spec
33
+ > already states; a feature-local choice (that lives in its spec); a task, a TODO or a finding (those
34
+ > are `## Remediation` and `specs/refactor-backlog.md`).
35
+
36
+ ## Live
37
+
38
+ <!-- newest last -->
39
+
40
+ ## Superseded
41
+
42
+ <!-- moved here when a line above supersedes them; never deleted -->
@@ -1,9 +1,11 @@
1
1
  ---
2
2
  feature_id: <slug>
3
3
  title: <Feature title>
4
- status: draft # draft → frozen → in-review → shipped
4
+ status: draft # draft → frozen → in-progress → in-review → shipped · blocked (see SCHEMA.md §Spec status)
5
5
  branch: <feature_branch_prefix><slug>
6
6
  created: <YYYY-MM-DD>
7
+ loop_pass: 0 # /drive bookkeeping — the review pass it is on; 0 = no loop running. Written by the driver, not by hand
8
+ loop_phase: # /drive bookkeeping — build | review | fix | done. With loop_pass, this is what `--resume` reads back
7
9
  reviewed_base: # merge-base sha at the last SHIP verdict — freshness-gate anchor (written by /review)
8
10
  reviewed_digest: # sha256 (16 hex) of the reviewed source diff vs reviewed_base, specs excluded — /ship re-checks
9
11
  design_files: [] # design page links — full URLs of the form https://claude.ai/design/p/<projectId>?file=<file> (each carries its own project + page); blank until designed; omit if no UI
@@ -53,7 +55,7 @@ design_files: [] # design page links — full URLs of the form https://claude.ai
53
55
 
54
56
  > One `###` subsection per surface in `PIPELINE.md` §surfaces (e.g. backend, frontend), each TDD.
55
57
  > Surfaces are subsections of §6, never top-level sections — §8 and §9 are referenced by number
56
- > across the whole pipeline (`/smoke` drives "§8 flows", `/review` checks "§9 acceptance"), so a
58
+ > across the whole pipeline (`/build` implements "§8 flows", `/review` checks "§9 acceptance"), so a
57
59
  > third surface must not become a §8 of its own.
58
60
 
59
61
  ### <surface.key>
@@ -20,7 +20,7 @@ Ask ONLY what you couldn't confidently detect. Batch related questions. Cover:
20
20
  Recommended option: dot/failures-only reporter (`--reporter=dot` vitest/playwright, `--silent`
21
21
  jest, `-q` pytest, `--quiet` eslint/ruff — whatever the detected runner supports). These land in
22
22
  `test_quiet_cmd`/`lint_quiet_cmd` + `commands.test_quiet`/`lint_quiet` and are what agents and the
23
- `/review`·`/smoke` pre-flight actually run (SCHEMA.md §Output discipline). If the human declines or
23
+ `/review` pre-flight actually run (SCHEMA.md §Output discipline). If the human declines or
24
24
  the runner has no such flag, leave `""` — consumers then fall back to `<cmd> 2>&1 | tail -40`.
25
25
  - **Contract** — mechanism (`shared-types-zod` / `openapi` / `protobuf` / `json-schema` / `none`) and
26
26
  where feature contracts are authored. If `none`, surfaces sync by the spec prose alone.
@@ -15,7 +15,7 @@
15
15
  (`<SURFACE_EXTRA_NEVER>`, `<SURFACE_DESIGN_INPUT>`, `<SURFACE_TDD_STEP1>` — fill design-related ones
16
16
  only when `uses_design`).
17
17
  Leave the fixed agents as-is (generic, shipped by the installer): `review.md`, `release.md`,
18
- `smoke.md`, `profile-reader.md`.
18
+ `profile-reader.md`.
19
19
  4. **Generate `.claude/gate-config.json`** from the `gate` block — copy all five keys verbatim:
20
20
  `{"deny": [...], "ask": [...], "ask_on_default_branch": [...], "default_branch": "<vcs.default_branch>",
21
21
  "preflight": {"enabled": <gate.preflight.enabled>, "agents": [...], "max_age_minutes": <n>}}`
@@ -27,7 +27,11 @@
27
27
  variants** and repo-wide `commands.*` equivalents as `Bash(<cmd>:*)` rules, plus read-only git —
28
28
  `Bash(git status:*)`, `Bash(git diff:*)`, `Bash(git log:*)`, `Bash(git rev-parse:*)` — plus the
29
29
  shipped pipeline scripts for BOTH cores (`Bash(.claude/pipeline/scripts/:*)` and
30
- `Bash(~/.claude/pipeline/scripts/:*)` — preflight, kanban-move, telemetry-send), and the
30
+ `Bash(~/.claude/pipeline/scripts/:*)` — preflight, kanban-move, telemetry-send) **plus the
31
+ `bash`-prefixed form the `/drive` driver uses** (`Bash(bash .claude/pipeline/scripts/loop.sh:*)`
32
+ and `Bash(bash ~/.claude/pipeline/scripts/loop.sh:*)`) — those prefix rules match a command
33
+ *starting* with the path, so `bash <path>` needs its own entry or `/drive` stalls on a permission
34
+ prompt at every launch, and the
31
35
  retrieval provider's MCP tools when wired (e.g. `mcp__serena`). Never allowlist anything matching
32
36
  a `gate.ask`/`gate.deny` pattern. Mention the human can widen it later with
33
37
  `/fewer-permission-prompts`) + the hooks, **conditioned on the install mode:**
@@ -86,8 +90,8 @@
86
90
  `build_cmd`s that are non-empty). Derive the setup steps from the detected stack — mirror what a
87
91
  sibling workflow does if one exists. `/ship` watches these checks before the merge.
88
92
  11. **Metrics sink & report buffer:** add `.claude/pipeline-metrics.jsonl` to `.gitignore` — `/build`,
89
- `/review`, `/fix` and `/smoke` append per-dispatch evidence there (SCHEMA §Specialization reads it).
90
- Also add `specs/reports/` — `/review` and `/smoke` stage their last report there so a `/fix` (or
93
+ `/review` and `/fix` append per-dispatch evidence there (SCHEMA §Specialization reads it).
94
+ Also add `specs/reports/` — `/review` stages its last report there so a `/fix` (or
91
95
  `/spec` Mode B) survives a `/clear`; it's a derived buffer, not a versioned artifact.
92
96
  12. **Design system:** if `design.enabled` with a snapshot dir, note that `/align-ds` is active; else the
93
97
  `/align-ds` command will no-op with a clear message.
@@ -131,11 +131,29 @@ const FINDING = {
131
131
  },
132
132
  }
133
133
 
134
+ // A DEFERRED finding is real but out of this feature's scope (pre-existing code the
135
+ // diff never touched). It carries its own out-of-scope reason, counts in no severity
136
+ // row, is never cross-checked, and can never move the verdict — it is routed to
137
+ // specs/refactor-backlog.md so /refactor owns it. See core/agents/review.md §Deferred.
138
+ const DEFERRED = {
139
+ type: 'object', required: ['severity', 'file', 'line', 'kind', 'problem', 'fix', 'outOfScope'],
140
+ additionalProperties: false,
141
+ properties: {
142
+ severity: { enum: ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'] },
143
+ file: { type: 'string' }, line: { type: 'integer' },
144
+ kind: { enum: ['quality', 'security', 'rule'] },
145
+ problem: { type: 'string', description: 'one line, no code excerpts' },
146
+ fix: { type: 'string', description: 'one concrete change, one line' },
147
+ outOfScope: { type: 'string', description: 'one line: why this feature does not own it' },
148
+ },
149
+ }
150
+
134
151
  const REPORT = {
135
152
  type: 'object', required: ['verdict', 'findings'], additionalProperties: false,
136
153
  properties: {
137
154
  verdict: { enum: ['SHIP', 'REVISE', 'BLOCK'] },
138
155
  findings: { type: 'array', maxItems: 20, items: FINDING },
156
+ deferred: { type: 'array', maxItems: 10, items: DEFERRED },
139
157
  overflow: { type: 'integer', description: 'findings beyond the 20-item cap, if any' },
140
158
  notes: { type: 'string', description: 'RBAC / mobile-first assessment only, when the profile enables them' },
141
159
  },
@@ -222,7 +240,8 @@ const reviewed = await pipeline(
222
240
  s => agent(
223
241
  'Review one feature surface against its frozen spec, per your agent instructions (read the staged ' +
224
242
  'diff FIRST; open a full source file only when a finding demands it; capped shape — max 20 findings, ' +
225
- 'one line each, no code excerpts). — Variable slots: ' +
243
+ 'one line each, no code excerpts). Put anything real but OUT of this feature\'s scope in `deferred` ' +
244
+ '(max 10, each with its out-of-scope reason) per your §Deferred rules — never in `findings`. — Variable slots: ' +
226
245
  `feature ${feature} · scope: the ${s.key} surface only · spec: specs/${feature}.md · ` +
227
246
  `staged diff: ${s.diff} · changed files: ${s.files.join(', ')}`,
228
247
  { agentType: 'review', label: `review:${s.key}`, phase: 'Review', schema: REPORT },
@@ -231,7 +250,10 @@ const reviewed = await pipeline(
231
250
  if (!report) return null
232
251
  const hard = report.findings.filter(f => f.severity === 'CRITICAL' || f.kind === 'security')
233
252
  const rest = report.findings.filter(f => !hard.includes(f))
234
- if (!hard.length) return { key: s.key, report, kept: rest, refuted: [] }
253
+ // Deferred items skip the cross-check entirely: refuting one would spend an agent
254
+ // to argue about something that cannot affect the verdict either way.
255
+ const deferred = report.deferred || []
256
+ if (!hard.length) return { key: s.key, report, kept: rest, refuted: [], deferred }
235
257
  const votes = await parallel(hard.map(f => () => agent(
236
258
  'Adversarially verify ONE review finding — your job is to REFUTE it if you can. Read the staged ' +
237
259
  'diff and the exact file:line; refuted=true when the code, a guard, a test, or the spec shows the ' +
@@ -246,6 +268,7 @@ const reviewed = await pipeline(
246
268
  report,
247
269
  kept: rest.concat(checkedVotes.filter(v => !v.refuted).map(v => v.f)),
248
270
  refuted: checkedVotes.filter(v => v.refuted).map(v => ({ ...v.f, reason: v.reason })),
271
+ deferred,
249
272
  }
250
273
  },
251
274
  )
@@ -259,6 +282,9 @@ const unreviewed = touched.filter(s => !results.some(r => r.key === s.key)).map(
259
282
  if (unreviewed.length) log(`Reviewer died on: ${unreviewed.join(', ')} — those surfaces are NOT reviewed`)
260
283
  const kept = results.flatMap(r => r.kept.map(f => ({ ...f, surface: r.key })))
261
284
  const refuted = results.flatMap(r => r.refuted.map(f => ({ ...f, surface: r.key })))
285
+ // Deferred findings are deliberately kept OUT of `counts`, out of `verdict` and out
286
+ // of `clean`: they belong to /refactor, not to this feature's fix loop.
287
+ const deferredAll = results.flatMap(r => (r.deferred || []).map(f => ({ ...f, surface: r.key })))
262
288
  const counts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 }
263
289
  for (const f of kept) counts[f.severity] = (counts[f.severity] || 0) + 1
264
290
  // Verdict from the findings that SURVIVED the cross-check (a refuted CRITICAL
@@ -283,6 +309,10 @@ const reportBody = [
283
309
  ...['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'].map(s => `| ${s} | ${counts[s] || 0} |`), '',
284
310
  `Verdict: ${verdict}`, '', '## Findings', '',
285
311
  kept.length ? kept.map(findingLine).join('\n') : 'None.',
312
+ '', '## Deferred', '',
313
+ deferredAll.length
314
+ ? deferredAll.map(f => `${findingLine(f)} · out of scope: ${f.outOfScope} · deferred:${feature}`).join('\n')
315
+ : 'None.',
286
316
  ...(unreviewed.length ? ['', '## NOT reviewed (reviewer died — no verdict on these)', '',
287
317
  unreviewed.map(k => `- \`${k}\` — re-run /review ${feature} (or the review workflow)`).join('\n')] : []),
288
318
  ...(refuted.length ? ['', '## Refuted by cross-check (no action needed)', '',
@@ -295,6 +325,17 @@ const staging = await agent(
295
325
  `{"ts":"<ISO now>","feature":"${feature}","phase":"review","seconds":0,"surfaces":{${results.map(r => `"${r.key}":"${verdict}:${r.kept.length}"`).join(',')}}}\n` +
296
326
  `3. Chain the opt-in usage ping: <core>/pipeline/scripts/telemetry-send.sh review "${feature}" 0 "${verdict}:${kept.length}" || true ` +
297
327
  '(<core> = .claude if .claude/pipeline/scripts/telemetry-send.sh exists, else ~/.claude; script on neither ⇒ skip the ping).\n' +
328
+ // Deferred findings must land in the backlog on EVERY verdict — parked only on a
329
+ // SHIP is parked nowhere the rest of the time, which is the leak this closes.
330
+ (deferredAll.length
331
+ ? `3b. Route the deferred findings to specs/refactor-backlog.md (create it if absent): for each line below, ` +
332
+ `append it under the \`## <domain>\` heading named in its prefix (create that heading if absent) — with \`>>\`, ` +
333
+ `never by rewriting the file, and skip any whose file path + first words already appear there (grep -F first, ` +
334
+ `they may be left from a prior round or an /audit):\n` +
335
+ deferredAll.map(f =>
336
+ `${f.surface}||- [ ] ${f.severity} · ${f.file}:${f.line} · ${f.kind} · ${f.fix} · deferred:${feature}`).join('\n') +
337
+ '\n'
338
+ : '') +
298
339
  // Stamp + tick only when nothing above LOW survived: the conversational /review
299
340
  // keeps the stamp only for LOW findings, and a SHIP verdict here can still carry
300
341
  // HIGH/MEDIUM ones — certifying those for /ship would ship known defects. A dead
@@ -318,6 +359,7 @@ const staged_ok = staging != null && /done/i.test(String(staging))
318
359
  return {
319
360
  verdict,
320
361
  counts,
362
+ deferred: deferredAll.length, // parked in the backlog for /refactor — never blocking
321
363
  refutedByCrossCheck: refuted.length,
322
364
  reportStaged: staged_ok,
323
365
  unreviewedSurfaces: unreviewed, // reviewers that died — these carry NO verdict