cohorte 1.6.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +84 -2
  2. package/README.md +57 -57
  3. package/bin/cli.js +23 -15
  4. package/core/agents/implementer.template.md +3 -3
  5. package/core/agents/release.md +1 -1
  6. package/core/agents/review.md +3 -3
  7. package/core/commands/{audit.md → cohorte-audit.md} +3 -3
  8. package/core/commands/{brainstorm.md → cohorte-brainstorm.md} +4 -4
  9. package/core/commands/{build.md → cohorte-build.md} +15 -15
  10. package/core/commands/{doctor.md → cohorte-doctor.md} +17 -9
  11. package/core/commands/{fix.md → cohorte-fix.md} +15 -13
  12. package/core/commands/{init-pipeline.md → cohorte-init-pipeline.md} +1 -1
  13. package/core/commands/cohorte-loop.md +110 -0
  14. package/core/commands/{refactor.md → cohorte-refactor.md} +3 -3
  15. package/core/commands/{review.md → cohorte-review.md} +20 -19
  16. package/core/commands/{ship.md → cohorte-ship.md} +5 -5
  17. package/core/commands/{spec.md → cohorte-spec.md} +13 -13
  18. package/core/commands/{update-pipeline.md → cohorte-update-pipeline.md} +11 -6
  19. package/core/hooks/gate.py +101 -6
  20. package/core/templates/brainstorm-return.md +4 -4
  21. package/core/templates/decisions.template.md +1 -1
  22. package/core/templates/design-brief.md +1 -1
  23. package/core/templates/spec.template.md +7 -7
  24. package/core/templates/steps/init-pipeline/01-detect-stack.md +1 -1
  25. package/core/templates/steps/init-pipeline/02-interview-gaps.md +6 -6
  26. package/core/templates/steps/init-pipeline/03-draft-profile.md +1 -1
  27. package/core/templates/steps/init-pipeline/04-write-render.md +16 -12
  28. package/core/templates/steps/init-pipeline/05-report.md +5 -5
  29. package/core/workflows/audit.js +6 -6
  30. package/core/workflows/refactor.js +14 -14
  31. package/core/workflows/review.js +22 -22
  32. package/dashboard/README.md +2 -2
  33. package/dashboard/dist/assets/{index-DYyn4p93.js → index-P1I1JGtj.js} +2 -2
  34. package/dashboard/dist/index.html +1 -1
  35. package/dashboard/server/doctor.js +69 -19
  36. package/dashboard/server/index.js +5 -5
  37. package/dashboard/server/metrics.js +1 -1
  38. package/install.ps1 +23 -14
  39. package/install.sh +24 -14
  40. package/package.json +2 -2
  41. package/profile/PIPELINE.template.md +17 -16
  42. package/profile/SCHEMA.md +89 -77
  43. package/profile/cohorte.config.template.yaml +8 -8
  44. package/scripts/loop-detach.sh +153 -0
  45. package/scripts/loop.sh +75 -27
  46. package/scripts/metrics/collect.mjs +17 -8
  47. package/scripts/new-feature.sh.template +3 -3
  48. package/scripts/preflight.sh +40 -4
  49. package/scripts/remove-feature.sh.template +2 -2
  50. package/scripts/test-dashboard.mjs +34 -7
  51. package/scripts/test-gate.mjs +58 -0
  52. package/scripts/test-loop.mjs +49 -7
  53. package/scripts/test-metrics.mjs +23 -11
  54. package/scripts/test-workflows.mjs +7 -7
  55. package/scripts/validate-core.mjs +45 -23
  56. package/core/commands/drive.md +0 -80
  57. /package/core/commands/{align-ds.md → cohorte-align-ds.md} +0 -0
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
- // Programmatic port of the /doctor checks (core/commands/doctor.md), for the dashboard.
2
+ // Programmatic port of the /cohorte-doctor checks (core/commands/doctor.md), for the dashboard.
3
3
  // Read-only: inspects files only. Checks that need a live process (MCP connectivity,
4
4
  // git worktree state, DesignSync) are reported as `skip` with a note — the node server
5
5
  // can't run them, and honest "not checked here" beats a false green.
@@ -20,14 +20,14 @@ const FIXED_AGENTS = new Set([
20
20
  ]);
21
21
 
22
22
  // The spec lifecycle (SCHEMA.md §Spec status). `in-progress` and `blocked` are written by
23
- // the /drive driver — they are what makes an interrupted autonomous loop resumable, so a
23
+ // the /cohorte-loop driver — they are what makes an interrupted autonomous loop resumable, so a
24
24
  // dashboard that flagged them as invalid would report the pipeline's own state as a defect.
25
25
  const VALID_STATUS = ['draft', 'frozen', 'in-progress', 'in-review', 'shipped', 'blocked'];
26
26
 
27
27
  // Artifacts the pipeline itself writes into specs/ that are NOT feature specs and have no
28
- // front-matter status. `/audit` writes specs/refactor-backlog.md by design, so scanning it
29
- // as a spec made /doctor warn about a file cohorte had just created — a false positive that
30
- // fired in every project that had ever run /audit. `_`-prefixed files (e.g. _template.md)
28
+ // front-matter status. `/cohorte-audit` writes specs/refactor-backlog.md by design, so scanning it
29
+ // as a spec made /cohorte-doctor warn about a file cohorte had just created — a false positive that
30
+ // fired in every project that had ever run /cohorte-audit. `_`-prefixed files (e.g. _template.md)
31
31
  // are already skipped by the reader below.
32
32
  const NON_SPEC_FILES = new Set(['refactor-backlog.md']);
33
33
 
