cohorte 2.9.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,31 @@ 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
+
10
35
  ## 2.9.0 — 2026-08-22
11
36
 
12
37
  - **`/cohorte-intake` — the door before the doors.** Work has always entered the pipeline
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
@@ -418,7 +410,10 @@ See `profile/SCHEMA.md` for every field in `PIPELINE.md` and how the pipeline us
418
410
 
419
411
  ```
420
412
  package.json # npm package (cohorte) — semver source of truth
421
- 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.
422
417
  install.sh # script installer (fresh + --update) for npm-less setups — still needs Node
423
418
  install.ps1 # same installer for Windows PowerShell (fresh + -Update)
424
419
  core/ # copied verbatim into ~/.claude (global) or <project>/.claude (bundled)
@@ -432,7 +427,4 @@ profile/
432
427
  SCHEMA.md # field reference
433
428
  cohorte.config.template.yaml # seeds ~/.claude/cohorte.config.yaml (kanban)
434
429
  scripts/ # worktree-isolation templates + the shipped preflight/kanban scripts
435
- dashboard/ # local web cockpit (cohorte dashboard) — see dashboard/README.md
436
- server/ # dependency-free node runtime (serves the built app + JSON/stream API)
437
- app/ # Vite + React source (built to dashboard/dist/ at publish time)
438
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
@@ -14,9 +14,8 @@ nothing more.
14
14
  fire a loop per worktree from here — is the 2.2.0 driver's grave: child sessions nobody
15
15
  supervises, stalling on prompts nobody sees. Each feature's build/review/loop runs in **its own
16
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 — not to be confused
18
- with the dashboard's multi-*project* fleet registry; this file is this repo's multi-*feature*
19
- flight plan).
17
+ lives in `specs/reports/fleet.json` (the main checkout's gitignored buffer — this repo's
18
+ multi-*feature* flight plan).
20
19
 
21
20
  > Read `PIPELINE.md` §`pipeline-profile` first: `surfaces` (paths — the overlap analysis keys
22
21
  > on them), `contract`, `vcs.default_branch`, and the `isolation` block. _Skip the re-read if
@@ -98,8 +98,8 @@ watch, rather than letting the job go red.
98
98
  **Move the card to Shipped — required, and verify it actually moved.** Run
99
99
  `<core>/pipeline/scripts/kanban-move.sh auto $ARGUMENTS shipped --pr <num>`, which **appends the PR
100
100
  number** so the line reads `- [ ] <title> #$ARGUMENTS — PR #<num>`. Take `<num>` from the PR URL
101
- (`…/pull/13` ⇒ `13`); **always pass it when a PR was created** (the `gh` path) — it is what the
102
- dashboard turns into a PR link. If only a compare URL was emitted (no PR yet), drop `--pr`.
101
+ (`…/pull/13` ⇒ `13`); **always pass it when a PR was created** (the `gh` path) — it is what a
102
+ board reader turns into a PR link. If only a compare URL was emitted (no PR yet), drop `--pr`.
103
103
 
104
104
  Then **read the script's own output**, which is the verification: `moved #$ARGUMENTS -> Shipped
105
105
  (PR #<num>)` means done, and a `kanban: <reason>` line means the mirror is off and says why. Both are
@@ -23,7 +23,7 @@ and subagents spawned by the Workflow runtime alike. Workflow subagents run in
23
23
  acceptEdits regardless of the session's permission mode (their Write/Edit calls
24
24
  are auto-approved), but acceptEdits does NOT auto-approve Bash or Task, so this
25
25
  gate still sees and can block them. In bypassPermissions (headless `claude -p`,
26
- dashboard actions) there is no human to answer a prompt, so every `ask` match is
26
+ unattended workflow runs) there is no human to answer a prompt, so every `ask` match is
27
27
  escalated to a hard deny with the reason attached — a clear refusal beats a
28
28
  prompt that can never be answered.
29
29
 
@@ -395,7 +395,7 @@ const staging = await agent(
395
395
  : 'the blocking list is empty, so <FP> is the empty string "":\n') +
396
396
  `${verdictJson}\n` +
397
397
  // Per-surface verdicts (not the merged one stamped on every row — one BLOCK used to
