cohorte 2.10.1 → 3.0.0-dev.1

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 (213) hide show
  1. package/README.md +7 -0
  2. package/assets/manifest.json +193 -0
  3. package/assets/migrations/config/README.md +12 -0
  4. package/assets/migrations/state/0001_init.sql +154 -0
  5. package/assets/prompts/README.md +11 -0
  6. package/assets/prompts/agents/fixer.md +7 -0
  7. package/assets/prompts/agents/implementer.md +12 -0
  8. package/assets/prompts/agents/reviewer.md +8 -0
  9. package/assets/prompts/agents/security-reviewer.md +7 -0
  10. package/assets/prompts/phases/build.md +5 -0
  11. package/assets/prompts/phases/review.md +5 -0
  12. package/assets/prompts/phases/test.md +5 -0
  13. package/assets/prompts/system.md +11 -0
  14. package/assets/schemas/agent-output.schema.json +225 -0
  15. package/assets/schemas/auth-status.schema.json +87 -0
  16. package/assets/schemas/command-result.schema.json +192 -0
  17. package/assets/schemas/commands.schema.json +952 -0
  18. package/assets/schemas/config.schema.json +2259 -0
  19. package/assets/schemas/doctor-report.schema.json +104 -0
  20. package/assets/schemas/events.schema.json +8514 -0
  21. package/assets/schemas/fake-script.schema.json +549 -0
  22. package/assets/schemas/inspect.schema.json +3133 -0
  23. package/assets/schemas/manifest.schema.json +79 -0
  24. package/assets/schemas/ownership.schema.json +52 -0
  25. package/assets/schemas/policy-verdict.schema.json +282 -0
  26. package/assets/schemas/project-model.schema.json +783 -0
  27. package/assets/schemas/project-status.schema.json +217 -0
  28. package/assets/schemas/reconcile-plan.schema.json +154 -0
  29. package/assets/schemas/run-diff.schema.json +177 -0
  30. package/assets/schemas/run-snapshot-manifest.schema.json +2104 -0
  31. package/assets/schemas/run-state.schema.json +1755 -0
  32. package/assets/schemas/runtime-capabilities.schema.json +1545 -0
  33. package/assets/schemas/sandbox-capabilities.schema.json +116 -0
  34. package/assets/schemas/skill.schema.json +87 -0
  35. package/assets/schemas/spec.schema.json +202 -0
  36. package/assets/schemas/tool-catalogue.schema.json +655 -0
  37. package/assets/schemas/trust-record.schema.json +38 -0
  38. package/assets/skills/README.md +9 -0
  39. package/assets/skills/inspect-surface.md +4 -0
  40. package/dist/agent-host.mjs +2187 -0
  41. package/dist/agent-host.mjs.map +1 -0
  42. package/dist/bundle-manifest.json +104 -0
  43. package/dist/chunks/__host-DgGPyco9.mjs +18 -0
  44. package/dist/chunks/__host-DgGPyco9.mjs.map +1 -0
  45. package/dist/chunks/approve-Cv135UqI.mjs +19 -0
  46. package/dist/chunks/approve-Cv135UqI.mjs.map +1 -0
  47. package/dist/chunks/auth-BT5oVp76.mjs +132 -0
  48. package/dist/chunks/auth-BT5oVp76.mjs.map +1 -0
  49. package/dist/chunks/auth-CYOh60KG.mjs +93 -0
  50. package/dist/chunks/auth-CYOh60KG.mjs.map +1 -0
  51. package/dist/chunks/brainstorm-BwKA6yZP.mjs +7 -0
  52. package/dist/chunks/brainstorm-BwKA6yZP.mjs.map +1 -0
  53. package/dist/chunks/cancel-KV_cpsqS.mjs +15 -0
  54. package/dist/chunks/cancel-KV_cpsqS.mjs.map +1 -0
  55. package/dist/chunks/canonical-C8UxeEmB.mjs +57 -0
  56. package/dist/chunks/canonical-C8UxeEmB.mjs.map +1 -0
  57. package/dist/chunks/catalogue-BvwSrM_j.mjs +1549 -0
  58. package/dist/chunks/catalogue-BvwSrM_j.mjs.map +1 -0
  59. package/dist/chunks/compose-Dvxunpto.mjs +7307 -0
  60. package/dist/chunks/compose-Dvxunpto.mjs.map +1 -0
  61. package/dist/chunks/config-DefHA2Lw.mjs +96 -0
  62. package/dist/chunks/config-DefHA2Lw.mjs.map +1 -0
  63. package/dist/chunks/contract-BkMjU1kR.mjs +295 -0
  64. package/dist/chunks/contract-BkMjU1kR.mjs.map +1 -0
  65. package/dist/chunks/contract-bQ9FJl4N.mjs +123 -0
  66. package/dist/chunks/contract-bQ9FJl4N.mjs.map +1 -0
  67. package/dist/chunks/deny-ClTxSvHJ.mjs +15 -0
  68. package/dist/chunks/deny-ClTxSvHJ.mjs.map +1 -0
  69. package/dist/chunks/diff-Qw0II0sj.mjs +101 -0
  70. package/dist/chunks/diff-Qw0II0sj.mjs.map +1 -0
  71. package/dist/chunks/discover-eS01-me1.mjs +21 -0
  72. package/dist/chunks/discover-eS01-me1.mjs.map +1 -0
  73. package/dist/chunks/doctor-B-H2UK5e.mjs +222 -0
  74. package/dist/chunks/doctor-B-H2UK5e.mjs.map +1 -0
  75. package/dist/chunks/fix-D4Tj8RVA.mjs +18 -0
  76. package/dist/chunks/fix-D4Tj8RVA.mjs.map +1 -0
  77. package/dist/chunks/gc-BBXlBF5w.mjs +161 -0
  78. package/dist/chunks/gc-BBXlBF5w.mjs.map +1 -0
  79. package/dist/chunks/init-BUnzIkeE.mjs +63 -0
  80. package/dist/chunks/init-BUnzIkeE.mjs.map +1 -0
  81. package/dist/chunks/inspect-Buby4cQD.mjs +22 -0
  82. package/dist/chunks/inspect-Buby4cQD.mjs.map +1 -0
  83. package/dist/chunks/logs-Giq5sUau.mjs +25 -0
  84. package/dist/chunks/logs-Giq5sUau.mjs.map +1 -0
  85. package/dist/chunks/migrate-DybihKzZ.mjs +33 -0
  86. package/dist/chunks/migrate-DybihKzZ.mjs.map +1 -0
  87. package/dist/chunks/models-Btp6X2YZ.mjs +24 -0
  88. package/dist/chunks/models-Btp6X2YZ.mjs.map +1 -0
  89. package/dist/chunks/pause-BASBfKLO.mjs +16 -0
  90. package/dist/chunks/pause-BASBfKLO.mjs.map +1 -0
  91. package/dist/chunks/policy-DUIgzF3W.mjs +139 -0
  92. package/dist/chunks/policy-DUIgzF3W.mjs.map +1 -0
  93. package/dist/chunks/providers-Dx78imKj.mjs +19 -0
  94. package/dist/chunks/providers-Dx78imKj.mjs.map +1 -0
  95. package/dist/chunks/reconcile-Bhy3IIYo.mjs +50 -0
  96. package/dist/chunks/reconcile-Bhy3IIYo.mjs.map +1 -0
  97. package/dist/chunks/render-DyZr5Lgi.mjs +53 -0
  98. package/dist/chunks/render-DyZr5Lgi.mjs.map +1 -0
  99. package/dist/chunks/resume-GJQRb2Eb.mjs +15 -0
  100. package/dist/chunks/resume-GJQRb2Eb.mjs.map +1 -0
  101. package/dist/chunks/retry-CLrUem1H.mjs +15 -0
  102. package/dist/chunks/retry-CLrUem1H.mjs.map +1 -0
  103. package/dist/chunks/review-BDdjb6kB.mjs +17 -0
  104. package/dist/chunks/review-BDdjb6kB.mjs.map +1 -0
  105. package/dist/chunks/run-CgTWYlcd.mjs +82 -0
  106. package/dist/chunks/run-CgTWYlcd.mjs.map +1 -0
  107. package/dist/chunks/run-tool-C3rAv4XF.mjs +26 -0
  108. package/dist/chunks/run-tool-C3rAv4XF.mjs.map +1 -0
  109. package/dist/chunks/scan-8Gy_B6kI.mjs +122 -0
  110. package/dist/chunks/scan-8Gy_B6kI.mjs.map +1 -0
  111. package/dist/chunks/schema-C9QbUW4H.mjs +578 -0
  112. package/dist/chunks/schema-C9QbUW4H.mjs.map +1 -0
  113. package/dist/chunks/send-BdnJ85Nz.mjs +19 -0
  114. package/dist/chunks/send-BdnJ85Nz.mjs.map +1 -0
  115. package/dist/chunks/ship-DTuLGoaJ.mjs +19 -0
  116. package/dist/chunks/ship-DTuLGoaJ.mjs.map +1 -0
  117. package/dist/chunks/shutdown-BM92Iyv2.mjs +15 -0
  118. package/dist/chunks/shutdown-BM92Iyv2.mjs.map +1 -0
  119. package/dist/chunks/skip-DZG5kpcH.mjs +18 -0
  120. package/dist/chunks/skip-DZG5kpcH.mjs.map +1 -0
  121. package/dist/chunks/spec-Bq0P3jVh.mjs +20 -0
  122. package/dist/chunks/spec-Bq0P3jVh.mjs.map +1 -0
  123. package/dist/chunks/src-CId6kMsY.mjs +1689 -0
  124. package/dist/chunks/src-CId6kMsY.mjs.map +1 -0
  125. package/dist/chunks/src-CfAOde7C.mjs +5403 -0
  126. package/dist/chunks/src-CfAOde7C.mjs.map +1 -0
  127. package/dist/chunks/src-CmFjh4M0.mjs +928 -0
  128. package/dist/chunks/src-CmFjh4M0.mjs.map +1 -0
  129. package/dist/chunks/src-Cu3TxNob.mjs +783 -0
  130. package/dist/chunks/src-Cu3TxNob.mjs.map +1 -0
  131. package/dist/chunks/src-DcU4IXMe.mjs +117 -0
  132. package/dist/chunks/src-DcU4IXMe.mjs.map +1 -0
  133. package/dist/chunks/src-s7YRvk0t.mjs +2116 -0
  134. package/dist/chunks/src-s7YRvk0t.mjs.map +1 -0
  135. package/dist/chunks/status-SMjiOMYy.mjs +29 -0
  136. package/dist/chunks/status-SMjiOMYy.mjs.map +1 -0
  137. package/dist/chunks/tail-BuhT-2PH.mjs +25 -0
  138. package/dist/chunks/tail-BuhT-2PH.mjs.map +1 -0
  139. package/dist/chunks/update-BXIppsbi.mjs +16 -0
  140. package/dist/chunks/update-BXIppsbi.mjs.map +1 -0
  141. package/dist/cli.mjs +175 -0
  142. package/dist/cli.mjs.map +1 -0
  143. package/package.json +17 -32
  144. package/CHANGELOG.md +0 -1461
  145. package/bin/cli.js +0 -817
  146. package/bin/report.js +0 -141
  147. package/core/adapter/render.js +0 -415
  148. package/core/agents/implementer.template.md +0 -142
  149. package/core/agents/profile-reader.md +0 -50
  150. package/core/agents/release.md +0 -58
  151. package/core/agents/review.md +0 -179
  152. package/core/commands/cohorte-align-ds.md +0 -33
  153. package/core/commands/cohorte-audit.md +0 -58
  154. package/core/commands/cohorte-brainstorm.md +0 -70
  155. package/core/commands/cohorte-build.md +0 -219
  156. package/core/commands/cohorte-doctor.md +0 -194
  157. package/core/commands/cohorte-fix.md +0 -80
  158. package/core/commands/cohorte-fleet.md +0 -103
  159. package/core/commands/cohorte-init-pipeline.md +0 -31
  160. package/core/commands/cohorte-intake.md +0 -92
  161. package/core/commands/cohorte-patch.md +0 -118
  162. package/core/commands/cohorte-refactor.md +0 -59
  163. package/core/commands/cohorte-retro.md +0 -85
  164. package/core/commands/cohorte-review.md +0 -270
  165. package/core/commands/cohorte-ship.md +0 -122
  166. package/core/commands/cohorte-spec.md +0 -101
  167. package/core/commands/cohorte-update-pipeline.md +0 -165
  168. package/core/hooks/gate.py +0 -588
  169. package/core/runtimes/claude.json +0 -74
  170. package/core/runtimes/codex.json +0 -85
  171. package/core/runtimes/cursor.json +0 -76
  172. package/core/runtimes/gemini.json +0 -76
  173. package/core/runtimes/opencode.json +0 -73
  174. package/core/templates/agent-handoff.md +0 -34
  175. package/core/templates/brainstorm-return.md +0 -36
  176. package/core/templates/decisions.template.md +0 -42
  177. package/core/templates/design-brief.md +0 -44
  178. package/core/templates/patch.template.md +0 -86
  179. package/core/templates/pr-body.md +0 -25
  180. package/core/templates/review-feedback.md +0 -38
  181. package/core/templates/spec.template.md +0 -87
  182. package/core/templates/steps/init-pipeline/01-detect-stack.md +0 -44
  183. package/core/templates/steps/init-pipeline/02-interview-gaps.md +0 -55
  184. package/core/templates/steps/init-pipeline/03-draft-profile.md +0 -10
  185. package/core/templates/steps/init-pipeline/04-write-render.md +0 -145
  186. package/core/templates/steps/init-pipeline/05-report.md +0 -12
  187. package/core/workflows/audit.js +0 -240
  188. package/core/workflows/loop.js +0 -641
  189. package/core/workflows/refactor.js +0 -276
  190. package/core/workflows/review.js +0 -456
  191. package/install.ps1 +0 -103
  192. package/install.sh +0 -114
  193. package/lib/doctor.js +0 -534
  194. package/lib/runtime.js +0 -139
  195. package/lib/versions.js +0 -149
  196. package/lib/yaml.js +0 -129
  197. package/profile/PIPELINE.template.md +0 -259
  198. package/profile/SCHEMA.md +0 -735
  199. package/profile/cohorte.config.template.yaml +0 -43
  200. package/scripts/assert-gate-hook.mjs +0 -75
  201. package/scripts/kanban-move.sh +0 -313
  202. package/scripts/metrics/collect.mjs +0 -531
  203. package/scripts/metrics/prices.json +0 -42
  204. package/scripts/new-feature.sh.template +0 -98
  205. package/scripts/preflight.sh +0 -124
  206. package/scripts/remove-feature.sh.template +0 -56
  207. package/scripts/test-adapter.mjs +0 -437
  208. package/scripts/test-gate.mjs +0 -443
  209. package/scripts/test-kanban.mjs +0 -304
  210. package/scripts/test-lib.mjs +0 -356
  211. package/scripts/test-metrics.mjs +0 -156
  212. package/scripts/test-workflows.mjs +0 -758
  213. package/scripts/validate-core.mjs +0 -325