@@ -62,7 +62,7 @@ function checkCore(v) {
62
62
  if (v.freshness === -1) {
63
63
  return mk('core', 'Core & pointer', 'warn',
64
64
  `core ${v.installedVersion} installed (${v.installMode}); npm latest is ${v.latest}`,
65
- '/update-pipeline (or npx cohorte update)');
65
+ '/cohorte-update-pipeline (or npx cohorte update)');
66
66
  }
67
67
  const tail = v.latest ? `, npm latest ${v.latest}` : ', npm unreachable';
68
68
  return mk('core', 'Core & pointer', 'ok', `core ${v.installedVersion} (${v.installMode})${tail}`);
@@ -71,12 +71,12 @@ function checkCore(v) {
71
71
  function checkProfile(profile, hasPipelineMd) {
72
72
  if (!hasPipelineMd) {
73
73
  return mk('profile', 'Profile (PIPELINE.md)', 'bad', 'PIPELINE.md not found',
74
- '/init-pipeline (generate the project profile)');
74
+ '/cohorte-init-pipeline (generate the project profile)');
75
75
  }
76
76
  if (!profile) {
77
77
  return mk('profile', 'Profile (PIPELINE.md)', 'bad',
78
78
  'PIPELINE.md present but its `yaml pipeline-profile` block is missing or unparseable',
79
- '/init-pipeline (or fix the fenced yaml block)');
79
+ '/cohorte-init-pipeline (or fix the fenced yaml block)');
80
80
  }
81
81
  const n = (profile.surfaces || []).length;
