cohorte 1.5.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +169 -3
  2. package/README.md +65 -57
  3. package/bin/cli.js +31 -15
  4. package/core/agents/implementer.template.md +3 -3
  5. package/core/agents/release.md +1 -1
  6. package/core/agents/review.md +25 -2
  7. package/core/commands/{audit.md → cohorte-audit.md} +11 -3
  8. package/core/commands/{brainstorm.md → cohorte-brainstorm.md} +9 -3
  9. package/core/commands/{build.md → cohorte-build.md} +95 -10
  10. package/core/commands/{doctor.md → cohorte-doctor.md} +22 -9
  11. package/core/commands/{fix.md → cohorte-fix.md} +20 -13
  12. package/core/commands/{init-pipeline.md → cohorte-init-pipeline.md} +1 -1
  13. package/core/commands/cohorte-loop.md +110 -0
  14. package/core/commands/{refactor.md → cohorte-refactor.md} +3 -3
  15. package/core/commands/{review.md → cohorte-review.md} +70 -20
  16. package/core/commands/{ship.md → cohorte-ship.md} +5 -5
  17. package/core/commands/{spec.md → cohorte-spec.md} +32 -12
  18. package/core/commands/{update-pipeline.md → cohorte-update-pipeline.md} +16 -6
  19. package/core/hooks/gate.py +101 -6
  20. package/core/templates/brainstorm-return.md +4 -4
  21. package/core/templates/decisions.template.md +42 -0
  22. package/core/templates/design-brief.md +1 -1
  23. package/core/templates/spec.template.md +8 -6
  24. package/core/templates/steps/init-pipeline/01-detect-stack.md +1 -1
  25. package/core/templates/steps/init-pipeline/02-interview-gaps.md +6 -6
  26. package/core/templates/steps/init-pipeline/03-draft-profile.md +1 -1
  27. package/core/templates/steps/init-pipeline/04-write-render.md +16 -12
  28. package/core/templates/steps/init-pipeline/05-report.md +5 -5
  29. package/core/workflows/audit.js +6 -6
  30. package/core/workflows/refactor.js +14 -14
  31. package/core/workflows/review.js +62 -20
  32. package/dashboard/README.md +2 -2
  33. package/dashboard/dist/assets/{index-dkO8UUVl.css → index-BZ_LQlEj.css} +1 -1
  34. package/dashboard/dist/assets/{index-8owBnqyv.js → index-P1I1JGtj.js} +11 -11
  35. package/dashboard/dist/index.html +2 -2
  36. package/dashboard/server/doctor.js +75 -18
  37. package/dashboard/server/index.js +5 -5
  38. package/dashboard/server/metrics.js +1 -1
  39. package/install.ps1 +31 -14
  40. package/install.sh +31 -14
  41. package/package.json +2 -2
  42. package/profile/PIPELINE.template.md +17 -16
  43. package/profile/SCHEMA.md +199 -48
  44. package/profile/cohorte.config.template.yaml +8 -8
  45. package/scripts/loop-detach.sh +153 -0
  46. package/scripts/loop.sh +202 -25
  47. package/scripts/metrics/collect.mjs +17 -8
  48. package/scripts/new-feature.sh.template +3 -3
  49. package/scripts/preflight.sh +40 -4
  50. package/scripts/remove-feature.sh.template +2 -2
  51. package/scripts/test-dashboard.mjs +34 -7
  52. package/scripts/test-gate.mjs +58 -0
  53. package/scripts/test-loop.mjs +269 -0
  54. package/scripts/test-metrics.mjs +23 -11
  55. package/scripts/test-workflows.mjs +33 -5
  56. package/scripts/validate-core.mjs +46 -9
  57. package/core/commands/loop.md +0 -61
  58. /package/core/commands/{align-ds.md → cohorte-align-ds.md} +0 -0
@@ -7,8 +7,8 @@
7
7
  <link rel="icon" type="image/png" sizes="16x16" href="./favicon-16.png" />
8
8
  <link rel="apple-touch-icon" sizes="180x180" href="./apple-touch-icon-180.png" />
9
9
  <title>cohorte · dashboard</title>