398
- // mark ALL surfaces failed on the dashboard), and dead reviewers logged as "dead"
398
+ // mark ALL surfaces failed), and dead reviewers logged as "dead"
399
399
  // per SCHEMA.md §Dead agents — an incomplete batch is the batch worth recording.
400
400
  `3. Append one line to $(dirname "$(git rev-parse --git-common-dir)")/.claude/pipeline-metrics.jsonl: ` +
401
401
  `{"ts":"<ISO now>","feature":"${feature}","phase":"review","seconds":0,"tokens":${Math.max(0, spentNow() - spentStart)},"surfaces":{${
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
- // Programmatic port of the /cohorte-doctor checks (core/commands/doctor.md), for the dashboard.
2
+ // Programmatic port of the /cohorte-doctor checks (core/commands/cohorte-doctor.md), so
3
+ // `cohorte doctor` can report without a coding agent in the loop.
3
4
  // Read-only: inspects files only. Checks that need a live process (MCP connectivity,
4
5
  // git worktree state, DesignSync) are reported as `skip` with a note — the node server
5
6
  // can't run them, and honest "not checked here" beats a false green.
@@ -247,7 +248,7 @@ function gateRegs(settingsPath, event) {
247
248
 
248
249
  function checkHooks(projectRoot, globalDir, installMode, all) {
249
250
  // A registration can exist without a discoverable core — a repo whose core lives in a global
250
- // dir the dashboard was not pointed at, or one predating `runtimes.json`. Falling through to
251
+ // dir the caller was not pointed at, or one predating `runtimes.json`. Falling through to
251
252
  // "no runtime installed" there would hide a real double-registration, so assume the Claude
252
253
  // layout: before the adapter it was the only one, and it is the only one whose hook can be
253
254
  // registered outside its own core dir.
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
  // Which coding agent(s) this project is wired for, and where each one keeps its files.
3
3
  //
4
- // Before 2.2.0 every path in the dashboard was `.claude/…`. That is now one layout of five:
4
+ // Before 2.2.0 every path here was `.claude/…`. That is now one layout of five:
5
5
  // a repo driven from Cursor keeps its core in `.cohorte/cursor/`, its agents in
6
6
  // `.cursor/agents/` and its gate registration in `.cursor/hooks.json`. Checking the Claude
7
7
  // paths there reports a perfectly healthy install as broken — "no core", "no rendered agent",
@@ -30,12 +30,12 @@ function pointerAt(projectRoot) {
30
30
 
31
31
  // Latest published version — registry fetch first, `npm view` as a fallback (it uses the
32
32
  // user's configured registry/proxy, which works where a raw fetch may be blocked). Cached
33
- // briefly so the dashboard's poll doesn't hammer the network. null only if both fail.
33
+ // briefly so repeated calls don't hammer the network. null only if both fail.
34
34
  let _cache = { value: null, at: 0 };
35
35
  const CACHE_MS = 5 * 60 * 1000;
36
36
  // Failures are cached too, briefly. Without this an offline machine paid the FULL
37
37
  // 5s fetch timeout + 8s `npm view` timeout on every call — and /api/fleet calls
38
- // this once per tracked project, so the dashboard's 15s poll never finished.
38
+ // this once per tracked project, so a batch of probes never finished.
39
39
  const FAIL_CACHE_MS = 60 * 1000;
40
40
 
41
41
  async function fetchRegistry(timeoutMs = 5000) {
package/package.json CHANGED
@@ -1,26 +1,21 @@
1
1
  {
2
2
  "name": "cohorte",
3
- "version": "2.9.0",
3
+ "version": "2.10.0",
4
4
  "description": "Portable, stack-agnostic multi-agent development pipeline for Claude Code, Codex CLI, Cursor, Gemini CLI and OpenCode — install the core, run /cohorte-init-pipeline, and it adapts to your project's stack.",
5
5
  "bin": {
6
6
  "cohorte": "bin/cli.js"
7
7
  },
8
- "scripts": {
9
- "build:dashboard": "npm --prefix dashboard/app ci && npm --prefix dashboard/app run build",
10
- "prepack": "npm run build:dashboard"
11
- },
12
8
  "files": [
13
9
  "bin",
10
+ "lib",
14
11
  "core",
15
12
  "profile",
16
13
  "scripts",
17
14
  "!scripts/new-feature.sh",
18
15
  "!scripts/remove-feature.sh",
16
+ "!scripts/demo",
19
17
  "!core/hooks/__pycache__",
20
18
  "!**/*.pyc",
21
- "dashboard/server",
22
- "dashboard/dist",
23
- "dashboard/README.md",
24
19
  "install.sh",
25
20
  "install.ps1",
26
21
  "CHANGELOG.md"
package/profile/SCHEMA.md CHANGED
@@ -176,7 +176,7 @@ the **main checkout's** `<state>/pipeline-metrics.jsonl` (gitignored) — one JS
176
176
  and `/cohorte-fix`.
177
177
  **`surfaces` keys are surface keys, nothing else** — run-level facts go in their own top-level
178
178
  fields. Anything put inside `surfaces` is read
179
- as a surface: the dashboard renders it as a row in the per-surface table and scores a non-`ok`
179
+ as a surface: `cohorte doctor` renders it as a row in the per-surface table and scores a non-`ok`
180
180
  value as that surface failing. Always the main checkout, never the feature worktree (which dies at teardown while
181
181
  metrics must accumulate across features) — resolve from anywhere with
182
182
  `$(dirname "$(git rev-parse --git-common-dir)")/<state>/pipeline-metrics.jsonl`. Read it before
@@ -189,7 +189,7 @@ what's SLOW. Tokens are recorded only where they can be read honestly: the **wor
189
189
  (`loop.js`, `review.js`) stamp an approximate `tokens` field per batch from the runtime's own
190
190
  counter (`budget.spent()` deltas), and the loop's return carries a per-round breakdown in its
191
191
  `history`. The **conversational** commands still record none — a lead cannot reliably read a
192
- subagent's token count, and a guessed number is worse than a missing one. The dashboard sums
192
+ subagent's token count, and a guessed number is worse than a missing one. `cohorte metrics` sums
193
193
  whatever is stamped (a token-less line aggregates as 0, rendered as absent, never as "free").
194
194
  For exact spend, use Claude Code's own accounting:
195
195
 
@@ -231,7 +231,7 @@ storing a bare `pnpm test` as the thing agents execute; `/cohorte-update-pipelin
231
231
  ## Spec status — the lifecycle state machine
