cohorte 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +264 -0
  2. package/LICENSE +661 -0
  3. package/README.md +269 -0
  4. package/bin/cli.js +339 -0
  5. package/core/agents/implementer.template.md +74 -0
  6. package/core/agents/release.md +51 -0
  7. package/core/agents/review.md +85 -0
  8. package/core/commands/align-ds.md +32 -0
  9. package/core/commands/audit.md +31 -0
  10. package/core/commands/brainstorm.md +48 -0
  11. package/core/commands/build.md +91 -0
  12. package/core/commands/doctor.md +50 -0
  13. package/core/commands/fix.md +62 -0
  14. package/core/commands/init-pipeline.md +32 -0
  15. package/core/commands/refactor.md +38 -0
  16. package/core/commands/review.md +68 -0
  17. package/core/commands/ship.md +68 -0
  18. package/core/commands/smoke.md +55 -0
  19. package/core/commands/spec.md +67 -0
  20. package/core/commands/update-pipeline.md +96 -0
  21. package/core/hooks/__pycache__/gate.cpython-312.pyc +0 -0
  22. package/core/hooks/gate.py +129 -0
  23. package/core/templates/agent-handoff.md +34 -0
  24. package/core/templates/brainstorm-return.md +36 -0
  25. package/core/templates/design-brief.md +35 -0
  26. package/core/templates/pr-body.md +29 -0
  27. package/core/templates/review-feedback.md +36 -0
  28. package/core/templates/spec.template.md +84 -0
  29. package/core/templates/steps/init-pipeline/01-detect-stack.md +40 -0
  30. package/core/templates/steps/init-pipeline/02-interview-gaps.md +41 -0
  31. package/core/templates/steps/init-pipeline/03-draft-profile.md +10 -0
  32. package/core/templates/steps/init-pipeline/04-write-render.md +88 -0
  33. package/core/templates/steps/init-pipeline/05-report.md +12 -0
  34. package/dashboard/README.md +54 -0
  35. package/dashboard/dist/apple-touch-icon-180.png +0 -0
  36. package/dashboard/dist/assets/index-CoBuEdy-.js +42 -0
  37. package/dashboard/dist/assets/index-DN5OGW9g.css +1 -0
  38. package/dashboard/dist/favicon-16.png +0 -0
  39. package/dashboard/dist/favicon-32.png +0 -0
  40. package/dashboard/dist/favicon-48.png +0 -0
  41. package/dashboard/dist/icon-192.png +0 -0
  42. package/dashboard/dist/icon-512.png +0 -0
  43. package/dashboard/dist/index.html +16 -0
  44. package/dashboard/server/doctor.js +266 -0
  45. package/dashboard/server/fleet.js +119 -0
  46. package/dashboard/server/index.js +306 -0
  47. package/dashboard/server/kanban.js +158 -0
  48. package/dashboard/server/versions.js +111 -0
  49. package/dashboard/server/yaml.js +126 -0
  50. package/install.ps1 +359 -0
  51. package/install.sh +301 -0
  52. package/package.json +40 -0
  53. package/profile/PIPELINE.template.md +208 -0
  54. package/profile/SCHEMA.md +303 -0
  55. package/profile/cohorte.config.template.yaml +43 -0
  56. package/scripts/new-feature.sh.template +89 -0
  57. package/scripts/remove-feature.sh.template +53 -0