10
- <script type="module" crossorigin src="./assets/index-8owBnqyv.js"></script>
11
- <link rel="stylesheet" crossorigin href="./assets/index-dkO8UUVl.css">
10
+ <script type="module" crossorigin src="./assets/index-P1I1JGtj.js"></script>
11
+ <link rel="stylesheet" crossorigin href="./assets/index-BZ_LQlEj.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="root"></div>
@@ -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.
@@ -19,12 +19,15 @@ const FIXED_AGENTS = new Set([
19
19
  'implementer.template',
20
20
  ]);
21
21
 
22
- const VALID_STATUS = ['draft', 'frozen', 'in-review', 'shipped'];
22
+ // The spec lifecycle (SCHEMA.md §Spec status). `in-progress` and `blocked` are written by
23
+ // the /cohorte-loop driver — they are what makes an interrupted autonomous loop resumable, so a
24
+ // dashboard that flagged them as invalid would report the pipeline's own state as a defect.
25
+ const VALID_STATUS = ['draft', 'frozen', 'in-progress', 'in-review', 'shipped', 'blocked'];
23
26
 
24
27
  // Artifacts the pipeline itself writes into specs/ that are NOT feature specs and have no
25
- // front-matter status. `/audit` writes specs/refactor-backlog.md by design, so scanning it
26
- // as a spec made /doctor warn about a file cohorte had just created — a false positive that
27
- // 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)
28
31
  // are already skipped by the reader below.
29
32
  const NON_SPEC_FILES = new Set(['refactor-backlog.md']);
30
33
 
@@ -59,7 +62,7 @@ function checkCore(v) {
59
62
  if (v.freshness === -1) {
60
63
  return mk('core', 'Core & pointer', 'warn',
61
64
  `core ${v.installedVersion} installed (${v.installMode}); npm latest is ${v.latest}`,
62
- '/update-pipeline (or npx cohorte update)');
65
+ '/cohorte-update-pipeline (or npx cohorte update)');
63
66
  }
64
67
  const tail = v.latest ? `, npm latest ${v.latest}` : ', npm unreachable';
65
68
  return mk('core', 'Core & pointer', 'ok', `core ${v.installedVersion} (${v.installMode})${tail}`);
@@ -68,12 +71,12 @@ function checkCore(v) {
68
71
  function checkProfile(profile, hasPipelineMd) {
69
72
  if (!hasPipelineMd) {
70
73
  return mk('profile', 'Profile (PIPELINE.md)', 'bad', 'PIPELINE.md not found',
71
- '/init-pipeline (generate the project profile)');
74
+ '/cohorte-init-pipeline (generate the project profile)');
72
75
  }
73
76
  if (!profile) {
74
77
  return mk('profile', 'Profile (PIPELINE.md)', 'bad',
75
78
  'PIPELINE.md present but its `yaml pipeline-profile` block is missing or unparseable',
76
- '/init-pipeline (or fix the fenced yaml block)');
79
+ '/cohorte-init-pipeline (or fix the fenced yaml block)');
77
80
  }
78
81
  const n = (profile.surfaces || []).length;
