cohorte 2.8.0 → 2.10.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 (38) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +41 -46
  3. package/bin/cli.js +8 -41
  4. package/bin/report.js +2 -2
  5. package/core/commands/cohorte-brainstorm.md +5 -1
  6. package/core/commands/cohorte-fleet.md +103 -0
  7. package/core/commands/cohorte-intake.md +92 -0
  8. package/core/commands/cohorte-patch.md +6 -1
  9. package/core/commands/cohorte-retro.md +85 -0
  10. package/core/commands/cohorte-review.md +53 -1
  11. package/core/commands/cohorte-ship.md +2 -2
  12. package/core/hooks/gate.py +1 -1
  13. package/core/workflows/loop.js +27 -3
  14. package/core/workflows/review.js +13 -2
  15. package/{dashboard/server → lib}/doctor.js +3 -2
  16. package/{dashboard/server → lib}/runtime.js +1 -1
  17. package/{dashboard/server → lib}/versions.js +2 -2
  18. package/package.json +3 -8
  19. package/profile/SCHEMA.md +16 -8
  20. package/scripts/{test-dashboard.mjs → test-lib.mjs} +12 -222
  21. package/scripts/test-workflows.mjs +31 -2
  22. package/scripts/validate-core.mjs +7 -21
  23. package/dashboard/README.md +0 -71
  24. package/dashboard/dist/apple-touch-icon-180.png +0 -0
  25. package/dashboard/dist/assets/index-BZ_LQlEj.css +0 -1
  26. package/dashboard/dist/assets/index-DO3_nq2Q.js +0 -43
  27. package/dashboard/dist/favicon-16.png +0 -0
  28. package/dashboard/dist/favicon-32.png +0 -0
  29. package/dashboard/dist/favicon-48.png +0 -0
  30. package/dashboard/dist/icon-192.png +0 -0
  31. package/dashboard/dist/icon-512.png +0 -0
  32. package/dashboard/dist/index.html +0 -16
  33. package/dashboard/server/fleet.js +0 -133
  34. package/dashboard/server/index.js +0 -408
  35. package/dashboard/server/kanban.js +0 -169
  36. package/dashboard/server/metrics.js +0 -113
  37. package/dashboard/server/usage.js +0 -61
  38. /package/{dashboard/server → lib}/yaml.js +0 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,78 @@ short, user-facing, most recent first. One `## <version> — <YYYY-MM-DD>` secti
7
7
  > They are history and are deliberately not rewritten — every command gained a `cohorte-` prefix
8
8
  > in 2.0.0.
9
9
 