@@ -0,0 +1,303 @@
1
+ # PIPELINE.md profile — field reference
2
+
3
+ `/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
+ | `repo.layout` | enum | build, audit | `monorepo` (many surfaces) or `single`. |
20
+ | `repo.workspace_tool` | enum | audit | `turborepo`/`nx`/`none`. |
21
+ | `retrieval.provider` | enum | init, update-pipeline, implementer | `serena` (default) / `graphify` / `none` — see §Code retrieval. |
22
+ | **`surfaces[]`** | list | **build, review, refactor, init** | One per independently-built area. Grows via reconcile (below). |
23
+ | `surfaces[].key` | string | build | Short id + review scope. |
24
+ | `surfaces[].path` | string | implementer | The ONLY tree that surface's agent may touch. |
25
+ | `surfaces[].label` | string | build, init (`<SURFACE_LABEL>`) | Human label + framework, e.g. `frontend (React)`. |
26
+ | `surfaces[].agent` | string | build (`subagent_type`) | Rendered agent file name. |
27
+ | `surfaces[].tools` | list | init | Frontmatter `tools:` for the rendered agent. |
28
+ | `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. |
29
+ | `surfaces[].*_cmd` | string | implementer | test/lint/format/typecheck/build commands. |
30
+ | `surfaces[].uses_design` | bool | build, frontend | Whether this surface consumes designs. |
31
+ | `contract.enabled` | bool | build | `false` ⇒ skip contract authoring (§2 of /build). |
32
+ | `contract.mechanism` | enum | build, lead | `shared-types-zod`/`openapi`/`protobuf`/`json-schema`/`none`. |
33
+ | `contract.path` `.ext` `.index` | string | build | Where `<feature_id>` contract is authored + barrel. |
34
+ | `contract.authored_by` | const `lead` | build | Implementers import it read-only, never edit. |
35
+ | `commands.*` | string | all | Repo-wide install/dev/lint/format/typecheck/test + migrate. |
36
+ | `rbac.enabled` | bool | brainstorm, review | Toggle RBAC personas + authz audit. |
37
+ | `rbac.hierarchy` | list | review | Highest→lowest role list. |
38
+ | `design.enabled` | bool | build, frontend, align-ds | `false` ⇒ design steps are no-ops. |
39
+ | `design.provider` | enum | frontend, align-ds | `claude-design`/`figma`/`none`. |
40
+ | `design.design_system_project` | id | align-ds, frontend | UI-kit source of truth. |
41
+ | `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). |
42
+ | `design.snapshot_dir` | path | align-ds | Committed DS snapshot for diffing. |
43
+ | `design.ui_kit_path` `.tokens_path` | path | align-ds, frontend | Where the kit + tokens live in code. |
44
+ | `isolation.enabled` | bool | new-feature script | `false` ⇒ build in main checkout. |
45
+ | `isolation.db_per_worktree` | bool | new-feature script | Create `<name>_<id>` DB per worktree. |
46
+ | `isolation.db_name_pattern` | string | new-feature script | `<name>_<id>`. |
47
+ | `isolation.port_base` | map | new-feature script | `api`/`web` base ports; +slot per worktree. |
48
+ | `isolation.compose_file` `.registry` | path | new-feature script | Docker stack + slot registry. |
49
+ | `gate.deny[]` | list | hooks/gate.py, settings | Command substrings hard-denied, on any branch. |
50
+ | `gate.ask[]` | list | hooks/gate.py, settings | Command substrings that require confirm, on any branch. |
51
+ | `gate.ask_on_default_branch[]` | list | hooks/gate.py | Confirm ONLY on `default_branch`; free on feature branches. |
52
+ | `gate.default_branch` | string | hooks/gate.py | Protected branch (default `main`); gate resolves via git. |
53
+
54
+ ## Prose sections
55
+
56
+ - **Conventions** — per-surface rules the implementer follows and review audits.
57
+ - **Testing** — the TDD contract per surface (what a test must cover, DB isolation).
58
+ - **Design brief note** — feeds `/spec` §8 and the Claude Design step.
59
+ - **Personas** — the `/brainstorm` panel; include one per RBAC role when `rbac.enabled`.
60
+
61
+ ## How the pieces reference this file
62
+
63
+ - **Agents** (`implementer`, `review`, `release`) are told at dispatch: _read `PIPELINE.md`
64
+ §Commands / §Conventions / §Surfaces first._ They have `Read`, so they load it live.
65
+ - **Commands** (`/build`, `/review`, …) parse the `yaml pipeline-profile` block to know how
66
+ many surfaces to dispatch, the contract mechanism, the commands, and the capability flags.
67
+ - **Hook** (`gate.py`) reads `gate.deny`/`gate.ask`/`gate.ask_on_default_branch`/`gate.default_branch`
68
+ from a generated `.claude/gate-config.json`. The last two make git + docker free on feature branches
69
+ but confirm-gated on the default branch (branch resolved at run time via `git rev-parse`).
70
+ - **Scripts** (`new-feature.sh`) read the `isolation` block (rendered in at init).
71
+
72
+ ## Code retrieval — `retrieval.provider`
73
+
74
+ Agents spend most of their wall-clock reading the repo; a retrieval provider replaces grep-and-read
75
+ with symbol/graph queries. The flag is a **value, not a boolean**, so switching provider later is a
76
+ one-line profile change + re-running the wiring (no agent re-render needed — the guidance agents
77
+ follow is provider-agnostic: _"prefer the retrieval MCP tools over Grep/Glob + whole-file Reads"_).
78
+
79
+ | Provider | Mechanism | Freshness | Cost |
80
+ | --- | --- | --- | --- |
81
+ | `serena` (default) | live LSP symbol navigation (find symbol, references, semantic edits) | always current | none — no index |
82
+ | `graphify` | persistent tree-sitter knowledge graph over code + docs | as fresh as the last rescan | index step + re-index discipline |
83
+ | `none` | agents fall back to Grep/Glob/Read | — | — |
84
+
85
+ **Wiring (done by `/init-pipeline`, or `/update-pipeline` retroactively):**
86
+
87
+ - `serena` — requires the `serena` CLI (`uv tool install -p 3.13 serena-agent`). For day-to-day CLI
88
+ use it should also be on PATH (`uv tool update-shell`; uv installs to `~/.local/bin`). Register at
89
+ **project scope** so the registration is committed and portable (`--project-from-cwd` resolves the
90
+ project at server start, so the committed entry works on every machine) — and register the
91
+ **PATH-proof launcher**, not the bare command: Claude Code spawns MCP servers with whatever
92
+ environment it was launched from (a stale terminal, a GUI/IDE launch that never sourced a shell
93
+ profile), where `~/.local/bin` may be missing from PATH — a bare `serena` entry then dies with
94
+ ENOENT and agents silently fall back to Grep/Read:
95
+
96
+ ```sh
97
+ 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'
98
+ ```
99
+
100
+ (Windows-native teams: no `sh` — register the bare `serena` form instead and ensure the uv tools
101
+ dir is on PATH; keep the `--open-web-dashboard False` flag.) `--open-web-dashboard False` keeps the
102
+ dashboard available (reachable at `http://localhost:24282/dashboard/`) but stops it popping a browser
103
+ tab on every server start — the flag overrides the machine's `serena_config.yml`, so the behaviour is
104
+ the same for everyone on the repo. Gitignore `.serena/` (per-machine cache/config). Optionally
105
+ pre-index large repos once: `serena project index`.
106
+ - `graphify` — requires `uv tool install graphify` + `graphify install`; build the initial graph
107
+ (`/graphify .`) and rescan incrementally after big changes (`--update`). See graphify.net.
108
+ - Rendered agents get the provider's MCP tools appended to their `tools:` list (e.g. `mcp__serena`
109
+ grants the whole server); `none` ⇒ nothing appended.
110
+
111
+ **Serena health check** — run after wiring in `/init-pipeline` AND on every `/update-pipeline`
112
+ reconcile (wiring that worked once can rot: PATH changes, tool uninstalled, entry hand-edited):
113
+
114
+ 1. **CLI resolves:** `command -v serena`. Fails but `~/.local/bin/serena` exists ⇒ PATH repair
115
+ above; missing entirely ⇒ reinstall.
116
+ 2. **Registered:** this repo's `.mcp.json` has the `serena` entry ⇒ else re-run the `claude mcp add`.
117
+ If the entry is the bare `serena` form on a POSIX machine, upgrade it to the PATH-proof launcher
118
+ above (immune to launch-environment PATH gaps). If a launcher entry predates the
119
+ `--open-web-dashboard False` flag, append it so the dashboard no longer auto-opens a browser tab.
120
+ 3. **Gitignored:** `.serena/` is in `.gitignore` ⇒ else append it.
121
+ 4. **Actually connected:** the `mcp__serena` tools are exposed in the session (or `claude mcp list`
122
+ shows serena connected). If 1–3 pass but this fails, a session restart is needed — say so
123
+ explicitly instead of reporting success.
124
+
125
+ Report each check's result; never report Serena "wired" on registration alone.
126
+
127
+ Teammates cloning the repo get the committed `.mcp.json` and only need the provider CLI installed
128
+ and on PATH — if either is missing, the MCP server fails to start and agents silently fall back to
129
+ Grep/Read; the health check above is the diagnostic.
130
+
131
+ ## Specialization — when to split one surface into more agents
132
+
133
+ `/build` dispatches ONE agent per surface, in parallel, so build wall-clock ≈ the **slowest single
134
+ surface**. More agents only build faster when they let the *slowest* surface's work run concurrently —
135
+ and only if the split is safe. The invariant that keeps parallelism safe is **one owner per tree, and
136
+ the frozen contract as the only cross-surface channel**. So specialization means carving a surface into
137
+ **smaller non-overlapping surfaces**, never pointing two agents at the same tree.
138
+
139
+ **Split a surface into specialized sub-surfaces only when BOTH hold:**
140
+
141
+ 1. **It's a bottleneck** — the surface is large (many modules / high LOC) and dominates build time.
142
+ 2. **The boundary is clean** — its work partitions into trees that don't share files, e.g. feature
143
+ modules (`src/features/*`, `src/modules/*`), route groups, or independent services (`services/*`).
144
+
145
+ **Rules when splitting (non-negotiable — they preserve the invariant):**
146
+
147
+ - **Shared code gets its own surface with a single owner.** Anything two slices both touch — routing,
148
+ global state/store, the design-system kit + tokens, shared utils — becomes its OWN surface (e.g.
149
+ `web-shared`), owned by exactly one agent. Never let two feature-slice agents both edit shared trees.
150
+ - **Cross-slice references go through the contract**, not direct imports between slice trees. If
151
+ `web-checkout` needs a shape produced by `api-billing`, that shape lives in the frozen contract.
152
+ - **Don't over-split.** A slice too small to hold ≥1 real task, or one with tangled boundaries, is worse
153
+ than not splitting — the coordination + token cost (each stateless agent re-reads `PIPELINE.md` + spec)
154
+ outweighs the parallelism. When boundaries aren't clean, keep one surface.
155
+
156
+ Coarse first, specialize on evidence: start with one `frontend` / `backend` surface each; split only a
157
+ surface that's proven slow and cleanly separable. The evidence lives in
158
+ `.claude/pipeline-metrics.jsonl` (gitignored) — one JSONL line per dispatched agent
159
+ (`ts`/`feature`/`phase`/`surface`/`seconds`/`result`), appended by `/build`, `/review`, `/fix` and
160
+ `/smoke`. Read it before proposing a split: split the surface that actually dominates wall-clock,
161
+ not the one that feels big.
162
+
163
+ ## Measuring cost — what's slow vs what's expensive
164
+
165
+ `pipeline-metrics.jsonl` records **wall-clock seconds** per dispatch (§Specialization) — it tells you what's
166
+ SLOW. It deliberately does NOT record tokens: the lead can't reliably read a subagent's token count to log
167
+ it. For what's EXPENSIVE, use Claude Code's own accounting:
168
+
169
+ - **`/cost`** (built-in, zero setup) — reports per-**subagent** and per-**slash-command** share of your usage
170
+ over the last 24 h / 7 d (e.g. _"Top subagents: frontend 7 %, backend 4 % · Top skills: /build 1 %,
171
+ /review 1 %"_). That IS the per-phase ledger — approximate (share-of-total, machine-local, not exact
172
+ tokens). Read it to see which surface/command actually dominates the bill before you tune a `model` tier.
173
+ - **OpenTelemetry** (exact numbers + dashboards) — add an `env` block to `~/.claude/settings.json`:
174
+ `{"env":{"CLAUDE_CODE_ENABLE_TELEMETRY":"1","OTEL_METRICS_EXPORTER":"otlp","OTEL_EXPORTER_OTLP_PROTOCOL":"http/protobuf","OTEL_EXPORTER_OTLP_ENDPOINT":"http://localhost:4318"}}`
175
+ and point it at a collector. Metrics `claude_code.token.usage` + `claude_code.cost.usage` carry
176
+ `session.id` + model + type (input/output/cacheRead). Subagent tokens roll into the session total;
177
+ per-subagent attribution needs traces (`CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1`, beta).
178
+
179
+ ## Rendering / reconciling a surface agent (shared procedure)
180
+
181
+ Both `/init-pipeline` (initial render) and `/build` (auto-reconcile when a spec needs a new agent) use
182
+ this exact procedure so a surface is always defined the same way. To add surface `S`:
183
+
184
+ 1. **Add the `surfaces[]` entry** to `PIPELINE.md`: `key`, `path` (the disjoint tree it exclusively
185
+ owns), `label`, `agent` (rendered file name), `tools` (add `DesignSync` only if `uses_design: true`;
186
+ append the retrieval provider's MCP tools when `retrieval.provider` ≠ `none` — e.g. `mcp__serena`),
187
+ `model` (tier for the rendered agent: `sonnet` (default) — the implementer mostly applies a frozen
188
+ contract, which Sonnet does well at a fraction of the Opus-lead cost; `haiku` for purely mechanical
189
+ scaffolding; `inherit` only when the surface makes real design decisions worth the lead's model),
190
+ the five `*_cmd`s (derive from the surface's `package.json` / workspace
191
+ filter, mirroring a sibling surface), and `uses_design`.
192
+ 2. **Render the agent file** `.claude/agents/<agent>.md` from `pipeline/implementer.template.md`
193
+ (resolve bundled `.claude/` vs global `~/.claude/`), substituting `<SURFACE_AGENT>`, `<SURFACE_LABEL>`,
194
+ `<SURFACE_PATH>`, `<SURFACE_TOOLS>`, `<SURFACE_MODEL>`, `<PROJECT_NAME>`, and the surface-specific
195
+ blocks (`<SURFACE_EXTRA_NEVER>`, `<SURFACE_DESIGN_INPUT>`, `<SURFACE_TDD_STEP1>` — leave the design
196
+ ones empty unless `uses_design`). For a `uses_design` surface, fill them **link-based** (never with a
197
+ stored `design_project` id — that goes stale on a DS rebuild):
198
+ - `<SURFACE_DESIGN_INPUT>` — a 4th input bullet: _"The **feature design** — the pages this feature
199
+ touches, listed in the spec front-matter `design_files` as full links
200
+ (`https://claude.ai/design/p/<projectId>?file=<file>`). For each, extract the `<projectId>` (the
201
+ `/p/…` segment) and `<file>` (the `?file=` query) from the URL and read it read-only via `DesignSync
202
+ get_file(<projectId>, <file>)`; `list_files(<projectId>)` to catch linked pages (shared nav/modals)
203
+ this feature also changes. The link is self-contained — no stored project id. Build with the code UI
204
+ kit (the `design_system_project`'s materialization: `@/components/ui/*` + tokens); read a primitive
205
+ via `get_file` only if it's missing/stale in code. Mobile-first."_
206
+ - `<SURFACE_TDD_STEP1>` — _"**Pull the feature design first:** `DesignSync get_file(<projectId>,
207
+ <file>)` for each `design_files` link and translate each into the code design system
208
+ (`@/components/ui/*`, `cn()` + CVA), mobile-first — never ad-hoc CSS. Then:"_
209
+ 3. **Add a §Conventions + §Testing stanza** for `S` in `PIPELINE.md` (mirror a sibling surface; keep it
210
+ rule-shaped). If `S` is a shared-code surface, its convention is "single owner of shared X; slices
211
+ consume, never redefine."
212
+
213
+ Removing/merging a surface is the reverse: drop the `surfaces[]` entry, delete its agent file, fold its
214
+ conventions. Never leave an agent file with no matching `surfaces[]` entry (orphan) or vice-versa.
215
+
216
+ ## Reconcile — bringing generated files up to the current core
217
+
218
+ `/init-pipeline` is **one-time per project**. Afterwards, `/update-pipeline` runs this procedure so a
219
+ core upgrade never requires re-running init — new pipeline features flow into the repo's generated
220
+ files automatically. It works because every generated artifact is a **deterministic function of
221
+ (current template × the profile's data)**; nothing needs re-detecting or re-interviewing.
222
+
223
+ 1. **Profile top-up.** Diff `PIPELINE.md`'s machine block against the current
224
+ `pipeline/PIPELINE.template.md`: every block/field the template has and the profile lacks is added
225
+ with its documented default (e.g. `surfaces[].model: sonnet`, `retrieval.provider: serena`).
226
+ **Ask only when a new field is a genuine human decision** (batch into ONE question set); never
227
+ change a value the profile already sets; never rewrite the prose sections.
228
+ 2. **Re-render agent frontmatter + body.** For each `surfaces[]` entry, re-render
229
+ `.claude/agents/<agent>.md` from the current `implementer.template.md` per §Rendering above. Safe by
230
+ doctrine: rendered agents are regenerable artifacts — hand-written rules belong in `PIPELINE.md`
231
+ §Conventions (which reconcile never touches), NOT in agent files, where they'd be clobbered here.
232
+ 3. **Additive settings patch.** Bring `.claude/settings.json` + `gate-config.json` up to the current
233
+ init spec (missing `allow` entries, hooks per install mode) — add what's missing, never remove or
234
+ rewrite existing/custom keys.
235
+ 4. **Capability wiring.** If a top-up added a capability needing external setup (e.g. a `retrieval`
236
+ provider whose MCP server isn't registered yet), run its wiring step from `/init-pipeline` Phase 4.
237
+ Even when nothing new was added, re-run the provider's health check (§Code retrieval) — wiring
238
+ rots (PATH changes, uninstalls, hand-edits) — and repair whatever fails.
239
+ 5. **Global config seed.** If `~/.claude/cohorte.config.yaml` is absent, seed it from the template
240
+ (`profile/cohorte.config.template.yaml`) so the kanban + shared-vault config has a home. Never
241
+ clobber an existing filled file; report what was seeded.
242
+ 6. **Kanban sync.** Run the §Kanban reconcile: link/create the project's board if configured, verify
243
+ its columns, and backfill/sync cards from `specs/*.md`. See §Kanban.
244
+
245
+ Re-running `/init-pipeline` remains possible (it reconciles too) but is only *needed* when the stack
246
+ itself changes in ways `/build` §1.5 can't auto-grow (e.g. package manager or contract mechanism swap).
247
+
248
+ ## Kanban — mirroring the pipeline onto an Obsidian board
249
+
250
+ An **optional, user-scoped** mirror of the dev flow: each pipeline stage moves a card across an
251
+ [Obsidian Kanban](https://github.com/mgmeyers/obsidian-kanban) board, one board per project. Config
252
+ lives in the consolidated global config `~/.claude/cohorte.config.yaml` §`kanban` (NOT in
253
+ `PIPELINE.md` — the board path points at the user's personal vault, so it is machine-specific and must
254
+ not be committed). Everything below **no-ops silently** when the config is absent, `kanban.enabled` is
255
+ false, no board is configured for the current project, or the board file is missing — the pipeline never
256
+ blocks on the board.
257
+
258
+ **Config & board resolution.** `kanban.boards` is keyed by the project's `PIPELINE.md` `name`. To resolve
259
+ the current project's board: read `name` from `PIPELINE.md`, look up `kanban.boards[name]`. Found ⇒ the
260
+ board file is `<obsidian.vault_path>/<boards[name].board>`, its columns are `boards[name].columns` if
261
+ present else `kanban.columns`. Not found ⇒ kanban off for this project.
262
+
263
+ **Card format.** A card is a Kanban list item under a `## <column>` heading:
264
+ `- [ ] <human title> #<feature_id>`. The `#<feature_id>` tag is the join key between a card and its
265
+ `specs/<feature_id>.md`; it is how every stage finds *its* card (Grep the board for `#<id>`). Free-text
266
+ notes a human writes as sub-bullets under an Ideas card are seed context for `/brainstorm`. Never touch
267
+ the trailing `%% kanban:settings … %%` block or the `kanban-plugin: board` front-matter.
268
+
269
+ Once shipped, `/ship` appends the **PR number** to the card — `- [ ] <title> #<feature_id> — PR #<num>`.
270
+ The bare `#<num>` is what the dashboard renders as a clickable link to the GitHub PR, so `/ship` always
271
+ writes it when a PR was actually created.
272
+
273
+ **Move a card (the core op).** To move card `#<id>` to a stage's column: find the list item carrying
274
+ `#<id>` under its current `## <column>` heading, delete it there, and append it (whole line, tag
275
+ preserved) under the target `## <column>` heading. If no card carries `#<id>` (feature started outside
276
+ the board), create the card in the target column instead of erroring. One card per `#<id>`; if
277
+ duplicates exist, keep the first and drop the rest.
278
+
279
+ **Stage → column**, used both by each pipeline command (to move its card live) and by backfill:
280
+
281
+ | Pipeline moment | Column |
282
+ | --------------------------------------- | --------------- |
283
+ | human drops a raw idea (manual) | `ideas` |
284
+ | `/brainstorm` picks it up | `brainstorm` |
285
+ | `/spec` opens (draft) | `spec` |
286
+ | `/spec` freezes (`status: frozen`) | `ready` |
287
+ | `/build` | `building` |
288
+ | `/smoke` · `/review` | `review` |
289
+ | `/fix` | `fix` |
290
+ | `/ship` starts | `ship` |
291
+ | PR opened (`status: shipped`) | `shipped` (+ `PR #<num>` on the card) |
292
+
293
+ **Backfill / sync from specs (reconcile).** `specs/*.md` is the source of truth. For each spec, read its
294
+ `feature_id` (front-matter or filename) and `status`, map `status`→column — `frozen`→`ready`,
295
+ `in-review`→`review`, `shipped`→`shipped`, anything else / a spec with no status→`spec` — then **full
296
+ sync**: card absent ⇒ add it in that column; card present ⇒ **move it** to that column so the board
297
+ always reflects the specs (this repositions cards the human may have moved by hand). Report cards
298
+ added vs. moved vs. already-correct.
299
+
300
+ **Create a board.** When linking a project with no board file yet: write
301
+ `<obsidian.vault_path>/<folder>/Tasks.md` with the `kanban-plugin: board` front-matter, one `## <heading>`
302
+ per configured column in pipeline order, and the closing `%% kanban:settings %%` block
303
+ (`{"kanban-plugin":"board","list-collapse":[false,…]}` with one `false` per column).
@@ -0,0 +1,43 @@
1
+ # ~/.claude/cohorte.config.yaml — GLOBAL, user-scoped config for the Cohorte
2
+ # pipeline capabilities. One machine, every project. NOT tied to any project's PIPELINE.md.
3
+ #
4
+ # Seeded once by the installer (npx cohorte install) and never clobbered on update. Wired
5
+ # interactively by /init-pipeline (new project) and /update-pipeline (existing project) —
6
+ # you should never need to hand-edit it.
7
+ #
8
+ # The `# cfg:<key>` markers below are anchors the installer's interactive prompts write to;
9
+ # leave them in place.
10
+
11
+ enabled: true # cfg:enabled — master switch; false disables every capability below
12
+
13
+ # ── obsidian ──────────────────────────────────────────────────────────────────
14
+ # Local vault used by the kanban mirror — defined here ONCE so no capability ever duplicates
15
+ # the path. Absolute path to the folder you open in Obsidian.
16
+ obsidian:
17
+ vault_path: "" # cfg:vault_path
18
+
19
+ # ── kanban (dev pipeline mirror) ─────────────────────────────────────────────
20
+ # Mirrors /brainstorm…/ship onto an Obsidian Kanban board — one board per project. The
21
+ # board path points at your PERSONAL vault, so it is machine-specific and lives here (never
22
+ # committed in a repo's PIPELINE.md). /init-pipeline + /update-pipeline write `boards` for you.
23
+ # See SCHEMA.md §Kanban.
24
+ kanban:
25
+ enabled: false # cfg:kanban_enabled — master switch for the kanban mirror
26
+ # Default column mapping: pipeline stage → the EXACT board column heading. Shared by every
27
+ # board; a board may override its own `columns` inside boards[] below.
28
+ columns:
29
+ ideas: "Ideas"
30
+ brainstorm: "Brainstorm"
31
+ spec: "Spec"
32
+ ready: "Ready to build"
33
+ building: "Building"
34
+ review: "Review"
35
+ fix: "Fix"
36
+ ship: "Ship"
37
+ shipped: "Shipped"
38
+ # One entry per project, keyed by the project's PIPELINE.md `name` (e.g. SAMO). `board` is a
39
+ # path RELATIVE to obsidian.vault_path. Written by /init-pipeline + /update-pipeline.
40
+ boards: {}
41
+ # SAMO:
42
+ # board: "Samo/Tasks.md"
43
+ # # columns: { ideas: "Idées", ... } # optional per-board override of the defaults above
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # new-feature.sh — spin up an ISOLATED git worktree for one feature so features
4
+ # can be built fully in parallel. Rendered from a template by /init-pipeline;
5
+ # the __TOKENS__ below are substituted from PIPELINE.md §isolation.
6
+ #
7
+ # Usage: scripts/new-feature.sh <feature_id>
8
+ #
9
+ # Creates a sibling worktree ../__PROJECT_SLUG__-<id> on branch
10
+ # __BRANCH_PREFIX__<id> (off __DEFAULT_BRANCH__) with its own isolation slot:
11
+ # • its own database __DB_NAME_PATTERN__ (if DB isolation is enabled)
12
+ # • its own API port __API_PORT_BASE__ + slot
13
+ # • its own web port __WEB_PORT_BASE__ + slot
14
+ # The infra servers stay a SINGLE shared stack — only the logical db + ports
15
+ # differ per worktree. Slot 0 = main checkout. New worktrees get slot >= 1.
16
+
17
+ set -euo pipefail
18
+
19
+ id="${1:-}"
20
+ if [[ -z "$id" ]]; then
21
+ echo "usage: scripts/new-feature.sh <feature_id>" >&2
22
+ exit 1
23
+ fi
24
+
25
+ repo_root="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)"
26
+ branch="__BRANCH_PREFIX__${id}"
27
+ worktree_dir="$(dirname "$repo_root")/__PROJECT_SLUG__-${id}"
28
+ db_name="__PROJECT_SLUG___$(echo "$id" | tr -c 'a-zA-Z0-9' '_' | sed 's/_*$//')"
29
+
30
+ registry_dir="$repo_root/.worktrees"
31
+ registry="$registry_dir/slots.tsv"
32
+ mkdir -p "$registry_dir"
33
+ touch "$registry"
34
+
35
+ if [[ -e "$worktree_dir" ]]; then
36
+ echo "error: $worktree_dir already exists" >&2
37
+ exit 1
38
+ fi
39
+
40
+ slot="$(awk -F'\t' -v id="$id" '$1==id{print $2}' "$registry" | head -n1)"
41
+ if [[ -z "$slot" ]]; then
42
+ slot=1
43
+ while awk -F'\t' -v s="$slot" '$2==s{found=1} END{exit !found}' "$registry"; do
44
+ slot=$((slot + 1))
45
+ done
46
+ printf '%s\t%s\n' "$id" "$slot" >>"$registry"
47
+ fi
48
+
49
+ api_port=$((__API_PORT_BASE__ + slot))
50
+ web_port=$((__WEB_PORT_BASE__ + slot))
51
+
52
+ echo "→ feature '$id' → slot $slot (db $db_name · api :$api_port · web :$web_port)"
53
+ echo "→ creating worktree $worktree_dir on $branch (off __DEFAULT_BRANCH__)"
54
+ git -C "$repo_root" fetch --quiet origin __DEFAULT_BRANCH__ || true
55
+ git -C "$repo_root" worktree add -b "$branch" "$worktree_dir" __DEFAULT_BRANCH__
56
+
57
+ # Replace (or append) KEY=VALUE in a dotenv file — one definitive line per key.
58
+ set_env() {
59
+ local file="$1" key="$2" value="$3" tmp
60
+ if [[ -s "$file" && -n "$(tail -c1 "$file")" ]]; then printf '\n' >>"$file"; fi
61
+ tmp="$(mktemp)"
62
+ grep -vE "^${key}=" "$file" >"$tmp" || true
63
+ mv "$tmp" "$file"
64
+ printf '%s=%s\n' "$key" "$value" >>"$file"
65
+ }
66
+
67
+ # --- per-worktree env (init customizes these stanzas per surface) -----------
68
+ # __ENV_STANZAS__
69
+
70
+ # --- create the isolated database (if DB isolation enabled) -----------------
71
+ # __DB_STANZA__
72
+
73
+ echo "→ ${repo_root##*/} install (shared store)"
74
+ ( cd "$worktree_dir" && __INSTALL_CMD__ )
75
+
76
+ cat <<EOF
77
+
78
+ ✓ worktree ready: $worktree_dir (branch $branch, slot $slot)
79
+ api http://localhost:$api_port
80
+ web http://localhost:$web_port
81
+
82
+ Fully isolated — run its dev + migrations alongside other worktrees:
83
+ cd $worktree_dir
84
+ __MIGRATE_CMD__
85
+ cd $worktree_dir && __DEV_CMD__
86
+ /brainstorm → /spec → (design) → /build $id → test → /review → /ship
87
+
88
+ Tear down when merged: scripts/remove-feature.sh $id
89
+ EOF
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # remove-feature.sh — tear down a feature's isolated worktree. Rendered from a
4
+ # template by /init-pipeline (__TOKENS__ from PIPELINE.md §isolation).
5
+ #
6
+ # Usage: scripts/remove-feature.sh <feature_id> [--drop-db]
7
+ #
8
+ # Removes the ../__PROJECT_SLUG__-<id> worktree, frees its slot, and (with
9
+ # --drop-db) drops its database. The db is kept by default. The shared stack and
10
+ # the main checkout are never touched.
11
+
12
+ set -euo pipefail
13
+
14
+ id="${1:-}"
15
+ drop_db="no"
16
+ [[ "${2:-}" == "--drop-db" ]] && drop_db="yes"
17
+
18
+ if [[ -z "$id" ]]; then
19
+ echo "usage: scripts/remove-feature.sh <feature_id> [--drop-db]" >&2
20
+ exit 1
21
+ fi
22
+
23
+ repo_root="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)"
24
+ worktree_dir="$(dirname "$repo_root")/__PROJECT_SLUG__-${id}"
25
+ db_name="__PROJECT_SLUG___$(echo "$id" | tr -c 'a-zA-Z0-9' '_' | sed 's/_*$//')"
26
+ registry="$repo_root/.worktrees/slots.tsv"
27
+ branch="__BRANCH_PREFIX__${id}"
28
+
29
+ echo "→ removing worktree $worktree_dir"
30
+ if git -C "$repo_root" worktree list | grep -q "$worktree_dir"; then
31
+ git -C "$repo_root" worktree remove "$worktree_dir" "${@:3}"
32
+ else
33
+ echo " (no such worktree registered — skipping)"
34
+ fi
35
+
36
+ if git -C "$repo_root" show-ref --verify --quiet "refs/heads/$branch"; then
37
+ if git -C "$repo_root" branch -d "$branch" 2>/dev/null; then
38
+ echo "→ deleted merged branch $branch"
39
+ else
40
+ echo " ! branch $branch is not fully merged — kept it." >&2
41
+ echo " delete manually when ready: git branch -D $branch" >&2
42
+ fi
43
+ fi
44
+
45
+ if [[ -f "$registry" ]]; then
46
+ tmp="$(mktemp)"
47
+ awk -F'\t' -v id="$id" '$1!=id' "$registry" >"$tmp" && mv "$tmp" "$registry"
48
+ echo "→ freed slot for '$id'"
49
+ fi
50
+
51
+ # __DB_DROP_STANZA__
52
+
53
+ echo "✓ feature '$id' torn down"