82
82
  return mk('profile', 'Profile (PIPELINE.md)', n ? 'ok' : 'warn',
@@ -101,7 +101,7 @@ function checkAgents(profile, projectRoot) {
101
101
  if (missing.length) {
102
102
  return mk('agents', 'Surfaces ↔ agents', 'bad',
103
103
  `surface(s) with no rendered agent: ${missing.join(', ')}`,
104
- '/init-pipeline (re-render surface agents)');
104
+ '/cohorte-init-pipeline (re-render surface agents)');
105
105
  }
106
106
  if (orphans.length) {
107
107
  return mk('agents', 'Surfaces ↔ agents', 'warn',
@@ -120,7 +120,7 @@ function checkGate(profile, projectRoot) {
120
120
  const cfg = readJson(path.join(projectRoot, '.claude', 'gate-config.json'));
121
121
  if (!cfg) {
122
122
  return mk('gate', 'Gate config', 'bad', '.claude/gate-config.json missing or unreadable',
123
- '/init-pipeline (regenerate gate-config.json from the gate block)');
123
+ '/cohorte-init-pipeline (regenerate gate-config.json from the gate block)');
124
124
  }
125
125
  const drifted = [];
126
126
  if (!sameSet(cfg.deny, gate.deny)) drifted.push('deny');
@@ -147,6 +147,55 @@ function checkGate(profile, projectRoot) {
147
147
  (branchGated ? `, ${branchGated} gated on ${gate.default_branch || 'main'}` : '') + ')');
148
148
  }
149
149
 
150
+ // Local-only pipeline artifacts. `.claude/preflight.ok` is the one that BREAKS things when
151
+ // versioned rather than merely being noise: the stamp names the tree it verified, committing
152
+ // it moves HEAD past that, and the committed copy then rides into every fresh clone and
153
+ // worktree — the phase gate either blocks a clean tree or greens one it never checked.
154
+ const LOCAL_ARTIFACTS = [
155
+ { path: '.claude/preflight.ok', why: 'the phase-gate stamp — a versioned one breaks the gate in every clone and worktree' },
156
+ { path: '.claude/pipeline-metrics.jsonl', why: 'the per-dispatch metrics sink (local, append-only)' },
157
+ { path: 'specs/reports/', why: 'the /cohorte-review report buffer (derived, regenerated each run)' },
158
+ ];
159
+
160
+ // Does any .gitignore in play cover `rel`? Deliberately literal — it recognizes the exact
161
+ // path, its basename, a trailing-slash dir prefix and a `*.<ext>` glob, which is every form
162
+ // /cohorte-init-pipeline writes. Anything more clever would need a real gitignore engine.
163
+ function ignoreCovers(projectRoot, rel) {
164
+ const base = rel.replace(/\/$/, '').split('/').pop();
165
+ const ext = base.includes('.') ? '*' + base.slice(base.lastIndexOf('.')) : null;
166
+ const files = [
167
+ path.join(projectRoot, '.gitignore'),
168
+ path.join(projectRoot, '.claude', '.gitignore'),
169
+ ];
170
+ for (const f of files) {
171
+ const text = readText(f);
172
+ if (!text) continue;
173
+ for (let line of text.split('\n')) {
174
+ line = line.trim();
175
+ if (!line || line.startsWith('#')) continue;
176
+ const p = line.replace(/^\/+/, '').replace(/\/+$/, '');
177
+ if (!p) continue;
178
+ if (p === rel.replace(/\/$/, '') || p === base || p === ext) return true;
179
+ // A directory rule covers everything under it (`.claude/` ignores the stamp too).
180
+ if (rel.startsWith(p + '/')) return true;
181
+ }
182
+ }
183
+ return false;
184
+ }
185
+
186
+ function checkLocalArtifacts(projectRoot) {
187
+ const unignored = LOCAL_ARTIFACTS.filter(a => !ignoreCovers(projectRoot, a.path));
188
+ if (!unignored.length) {
189
+ return mk('artifacts', 'Local artifacts', 'ok',
190
+ `${LOCAL_ARTIFACTS.length} local-only artifact path(s) all gitignored`);
191
+ }
192
+ const stamp = unignored.find(a => a.path === '.claude/preflight.ok');
193
+ return mk('artifacts', 'Local artifacts', stamp ? 'bad' : 'warn',
194
+ `not gitignored: ${unignored.map(a => a.path).join(', ')} — ${unignored[0].why}`,
195
+ `add ${unignored.map(a => a.path).join(' + ')} to .gitignore`
196
+ + (stamp ? ', then `git rm --cached --ignore-unmatch .claude/preflight.ok`' : ''));
197
+ }
198
+
150
199
  function gateRegs(settingsPath) {
151
200
  const data = readJson(settingsPath);
152
201
  const pre = data && data.hooks && Array.isArray(data.hooks.PreToolUse) ? data.hooks.PreToolUse : [];
@@ -159,7 +208,7 @@ function gateRegs(settingsPath) {
159
208
 
160
209
  function checkHooks(projectRoot, globalDir, installMode) {
161
210
  // A registration in EITHER scope serves the project: bundled repos get it from
162
- // /init-pipeline in project settings, but on a machine with the global core the
211
+ // /cohorte-init-pipeline in project settings, but on a machine with the global core the
163
212
  // hook usually lives (correctly, exactly once) in global settings — warning
164
213
  // there would prescribe a re-registration that double-prompts.
165
214
  const scopes = [
@@ -173,7 +222,7 @@ function checkHooks(projectRoot, globalDir, installMode) {
173
222
  return mk('hooks', 'Gate hook', 'warn', 'gate.py not registered in project or global settings.json',
174
223
  installMode === 'global'
175
224
  ? 'npx cohorte install --global (re-registers the hook)'
176
- : '/init-pipeline (register the PreToolUse gate hook)');
225
+ : '/cohorte-init-pipeline (register the PreToolUse gate hook)');
177
226
  }
178
227
  const total = found.reduce((n, s) => n + s.regs.length, 0);
179
228
  if (total > 1) {
@@ -195,7 +244,7 @@ function checkRetrieval(profile, projectRoot) {
195
244
  if (!provider || provider === 'none' || String(provider).startsWith('<')) {
196
245
  return mk('retrieval', 'Code retrieval', 'skip', 'provider: none');
197
246
  }
198
- // The profile alone isn't proof the provider was ever wired: /init-pipeline
247
+ // The profile alone isn't proof the provider was ever wired: /cohorte-init-pipeline
199
248
  // registers it at project scope in .mcp.json. Verify the entry exists on disk;
200
249
  // live connectivity still needs a session — note it, don't fake green.
201
250
  const mcp = readJson(path.join(projectRoot, '.mcp.json'));
@@ -204,10 +253,10 @@ function checkRetrieval(profile, projectRoot) {
204
253
  if (!wired) {
205
254
  return mk('retrieval', 'Code retrieval', 'warn',
206
255
  `profile says provider: ${provider} but .mcp.json has no matching server entry`,
207
- '/init-pipeline or /update-pipeline (re-wire the retrieval provider)');
256
+ '/cohorte-init-pipeline or /cohorte-update-pipeline (re-wire the retrieval provider)');
208
257
  }
209
258
  return mk('retrieval', 'Code retrieval', 'ok',
210
- `provider: ${provider} — registered in .mcp.json (connectivity needs /doctor in-session)`);
259
+ `provider: ${provider} — registered in .mcp.json (connectivity needs /cohorte-doctor in-session)`);
211
260
  }
212
261
 
213
262
  function checkDesign(profile, projectRoot) {
@@ -234,14 +283,14 @@ function checkIsolation(profile, projectRoot) {
234
283
  }
235
284
  if (problems.length) {
236
285
  return mk('isolation', 'Isolation', 'warn', problems.join('; '),
237
- '/init-pipeline (re-render the isolation scripts)');
286
+ '/cohorte-init-pipeline (re-render the isolation scripts)');
238
287
  }
239
288
  return mk('isolation', 'Isolation', 'ok', 'feature scripts rendered (worktree state not checked here)');
240
289
  }
241
290
 
242
291
  // Workflow variants (review/audit/refactor as deterministic multi-agent runs) are opt-in;
243
292
  // the conversational commands stay the default path, so nothing here is ever 'bad'.
244
- // Whether the session has workflows ENABLED needs a live Claude session — /doctor
293
+ // Whether the session has workflows ENABLED needs a live Claude session — /cohorte-doctor
245
294
  // in-session checks that; here we only check what's on disk.
246
295
  function checkWorkflows(projectRoot, globalDir, installMode) {
247
296
  if (installMode === 'none') return mk('workflows', 'Workflows', 'skip', 'no core installed');
@@ -269,7 +318,7 @@ function checkWorkflows(projectRoot, globalDir, installMode) {
269
318
  }
270
319
  return mk('workflows', 'Workflows', 'ok',
271
320
  'scripts + profile-reader installed — opt-in per run; needs Claude Code ≥ 2.1.154 with ' +
272
- 'workflows enabled (run /doctor in-session to check the live half)');
321
+ 'workflows enabled (run /cohorte-doctor in-session to check the live half)');
273
322
  }
274
323
 
275
324
  function scanSpecs(projectRoot) {
@@ -284,7 +333,7 @@ function scanSpecs(projectRoot) {
284
333
  const body = fm ? fm[1] : '';
285
334
  const get = k => { const m = body.match(new RegExp(`^${k}:\\s*(.*)$`, 'm')); return m ? m[1].trim() : null; };
286
335
  const status = get('status');
287
- // The loop driver's resume state, when a /drive is (or was) running on this spec.
336
+ // The loop driver's resume state, when a /cohorte-loop is (or was) running on this spec.
288
337
  const pass = parseInt(get('loop_pass'), 10);
289
338
  const phase = get('loop_phase');
290
339
  specs.push({
@@ -327,6 +376,7 @@ async function state({ projectRoot, globalDir, cliVersion }) {
327
376
  checkProfile(profile, pipelineMd != null),
328
377
  checkAgents(profile, projectRoot),
329
378
  checkGate(profile, projectRoot),
379
+ checkLocalArtifacts(projectRoot),
330
380
  checkHooks(projectRoot, globalDir, v.installMode),
331
381
  checkRetrieval(profile, projectRoot),
332
382
  checkDesign(profile, projectRoot),
@@ -143,8 +143,8 @@ function runAction(req, res, body, { pkgRoot }) {
143
143
  function runClaude(req, res, body) {
144
144
  const project = body.project ? path.resolve(body.project) : null;
145
145
  const command = String(body.command || '');
146
- if (!/^\/(init-pipeline|update-pipeline|audit)$/.test(command)) {
147
- return sendJson(res, 400, { error: 'unsupported command (only /init-pipeline, /update-pipeline or /audit)' });
146
+ if (!/^\/cohorte-(init-pipeline|update-pipeline|audit)$/.test(command)) {
147
+ return sendJson(res, 400, { error: 'unsupported command (only /cohorte-init-pipeline, /cohorte-update-pipeline or /cohorte-audit)' });
148
148
  }
149
149
  if (!project || !fs.existsSync(project)) {
150
150
  return sendJson(res, 400, { error: 'project path not found' });
@@ -174,7 +174,7 @@ function runClaude(req, res, body) {
174
174
  // Full project reset: back up the project's pipeline footprint (.claude/, PIPELINE.md, and
175
175
  // optionally specs/) to .claude.bak-<ts>, remove it, then reinstall a fresh BUNDLED core (or,
176
176
  // for global-mode projects, leave the shared ~/.claude core untouched). Never touches ~/.claude.
177
- // Streams progress; ends with __EXIT__ <code>. The profile is regenerated by /init-pipeline after.
177
+ // Streams progress; ends with __EXIT__ <code>. The profile is regenerated by /cohorte-init-pipeline after.
178
178
  function runReset(req, res, body, { pkgRoot, globalDir }) {
179
179
  const project = body.project ? path.resolve(body.project) : null;
180
180
  const purgeSpecs = !!body.purgeSpecs;
@@ -230,14 +230,14 @@ function runReset(req, res, body, { pkgRoot, globalDir }) {
230
230
  child.stdout.on('data', d => res.write(d));
231
231
  child.stderr.on('data', d => res.write(d));
232
232
  child.on('close', code => {
233
- log('\n✔ Reset complete. Now run /init-pipeline in Claude Code to regenerate PIPELINE.md + the surface agents.');
233
+ log('\n✔ Reset complete. Now run /cohorte-init-pipeline in Claude Code to regenerate PIPELINE.md + the surface agents.');
234
234
  done(code);
235
235
  });
236
236
  child.on('error', err => { log(`\nspawn error: ${err.message}`); done(1); });
237
237
  req.on('close', () => { try { child.kill(); } catch { /* already gone */ } });
238
238
  } else {
239
239
  log('\n✔ Reset complete. The shared global core stays installed in ~/.claude.');
240
- log('Now run /init-pipeline in Claude Code to regenerate this project\'s PIPELINE.md + agents.');
240
+ log('Now run /cohorte-init-pipeline in Claude Code to regenerate this project\'s PIPELINE.md + agents.');
241
241
  done(0);
242
242
  }
243
243
  } catch (e) {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
  // Read a project's `.claude/pipeline-metrics.jsonl` (one line per phase batch, appended by
3
- // /build, /review and /fix) and aggregate it per feature: wall-clock per phase, fix
3
+ // /cohorte-build, /cohorte-review and /cohorte-fix) and aggregate it per feature: wall-clock per phase, fix
4
4
  // rounds, and per-surface results. Dependency-free; a missing file is simply "no data yet".
5
5
  //
6
6
  // Two line formats coexist in the file:
package/install.ps1 CHANGED
@@ -14,7 +14,7 @@
14
14
  # .\install.ps1 -Update -Global
15
15
  #
16
16
  # Per-project install copies the core into <target>\.claude; global install copies it once
17
- # into ~\.claude and registers the gate hook there. Either way you then run `/init-pipeline`
17
+ # into ~\.claude and registers the gate hook there. Either way you then run `/cohorte-init-pipeline`
18
18
  # in each repo to generate PIPELINE.md + render the surface agents. Update refreshes ONLY the
19
19
  # stack-agnostic files; generated profiles, rendered agents, gate-config.json and any project
20
20
  # settings.json are left untouched.
@@ -152,6 +152,7 @@ try {
152
152
  Copy-Item (Join-Path $src 'scripts\telemetry-send.sh') (Join-Path $dest 'pipeline\scripts') -Force
153
153
  Copy-Item (Join-Path $src 'scripts\preflight.sh') (Join-Path $dest 'pipeline\scripts') -Force
154
154
  Copy-Item (Join-Path $src 'scripts\loop.sh') (Join-Path $dest 'pipeline\scripts') -Force
155
+ Copy-Item (Join-Path $src 'scripts\loop-detach.sh') (Join-Path $dest 'pipeline\scripts') -Force
155
156
  Copy-Item (Join-Path $src 'core\agents\implementer.template.md') (Join-Path $dest 'pipeline') -Force
156
157
  if (Test-Path (Join-Path $src 'CHANGELOG.md')) { Copy-Item (Join-Path $src 'CHANGELOG.md') (Join-Path $dest 'pipeline') -Force }
157
158
  [System.IO.File]::WriteAllText((Join-Path $dest 'pipeline\VERSION'), "$ver`n", [System.Text.UTF8Encoding]::new($false))
@@ -201,6 +202,14 @@ try {
201
202
  # 1.6.0 renamed /loop → /drive: Claude Code's own built-in /loop shadowed ours, so a leftover
202
203
  # commands\loop.md is a command the user can never reach — scrub it rather than leave a decoy.
203
204
  Remove-Item -LiteralPath (Join-Path $dest 'commands\loop.md') -Force -ErrorAction SilentlyContinue
205
+ # 2.0.0 prefixed every command with `cohorte-`, which ends the shadowing problem for good.
206
+ # Copy-Item never deletes, so all 13 bare names would survive an upgrade as decoys — and a
207
+ # stale /build is the worst kind: it still dispatches implementers, from a 1.x command file
208
+ # that knows nothing of this core's contract. /drive goes too (it became /cohorte-loop).
209
+ foreach ($c in @('align-ds','audit','brainstorm','build','doctor','drive','fix',
210
+ 'init-pipeline','refactor','review','ship','spec','update-pipeline')) {
211
+ Remove-Item -LiteralPath (Join-Path $dest "commands\$c.md") -Force -ErrorAction SilentlyContinue
212
+ }
204
213
  # 0.1.19 split the bi-mode questionnaire-researcher into research-agent + questionnaire-architect;
205
214
  # copy-over never deletes, so scrub the retired agent lest a dead subagent_type linger.
206
215
  Remove-Item -LiteralPath (Join-Path $dest 'agents\questionnaire-researcher.md') -Force -ErrorAction SilentlyContinue
@@ -224,8 +233,8 @@ try {
224
233
 
225
234
  # pipeline capability config is USER-level (vault, Notion DB, kanban boards) — it lives in
226
235
  # the user's .claude regardless of install scope. Seed only if neither the consolidated nor
227
- # the legacy copy exists. Non-interactive here: seeds disabled defaults; /init-pipeline +
228
- # /update-pipeline wire it (npx's installer offers a quick interview instead).
236
+ # the legacy copy exists. Non-interactive here: seeds disabled defaults; /cohorte-init-pipeline +
237
+ # /cohorte-update-pipeline wire it (npx's installer offers a quick interview instead).
229
238
  function Initialize-Config {
230
239
  $userClaude = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $HOME '.claude' }
231
240
  $cfg = Join-Path $userClaude 'cohorte.config.yaml'
@@ -237,11 +246,11 @@ try {
237
246
  Write-Host " - kept your existing $cfg"
238
247
  } elseif ($legacy) {
239
248
  Write-Host " - found legacy $legacy — kept as-is (read as a fallback)."
240
- Write-Host " Run /update-pipeline to migrate it into cohorte.config.yaml + wire the kanban."
249
+ Write-Host " Run /cohorte-update-pipeline to migrate it into cohorte.config.yaml + wire the kanban."
241
250
  } else {
242
251
  New-Item -ItemType Directory -Force -Path $userClaude | Out-Null
243
252
  Copy-Item (Join-Path $src 'profile\cohorte.config.template.yaml') $cfg
244
- Write-Host " - seeded $cfg (disabled defaults — enable via /init-pipeline or /update-pipeline)"
253
+ Write-Host " - seeded $cfg (disabled defaults — enable via /cohorte-init-pipeline or /cohorte-update-pipeline)"
245
254
  }
246
255
  }
247
256
 
@@ -318,24 +327,24 @@ try {
318
327
  OK pipeline core installed globally into $dest (version $ver)
319
328
  gate hook: $hookState (reads each repo's .claude/gate-config.json; silent where absent)
320
329
 
321
- The commands (/init-pipeline, /brainstorm, /build ...) and the review/release agents are now
330
+ The commands (/cohorte-init-pipeline, /cohorte-brainstorm, /cohorte-build ...) and the review/release agents are now
322
331
  available in EVERY project on this machine — nothing is copied per repo.
323
332
 
324
333
  Per repo:
325
334
  1. Open the project in Claude Code.
326
- 2. Run /init-pipeline — it generates PIPELINE.md, renders the surface agents, writes
335
+ 2. Run /cohorte-init-pipeline — it generates PIPELINE.md, renders the surface agents, writes
327
336
  .claude/gate-config.json, and drops a committed .claude/pipeline.json pointer so
328
337
  teammates know to install the global core ($repoUrl).
329
- 3. Commit PIPELINE.md + .claude/, then /brainstorm to start a feature.
338
+ 3. Commit PIPELINE.md + .claude/, then /cohorte-brainstorm to start a feature.
330
339
 
331
- Code retrieval (Serena — the default provider /init-pipeline wires per repo):
340
+ Code retrieval (Serena — the default provider /cohorte-init-pipeline wires per repo):
332
341
  uv tool install -p 3.13 serena-agent # once per machine
333
342
  Make sure the uv tools dir is on PATH (uv tool update-shell) — otherwise the
334
343
  registered MCP server silently fails to start.
335
344
 
336
345
  Global kanban config, user-scoped — optional:
337
346
  · One consolidated file: ~/.claude/cohorte.config.yaml (don't hand-edit it).
338
- · /init-pipeline (new project) and /update-pipeline (existing) wire it for you: creating +
347
+ · /cohorte-init-pipeline (new project) and /cohorte-update-pipeline (existing) wire it for you: creating +
339
348
  syncing an Obsidian kanban board of the pipeline in your shared vault.
340
349
  "@
341
350
  return
@@ -356,11 +365,11 @@ OK pipeline core installed into $dest (version $ver)
356
365
 
357
366
  Next:
358
367
  1. Open the project in Claude Code.
359
- 2. Run /init-pipeline — it detects your stack, asks the gaps, and generates
368
+ 2. Run /cohorte-init-pipeline — it detects your stack, asks the gaps, and generates
360
369
  PIPELINE.md + renders one implementer agent per surface.
361
- 3. Commit PIPELINE.md, then /brainstorm to start a feature.
370
+ 3. Commit PIPELINE.md, then /cohorte-brainstorm to start a feature.
362
371
 
363
- Code retrieval (Serena — the default provider /init-pipeline wires per repo):
372
+ Code retrieval (Serena — the default provider /cohorte-init-pipeline wires per repo):
364
373
  uv tool install -p 3.13 serena-agent # once per machine
365
374
  Make sure the uv tools dir is on PATH (uv tool update-shell) — otherwise the
366
375
  registered MCP server silently fails to start.
@@ -378,7 +387,7 @@ Prefer one shared core across all your repos? Re-run with -Global.
378
387
  Write-Host @"
379
388
 
380
389
  OK core refreshed to $ver. Your PIPELINE.md, rendered surface agents, gate-config.json and
381
- settings.json were left as-is. Re-run /init-pipeline if your stack changed.
390
+ settings.json were left as-is. Re-run /cohorte-init-pipeline if your stack changed.
382
391
  "@
383
392
  }
384
393
  } finally {
package/install.sh CHANGED
@@ -16,7 +16,7 @@
16
16
  # sh install.sh --update --global
17
17
  #
18
18
  # Per-project install copies the core into <target>/.claude; global install copies it once
19
- # into ~/.claude and registers the gate hook there. Either way you then run `/init-pipeline`
19
+ # into ~/.claude and registers the gate hook there. Either way you then run `/cohorte-init-pipeline`
20
20
  # in each repo to generate PIPELINE.md + render the surface agents. Update refreshes ONLY the
21
21
  # stack-agnostic files; generated profiles, rendered agents, gate-config.json and any project
22
22
  # settings.json are left untouched.
@@ -108,8 +108,10 @@ copy_core() {
108
108
  cp "$src/scripts/telemetry-send.sh" "$dest/pipeline/scripts/"
109
109
  cp "$src/scripts/preflight.sh" "$dest/pipeline/scripts/"
110
110
  cp "$src/scripts/loop.sh" "$dest/pipeline/scripts/"
111
+ cp "$src/scripts/loop-detach.sh" "$dest/pipeline/scripts/"
111
112
  chmod +x "$dest/pipeline/scripts/kanban-move.sh" "$dest/pipeline/scripts/telemetry-send.sh" \
112
- "$dest/pipeline/scripts/preflight.sh" "$dest/pipeline/scripts/loop.sh" 2>/dev/null || true
113
+ "$dest/pipeline/scripts/preflight.sh" "$dest/pipeline/scripts/loop.sh" \
114
+ "$dest/pipeline/scripts/loop-detach.sh" 2>/dev/null || true
113
115
  cp "$src/core/agents/implementer.template.md" "$dest/pipeline/"
114
116
  [ -f "$src/CHANGELOG.md" ] && cp "$src/CHANGELOG.md" "$dest/pipeline/"
115
117
  printf '%s\n' "$ver" > "$dest/pipeline/VERSION"
@@ -161,6 +163,14 @@ copy_fixed_agents() {
161
163
  # 1.6.0 renamed /loop → /drive: Claude Code's own built-in /loop shadowed ours, so a leftover
162
164
  # commands/loop.md is a command the user can never reach — scrub it rather than leave a decoy.
163
165
  rm -f "$dest/commands/loop.md"
166
+ # 2.0.0 prefixed every command with `cohorte-`, which ends the shadowing problem for good.
167
+ # Copy-over never deletes, so all 13 bare names would survive an upgrade as decoys — and a
168
+ # stale /build is the worst kind: it still dispatches implementers, from a 1.x command file
169
+ # that knows nothing of this core's contract. /drive goes too (it became /cohorte-loop).
170
+ for c in align-ds audit brainstorm build doctor drive fix init-pipeline \
171
+ refactor review ship spec update-pipeline; do
172
+ rm -f "$dest/commands/$c.md"
173
+ done
164
174
  # 0.1.19 split the bi-mode questionnaire-researcher into research-agent + questionnaire-architect;
165
175
  # copy-over never deletes, so scrub the retired agent lest a dead subagent_type linger.
166
176
  rm -f "$dest/agents/questionnaire-researcher.md"
@@ -186,7 +196,7 @@ scrub_research_questionnaire() {
186
196
  # pipeline capability config is USER-level (vault, Notion DB, kanban boards) — it lives in
187
197
  # ~/.claude regardless of install scope. Seed it only if neither the consolidated nor the
188
198
  # legacy copy exists. This piped installer is non-interactive: it seeds disabled defaults;
189
- # /init-pipeline + /update-pipeline wire it (npx's installer offers a quick interview instead).
199
+ # /cohorte-init-pipeline + /cohorte-update-pipeline wire it (npx's installer offers a quick interview instead).
190
200
  seed_config() {
191
201
  base="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
192
202
  cfg="$base/cohorte.config.yaml"
@@ -198,11 +208,11 @@ seed_config() {
198
208
  echo " · kept your existing $cfg"
199
209
  elif [ -n "$legacy" ]; then
200
210
  echo " · found legacy $legacy — kept as-is (read as a fallback)."
201
- echo " Run /update-pipeline to migrate it into cohorte.config.yaml + wire the kanban."
211
+ echo " Run /cohorte-update-pipeline to migrate it into cohorte.config.yaml + wire the kanban."
202
212
  else
203
213
  mkdir -p "$base"
204
214
  cp "$src/profile/cohorte.config.template.yaml" "$cfg"
205
- echo " · seeded $cfg (disabled defaults — enable via /init-pipeline or /update-pipeline)"
215
+ echo " · seeded $cfg (disabled defaults — enable via /cohorte-init-pipeline or /cohorte-update-pipeline)"
206
216
  fi
207
217
  }
208
218
 
@@ -290,24 +300,24 @@ if [ "$scope" = "global" ]; then
290
300
  ✓ pipeline core installed globally into $dest (version $ver)
291
301
  gate hook: $hook_state (reads each repo's .claude/gate-config.json; silent where absent)
292
302
 
293
- The commands (/init-pipeline, /brainstorm, /build …) and the review/release agents are now
303
+ The commands (/cohorte-init-pipeline, /cohorte-brainstorm, /cohorte-build …) and the review/release agents are now
294
304
  available in EVERY project on this machine — nothing is copied per repo.
295
305
 
296
306
  Per repo:
297
307
  1. Open the project in Claude Code.
298
- 2. Run /init-pipeline — it generates PIPELINE.md, renders the surface agents, writes
308
+ 2. Run /cohorte-init-pipeline — it generates PIPELINE.md, renders the surface agents, writes
299
309
  .claude/gate-config.json, and drops a committed .claude/pipeline.json pointer so
300
310
  teammates know to install the global core ($REPO_URL).
301
- 3. Commit PIPELINE.md + .claude/, then /brainstorm to start a feature.
311
+ 3. Commit PIPELINE.md + .claude/, then /cohorte-brainstorm to start a feature.
302
312
 
303
- Code retrieval (Serena — the default provider /init-pipeline wires per repo):
313
+ Code retrieval (Serena — the default provider /cohorte-init-pipeline wires per repo):
304
314
  uv tool install -p 3.13 serena-agent # once per machine
305
315
  Make sure ~/.local/bin is on PATH (uv tool update-shell) — otherwise the
306
316
  registered MCP server silently fails to start.
307
317
 
308
318
  Global kanban config, user-scoped — optional:
309
319
  · One consolidated file: ~/.claude/cohorte.config.yaml (don't hand-edit it).
310
- · /init-pipeline (new project) and /update-pipeline (existing) wire it for you: creating +
320
+ · /cohorte-init-pipeline (new project) and /cohorte-update-pipeline (existing) wire it for you: creating +
311
321
  syncing an Obsidian kanban board of the pipeline in your shared vault.
312
322
  EOF
313
323
  exit 0
@@ -326,11 +336,11 @@ if [ "$mode" = "install" ]; then
326
336
 
327
337
  Next:
328
338
  1. Open the project in Claude Code.
329
- 2. Run /init-pipeline — it detects your stack, asks the gaps, and generates
339
+ 2. Run /cohorte-init-pipeline — it detects your stack, asks the gaps, and generates
330
340
  PIPELINE.md + renders one implementer agent per surface.
331
- 3. Commit PIPELINE.md, then /brainstorm to start a feature.
341
+ 3. Commit PIPELINE.md, then /cohorte-brainstorm to start a feature.
332
342
 
333
- Code retrieval (Serena — the default provider /init-pipeline wires per repo):
343
+ Code retrieval (Serena — the default provider /cohorte-init-pipeline wires per repo):
334
344
  uv tool install -p 3.13 serena-agent # once per machine
335
345
  Make sure ~/.local/bin is on PATH (uv tool update-shell) — otherwise the
336
346
  registered MCP server silently fails to start.
@@ -346,6 +356,6 @@ else
346
356
  cat <<EOF
347
357
 
348
358
  ✓ core refreshed to $ver. Your PIPELINE.md, rendered surface agents, gate-config.json and
349
- settings.json were left as-is. Re-run /init-pipeline if your stack changed.
359
+ settings.json were left as-is. Re-run /cohorte-init-pipeline if your stack changed.
350
360
  EOF
351
361
  fi
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cohorte",
3
- "version": "1.6.0",
4
- "description": "Portable, stack-agnostic multi-agent development pipeline for Claude Code — install the core, run /init-pipeline, and it adapts to your project's stack.",
3
+ "version": "2.0.0",
4
+ "description": "Portable, stack-agnostic multi-agent development pipeline for Claude Code — 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
  },
@@ -1,9 +1,9 @@
1
1
  # PIPELINE.md — <PROJECT_NAME> profile
2
2
 
3
- > **Generated by `/init-pipeline`.** This is the project profile: the single place the
3
+ > **Generated by `/cohorte-init-pipeline`.** This is the project profile: the single place the
4
4
  > portable multi-agent pipeline reads for everything stack-specific. The pipeline core
5
5
  > under `.claude/` is generic and references THIS file by section — keep project facts
6
- > here, never hardcoded into agent prompts. Kept current by `/update-pipeline` (reconcile:
6
+ > here, never hardcoded into agent prompts. Kept current by `/cohorte-update-pipeline` (reconcile:
7
7
  > new fields are topped up, your values never overwritten). Rendered agent files are
8
8
  > regenerated from this profile on every reconcile — customize agents through §Conventions
9
9
  > here, never by editing the agent files. Free-form notes belong in `CLAUDE.md`.
@@ -36,15 +36,15 @@ repo:
36
36
  # wired. serena = live LSP symbol navigation (default, no index to maintain);
37
37
  # graphify = persistent tree-sitter knowledge graph over code + docs (needs an
38
38
  # index step + rescans); none = agents fall back to Grep/Glob/Read.
39
- # Registered by /init-pipeline as a project-scope MCP server (committed .mcp.json).
39
+ # Registered by /cohorte-init-pipeline as a project-scope MCP server (committed .mcp.json).
40
40
  retrieval:
41
41
  provider: serena # serena | graphify | none
42
42
 
43
43
  # ── surfaces ────────────────────────────────────────────────────────────────
44
- # One entry per independently-implemented code area. /build dispatches ONE
44
+ # One entry per independently-implemented code area. /cohorte-build dispatches ONE
45
45
  # implementer agent per surface, in parallel. Each surface is rendered by
46
- # /init-pipeline into its own agent file (agent field) with the tools listed.
47
- # This list GROWS automatically: /build §1.5 adds a surface (and renders its
46
+ # /cohorte-init-pipeline into its own agent file (agent field) with the tools listed.
47
+ # This list GROWS automatically: /cohorte-build §1.5 adds a surface (and renders its
48
48
  # agent) when a spec touches a tree no surface owns, or splits a bottleneck
49
49
  # surface into specialized sub-surfaces. Keep one owner per tree; shared code
50
50
  # (routing/state/DS) is its own single-owner surface. See SCHEMA.md §Specialization.
@@ -61,7 +61,7 @@ surfaces:
61
61
  test_cmd: pnpm --filter api test
62
62
  # Bridled variants — what agents actually RUN (dot reporter / failures-only /
63
63
  # --quiet), so a green run costs lines, not pages. "" ⇒ callers fall back to
64
- # `<cmd> 2>&1 | tail -40`. /init-pipeline asks for these; never store a bare
64
+ # `<cmd> 2>&1 | tail -40`. /cohorte-init-pipeline asks for these; never store a bare
65
65
  # `pnpm test` as the thing agents execute.
66
66
  test_quiet_cmd: pnpm --filter api test --reporter=dot
67
67
  lint_cmd: pnpm --filter api lint
@@ -90,7 +90,7 @@ surfaces:
90
90
 
91
91
  # ── contract (the only cross-surface sync channel) ──────────────────────────
92
92
  contract:
93
- enabled: <true | false> # false ⇒ no shared contract; skip §2 of /build
93
+ enabled: <true | false> # false ⇒ no shared contract; skip §2 of /cohorte-build
94
94
  mechanism: <shared-types-zod | openapi | protobuf | json-schema | none>
95
95
  path: packages/shared-types/src # where <feature_id>.<ext> is authored
96
96
  ext: ts
@@ -106,7 +106,7 @@ commands:
106
106
  format: pnpm format
107
107
  typecheck: pnpm check-types
108
108
  test: pnpm test
109
- test_quiet: pnpm test --reporter=dot # bridled variant — what the /review preflight runs
109
+ test_quiet: pnpm test --reporter=dot # bridled variant — what the /cohorte-review preflight runs
110
110
  # migration commands — omit / leave "" if the project has no DB migrations
111
111
  migrate: "cd apps/api && node ace migration:run"
112
112
  make_migration: "cd apps/api && node ace make:migration"
@@ -119,11 +119,11 @@ rbac:
119
119
 
120
120
  # ── design system (optional) ────────────────────────────────────────────────
121
121
  design:
122
- enabled: <true | false> # false ⇒ /align-ds + design gates are no-ops
122
+ enabled: <true | false> # false ⇒ /cohorte-align-ds + design gates are no-ops
123
123
  provider: <claude-design | figma | none>
124
124
  design_system_project: <uuid-or-id> # the UI-kit source of truth
125
125
  design_project: none # legacy fallback for bare-filename design_files only; keep `none` — new specs use full links that carry their own project, so nothing here goes stale on a DS rebuild
126
- snapshot_dir: apps/web/design-reference # committed DS snapshot for /align-ds diff
126
+ snapshot_dir: apps/web/design-reference # committed DS snapshot for /cohorte-align-ds diff
127
127
  direction: design-to-code # NEVER push code→design for a curated DS
128
128
  ui_kit_path: apps/web/src/components/ui
129
129
  tokens_path: apps/web/src/index.css
@@ -166,7 +166,8 @@ gate:
166
166
  - "docker compose"
167
167
  # Phase gate: review dispatches require a fresh `.claude/preflight.ok` stamp,
168
168
  # written by pipeline/scripts/preflight.sh when typecheck+lint+tests are green —
169
- # gate.py "ask"s the dispatch when the stamp is missing, stale, or HEAD moved.
169
+ # gate.py "ask"s the dispatch when the stamp is missing, stale, or the code changed since.
170
+ # The stamp is local: keep `.claude/preflight.ok` gitignored, never committed.
170
171
  preflight:
171
172
  enabled: true
172
173
  agents: [review] # subagent_types the stamp gates
@@ -176,7 +177,7 @@ gate:
176
177
 
177
178
  > **Note.** The **kanban** board link is **user-scoped** — NOT configured here. Its facts live in the
178
179
  > consolidated `~/.claude/cohorte.config.yaml`, read at runtime (the kanban board is keyed by this
179
- > profile's `name`). This project profile only governs the dev pipeline (`/brainstorm…/ship`).
180
+ > profile's `name`). This project profile only governs the dev pipeline (`/cohorte-brainstorm…/cohorte-ship`).
180
181
 
181
182
  ---
182
183
 
@@ -205,18 +206,18 @@ gate:
205
206
 
206
207
  ---
207
208
 
208
- ## Design brief note (feeds `/spec` §8 and Claude Design)
209
+ ## Design brief note (feeds `/cohorte-spec` §8 and Claude Design)
209
210
 
210
211
  - <design system name + primitives to use · mobile-first · copy language · brand/theming constraints>
211
212
  - A feature lists its design pages in the spec front-matter `design_files` as **full links** of the form
212
213
  `https://claude.ai/design/p/<projectId>?file=<file>` — each carries its own project (`/p/<projectId>`)
213
214
  and page (`?file=`), so an agent extracts both and reads it via `DesignSync get_file(<projectId>,
214
- <file>)`. Paste the link at `/build`'s design gate. No stored project id ⇒ a design-system rebuild
215
+ <file>)`. Paste the link at `/cohorte-build`'s design gate. No stored project id ⇒ a design-system rebuild
215
216
  (new project) just means pasting the new links. (Bare file names are legacy, resolved in `design_project`.)
216
217
 
217
218
  ---
218
219
 
219
- ## Personas for `/brainstorm` (optional — the panel)
220
+ ## Personas for `/cohorte-brainstorm` (optional — the panel)
220
221
 
221
222
  <Panel members with a job AND a personality who challenge the idea from their angle and
222
223
  disagree with each other. Include a role-coverage guard if `rbac.enabled`. Example:>