cohorte 2.5.0 → 2.7.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,74 @@ 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.7.0 — 2026-08-22
11
+
12
+ - **The metrics panel billed Sonnet 5 fifty percent over.** `prices.json` carried $3/$15 because
13
+ that was the rate scheduled to take effect on 2026-09-01. It never will: Anthropic made the
14
+ $2/$10 introductory rate the standard one. Every run costed since the entry was written reads
15
+ high, and the further back a run is, the more confidently wrong the number looks.
16
+
17
+ Sonnet 5 is now $2/$10, and the file says in prose why the increase must not be reinstated —
18
+ the next person to "correct" this back will at least have to argue with a comment first. Sonnet
19
+ 4.6 and earlier stay at $3/$15: same tier, different price, and the longest-prefix lookup keeps
20
+ them apart on its own.
21
+
22
+ - **A concurrency ceiling that moved while the comments stood still.** `audit.js` described the
23
+ runtime as capping concurrent agents at "~16". It is 20, it is named
24
+ (`CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS`), and raising it raises the ceiling without changing this
25
+ script — the queue belongs to the runtime, and a queued domain was never a lost one. Comments
26
+ only; no dispatch logic moved.
27
+
28
+ - **The design brief was the one step the pipeline handed back to you.** `/cohorte-spec` freezes a
29
+ brief to `specs/design/<feature_id>.md` and then said, in effect, paste this somewhere else. With
30
+ `design.inline: true` it can instead hand that file to `/design`, which reads the codebase,
31
+ matches the existing UI style, and returns editable artboards without leaving the session.
32
+
33
+ The flag is off by default and degrades rather than fails: an unmet floor falls back to the
34
+ paste-it-yourself path with a note, never an error. It needs `provider: claude-design`, the new
35
+ `inline_design` runtime capability (Claude Code only), and a CLI ≥ 2.1.234 — a **higher floor than
36
+ workflows' 2.1.154**, which is why `/cohorte-doctor` grew a separate check `8b` instead of raising
37
+ the existing one. Raising the shared floor would have made every install between the two versions
38
+ read as broken while its workflows ran fine.
39
+
40
+ Understand what inline does and does not change: it changes who does the pasting. The brief is
41
+ still written to disk first and is still what `/cohorte-build` reads — artboards are an aid to the
42
+ human, not an input to the pipeline. And `/design` is a research preview that **does not save them
43
+ for you**, so check 8b says so on every run, including the runs where everything passes. An
44
+ artboard nobody exported dies with the session, and there is no way to notice afterwards.
45
+
46
+ - This release adds a runtime capability, a profile flag, and a command section: run
47
+ `/cohorte-update-pipeline` after updating.
48
+
49
+ ## 2.6.0 — 2026-08-14
50
+
51
+ - **Two ways to type the same command, and one of them silently doesn't work.** The docs wrote
52
+ `npx cohorte install`, the Francois extension's manifest names a bare `cohorte` binary, and
53
+ nothing said why the two could not be the same string. They cannot: `npx` fetches a package
54
+ into a cache and runs it once, leaving nothing on `PATH`, and a Francois panel may only spawn
55
+ a **bare binary name resolved on `PATH`**. So the extension's requirement read like a
56
+ preference the rest of the documentation contradicted.
57
+
58
+ Every command is now written one way — `cohorte <verb>`, after `npm i -g cohorte` — across the
59
+ README, the docs, both shell installers, the CLI's own banners, the `/cohorte-doctor` fix
60
+ lines, and the `install` string `/cohorte-init-pipeline` writes into a repo's committed
61
+ `pipeline.json`. `npx cohorte@latest <verb>` still works and is still the right call for a
62
+ one-off on a machine you don't own; it is named once, as the escape hatch it is.
63
+
64
+ - **The cost of pinning, removed before it could bite.** `install` and `update` lay down the core
65
+ carried by the CLI that runs them. `npx cohorte@latest` made that self-correcting; a global
66
+ install does not, so `cohorte update` on a CLI left at an old version would re-lay an old core
67
+ and report success — an update that updates nothing.
68
+
69
+ Both verbs now compare themselves against the registry when they finish and print exactly what
70
+ to run (`npm i -g cohorte@latest`) when they are behind. It is never fatal, never blocks a
71
+ successful install, costs a 2.5s fetch with no `npm view` fallback, and stays silent on any
72
+ failure — an offline install pays 2.5 seconds and says nothing. `COHORTE_NO_VERSION_CHECK=1`
73
+ opts out; `CI` opts out already.
74
+
75
+ Nothing in the pipeline changed: no new agent, no gate, no template — this release needs no
76
+ `/cohorte-update-pipeline`.
77
+
10
78
  ## 2.5.0 — 2026-08-14
11
79
 
12
80
  - **Everything the pipeline knew about your repo, it would only say to a coding agent.**
package/README.md CHANGED
@@ -46,9 +46,9 @@ The doctrine is one set of source prompts. The installer renders them into whate
46
46
  actually reads, and branches the instructions on what it can actually do:
47
47
 
48
48
  ```sh
49
- npx cohorte install --runtime=codex,cursor # pick explicitly
50
- npx cohorte install --all-runtimes # every supported one
51
- npx cohorte install # detects what you have and asks
49
+ cohorte install --runtime=codex,cursor # pick explicitly
50
+ cohorte install --all-runtimes # every supported one
51
+ cohorte install # detects what you have and asks
52
52
  ```
53
53
 
54
54
  | | Commands | Subagents | Gate | Workflows |
@@ -82,10 +82,10 @@ install is unchanged.
82
82
 
83
83
  Only one hard requirement — the rest is optional and independent:
84
84
 