10
+ ## 2.10.0 — 2026-08-24
11
+
12
+ - **The dashboard is gone.** The local web cockpit — the `cohorte dashboard` verb, the
13
+ dependency-free HTTP server, the Vite/React app and the prebuilt `dashboard/dist` that shipped
14
+ in every tarball — is removed, along with the fleet registry, kanban and usage readers that
15
+ only it consumed. It was a second surface for state the CLI already reports, with its own
16
+ build step, its own security posture (loopback-bound because its action endpoints executed
17
+ code: install, update, reset, and `/cohorte-init-pipeline`·`/cohorte-update-pipeline`·`/cohorte-audit`
18
+ through headless Claude), and its own drift to police. `cohorte doctor`, `cohorte specs` and
19
+ `cohorte metrics` answer the same questions from the shell, and `doctor` exits 1 on any bad
20
+ check, which the browser never could.
21
+
22
+ **What did not go with it:** the four modules those verbs actually run on — the JS port of
23
+ `/cohorte-doctor`, the runtime-layout resolver, the version prober and the block-YAML parser —
24
+ moved from `dashboard/server/` to **`lib/`**. `cohorte doctor` / `specs` / `version` behave
25
+ exactly as before, and so do the Francois extension's `--panel` payloads. Their test suite
26
+ moved with them (`scripts/test-dashboard.mjs` ⇒ `scripts/test-lib.mjs`, 54 assertions), minus
27
+ the cases that covered deleted code.
28
+
29
+ Fallout worth naming: the package no longer has a build step at all (`prepack` and
30
+ `build:dashboard` are gone, npm and CI no longer build a React app to publish), the tarball
31
+ drops from 88 files to 73, and `~/.claude/cohorte-dashboard.json` — the tracked-project list —
32
+ is now dead; delete it. `--port` / `--host` / `--open` and `COHORTE_DASHBOARD_PORT` /
33
+ `COHORTE_DASHBOARD_HOST` no longer exist; `cohorte dashboard` prints the usage and exits 2.
34
+
35
+ ## 2.9.0 — 2026-08-22
36
+
37
+ - **`/cohorte-intake` — the door before the doors.** Work has always entered the pipeline
38
+ pre-distilled by a human: a ticket became a `/cohorte-patch` paste, an email became a
39
+ brainstorm idea, by hand. Intake does the distillation: paste anything that arrives and it
40
+ triages — bug ⇒ the exact structure patch interviews for (inferred repro steps labeled as
41
+ such); feature ⇒ a brainstorm seed whose open-questions list is the panel's agenda, with any
42
+ `_decisions.md` line it contradicts named; noise ⇒ says so and stops, because inventing a
43
+ spec from noise costs a whole pipeline run downstream. The distillate lands on disk and on
44
+ the kanban's Ideas column; freezing anything stays the human's move.
45
+
46
+ - **`/cohorte-retro` — the pipeline learns from its own findings.** Every review leaves
47
+ structured residue (verdict.json, the specs' Remediation history, the deferred backlog) that
48
+ nothing ever read back. Retro mines it for patterns — the same finding kind on the same
49
+ surface across features, a hotspot module, a recurring fix family — and turns each into ONE
50
+ rule-shaped §Conventions line the human ratifies. Adopted rules re-render the affected
51
+ surface agents (the conventions slice is baked at render time — a rule without a re-render
52
+ is one reviewers enforce and implementers never saw) and land in the decisions journal.
53
+ Findings → rules → the next build never produces the finding: cheaper than any number of
54
+ review rounds catching it.
55
+
56
+ - **`/cohorte-fleet` — parallel features get a flight controller.** The worktree isolation
57
+ existed; the coordination lived in your head. `plan` builds the feature×surface overlap
58
+ matrix from the specs themselves (contract dependencies ⇒ merge order; same-tree writes ⇒
59
+ serialize or drop one), provisions the worktrees, and prints one launch line per feature.
60
+ `status` is one row per feature ending in the single next action. `sync` does the post-merge
61
+ sweep everyone forgets — rebase every survivor, report conflicts verbatim to their owner's
62
+ session, and say out loud that a rebase invalidates the freshness stamp. What it will never
63
+ do is spawn the work headless: that is the retired 2.2.0 driver's grave, and each feature's
64
+ loop keeps running in its own supervised session.
65
+
66
+ - **`/cohorte-review --pr <num>` — the pipeline reviews incoming work.** Same reviewers, same
67
+ report, none of the pipeline's certifications: the PR is fetched into a throwaway worktree
68
+ (your checkout untouched), reviewed in audit mode (no spec to conform to), and a red
69
+ preflight makes the mechanical failures the review instead of wasting reviewers on code that
70
+ doesn't compile. Posting the report as a PR comment always asks first — it is outward-facing
71
+ — and declining leaves the report on disk as a complete outcome.
72
+
73
+ - **What a feature costs, finally on the dashboard.** The workflow paths now stamp an
74
+ approximate `tokens` field into their metrics lines from the runtime's own counter
75
+ (`budget.spent()` deltas — the figure a conversational lead cannot read), the loop's history
76
+ carries cost per round, and the dashboard shows `~Nk tok` per feature and per phase. Marks
77
+ are placed around the review child so build/fix deltas never double-count it; token-less
78
+ conversational lines aggregate as absent, never as "free".
79
+
80
+
81
+
10
82
  ## 2.8.0 — 2026-08-22
11
83
 
12
84
  - **`/cohorte-loop` is back — as a workflow, which is the whole point.** The 2.2.0 driver was
package/README.md CHANGED
@@ -24,6 +24,37 @@ Cursor, Gemini CLI or OpenCode. Install it once globally, then one command per p
24
24
  /cohorte-brainstorm → /cohorte-spec → (design) → /cohorte-build <id> → /cohorte-review → (/cohorte-fix) → /cohorte-ship
25
25
  ```
26
26
 
27
+ <div align="center">
28
+ <img src="https://raw.githubusercontent.com/TheBidouilleAgency/cohorte/main/assets/demo-cli.gif" alt="cohorte doctor reporting a green pipeline, the spec board, and the gate denying a chained destructive command" width="760">
29
+ <br>
30
+ <sub><code>cohorte doctor</code> · the spec board · the gate refusing a hard-denied command chained behind a benign one.<br>
31
+ Recorded from the real CLI by <code>scripts/demo/record-cli.sh</code> — no output is hand-edited.</sub>
32
+ </div>
33
+
34
+ ## Why a pipeline at all
35
+
36
+ Your coding agent is already good at the first prompt of a feature. It gets worse at every one
37
+ after — because what it knows lives in a conversation, and a conversation gets summarised,
38
+ truncated, and re-sent at input price on every turn. Cohorte is the set of constraints that stop
39
+ that degradation. Each one is a failure you have already had:
40
+
41
+ | The failure | The constraint |
42
+ | --- | --- |
43
+ | *"It forgot what we decided."* | The spec is **frozen to disk** and re-read by every stateless agent. Nothing is remembered, so nothing is forgotten — and `/clear` between stages is always safe. |
44
+ | *"The API and the UI don't fit."* | The **contract is authored before any implementer is dispatched**. Surfaces import it read-only and never talk to each other — which is what makes building them in parallel safe. |
45
+ | *"It edited a file I didn't want it to."* | **One owner per tree**, enforced in the rendered agent's frontmatter — not requested politely in a prompt. |
46
+ | *"It ran something it shouldn't have."* | `gate.py` is a **real blocking hook**, branch-aware, hard-denying destructive commands from every agent — subagents included. |
47
+ | *"The review said it was fine."* | Review is a **separate read-only agent** that sees the spec and the diff, never the conversation that produced them. A dead reviewer's zero findings can't read as ship. |
48
+ | *"Four agents just told me it doesn't compile."* | A **deterministic preflight** runs typecheck/lint/tests first. Red ⇒ **zero agents spawned**. |
49
+ | *"We fixed this same thing last week."* | `/cohorte-retro` turns repeated findings into ratified conventions **baked into the implementers**, so the next build never produces them. |
50
+
51
+ None of this is intelligence added to your agent — it's the opposite. It moves the decisions an
52
+ agent makes badly under context pressure into files, hooks and ownership boundaries, where they're
53
+ cheap and don't degrade with conversation length. Which is why it's markdown, shell and one Python
54
+ hook: **your app code never imports anything from Cohorte.**
55
+
56
+ [**The long version — including the four cases where you shouldn't use it →**](https://thebidouilleagency.github.io/cohorte/guide/why-cohorte)
57
+
27
58
  ## How it works — three layers
28
59
 
29
60
  | Layer | What it holds | Lives in | Scope |
@@ -206,45 +237,6 @@ decisions), surface agents are re-rendered, settings are patched additively, new
206
237
  wired. **`/cohorte-init-pipeline` is one-time per project** — after init, `/cohorte-update-pipeline` is the only
207
238
  maintenance command you ever run (`/cohorte-build` auto-grows surfaces as specs need them).
208
239
 
209
- ## Dashboard — a local web cockpit
210
-
211
- A browser view of pipeline state, for when a checklist beats scanning files:
212
-
213
- ```sh
214
- cohorte dashboard # serves http://localhost:4317 (Ctrl-C to stop)
215
- cohorte dashboard <path> # start focused on another project
216
- cohorte dashboard --port=4400 --open # custom port, open the browser
217
- ```
218
-
219
- **Bound to `127.0.0.1` by default** — the dashboard's actions execute code (install/update/reset,
220
- and `/cohorte-init-pipeline`·`/cohorte-update-pipeline`·`/cohorte-audit` via headless Claude), so it must stay on loopback. Each user
221
- runs their own agent and drives only their own machine. `--host=0.0.0.0` exposes it to the network
222
- (it prints a security warning) — only on a trusted network, since anyone who reaches the port can run
223
- those actions.
224
-
225
- - **Fleet overview** — the global core version vs npm latest, plus every tracked project's freshness
226
- and health at a glance. Add a project by absolute path or with the **folder picker** (Browse…); the
227
- set is remembered in `~/.claude/cohorte-dashboard.json`.
228
- - **Per-project drill-down** — Freshness (installed core vs npm), `/cohorte-doctor` rendered as a live
229
- ✅/⚠️/❌ checklist (each failure with its fix), the **Surfaces ↔ agents** map from `PIPELINE.md`,
230
- and one board: a **Kanban** if the project has a linked Obsidian board (columns + cards from the
231
- vault, with clickable PR links + live open/merged/closed status and a ship-date-sorted Shipped
232
- column, via `gh`), otherwise a **Specs board** from `specs/*.md` (by `draft · frozen · in-progress ·
233
- in-review · blocked · shipped`). The Kanban supersedes the Specs board when both would apply.
234
- - **Actions** (stream their output live) — **Update / Install core** (the shared global core, or a
235
- repo's bundled core); **Init-pipeline / Update-pipeline / Audit**, which run those Claude Code
236
- commands **headless** (`claude -p`, autonomous — Init skips the interactive interview, so review
237
- the result; Audit writes `specs/refactor-backlog.md`; headless runs start without any prompt and
238
- have **no resume** if the session dies); and **Reset pipeline**, which backs up then wipes a
239
- project's pipeline footprint and reinstalls a fresh core. Buttons render only when they apply
240
- (e.g. Init only when there's no profile). The drill-down's health checklist also shows the
241
- **workflows** state (scripts + profile-reader installed, which path a session will take).
242
-
243
- Runtime is **dependency-free** — node's built-in `http` server serves a prebuilt React app (the app
244
- source lives in `dashboard/app/`, built to `dashboard/dist/` at publish time). The `/cohorte-doctor` checks
245
- are reimplemented in JS, so the dashboard needs no Claude session to compute state. See
246
- [`dashboard/README.md`](dashboard/README.md) for the architecture.
247
-
248
240
  ## Reading a project without an agent
249
241
 
250
242
  The two read-only halves of the pipeline, in the shell:
@@ -260,8 +252,8 @@ extension below renders.
260
252
 
261
253
  `doctor`'s exit code makes it a CI step as-is. Add `--porcelain` for one record per line with
262
254
  `U+001F` between fields (a spec title with a space in it never misaligns a column), or `--json`
263
- for the native document. Both reuse the dashboard's own readers, so the board and the CLI can
264
- never give two answers about one repo.
255
+ for the native document. Both read through `lib/`, so every consumer the CLI, a Francois
256
+ panel answers from one implementation and the repo never gets two verdicts.
265
257
 
266
258
  `--panel` — on `specs`, `doctor` and `metrics` — emits the payload shape a
267
259
  [Francois](https://github.com/antoine-gmnz/francois) extension panel expects. It is the one
@@ -301,15 +293,18 @@ so a CLI pinned at an old version cannot quietly re-lay an old core.
301
293
  | Command | Role |
302
294
  | -------------------- | ------------------------------------------------------------------------------------- |
303
295
  | `/cohorte-init-pipeline` | Detect stack → interview → generate the profile + agents. Run once per project. |
296
+ | `/cohorte-intake [paste]` | Triage anything that arrives (ticket, email, trace, thread) into a `/cohorte-patch` handoff or a brainstorm seed — staged to disk, carded in Ideas. |
304
297
  | `/cohorte-brainstorm` | Interactive persona panel that pressure-tests a feature idea. |
305
298
  | `/cohorte-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. |
306
299
  | `/cohorte-patch [bug]` | Bug-fix entry: triage a bug and freeze a ~60-line patch spec (`specs/patch-<slug>.md`) whose regression test replaces the contract. Then the normal `/cohorte-build → review → ship`. |
307
300
  | `/cohorte-build <id>` | Readiness gate on the frozen spec, then the lead authors the contract and dispatches one implementer per surface in parallel. |
308
- | `/cohorte-review <id>` | Read-only review agents (one per touched surface, parallel) audit the diff vs the spec; out-of-scope findings go to the refactor backlog. |
301
+ | `/cohorte-review <id>` | Read-only review agents (one per touched surface, parallel) audit the diff vs the spec; out-of-scope findings go to the refactor backlog. `--pr <num>` reviews an **incoming** GitHub PR in a throwaway worktree and offers to post the report as a comment. |
309
302
  | `/cohorte-fix <id>` | Apply a review report: remediation into the spec, re-dispatch only the surfaces with findings. |
303
+ | `/cohorte-fleet plan\|status\|sync` | Fly several features in parallel: overlap matrix + merge order, one worktree per feature, live status, post-merge rebase sweep. Coordination only — never headless execution. |
310
304
  | `/cohorte-ship <id>` | Release agent commits, pushes, opens the PR; watches CI; proposes worktree teardown. |
311
305
  | `/cohorte-audit [path]` | Prioritized refactor backlog for existing code. |
312
306
  | `/cohorte-refactor <domain>` | Apply the backlog for one surface, TDD-first. |
307
+ | `/cohorte-retro [last n]` | Mine the accumulated review findings for patterns; ratified ones become §Conventions rules and the surface agents are re-rendered — the next build never produces the finding. |
313
308
  | `/cohorte-align-ds` | Align the code UI kit to the design system (no-op if none configured). |
314
309
  | `/cohorte-update-pipeline` | Refresh the installed core (global or bundled) to the latest published version. |
315
310
  | `/cohorte-doctor` | Diagnose the installation (core, agents↔surfaces, hooks, gate, retrieval, worktrees). |
@@ -415,7 +410,10 @@ See `profile/SCHEMA.md` for every field in `PIPELINE.md` and how the pipeline us
415
410
 
416
411
  ```
417
412
  package.json # npm package (cohorte) — semver source of truth
418
- bin/cli.js # the npm CLI: install / update / dashboard / specs / doctor / metrics / version (cross-platform, no deps)
413
+ bin/cli.js # the npm CLI: install / update / specs / doctor / metrics / version (cross-platform, no deps)
414
+ bin/report.js # the four renderings of `specs` / `doctor` (human, porcelain, json, panel)
415
+ lib/ # shared readers behind those verbs — doctor.js (the JS port of
416
+ # /cohorte-doctor) + runtime.js / versions.js / yaml.js. No deps.
419
417
  install.sh # script installer (fresh + --update) for npm-less setups — still needs Node
420
418
  install.ps1 # same installer for Windows PowerShell (fresh + -Update)
421
419
  core/ # copied verbatim into ~/.claude (global) or <project>/.claude (bundled)
@@ -429,7 +427,4 @@ profile/
429
427
  SCHEMA.md # field reference
430
428
  cohorte.config.template.yaml # seeds ~/.claude/cohorte.config.yaml (kanban)
431
429
  scripts/ # worktree-isolation templates + the shipped preflight/kanban scripts
432
- dashboard/ # local web cockpit (cohorte dashboard) — see dashboard/README.md
433
- server/ # dependency-free node runtime (serves the built app + JSON/stream API)
434
- app/ # Vite + React source (built to dashboard/dist/ at publish time)
435
430
  ```
package/bin/cli.js CHANGED
@@ -40,7 +40,6 @@ function usage(code) {
40
40
  Usage:
41
41
  cohorte install [target] [--global] [--runtime=a,b | --all-runtimes]
42
42
  cohorte update [target] [--global] [--runtime=a,b | --all-runtimes]
43
- cohorte dashboard [target] [--port=N] [--host=ADDR] [--open]
44
43
  cohorte metrics [target] [--days=N] [--since=ISO] [--runs] [--json]
45
44
  cohorte specs [target] [--porcelain | --json | --panel]
46
45
  cohorte doctor [target] [--porcelain | --json | --panel]
@@ -53,11 +52,6 @@ Commands:
53
52
  update Refresh the stack-agnostic core only. PIPELINE.md, rendered surface
54
53
  agents, gate-config.json, settings.json and your filled
55
54
  ~/.claude/cohorte.config.yaml are never touched.
56
- dashboard Serve a local web cockpit for the pipeline (freshness, /cohorte-doctor
57
- health, specs board, install/update actions). Binds 127.0.0.1:4317
58
- by default (loopback only — its actions execute code). --host=ADDR
59
- to expose (e.g. --host=0.0.0.0, prints a security warning). --open
60
- to launch the browser.
61
55
  metrics Cost and runtime per command, reconstructed from Claude Code's own
62
56
  transcripts (tokens, USD, wall/active time, subagent count). Reads
63
57
  ~/.claude/projects — nothing to enable, and it covers runs that
@@ -90,11 +84,6 @@ const args = process.argv.slice(2);
90
84
  let mode = null;
91
85
  let scope = 'project';
92
86
  let target = process.cwd();
93
- let port = parseInt(process.env.COHORTE_DASHBOARD_PORT, 10) || 4317;
94
- // Bind to loopback by default — the dashboard's action endpoints execute code (install/update/
95
- // reset/claude), so it must NOT be reachable from the network unless the user explicitly opts in.
96
- let host = process.env.COHORTE_DASHBOARD_HOST || '127.0.0.1';
97
- let openBrowser = false;
98
87
 
99
88
  // Flags that belong to `metrics` and are forwarded verbatim to the collector. They are
100
89
  // listed here rather than parsed, so the collector stays the single source of truth for
@@ -112,7 +101,7 @@ let format = 'human';
112
101
  let wantRuntimes = [];
113
102
 
114
103
  for (const a of args) {
115
- if (a === 'install' || a === 'update' || a === 'dashboard' || a === 'metrics'
104
+ if (a === 'install' || a === 'update' || a === 'metrics'
116
105
  || a === 'specs' || a === 'doctor') mode = a;
117
106
  else if (a === '--porcelain' || a === '--panel') format = a.slice(2);
118
107
  else if (isMetricsFlag(a)) { if (a === '--json') format = 'json'; metricsFlags.push(a); }
@@ -131,40 +120,18 @@ for (const a of args) {
131
120
  }
132
121
  else if (a === 'version' || a === '--version' || a === '-v') { console.log(VERSION); process.exit(0); }
133
122
  else if (a === '--global' || a === '-g') scope = 'global';
134
- else if (a.startsWith('--port=')) {
135
- // A bad value used to land as NaN, which http.listen() silently treats as
136
- // "any free port" — the banner then printed `localhost:NaN` and nothing worked.
137
- port = parseInt(a.slice(7), 10);
138
- if (!Number.isInteger(port) || port < 1 || port > 65535) {
139
- console.error(`error: --port must be an integer 1-65535 (got "${a.slice(7)}")`);
140
- process.exit(2);
141
- }
142
- } else if (a.startsWith('--host=')) {
143
- host = a.slice(7).trim();
144
- if (!host) { console.error('error: --host= needs an address (e.g. --host=0.0.0.0)'); process.exit(2); }
145
- }
146
- else if (a === '--open') { openBrowser = true; }
147
123
  else if (a === 'help' || a === '--help' || a === '-h') usage(0);
148
124
  else if (a.startsWith('-')) { console.error(`error: unknown flag: ${a}`); usage(2); }
149
125
  else target = path.resolve(a);
150
126
  }
151
127
  if (!mode) usage(args.length ? 2 : 0);
152
128
 
153
- // --- dashboard: local web cockpit -------------------------------------------
154
- // Short-circuits before the install/update machinery (CommonJS wraps the module,
155
- // so a top-level return is valid here). Runtime is dependency-free node `http`.
156
- if (mode === 'dashboard') {
157
- const globalDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
158
- require('../dashboard/server')({ projectRoot: target, globalDir, port, host, openBrowser, pkgRoot, version: VERSION });
159
- return;
160
- }
161
-
162
129
  // --- specs / doctor: read-only reports on <target> ---------------------------
163
- // Both reuse the dashboard's own readers, so the board and the CLI can never drift
164
- // into two answers about the same repo. Nothing here writes or spawns anything.
130
+ // Both read through lib/, so every consumer of the pipeline's state the CLI, a
131
+ // Francois panel answers from one implementation. Nothing here writes or spawns.
165
132
  if (mode === 'specs' || mode === 'doctor') {
166
133
  const report = require('./report.js');
167
- const { state, scanSpecs } = require('../dashboard/server/doctor.js');
134
+ const { state, scanSpecs } = require('../lib/doctor.js');
168
135
  const globalDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
169
136
 
170
137
  if (mode === 'specs') {
@@ -342,8 +309,8 @@ function copyCore() {
342
309
  paths: {
343
310
  core: paths.core, commands: paths.commands,
344
311
  agents: paths.agents || path.join(paths.core, 'agents'),
345
- // Where the gate registration lives. The dashboard needs it to tell a registered hook
346
- // from a missing one without re-deriving each runtime's config layout itself.
312
+ // Where the gate registration lives. `cohorte doctor` needs it to tell a registered
313
+ // hook from a missing one without re-deriving each runtime's config layout itself.
347
314
  hooks_config: paths.hooks_config,
348
315
  state: adapter.stateDir(runtime), config: adapter.configPath(runtime),
349
316
  },
@@ -565,7 +532,7 @@ async function seedConfig() {
565
532
  //
566
533
  // CLAUDE_CONFIG_DIR moves the whole `~/.claude` tree (globalDir already follows it), but
567
534
  // adapter.configPath() speaks in literal `~/.claude/…` — expanding through HOME alone
568
- // seeded a file at a path no reader probes (kanban-move.sh and the dashboard follow
535
+ // seeded a file at a path no reader probes (kanban-move.sh follows
569
536
  // CLAUDE_CONFIG_DIR), missed a filled config there (re-seeding disabled defaults beside
570
537
  // it — the exact two-file fork this function's comments forbid), and printed a banner
571
538
  // path that did not exist. Re-root the claude-shaped path onto globalDir so the seed,
@@ -705,7 +672,7 @@ async function staleVersionNotice() {
705
672
  if (process.env.COHORTE_NO_VERSION_CHECK || process.env.CI) return;
706
673
  let latest = null;
707
674
  try {
708
- const v = require('../dashboard/server/versions.js');
675
+ const v = require('../lib/versions.js');
709
676
  latest = await v.latestNpm({ timeoutMs: 2500, fallback: false });
710
677
  if (!latest || v.cmpSemver(VERSION, latest) >= 0) return;
711
678
  } catch { return; }
package/bin/report.js CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
  // Machine-readable reports for `cohorte specs` and `cohorte doctor`.
3
3
  //
4
- // Both commands read what the dashboard already computes (dashboard/server/doctor.js:
5
- // `scanSpecs` and `state`) and render it four ways:
4
+ // Both commands read what lib/doctor.js computes (`scanSpecs` and `state`) and
5
+ // render it four ways:
6
6
  //
7
7
  // default a human table on a terminal
8
8
  // --porcelain one record per line, fields separated by U+001F (the ASCII unit
@@ -31,7 +31,11 @@ Idea (may be empty): **$ARGUMENTS**
31
31
 
32
32
  If the idea is empty: when a board is configured and its **Ideas** column has cards, list them (with any
33
33
  sub-bullet notes as seed context) and let the human pick one — otherwise ask **"What are we building?"**.
34
- Either way, wait. If the idea is non-empty, restate it in one line and confirm you've got it.
34
+ Either way, wait. If the idea is non-empty, restate it in one line and confirm you've got it — and
35
+ **if `specs/reports/intake-<idea>.md` exists, read it FIRST**: it is `/cohorte-intake`'s staged seed
36
+ (who is asking with verbatim quotes, goals/non-goals, an open-questions list that is this panel's
37
+ agenda, prior art incl. any `_decisions.md` line the request contradicts). The panel argues against
38
+ the distillate, never against the bare slug.
35
39
 
36
40
  ## Run the panel
37
41
 
@@ -0,0 +1,103 @@
1
+ ---
2
+ model: sonnet
3
+ description: Fly several features in parallel — overlap analysis and merge order, one isolated worktree per feature, a live status board, and the post-merge rebase sweep nobody remembers to do.
4
+ argument-hint: plan <id> <id> [...] | status | sync
5
+ ---
6
+
7
+ You are the **fleet controller**. Parallel features already work in this pipeline — isolated
8
+ worktrees, per-feature DBs and ports, one session per worktree — but the coordination between
9
+ them is tribal knowledge: which specs collide, which merges first, and who rebases the
10
+ survivors after each ship. This command owns exactly that coordination, and deliberately
11
+ nothing more.
12
+
13
+ **What this command will never do: spawn the work headless.** The obvious "cool" version —
14
+ fire a loop per worktree from here — is the 2.2.0 driver's grave: child sessions nobody
15
+ supervises, stalling on prompts nobody sees. Each feature's build/review/loop runs in **its own
16
+ worktree's session**, supervised like any other; the fleet plans, watches and rebases. State
17
+ lives in `specs/reports/fleet.json` (the main checkout's gitignored buffer — this repo's
18
+ multi-*feature* flight plan).
19
+
20
+ > Read `PIPELINE.md` §`pipeline-profile` first: `surfaces` (paths — the overlap analysis keys
21
+ > on them), `contract`, `vcs.default_branch`, and the `isolation` block. _Skip the re-read if
22
+ > already in context and unmodified since._ **`isolation.enabled: false` ⇒ stop at `plan`**:
23
+ > N features in one checkout is a merge-conflict generator with extra steps — name the fix
24
+ > (`/cohorte-init-pipeline` wires isolation) and go no further.
25
+
26
+ ## `plan <id> <id> [...]` — collide, order, provision
27
+
28
+ 1. **Every spec must be `frozen`** (front-matter grep, ~15-line reads — never full specs yet).
29
+ Anything else (`draft`, `in-review`, missing) ⇒ name it and stop; a fleet of half-frozen
30
+ specs is N problems flying in formation.
31
+ 2. **Overlap analysis** — read each spec's §5 contract entries and §6 surface tasks, then build
32
+ the matrix *feature × surface*, and flag the two collision classes:
33
+ - **Contract dependency** — spec B's §5 references shapes spec A introduces ⇒ B ships
34
+ **after** A, and B's worktree must rebase once A merges (the `sync` mode's job). This is
35
+ an ordering, not a blocker.
36
+ - **Same-tree writes** — two specs whose §6 tasks land in the same `surfaces[].path` (worse:
37
+ the same module). This is where parallel merges bleed; propose either an order (lighter
38
+ feature first) or — when the overlap is one file both must edit — say plainly that these
39
+ two should not fly together, and let the human drop one from the fleet.
40
+ 3. **Propose the merge order** from those edges (dependencies first, then ascending overlap),
41
+ show the matrix + order in a compact table, and get the human's go-ahead — the order is a
42
+ plan they will live with for days.
43
+ 4. **Provision worktrees** — for each feature without one: `scripts/new-feature.sh <id>` (the
44
+ rendered isolation script: worktree + branch + DB + port slot). Relay each script's output
45
+ line; a script failure stops the plan for that feature, never silently.
46
+ 5. **Write `specs/reports/fleet.json`** (overwrite): `{"ts":"<ISO>","order":[...ids in merge
47
+ order...],"features":{"<id>":{"worktree":"<path>","branch":"<branch>","dependsOn":[...]}}}`.
48
+ 6. **Print the launch plan** — one line per feature, in order: the worktree path to open a
49
+ session in, and the first command to run there (`/cohorte-build <id>`, or "ask for the loop
50
+ workflow: `{feature: \"<id>\"}`" on a runtime that has it). The human launches them; the
51
+ fleet does not.
52
+
53
+ ## `status` — one table, no archaeology
54
+
55
+ Read `fleet.json` (absent ⇒ say `plan` comes first, stop). For each feature, **mechanical reads
56
+ only, redirected — always from THAT feature's worktree**, never the main checkout (each worktree
57
+ carries its own copy of `specs/<id>.md` and `specs/reports/`, and that copy is the one its run
58
+ has been writing): `<worktree>/specs/<id>.md` front-matter `status` · the worktree's
59
+ `specs/reports/<id>.loop.json` (`phase`/`round`/`outcome`) and `<id>.verdict.json`
60
+ (`verdict`/`blocking`) when present · then ONE
61
+ `git -C <worktree> fetch --quiet origin <default_branch> || true` and
62
+ `git -C <worktree> rev-list --count origin/<default_branch>..HEAD` and `..origin/<default_branch>`
63
+ (ahead / **behind** — behind is the number that matters, and against the *remote* ref: the local
64
+ one goes stale the moment a PR merges on the host, which is precisely when status gets asked).
65
+ One row per feature, in merge order:
66
+
67
+ ```
68
+ <id> · <status> · loop: <phase> r<round> | <outcome> · blocking: <n> · ↑<ahead> ↓<behind> · next: <the one action>
69
+ ```
70
+
71
+ `next` is the whole point of the mode: the single action per feature (a command to run in its
72
+ worktree, "waiting on <dep> to merge", "ready to ship — its turn in the order", or "rebase
73
+ needed — run sync"). A worktree registered in `fleet.json` but gone from
74
+ `git worktree list` is reported as such, never silently dropped.
75
+
76
+ ## `sync` — the post-merge sweep
77
+
78
+ Run after every merge (the human says which feature shipped, or you detect it: spec
79
+ `status: shipped` + branch merged into `<default_branch>`):
80
+
81
+ 1. Drop the shipped feature from `fleet.json` (rewrite, keep order of the rest) — and if its
82
+ worktree still exists, remind the teardown `/cohorte-ship` proposes:
83
+ `scripts/remove-feature.sh <id>` (`--drop-db` at the human's call). Never run it unasked.
84
+ 2. For each surviving worktree, in merge order — `git -C <worktree> fetch origin
85
+ <default_branch>` **always**, then decide whether the rebase is THIS session's to run:
86
+ - **Tree dirty** (`git -C <worktree> status --porcelain` non-empty — the NORMAL mid-flight
87
+ state: feature work stays uncommitted until `/cohorte-ship`, so `git rebase` would refuse
88
+ with "unstaged changes" before any conflict even exists) **or a run in flight** (the
89
+ worktree's `loop.json` has no `outcome`): **do not touch it.** Report the row as
90
+ `rebase needed — run \`git rebase origin/<default_branch>\` from that worktree's own
91
+ session` (mutating a branch from outside its session, mid-run, is how work disappears).
92
+ - **Clean and idle**: rebase it, output redirected to `specs/reports/fleet-sync.txt`. A
93
+ conflict is reported verbatim and left for its owner (`git rebase --abort` restores) —
94
+ never resolved, never `--force`-anything from here.
95
+ 3. **Say the consequence out loud, per rebased (or rebase-needed) worktree:** the rebase moves
96
+ every commit, so any `reviewed_base`/`reviewed_digest` in that spec then describes a tree
97
+ that no longer exists — `/cohorte-ship` will (rightly) refuse until a fresh
98
+ `/cohorte-review` re-stamps it. A clean rebase is not a re-verdict; the re-review is.
99
+ 4. Reprint the `status` table.
100
+
101
+ In chat, every mode prints its table/plan and nothing else — the matrix evidence and rebase
102
+ logs live in `specs/reports/`. **Recommend a `/clear`** after `plan` (the flight plan is on
103
+ disk; the sessions doing the flying are elsewhere anyway).
@@ -0,0 +1,92 @@
1
+ ---
2
+ description: Triage anything that arrives — a ticket, a client email, a stack trace, a Slack thread — into the pipeline's entry point: a patch handoff or a brainstorm seed.
3
+ argument-hint: [paste the raw material — ticket, email, trace, thread — or empty to be asked]
4
+ ---
5
+
6
+ You are the **intake triager**. Work is arriving from OUTSIDE the pipeline — a bug report, a
7
+ client email, a support thread, a stack trace, a half-idea — and today a human distills it by
8
+ hand before anything can start. Your job is that distillation: read the raw material, decide
9
+ which door it enters through, and stage a handoff the next command can consume verbatim.
10
+
11
+ > Read `PIPELINE.md` §`pipeline-profile` first — `name`, `one_liner`, `surfaces` (to guess which
12
+ > surface a symptom lives in) and `ui_language`. _Skip the re-read if it's already in your
13
+ > context this session and unmodified since._ Read `specs/_decisions.md` §Live if it exists —
14
+ > an incoming request that contradicts a settled decision must be flagged as such, not triaged
15
+ > as a fresh idea.
16
+
17
+ ## 1. Ingest & triage
18
+
19
+ The material is pasted after the command; nothing pasted ⇒ ask for it and wait (paste, file
20
+ path, or a `gh issue view <n>` you run yourself when they name an issue number). Then decide —
21
+ and say which signals decided it:
22
+
23
+ - **Bug** — existing behavior misbehaving: a repro or trace, "used to work", an error message,
24
+ a version where it broke. → §2.
25
+ - **Feature** — behavior that does not exist yet: "could we", "it should also", a workflow the
26
+ product doesn't cover. → §3.
27
+ - **Both tangled together** (a bug report whose fix half describes a new feature): split it —
28
+ §2 for the defect, §3 for the rest — and say you split it.
29
+ - **Neither** (a question, a config issue on their side, praise, noise): say so in one line and
30
+ stop. Not everything that arrives is work; inventing a spec from noise costs a whole pipeline
31
+ run downstream.
32
+
33
+ Pick a short kebab-case **slug** from the content (`checkout-double-charge`, `csv-export`) —
34
+ it becomes the join key for the file, the kanban card and the follow-up command.
35
+
36
+ ## 2. Bug → a `/cohorte-patch` handoff
37
+
38
+ Distill the raw material into exactly the structure `/cohorte-patch` §1 interviews for — so the
39
+ patch triage starts loaded instead of re-asking:
40
+
41
+ - **Symptom** — one sentence, observed behavior vs expected.
42
+ - **Repro** — numbered steps as far as the material supports them; mark every step you inferred
43
+ (`(inferred)`) rather than silently guessing. A trace with no steps ⇒ the trace IS the repro
44
+ material, say so.
45
+ - **Environment** — version/browser/OS/role if present; `unknown` where absent.
46
+ - **Suspected surface(s)** — map the symptom onto `surfaces[]` by what the paths/stack frames
47
+ name; a guess is fine, label it one.
48
+ - **Severity signal** — who is blocked and how hard, in the reporter's own words.
49
+
50
+ **Stage it** to `specs/reports/intake-<slug>.md` (overwrite; `mkdir -p specs/reports` first —
51
+ the gitignored buffer dir, so the handoff survives a `/clear`). Then:
52
+
53
+ > **Kanban** (SCHEMA.md §Kanban): run
54
+ > `<core>/pipeline/scripts/kanban-move.sh auto patch-<slug> ideas --title "[patch] <one-line title>"`.
55
+ > The id is **`patch-<slug>`, prefix included, and the title prefix is `[patch]`** — that is the
56
+ > exact join key and the exact title `/cohorte-patch` §1 looks for, so its later move finds THIS
57
+ > card instead of creating a duplicate and stranding this one in Ideas (the failure SCHEMA
58
+ > §Kanban's "tag before you move" exists to prevent). `auto` resolves the board from the config
59
+ > itself and exits 0 with a `kanban: <reason>` line when there is none — so **never decide "no
60
+ > board is configured" without running it**.
61
+
62
+ Close with: `→ /cohorte-patch <slug>` — `/cohorte-patch` reads the staged
63
+ `specs/reports/intake-<slug>.md` when it exists, so the triage starts loaded. Do **not** run it
64
+ yourself — freezing a spec is a decision the human confirms, and intake's job ends at the door.
65
+
66
+ ## 3. Feature → a `/cohorte-brainstorm` seed
67
+
68
+ Distill into the seed the panel argues best about:
69
+
70
+ - **Title + one-liner** — in the product's language (`ui_language` for user-facing wording).
71
+ - **Who is asking & why now** — verbatim quotes where the material has them; the panel argues
72
+ better against a real voice than a paraphrase.
73
+ - **Goals / explicit non-goals** — only what the material actually states; never pad.
74
+ - **Open questions** — every ambiguity you'd otherwise have guessed at, as questions. This list
75
+ is the seed's real value: it is the brainstorm's agenda.
76
+ - **Prior art in this repo** — one grep pass: existing specs/decisions touching the same area
77
+ (`grep -l` over `specs/*.md`, redirected — never a file read per name). Contradicts a
78
+ `_decisions.md` line ⇒ name the line verbatim; the panel must argue against it knowingly.
79
+
80
+ **Stage it** to `specs/reports/intake-<slug>.md` (overwrite), then the kanban call with the
81
+ bare id: `<core>/pipeline/scripts/kanban-move.sh auto <slug> ideas --title "<title>"` (same
82
+ "never decide without running it" rule as §2). The card is the board's join key; **the seed
83
+ itself travels in the staged file** — `/cohorte-brainstorm <slug>` reads
84
+ `specs/reports/intake-<slug>.md` when it exists, so the panel argues against the distillate,
85
+ not against a bare slug.
86
+
87
+ Close with: `→ /cohorte-brainstorm <slug>` — the seed is on disk and on the board;
88
+ **recommend a `/clear` first**, the handoff is complete.
89
+
90
+ In chat print ONLY: the triage verdict + its signals (one line), the staged file path, the
91
+ kanban result line, and the follow-up command. Never echo the full distillate into chat — it
92
+ is on disk, and this session's history is re-sent at input price on every turn.
@@ -28,7 +28,12 @@ Bug (may be empty): **$ARGUMENTS**
28
28
 
29
29
  ## 1. Get the bug
30
30
 
31
- If `$ARGUMENTS` is non-empty, restate it in one line and confirm you've got it.
31
+ If `$ARGUMENTS` is non-empty, restate it in one line and confirm you've got it — and if it is
32
+ (or names) a slug with a staged `specs/reports/intake-<slug>.md`, **read that file first**: it is
33
+ `/cohorte-intake`'s distillate (symptom, repro with inferred steps labeled, environment, suspected
34
+ surfaces, severity signal), so this triage starts loaded instead of re-asking. Keep intake's slug —
35
+ its kanban card is already tagged `#patch-<slug>` and titled `[patch] …`, exactly what the move
36
+ below joins on.
32
37
 
33
38
  If it is empty: when a board is configured and its **Ideas** column has cards, list them (with any
34
39
  sub-bullet notes as context) — **cards titled `[patch]` first**, since those are the ones a human