package/profile/SCHEMA.md DELETED
@@ -1,735 +0,0 @@
1
- # PIPELINE.md profile — field reference
2
-
3
- `/cohorte-init-pipeline` fills the `yaml pipeline-profile` block in `PIPELINE.md` (from
4
- `PIPELINE.template.md`) plus the prose sections. This documents every field and how the
5
- generic pipeline uses it, so a stateless agent can read/regenerate the profile correctly.
6
-
7
- ## `yaml pipeline-profile` block
8
-
9
- | Field | Type | Used by | Meaning |
10
- | ------------------------------------ | ------------ | --------------------------------- | ------------------------------------------------------------- |
11
- | `name` | string | all | Project name, used in agent prose + commit scopes. |
12
- | `one_liner` | string | brainstorm/spec | One-sentence product description. |
13
- | `ui_language` | string | implementer, review | Language of ALL user-facing copy. |
14
- | `package_manager` | enum | all | `pnpm`/`npm`/`yarn`/`bun`/`pip`/`cargo`/`go`. |
15
- | `vcs.host` | enum | release | `github`→use `gh`; else emit compare URL. |
16
- | `vcs.remote` | string | release | `owner/repo` for the PR/compare URL. |
17
- | `vcs.default_branch` | string | build, review, release | Base branch for diffs + PRs. |
18
- | `vcs.feature_branch_prefix` | string | ship, isolation script | `feature/` → branch `feature/<id>`. |
19
- | `vcs.patch_branch_prefix` | string | ship | Same, for a `kind: patch` spec: `fix/` → branch `fix/patch-<slug>`. Optional — a profile that predates it falls back to `fix/`. |
20
- | `repo.layout` | enum | build, audit | `monorepo` (many surfaces) or `single`. |
21
- | `repo.workspace_tool` | enum | audit | `turborepo`/`nx`/`none`. |
22
- | `retrieval.provider` | enum | init, update-pipeline, implementer | `serena` (default) / `graphify` / `none` — see §Code retrieval. |
23
- | **`surfaces[]`** | list | **build, review, refactor, init** | One per independently-built area. Grows via reconcile (below). |
24
- | `surfaces[].key` | string | build | Short id + review scope. |
25
- | `surfaces[].path` | string | implementer | The ONLY tree that surface's agent may touch. |
26
- | `surfaces[].label` | string | build, init (`<SURFACE_LABEL>`) | Human label + framework, e.g. `frontend (React)`. |
27
- | `surfaces[].agent` | string | build (`subagent_type`) | Rendered agent file name. |
28
- | `surfaces[].tools` | list | init | Frontmatter `tools:` for the rendered agent. |
29
- | `surfaces[].model` | enum | init (`<SURFACE_MODEL>`) | Frontmatter `model:` tier — `sonnet`/`haiku`/`inherit`. Default `sonnet` (implementers mostly apply a frozen contract — far cheaper than the Opus lead the dispatcher runs on, and Sonnet handles it well); `haiku` for purely mechanical surfaces (scaffolding); `inherit` only for surfaces with real design decisions worth the lead's model. |
30
- | `surfaces[].*_cmd` | string | implementer | test/lint/format/typecheck/build commands. |
31
- | `surfaces[].test_quiet_cmd` `.lint_quiet_cmd` | string | implementer, preflight, workflows | Bridled variants agents actually run (dot reporter / `--quiet` / failures-only). `""` ⇒ `<cmd> 2>&1 \| tail -40`. See §Output discipline. |
32
- | `surfaces[].uses_design` | bool | build, frontend | Whether this surface consumes designs. |
33
- | `contract.enabled` | bool | build | `false` ⇒ skip contract authoring (§2 of /cohorte-build). |
34
- | `contract.mechanism` | enum | build, lead | `shared-types-zod`/`openapi`/`protobuf`/`json-schema`/`none`. |
35
- | `contract.path` `.ext` `.index` | string | build | Where `<feature_id>` contract is authored + barrel. |
36
- | `contract.authored_by` | const `lead` | build | Implementers import it read-only, never edit. |
37
- | `release_notes.enabled` | bool | ship | `false` ⇒ skip note authoring (§2b of /cohorte-ship). See §Release notes. |
38
- | `release_notes.tool` | enum | ship | `changesets`/`none` — what consumes the file. |
39
- | `release_notes.dir` `.filename` | string | ship | Where the per-feature note is authored, e.g. `.changeset/<feature_id>.md`. |
40
- | `release_notes.anchor_package` | string | ship | Sole key in the note's front-matter; a lockstep/`fixed` group propagates the bump. |
41
- | `release_notes.language` | string | ship | Language of the note's prose — usually `ui_language`. |
42
- | `release_notes.forbid_levels` | list | ship | Bump levels project policy refuses, e.g. `[major]` while `0.x`. |
43
- | `release_notes.empty_cmd` | string | ship | Escape hatch when no version should move; `""` if none. |
44
- | `release_notes.ci_job` | string | ship | CI job that fails on a missing note — lets §5 name the red check. |
45
- | `release_notes.guidance` | string | ship | Project policy the lead follows when picking the bump + writing the prose. |
46
- | `commands.*` | string | all | Repo-wide install/dev/lint/format/typecheck/test + migrate. |
47
- | `commands.test_quiet` `.lint_quiet` | string | review, audit, workflows | Repo-wide bridled variants — what the `/cohorte-review` pre-flight runs. Same fallback as the per-surface ones. |
48
- | `rbac.enabled` | bool | brainstorm, review | Toggle RBAC personas + authz audit. |
49
- | `rbac.hierarchy` | list | review | Highest→lowest role list. |
50
- | `design.enabled` | bool | build, frontend, align-ds | `false` ⇒ design steps are no-ops. |
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. |
53
- | `design.design_system_project` | id | align-ds, frontend | UI-kit source of truth. |
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). |
55
- | `design.snapshot_dir` | path | align-ds | Committed DS snapshot for diffing. |
56
- | `design.ui_kit_path` `.tokens_path` | path | align-ds, frontend | Where the kit + tokens live in code. |
57
- | `isolation.enabled` | bool | new-feature script | `false` ⇒ build in main checkout. |
58
- | `isolation.db_per_worktree` | bool | new-feature script | Create `<name>_<id>` DB per worktree. |
59
- | `isolation.db_name_pattern` | string | new-feature script | `<name>_<id>`. |
60
- | `isolation.port_base` | map | new-feature script | `api`/`web` base ports; +slot per worktree. |
61
- | `isolation.compose_file` `.registry` | path | new-feature script | Docker stack + slot registry. |
62
- | `gate.deny[]` | list | hooks/gate.py, settings | Command substrings hard-denied, on any branch. |
63
- | `gate.ask[]` | list | hooks/gate.py, settings | Command substrings that require confirm, on any branch. |
64
- | `gate.ask_on_default_branch[]` | list | hooks/gate.py | Confirm ONLY on `default_branch`; free on feature branches. |
65
- | `gate.default_branch` | string | hooks/gate.py | Protected branch (default `main`); gate resolves via git. |
66
- | `gate.preflight.enabled` | bool | hooks/gate.py, review | Phase gate: review dispatches need a fresh preflight stamp. See §Preflight. |
67
- | `gate.preflight.agents[]` | list | hooks/gate.py | `subagent_type`s the stamp gates (default `[review]`). |
68
- | `gate.preflight.max_age_minutes` | number | hooks/gate.py | Stamp freshness window (default 30). |
69
-
70
- ## Prose sections
71
-
72
- - **Conventions** — per-surface rules the implementer follows and review audits.
73
- - **Testing** — the TDD contract per surface (what a test must cover, DB isolation).
74
- - **Design brief note** — feeds `/cohorte-spec` §8 and the Claude Design step.
75
- - **Personas** — the `/cohorte-brainstorm` panel; include one per RBAC role when `rbac.enabled`.
76
-
77
- ## How the pieces reference this file
78
-
79
- - **Agents** (`implementer`, `review`, `release`) are told at dispatch: _read `PIPELINE.md`
80
- §Commands / §Conventions / §Surfaces first._ They have `Read`, so they load it live.
81
- - **Commands** (`/cohorte-build`, `/cohorte-review`, …) parse the `yaml pipeline-profile` block to know how
82
- many surfaces to dispatch, the contract mechanism, the commands, and the capability flags.
83
- - **Hook** (`gate.py`) reads `gate.deny`/`gate.ask`/`gate.ask_on_default_branch`/`gate.default_branch`
84
- from a generated `<state>/gate-config.json`. The last two make git + docker free on feature branches
85
- but confirm-gated on the default branch (branch resolved at run time via `git rev-parse`).
86
- - **Scripts** (`new-feature.sh`) read the `isolation` block (rendered in at init).
87
-
88
- ## Code retrieval — `retrieval.provider`
89
-
90
- Agents spend most of their wall-clock reading the repo; a retrieval provider replaces grep-and-read
91
- with symbol/graph queries. The flag is a **value, not a boolean**, so switching provider later is a
92
- one-line profile change + re-running the wiring (no agent re-render needed — the guidance agents
93
- follow is provider-agnostic: _"prefer the retrieval MCP tools over Grep/Glob + whole-file Reads"_).
94
-
95
- | Provider | Mechanism | Freshness | Cost |
96
- | --- | --- | --- | --- |
97
- | `serena` (default) | live LSP symbol navigation (find symbol, references, semantic edits) | always current | none — no index |
98
- | `graphify` | persistent tree-sitter knowledge graph over code + docs | as fresh as the last rescan | index step + re-index discipline |
99
- | `none` | agents fall back to Grep/Glob/Read | — | — |
100
-
101
- **Wiring (done by `/cohorte-init-pipeline`, or `/cohorte-update-pipeline` retroactively):**
102
-
103
- <!-- cohorte:if runtime:codex -->
104
- For `serena`, install its CLI if missing (`uv tool install -p 3.13 serena-agent`), then merge
105
- this project-scoped table into `.codex/config.toml`, preserving all existing settings:
106
-
107
- ```toml
108
- [mcp_servers.serena]
109
- command = "sh"
110
- args = ["-c", 'exec "$(command -v serena || echo "$HOME/.local/bin/serena")" start-mcp-server --context codex --project-from-cwd --open-web-dashboard False']
111
- ```
112
-
113
- On Windows without `sh`, use `command = "serena"` and the server arguments directly; ensure
114
- the CLI is on PATH. Gitignore `.serena/`. Keep `CODEX_HOME` at its normal user location;
115
- the project table is discovered natively once the project is trusted.
116
- For `graphify`, install its CLI and build/update the graph according to the provider's instructions;
117
- verify any required MCP registration in `.codex/config.toml` rather than `.mcp.json`.
118
-
119
- **Health check:** verify (1) `command -v serena`, (2) the `[mcp_servers.serena]` table,
120
- (3) `.serena/` ignored, (4) actual tools in the session. `codex mcp list` inspects registration,
121
- but is not proof of a live connection; restart the session when needed and report that limitation.
122
- Codex agents inherit MCP configuration; do not write a Claude `tools:` allowlist.
123
- Teammates receive `.codex/config.toml` and need the provider CLI installed and the project trusted.
124
- <!-- cohorte:else -->
125
- - `serena` — requires the `serena` CLI (`uv tool install -p 3.13 serena-agent`). For day-to-day CLI
126
- use it should also be on PATH (`uv tool update-shell`; uv installs to `~/.local/bin`). Register at
127
- **project scope** so the registration is committed and portable (`--project-from-cwd` resolves the
128
- project at server start, so the committed entry works on every machine) — and register the
129
- **PATH-proof launcher**, not the bare command: Claude Code spawns MCP servers with whatever
130
- environment it was launched from (a stale terminal, a GUI/IDE launch that never sourced a shell
131
- profile), where `~/.local/bin` may be missing from PATH — a bare `serena` entry then dies with
132
- ENOENT and agents silently fall back to Grep/Read:
133
-
134
- ```sh
135
- claude mcp add --scope project serena -- sh -c 'exec "$(command -v serena || echo "$HOME/.local/bin/serena")" start-mcp-server --context claude-code --project-from-cwd --open-web-dashboard False'
136
- ```
137
-
138
- (Windows-native teams: no `sh` — register the bare `serena` form instead and ensure the uv tools
139
- dir is on PATH; keep the `--open-web-dashboard False` flag.) `--open-web-dashboard False` keeps the
140
- dashboard available (reachable at `http://localhost:24282/dashboard/`) but stops it popping a browser
141
- tab on every server start — the flag overrides the machine's `serena_config.yml`, so the behaviour is
142
- the same for everyone on the repo. Gitignore `.serena/` (per-machine cache/config). Optionally
143
- pre-index large repos once: `serena project index`.
144
- - `graphify` — requires `uv tool install graphify` + `graphify install`; build the initial graph
145
- (`/graphify .`) and rescan incrementally after big changes (`--update`). See graphify.net.
146
- - Rendered agents get the provider's MCP tools appended to their `tools:` list (e.g. `mcp__serena`
147
- grants the whole server); `none` ⇒ nothing appended.
148
-
149
- **Serena health check** — run after wiring in `/cohorte-init-pipeline` AND on every `/cohorte-update-pipeline`
150
- reconcile (wiring that worked once can rot: PATH changes, tool uninstalled, entry hand-edited):
151
-
152
- 1. **CLI resolves:** `command -v serena`. Fails but `~/.local/bin/serena` exists ⇒ PATH repair
153
- above; missing entirely ⇒ reinstall.
154
- 2. **Registered:** this repo's `.mcp.json` has the `serena` entry ⇒ else re-run the `claude mcp add`.
155
- If the entry is the bare `serena` form on a POSIX machine, upgrade it to the PATH-proof launcher
156
- above (immune to launch-environment PATH gaps). If a launcher entry predates the
157
- `--open-web-dashboard False` flag, append it so the dashboard no longer auto-opens a browser tab.
158
- 3. **Gitignored:** `.serena/` is in `.gitignore` ⇒ else append it.
159
- 4. **Actually connected:** the `mcp__serena` tools are exposed in the session (or `claude mcp list`
160
- shows serena connected). If 1–3 pass but this fails, a session restart is needed — say so
161
- explicitly instead of reporting success.
162
-
163
- Report each check's result; never report Serena "wired" on registration alone.
164
-
165
- Teammates cloning the repo get the committed `.mcp.json` and only need the provider CLI installed
166
- and on PATH — if either is missing, the MCP server fails to start and agents silently fall back to
167
- Grep/Read; the health check above is the diagnostic.
168
- <!-- cohorte:endif -->
169
-
170
- ## Specialization — when to split one surface into more agents
171
-
172
- `/cohorte-build` dispatches ONE agent per surface, in parallel, so build wall-clock ≈ the **slowest single
173
- surface**. More agents only build faster when they let the *slowest* surface's work run concurrently —
174
- and only if the split is safe. The invariant that keeps parallelism safe is **one owner per tree, and
175
- the frozen contract as the only cross-surface channel**. So specialization means carving a surface into
176
- **smaller non-overlapping surfaces**, never pointing two agents at the same tree.
177
-
178
- **Split a surface into specialized sub-surfaces only when BOTH hold:**
179
-
180
- 1. **It's a bottleneck** — the surface is large (many modules / high LOC) and dominates build time.
181
- 2. **The boundary is clean** — its work partitions into trees that don't share files, e.g. feature
182
- modules (`src/features/*`, `src/modules/*`), route groups, or independent services (`services/*`).
183
-
184
- **Rules when splitting (non-negotiable — they preserve the invariant):**
185
-
186
- - **Shared code gets its own surface with a single owner.** Anything two slices both touch — routing,
187
- global state/store, the design-system kit + tokens, shared utils — becomes its OWN surface (e.g.
188
- `web-shared`), owned by exactly one agent. Never let two feature-slice agents both edit shared trees.
189
- - **Cross-slice references go through the contract**, not direct imports between slice trees. If
190
- `web-checkout` needs a shape produced by `api-billing`, that shape lives in the frozen contract.
191
- - **Don't over-split.** A slice too small to hold ≥1 real task, or one with tangled boundaries, is worse
192
- than not splitting — the coordination + token cost (each stateless agent re-reads `PIPELINE.md` + spec)
193
- outweighs the parallelism. When boundaries aren't clean, keep one surface.
194
-
195
- Coarse first, specialize on evidence: start with one `frontend` / `backend` surface each; split only a
196
- surface that's proven slow and cleanly separable. The evidence lives in
197
- the **main checkout's** `<state>/pipeline-metrics.jsonl` (gitignored) — one JSONL line per phase batch
198
- (`ts`/`feature`/`phase`/`seconds`/`surfaces:{key: result}`), appended by `/cohorte-build`, `/cohorte-review`
199
- and `/cohorte-fix`.
200
- **`surfaces` keys are surface keys, nothing else** — run-level facts go in their own top-level
201
- fields. Anything put inside `surfaces` is read
202
- as a surface: `cohorte doctor` renders it as a row in the per-surface table and scores a non-`ok`
203
- value as that surface failing. Always the main checkout, never the feature worktree (which dies at teardown while
204
- metrics must accumulate across features) — resolve from anywhere with
205
- `$(dirname "$(git rev-parse --git-common-dir)")/<state>/pipeline-metrics.jsonl`. Read it before
206
- proposing a split: split the surface that actually dominates wall-clock, not the one that feels big.
207
-
208
- ## Measuring cost — what's slow vs what's expensive
209
-
210
- `pipeline-metrics.jsonl` records **wall-clock seconds** per phase batch (§Specialization) — it tells you
211
- what's SLOW. Tokens are recorded only where they can be read honestly: the **workflow paths**
212
- (`loop.js`, `review.js`) stamp an approximate `tokens` field per batch from the runtime's own
213
- counter (`budget.spent()` deltas), and the loop's return carries a per-round breakdown in its
214
- `history`. The **conversational** commands still record none — a lead cannot reliably read a
215
- subagent's token count, and a guessed number is worse than a missing one. `cohorte metrics` sums
216
- whatever is stamped (a token-less line aggregates as 0, rendered as absent, never as "free").
217
- For exact spend, use Claude Code's own accounting:
218
-
219
- - **`/cost`** (built-in, zero setup) — reports per-**subagent** and per-**slash-command** share of your usage
220
- over the last 24 h / 7 d (e.g. _"Top subagents: frontend 7 %, backend 4 % · Top skills: /cohorte-build 1 %,
221
- /cohorte-review 1 %"_). That IS the per-phase ledger — approximate (share-of-total, machine-local, not exact
222
- tokens). Read it to see which surface/command actually dominates the bill before you tune a `model` tier.
223
-
224
- **Lead context discipline — the silent bill.** The lead session's conversation history is re-sent as
225
- input on EVERY turn; a session that spans spec→build→review→fix without clearing re-pays the
226
- accumulated spec walk-through, handoffs, and reports on each turn. The pipeline is built so this is
227
- never necessary: every phase handoff (spec, contract, diff, staged reports) lives on disk, so `/clear`
228
- at each phase boundary is always safe — each command's closing line recommends it. Corollaries the
229
- commands enforce: never paste a diff into a dispatch (agents compute their own, scoped); never echo a
230
- staged report or design brief into chat; redirect bulky command output to a file and grep it.
231
-
232
- ## Output discipline — quiet commands
233
-
234
- A test runner's default output is written for a human watching a terminal: one line per test, banners,
235
- timing tables. An agent pays input price for every one of those lines, on every turn they survive in its
236
- context. The profile therefore stores **two forms of each noisy command**:
237
-
238
- - `test_cmd` / `lint_cmd` — the full form, for a human running it by hand.
239
- - `test_quiet_cmd` / `lint_quiet_cmd` (per surface) and `commands.test_quiet` / `commands.lint_quiet`
240
- (repo-wide) — the **bridled** form agents actually execute: dot/failures-only reporter
241
- (`--reporter=dot`, `--quiet`, `-q`, `--silent`, framework equivalent) so a green run costs lines,
242
- not pages, and a red run prints only the failures.
243
-
244
- Rules for every consumer (implementers, preflight, `/cohorte-audit` gates, workflow agents):
245
-
246
- 1. Run the quiet variant when set.
247
- 2. Quiet variant empty/absent (older profile) ⇒ run `<full cmd> 2>&1 | tail -40` — never the bare
248
- command into your context.
249
- 3. Need the full log? Redirect it to a file and grep it; never print it.
250
-
251
- `/cohorte-init-pipeline` **asks** for these variants (detected defaults offered first) instead of silently
252
- storing a bare `pnpm test` as the thing agents execute; `/cohorte-update-pipeline` tops up older profiles.
253
-
254
- ## Spec status — the lifecycle state machine
255
-
256
- A spec's front-matter `status` is not a label, it is the pipeline's **state**: every command routes on
257
- it, `cohorte specs` boards on it, and the kanban backfill maps it to a column. Six states:
258
-
259
- | status | meaning | written by | who may build it |
260
- | --- | --- | --- | --- |
261
- | `draft` | the interview is open, nothing is frozen | `/cohorte-spec` Mode A | no |
262
- | `frozen` | the contract is frozen — the handoff to `/cohorte-build` | `/cohorte-spec` Mode A freeze | yes |
263
- | `in-progress` | a round is under way on this spec (or died mid-way) | an automated driver, if any | yes |
264
- | `in-review` | reviewed / awaiting the next round or `/cohorte-ship` | `/cohorte-spec` Mode B, `/cohorte-fix` | yes |
265
- | `blocked` | a round gave up here (non-convergent, no verdict, not implementable) | an automated driver, if any | yes, with the reason named |
266
- | `shipped` | the PR is open; the status flip is part of the release commit | `/cohorte-ship` | no |
267
-
268
- **`in-progress` and `blocked` are driver states.** No conversational command writes them — the
269
- human-driven cycle moves `frozen` → `in-review` → `shipped`. Their producer is the **loop
270
- workflow** (`core/workflows/loop.js`, `/cohorte-loop` — the successor of the 2.2.0-retired
271
- conversational driver): it stamps `in-progress` at each round, `in-review` when a run ends at
272
- zero blocking findings, and `blocked` when a round gives up (non-convergent, unreviewed
273
- surfaces, dead implementers, a contract-change finding), with the reason in
274
- `specs/reports/<id>.loop.json`. External drivers may write them too. Every reader routes on
275
- them either way, and a stamp on a spec with no front-matter stays a silent no-op — no driver
276
- dies over a status line.
277
-
278
- **`kind` — feature (default) or `patch`.** Orthogonal to `status`, and the only other front-matter
279
- field commands route on. `/cohorte-patch` freezes `specs/patch-<slug>.md` with `kind: patch` from
280
- `templates/patch.template.md`: a ~60-line bug spec whose §4 **regression test** replaces §5 CONTRACT
281
- as the thing the diff is checked against. It moves through the same states and the same commands —
282
- `/cohorte-build` → `/cohorte-review` → `/cohorte-fix`* → `/cohorte-ship` — which is the whole design:
283
- a patch is a spec, so nothing downstream is special-cased beyond three lines.
284
-
285
- | what reads `kind: patch` | what it does differently |
286
- | --- | --- |
287
- | `/cohorte-build` §1.6 | judges §1 repro + §4 regression test instead of contract completeness; gap check `repro` |
288
- | `/cohorte-build` §2 | authors no contract when §5 Contract delta is `none` (the usual case) |
289
- | `/cohorte-ship` §1/§2b/§3 | branches off `vcs.patch_branch_prefix`; a `patch` bump by default; `fix(<scope>)` commit |
290
-
291
- A patch may span **several surfaces** — one bug, one repro, one spec. What it may never do is add
292
- **new** contract surface area: two surfaces agreeing on a shape that doesn't exist yet is what §5 is
293
- for, so `/cohorte-patch` routes that to `/cohorte-spec` instead. Changing an *existing* contract entry
294
- is a legitimate delta. The patch template keeps contract on **§5** and acceptance on **§9** — the two
295
- numbers `review.md` and `implementer.template.md` name verbatim — and simply has no §8.
296
-
297
- Corollaries worth knowing:
298
-
299
- - A spec with no front-matter makes every stamp a **silent no-op** — the state is bookkeeping, and the
300
- loop must never die over a status line.
301
- - Child commands write `status` too (`/cohorte-fix` sets `in-review`); re-stamping before each phase is what
302
- keeps `in-progress` true for the duration of the run rather than for its first phase.
303
- - `blocked` is not a failure to hide: it is the resumable state. `/cohorte-build` accepts it, names it, and
304
- routes by the spec's `## Remediation` (open items ⇒ `/cohorte-fix`).
305
-
306
- ## Dead agents — silence is not a green light
307
-
308
- A subagent can die mid-run: a rate limit, a transport error that outlived its retries, its own context
309
- exhausted on a big surface. When it does it returns **nothing** — and nothing is byte-identical to
310
- "finished, nothing to report". Every phase that fans out therefore does a **roll call** before it
311
- integrates anything, because the default reading of silence is the most dangerous one available:
312
-
313
- | phase | what a dead agent looks like | what the phase must do |
314
- | --- | --- | --- |
315
- | `/cohorte-build` | a surface with no handoff | retry it **once** alone (byte-identical prompt), then mark it `dead`, verify the tree with that surface's own quiet commands, never call the batch ok |
316
- | `/cohorte-review` | a reviewer with no report ⇒ **zero findings** | retry once, then list the surface in `unreviewed` and refuse to score `SHIP` |
317
- | `/cohorte-fix` | a re-dispatched agent with no handoff | retry once, then leave **every** one of its items `- [ ]` — a dead agent never ticks a box |
318
- | workflows | `agent()` resolves to `null` | already enforced (`review.js` `unreviewedSurfaces`) — the doctrine started here |
319
-
320
- Non-negotiables, in every phase:
321
-
322
- - **Retry once, alone, byte-identical.** Most deaths are transient, and the other surfaces' work is
323
- already on disk — so recovery costs one agent, never a rebuild. Never retry an agent that answered.
324
- - **Never speak for a dead agent.** You did not see its work: report what the *tree* says (quiet
325
- commands, redirected to a file, grepped), not what a handoff would have said.
326
- - **Never let it reach a driver as clean.** `/cohorte-build` writes `dead[]` into
327
- `specs/reports/<id>.build.json`, `/cohorte-review` writes `unreviewed[]` into the verdict; a driver
328
- must abort on either *before* it reads `blocking`, since a dead reviewer makes
329
- `blocking == 0` a statement about code nobody read.
330
- - **`unreviewed` is separate from `blocking` on purpose.** Faking a count in `blocking` to force a
331
- driver's hand would corrupt the one field the whole contract rests on; a driver reads them as two
332
- different facts — "what was found" and "what was covered".
333
- - **Write the metrics line anyway** (`"<key>":"dead"`). An incomplete batch is exactly the batch worth
334
- recording; holding the append back "until it's complete" deletes the evidence that anything failed.
335
-
336
- ## Readiness — the gate between a frozen spec and N implementers
337
-
338
- `/cohorte-build` §1.6 scores the frozen spec on **implementability** before authoring the contract and before
339
- dispatching anything, and writes `specs/reports/<id>.readiness.json`
340
- (`verdict`: `READY` · `RESERVATIONS` · `NOT-READY`, plus `gaps[]`). It costs **zero extra agents** — the
341
- lead already holds the spec, the profile and the reconciled surface list — which is the whole economics
342
- of the step: a spec that cannot be built does not get cheaper by being built on N surfaces in parallel.
343
-
344
- - Five checks: contract completeness · surface coverage · dependencies exist · residual ambiguity ·
345
- the design gate. Each maps to `NOT-READY` (a surface would have to invent the answer) or
346
- `RESERVATIONS` (a surface can proceed on a stated assumption).
347
- - **`NOT-READY` aborts the build with no agent spawned** and sends the human to `/cohorte-spec`.
348
- A driver reads the same file and must stop rather than retry — it is the one outcome more passes
349
- cannot fix.
350
- - **`RESERVATIONS` never blocks.** Each gap is inlined verbatim into the dispatch of the surface it
351
- affects, as an assumption the implementer must apply *and* flag in its handoff. A gate that stalled a
352
- sound build on a missing error case would cost more human round-trips than it saves.
353
-
354
- ## Deferred findings — real, but not this feature's problem
355
-
356
- `/cohorte-review` ends on "zero blocking findings", so everything non-blocking used to be discarded with the
357
- report. A **deferred** finding is one the reviewer judges true and **out of this feature's scope**
358
- (pre-existing code the staged diff never touched, adjacent debt the spec never claims to fix). The
359
- review agent returns them in their own `## Deferred` section — never in `findings` — each carrying its
360
- own out-of-scope reason.
361
-
362
- - They count in **no** severity row, enter **no** verdict, and are **never** cross-checked: a deferred
363
- item cannot cost a fix loop an iteration, and refuting one would spend an agent arguing about
364
- something that cannot change the outcome.
365
- - **Not deferrable, ever:** anything the diff touched or introduced, any spec violation, any security
366
- issue on a path this feature adds, calls or modifies.
367
- - `/cohorte-review` §3.5 routes them, **on every verdict**, into `specs/refactor-backlog.md` under the
368
- `## <domain>` heading of the owning surface, tagged `deferred:<feature_id>` — the same grouping
369
- `/cohorte-audit` writes, so `/cohorte-refactor <domain>` picks them up with no extra plumbing. Never into the spec's
370
- `## Remediation`, which is what `/cohorte-fix` re-dispatches.
371
- - `/cohorte-audit` **carries open `deferred:` items over** when it rewrites the backlog; overwriting them away
372
- is the one way they silently vanish.
373
- - The verdict JSON carries `deferred: <n>` (informational, outside `blocking`), so a driver can name
374
- them in its closing line without reading a report.
375
-
376
- ## Decisions — the transverse decision journal
377
-
378
- `PIPELINE.md` is a **stack profile** (surfaces, commands, conventions); it says nothing about what this
379
- project has *decided*. Without somewhere for those, every `/cohorte-spec` re-discovers or contradicts them.
380
- `specs/_decisions.md` (from `core/templates/decisions.template.md`) is that place, deliberately small:
381
-
382
- - **Append-only, one line per decision, ≤ ~160 chars:**
383
- `- <YYYY-MM-DD> · <area> · <decision> — because <reason> · <origin>`, where `<origin>` is the
384
- `feature_id` that decided it — or the originating command (`retro`) when no single feature owns
385
- it. Reversal never edits a line:
386
- append a superseding one (`· supersedes <date> <area>`) and move the old one to `## Superseded`. When
387
- `## Live` passes ~100 lines, sweep the superseded ones down.
388
- - **Written by** `/cohorte-spec` at freeze (the decisions that outlive the feature — typically 0–3 lines, and
389
- zero is a normal outcome), `/cohorte-build` §1.5 when it adds or splits a surface, and
390
- `/cohorte-retro` §4 when the human ratifies a convention rule (one line per adopted rule).
391
- - **Read by the deciding stages only** — `/cohorte-brainstorm` (so the panel argues about the idea, not about
392
- settled ground), `/cohorte-spec` (so a new spec does not silently un-decide something), `/cohorte-audit` (standing
393
- decisions are part of the rulebook it audits against).
394
- - **Never read by implementers or reviewers.** They work from the frozen contract, which already tells
395
- them what to do; shipping them the rationale would cost `surfaces × dispatches` tokens per feature
396
- for a fact they cannot act on. This is what keeps the journal cheap enough to be worth having.
397
- - The `_` prefix is load-bearing: `/cohorte-doctor`, the `cohorte specs` scanner and the kanban backfill all skip
398
- `specs/_*.md`, so the journal is never mistaken for a spec (no phantom card, no bogus stage).
399
-
400
- ## Preflight — the deterministic phase gate
401
-
402
- `/cohorte-review` starts by running `pipeline/scripts/preflight.sh` — a plain shell script (no
403
- agent) that executes the profile's mechanical checks in order (typecheck → lint → tests, quiet
404
- variants) with all output redirected to `specs/reports/<id>.preflight.txt`:
405
-
406
- - **Any check red** ⇒ the script prints the last 40 lines raw and exits 1. The command **aborts
407
- there: zero agents are spawned.** A reviewer dispatched onto code that doesn't compile burns its
408
- whole run rediscovering what `tsc` already printed for free — the failure goes straight to the
409
- human (or `/cohorte-fix`) instead.
410
- - **All green** ⇒ the script stamps `<state>/preflight.ok` (`<epoch> <HEAD sha> <tree digest>` —
411
- local and **gitignored**; a versioned stamp describes the tree *before* its own commit and rides
412
- into every clone and worktree, which breaks the gate both ways).
413
-
414
- `hooks/gate.py` enforces the stamp as a **phase gate** (the `preflight` block of `gate-config.json`,
415
- generated from `gate.preflight`): a Task dispatch of a listed `subagent_type` (default
416
- `review`) with a missing/stale stamp — older than `max_age_minutes`, or the digest no longer
417
- matches the working tree (`.claude`, `.cohorte` and `specs` excluded, so the pipeline's own writes and a
418
- commit of already-verified code do not invalidate it) — gets an
419
- "ask", so a lead can't accidentally skip the gate but a human can consciously override it. The gate
420
- hook fires for **every** agent in the session, including subagents spawned by the Workflow runtime
421
- (they run in `acceptEdits` whatever the session mode — Write/Edit auto-approved — but Bash and Task
422
- still pass through hooks). In `bypassPermissions` (headless runs) every gate "ask" is escalated to a
423
- hard deny, because nobody is there to answer a prompt.
424
-
425
- ## Rendering / reconciling a surface agent (shared procedure)
426
-
427
- Both `/cohorte-init-pipeline` (initial render) and `/cohorte-build` (auto-reconcile when a spec needs a new agent) use
428
- this exact procedure so a surface is always defined the same way. To add surface `S`:
429
-
430
- 1. **Add the `surfaces[]` entry** to `PIPELINE.md`: `key`, `path` (the disjoint tree it exclusively
431
- owns), `label`, `agent` (rendered file name), `tools` (add `DesignSync` only if `uses_design: true`;
432
- append the retrieval provider's MCP tools when `retrieval.provider` ≠ `none` — e.g. `mcp__serena`),
433
- `model` (tier for the rendered agent: `sonnet` (default) — the implementer mostly applies a frozen
434
- contract, which Sonnet does well at a fraction of the Opus-lead cost; `haiku` for purely mechanical
435
- scaffolding; `inherit` only when the surface makes real design decisions worth the lead's model),
436
- the five `*_cmd`s (derive from the surface's `package.json` / workspace
437
- filter, mirroring a sibling surface), and `uses_design`.
438
- <!-- cohorte:if runtime:codex -->
439
- **Codex model policy:** use `model: inherit` by default, or a model explicitly selected for
440
- Codex. Legacy `sonnet`/`haiku` values are not executable Codex pins: omit them in the rendered
441
- agent and report inheritance. Preserve explicit Codex `model`/`model_reasoning_effort` choices.
442
- <!-- cohorte:endif -->
443
- 2. **Render the agent file** `<agents>/<agent>.md` from `<core>/pipeline/implementer.template.md`
444
- — the template is already rendered for this runtime, so only the placeholders are yours to fill —
445
- substituting `<SURFACE_AGENT>`, `<SURFACE_LABEL>`,
446
- `<SURFACE_PATH>`, `<SURFACE_TOOLS>`, `<SURFACE_MODEL>`, `<PROJECT_NAME>`, and the surface-specific
447
- blocks (`<SURFACE_EXTRA_NEVER>`, `<SURFACE_DESIGN_INPUT>`, `<SURFACE_TDD_STEP1>` — leave the design
448
- ones empty unless `uses_design`). Fill `<SURFACE_CONVENTIONS>` with the surface's convention slice
449
- **baked at render time**: `PIPELINE.md` §Conventions `### Shared` + this surface's
450
- `### Surface: <key>` stanza + its §Testing lines, verbatim. At runtime the agent then reads only
451
- the profile's machine block (the fenced `yaml pipeline-profile`) — never the prose sections. The
452
- bake stays honest because §Conventions edits go through `/cohorte-update-pipeline`, whose reconcile
453
- re-renders every agent (step 2 below); hand-edit the prose without re-rendering and the baked
454
- slice goes stale — that's the trade for not re-reading the prose on every dispatch. For a `uses_design` surface, fill them **link-based** (never with a
455
- stored `design_project` id — that goes stale on a DS rebuild):
456
- - `<SURFACE_DESIGN_INPUT>` — a 4th input bullet: _"The **feature design** — the pages this feature
457
- touches, listed in your dispatch's design slot as full links
458
- (`https://claude.ai/design/p/<projectId>?file=<file>`); a slot saying `none` means a fix loop with
459
- no visual work — skip DesignSync entirely. For each link, extract the `<projectId>` (the
460
- `/p/…` segment) and `<file>` (the `?file=` query) from the URL and read it read-only via `DesignSync
461
- get_file(<projectId>, <file>)`; `list_files(<projectId>)` to catch linked pages (shared nav/modals)
462
- this feature also changes. The link is self-contained — no stored project id. Build with the code UI
463
- kit (the `design_system_project`'s materialization: `@/components/ui/*` + tokens); read a primitive
464
- via `get_file` only if it's missing/stale in code. Mobile-first."_
465
- - `<SURFACE_TDD_STEP1>` — a **lead-in paragraph** above the TDD list (not a numbered item; it
466
- renders as nothing for a non-design surface, which is why the list must not start at it):
467
- _"**Pull the feature design first** (skip if your dispatch's design slot
468
- says `none`): `DesignSync get_file(<projectId>, <file>)` for each link in the slot and translate
469
- each into the code design system (`@/components/ui/*`, `cn()` + CVA), mobile-first — never ad-hoc
470
- CSS. Then:"_
471
- <!-- cohorte:if runtime:codex -->
472
- **Codex destination and format:** always write `.codex/agents/<agent>.toml` in the current
473
- project, including with a global core. The source template has a `.md` filename but contains
474
- TOML for this runtime. Validate TOML after substitutions; keep `name`, `description`, and
475
- `developer_instructions`. Do not add Claude `tools:`/`model: sonnet` frontmatter.
476
- Keep generic agents under `<fixed-agents>/`; never write surface agents there in global mode.
477
- No `CODEX_HOME` override, auth symlink or per-project launcher is needed. When migrating an
478
- old global surface agent, compare ownership/content with this project's profile before
479
- removing its old copy; do not overwrite local customizations or touch other projects' agents.
480
- <!-- cohorte:endif -->
481
- 3. **Add a §Conventions + §Testing stanza** for `S` in `PIPELINE.md` (mirror a sibling surface; keep it
482
- rule-shaped). If `S` is a shared-code surface, its convention is "single owner of shared X; slices
483
- consume, never redefine."
484
-
485
- Removing/merging a surface is the reverse: drop the `surfaces[]` entry, delete its agent file, fold its
486
- conventions. Never leave an agent file with no matching `surfaces[]` entry (orphan) or vice-versa.
487
-
488
- ## Release notes — the per-feature note the versioning tool consumes
489
-
490
- Many repos gate merges on a **per-feature release note**: Changesets' `changeset` CI job fails any PR
491
- that touches product code without a `.changeset/*.md`. That file is **not** something the release
492
- agent can invent — picking the bump level is project policy (semver over an API vs. over a product,
493
- a `0.x` rule forbidding `major`, what counts as user-visible), and the prose is outward-facing copy.
494
- It is the **lead's** to write, exactly like the contract.
495
-
496
- The failure mode this block exists to prevent is silent and reproducible: the requirement lives in the
497
- project's `CLAUDE.md`, which the ship flow never reads, so `/cohorte-ship` completes, opens the PR,
498
- moves the kanban card to **Shipped** — and CI goes red on a job nobody looked at. The feature reads as
499
- shipped while being unmergeable. Encoding it in the profile is what makes the step survive a stateless
500
- lead.
501
-
502
- - **Authored before the dispatch**, next to the `status: shipped` flip (`/cohorte-ship` §2b), so the note
503
- lands **inside** the release commit. Written after the fact it needs a second commit, and the PR is
504
- already open and red.
505
- - **One key in the front-matter** — `anchor_package`. Lockstep/`fixed` version groups propagate the bump
506
- from it to every other workspace; listing more is how a repo ends up with a package bumped twice.
507
- - **`forbid_levels`** encodes policy the tool itself may not enforce. The common one: while the product
508
- is `0.y.z`, a `major` changeset makes Changesets jump to `1.0.0` with no human deciding it — so `0.x`
509
- repos forbid `major` and declare a breaking change as `minor`.
510
- - **Ask, don't guess, on an ambiguous bump.** Between two defensible levels (a refactor that also changes
511
- what the user sees), state the reading and let the human pick — a wrong bump is a published version
512
- number, not a fixable draft.
513
- - **`empty_cmd`** covers the honest no-op: a PR that touches product code but must move no version.
514
- Prefer it to skipping the step; the CI job wants a file, not a version.
515
-
516
- `enabled: false` (or `tool: none`) ⇒ §2b is a no-op and nothing below applies.
517
-
518
- ## Reconcile — bringing generated files up to the current core
519
-
520
- `/cohorte-init-pipeline` is **one-time per project**. Afterwards, `/cohorte-update-pipeline` runs this procedure so a
521
- core upgrade never requires re-running init — new pipeline features flow into the repo's generated
522
- files automatically. It works because every generated artifact is a **deterministic function of
523
- (current template × the profile's data)**; nothing needs re-detecting or re-interviewing.
524
-
525
- 1. **Profile top-up.** Diff `PIPELINE.md`'s machine block against the current
526
- `pipeline/PIPELINE.template.md`: every block/field the template has and the profile lacks is added
527
- with its documented default (e.g. `surfaces[].model: sonnet`, `retrieval.provider: serena`).
528
- **Ask only when a new field is a genuine human decision** (batch into ONE question set); never
529
- change a value the profile already sets; never rewrite the prose sections. `release_notes` is one
530
- such decision and must not be defaulted blind: detect a versioning tool / note-enforcing CI job per
531
- `/cohorte-init-pipeline` Phase 1, and if there is one, ask Phase 2's release-notes question (anchor
532
- package, language, bump policy, forbidden levels). No tool found ⇒ top up with `enabled: false`.
533
- 2. **Re-render agent frontmatter + body.** For each `surfaces[]` entry, re-render
534
- `<agents>/<agent>.md` from the current `implementer.template.md` per §Rendering above. Safe by
535
- doctrine: rendered agents are regenerable artifacts — hand-written rules belong in `PIPELINE.md`
536
- §Conventions (which reconcile never touches), NOT in agent files, where they'd be clobbered here.
537
- 3. **Additive settings patch.** Bring `<state>/gate-config.json` — and, on a runtime with a settings
538
- file the pipeline generates, that too — up to the current init spec (missing `allow` entries,
539
- hooks per install mode): add what's missing, never remove or rewrite existing/custom keys.
540
- 4. **Capability wiring.** If a top-up added a capability needing external setup (e.g. a `retrieval`
541
- provider whose MCP server isn't registered yet), run its wiring step from `/cohorte-init-pipeline` Phase 4.
542
- Even when nothing new was added, re-run the provider's health check (§Code retrieval) — wiring
543
- rots (PATH changes, uninstalls, hand-edits) — and repair whatever fails.
544
- 5. **Global config seed.** If `<config>` is absent, seed it from the template
545
- (`profile/cohorte.config.template.yaml`) so the kanban + shared-vault config has a home. Never
546
- clobber an existing filled file; report what was seeded. Then **scrub the retired `telemetry:`
547
- block** if the file still carries one (every install seeded before 2.3.0 does): delete the block
548
- and its comment header, leaving the rest byte-identical. It is dead config — the sender is gone
549
- and nothing reads it — but a `telemetry.enabled: true` sitting in a file the human may open reads
550
- as "this is still sending", which is the one thing it must not imply. This is the single
551
- exception to "never rewrite the config": a targeted deletion of a block the core no longer
552
- defines, never a re-seed.
553
- 6. **Kanban sync.** Run the §Kanban reconcile: link/create the project's board if configured, verify
554
- its columns, and backfill/sync cards from `specs/*.md`. See §Kanban.
555
- 7. **Spec-template top-up.** `specs/_template.md` is seeded once at install and then **never**
556
- refreshed, so a repo keeps whatever front-matter the core shipped the day it was installed (a
557
- pre-1.6 copy's `status` comment still lists four states). Top it up the same way as the profile:
558
- add the **front-matter fields** the current `templates/spec.template.md` has and the repo's copy
559
- lacks, with their documented defaults, drop `loop_pass`/`loop_phase` (retired with
560
- `/cohorte-loop` in 2.2.0), and refresh the `status:` comment. Never rewrite its body — the section list is the human's to shape,
561
- and some repos have deliberately trimmed it. Nothing breaks without this (the fields are written on
562
- demand when a driver needs them); it just keeps a new spec's front-matter honest about the states
563
- the pipeline can put it in.
564
-
565
- 8. **Local-artifact hygiene.** The pipeline's own runtime files must stay out of git:
566
- `<state>/preflight.ok`, `<state>/pipeline-metrics.jsonl`, `specs/reports/`. Add any missing entry to
567
- `.gitignore`, and **untrack** what a pre-2.0.0 install let slip in —
568
- `git rm --cached --ignore-unmatch <state>/preflight.ok` (repeat per stray path). The stamp is the
569
- one that actively breaks: it records the tree it verified, the commit carrying it moves HEAD past
570
- that tree, and the committed copy lands in every clone and worktree — so the phase gate ends up
571
- blocking clean trees and greening unchecked ones. Report what was untracked; the human commits it.
572
-
573
- Re-running `/cohorte-init-pipeline` remains possible (it reconciles too) but is only *needed* when the stack
574
- itself changes in ways `/cohorte-build` §1.5 can't auto-grow (e.g. package manager or contract mechanism swap).
575
-
576
- ## Workflows — deterministic multi-agent runs (opt-in)
577
-
578
- Four scripts run under the Claude Code Workflow runtime instead of the lead reasoning out the
579
- fan-out turn by turn: `<core>/workflows/review.js`, `audit.js`, `refactor.js` — each the
580
- **workflow variant** of its same-named conversational command — plus `loop.js`
581
- (`/cohorte-loop`), which has **no conversational form at all** (below). For the variant pairs,
582
- the conversational commands (`/cohorte-review`, `/cohorte-audit`, `/cohorte-refactor`)
583
- **remain the default path and the fallback** — a workflow runs only when the human explicitly asks
584
- for it ("run the review workflow"), and requires Claude Code ≥ **2.1.154** with workflows
585
- enabled.
586
- `/cohorte-doctor` reports which path a session will take. The interactive commands (`/cohorte-init-pipeline`,
587
- `/cohorte-brainstorm`, `/cohorte-spec`) and the dispatch-only ones (`/cohorte-build`, `/cohorte-ship`) have **no** workflow variant on
588
- purpose: they're interviews or already a single parallel dispatch — a script adds nothing.
589
- `/cohorte-loop` does not change that: it **consumes** `/cohorte-build`'s outputs (the frozen
590
- spec, `readiness.json`, the lead-authored contract, `build.json`) — it is not a build variant,
591
- and adding one would put the lead-only steps (§1.5 reconcile, §2 contract authoring) inside a
592
- script that cannot ask.
593
-
594
- Shared design, all four scripts:
595
-
596
- - **Phase 0 is always `profile-reader`** — workflow scripts have no filesystem or shell access, so a
597
- dedicated agent (`core/agents/profile-reader.md`, haiku, read-only) reads `PIPELINE.md` and returns
598
- the `yaml pipeline-profile` block as JSON. Every later phase is parameterized from that object.
599
- - **Mechanical phases run on haiku** (profile read, preflight, diff staging, report merging/writing);
600
- judgment phases dispatch the same pinned agents the commands use (`review` at sonnet, the surface
601
- implementers at their `surfaces[].model` tier) — the per-surface `model:` routing carries over.
602
- - **Only the verdict comes back.** Bulk (diffs, reports, backlogs) is staged to the same disk
603
- buffers the commands use (`specs/reports/`, `specs/refactor-backlog.md`); the workflow's return is
604
- counts + verdict + paths.
605
- - **A dead agent is never a clean result.** `agent()` resolves to `null` when a subagent dies, and a
606
- dead *reviewer* returns zero findings — byte-identical to a surface that is genuinely clean. Any
607
- script that derives a verdict from "how many findings came back" must first subtract the agents
608
- that never answered: `review.js` names them in `unreviewedSurfaces` and refuses to score
609
- `SHIP`. `scripts/test-workflows.mjs`
610
- pins this — it is the one invariant the structural checks in `validate-core.mjs` cannot see.
611
- The conversational commands enforce the same rule by roll call (§Dead agents); it was the workflows
612
- that had it first, and for three releases they had it **alone** — the same crash on the
613
- conversational path went unreported.
614
- - **`review.js`** — preflight gate (aborts red, zero agents), one `git diff --stat` staged per
615
- touched surface, one reviewer per surface in parallel, then an **adversarial cross-check** phase
616
- that tries to refute each CRITICAL/security finding before it can trigger a fix loop.
617
- - **`audit.js`** — one auditor per domain (each surface + `shared`), concurrency capped by the
618
- runtime (~16), merged into the prioritized `specs/refactor-backlog.md`.
619
- - **`refactor.js`** — big domains only (it skips domains with a handful of open items — the
620
- conversational `/cohorte-refactor` is cheaper there): `shared` first and alone, then the other domains'
621
- implementers in parallel, each verified per-domain.
622
- - **`loop.js`** (`/cohorte-loop`) — build → review → [fix → review]* for ONE feature, unattended and
623
- resumable. Preconditions it verifies and refuses to work around: frozen/`in-review` spec, a fresh
624
- `readiness.json` at `READY`/`RESERVATIONS`, the contract on disk, every readiness surface owned by
625
- the profile. Round exits, in order: child abort relayed → `unreviewed` non-empty → `blocking == 0`
626
- ⇒ ship → same blocking-item identity two consecutive rounds ⇒ treading water → `maxRounds`
627
- (default 5). A blocking finding on the contract file aborts (`contract-change` — lead-only, per
628
- `/cohorte-fix` §1). It calls the review **workflow** per round and reads the same `verdict.json`
629
- contract the conversational `/cohorte-review` §3 writes; it stamps `in-progress` while running,
630
- `in-review` on ship, `blocked` on a give-up. **Workflow-only, no command file, ever**
631
- (`validate-core.mjs` pins it): if the runtime is unavailable it refuses explicitly rather than
632
- degrading to a lead re-reasoning the fan-out every round at session-model prices.
633
- - **No input mid-run.** A workflow runs to completion without questions; anything interactive
634
- (contract changes, human decisions) belongs to the conversational path. The gate hook still
635
- fires on workflow subagents (see
636
- §Preflight) — in unattended runs its asks become denies. Know what that means for edits:
637
- workflow subagents run in **`acceptEdits` whatever the session mode**, so for the length of a
638
- run — and `loop.js` runs long, unattended stretches — `hooks/gate.py` is the only brake on
639
- what agents write. That is stated here rather than discovered.
640
- - **Permissions:** `/cohorte-init-pipeline` and `/cohorte-update-pipeline` extend the generated `settings.json`
641
- `allow` list with what workflow agents need (the quiet commands, the shipped
642
- `pipeline/scripts/*.sh`, read-only git incl. `git rev-parse`, and the retrieval provider's MCP
643
- tools) so a run never stalls mid-workflow on a permission prompt nobody is watching.
644
-
645
- ## Kanban — mirroring the pipeline onto an Obsidian board
646
-
647
- An **optional, user-scoped** mirror of the dev flow: each pipeline stage moves a card across an
648
- [Obsidian Kanban](https://github.com/mgmeyers/obsidian-kanban) board, one board per project. Config
649
- lives in the consolidated global config `<config>` §`kanban` (NOT in
650
- `PIPELINE.md` — the board path points at the user's personal vault, so it is machine-specific and must
651
- not be committed). Everything below **no-ops silently** when the config is absent, `kanban.enabled` is
652
- false, no board is configured for the current project, or the board file is missing — the pipeline never
653
- blocks on the board.
654
-
655
- **Config & board resolution.** `kanban.boards` is keyed by the project's `PIPELINE.md` `name`. To resolve
656
- the current project's board: read `name` from `PIPELINE.md`, look up `kanban.boards[name]`. Found ⇒ the
657
- board file is `<obsidian.vault_path>/<boards[name].board>`, its columns are `boards[name].columns` if
658
- present else `kanban.columns`. Not found ⇒ kanban off for this project.
659
-
660
- **Card format.** A card is a Kanban list item under a `## <column>` heading:
661
- `- [ ] <human title> #<feature_id>`. The `#<feature_id>` tag is the join key between a card and its
662
- `specs/<feature_id>.md`; it is how every stage finds *its* card (Grep the board for `#<id>`). Free-text
663
- notes a human writes as sub-bullets under an Ideas card are seed context for `/cohorte-brainstorm`. Never touch
664
- the trailing `%% kanban:settings … %%` block or the `kanban-plugin: board` front-matter.
665
-
666
- Once shipped, `/cohorte-ship` appends the **PR number** to the card — `- [ ] <title> #<feature_id> — PR #<num>`.
667
- The bare `#<num>` is what a board reader renders as a clickable link to the GitHub PR, so `/cohorte-ship` always
668
- writes it when a PR was actually created.
669
-
670
- **Move a card (the core op).** One call — the script does resolution AND the move outside the
671
- agent's context (find, dedupe, sub-notes carried along, settings block preserved):
672
-
673
- ```
674
- <core>/pipeline/scripts/kanban-move.sh auto <id> <stage> [--pr <num>] [--title <title>]
675
- ```
676
-
677
- It creates
678
- the card in the target column when none exists, keeps the first and drops duplicates, and appends
679
- ` — PR #<num>` with `--pr`.
680
-
681
- **`auto` is not a convenience, it is the contract.** It reads `name` from `PIPELINE.md`, then
682
- `kanban.enabled` / `obsidian.vault_path` / `boards[name]` from `<config>`
683
- (override with `COHORTE_CONFIG`, or skip the profile with `--project <name>`), and it maps the
684
- **stage key** (`ideas` · `brainstorm` · `spec` · `ready` · `building` · `review` · `fix` · `ship` ·
685
- `shipped`) to that board's heading through `boards[name].columns` → `kanban.columns` → the built-in
686
- default. An explicit `<board.md>` path and a literal heading both still work, for one-off and
687
- non-pipeline moves.
688
-
689
- **Never conclude "no board is configured" without running it.** The command that resolves nothing
690
- prints `kanban: <reason>` — naming the missing link (no config file, `enabled: false`, no entry for
691
- this project, vault unset, board file gone) — and exits **0**. A configured board that cannot be
692
- moved is loud instead: exit 2 on usage, exit 3 on a missing board file or an unknown column. Both
693
- readings are on stdout, so a caller reports which one it got. This exists because inference was the
694
- actual failure mode: with only "no-op silently if no board" to go on, a fresh phase session (every
695
- phase runs after a `/clear`) decided there was no board without ever opening the config, and cards
696
- stopped moving mid-pipeline while every command still reported success.
697
-
698
- **Fallback when the script is absent** (older core): do it by hand, but never read the whole board
699
- into context — it grows with every feature ever tracked: `grep -n` for `#<id>` and the `## ` headings
700
- to locate lines, then use offset-limited Reads + targeted Edits around the matches. Either way: one
701
- card per `#<id>`, whole line moved tag-preserved, card created in the target column if missing.
702
-
703
- **Tag before you move.** The join key is the `#<id>` tag, and an **Ideas** card a human typed by hand
704
- does not have one. Moving it first finds nothing, creates a second card, and strands the original in
705
- Ideas — so `/cohorte-brainstorm` appends the tag to the picked line before its first move.
706
-
707
- **Stage → column**, used both by each pipeline command (to move its card live) and by backfill:
708
-
709
- | Pipeline moment | Column |
710
- | --------------------------------------- | --------------- |
711
- | human drops a raw idea (manual) | `ideas` |
712
- | `/cohorte-brainstorm` picks it up | `brainstorm` |
713
- | `/cohorte-spec` opens (draft) | `spec` |
714
- | `/cohorte-spec` freezes (`status: frozen`) | `ready` |
715
- | `/cohorte-patch` triages / freezes | `spec` → `ready` (card titled `[patch] <title>`, tag `#patch-<slug>`) |
716
- | `/cohorte-build` | `building` |
717
- | `/cohorte-review` | `review` |
718
- | `/cohorte-fix` | `fix` |
719
- | a round is under way (`in-progress`) | `building` |
720
- | a round gave up (`blocked`) | `fix` |
721
- | `/cohorte-ship` starts | `ship` |
722
- | PR opened (`status: shipped`) | `shipped` (+ `PR #<num>` on the card) |
723
-
724
- **Backfill / sync from specs (reconcile).** `specs/*.md` is the source of truth. For each spec, read its
725
- `feature_id` (front-matter or filename) and `status`, map `status`→column — `frozen`→`ready`,
726
- `in-progress`→`building`, `in-review`→`review`, `blocked`→`fix`, `shipped`→`shipped`, anything else / a spec with no
727
- status→`spec` — then **full
728
- sync**: card absent ⇒ add it in that column; card present ⇒ **move it** to that column so the board
729
- always reflects the specs (this repositions cards the human may have moved by hand). Report cards
730
- added vs. moved vs. already-correct.
731
-
732
- **Create a board.** When linking a project with no board file yet: write
733
- `<obsidian.vault_path>/<folder>/Tasks.md` with the `kanban-plugin: board` front-matter, one `## <heading>`
734
- per configured column in pipeline order, and the closing `%% kanban:settings %%` block
735
- (`{"kanban-plugin":"board","list-collapse":[false,…]}` with one `false` per column).