cohorte 1.5.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +169 -3
  2. package/README.md +65 -57
  3. package/bin/cli.js +31 -15
  4. package/core/agents/implementer.template.md +3 -3
  5. package/core/agents/release.md +1 -1
  6. package/core/agents/review.md +25 -2
  7. package/core/commands/{audit.md → cohorte-audit.md} +11 -3
  8. package/core/commands/{brainstorm.md → cohorte-brainstorm.md} +9 -3
  9. package/core/commands/{build.md → cohorte-build.md} +95 -10
  10. package/core/commands/{doctor.md → cohorte-doctor.md} +22 -9
  11. package/core/commands/{fix.md → cohorte-fix.md} +20 -13
  12. package/core/commands/{init-pipeline.md → cohorte-init-pipeline.md} +1 -1
  13. package/core/commands/cohorte-loop.md +110 -0
  14. package/core/commands/{refactor.md → cohorte-refactor.md} +3 -3
  15. package/core/commands/{review.md → cohorte-review.md} +70 -20
  16. package/core/commands/{ship.md → cohorte-ship.md} +5 -5
  17. package/core/commands/{spec.md → cohorte-spec.md} +32 -12
  18. package/core/commands/{update-pipeline.md → cohorte-update-pipeline.md} +16 -6
  19. package/core/hooks/gate.py +101 -6
  20. package/core/templates/brainstorm-return.md +4 -4
  21. package/core/templates/decisions.template.md +42 -0
  22. package/core/templates/design-brief.md +1 -1
  23. package/core/templates/spec.template.md +8 -6
  24. package/core/templates/steps/init-pipeline/01-detect-stack.md +1 -1
  25. package/core/templates/steps/init-pipeline/02-interview-gaps.md +6 -6
  26. package/core/templates/steps/init-pipeline/03-draft-profile.md +1 -1
  27. package/core/templates/steps/init-pipeline/04-write-render.md +16 -12
  28. package/core/templates/steps/init-pipeline/05-report.md +5 -5
  29. package/core/workflows/audit.js +6 -6
  30. package/core/workflows/refactor.js +14 -14
  31. package/core/workflows/review.js +62 -20
  32. package/dashboard/README.md +2 -2
  33. package/dashboard/dist/assets/{index-dkO8UUVl.css → index-BZ_LQlEj.css} +1 -1
  34. package/dashboard/dist/assets/{index-8owBnqyv.js → index-P1I1JGtj.js} +11 -11
  35. package/dashboard/dist/index.html +2 -2
  36. package/dashboard/server/doctor.js +75 -18
  37. package/dashboard/server/index.js +5 -5
  38. package/dashboard/server/metrics.js +1 -1
  39. package/install.ps1 +31 -14
  40. package/install.sh +31 -14
  41. package/package.json +2 -2
  42. package/profile/PIPELINE.template.md +17 -16
  43. package/profile/SCHEMA.md +199 -48
  44. package/profile/cohorte.config.template.yaml +8 -8
  45. package/scripts/loop-detach.sh +153 -0
  46. package/scripts/loop.sh +202 -25
  47. package/scripts/metrics/collect.mjs +17 -8
  48. package/scripts/new-feature.sh.template +3 -3
  49. package/scripts/preflight.sh +40 -4
  50. package/scripts/remove-feature.sh.template +2 -2
  51. package/scripts/test-dashboard.mjs +34 -7
  52. package/scripts/test-gate.mjs +58 -0
  53. package/scripts/test-loop.mjs +269 -0
  54. package/scripts/test-metrics.mjs +23 -11
  55. package/scripts/test-workflows.mjs +33 -5
  56. package/scripts/validate-core.mjs +46 -9
  57. package/core/commands/loop.md +0 -61
  58. /package/core/commands/{align-ds.md → cohorte-align-ds.md} +0 -0
@@ -13,7 +13,7 @@ You are the **lead**. Dispatch the review for feature **$ARGUMENTS**.
13
13
  >
14
14
  > **Workflow variant** (opt-in — SCHEMA.md §Workflows): on Claude Code ≥ 2.1.154 with workflows
15
15
  > enabled, the human can ask to "run the review workflow" (`<core>/workflows/review.js`) instead.
16
- > This conversational path stays the default and the fallback; `/doctor` shows which is available.
16
+ > This conversational path stays the default and the fallback; `/cohorte-doctor` shows which is available.
17
17
 
18
18
  ## 0. Deterministic pre-flight — no agents while red
19
19
 