85
- - **Node ≥ 18 + npm** — _required_, for the `npx` installer that lays down the core. Nothing else needs it.
85
+ - **Node ≥ 18 + npm** — _required_, for the `cohorte` CLI that lays down the core. Nothing else needs it.
86
86
  - **[`uv`](https://docs.astral.sh/uv/) + the Serena CLI** — _optional_, the default code-retrieval
87
87
  provider. Install it separately (`uv tool install -p 3.13 serena-agent && uv tool update-shell`); the
88
- `npx` install neither needs nor touches it, so the order between the two is irrelevant. Without Serena
88
+ cohorte install neither needs nor touches it, so the order between the two is irrelevant. Without Serena
89
89
  the pipeline still runs — agents just fall back to Grep/Read. Having it installed **before**
90
90
  `/cohorte-init-pipeline` lets init wire it in one pass (otherwise `/cohorte-update-pipeline` wires it later).
91
91
  - **On a new machine cloning a repo that's already pipeline-ised:** the Serena registration is committed
@@ -94,15 +94,25 @@ Only one hard requirement — the rest is optional and independent:
94
94
 
95
95
  ## Install
96
96
 
97
- The pipeline ships as an npm package (`cohorte`), so releases are semver-tagged and
98
- `npx` always fetches the latest published version — no clone needed, works on macOS/Linux/Windows.
97
+ The pipeline ships as an npm package (`cohorte`), so releases are semver-tagged — no clone
98
+ needed, works on macOS/Linux/Windows. **Install it once, globally**; every command below is
99
+ written as `cohorte …`, and that single form is what the rest of this README, the docs, and the
100
+ [Francois extension](https://github.com/TheBidouilleAgency/francois-plugin-cohorte) all assume:
101
+
102
+ ```sh
103
+ npm i -g cohorte
104
+ ```
105
+
106
+ `npx cohorte <verb>` runs any of them without installing anything, for a one-off on a machine
107
+ you don't own. It is the escape hatch, not the path — see
108
+ [why one form](https://thebidouilleagency.github.io/cohorte/reference/installers).
99
109
 
100
110
  **Global (recommended)** — install the generic core ONCE into `~/.claude`; it serves every repo on
101
111
  your machine. Nothing is copied per project; the gate hook is registered once and reads each repo's
102
112
  own `gate-config.json`:
103
113
 
104
114
  ```sh
105
- npx cohorte install --global
115
+ cohorte install --global
106
116
  ```
107
117
 
108
118
  The per-project part is NOT the core — it's the **profile** `/cohorte-init-pipeline` generates and you
@@ -116,7 +126,7 @@ repo; each teammate just runs the same global one-liner once, guided by the comm
116
126
 
117
127
  ```sh
118
128
  # inside your project (or pass its path as an argument)
119
- npx cohorte install
129
+ cohorte install
120
130
  ```
121
131
 
122
132
  Copies the core into `<project>/.claude`, committed with the repo. Choose this when you want
@@ -178,8 +188,9 @@ Sanity-check `PIPELINE.md`, commit it, and run `/cohorte-brainstorm`.
178
188
  ## Update
179
189
 
180
190
  ```sh
181
- npx cohorte@latest update --global # the shared core in ~/.claude (recommended setup)
182
- npx cohorte@latest update # a repo's bundled core in <project>/.claude
191
+ npm i -g cohorte@latest # refresh the CLI — the core it lays down is its own
192
+ cohorte update --global # the shared core in ~/.claude (recommended setup)
193
+ cohorte update # a repo's bundled core in <project>/.claude
183
194
  ```
184
195
 
185
196
  (Script equivalents: `sh install.sh --update [--global]` / `.\install.ps1 -Update [-Global]`.)
@@ -200,9 +211,9 @@ maintenance command you ever run (`/cohorte-build` auto-grows surfaces as specs
200
211
  A browser view of pipeline state, for when a checklist beats scanning files:
201
212
 
202
213
  ```sh
203
- npx cohorte dashboard # serves http://localhost:4317 (Ctrl-C to stop)
204
- npx cohorte dashboard <path> # start focused on another project
205
- npx cohorte dashboard --port=4400 --open # custom port, open the browser
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
206
217
  ```
207
218
 
208
219
  **Bound to `127.0.0.1` by default** — the dashboard's actions execute code (install/update/reset,
@@ -239,11 +250,14 @@ are reimplemented in JS, so the dashboard needs no Claude session to compute sta
239
250
  The two read-only halves of the pipeline, in the shell:
240
251
 
241
252
  ```sh
242
- npx cohorte specs # the board: id · status · branch · title, from specs/*.md
243
- npx cohorte doctor # the /cohorte-doctor checks — exits 1 when any check is bad
244
- npx cohorte metrics --days=30 # cost + runtime per command, from Claude Code's transcripts
253
+ cohorte specs # the board: id · status · branch · title, from specs/*.md
254
+ cohorte doctor # the /cohorte-doctor checks — exits 1 when any check is bad
255
+ cohorte metrics --days=30 # cost + runtime per command, from Claude Code's transcripts
245
256
  ```
246
257
 
258
+ `doctor`'s exit code is what makes it a CI step, and the three of them are what the Francois
259
+ extension below renders.
260
+
247
261
  `doctor`'s exit code makes it a CI step as-is. Add `--porcelain` for one record per line with
248
262
  `U+001F` between fields (a spec title with a space in it never misaligns a column), or `--json`
249
263
  for the native document. Both reuse the dashboard's own readers, so the board and the CLI can
@@ -256,9 +270,13 @@ Francois-aware surface in the package, and it exists for
256
270
  manifest-only extension that renders the spec board, the doctor report and the 30-day cost as
257
271
  three panels beside your sessions.
258
272
 
273
+ A Francois extension may only spawn a **bare binary name resolved on `PATH`** — never `npx`,
274
+ never a shell, never an absolute path. So this one needs the global install, and nothing else
275
+ will do:
276
+
259
277
  ```sh
260
- npm i -g cohorte # a Francois extension may only spawn a bare
261
- francois ext install TheBidouilleAgency/cohorte # binary on PATH — never npx, never a shell
278
+ npm i -g cohorte
279
+ francois ext install TheBidouilleAgency/cohorte
262
280
  ```
263
281
 
264
282
  ## Releasing (maintainers)
@@ -273,8 +291,10 @@ a version bump just run the sanity checks.
273
291
  `npm version patch --no-git-tag-version`), commit, push — CI does the rest (publish + tag +
274
292
  release). No local tagging needed.
275
293
 
276
- `npx cohorte@latest …` then serves the new version everywhere; installed cores record
294
+ `npm i -g cohorte@latest` then serves the new version everywhere; installed cores record
277
295
  it in `.claude/pipeline/VERSION` and bundled repos in their committed `pipeline.json` pointer.
296
+ `install` and `update` compare themselves against the registry and say so when they are behind,
297
+ so a CLI pinned at an old version cannot quietly re-lay an old core.
278
298
 
279
299
  ## The commands
280
300
 
@@ -407,7 +427,7 @@ profile/
407
427
  SCHEMA.md # field reference
408
428
  cohorte.config.template.yaml # seeds ~/.claude/cohorte.config.yaml (kanban)
409
429
  scripts/ # worktree-isolation templates + the shipped preflight/kanban scripts
410
- dashboard/ # local web cockpit (npx dashboard) — see dashboard/README.md
430
+ dashboard/ # local web cockpit (cohorte dashboard) — see dashboard/README.md
411
431
  server/ # dependency-free node runtime (serves the built app + JSON/stream API)
412
432
  app/ # Vite + React source (built to dashboard/dist/ at publish time)
413
433
  ```
package/bin/cli.js CHANGED
@@ -2,11 +2,16 @@
2
2
  // cohorte — installer CLI for the portable multi-agent pipeline.
3
3
  // Cross-platform, dependency-free port of install.sh / install.ps1.
4
4
  //
5
- // npx cohorte install # bundle the core into <cwd>/.claude (committable)
6
- // npx cohorte install [target] # same, into another project
7
- // npx cohorte install --global # one shared core in ~/.claude
8
- // npx cohorte update [--global] # refresh the core, keep every generated file
9
- // npx cohorte version
5
+ // npm i -g cohorte # once the CLI, and the `cohorte` binary on PATH
6
+ // cohorte install # bundle the core into <cwd>/.claude (committable)
7
+ // cohorte install [target] # same, into another project
8
+ // cohorte install --global # one shared core in ~/.claude
9
+ // cohorte update [--global] # refresh the core, keep every generated file
10
+ // cohorte version
11
+ //
12
+ // `npx cohorte <verb>` runs any of these without installing anything — it is the
13
+ // escape hatch, not the documented path: a Francois extension panel can only spawn
14
+ // a bare binary on PATH, and a globally installed CLI is what puts one there.
10
15
 
11
16
  'use strict';
12
17
 
@@ -291,7 +296,7 @@ function copyCore() {
291
296
  resolveTemplateConditionals(pipelineDir);
292
297
  // Copy the *.template files AND the shipped executables (kanban-move.sh,
293
298
  // preflight.sh). Until 1.2.4 this loop took only `.template`, so every
294
- // `npx cohorte install/update` produced a core missing both scripts — and since
299
+ // `cohorte install/update` produced a core missing both scripts — and since
295
300
  // every caller chains them with `|| true`, the result was silent: no kanban card
296
301
  // moves, no error. The shell installers named them explicitly
297
302
  // and this port drifted. The rule below needs no list to keep in sync: a `<x>.sh`
@@ -386,7 +391,7 @@ function renderSurfaces() {
386
391
 
387
392
  // Every agent in core/agents/ EXCEPT the *.template.md ones, which /cohorte-init-pipeline renders
388
393
  // per-surface. Until 1.2.6 this was a hardcoded ['review.md', 'release.md'] that never grew
389
- // the agents the shell installers copy, so `npx cohorte install` shipped a command with no
394
+ // the agents the shell installers copy, so `cohorte install` shipped a command with no
390
395
  // agent to dispatch — the run reported the command as not installed.
391
396
  // Reading the directory needs no list to keep in sync with the shell installers.
392
397
  const agentDir = path.join(src, 'core', 'agents');
@@ -615,7 +620,7 @@ function registerGlobalHook() {
615
620
  const file = path.join(dest, 'hooks', 'gate.py');
616
621
  const base = path.basename(file);
617
622
  // Trailing-quote tolerant: the Windows form is `py "C:\...\gate.py"`, and a
618
- // bare .endsWith() missed it — which is how repeat `npx cohorte install`
623
+ // bare .endsWith() missed it — which is how repeat `cohorte install`
619
624
  // runs accumulated a duplicate registration every time (gate.py then ran
620
625
  // once per copy on every Bash call).
621
626
  const isGate = entry => (entry.hooks || []).some(
@@ -657,7 +662,7 @@ function bumpPointerVersion(ptr) {
657
662
  // configured on this machine, and — on a TTY — let the human confirm, because installing
658
663
  // into a runtime they don't use litters a config dir they never asked us to touch. With no
659
664
  // TTY and no flag we install for Claude Code alone: the behaviour of every version before
660
- // the adapter, so a scripted `npx cohorte install` keeps doing exactly what it did.
665
+ // the adapter, so a scripted `cohorte install` keeps doing exactly what it did.
661
666
  async function selectRuntimes() {
662
667
  if (wantRuntimes.length) return wantRuntimes;
663
668
  const found = adapter.detectRuntimes();
@@ -675,6 +680,33 @@ async function selectRuntimes() {
675
680
  return picked.length ? [...new Set(picked)] : found;
676
681
  }
677
682
 
683
+ // --- staleness notice --------------------------------------------------------
684
+ // `install` and `update` write a core taken from THIS package, so the core is only
685
+ // as fresh as the CLI that ran. `npx cohorte@latest` made that self-correcting; a
686
+ // global install does not — `cohorte update` on a pinned 2.5.0 would re-lay the
687
+ // 2.5.0 core forever and report success, which is an update that updates nothing.
688
+ // So the CLI checks its own version against the registry and says what to run.
689
+ // Never fatal, never blocking a successful install: a short fetch, no `npm view`
690
+ // fallback, and silence on any failure. COHORTE_NO_VERSION_CHECK / CI opt out.
691
+ async function staleVersionNotice() {
692
+ if (process.env.COHORTE_NO_VERSION_CHECK || process.env.CI) return;
693
+ let latest = null;
694
+ try {
695
+ const v = require('../dashboard/server/versions.js');
696
+ latest = await v.latestNpm({ timeoutMs: 2500, fallback: false });
697
+ if (!latest || v.cmpSemver(VERSION, latest) >= 0) return;
698
+ } catch { return; }
699
+ // How you got here decides the fix: under npx the package is transient (and may
700
+ // have come from the cache rather than the registry), so the answer is to pin
701
+ // @latest; with a global install the answer is to upgrade the global.
702
+ const viaNpx = /[\\/]_npx[\\/]/.test(pkgRoot);
703
+ console.log(`
704
+ ! You ran cohorte ${VERSION}, but ${latest} is published — the core just written is ${VERSION}.
705
+ ${viaNpx
706
+ ? 'Re-run as npx cohorte@latest <same command> to lay down the current core.'
707
+ : 'Upgrade with npm i -g cohorte@latest and re-run the same command.'}`);
708
+ }
709
+
678
710
  // --- run ---------------------------------------------------------------------
679
711
  (async () => {
680
712
  const selected = await selectRuntimes();
@@ -693,6 +725,8 @@ if (selected.length > 1) {
693
725
  console.log(' The doctrine is identical; what differs is enforcement — run /cohorte-doctor in');
694
726
  console.log(' each one to see what it can and cannot guarantee.');
695
727
  }
728
+ // Last, so a stale CLI is the final thing on screen rather than scrolled past.
729
+ await staleVersionNotice();
696
730
  })();
697
731
 
698
732
  async function installOne() {
@@ -738,7 +772,7 @@ if (scope === 'global') {
738
772
  copyCore();
739
773
  // Register on UPDATE too — install.sh and install.ps1 always have, and this
740
774
  // port skipping it is why a duplicated or stale-matcher registration could
741
- // never be repaired by `npx cohorte update`: the only route that rewrites it
775
+ // never be repaired by `cohorte update`: the only route that rewrites it
742
776
  // was a full re-install, which is not what anyone runs to get a fix. Safe to
743
777
  // run every time — registration reconciles only gate.py entries and leaves
744
778
  // every other hook and settings key untouched.
@@ -758,7 +792,7 @@ Per repo:
758
792
  teammates know to install the global core (${REPO_URL}).
759
793
  3. Commit PIPELINE.md + .claude/, then /cohorte-brainstorm to start a feature.
760
794
 
761
- Update later with: npx cohorte@latest update --global
795
+ Update later with: npm i -g cohorte@latest && cohorte update --global
762
796
 
763
797
  Global kanban config, user-scoped — optional:
764
798
  · One consolidated file: ${path.join(globalDir, 'cohorte.config.yaml')}
@@ -783,7 +817,7 @@ Next:
783
817
  PIPELINE.md + renders one implementer agent per surface.
784
818
  3. Commit PIPELINE.md, then /cohorte-brainstorm to start a feature.
785
819
 
786
- Update later with: npx cohorte@latest update
820
+ Update later with: npm i -g cohorte@latest && cohorte update
787
821
  Prefer one shared core across all your repos? Re-run with --global.`);
788
822
  } else {
789
823
  console.log(`→ updating pipeline core in ${dest} (keeping your PIPELINE.md + rendered agents)`);
@@ -110,7 +110,10 @@ fix only with the human's go-ahead (or hand them the command).
110
110
  stay the default, so failures here are ⚠️ at most, never ❌). Report which path this machine will
111
111
  take and why:
112
112
  - **Claude Code version** ≥ 2.1.154 (`claude --version 2>/dev/null | head -1`) — older or no CLI
113
- on PATH ⇒ conversational only.
113
+ on PATH ⇒ conversational only. This is the **workflow** floor and the only one that gates this
114
+ check; do NOT raise it to match a newer feature's floor, or every install between the two
115
+ versions reads as broken while its workflows run fine. When `design.inline` is on, report the
116
+ design floor (≥ 2.1.234) as its own line under check 8b — separate prerequisite, separate verdict.
114
117
  - **Scripts present:** `<core>/workflows/review.js` + `audit.js` + `refactor.js` —
115
118
  missing on a current core ⇒ half-done install, re-run install/update.
116
119
  - **Phase-0 agent present:** `<agents>/profile-reader.md` — the workflows abort without it.
@@ -121,6 +124,24 @@ fix only with the human's go-ahead (or hand them the command).
121
124
  End the check with ONE summary line, e.g.
122
125
  `workflows: available (opt-in — ask to "run the review workflow")` or
123
126
  `workflows: unavailable (<first failing prerequisite>) — conversational commands (the default)`.
127
+ <!-- cohorte:if inline_design -->
128
+ 8b. **Inline design** (`design.inline: true` — the `/design` artboard step between spec and build).
129
+ A research preview, so every failure here is ⚠️, never ❌: the design brief still exists on disk
130
+ at `specs/design/<feature_id>.md` and can be carried to the design tool by hand, which is what
131
+ every install did before this flag. Report:
132
+ - **Claude Code version** ≥ 2.1.234 — `/design` ships as a skill and is simply absent below it.
133
+ Older CLI ⇒ say so and name `npm i -g cohorte@latest`'s sibling, `claude update`.
134
+ - **The `/design` skill resolves in this session** — absent ⇒ the preview is off for this account
135
+ or plan (Pro/Max/Team/Enterprise are the eligible ones), not a cohorte defect.
136
+ - **`design.enabled` is true and `provider` is `claude-design`** — `inline` on top of a `figma` or
137
+ `none` provider is a profile contradiction; name it and point at `/cohorte-update-pipeline`.
138
+ - **Artboards are not persisted for you.** State it every run, unconditionally, even when all
139
+ three checks pass: the preview hands designs to the build step but does not save them, so an
140
+ artboard nobody exported dies with the session. This is the single thing most likely to lose
141
+ work, and it is not detectable after the fact.
142
+ One summary line: `inline design: available (preview — export artboards yourself)` or
143
+ `inline design: unavailable (<first failing prerequisite>) — design brief on disk, carry it over by hand`.
144
+ <!-- cohorte:endif -->
124
145
  <!-- cohorte:else -->
125
146
  8. **Preflight wiring.** `<core>/pipeline/scripts/preflight.sh` is executable and
126
147
  `gate-config.json` carries the `preflight` block — mismatch ⇒ regenerate from the profile.
@@ -35,11 +35,15 @@ the human's choices — so `/cohorte-init-pipeline` never needs re-running for a
35
35
  - Otherwise use the published npm package (preferred — installs the latest tagged release):
36
36
 
37
37
  ```sh
38
- npx cohorte@latest update --global # global core
39
- npx cohorte@latest update # bundled core of the current repo
38
+ npm i -g cohorte@latest # the CLI itself, refreshed
39
+ cohorte update --global # global core
40
+ cohorte update # bundled core of the current repo
40
41
  ```
41
42
 
42
- - If npm/npx is unavailable, fall back to piping the installer from the repo's latest `main`:
43
+ If `cohorte` is not on PATH, `npx cohorte@latest update [--global]` runs the same thing
44
+ without installing anything.
45
+
46
+ - If npm is unavailable, fall back to piping the installer from the repo's latest `main`:
43
47
 
44
48
  ```sh
45
49
  curl -fsSL https://raw.githubusercontent.com/TheBidouilleAgency/cohorte/main/install.sh | sh -s -- --update --global
@@ -58,6 +58,7 @@
58
58
  "subagents": true,
59
59
  "hooks": true,
60
60
  "workflows": true,
61
+ "inline_design": true,
61
62
  "mcp": true,
62
63
  "tool_restriction": true
63
64
  },
@@ -74,6 +74,7 @@
74
74
  "subagents": true,
75
75
  "hooks": true,
76
76
  "workflows": false,
77
+ "inline_design": false,
77
78
  "mcp": true,
78
79
  "tool_restriction": true
79
80
  },
@@ -67,6 +67,7 @@
67
67
  "subagents": true,
68
68
  "hooks": true,
69
69
  "workflows": false,
70
+ "inline_design": false,
70
71
  "mcp": true,
71
72
  "tool_restriction": true
72
73
  },
@@ -67,6 +67,7 @@
67
67
  "subagents": true,
68
68
  "hooks": true,
69
69
  "workflows": false,
70
+ "inline_design": false,
70
71
  "mcp": true,
71
72
  "tool_restriction": false
72
73
  },
@@ -63,6 +63,7 @@
63
63
  "subagents": true,
64
64
  "hooks": false,
65
65
  "workflows": false,
66
+ "inline_design": false,
66
67
  "mcp": true,
67
68
  "tool_restriction": false
68
69
  },
@@ -1,8 +1,17 @@
1
1
  # DESIGN BRIEF — <feature title> (`<feature_id>`)
2
2
 
3
- > The "spec return". Paste into the design tool (see `PIPELINE.md` §design). This is §8 of the frozen
4
- > spec, standalone — `/cohorte-spec` writes it to `specs/design/<feature_id>.md` on freeze. Omit entirely if
5
- > the project has no UI.
3
+ > The "spec return". This is §8 of the frozen spec, standalone `/cohorte-spec` writes it to
4
+ > `specs/design/<feature_id>.md` on freeze. Omit entirely if the project has no UI.
5
+ >
6
+ > **Getting it into the design tool.** By default: paste it there yourself (see `PIPELINE.md` §design).
7
+ > With `design.inline: true`, `/cohorte-spec` instead offers to hand this file straight to `/design`,
8
+ > which reads the codebase, matches the existing UI style, and returns editable artboards in-session.
9
+ >
10
+ > Inline changes who does the pasting, and nothing else. This file is still written to disk first and
11
+ > is still what `/cohorte-build` reads — the artboards are an aid to the human, not an input to the
12
+ > pipeline, and `/design` is a research preview that does not save them for you. Export anything worth
13
+ > keeping before the session ends. If the design floor is unmet, the flag degrades to the paste-it-
14
+ > yourself path with a note, never to an error: a brief that exists is worth more than a step that ran.
6
15
 
7
16
  **Goal:** <one line — what the user accomplishes>
8
17
 
@@ -83,8 +83,8 @@
83
83
  9. **Write the pointer** `<state>/pipeline.json` (committed — this is how a teammate who clones the repo
84
84
  knows which core to install):
85
85
  `{ "pipeline": "cohorte", "mode": "<bundled|global>", "core_version": "<contents of the
86
- installer's pipeline/VERSION>", "install": "<per mode: bundled ⇒ \"npx cohorte install\"
87
- note that the core is committed under .claude/; global ⇒ \"npx cohorte install --global\"
86
+ installer's pipeline/VERSION>", "install": "<per mode: bundled ⇒ \"npm i -g cohorte && cohorte install\"
87
+ note that the core is committed under .claude/; global ⇒ \"npm i -g cohorte && cohorte install --global\"
88
88
  (or, without npm: curl -fsSL https://raw.githubusercontent.com/TheBidouilleAgency/cohorte/main/install.sh | sh -s -- --global;
89
89
  Windows: install.ps1 -Global from the same repo)> " }`.
90
90
  In **global** mode also add, near the top of `<memory>`, a one-liner:
@@ -6,7 +6,8 @@
6
6
  // Shape (SCHEMA.md §Workflows): profile via profile-reader (phase 0), the
7
7
  // mechanical gates staged to disk by one haiku agent, then ONE auditor per
8
8
  // domain (each surface + `shared`) running concurrently — the runtime caps
9
- // concurrency at ~16, extra domains queue — and a merge phase that writes the
9
+ // concurrency at 20 (CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS since 2026-08), extra
10
+ // domains queue — and a merge phase that writes the
10
11
  // prioritized specs/refactor-backlog.md. Only the summary comes back.
11
12
 
12
13
  export const meta = {
@@ -16,7 +17,7 @@ export const meta = {
16
17
  phases: [
17
18
  { title: 'Profile', detail: 'PIPELINE.md → JSON via profile-reader', model: 'haiku' },
18
19
  { title: 'Gates', detail: 'format/lint/typecheck/tests → specs/reports/audit-gates.txt', model: 'haiku' },
19
- { title: 'Audit', detail: 'one review-in-audit-mode agent per domain (concurrent, runtime-capped ~16)' },
20
+ { title: 'Audit', detail: 'one review-in-audit-mode agent per domain (concurrent, runtime-capped 20)' },
20
21
  { title: 'Backlog', detail: 'merge + write specs/refactor-backlog.md', model: 'haiku' },
21
22
  ],
22
23
  }
@@ -161,7 +162,9 @@ log(`Mechanical failures: ${mech.length}${gates && gates.overflow ? ` (+${gates.
161
162
 
162
163
  // ── Phase 2 — one auditor per domain, concurrent ─────────────────────────────
163
164
  // Domains = every surface + `shared` (contract package + anything outside the
164
- // surface trees). The runtime caps concurrent agents (~16); more domains queue.
165
+ // surface trees). The runtime caps concurrent agents at 20 by default; more domains
166
+ // queue. Raising CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS raises the ceiling, it does not
167
+ // change this script — the queue is the runtime's, and a queued domain is not a lost one.
165
168
  phase('Audit')
166
169
  const domains = surfaces.map(s => ({ key: s.key, path: s.path }))
167
170
  .concat([{ key: 'shared', path: (profile.contract && profile.contract.path) || '(everything outside the surface trees)' }])
@@ -1,6 +1,6 @@
1
1
  # Dashboard — architecture
2
2
 
3
- A local web cockpit for the pipeline, launched with `npx cohorte dashboard`
3
+ A local web cockpit for the pipeline, launched with `cohorte dashboard`
4
4
  (see the [root README](../README.md#dashboard--a-local-web-cockpit) for user-facing docs).
5
5
 
6
6
  ## Two halves: shipped runtime vs dev build
@@ -12,7 +12,7 @@ dashboard/
12
12
  dist/ → app/ built output. Shipped, served by server/. Git-ignored, rebuilt at publish.
13
13
  ```
14
14
 
15
- - **`server/`** is plain node (`http`, `fs`, `child_process`) — no deps, so `npx dashboard`
15
+ - **`server/`** is plain node (`http`, `fs`, `child_process`) — no deps, so `cohorte dashboard`
16
16
  needs no install. It serves `dist/` as static files + a small JSON/stream API.
17
17
  - **`app/`** is a Vite+React app built to `dist/`. `npm run build:dashboard` (root) runs
18
18
  `npm --prefix dashboard/app ci && … run build`; CI does this before `npm pack`/`publish`
@@ -58,18 +58,18 @@ function mk(id, label, status, detail, fix) {
58
58
  function checkCore(v) {
59
59
  if (v.installMode === 'none') {
60
60
  return mk('core', 'Core & pointer', 'bad', 'no pipeline core installed for this project',
61
- 'npx cohorte install (or --global)');
61
+ 'cohorte install (or --global)');
62
62
  }
63
63
  if (v.pointer.present && v.pointer.core_version && v.installedVersion &&
64
64
  v.pointer.core_version !== v.installedVersion) {
65
65
  return mk('core', 'Core & pointer', 'warn',
66
66
  `pointer says core ${v.pointer.core_version} but installed core is ${v.installedVersion}`,
67
- 'npx cohorte update (reconcile the pointer)');
67
+ 'cohorte update (reconcile the pointer)');
68
68
  }
69
69
  if (v.freshness === -1) {
70
70
  return mk('core', 'Core & pointer', 'warn',
71
71
  `core ${v.installedVersion} installed (${v.installMode}); npm latest is ${v.latest}`,
72
- '/cohorte-update-pipeline (or npx cohorte update)');
72
+ '/cohorte-update-pipeline (or cohorte update)');
73
73
  }
74
74
  const tail = v.latest ? `, npm latest ${v.latest}` : ', npm unreachable';
75
75
  return mk('core', 'Core & pointer', 'ok', `core ${v.installedVersion} (${v.installMode})${tail}`);
@@ -264,7 +264,7 @@ function checkHooks(projectRoot, globalDir, installMode, all) {
264
264
  'nothing to fix; run /cohorte-doctor inside that runtime for the full picture');
265
265
  }
266
266
  return mk('hooks', 'Gate hook', 'warn', 'no runtime installed to register the gate hook against',
267
- 'npx cohorte install (or --global)');
267
+ 'cohorte install (or --global)');
268
268
  }
269
269
 
270
270
  const problems = [];
@@ -306,7 +306,7 @@ function checkHooks(projectRoot, globalDir, installMode, all) {
306
306
  // alone reads as if the gate were off everywhere, when four of five may be fine.
307
307
  const tail = okLines.length ? ` (ok: ${okLines.join(', ')})` : '';
308
308
  return mk('hooks', 'Gate hook', 'warn', problems.join(' · ') + tail,
309
- 'npx cohorte@latest update (re-registers the gate hook for every installed runtime)');
309
+ 'npm i -g cohorte@latest && cohorte update (re-registers the gate hook for every installed runtime)');
310
310
  }
311
311
  const tail = advisory.length ? ` · ${advisory.map(l => l.label).join(', ')}: advisory --check only` : '';
312
312
  return mk('hooks', 'Gate hook', 'ok', `registered once for ${okLines.join(', ')}${tail}`);
@@ -381,20 +381,22 @@ function checkWorkflows(projectRoot, globalDir, installMode, all) {
381
381
  if (missing.length === scripts.length) {
382
382
  return mk('workflows', 'Workflows', 'warn',
383
383
  'no workflow scripts installed — conversational commands only (the default path)',
384
- 'npx cohorte update (ships core/workflows/)');
384
+ 'cohorte update (ships core/workflows/)');
385
385
  }
386
386
  if (missing.length) {
387
387
  return mk('workflows', 'Workflows', 'warn', `missing script(s): ${missing.join(', ')}`,
388
- 'npx cohorte update (half-copied core)');
388
+ 'cohorte update (half-copied core)');
389
389
  }
390
390
  if (!exists(path.join(agentsDir, 'profile-reader.md'))) {
391
391
  return mk('workflows', 'Workflows', 'warn',
392
392
  'scripts present but the profile-reader agent (their phase 0) is missing',
393
- 'npx cohorte update (re-copies the fixed agents)');
393
+ 'cohorte update (re-copies the fixed agents)');
394
394
  }
395
395
  return mk('workflows', 'Workflows', 'ok',
396
396
  'scripts + profile-reader installed — opt-in per run; needs Claude Code ≥ 2.1.154 with ' +
397
- 'workflows enabled (run /cohorte-doctor in-session to check the live half)');
397
+ 'workflows enabled (run /cohorte-doctor in-session to check the live half). Inline design ' +
398
+ '(design.inline) has its own, higher floor of ≥ 2.1.234 — it is a separate prerequisite and ' +
399
+ 'does not gate this check either way');
398
400
  }
399
401
 
400
402
  function scanSpecs(projectRoot) {
@@ -38,9 +38,9 @@ const CACHE_MS = 5 * 60 * 1000;
38
38
  // this once per tracked project, so the dashboard's 15s poll never finished.
39
39
  const FAIL_CACHE_MS = 60 * 1000;
40
40
 
41
- async function fetchRegistry() {
41
+ async function fetchRegistry(timeoutMs = 5000) {
42
42
  const ctrl = new AbortController();
43
- const t = setTimeout(() => ctrl.abort(), 5000);
43
+ const t = setTimeout(() => ctrl.abort(), timeoutMs);
44
44
  try {
45
45
  const res = await fetch('https://registry.npmjs.org/cohorte/latest', {
46
46
  signal: ctrl.signal,
@@ -75,7 +75,12 @@ function npmView() {
75
75
  }
76
76
 
77
77
  let _inflight = null;
78
- async function latestNpm() {
78
+ // `opts` bounds what a CALLER is willing to pay for the answer. The CLI's staleness
79
+ // notice runs at the tail of an install, where the default 5s fetch + 8s `npm view`
80
+ // fallback would make an OFFLINE install appear to hang for 13 seconds after it had
81
+ // already succeeded — so it asks for a short fetch and no fallback. The cache is
82
+ // shared across callers either way: a cheap lookup still serves an expensive one.
83
+ async function latestNpm({ timeoutMs = 5000, fallback = true } = {}) {
79
84
  const age = Date.now() - _cache.at;
80
85
  if (_cache.at && age < (_cache.value ? CACHE_MS : FAIL_CACHE_MS)) return _cache.value;
81
86
  // /api/fleet resolves N projects concurrently — without this, N lookups race and
@@ -83,7 +88,7 @@ async function latestNpm() {
83
88
  if (_inflight) return _inflight;
84
89
  _inflight = (async () => {
85
90
  try {
86
- const v = (await fetchRegistry()) || npmView();
91
+ const v = (await fetchRegistry(timeoutMs)) || (fallback ? npmView() : null);
87
92
  _cache = { value: v || null, at: Date.now() };
88
93
  return v || null;
89
94
  } finally {
@@ -141,4 +146,4 @@ async function versions({ projectRoot, globalDir, cliVersion }) {
141
146
  };
142
147
  }
143
148
 
144
- module.exports = { versions };
149
+ module.exports = { versions, latestNpm, cmpSemver };
package/install.ps1 CHANGED
@@ -122,7 +122,7 @@ cohorte needs Node >= 18 to install.
122
122
  The pipeline's commands are rendered per coding agent (Claude Code, Codex, Cursor,
123
123
  Gemini CLI, OpenCode) at install time; there is no PowerShell equivalent of that
124
124
  step, and a raw copy would install prompts this runtime cannot follow.
125
- Install Node, then: npx cohorte install$(if ($Global) { ' --global' })
125
+ Install Node, then: npm i -g cohorte; cohorte install$(if ($Global) { ' --global' })
126
126
  "@
127
127
  exit 1
128
128
 
@@ -257,7 +257,7 @@ cohorte needs Node >= 18 to install.
257
257
  # pipeline capability config is USER-level (vault, Notion DB, kanban boards) — it lives in
258
258
  # the user's .claude regardless of install scope. Seed only if neither the consolidated nor
259
259
  # the legacy copy exists. Non-interactive here: seeds disabled defaults; /cohorte-init-pipeline +
260
- # /cohorte-update-pipeline wire it (npx's installer offers a quick interview instead).
260
+ # /cohorte-update-pipeline wire it (the npm CLI's installer offers a quick interview instead).
261
261
  function Initialize-Config {
262
262
  $userClaude = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $HOME '.claude' }
263
263
  $cfg = Join-Path $userClaude 'cohorte.config.yaml'
package/install.sh CHANGED
@@ -42,8 +42,9 @@ install.sh — install the cohorte pipeline core.
42
42
  sh install.sh --update --global
43
43
 
44
44
  Honours $CLAUDE_CONFIG_DIR for the global destination and $PIPELINE_REPO for the
45
- source when piped through curl. The npm CLI (`npx cohorte install`) does the same
46
- thing and is the documented route; this script exists for Node-less environments.
45
+ source when piped through curl. The npm CLI (`npm i -g cohorte` then `cohorte install`)
46
+ does the same thing and is the documented route; this script exists for Node-less
47
+ environments.
47
48
  USAGE
48
49
  exit 0 ;;
49
50
  --) shift; break ;;
@@ -89,7 +90,7 @@ echo "error: cohorte needs Node ≥ 18 to install." >&2
89
90
  echo " The pipeline's commands are rendered per coding agent (Claude Code, Codex, Cursor," >&2
90
91
  echo " Gemini CLI, OpenCode) at install time; there is no shell equivalent of that step," >&2
91
92
  echo " and a raw copy would install prompts this runtime cannot follow." >&2
92
- echo " Install Node, then: npx cohorte install${scope:+ }$([ "$scope" = global ] && echo --global)" >&2
93
+ echo " Install Node, then: npm i -g cohorte && cohorte install$([ "$scope" = global ] && echo ' --global')" >&2
93
94
  exit 1
94
95
 
95
96
  # --- resolve the destination .claude dir ------------------------------------
@@ -218,7 +219,7 @@ scrub_research_questionnaire() {
218
219
  # pipeline capability config is USER-level (vault, Notion DB, kanban boards) — it lives in
219
220
  # ~/.claude regardless of install scope. Seed it only if neither the consolidated nor the
220
221
  # legacy copy exists. This piped installer is non-interactive: it seeds disabled defaults;
221
- # /cohorte-init-pipeline + /cohorte-update-pipeline wire it (npx's installer offers a quick interview instead).
222
+ # /cohorte-init-pipeline + /cohorte-update-pipeline wire it (the npm CLI's installer offers a quick interview instead).
222
223
  seed_config() {
223
224
  base="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
224
225
  cfg="$base/cohorte.config.yaml"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cohorte",
3
- "version": "2.5.0",
3
+ "version": "2.7.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"
package/profile/SCHEMA.md CHANGED
@@ -49,6 +49,7 @@ generic pipeline uses it, so a stateless agent can read/regenerate the profile c
49
49
  | `rbac.hierarchy` | list | review | Highest→lowest role list. |
50
50
  | `design.enabled` | bool | build, frontend, align-ds | `false` ⇒ design steps are no-ops. |
51
51
  | `design.provider` | enum | frontend, align-ds | `claude-design`/`figma`/`none`. |
52
+ | `design.inline` | bool | spec, build, doctor | Default `false`. `true` ⇒ `/cohorte-spec` offers the `/design` artboard step after freezing the brief, instead of ending at "paste this into the design tool". Requires `provider: claude-design`, the `inline_design` runtime capability, and Claude Code ≥ 2.1.234. **A research preview: artboards are not persisted for you.** The brief on disk stays the source of truth either way, so `false` loses nothing but the round trip. |
52
53
  | `design.design_system_project` | id | align-ds, frontend | UI-kit source of truth. |
53
54
  | `design.design_project` | id | build, frontend | Legacy fallback for bare-filename `design_files` only; default `none`. New specs use full `…/design/p/<projectId>?file=<file>` links that carry their own project + page (nothing to go stale on a DS rebuild). |
54
55
  | `design.snapshot_dir` | path | align-ds | Committed DS snapshot for diffing. |
@@ -7,9 +7,12 @@
7
7
  "TTL, 2x for 1h, read 0.1x) rather than restated per model, so a price change",
8
8
  "is a one-line edit. `fast` is the fast-mode premium (usage.speed === 'fast').",
9
9
  "Model lookup is longest-prefix, so dated ids (claude-haiku-4-5-20251001) hit",
10
- "their base entry without needing a row of their own."
10
+ "their base entry without needing a row of their own.",
11
+ "Sonnet 5 is $2/$10: the introductory rate became the standard rate on 2026-08-20.",
12
+ "The $3/$15 increase once scheduled for 2026-09-01 was cancelled and must not",
13
+ "be reinstated. Sonnet 4.6 and earlier stay at $3/$15 — same tier, different price."
11
14
  ],
12
- "updated": "2026-07-31",
15
+ "updated": "2026-08-22",
13
16
  "multipliers": {
14
17
  "cacheWrite5m": 1.25,
15
18
  "cacheWrite1h": 2.0,
@@ -31,7 +34,7 @@
31
34
  "claude-opus-4-7": { "input": 5, "output": 25 },
32
35
  "claude-opus-4-6": { "input": 5, "output": 25 },
33
36
  "claude-opus-4-5": { "input": 5, "output": 25 },
34
- "claude-sonnet-5": { "input": 3, "output": 15 },
37
+ "claude-sonnet-5": { "input": 2, "output": 10 },
35
38
  "claude-sonnet-4-6": { "input": 3, "output": 15 },
36
39
  "claude-sonnet-4-5": { "input": 3, "output": 15 },
37
40
  "claude-haiku-4-5": { "input": 1, "output": 5 }
@@ -144,8 +144,8 @@ check('…and keeps its own spend', retired && retired.tokens.output, 90);
144
144
  // opus-5 $5 in / $25 out per MTok; 5m cache write 1.25x input, cache read 0.1x input.
145
145
  // m1 100*5 + 1000*25 + 1000*6.25 + 10000*0.5 = 36750
146
146
  // m3 500*25 = 12500
147
- // s1 sonnet-5 2000*15 = 30000 (subagent)
148
- check('cost sums the cache tiers at their own rates', Number(build.cost.total.toFixed(6)), 0.07925);
147
+ // s1 sonnet-5 2000*10 = 20000 (subagent, $2/$10 since 2026-08)
148
+ check('cost sums the cache tiers at their own rates', Number(build.cost.total.toFixed(6)), 0.06925);
149
149
  check('the unpriced list stays empty for known models', build.unpriced, []);
150
150
 
151
151
  const detail = out.runs.find((r) => r.command === '/cohorte-build');
@@ -177,7 +177,7 @@ for (const c of KANBAN_STAGES) {
177
177
  // Every scripts/*.sh must be copied by BOTH shell installers. Callers chain these
178
178
  // with `|| true`, so one an installer forgets is a silent no-op forever — no kanban
179
179
  // card moves, no error. CI is the only place this is loud.
180
- // The third installer, bin/cli.js (what `npx cohorte` runs), copies by rule rather
180
+ // The third installer, bin/cli.js (what the `cohorte` CLI runs), copies by rule rather
181
181
  // than by name, so grepping for filenames can't see it — ci.yml dry-runs it into a
182
182
  // scratch HOME and asserts the same postconditions instead. Both are needed: this
183
183
  // check catches a forgotten name, that one catches a drifted rule.