cohorte 1.3.4 → 1.5.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 +110 -0
- package/README.md +36 -16
- package/bin/cli.js +34 -4
- package/core/agents/profile-reader.md +22 -0
- package/core/commands/build.md +4 -4
- package/core/commands/doctor.md +10 -8
- package/core/commands/fix.md +5 -5
- package/core/commands/loop.md +61 -0
- package/core/commands/review.md +38 -5
- package/core/hooks/gate.py +4 -4
- package/core/templates/spec.template.md +1 -1
- 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/audit.js +68 -4
- package/core/workflows/refactor.js +69 -4
- package/core/workflows/review.js +73 -4
- package/dashboard/dist/assets/index-8owBnqyv.js +43 -0
- package/dashboard/dist/assets/{index-AFQnlfjO.css → index-dkO8UUVl.css} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dashboard/server/doctor.js +15 -5
- package/dashboard/server/index.js +7 -0
- package/dashboard/server/metrics.js +6 -5
- package/dashboard/server/usage.js +61 -0
- package/install.ps1 +4 -1
- package/install.sh +5 -2
- package/package.json +1 -1
- package/profile/PIPELINE.template.md +3 -3
- package/profile/SCHEMA.md +21 -44
- package/scripts/loop.sh +189 -0
- package/scripts/metrics/collect.mjs +504 -0
- package/scripts/metrics/prices.json +39 -0
- package/scripts/preflight.sh +2 -2
- package/scripts/telemetry-send.sh +5 -2
- package/scripts/test-dashboard.mjs +29 -3
- package/scripts/test-gate.mjs +1 -2
- package/scripts/test-metrics.mjs +144 -0
- package/scripts/test-workflows.mjs +56 -178
- package/scripts/validate-core.mjs +10 -9
- package/core/agents/smoke.md +0 -63
- package/core/commands/cycle.md +0 -61
- package/core/commands/smoke.md +0 -55
- package/core/workflows/cycle.js +0 -513
- package/dashboard/dist/assets/index-DLBzciIC.js +0 -43
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,116 @@
|
|
|
3
3
|
Entries are shown by `/update-pipeline` ("What's new") after a core refresh. Keep them short,
|
|
4
4
|
user-facing, most recent first. One `## <version> — <YYYY-MM-DD>` section per release.
|
|
5
5
|
|
|
6
|
+
## 1.5.0 — 2026-08-01
|
|
7
|
+
|
|
8
|
+
> **Re-run `npx cohorte@latest update --global` (or `update`)** to pick up the collector and the
|
|
9
|
+
> `/smoke` removal — the update *deletes* the command and its agent from your install, it does not
|
|
10
|
+
> just stop shipping them. The new dashboard panel comes with `npx cohorte dashboard`.
|
|
11
|
+
|
|
12
|
+
- **New — `/loop <id>`: the review ⇄ fix cycle, run for you.** `/build` → `/review` → `/fix` →
|
|
13
|
+
`/review` … until a review reports **zero blocking findings** (a CRITICAL or a security issue —
|
|
14
|
+
a LOW nit never costs a pass), or the pass ceiling (`--max=N`, default 5), or two consecutive
|
|
15
|
+
reviews returning the *same* blocking findings, which means the fix is treading water and more
|
|
16
|
+
passes won't help. `--no-build` re-runs the loop on an already-built feature; `--rebuild` forces
|
|
17
|
+
a build. Every fix pass is committed (`loop(<id>): fix pass <i>`) — your way back after N
|
|
18
|
+
autonomous passes — and **no fix runs on the last pass**, since fixing without a review behind
|
|
19
|
+
it leaves unaudited code. Exit codes distinguish clean · ceiling · no verdict · non-convergent ·
|
|
20
|
+
usage, so a wrapper can tell "needs more passes" from "needs a human".
|
|
21
|
+
- **The loop does not run in your session — that's the whole design.** Each phase is a separate
|
|
22
|
+
`claude -p` child with its own fresh context, driven by the new shipped `loop.sh`; all child
|
|
23
|
+
output goes to `specs/reports/<id>.loop.log`, which the command is forbidden to read back. Your
|
|
24
|
+
session sees one line per phase and a three-line summary. A slash command cannot `/clear` itself,
|
|
25
|
+
so a conversational loop would pile the diff plus N review reports plus N contracts into a
|
|
26
|
+
history re-sent at input price every turn — it would cost more than the loop saves.
|
|
27
|
+
`disable-model-invocation: true`: an autonomous loop only ever starts because you asked.
|
|
28
|
+
- **`/review` now writes a machine-readable verdict** to `specs/reports/<id>.verdict.json` on every
|
|
29
|
+
run — verdict, finding counts by severity, per-surface breakdown, the normalized blocking items
|
|
30
|
+
and a stable `fingerprint` over them. It is the only contract between the pipeline and any
|
|
31
|
+
driver; no prose is parsed. `blocking` restates the reviewer's existing rule as a number
|
|
32
|
+
(CRITICAL + security, deduplicated), so `blocking == 0` ⟺ `SHIP`. The fingerprint hashes
|
|
33
|
+
*surface + file + problem* with the line number deliberately dropped — a fix that inserts lines
|
|
34
|
+
would otherwise change it every pass and the drift detection would never fire. A red preflight
|
|
35
|
+
writes a degraded `{"aborted":"preflight"}` verdict rather than nothing, so an abort is a
|
|
36
|
+
diagnosis instead of a silence.
|
|
37
|
+
- **BREAKING — `/smoke` and the `smoke` agent are removed.** The end-to-end run phase is gone:
|
|
38
|
+
the command, the agent, its preflight wiring, its telemetry phase and its documentation. The
|
|
39
|
+
loop is now `/brainstorm` → `/spec` → `/build` → `/review` → (`/fix` → `/review`)* → `/ship`,
|
|
40
|
+
with `/clear` safe between each. Nothing else depended on it; a `/smoke` in an old habit will
|
|
41
|
+
report an unknown command.
|
|
42
|
+
- **Nothing in the pipeline runs your app any more — that part is yours.** `/build` now closes by
|
|
43
|
+
telling you to exercise the feature by hand if it's worth it, and `/fix` says the same for
|
|
44
|
+
runtime failures. `/review` follows suit at the SHIP verdict: it ticks only what a stage
|
|
45
|
+
actually verified, and **leaves any DoD criterion that needs the app up open** (runtime flows,
|
|
46
|
+
a visual check against the design) unless you say you exercised it yourself and it held.
|
|
47
|
+
- **The preflight phase gate now gates `review` alone** (`gate.preflight.agents` defaults to
|
|
48
|
+
`[review]`). Existing profiles that list `smoke` keep working — the hook just never sees that
|
|
49
|
+
dispatch. `/doctor` compares against the new default, so re-run it after the update if it
|
|
50
|
+
flags gate drift.
|
|
51
|
+
- **Retired-phase data still renders.** Metrics files and dashboards carrying `phase: "smoke"`
|
|
52
|
+
keep their column, the transcript collector keeps attributing past `/smoke` runs to `/smoke`
|
|
53
|
+
instead of silently reclassifying them, and `telemetry-send.sh` still accepts the phase from a
|
|
54
|
+
stale install. Same treatment `/cycle` got in 1.4.0.
|
|
55
|
+
- **The cockpit now shows what a feature actually cost.** The dashboard's only metrics source
|
|
56
|
+
was `pipeline-metrics.jsonl`, written by the model itself — so it misses any run that ended
|
|
57
|
+
early and can never report tokens. On a real project it had captured 18 phase batches where
|
|
58
|
+
the transcripts hold 53 runs. The new **Cost & runtime** panel reads
|
|
59
|
+
`cohorte metrics` instead: per command, the number of runs, $ per run, $ total, tokens, wall
|
|
60
|
+
and active time, and the median number of subagents dispatched. That last column is the one
|
|
61
|
+
that makes a broken run obvious — a `/build` reporting 0 agents did no fan-out at all.
|
|
62
|
+
- **Both metrics sources are kept, because they answer different questions.** `pipeline-metrics.jsonl`
|
|
63
|
+
carries per-surface verdicts (`ok`, `REVISE:2`, `error`) that only the model knows and the
|
|
64
|
+
transcripts never contain; the collector carries money and time, which the model cannot report
|
|
65
|
+
and the transcripts record exactly. The two panels sit side by side and each says what it is
|
|
66
|
+
for. Neither replaces the other.
|
|
67
|
+
- **Fixed: discussing a command counted as running it.** An inline command mention was treated
|
|
68
|
+
as an invocation regardless of context, so a long message *about* `/review` billed that whole
|
|
69
|
+
conversation to `/review` — in cohorte's own repo it invented five `/cycle` runs out of a
|
|
70
|
+
design discussion. Inline mentions are now length-gated (an instruction is short; a discussion
|
|
71
|
+
is not); an explicit slash-command invocation is always counted.
|
|
72
|
+
|
|
73
|
+
## 1.4.0 — 2026-08-01
|
|
74
|
+
|
|
75
|
+
> **Re-run `npx cohorte@latest update --global` (or `update`)** — the workflow fixes only apply
|
|
76
|
+
> once the installed core is refreshed. Both the workflow scripts and the `profile-reader` agent
|
|
77
|
+
> are replaced by the update.
|
|
78
|
+
|
|
79
|
+
- **BREAKING — `/cycle` and `cycle.js` are removed.** The full-cycle workflow is gone: the command
|
|
80
|
+
file, the script, its tests and its documentation. The conversational path it wrapped is
|
|
81
|
+
unchanged and remains the way to run a feature — `/build` → `/smoke` → `/review` → `/fix` →
|
|
82
|
+
`/ship`, with `/clear` safe between each. `review.js`, `audit.js` and `refactor.js` are
|
|
83
|
+
untouched. Nothing else in the pipeline depended on it; a `/cycle` in an old habit will simply
|
|
84
|
+
report an unknown command. Metrics files and dashboards that already carry `phase: "cycle"`
|
|
85
|
+
lines keep rendering them.
|
|
86
|
+
- **A workflow could dispatch zero agents and still report a verdict.** Phase 0's `profile-reader`
|
|
87
|
+
(haiku) intermittently returned the profile as a JSON *string* nested under a wrapper field
|
|
88
|
+
(`{"output": "{\"surfaces\": …}"}`) instead of at the top level. The schema was
|
|
89
|
+
`{type: 'object', additionalProperties: true}` — no declared properties, no required keys — so
|
|
90
|
+
the wrapper validated cleanly and every field then read as `undefined`: `surfaces` fell back to
|
|
91
|
+
`[]`, `parallel([])` dispatched **nothing**, and because every later guard compares against
|
|
92
|
+
`surfaces`, an empty list made them all vacuously pass. The run finished with a verdict, no code
|
|
93
|
+
written, and no complaint — indistinguishable from a clean run with an empty diff. Fixed in three
|
|
94
|
+
places: `profile-reader.md` now states that the profile's keys go at the top level of the
|
|
95
|
+
structured-output tool (with the wrong shapes shown), the schema declares what it expects, and a
|
|
96
|
+
profile with no surfaces **aborts loudly** instead of proceeding. All three workflows.
|
|
97
|
+
- **`args` given as a JSON string became the feature id.** A caller that JSON-encoded its arguments
|
|
98
|
+
got that whole blob used as the id — which is how a report was written to
|
|
99
|
+
`specs/reports/{"feature": "x"}.md` — and the other options (`maxRounds`, `smoke`) silently read
|
|
100
|
+
as `undefined` on the same run, so a run could skip smoke without saying so. `args` is now parsed
|
|
101
|
+
back into an object (a bare slug is still valid shorthand), and a feature id that is not a slug
|
|
102
|
+
throws with an actionable message **before** anything touches the filesystem — so no junk file
|
|
103
|
+
can be written, and a path-shaped id is rejected.
|
|
104
|
+
- **`/doctor` warned about a file cohorte itself had written.** `/audit` writes
|
|
105
|
+
`specs/refactor-backlog.md` by design; the spec scanner globbed `specs/*.md` and flagged it for
|
|
106
|
+
having no valid front-matter `status`. It fired in every project that had ever run `/audit`. Both
|
|
107
|
+
the conversational `/doctor` and the dashboard port now exclude it.
|
|
108
|
+
- **New: `cohorte metrics` — real cost and runtime per command.** Reconstructs tokens, USD,
|
|
109
|
+
wall/active time and subagent counts from Claude Code's own transcripts, so it needs nothing
|
|
110
|
+
enabled and works retroactively on runs that already happened. It is worktree-aware (a feature
|
|
111
|
+
built across worktrees adds up instead of being dropped), attributes subagent spend back to the
|
|
112
|
+
command that spawned it, and de-duplicates the repeated `usage` blocks a single API response
|
|
113
|
+
writes across several transcript lines — summing those naively inflates tokens ~1.8×.
|
|
114
|
+
`--json`, `--runs`, `--days=N`, `--since=ISO`. Prices live in `scripts/metrics/prices.json`.
|
|
115
|
+
|
|
6
116
|
## 1.3.4 — 2026-07-31
|
|
7
117
|
|
|
8
118
|
> **Re-run `npx cohorte@latest update --global` (or `update`)** — the workflow and script
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ then one command per project (`/init-pipeline`) adapts it to that project's stac
|
|
|
20
20
|
**stateless agents** that only communicate through a frozen contract:
|
|
21
21
|
|
|
22
22
|
```
|
|
23
|
-
/brainstorm → /spec → (design) → /build <id> → /
|
|
23
|
+
/brainstorm → /spec → (design) → /build <id> → /review → (/fix) → /ship
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
## How it works — three layers
|
|
@@ -217,10 +217,9 @@ it in `.claude/pipeline/VERSION` and bundled repos in their committed `pipeline.
|
|
|
217
217
|
| `/brainstorm` | Interactive persona panel that pressure-tests a feature idea. |
|
|
218
218
|
| `/spec` | Freeze the feature spec + contract into `specs/<id>.md` (UI features also get a standalone design brief at `specs/design/<id>.md`). Also applies review returns. |
|
|
219
219
|
| `/build <id>` | Lead authors the contract, then dispatches one implementer per surface in parallel. |
|
|
220
|
-
| `/cycle <id>` | Launch the full dev-cycle **workflow** on a frozen spec: build → review → fix until zero findings (add `smoke` to run the app each round); deferred questions in the output. Needs workflows enabled (falls back to the conversational path). |
|
|
221
|
-
| `/smoke <id>` | Run the feature for real: infra up, contract endpoints, UI flows, design conformance. |
|
|
222
220
|
| `/review <id>` | Read-only review agents (one per touched surface, parallel) audit the diff vs the spec. |
|
|
223
|
-
| `/fix <id>` | Apply a review
|
|
221
|
+
| `/fix <id>` | Apply a review report: remediation into the spec, re-dispatch only the surfaces with findings. |
|
|
222
|
+
| `/loop <id>` | Autonomous `/build → /review → /fix → /review …` until no blocking finding is left (see below). |
|
|
224
223
|
| `/ship <id>` | Release agent commits, pushes, opens the PR; watches CI; proposes worktree teardown. |
|
|
225
224
|
| `/audit [path]` | Prioritized refactor backlog for existing code. |
|
|
226
225
|
| `/refactor <domain>` | Apply the backlog for one surface, TDD-first. |
|
|
@@ -231,12 +230,12 @@ it in `.claude/pipeline/VERSION` and bundled repos in their committed `pipeline.
|
|
|
231
230
|
### Run the loop cheaply — `/clear` between stages
|
|
232
231
|
|
|
233
232
|
Every command reloads all the state it needs **from disk** — the frozen spec, the contract, the diff, the
|
|
234
|
-
Remediation checkboxes, the freshness stamp, and the last `/review
|
|
233
|
+
Remediation checkboxes, the freshness stamp, and the last `/review` report (staged to a gitignored
|
|
235
234
|
`specs/reports/<id>.md`). Nothing essential lives in the conversation. So the loop is **`/clear`-safe at
|
|
236
235
|
every boundary**:
|
|
237
236
|
|
|
238
237
|
```
|
|
239
|
-
/spec → /clear → /build → /clear → /
|
|
238
|
+
/spec → /clear → /build → /clear → /review → /clear → /fix → /clear → /review → /ship
|
|
240
239
|
```
|
|
241
240
|
|
|
242
241
|
`/clear`-ing between stages sheds the accumulated main-thread context, which is the single biggest token
|
|
@@ -244,6 +243,29 @@ lever: long sessions (>150k) are expensive even when cached. Each command tells
|
|
|
244
243
|
safe to clear. If you'd rather stay in one session, `/compact` mid-task does the lighter version. (Claude
|
|
245
244
|
can't fire `/clear` itself — it's a client-side command; the pipeline just makes it always safe to type.)
|
|
246
245
|
|
|
246
|
+
### Let it run itself — `/loop`
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
/loop feat-x # /build, then /review ⇄ /fix until clean (max 5 passes)
|
|
250
|
+
/loop feat-x --no-build # already built — just re-run the /review ⇄ /fix loop
|
|
251
|
+
/loop feat-x --max=8
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
It stops when `/review` reports **zero blocking findings** (a CRITICAL or a security issue — a LOW
|
|
255
|
+
nit never costs a pass), at the pass ceiling, or as soon as two consecutive reviews return the same
|
|
256
|
+
blocking findings, which means the fix is treading water and more passes won't help. **Each fix pass
|
|
257
|
+
is committed** (`loop(<id>): fix pass <i>`) — that's your way back after N autonomous passes — and
|
|
258
|
+
**no fix runs on the last pass**, since fixing without a review behind it leaves unaudited code.
|
|
259
|
+
|
|
260
|
+
**The loop does not run in your session.** Each phase is a separate `claude -p` child with its own
|
|
261
|
+
fresh context, driven by `pipeline/scripts/loop.sh`; all of their output goes to
|
|
262
|
+
`specs/reports/<id>.loop.log`, which the command is forbidden to read back. Your session sees one
|
|
263
|
+
line per phase and a three-line summary. That's the whole design: a slash command can't `/clear`
|
|
264
|
+
itself, so a conversational loop would pile the diff plus N review reports plus N contracts into a
|
|
265
|
+
history that is re-sent at input price every turn — it would cost more than the loop saves. The
|
|
266
|
+
machine contract is `specs/reports/<id>.verdict.json`, which `/review` now writes on every run; no
|
|
267
|
+
prose is ever parsed.
|
|
268
|
+
|
|
247
269
|
### Run features in parallel — one session per feature
|
|
248
270
|
|
|
249
271
|
With `isolation.enabled`, every feature already gets its own worktree, ports, and database
|
|
@@ -257,8 +279,8 @@ The pattern:
|
|
|
257
279
|
```
|
|
258
280
|
session 1 (main checkout): /spec feat-a → /build feat-a (agents run…)
|
|
259
281
|
session 2 (main checkout): /spec feat-b → /build feat-b (agents run…)
|
|
260
|
-
session 1: /
|
|
261
|
-
session 2: /
|
|
282
|
+
session 1: /review feat-a → /ship feat-a
|
|
283
|
+
session 2: /review feat-b → …
|
|
262
284
|
```
|
|
263
285
|
|
|
264
286
|
Rules that make it safe:
|
|
@@ -267,7 +289,7 @@ Rules that make it safe:
|
|
|
267
289
|
(`specs/<id>.md`, `<contract.path>/<id>.*`, `specs/reports/<id>*`), so sessions never share state —
|
|
268
290
|
but a single session interleaving two features accumulates both in its context, paying for both.
|
|
269
291
|
- **Disjoint surfaces per feature are guaranteed** (each worktree is a full checkout), and each
|
|
270
|
-
feature's DB/ports come from its slot —
|
|
292
|
+
feature's DB/ports come from its slot — two features' dev servers collide on neither.
|
|
271
293
|
- **The contract package is the one shared tree.** Two features editing
|
|
272
294
|
`<contract.path>/<their-own-id>.<ext>` never conflict (one file per feature); merge order only
|
|
273
295
|
matters if a later feature *imports* an earlier one's contract — ship the dependency first.
|
|
@@ -284,7 +306,6 @@ it out turn by turn:
|
|
|
284
306
|
|
|
285
307
|
| Script | What it runs |
|
|
286
308
|
| ----------------------- | ----------------------------------------------------------------------------------------- |
|
|
287
|
-
| `workflows/cycle.js` | **The full dev cycle on a frozen spec**: contract → parallel build → review(+cross-check) → fix (∥ smoke each round if opted in via `args.smoke`), looping until zero findings (contract changes handled in-loop by a lead-equivalent agent). Human decisions come back in a `questions` array at the END — empty when `/brainstorm`+`/spec` did their job. Exits SHIP-ready (DoD ticked, freshness stamped) so `/ship` is a straight shot. |
|
|
288
309
|
| `workflows/review.js` | Preflight gate (aborts while red — zero agents), one reviewer per touched surface, adversarial cross-check of CRITICAL/security findings, merged verdict only. |
|
|
289
310
|
| `workflows/audit.js` | One auditor per domain (every surface + shared) concurrently, prioritized `specs/refactor-backlog.md`. |
|
|
290
311
|
| `workflows/refactor.js` | Big domains only: `shared` first and alone, then parallel surface implementers, per-domain verify + one retry. |
|
|
@@ -297,10 +318,9 @@ The essentials:
|
|
|
297
318
|
- **Prerequisite: Claude Code ≥ 2.1.154** with workflows enabled. `/doctor` (check 8) tells you
|
|
298
319
|
which path your session will take and why.
|
|
299
320
|
- **No input mid-run — questions at the edges.** A workflow runs to completion without asking
|
|
300
|
-
anything
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
command gate still fires inside workflow subagents; in unattended runs its confirms become denies.
|
|
321
|
+
anything: whatever would have been a mid-run question lands in the result at the end. The
|
|
322
|
+
destructive-command gate still fires inside workflow subagents; in unattended runs its confirms
|
|
323
|
+
become denies.
|
|
304
324
|
- Phase 0 of every script is the `profile-reader` agent (haiku) — it reads `PIPELINE.md` and hands
|
|
305
325
|
the script the profile as JSON, since workflow scripts have no filesystem access. Mechanical
|
|
306
326
|
phases run on haiku; judgment phases use the same pinned agents as the commands.
|
|
@@ -334,11 +354,11 @@ bin/cli.js # the npm CLI: install / update / dashboard / version (c
|
|
|
334
354
|
install.sh # script installer (fresh + --update) for no-Node environments
|
|
335
355
|
install.ps1 # same installer for Windows PowerShell (fresh + -Update)
|
|
336
356
|
core/ # copied verbatim into ~/.claude (global) or <project>/.claude (bundled)
|
|
337
|
-
agents/ # implementer.template.md (rendered per surface) + review / release /
|
|
357
|
+
agents/ # implementer.template.md (rendered per surface) + review / release / profile-reader
|
|
338
358
|
commands/ # init-pipeline + the pipeline commands + /update-pipeline
|
|
339
359
|
hooks/ # gate.py (destructive-command gate; branch-aware; preflight phase gate)
|
|
340
360
|
templates/ # handoff / brainstorm-return / design-brief / review-feedback / pr-body / spec
|
|
341
|
-
workflows/ # opt-in Workflow-runtime scripts:
|
|
361
|
+
workflows/ # opt-in Workflow-runtime scripts: review.js / audit.js / refactor.js
|
|
342
362
|
profile/
|
|
343
363
|
PIPELINE.template.md # the profile skeleton /init-pipeline fills
|
|
344
364
|
SCHEMA.md # field reference
|
package/bin/cli.js
CHANGED
|
@@ -35,6 +35,7 @@ Usage:
|
|
|
35
35
|
cohorte install [target] [--global]
|
|
36
36
|
cohorte update [target] [--global]
|
|
37
37
|
cohorte dashboard [target] [--port=N] [--host=ADDR] [--open]
|
|
38
|
+
cohorte metrics [target] [--days=N] [--since=ISO] [--runs] [--json]
|
|
38
39
|
cohorte version
|
|
39
40
|
|
|
40
41
|
Commands:
|
|
@@ -49,6 +50,11 @@ Commands:
|
|
|
49
50
|
by default (loopback only — its actions execute code). --host=ADDR
|
|
50
51
|
to expose (e.g. --host=0.0.0.0, prints a security warning). --open
|
|
51
52
|
to launch the browser.
|
|
53
|
+
metrics Cost and runtime per command, reconstructed from Claude Code's own
|
|
54
|
+
transcripts (tokens, USD, wall/active time, subagent count). Reads
|
|
55
|
+
~/.claude/projects — nothing to enable, and it covers runs that
|
|
56
|
+
already happened. Worktree-aware, so a feature adds up. --json for
|
|
57
|
+
the raw rollup, --runs to include every individual invocation.
|
|
52
58
|
version Print the installed CLI version.`);
|
|
53
59
|
process.exit(code);
|
|
54
60
|
}
|
|
@@ -64,8 +70,16 @@ let port = parseInt(process.env.COHORTE_DASHBOARD_PORT, 10) || 4317;
|
|
|
64
70
|
let host = process.env.COHORTE_DASHBOARD_HOST || '127.0.0.1';
|
|
65
71
|
let openBrowser = false;
|
|
66
72
|
|
|
73
|
+
// Flags that belong to `metrics` and are forwarded verbatim to the collector. They are
|
|
74
|
+
// listed here rather than parsed, so the collector stays the single source of truth for
|
|
75
|
+
// its own options — but the generic "unknown flag" guard below must not reject them.
|
|
76
|
+
const metricsFlags = [];
|
|
77
|
+
const isMetricsFlag = (a) =>
|
|
78
|
+
a === '--json' || a === '--runs' || a.startsWith('--days=') || a.startsWith('--since=');
|
|
79
|
+
|
|
67
80
|
for (const a of args) {
|
|
68
|
-
if (a === 'install' || a === 'update' || a === 'dashboard') mode = a;
|
|
81
|
+
if (a === 'install' || a === 'update' || a === 'dashboard' || a === 'metrics') mode = a;
|
|
82
|
+
else if (isMetricsFlag(a)) metricsFlags.push(a);
|
|
69
83
|
else if (a === 'version' || a === '--version' || a === '-v') { console.log(VERSION); process.exit(0); }
|
|
70
84
|
else if (a === '--global' || a === '-g') scope = 'global';
|
|
71
85
|
else if (a.startsWith('--port=')) {
|
|
@@ -96,6 +110,19 @@ if (mode === 'dashboard') {
|
|
|
96
110
|
return;
|
|
97
111
|
}
|
|
98
112
|
|
|
113
|
+
// --- metrics: cost + runtime per command ------------------------------------
|
|
114
|
+
// The collector is ESM and this CLI is CommonJS, so it runs as a child process rather
|
|
115
|
+
// than being required. stdio is inherited so --json stays pipeable.
|
|
116
|
+
if (mode === 'metrics') {
|
|
117
|
+
const script = path.join(pkgRoot, 'scripts', 'metrics', 'collect.mjs');
|
|
118
|
+
if (!fs.existsSync(script)) {
|
|
119
|
+
console.error(`error: metrics collector not found at ${script}`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
const r = spawnSync(process.execPath, [script, target, ...metricsFlags], { stdio: 'inherit' });
|
|
123
|
+
process.exit(r.status == null ? 1 : r.status);
|
|
124
|
+
}
|
|
125
|
+
|
|
99
126
|
// --- paths -------------------------------------------------------------------
|
|
100
127
|
const globalDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
101
128
|
const dest = scope === 'global' ? globalDir : path.join(target, '.claude');
|
|
@@ -109,7 +136,7 @@ fs.mkdirSync(dest, { recursive: true });
|
|
|
109
136
|
|
|
110
137
|
// --- helpers (mirror install.sh) --------------------------------------------
|
|
111
138
|
function copyCore() {
|
|
112
|
-
// `workflows` = the deterministic orchestration scripts (
|
|
139
|
+
// `workflows` = the deterministic orchestration scripts (review/audit/refactor) the
|
|
113
140
|
// Workflow runtime resolves from .claude/workflows (bundled) or ~/.claude/workflows
|
|
114
141
|
// (global) — same copy rule in both modes, like commands.
|
|
115
142
|
for (const d of ['commands', 'hooks', 'templates', 'workflows']) {
|
|
@@ -186,8 +213,8 @@ function copyFixedAgents() {
|
|
|
186
213
|
fs.mkdirSync(path.join(dest, 'agents'), { recursive: true });
|
|
187
214
|
// Every agent in core/agents/ EXCEPT the *.template.md ones, which /init-pipeline renders
|
|
188
215
|
// per-surface. Until 1.2.6 this was a hardcoded ['review.md', 'release.md'] that never grew
|
|
189
|
-
// the
|
|
190
|
-
//
|
|
216
|
+
// the agents the shell installers copy, so `npx cohorte install` shipped a command with no
|
|
217
|
+
// agent to dispatch — the run reported the command as not installed.
|
|
191
218
|
// Reading the directory needs no list to keep in sync with the shell installers.
|
|
192
219
|
const agentDir = path.join(src, 'core', 'agents');
|
|
193
220
|
for (const f of fs.readdirSync(agentDir)) {
|
|
@@ -197,6 +224,9 @@ function copyFixedAgents() {
|
|
|
197
224
|
// 0.1.19 split the bi-mode questionnaire-researcher into research-agent + questionnaire-architect;
|
|
198
225
|
// copy-over never deletes, so scrub the retired agent lest a dead subagent_type linger.
|
|
199
226
|
fs.rmSync(path.join(dest, 'agents', 'questionnaire-researcher.md'), { force: true });
|
|
227
|
+
// 1.5.0 removed the /smoke phase; copy-over never deletes, so scrub the orphan agent+command.
|
|
228
|
+
fs.rmSync(path.join(dest, 'agents', 'smoke.md'), { force: true });
|
|
229
|
+
fs.rmSync(path.join(dest, 'commands', 'smoke.md'), { force: true });
|
|
200
230
|
scrubResearchQuestionnaire();
|
|
201
231
|
}
|
|
202
232
|
|
|
@@ -21,6 +21,28 @@ nothing only when something is wrong.
|
|
|
21
21
|
3. Return **only** the JSON object — no fences, no commentary, no markdown. Your final message is
|
|
22
22
|
parsed by a script.
|
|
23
23
|
|
|
24
|
+
## Returning through a structured-output tool
|
|
25
|
+
|
|
26
|
+
When you are given a tool to return structured output, the profile's own keys go at the **top
|
|
27
|
+
level** of that tool's input — `surfaces`, `commands`, `vcs`, `contract`, and the rest, as real
|
|
28
|
+
JSON values.
|
|
29
|
+
|
|
30
|
+
Do **not** serialize the profile to a string and nest it under a wrapper field. These are wrong:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{"output": "{\"surfaces\": [ … ]}"} ← the profile as text under a wrapper
|
|
34
|
+
{"profile": {"surfaces": [ … ]}} ← the profile nested one level down
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This is right:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{"surfaces": [ … ], "commands": { … }, "vcs": { … }}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
A wrapped return costs the caller everything: the script reads `surfaces` as absent, builds
|
|
44
|
+
nothing, and reports success anyway.
|
|
45
|
+
|
|
24
46
|
## Failure shape
|
|
25
47
|
|
|
26
48
|
If `PIPELINE.md` is missing, or the `yaml pipeline-profile` fence is absent or unparseable, return
|
package/core/commands/build.md
CHANGED
|
@@ -105,10 +105,10 @@ Bash call, chain the opt-in usage ping — **the shared form every phase command
|
|
|
105
105
|
`<core>/pipeline/scripts/telemetry-send.sh <phase> "$ARGUMENTS" <seconds> "<results>" || true`
|
|
106
106
|
(`<core>` = `~/.claude` global / `.claude` bundled; here `<phase>` = `build`, `<results>` =
|
|
107
107
|
`<ok,ok|error,…>`) — a silent no-op unless the human explicitly consented (SCHEMA.md §Telemetry);
|
|
108
|
-
never ask about consent here. `/review
|
|
108
|
+
never ask about consent here. `/review` and `/fix` chain the same line with their own
|
|
109
109
|
phase + results. The `|| true` swallows a **missing** script too, so a half-copied core goes
|
|
110
110
|
silent rather than loud — `/doctor` check 1 is what catches that.
|
|
111
|
-
Then tell the human:
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
Then tell the human: exercise the feature by hand if it's worth it, then run `/review $ARGUMENTS`.
|
|
112
|
+
Do not run the app or migrations yourself here — building is not running.
|
|
113
|
+
**Recommend a `/clear` now** — the spec, contract and diff are all on
|
|
114
114
|
disk, and the lead's history is re-sent at input price on every turn it survives.
|
package/core/commands/doctor.md
CHANGED
|
@@ -21,7 +21,7 @@ fix only with the human's go-ahead (or hand them the command).
|
|
|
21
21
|
commands' step files are present — `templates/steps/init-pipeline/` non-empty (a router whose
|
|
22
22
|
`templates/steps/<cmd>/` dir is missing is a partial/stale install ⇒
|
|
23
23
|
re-run install/update). **Shipped scripts present and executable** in `<core>/pipeline/scripts/`:
|
|
24
|
-
`kanban-move.sh`, `telemetry-send.sh`, `preflight.sh`, `new-feature.sh.template`,
|
|
24
|
+
`kanban-move.sh`, `telemetry-send.sh`, `preflight.sh`, `loop.sh`, `new-feature.sh.template`,
|
|
25
25
|
`remove-feature.sh.template` — ❌ any missing one. Every caller chains these with `|| true`, so an absent script is a **silent**
|
|
26
26
|
no-op (no kanban card moves, no telemetry ping, no error anywhere) — this check is the only thing
|
|
27
27
|
that sees it. Also flag ❌ a `VERSION` **newer than** the other `pipeline/` files (compare mtimes):
|
|
@@ -34,10 +34,10 @@ fix only with the human's go-ahead (or hand them the command).
|
|
|
34
34
|
its `surfaces[].model` — ❌ if missing, mismatched, or a literal `<SURFACE_MODEL>` placeholder
|
|
35
35
|
(all three silently fall back to inheriting the lead session's model — often Opus — on every
|
|
36
36
|
dispatch); ⚠️ any `inherit` with the note that it bills at the lead's tier. The generic agents
|
|
37
|
-
(`review.md`, `release.md`, `
|
|
38
|
-
each carry their `model:` line too (sonnet/haiku/
|
|
39
|
-
(`build`, `review`, `fix`, `
|
|
40
|
-
`update-pipeline
|
|
37
|
+
(`review.md`, `release.md`, `profile-reader.md` — repo or `~/.claude/agents/`) must
|
|
38
|
+
each carry their `model:` line too (sonnet/haiku/haiku). **Command pins:** every mechanical command file
|
|
39
|
+
(`build`, `review`, `fix`, `loop`, `ship`, `audit`, `refactor`, `doctor`, `align-ds`,
|
|
40
|
+
`update-pipeline` — in `.claude/commands/` or `~/.claude/commands/`) carries `model: sonnet` in
|
|
41
41
|
its frontmatter — ⚠️ if missing (the lead's orchestration turn then bills at the session model,
|
|
42
42
|
e.g. Opus/Fable). `brainstorm`, `spec`, and `init-pipeline` are intentionally unpinned
|
|
43
43
|
(interactive — they inherit the session model).
|
|
@@ -73,7 +73,7 @@ fix only with the human's go-ahead (or hand them the command).
|
|
|
73
73
|
take and why:
|
|
74
74
|
- **Claude Code version** ≥ 2.1.154 (`claude --version 2>/dev/null | head -1`) — older or no CLI
|
|
75
75
|
on PATH ⇒ conversational only.
|
|
76
|
-
- **Scripts present:** `<core>/workflows/review.js` + `audit.js` + `refactor.js`
|
|
76
|
+
- **Scripts present:** `<core>/workflows/review.js` + `audit.js` + `refactor.js` —
|
|
77
77
|
missing on a current core ⇒ half-done install, re-run install/update.
|
|
78
78
|
- **Phase-0 agent present:** `<agents dir>/profile-reader.md` (repo `.claude/agents/` bundled or
|
|
79
79
|
`~/.claude/agents/` global) — the workflows abort without it.
|
|
@@ -84,9 +84,11 @@ fix only with the human's go-ahead (or hand them the command).
|
|
|
84
84
|
End the check with ONE summary line, e.g.
|
|
85
85
|
`workflows: available (opt-in — ask to "run the review workflow")` or
|
|
86
86
|
`workflows: unavailable (<first failing prerequisite>) — conversational commands (the default)`.
|
|
87
|
-
9. **Specs & metrics.** Every `specs/*.md` front-matter `status` is a valid stage
|
|
87
|
+
9. **Specs & metrics.** Every `specs/*.md` front-matter `status` is a valid stage — excluding
|
|
88
|
+
`_`-prefixed files and `specs/refactor-backlog.md`, which `/audit` writes as a backlog, not a
|
|
89
|
+
spec, and which has no front-matter to check; `shipped` specs
|
|
88
90
|
with a live worktree flagged (see 6). `.claude/pipeline-metrics.jsonl` and `specs/reports/` (the
|
|
89
|
-
`/review
|
|
91
|
+
`/review` report buffer that lets a `/fix` survive a `/clear`) are gitignored. Metrics
|
|
90
92
|
belong to the **main checkout** — a `pipeline-metrics.jsonl` inside a live feature worktree is a
|
|
91
93
|
stale-core sign (its lines die at teardown) ⇒ suggest appending its lines to the main checkout's
|
|
92
94
|
file and deleting the stray.
|
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
|
|
@@ -65,7 +65,7 @@ When the agents return:
|
|
|
65
65
|
handoff content — and append ONE metrics line for the batch to `pipeline-metrics.jsonl`
|
|
66
66
|
(see `/build` §4, `phase: "fix"`), chaining the opt-in usage ping in the same Bash call
|
|
67
67
|
(results = items fixed over items found across surfaces, e.g. `"5/6"`).
|
|
68
|
-
- Tell the human: re-
|
|
68
|
+
- Tell the human: re-test by hand if the failures were runtime ones, and `/review $ARGUMENTS` for the
|
|
69
69
|
re-verdict — the re-review is what *verifies* the ticked items actually hold (a regression simply
|
|
70
70
|
reappears as a new finding in the next round). **Recommend a `/clear`** — all state (spec,
|
|
71
71
|
checkboxes, staged report) is on disk, and the lead's history is re-sent at input price every turn.
|
|
@@ -0,0 +1,61 @@
|
|
|
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]
|
|
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 exists because a slash command cannot `/clear` itself. Every phase of the loop runs
|
|
12
|
+
> as a **separate `claude -p` child session** with its own fresh context, driven by a bash script —
|
|
13
|
+
> so the diff, the N review reports and the N contracts never accumulate in YOUR history, which is
|
|
14
|
+
> re-sent at input price on every turn. Running the loop conversationally here would cost more than
|
|
15
|
+
> the loop saves.
|
|
16
|
+
|
|
17
|
+
## 1. Launch
|
|
18
|
+
|
|
19
|
+
Probe the core, then run the script — ONE Bash call, and let it run to completion:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
test -f .claude/pipeline/scripts/loop.sh \
|
|
23
|
+
&& bash .claude/pipeline/scripts/loop.sh $ARGUMENTS \
|
|
24
|
+
|| bash ~/.claude/pipeline/scripts/loop.sh $ARGUMENTS
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Pass `$ARGUMENTS` through untouched — the script owns its own flag parsing (`--max=N`,
|
|
28
|
+
`--no-build`, `--rebuild`) and exits 64 on anything it doesn't know. Don't validate flags yourself,
|
|
29
|
+
don't rewrite them, don't add any.
|
|
30
|
+
|
|
31
|
+
**Never read `specs/reports/<id>.loop.log`.** It holds the full transcript of every child session —
|
|
32
|
+
the entire diff, every review report, every fix handoff. Pulling it into this session re-imports
|
|
33
|
+
exactly the context the loop was built to keep out, and it is the one mistake that turns this
|
|
34
|
+
command into the most expensive one in the pipeline. Point the human at the path instead; they can
|
|
35
|
+
open it in an editor for free. The same goes for the per-surface `.diff` and `.preflight.txt` files.
|
|
36
|
+
|
|
37
|
+
## 2. Report — three lines, from the exit code
|
|
38
|
+
|
|
39
|
+
The script prints one line per phase and one closing line; that is your raw material. For exit
|
|
40
|
+
**1** or **3** only, also Read `specs/reports/<id>.verdict.json` (small, structured, safe) to name
|
|
41
|
+
the remaining findings — never the markdown report, which is the findings body in full.
|
|
42
|
+
|
|
43
|
+
| exit | meaning | what to say |
|
|
44
|
+
| ---- | ------- | ----------- |
|
|
45
|
+
| `0` | clean | no blocking findings left; the human can `/ship <id>` |
|
|
46
|
+
| `1` | ceiling hit | the fix was progressing but ran out of passes ⇒ re-run with a higher `--max` |
|
|
47
|
+
| `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` |
|
|
48
|
+
| `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) |
|
|
49
|
+
| `64` | usage | relay the script's own message verbatim |
|
|
50
|
+
|
|
51
|
+
Then print exactly three lines and nothing else:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
outcome: <one clause — clean / ceiling / no verdict / non-convergent / usage>
|
|
55
|
+
iterations: <n> review pass(es)<, m fix pass(es) committed>
|
|
56
|
+
remaining: <blocking count + one short phrase per blocking item, or "none">
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Add at most one follow-up sentence: the next command to run. Never restate a finding's fix, never
|
|
60
|
+
summarize the log, never open the diff. Each fix pass is already committed
|
|
61
|
+
(`loop(<id>): fix pass <i>`) — say so on a non-zero exit, since those commits are the way back.
|
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
|
|
|
@@ -91,6 +95,33 @@ merged verdict + total finding count, e.g. `"REVISE:3"`).
|
|
|
91
95
|
**Stage the full report to `specs/reports/$ARGUMENTS.md`** (overwrite) — a gitignored buffer so a
|
|
92
96
|
`/fix` after a `/clear` can still read the findings; the `specs/reports/` subfolder is skipped by the
|
|
93
97
|
non-recursive `specs/*.md` glob, so it's never mistaken for a spec (no phantom card, no bogus stage).
|
|
98
|
+
**Write the machine-readable verdict** to `specs/reports/$ARGUMENTS.verdict.json` (overwrite) — on
|
|
99
|
+
**every** run, including the small-diff fast path of §2 and a `SHIP`. This file is the ONLY contract
|
|
100
|
+
between the pipeline and an automated driver (`/loop`), which parses no prose:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{ "id": "$ARGUMENTS", "phase": "review", "ts": "<ISO>", "verdict": "REVISE",
|
|
104
|
+
"findings": 7, "blocking": 2, "security": 1,
|
|
105
|
+
"severity": {"CRITICAL": 1, "HIGH": 2, "MEDIUM": 3, "LOW": 1},
|
|
106
|
+
"surfaces": {"backend": {"verdict":"BLOCK","findings":4,"blocking":2}},
|
|
107
|
+
"blocking_items": ["backend|apps/api/src/routes/order.ts|missing authz on post"],
|
|
108
|
+
"fingerprint": "b3f1c2a90d4e5f67" }
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- **`blocking` = CRITICAL findings + `security` findings, deduplicated** (a finding that is both
|
|
112
|
+
counts once). That is exactly the agent's existing verdict rule restated as a number, so
|
|
113
|
+
`blocking == 0` ⟺ `verdict == SHIP`. HIGH/MEDIUM/LOW quality findings are **not** blocking —
|
|
114
|
+
they follow the `deferred:<id>` backlog route below, and must never cost a driver an iteration.
|
|
115
|
+
- **`blocking_items`** — one normalized string per blocking finding, `<surface>|<file>|<problem>`:
|
|
116
|
+
the file path **without the `:line`** (a fix that inserts lines shifts every line below it — a
|
|
117
|
+
line-bearing identity would change every pass and the drift detection would never fire), and the
|
|
118
|
+
**problem**, not the fix, cut to its first 8 words, lowercased, every run of non-alphanumerics
|
|
119
|
+
collapsed to one space. Identity of a finding, not its wording.
|
|
120
|
+
- **`fingerprint`** — computed in the same Bash call, never by hand:
|
|
121
|
+
`printf '%s\n' "<item>" … | LC_ALL=C sort | sha256sum | cut -c1-16` (`shasum -a 256` where there
|
|
122
|
+
is no `sha256sum`). Empty list ⇒ `""`. A driver comparing two consecutive fingerprints detects a
|
|
123
|
+
fix loop that is treading water.
|
|
124
|
+
|
|
94
125
|
In chat print ONLY: the verdict, the severity-count table, a one-line digest of each CRITICAL/security
|
|
95
126
|
finding, and `Full report: specs/reports/$ARGUMENTS.md` — never echo the findings body into chat (it
|
|
96
127
|
would sit in this session's history, re-sent every turn). Then:
|
|
@@ -98,9 +129,11 @@ would sit in this session's history, re-sent every turn). Then:
|
|
|
98
129
|
- **SHIP** → a SHIP verdict *is* the pipeline's statement that the feature meets its Definition of
|
|
99
130
|
Done, so **tick the DoD**: in `specs/$ARGUMENTS.md` §`Acceptance criteria / DoD`, flip each `- [ ]`
|
|
100
131
|
→ `- [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
|
-
|
|
132
|
+
copy (this review), tests · lint · typecheck (a green `/build`), mobile-first as far as the code
|
|
133
|
+
shows it (this review). **Leave `- [ ]` (and say which) any item no stage actually verified** —
|
|
134
|
+
nothing in the pipeline *runs* the feature, so any criterion that needs the app up (runtime flows,
|
|
135
|
+
a visual check against the design) stays open unless the human says they exercised it by hand and
|
|
136
|
+
it held. Ticking is the lead's job
|
|
104
137
|
(the reviewer is read-only). **Then stamp the freshness gate** so `/ship` can refuse to ship code
|
|
105
138
|
edited after this verdict: compute `BASE=$(git merge-base <default_branch> HEAD)` and write into the
|
|
106
139
|
spec front-matter `reviewed_base: $BASE` plus
|
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
|
|
@@ -53,7 +53,7 @@ design_files: [] # design page links — full URLs of the form https://claude.ai
|
|
|
53
53
|
|
|
54
54
|
> One `###` subsection per surface in `PIPELINE.md` §surfaces (e.g. backend, frontend), each TDD.
|
|
55
55
|
> Surfaces are subsections of §6, never top-level sections — §8 and §9 are referenced by number
|
|
56
|
-
> across the whole pipeline (`/
|
|
56
|
+
> across the whole pipeline (`/build` implements "§8 flows", `/review` checks "§9 acceptance"), so a
|
|
57
57
|
> third surface must not become a §8 of its own.
|
|
58
58
|
|
|
59
59
|
### <surface.key>
|