@@ -27,7 +27,7 @@ with `test -x`); note the epoch (`date +%s`) in the same call — §3's metrics
27
27
  ```
28
28
 
29
29
  - **Non-zero exit** ⇒ the script already printed the raw last-40 lines. **STOP: relay them verbatim
30
- and spawn NO agent** — a compiler/test failure needs `/fix` (or the human), not a review that
30
+ and spawn NO agent** — a compiler/test failure needs `/cohorte-fix` (or the human), not a review that
31
31
  rediscovers it at agent prices. This abort is the whole point of the step. Before stopping, write
32
32
  the **aborted verdict** (§3's contract, degraded form) so an automated driver gets a diagnosis
33
33
  rather than silence:
@@ -58,7 +58,7 @@ with `test -x`); note the epoch (`date +%s`) in the same call — §3's metrics
58
58
  ## 2. Dispatch review agents — one per touched surface, IN PARALLEL
59
59
 
60
60
  Spawn ONE `review` agent per surface that has changed files, in a **single message** (one Task call
61
- each, like `/build`) so they run concurrently — NEVER serially: review wall-clock must be the
61
+ each, like `/cohorte-build`) so they run concurrently — NEVER serially: review wall-clock must be the
62
62
  slowest surface, not the sum. A diff touching a single surface ⇒ a single reviewer.
63
63
 
64
64
  **Small-diff fast path (re-reviews only):** if a surface's staged diff is tiny (≤2 files and ≤~40
@@ -86,22 +86,39 @@ metrics line needs it.
86
86
 
87
87
  ## 3. Merge & relay the verdict
88
88
 
89
- Merge the returned reports into **one** REVIEW REPORT (same template): findings concatenated and
89
+ **Roll call FIRST a dead reviewer is not a clean surface.** Every surface you dispatched in §2 must
90
+ come back with a REVIEW REPORT. A reviewer that died (rate limit, transport error, exhausted context)
91
+ returns **nothing**, and zero findings from a dead reviewer is byte-identical to zero findings from a
92
+ genuinely clean one — which is how "every reviewer crashed" reads as the strongest possible verdict
93
+ from no evidence at all (SCHEMA.md §Dead agents). So:
94
+
95
+ - **Retry a silent surface ONCE**, byte-identical dispatch. Most deaths are transient, and the staged
96
+ diff is already on disk — the retry costs one agent, not a re-review.
97
+ - **Silent twice ⇒ that surface is `unreviewed`.** Name it in the report under
98
+ `## NOT reviewed (no verdict on these)`, list it in the verdict JSON's `unreviewed`, and **refuse to
99
+ score `SHIP`** — the merged verdict is at least `REVISE`. Absence of evidence is not evidence of
100
+ absence, and it must never reach `/cohorte-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
90
104
  re-ordered by severity, counts summed, duplicates collapsed, verdict = the worst returned
91
- (`BLOCK` > `REVISE` > `SHIP`). Append ONE metrics line for the batch to `pipeline-metrics.jsonl`
92
- (main-checkout path + rules in `/build` §4): `{"ts":"<ISO>","feature":"$ARGUMENTS","phase":"review","seconds":<wall-clock>,"surfaces":{"<key>":"<verdict>:<finding count>",…}}`.
93
- In the same Bash call, chain the opt-in usage ping (`/build` §4, `phase: "review"`, results = the
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 the **main checkout's**
108
+ `$(dirname "$(git rev-parse --git-common-dir)")/.claude/pipeline-metrics.jsonl` (rules in
109
+ `/cohorte-build` §4; never a bare relative path — from a worktree that strands the lines): `{"ts":"<ISO>","feature":"$ARGUMENTS","phase":"review","seconds":<wall-clock>,"surfaces":{"<key>":"<verdict>:<finding count>",…}}`.
110
+ In the same Bash call, chain the opt-in usage ping (`/cohorte-build` §4, `phase: "review"`, results = the
94
111
  merged verdict + total finding count, e.g. `"REVISE:3"`).
95
112
  **Stage the full report to `specs/reports/$ARGUMENTS.md`** (overwrite) — a gitignored buffer so a
96
- `/fix` after a `/clear` can still read the findings; the `specs/reports/` subfolder is skipped by the
113
+ `/cohorte-fix` after a `/clear` can still read the findings; the `specs/reports/` subfolder is skipped by the
97
114
  non-recursive `specs/*.md` glob, so it's never mistaken for a spec (no phantom card, no bogus stage).
98
115
  **Write the machine-readable verdict** to `specs/reports/$ARGUMENTS.verdict.json` (overwrite) — on
99
116
  **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:
117
+ between the pipeline and an automated driver (`/cohorte-loop`), which parses no prose:
101
118
 
102
119
  ```json
103
120
  { "id": "$ARGUMENTS", "phase": "review", "ts": "<ISO>", "verdict": "REVISE",
104
- "findings": 7, "blocking": 2, "security": 1,
121
+ "findings": 7, "blocking": 2, "security": 1, "deferred": 3, "unreviewed": [],
105
122
  "severity": {"CRITICAL": 1, "HIGH": 2, "MEDIUM": 3, "LOW": 1},
106
123
  "surfaces": {"backend": {"verdict":"BLOCK","findings":4,"blocking":2}},
107
124
  "blocking_items": ["backend|apps/api/src/routes/order.ts|missing authz on post"],
@@ -121,32 +138,65 @@ between the pipeline and an automated driver (`/loop`), which parses no prose:
121
138
  `printf '%s\n' "<item>" … | LC_ALL=C sort | sha256sum | cut -c1-16` (`shasum -a 256` where there
122
139
  is no `sha256sum`). Empty list ⇒ `""`. A driver comparing two consecutive fingerprints detects a
123
140
  fix loop that is treading water.
141
+ - **`deferred`** — the count of merged `## Deferred` items §3.5 parked in the backlog. Informational:
142
+ it never enters `blocking`, so it can never cost a driver an iteration.
143
+ - **`unreviewed`** — the surface keys whose reviewer died twice, `[]` on a complete run. It is
144
+ **separate from `blocking` on purpose**: `blocking` counts real findings (CRITICAL + security), and
145
+ faking a number there to force a driver's hand would corrupt the one field the whole contract rests
146
+ on. A non-empty `unreviewed` means "this run does not cover everything" — a driver treats it as no
147
+ usable verdict, never as clean, whatever `blocking` says.
148
+
149
+ ## 3.5 Route the deferred findings — the backlog, not the fix loop
150
+
151
+ Do this on **every** run, before the verdict branch below, and whatever the verdict — a deferred
152
+ finding that is only routed on a `SHIP` is a deferred finding lost on every other verdict, which is
153
+ exactly the leak this step closes. Append each merged `## Deferred` item to
154
+ **`specs/refactor-backlog.md`**, under the `## <domain>` heading of the surface that owns its
155
+ `file:line` (create the file and/or heading if absent — same grouping `/cohorte-audit` writes, so
156
+ `/cohorte-refactor <domain>` picks them up with no extra plumbing):
157
+
158
+ ```
159
+ - [ ] <SEVERITY> · <file:line> · <quality|security|rule> · <concrete fix> · deferred:$ARGUMENTS
160
+ ```
161
+
162
+ - **Never into the spec's `## Remediation`** — that list is what `/cohorte-fix` re-dispatches and what `/cohorte-loop`
163
+ waits on, so a deferred item there would re-trigger the very loop it was deferred out of.
164
+ - **Dedupe before appending:** `grep -F` the backlog for the item's `<file>` + the first words of its
165
+ problem; already there (from a prior round or an `/cohorte-audit`) ⇒ skip it, don't stack duplicates round
166
+ after round.
167
+ - Append with `>>` in ONE Bash call; never read the whole backlog into context to rewrite it (it grows
168
+ with every audit the repo has ever run).
169
+ - Report it as **one line** in chat: `deferred: <n> parked in specs/refactor-backlog.md (<domains>)`.
124
170
 
125
171
  In chat print ONLY: the verdict, the severity-count table, a one-line digest of each CRITICAL/security
126
172
  finding, and `Full report: specs/reports/$ARGUMENTS.md` — never echo the findings body into chat (it
127
173
  would sit in this session's history, re-sent every turn). Then:
128
174
 
129
- - **SHIP** → a SHIP verdict *is* the pipeline's statement that the feature meets its Definition of
175
+ - **SHIP** → only reachable with `unreviewed` empty (the roll call above forbids it otherwise). A SHIP
176
+ verdict *is* the pipeline's statement that the feature meets its Definition of
130
177
  Done, so **tick the DoD**: in `specs/$ARGUMENTS.md` §`Acceptance criteria / DoD`, flip each `- [ ]`
131
178
  → `- [x]` for the criteria the pipeline has actually verified — spec conformance + `ui_language`
132
- copy (this review), tests · lint · typecheck (a green `/build`), mobile-first as far as the code
179
+ copy (this review), tests · lint · typecheck (a green `/cohorte-build`), mobile-first as far as the code
133
180
  shows it (this review). **Leave `- [ ]` (and say which) any item no stage actually verified** —
134
181
  nothing in the pipeline *runs* the feature, so any criterion that needs the app up (runtime flows,
135
182
  a visual check against the design) stays open unless the human says they exercised it by hand and
136
183
  it held. Ticking is the lead's job
137
- (the reviewer is read-only). **Then stamp the freshness gate** so `/ship` can refuse to ship code
184
+ (the reviewer is read-only). **Then stamp the freshness gate** so `/cohorte-ship` can refuse to ship code
138
185
  edited after this verdict: compute `BASE=$(git merge-base <default_branch> HEAD)` and write into the
139
186
  spec front-matter `reviewed_base: $BASE` plus
140
187
  `reviewed_digest: $(git diff $BASE -- . ':(exclude)specs/' | sha256sum | cut -c1-16)` — the fingerprint
141
188
  of exactly the source you just reviewed (specs excluded, so DoD ticks + the ship status flip don't
142
- trip it). Then tell the human they can `/ship` — **recommend a `/clear` first**, the handoff is
189
+ trip it). Then tell the human they can `/cohorte-ship` — **recommend a `/clear` first**, the handoff is
143
190
  fully on disk. **SHIP with leftover LOW findings** (or LOW+MEDIUM at the human's call) does NOT
144
- force a fix cycle for nits: park them in `specs/refactor-backlog.md` tagged `deferred:<id>` (NOT as
145
- open `## Remediation` items, which would re-trigger the fix loop), keep the SHIP verdict and the
146
- freshness stamp, and let the human ship.
191
+ force a fix cycle for nits: park them through §3.5's exact route (the backlog, under their surface's
192
+ domain heading, tagged `deferred:$ARGUMENTS` — never as open `## Remediation` items, which would
193
+ re-trigger the fix loop), keep the SHIP verdict and the freshness stamp, and let the human ship.
147
194
  - **REVISE / BLOCK**, or any CRITICAL/HIGH/security finding → tell the human to run
148
- **`/fix $ARGUMENTS`** — it appends the report to the spec's `## Remediation` and re-dispatches ONLY
149
- the surfaces with findings. The full path (`/spec` Mode B then `/build`) remains for findings that
195
+ **`/cohorte-fix $ARGUMENTS`** — it appends the report to the spec's `## Remediation` and re-dispatches ONLY
196
+ the surfaces with findings. (If they'd rather automate the rounds, the autonomous driver is
197
+ `disable-model-invocation: true` on purpose: **you cannot start it, they must type it**. Name the
198
+ exact line for them to type rather than attempting it — an attempt that silently fails reads as a
199
+ loop that is running when nothing is.) The full path (`/cohorte-spec` Mode B then `/cohorte-build`) remains for findings that
150
200
  change the contract in ways that ripple into clean surfaces. _The report is staged to
151
- `specs/reports/$ARGUMENTS.md`, so you can `/clear` before `/fix` — it reads the findings back from
201
+ `specs/reports/$ARGUMENTS.md`, so you can `/clear` before `/cohorte-fix` — it reads the findings back from
152
202
  disk._
@@ -15,15 +15,15 @@ You are the **lead**. Ship feature **$ARGUMENTS**. This is the outward-facing ga
15
15
 
16
16
  ## 1. Pre-flight (confirm before doing anything irreversible)
17
17
 
18
- - Confirm the latest `/review` returned **SHIP** (no CRITICAL, no security). If not reviewed, or the
18
+ - Confirm the latest `/cohorte-review` returned **SHIP** (no CRITICAL, no security). If not reviewed, or the
19
19
  verdict was REVISE/BLOCK, stop and say so.
20
20
  - **Freshness gate** — the reviewed code must be exactly what ships. If the spec front-matter carries
21
21
  `reviewed_base` + `reviewed_digest`, recompute
22
22
  `git diff <reviewed_base> -- . ':(exclude)specs/' | sha256sum | cut -c1-16` and compare to
23
23
  `reviewed_digest`. **Match** ⇒ source unchanged since the SHIP verdict, proceed. **Mismatch** ⇒ source
24
24
  (or the contract) was edited after review — the verdict is **stale**: stop and tell the human to re-run
25
- `/review $ARGUMENTS` before shipping. Missing fields (spec predates the gate) ⇒ skip, don't block.
26
- - **DoD gate (verify, don't tick — `/review` owns the ticking).** Read `specs/$ARGUMENTS.md`
25
+ `/cohorte-review $ARGUMENTS` before shipping. Missing fields (spec predates the gate) ⇒ skip, don't block.
26
+ - **DoD gate (verify, don't tick — `/cohorte-review` owns the ticking).** Read `specs/$ARGUMENTS.md`
27
27
  §`Acceptance criteria / DoD`; if any item is still `- [ ]`, list the open ones and ask the human to
28
28
  confirm shipping anyway (they may be deferred on purpose — e.g. a UI item on a backend-only feature).
29
29
  All `- [x]` ⇒ proceed silently.
@@ -61,7 +61,7 @@ or an offset-limited Read around the match): exactly one card, under the `shippe
61
61
  re-read the whole board into context. No board ⇒ skip silently.
62
62
 
63
63
  **Telemetry — the usage ping that closes the funnel.** Chain it onto the verify call above
64
- (`/build` §4's shared form, `<phase>` = `ship`, `<seconds>` = `0` — the release agent's duration is
64
+ (`/cohorte-build` §4's shared form, `<phase>` = `ship`, `<seconds>` = `0` — the release agent's duration is
65
65
  not the pipeline's, `<results>` = `pr` when a PR was created / `compare` when only a compare URL was
66
66
  emitted). Fire it **after** the release agent reports success, never on an aborted ship — a `ship`
67
67
  event must mean the feature actually left the pipeline. No board ⇒ still ping, in its own `|| true`
@@ -70,7 +70,7 @@ call. Silent no-op without consent; never ask about consent here.
70
70
  ## 5. After the PR — CI gate + teardown
71
71
 
72
72
  - If `host: github` and `gh` is available, watch the PR's checks (`gh pr checks <url> --watch`) and
73
- report the result — the human merges only on green. A red check ⇒ back to `/fix $ARGUMENTS`.
73
+ report the result — the human merges only on green. A red check ⇒ back to `/cohorte-fix $ARGUMENTS`.
74
74
  - Once the human confirms the PR is **merged**: if `isolation.enabled`, propose the teardown —
75
75
  `scripts/remove-feature.sh $ARGUMENTS` (add `--drop-db` to also drop the feature db; kept by
76
76
  default). It removes the worktree, deletes the merged branch, frees the slot. Never run it before
@@ -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
 
@@ -21,7 +25,7 @@ Detect the mode from the pasted content:
21
25
  ## Mode A — new spec (input is a brainstorm return, or empty)
22
26
 
23
27
  1. If empty, look for a staged brainstorm return first — `specs/reports/*-brainstorm.md` (where
24
- `/brainstorm` stages its output); one match ⇒ read it and confirm, several ⇒ ask which. None ⇒
28
+ `/cohorte-brainstorm` stages its output); one match ⇒ read it and confirm, several ⇒ ask which. None ⇒
25
29
  ask the human to paste the return (or describe the feature) and wait.
26
30
  2. Derive a `feature_id` (kebab-case slug). Confirm it.
27
31
  2b. **Size budget — a spec is a contract, not a novel.** Target ≤ ~300 lines; hard-think at 500.
@@ -43,44 +47,60 @@ Detect the mode from the pasted content:
43
47
  non-design surfaces never re-read the full brief on every dispatch).
44
48
  4b. **New-surface heads-up.** If the feature clearly introduces an area no existing `surfaces[].path`
45
49
  owns (a new service/app/top-level module), note it in the spec (a line in the relevant task section:
46
- `> needs new surface: <proposed key/path>`). Don't render agents here — `/build` §1.5 auto-reconciles
47
- it. This is just so the human isn't surprised when `/build` proposes a new agent.
50
+ `> needs new surface: <proposed key/path>`). Don't render agents here — `/cohorte-build` §1.5 auto-reconciles
51
+ it. This is just so the human isn't surprised when `/cohorte-build` proposes a new agent.
48
52
  5. When the human validates, **freeze**: write `specs/<id>.md` (`status: frozen`, front-matter filled).
49
53
  Create the file — do not ask the human to. **Postcondition:** `grep -q '^status: frozen' specs/<id>.md`
50
- — if it fails the freeze didn't land; fix it before pointing the human at `/build`. Chain the
51
- opt-in usage ping onto the postcondition's Bash call (`/build` §4's shared form, `<phase>` =
54
+ — if it fails the freeze didn't land; fix it before pointing the human at `/cohorte-build`. Chain the
55
+ opt-in usage ping onto the postcondition's Bash call (`/cohorte-build` §4's shared form, `<phase>` =
52
56
  `spec`, `<seconds>` = `0` — interactive time, not pipeline wall-clock, `<results>` = `frozen`).
53
57
  Ping only on a **landed** freeze, so the funnel counts specs that exist, not attempts. Mode B does
54
- not ping — it re-enters an already-counted spec, and `/fix` covers that loop. Silent no-op without
58
+ not ping — it re-enters an already-counted spec, and `/cohorte-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 `/cohorte-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._
59
79
  - **Write it to `specs/design/<id>.md`** (the authored artifact, versioned with the spec; spec §8
60
80
  holds the summary + pointer). Create the file — do not ask the human to. Keep it in the
61
81
  `specs/design/` subfolder, **not** `specs/<id>....md`: the `specs/*.md` glob that drives the
62
- kanban backfill and `/doctor` is non-recursive, so a brief in the subfolder never gets mistaken
82
+ kanban backfill and `/cohorte-doctor` is non-recursive, so a brief in the subfolder never gets mistaken
63
83
  for a spec (no phantom card, no bogus stage). Overwrite it on every freeze.
64
84
  - Print ONLY the path + a one-line summary — never echo the brief into chat (it would sit in this
65
85
  session's history; echo it only if the human asks). Tell the human: copy it from
66
86
  `specs/design/<id>.md` into the design tool (if any — typically a fresh design project for this
67
- feature), then run `/build <id>` and hand its design gate the resulting page link(s) — a full
87
+ feature), then run `/cohorte-build <id>` and hand its design gate the resulting page link(s) — a full
68
88
  `https://claude.ai/design/p/<projectId>?file=<file>` link carries its own project + page, no
69
89
  profile change needed. (They can also paste the links into the spec's `design_files` themselves.)
70
- **Recommend a `/clear` before `/build`** — the frozen spec + `specs/design/<id>.md` are the whole
90
+ **Recommend a `/clear` before `/cohorte-build`** — the frozen spec + `specs/design/<id>.md` are the whole
71
91
  handoff.
72
92
 
73
93
  ## Mode B — review return (input is a REVIEW REPORT)
74
94
 
75
95
  1. Read the report — pasted as input, or (whenever nothing is pasted) read from
76
- `specs/reports/<id>.md`, where `/review` stages its last report. Identify `feature_id` from its
96
+ `specs/reports/<id>.md`, where `/cohorte-review` stages its last report. Identify `feature_id` from its
77
97
  header; open `specs/<id>.md`.
78
98
  2. Append each finding to the spec's **`## Remediation`**, one per line:
79
99
  `- [ ] <severity> · <file:line> · <spec-violation|quality|security> · <concrete fix>`
80
100
  (Keep prior items; add the new round under a dated/numbered subheading.)
81
101
  3. If a finding implies the **contract** must change, update §5 and flag it so the lead re-authors the
82
102
  contract file.
83
- 4. Set `status: in-review`. Tell the human to run `/build <id>` to re-dispatch fresh agents.
84
- **Recommend a `/clear` before `/build`** — the spec is the whole handoff.
103
+ 4. Set `status: in-review`. Tell the human to run `/cohorte-build <id>` to re-dispatch fresh agents.
104
+ **Recommend a `/clear` before `/cohorte-build`** — the spec is the whole handoff.
85
105
 
86
106
  In both modes the spec is the single source of truth; agents are stateless and read only it + the diff.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  model: sonnet
3
- description: Refresh the pipeline core (global ~/.claude, or a repo's bundled .claude) to the latest published cohorte version, then reconcile this repo's generated files to it — /init-pipeline stays one-time.
3
+ description: Refresh the pipeline core (global ~/.claude, or a repo's bundled .claude) to the latest published cohorte version, then reconcile this repo's generated files to it — /cohorte-init-pipeline stays one-time.
4
4
  argument-hint: [path-to-local-checkout]
5
5
  ---
6
6
 
@@ -8,7 +8,7 @@ You are the **pipeline updater**. Refresh the installed pipeline core to the lat
8
8
  repo. The installer's `--update` mode never touches generated files: `PIPELINE.md`, rendered surface agents,
9
9
  `gate-config.json`, `settings.json`, and the filled `~/.claude/cohorte.config.yaml` are all preserved.
10
10
  YOU then bring those generated files up to the new core yourself (§3.5) — additively, never clobbering
11
- the human's choices — so `/init-pipeline` never needs re-running for an upgrade.
11
+ the human's choices — so `/cohorte-init-pipeline` never needs re-running for an upgrade.
12
12
 
13
13
  ## 1. Detect the install scope + current version
14
14
 
@@ -57,7 +57,7 @@ differs from the core you just installed, rewrite that one field (leave every ot
57
57
  and tell the human to commit it. In **bundled** mode the installer already did it; in **global** mode
58
58
  **nothing does** — the installer refreshes one shared core and cannot know which repos point at it,
59
59
  so before 1.2.5 the field simply drifted forever (a repo on a current core still claiming `1.0.0`).
60
- `/doctor` check 1 requires the pointer to be coherent with the VERSION file, so a drifted field reads
60
+ `/cohorte-doctor` check 1 requires the pointer to be coherent with the VERSION file, so a drifted field reads
61
61
  as a broken install when nothing is broken.
62
62
 
63
63
  Then print **What's new**: read the installed `<core>/pipeline/CHANGELOG.md` and show the entries
@@ -82,9 +82,19 @@ health check** (SCHEMA.md §Code retrieval: CLI resolvable from PATH, `.mcp.json
82
82
  upgrading a bare `serena` entry to the PATH-proof launcher form, `.serena/` gitignored, server
83
83
  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
- missing, say so. This is why `/init-pipeline` never needs re-running for a core upgrade.
85
+ missing, say so. This is why `/cohorte-init-pipeline` never needs re-running for a core upgrade.
86
86
 
87
- Two of the §Reconcile steps matter specifically here:
87
+ Four of the §Reconcile steps matter specifically here:
88
+
89
+ - **Local-artifact hygiene** (§Reconcile step 8): gitignore + untrack the pipeline's runtime files
90
+ (`.claude/preflight.ok`, `.claude/pipeline-metrics.jsonl`, `specs/reports/`). A tracked
91
+ `preflight.ok` — what every pre-2.0.0 install ends up with once a release agent stages `.claude/` —
92
+ makes the phase gate ask on every single review dispatch, so fix it here and say so.
93
+
94
+ - **Spec-template top-up** (§Reconcile step 7): `specs/_template.md` was seeded at install and never
95
+ refreshed since, so add the front-matter fields the current `templates/spec.template.md` has and the
96
+ repo's copy lacks (1.6 added `loop_pass`/`loop_phase` and two states to the `status:` comment) —
97
+ front-matter only, never the body.
88
98
 
89
99
  - **Global config seed** (§Reconcile step 5): if `~/.claude/cohorte.config.yaml` is absent, seed it
90
100
  from the template so the kanban + shared-vault config has a home. Never clobber an existing filled
@@ -106,7 +116,7 @@ Two of the §Reconcile steps matter specifically here:
106
116
  - **Restart / reload the Claude Code session** so it picks up updated commands, agents, and any
107
117
  newly-registered MCP server.
108
118
  - **Other repos using the global core:** their core is already fresh, but reconcile is per-repo — run
109
- `/update-pipeline` inside each (it will skip the already-done core update and just reconcile).
119
+ `/cohorte-update-pipeline` inside each (it will skip the already-done core update and just reconcile).
110
120
  - **Commit** the reconciled files (`PIPELINE.md`, `.claude/`, `.mcp.json` if added) so teammates get them.
111
121
  - The kanban config is global and user-scoped
112
122
  (`~/.claude/cohorte.config.yaml`) — never committed. The core update never touches it; only the
@@ -6,7 +6,7 @@ inspecting the FULL command string so chained forms like
6
6
  `cd apps/api && node ace migration:run` are caught — something prefix-based
7
7
  permission rules in settings.json miss.
8
8
 
9
- Patterns come from `.claude/gate-config.json` (generated by /init-pipeline from
9
+ Patterns come from `.claude/gate-config.json` (generated by /cohorte-init-pipeline from
10
10
  the PIPELINE.md `gate` block):
11
11
 
12
12
  { "deny": ["node ace migration:fresh", ...],
@@ -16,7 +16,7 @@ A pattern matches a command segment when the segment *contains* the pattern
16
16
  (after normalizing whitespace). `deny` wins over `ask`. If the config is missing
17
17
  or unreadable, the hook stays silent (exit 0) and lets settings.json decide.
18
18
 
19
- The hook fires for EVERY agent in the session — the lead, /build's implementers,
19
+ The hook fires for EVERY agent in the session — the lead, /cohorte-build's implementers,
20
20
  and subagents spawned by the Workflow runtime alike. Workflow subagents run in
21
21
  acceptEdits regardless of the session's permission mode (their Write/Edit calls
22
22
  are auto-approved), but acceptEdits does NOT auto-approve Bash or Task, so this
@@ -31,7 +31,10 @@ Two extra duties beyond Bash patterns:
31
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
- reviewers onto code that doesn't compile burns their whole run.
34
+ reviewers onto code that doesn't compile burns their whole run. Freshness is
35
+ a content digest (see tree_digest), not the HEAD sha: the reviewed tree is
36
+ normally dirty, so a commit must not invalidate a stamp and an uncommitted
37
+ edit must. The stamp is local and gitignored — never versioned.
35
38
 
36
39
  Protocol: reads the PreToolUse payload on stdin; emits a JSON permissionDecision
37
40
  of "deny" or "ask" on a match; otherwise exits 0 silently.
@@ -40,8 +43,10 @@ of "deny" or "ask" on a match; otherwise exits 0 silently.
40
43
  import json
41
44
  import os
42
45
  import re
46
+ import shutil
43
47
  import subprocess
44
48
  import sys
49
+ import tempfile
45
50
  import time
46
51
 
47
52
  SPLIT = re.compile(r"&&|\|\||[;|\n]")
@@ -129,6 +134,84 @@ def known_heads(cwd: str):
129
134
  return heads
130
135
 
131
136
 
137
+ def worktree_dirs(cwd: str):
138
+ """`cwd` plus every linked worktree path — the checkouts a stamp may describe."""
139
+ dirs = [cwd]
140
+ try:
141
+ out = subprocess.run(
142
+ ["git", "worktree", "list", "--porcelain"],
143
+ cwd=cwd, capture_output=True, text=True, timeout=3,
144
+ )
145
+ if out.returncode == 0:
146
+ for line in out.stdout.splitlines():
147
+ if line.startswith("worktree "):
148
+ p = line.split(" ", 1)[1].strip()
149
+ if p and p not in dirs:
150
+ dirs.append(p)
151
+ except Exception:
152
+ pass
153
+ return dirs
154
+
155
+
156
+ def tree_digest(cwd: str):
157
+ """The digest pipeline/scripts/preflight.sh stamps for the checkout at `cwd`: the git
158
+ tree id of the working tree, computed in a throwaway index (`.claude` and `specs`
159
+ excluded — the pipeline writes those itself). Content-addressed, so a commit of the
160
+ same code keeps the stamp valid and any real edit invalidates it. Must stay identical
161
+ to the shell side. Returns None when git can't answer, and the caller falls back."""
162
+ tmp = None
163
+ try:
164
+ idx = subprocess.run(
165
+ ["git", "rev-parse", "--absolute-git-dir"],
166
+ cwd=cwd, capture_output=True, text=True, timeout=3,
167
+ )
168
+ if idx.returncode != 0:
169
+ return None
170
+ real_index = os.path.join(idx.stdout.strip(), "index")
171
+ fd, tmp = tempfile.mkstemp(prefix="cohorte-idx-")
172
+ os.close(fd)
173
+ # Seed from the real index to keep its stat cache — `add` then re-hashes only
174
+ # what changed instead of the whole tree on every dispatch.
175
+ if os.path.exists(real_index):
176
+ shutil.copyfile(real_index, tmp)
177
+ else:
178
+ os.unlink(tmp) # a 0-byte index is a corrupt index — let git create it
179
+ env = dict(os.environ, GIT_INDEX_FILE=tmp)
180
+ # Drop the excluded paths outright — an `add` exclude only stops them being
181
+ # *updated*, so already-tracked ones would still shift the tree id.
182
+ subprocess.run(
183
+ ["git", "rm", "--cached", "-r", "-q", "--ignore-unmatch", "--", ".claude", "specs"],
184
+ cwd=cwd, capture_output=True, timeout=10, env=env,
185
+ )
186
+ add = subprocess.run(
187
+ ["git", "add", "-A", "--", ".", ":(exclude).claude", ":(exclude)specs"],
188
+ cwd=cwd, capture_output=True, timeout=30, env=env,
189
+ )
190
+ if add.returncode != 0:
191
+ return None
192
+ out = subprocess.run(
193
+ ["git", "write-tree"], cwd=cwd, capture_output=True, text=True, timeout=10, env=env,
194
+ )
195
+ if out.returncode != 0:
196
+ return None
197
+ return out.stdout.strip() or None
198
+ except Exception:
199
+ return None
200
+ finally:
201
+ for p in ((tmp, tmp + ".lock") if tmp else ()):
202
+ try:
203
+ os.unlink(p)
204
+ except Exception:
205
+ pass
206
+
207
+
208
+ def known_digests(cwd: str):
209
+ """Every checkout's current digest. The preflight may have run in a feature worktree
210
+ while the Task dispatch fires from the main checkout (or vice versa) — same reason
211
+ known_heads() spans worktrees."""
212
+ return {d for d in (tree_digest(p) for p in worktree_dirs(cwd)) if d}
213
+
214
+
132
215
  def check_preflight(payload: dict, cfg: dict) -> int:
133
216
  """Phase gate on Task dispatches: review agents need a green preflight stamp."""
134
217
  pf = cfg.get("preflight") or {}
@@ -149,17 +232,29 @@ def check_preflight(payload: dict, cfg: dict) -> int:
149
232
  why = "no preflight stamp found"
150
233
  if raw is not None:
151
234
  try:
152
- epoch_s, _, sha = raw.partition(" ")
235
+ fields = raw.split()
236
+ epoch_s = fields[0]
237
+ sha = fields[1] if len(fields) > 1 else ""
238
+ digest = fields[2] if len(fields) > 2 else ""
153
239
  age_min = (time.time() - float(epoch_s)) / 60
154
240
  max_age = float(pf.get("max_age_minutes", 30) or 30)
241
+ cwd = session_cwd(payload)
155
242
  if age_min > max_age:
156
243
  why = f"the preflight stamp is {age_min:.0f} min old (max {max_age:.0f})"
244
+ elif digest not in ("", "none"):
245
+ # 2.0.0+ stamp: key off the verified CONTENT, not HEAD. A commit that
246
+ # doesn't touch code keeps the stamp valid; an edit that never reaches a
247
+ # commit invalidates it — both of which the sha comparison got backwards.
248
+ digests = known_digests(cwd)
249
+ if digests and digest not in digests:
250
+ why = "the code changed since the preflight ran"
157
251
  else:
158
- heads = known_heads(session_cwd(payload))
252
+ # Pre-2.0.0 stamp (no digest) — fall back to the HEAD comparison.
253
+ heads = known_heads(cwd)
159
254
  if heads and sha not in ("", "none") and sha not in heads:
160
255
  why = "HEAD moved since the preflight ran"
161
256
  except Exception:
162
- why = "the preflight stamp is unreadable (expected `<epoch> <sha>`)"
257
+ why = "the preflight stamp is unreadable (expected `<epoch> <sha> <digest>`)"
163
258
  if why is None:
164
259
  return 0
165
260
  # Same rule as the Bash gate: unattended runs have nobody to answer an "ask".
@@ -1,6 +1,6 @@
1
1
  # BRAINSTORM RETURN — <feature title>
2
2
 
3
- > Paste this into `/spec`.
3
+ > Paste this into `/cohorte-spec`.
4
4
 
5
5
  **One-liner:** <what we're building, in one sentence>
6
6
 
@@ -24,13 +24,13 @@
24
24
 
25
25
  - **Data:** <entities / fields touched>
26
26
  - **Screens:** <key screens/flows — mobile-first if the project has UI>
27
- - **Interface (rough):** <endpoints / methods we'll need — refined into the contract in /spec>
27
+ - **Interface (rough):** <endpoints / methods we'll need — refined into the contract in /cohorte-spec>
28
28
 
29
29
  ## Risks & open questions
30
30
 
31
31
  - <risk / unknown>
32
- - <open question for /spec to resolve>
32
+ - <open question for /cohorte-spec to resolve>
33
33
 
34
34
  ## Panel dissent (what was contested)
35
35
 
36
- <the strongest disagreement the panel surfaced — so /spec doesn't paper over it>
36
+ <the strongest disagreement the panel surfaced — so /cohorte-spec doesn't paper over it>
@@ -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 `/cohorte-spec`, `/cohorte-brainstorm` and `/cohorte-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,7 +1,7 @@
1
1
  # DESIGN BRIEF — <feature title> (`<feature_id>`)
2
2
 
3
3
  > The "spec return". Paste into the design tool (see `PIPELINE.md` §design). This is §8 of the frozen
4
- > spec, standalone — `/spec` writes it to `specs/design/<feature_id>.md` on freeze. Omit entirely if
4
+ > spec, standalone — `/cohorte-spec` writes it to `specs/design/<feature_id>.md` on freeze. Omit entirely if
5
5
  > the project has no UI.
6
6
 
7
7
  **Goal:** <one line — what the user accomplishes>
@@ -1,11 +1,13 @@
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
- reviewed_base: # merge-base sha at the last SHIP verdict freshness-gate anchor (written by /review)
8
- reviewed_digest: # sha256 (16 hex) of the reviewed source diff vs reviewed_base, specs excluded /ship re-checks
7
+ loop_pass: 0 # /cohorte-loop bookkeeping the review pass it is on; 0 = no loop running. Written by the driver, not by hand
8
+ loop_phase: # /cohorte-loop bookkeeping build | review | fix | done. With loop_pass, this is what `--resume` reads back
9
+ reviewed_base: # merge-base sha at the last SHIP verdict — freshness-gate anchor (written by /cohorte-review)
10
+ reviewed_digest: # sha256 (16 hex) of the reviewed source diff vs reviewed_base, specs excluded — /cohorte-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
10
12
  ---
11
13
 
@@ -45,7 +47,7 @@ design_files: [] # design page links — full URLs of the form https://claude.ai
45
47
 
46
48
  <repeat per endpoint/interface>
47
49
 
48
- ### Contract types — lead authors `<contract.path>/<slug>.<ext>` before /build
50
+ ### Contract types — lead authors `<contract.path>/<slug>.<ext>` before /cohorte-build
49
51
 
50
52
  - `<slug>...Request` / `<slug>...Response` / `<slug>Base` … (sketch the schemas here)
51
53
 
@@ -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 (`/build` implements "§8 flows", `/review` checks "§9 acceptance"), so a
58
+ > across the whole pipeline (`/cohorte-build` implements "§8 flows", `/cohorte-review` checks "§9 acceptance"), so a
57
59
  > third surface must not become a §8 of its own.
58
60
 
59
61
  ### <surface.key>
@@ -83,5 +85,5 @@ design_files: [] # design page links — full URLs of the form https://claude.ai
83
85
 
84
86
  ## Remediation
85
87
 
86
- > Filled by `/spec` in review-return mode; empty otherwise. Each item:
88
+ > Filled by `/cohorte-spec` in review-return mode; empty otherwise. Each item:
87
89
  > `[ ] <SEVERITY> · <file:line> · <spec-violation|quality|security> · <concrete fix>`
@@ -1,4 +1,4 @@
1
- # /init-pipeline · 01 Detect the stack
1
+ # /cohorte-init-pipeline · 01 Detect the stack
2
2
 
3
3
  ### Phase 1 — Detect the stack (read-only, no questions yet)
4
4