79
82
  return mk('profile', 'Profile (PIPELINE.md)', n ? 'ok' : 'warn',
@@ -98,7 +101,7 @@ function checkAgents(profile, projectRoot) {
98
101
  if (missing.length) {
99
102
  return mk('agents', 'Surfaces ↔ agents', 'bad',
100
103
  `surface(s) with no rendered agent: ${missing.join(', ')}`,
101
- '/init-pipeline (re-render surface agents)');
104
+ '/cohorte-init-pipeline (re-render surface agents)');
102
105
  }
103
106
  if (orphans.length) {
104
107
  return mk('agents', 'Surfaces ↔ agents', 'warn',
@@ -117,7 +120,7 @@ function checkGate(profile, projectRoot) {
117
120
  const cfg = readJson(path.join(projectRoot, '.claude', 'gate-config.json'));
118
121
  if (!cfg) {
119
122
  return mk('gate', 'Gate config', 'bad', '.claude/gate-config.json missing or unreadable',
120
- '/init-pipeline (regenerate gate-config.json from the gate block)');
123
+ '/cohorte-init-pipeline (regenerate gate-config.json from the gate block)');
121
124
  }
122
125
  const drifted = [];
123
126
  if (!sameSet(cfg.deny, gate.deny)) drifted.push('deny');
@@ -144,6 +147,55 @@ function checkGate(profile, projectRoot) {
144
147
  (branchGated ? `, ${branchGated} gated on ${gate.default_branch || 'main'}` : '') + ')');
145
148
  }
146
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
+
147
199
  function gateRegs(settingsPath) {
148
200
  const data = readJson(settingsPath);
149
201
  const pre = data && data.hooks && Array.isArray(data.hooks.PreToolUse) ? data.hooks.PreToolUse : [];
@@ -156,7 +208,7 @@ function gateRegs(settingsPath) {
156
208
 
157
209
  function checkHooks(projectRoot, globalDir, installMode) {
158
210
  // A registration in EITHER scope serves the project: bundled repos get it from
159
- // /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
160
212
  // hook usually lives (correctly, exactly once) in global settings — warning
161
213
  // there would prescribe a re-registration that double-prompts.
162
214
  const scopes = [
@@ -170,7 +222,7 @@ function checkHooks(projectRoot, globalDir, installMode) {
170
222
  return mk('hooks', 'Gate hook', 'warn', 'gate.py not registered in project or global settings.json',
171
223
  installMode === 'global'
172
224
  ? 'npx cohorte install --global (re-registers the hook)'
173
- : '/init-pipeline (register the PreToolUse gate hook)');
225
+ : '/cohorte-init-pipeline (register the PreToolUse gate hook)');
174
226
  }
175
227
  const total = found.reduce((n, s) => n + s.regs.length, 0);
176
228
  if (total > 1) {
@@ -192,7 +244,7 @@ function checkRetrieval(profile, projectRoot) {
192
244
  if (!provider || provider === 'none' || String(provider).startsWith('<')) {
193
245
  return mk('retrieval', 'Code retrieval', 'skip', 'provider: none');
194
246
  }
195
- // 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
196
248
  // registers it at project scope in .mcp.json. Verify the entry exists on disk;
197
249
  // live connectivity still needs a session — note it, don't fake green.
198
250
  const mcp = readJson(path.join(projectRoot, '.mcp.json'));
@@ -201,10 +253,10 @@ function checkRetrieval(profile, projectRoot) {
201
253
  if (!wired) {
202
254
  return mk('retrieval', 'Code retrieval', 'warn',
203
255
  `profile says provider: ${provider} but .mcp.json has no matching server entry`,
204
- '/init-pipeline or /update-pipeline (re-wire the retrieval provider)');
256
+ '/cohorte-init-pipeline or /cohorte-update-pipeline (re-wire the retrieval provider)');
205
257
  }
206
258
  return mk('retrieval', 'Code retrieval', 'ok',
207
- `provider: ${provider} — registered in .mcp.json (connectivity needs /doctor in-session)`);
259
+ `provider: ${provider} — registered in .mcp.json (connectivity needs /cohorte-doctor in-session)`);
208
260
  }
209
261
 
210
262
  function checkDesign(profile, projectRoot) {
@@ -231,14 +283,14 @@ function checkIsolation(profile, projectRoot) {
231
283
  }
232
284
  if (problems.length) {
233
285
  return mk('isolation', 'Isolation', 'warn', problems.join('; '),
234
- '/init-pipeline (re-render the isolation scripts)');
286
+ '/cohorte-init-pipeline (re-render the isolation scripts)');
235
287
  }
236
288
  return mk('isolation', 'Isolation', 'ok', 'feature scripts rendered (worktree state not checked here)');
237
289
  }
238
290
 
239
291
  // Workflow variants (review/audit/refactor as deterministic multi-agent runs) are opt-in;
240
292
  // the conversational commands stay the default path, so nothing here is ever 'bad'.
241
- // 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
242
294
  // in-session checks that; here we only check what's on disk.
243
295
  function checkWorkflows(projectRoot, globalDir, installMode) {
244
296
  if (installMode === 'none') return mk('workflows', 'Workflows', 'skip', 'no core installed');
@@ -266,7 +318,7 @@ function checkWorkflows(projectRoot, globalDir, installMode) {
266
318
  }
267
319
  return mk('workflows', 'Workflows', 'ok',
268
320
  'scripts + profile-reader installed — opt-in per run; needs Claude Code ≥ 2.1.154 with ' +
269
- 'workflows enabled (run /doctor in-session to check the live half)');
321
+ 'workflows enabled (run /cohorte-doctor in-session to check the live half)');
270
322
  }
271
323
 
272
324
  function scanSpecs(projectRoot) {
@@ -281,12 +333,16 @@ function scanSpecs(projectRoot) {
281
333
  const body = fm ? fm[1] : '';
282
334
  const get = k => { const m = body.match(new RegExp(`^${k}:\\s*(.*)$`, 'm')); return m ? m[1].trim() : null; };
283
335
  const status = get('status');
336
+ // The loop driver's resume state, when a /cohorte-loop is (or was) running on this spec.
337
+ const pass = parseInt(get('loop_pass'), 10);
338
+ const phase = get('loop_phase');
284
339
  specs.push({
285
340
  file: f,
286
341
  id: get('feature_id') || f.replace(/\.md$/, ''),
287
342
  title: get('title'),
288
343
  status: status ? status.split('#')[0].trim() : null,
289
344
  branch: get('branch'),
345
+ loop: pass > 0 ? { pass, phase: phase && phase !== 'done' ? phase : null } : null,
290
346
  });
291
347
  }
292
348
  return specs;
@@ -320,6 +376,7 @@ async function state({ projectRoot, globalDir, cliVersion }) {
320
376
  checkProfile(profile, pipelineMd != null),
321
377
  checkAgents(profile, projectRoot),
322
378
  checkGate(profile, projectRoot),
379
+ checkLocalArtifacts(projectRoot),
323
380
  checkHooks(projectRoot, globalDir, v.installMode),
324
381
  checkRetrieval(profile, projectRoot),
325
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))
@@ -193,6 +194,22 @@ try {
193
194
  # 1.5.0 removed the /smoke phase; copy-over never deletes, so scrub the orphan agent.
194
195
  Remove-Item -LiteralPath (Join-Path $dest 'agents\smoke.md') -Force -ErrorAction SilentlyContinue
195
196
  Remove-Item -LiteralPath (Join-Path $dest 'commands\smoke.md') -Force -ErrorAction SilentlyContinue
197
+ # 1.4.0 removed /cycle and its workflow — and no installer ever scrubbed them, so every
198
+ # install since has kept offering a command that dispatches a workflow whose phases were
199
+ # later deleted. A dead command is worse than a missing one: the model can still fire it.
200
+ Remove-Item -LiteralPath (Join-Path $dest 'commands\cycle.md') -Force -ErrorAction SilentlyContinue
201
+ Remove-Item -LiteralPath (Join-Path $dest 'workflows\cycle.js') -Force -ErrorAction SilentlyContinue
202
+ # 1.6.0 renamed /loop → /drive: Claude Code's own built-in /loop shadowed ours, so a leftover
203
+ # commands\loop.md is a command the user can never reach — scrub it rather than leave a decoy.
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
+ }
196
213
  # 0.1.19 split the bi-mode questionnaire-researcher into research-agent + questionnaire-architect;
197
214
  # copy-over never deletes, so scrub the retired agent lest a dead subagent_type linger.
198
215
  Remove-Item -LiteralPath (Join-Path $dest 'agents\questionnaire-researcher.md') -Force -ErrorAction SilentlyContinue
@@ -216,8 +233,8 @@ try {
216
233
 
217
234
  # pipeline capability config is USER-level (vault, Notion DB, kanban boards) — it lives in
218
235
  # the user's .claude regardless of install scope. Seed only if neither the consolidated nor
219
- # the legacy copy exists. Non-interactive here: seeds disabled defaults; /init-pipeline +
220
- # /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).
221
238
  function Initialize-Config {
222
239
  $userClaude = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $HOME '.claude' }
223
240
  $cfg = Join-Path $userClaude 'cohorte.config.yaml'
@@ -229,11 +246,11 @@ try {
229
246
  Write-Host " - kept your existing $cfg"
230
247
  } elseif ($legacy) {
231
248
  Write-Host " - found legacy $legacy — kept as-is (read as a fallback)."
232
- 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."
233
250
  } else {
234
251
  New-Item -ItemType Directory -Force -Path $userClaude | Out-Null
235
252
  Copy-Item (Join-Path $src 'profile\cohorte.config.template.yaml') $cfg
236
- 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)"
237
254
  }
238
255
  }
239
256
 
@@ -310,24 +327,24 @@ try {
310
327
  OK pipeline core installed globally into $dest (version $ver)
311
328
  gate hook: $hookState (reads each repo's .claude/gate-config.json; silent where absent)
312
329
 
313
- 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
314
331
  available in EVERY project on this machine — nothing is copied per repo.
315
332
 
316
333
  Per repo:
317
334
  1. Open the project in Claude Code.
318
- 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
319
336
  .claude/gate-config.json, and drops a committed .claude/pipeline.json pointer so
320
337
  teammates know to install the global core ($repoUrl).
321
- 3. Commit PIPELINE.md + .claude/, then /brainstorm to start a feature.
338
+ 3. Commit PIPELINE.md + .claude/, then /cohorte-brainstorm to start a feature.
322
339
 
323
- Code retrieval (Serena — the default provider /init-pipeline wires per repo):
340
+ Code retrieval (Serena — the default provider /cohorte-init-pipeline wires per repo):
324
341
  uv tool install -p 3.13 serena-agent # once per machine
325
342
  Make sure the uv tools dir is on PATH (uv tool update-shell) — otherwise the
326
343
  registered MCP server silently fails to start.
327
344
 
328
345
  Global kanban config, user-scoped — optional:
329
346
  · One consolidated file: ~/.claude/cohorte.config.yaml (don't hand-edit it).
330
- · /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 +
331
348
  syncing an Obsidian kanban board of the pipeline in your shared vault.
332
349
  "@
333
350
  return
@@ -348,11 +365,11 @@ OK pipeline core installed into $dest (version $ver)
348
365
 
349
366
  Next:
350
367
  1. Open the project in Claude Code.
351
- 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
352
369
  PIPELINE.md + renders one implementer agent per surface.
353
- 3. Commit PIPELINE.md, then /brainstorm to start a feature.
370
+ 3. Commit PIPELINE.md, then /cohorte-brainstorm to start a feature.
354
371
 
355
- Code retrieval (Serena — the default provider /init-pipeline wires per repo):
372
+ Code retrieval (Serena — the default provider /cohorte-init-pipeline wires per repo):
356
373
  uv tool install -p 3.13 serena-agent # once per machine
357
374
  Make sure the uv tools dir is on PATH (uv tool update-shell) — otherwise the
358
375
  registered MCP server silently fails to start.
@@ -370,7 +387,7 @@ Prefer one shared core across all your repos? Re-run with -Global.
370
387
  Write-Host @"
371
388
 
372
389
  OK core refreshed to $ver. Your PIPELINE.md, rendered surface agents, gate-config.json and
373
- 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.
374
391
  "@
375
392
  }
376
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"
@@ -154,6 +156,21 @@ copy_fixed_agents() {
154
156
  "$dest/agents/"
155
157
  # 1.5.0 removed the /smoke phase; copy-over never deletes, so scrub the orphan agent.
156
158
  rm -f "$dest/agents/smoke.md" "$dest/commands/smoke.md"
159
+ # 1.4.0 removed /cycle and its workflow — and no installer ever scrubbed them, so every
160
+ # install since has kept offering a command that dispatches a workflow whose phases were
161
+ # later deleted. A dead command is worse than a missing one: the model can still fire it.
162
+ rm -f "$dest/commands/cycle.md" "$dest/workflows/cycle.js"
163
+ # 1.6.0 renamed /loop → /drive: Claude Code's own built-in /loop shadowed ours, so a leftover
164
+ # commands/loop.md is a command the user can never reach — scrub it rather than leave a decoy.
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
157
174
  # 0.1.19 split the bi-mode questionnaire-researcher into research-agent + questionnaire-architect;
158
175
  # copy-over never deletes, so scrub the retired agent lest a dead subagent_type linger.
159
176
  rm -f "$dest/agents/questionnaire-researcher.md"
@@ -179,7 +196,7 @@ scrub_research_questionnaire() {
179
196
  # pipeline capability config is USER-level (vault, Notion DB, kanban boards) — it lives in
180
197
  # ~/.claude regardless of install scope. Seed it only if neither the consolidated nor the
181
198
  # legacy copy exists. This piped installer is non-interactive: it seeds disabled defaults;
182
- # /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).
183
200
  seed_config() {
184
201
  base="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
185
202
  cfg="$base/cohorte.config.yaml"
@@ -191,11 +208,11 @@ seed_config() {
191
208
  echo " · kept your existing $cfg"
192
209
  elif [ -n "$legacy" ]; then
193
210
  echo " · found legacy $legacy — kept as-is (read as a fallback)."
194
- 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."
195
212
  else
196
213
  mkdir -p "$base"
197
214
  cp "$src/profile/cohorte.config.template.yaml" "$cfg"
198
- 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)"
199
216
  fi
200
217
  }
201
218
 
@@ -283,24 +300,24 @@ if [ "$scope" = "global" ]; then
283
300
  ✓ pipeline core installed globally into $dest (version $ver)
284
301
  gate hook: $hook_state (reads each repo's .claude/gate-config.json; silent where absent)
285
302
 
286
- 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
287
304
  available in EVERY project on this machine — nothing is copied per repo.
288
305
 
289
306
  Per repo:
290
307
  1. Open the project in Claude Code.
291
- 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
292
309
  .claude/gate-config.json, and drops a committed .claude/pipeline.json pointer so
293
310
  teammates know to install the global core ($REPO_URL).
294
- 3. Commit PIPELINE.md + .claude/, then /brainstorm to start a feature.
311
+ 3. Commit PIPELINE.md + .claude/, then /cohorte-brainstorm to start a feature.
295
312
 
296
- Code retrieval (Serena — the default provider /init-pipeline wires per repo):
313
+ Code retrieval (Serena — the default provider /cohorte-init-pipeline wires per repo):
297
314
  uv tool install -p 3.13 serena-agent # once per machine
298
315
  Make sure ~/.local/bin is on PATH (uv tool update-shell) — otherwise the
299
316
  registered MCP server silently fails to start.
300
317
 
301
318
  Global kanban config, user-scoped — optional:
302
319
  · One consolidated file: ~/.claude/cohorte.config.yaml (don't hand-edit it).
303
- · /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 +
304
321
  syncing an Obsidian kanban board of the pipeline in your shared vault.
305
322
  EOF
306
323
  exit 0
@@ -319,11 +336,11 @@ if [ "$mode" = "install" ]; then
319
336
 
320
337
  Next:
321
338
  1. Open the project in Claude Code.
322
- 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
323
340
  PIPELINE.md + renders one implementer agent per surface.
324
- 3. Commit PIPELINE.md, then /brainstorm to start a feature.
341
+ 3. Commit PIPELINE.md, then /cohorte-brainstorm to start a feature.
325
342
 
326
- Code retrieval (Serena — the default provider /init-pipeline wires per repo):
343
+ Code retrieval (Serena — the default provider /cohorte-init-pipeline wires per repo):
327
344
  uv tool install -p 3.13 serena-agent # once per machine
328
345
  Make sure ~/.local/bin is on PATH (uv tool update-shell) — otherwise the
329
346
  registered MCP server silently fails to start.
@@ -339,6 +356,6 @@ else
339
356
  cat <<EOF
340
357
 
341
358
  ✓ core refreshed to $ver. Your PIPELINE.md, rendered surface agents, gate-config.json and
342
- 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.
343
360
  EOF
344
361
  fi
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cohorte",
3
- "version": "1.5.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:>