232
232
 
233
233
  A spec's front-matter `status` is not a label, it is the pipeline's **state**: every command routes on
234
- it, the dashboard boards on it, and the kanban backfill maps it to a column. Six states:
234
+ it, `cohorte specs` boards on it, and the kanban backfill maps it to a column. Six states:
235
235
 
236
236
  | status | meaning | written by | who may build it |
237
237
  | --- | --- | --- | --- |
@@ -371,7 +371,7 @@ project has *decided*. Without somewhere for those, every `/cohorte-spec` re-dis
371
371
  - **Never read by implementers or reviewers.** They work from the frozen contract, which already tells
372
372
  them what to do; shipping them the rationale would cost `surfaces × dispatches` tokens per feature
373
373
  for a fact they cannot act on. This is what keeps the journal cheap enough to be worth having.
374
- - The `_` prefix is load-bearing: `/cohorte-doctor`, the dashboard spec scanner and the kanban backfill all skip
374
+ - The `_` prefix is load-bearing: `/cohorte-doctor`, the `cohorte specs` scanner and the kanban backfill all skip
375
375
  `specs/_*.md`, so the journal is never mistaken for a spec (no phantom card, no bogus stage).
376
376
 
377
377
  ## Preflight — the deterministic phase gate
@@ -626,7 +626,7 @@ notes a human writes as sub-bullets under an Ideas card are seed context for `/c
626
626
  the trailing `%% kanban:settings … %%` block or the `kanban-plugin: board` front-matter.
627
627
 
628
628
  Once shipped, `/cohorte-ship` appends the **PR number** to the card — `- [ ] <title> #<feature_id> — PR #<num>`.
629
- The bare `#<num>` is what the dashboard renders as a clickable link to the GitHub PR, so `/cohorte-ship` always
629
+ The bare `#<num>` is what a board reader renders as a clickable link to the GitHub PR, so `/cohorte-ship` always
630
630
  writes it when a PR was actually created.
631
631
 
632
632
  **Move a card (the core op).** One call — the script does resolution AND the move outside the