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.
- package/CHANGELOG.md +151 -0
- package/README.md +43 -12
- package/bin/cli.js +13 -2
- package/core/agents/review.md +23 -0
- package/core/commands/audit.md +9 -1
- package/core/commands/brainstorm.md +6 -0
- package/core/commands/build.md +93 -8
- package/core/commands/doctor.md +13 -8
- package/core/commands/drive.md +80 -0
- package/core/commands/fix.md +10 -5
- package/core/commands/review.md +94 -12
- package/core/commands/spec.md +20 -0
- package/core/commands/update-pipeline.md +6 -1
- package/core/hooks/gate.py +4 -4
- package/core/templates/decisions.template.md +42 -0
- package/core/templates/spec.template.md +4 -2
- package/core/templates/steps/init-pipeline/02-interview-gaps.md +1 -1
- package/core/templates/steps/init-pipeline/04-write-render.md +8 -4
- package/core/workflows/review.js +44 -2
- package/dashboard/dist/assets/{index-AFQnlfjO.css → index-BZ_LQlEj.css} +1 -1
- package/dashboard/dist/assets/index-DYyn4p93.js +43 -0
- package/dashboard/dist/index.html +2 -2
- package/dashboard/server/doctor.js +13 -3
- package/dashboard/server/index.js +7 -0
- package/dashboard/server/metrics.js +4 -4
- package/dashboard/server/usage.js +61 -0
- package/install.ps1 +12 -1
- package/install.sh +12 -2
- package/package.json +1 -1
- package/profile/PIPELINE.template.md +3 -3
- package/profile/SCHEMA.md +150 -12
- package/scripts/loop.sh +318 -0
- package/scripts/metrics/collect.mjs +11 -2
- package/scripts/preflight.sh +2 -2
- package/scripts/telemetry-send.sh +5 -2
- package/scripts/test-dashboard.mjs +22 -2
- package/scripts/test-gate.mjs +1 -2
- package/scripts/test-loop.mjs +227 -0
- package/scripts/test-metrics.mjs +12 -3
- package/scripts/test-workflows.mjs +28 -0
- package/scripts/validate-core.mjs +21 -6
- package/core/agents/smoke.md +0 -63
- package/core/commands/smoke.md +0 -55
- 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.
|
package/core/commands/fix.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
model: sonnet
|
|
3
|
-
description: Apply a REVIEW REPORT
|
|
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
|
|
20
|
-
session's last `/review`
|
|
21
|
-
`specs/reports/<id>.md`, where `/review`
|
|
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-
|
|
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.
|
package/core/commands/review.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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`).
|
|
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** →
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
112
|
-
open `## Remediation` items, which would
|
|
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.
|
|
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._
|
package/core/commands/spec.md
CHANGED
|
@@ -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
|
-
|
|
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
|
package/core/hooks/gate.py
CHANGED
|
@@ -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
|
|
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"
|
|
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
|
|
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"
|
|
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 (`/
|
|
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
|
|
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
|
-
`
|
|
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)
|
|
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
|
|
90
|
-
Also add `specs/reports/` — `/review`
|
|
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.
|
package/core/workflows/review.js
CHANGED
|
@@ -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).
|
|
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
|
-
|
|
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
|