cohorte 2.8.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -0
- package/README.md +41 -46
- package/bin/cli.js +8 -41
- package/bin/report.js +2 -2
- package/core/commands/cohorte-brainstorm.md +5 -1
- package/core/commands/cohorte-fleet.md +103 -0
- package/core/commands/cohorte-intake.md +92 -0
- package/core/commands/cohorte-patch.md +6 -1
- package/core/commands/cohorte-retro.md +85 -0
- package/core/commands/cohorte-review.md +53 -1
- package/core/commands/cohorte-ship.md +2 -2
- package/core/hooks/gate.py +1 -1
- package/core/workflows/loop.js +27 -3
- package/core/workflows/review.js +13 -2
- package/{dashboard/server → lib}/doctor.js +3 -2
- package/{dashboard/server → lib}/runtime.js +1 -1
- package/{dashboard/server → lib}/versions.js +2 -2
- package/package.json +3 -8
- package/profile/SCHEMA.md +16 -8
- package/scripts/{test-dashboard.mjs → test-lib.mjs} +12 -222
- package/scripts/test-workflows.mjs +31 -2
- package/scripts/validate-core.mjs +7 -21
- package/dashboard/README.md +0 -71
- package/dashboard/dist/apple-touch-icon-180.png +0 -0
- package/dashboard/dist/assets/index-BZ_LQlEj.css +0 -1
- package/dashboard/dist/assets/index-DO3_nq2Q.js +0 -43
- package/dashboard/dist/favicon-16.png +0 -0
- package/dashboard/dist/favicon-32.png +0 -0
- package/dashboard/dist/favicon-48.png +0 -0
- package/dashboard/dist/icon-192.png +0 -0
- package/dashboard/dist/icon-512.png +0 -0
- package/dashboard/dist/index.html +0 -16
- package/dashboard/server/fleet.js +0 -133
- package/dashboard/server/index.js +0 -408
- package/dashboard/server/kanban.js +0 -169
- package/dashboard/server/metrics.js +0 -113
- package/dashboard/server/usage.js +0 -61
- /package/{dashboard/server → lib}/yaml.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,78 @@ short, user-facing, most recent first. One `## <version> — <YYYY-MM-DD>` secti
|
|
|
7
7
|
> They are history and are deliberately not rewritten — every command gained a `cohorte-` prefix
|
|
8
8
|
> in 2.0.0.
|
|
9
9
|
|
|
10
|
+
## 2.10.0 — 2026-08-24
|
|
11
|
+
|
|
12
|
+
- **The dashboard is gone.** The local web cockpit — the `cohorte dashboard` verb, the
|
|
13
|
+
dependency-free HTTP server, the Vite/React app and the prebuilt `dashboard/dist` that shipped
|
|
14
|
+
in every tarball — is removed, along with the fleet registry, kanban and usage readers that
|
|
15
|
+
only it consumed. It was a second surface for state the CLI already reports, with its own
|
|
16
|
+
build step, its own security posture (loopback-bound because its action endpoints executed
|
|
17
|
+
code: install, update, reset, and `/cohorte-init-pipeline`·`/cohorte-update-pipeline`·`/cohorte-audit`
|
|
18
|
+
through headless Claude), and its own drift to police. `cohorte doctor`, `cohorte specs` and
|
|
19
|
+
`cohorte metrics` answer the same questions from the shell, and `doctor` exits 1 on any bad
|
|
20
|
+
check, which the browser never could.
|
|
21
|
+
|
|
22
|
+
**What did not go with it:** the four modules those verbs actually run on — the JS port of
|
|
23
|
+
`/cohorte-doctor`, the runtime-layout resolver, the version prober and the block-YAML parser —
|
|
24
|
+
moved from `dashboard/server/` to **`lib/`**. `cohorte doctor` / `specs` / `version` behave
|
|
25
|
+
exactly as before, and so do the Francois extension's `--panel` payloads. Their test suite
|
|
26
|
+
moved with them (`scripts/test-dashboard.mjs` ⇒ `scripts/test-lib.mjs`, 54 assertions), minus
|
|
27
|
+
the cases that covered deleted code.
|
|
28
|
+
|
|
29
|
+
Fallout worth naming: the package no longer has a build step at all (`prepack` and
|
|
30
|
+
`build:dashboard` are gone, npm and CI no longer build a React app to publish), the tarball
|
|
31
|
+
drops from 88 files to 73, and `~/.claude/cohorte-dashboard.json` — the tracked-project list —
|
|
32
|
+
is now dead; delete it. `--port` / `--host` / `--open` and `COHORTE_DASHBOARD_PORT` /
|
|
33
|
+
`COHORTE_DASHBOARD_HOST` no longer exist; `cohorte dashboard` prints the usage and exits 2.
|
|
34
|
+
|
|
35
|
+
## 2.9.0 — 2026-08-22
|
|
36
|
+
|
|
37
|
+
- **`/cohorte-intake` — the door before the doors.** Work has always entered the pipeline
|
|
38
|
+
pre-distilled by a human: a ticket became a `/cohorte-patch` paste, an email became a
|
|
39
|
+
brainstorm idea, by hand. Intake does the distillation: paste anything that arrives and it
|
|
40
|
+
triages — bug ⇒ the exact structure patch interviews for (inferred repro steps labeled as
|
|
41
|
+
such); feature ⇒ a brainstorm seed whose open-questions list is the panel's agenda, with any
|
|
42
|
+
`_decisions.md` line it contradicts named; noise ⇒ says so and stops, because inventing a
|
|
43
|
+
spec from noise costs a whole pipeline run downstream. The distillate lands on disk and on
|
|
44
|
+
the kanban's Ideas column; freezing anything stays the human's move.
|
|
45
|
+
|
|
46
|
+
- **`/cohorte-retro` — the pipeline learns from its own findings.** Every review leaves
|
|
47
|
+
structured residue (verdict.json, the specs' Remediation history, the deferred backlog) that
|
|
48
|
+
nothing ever read back. Retro mines it for patterns — the same finding kind on the same
|
|
49
|
+
surface across features, a hotspot module, a recurring fix family — and turns each into ONE
|
|
50
|
+
rule-shaped §Conventions line the human ratifies. Adopted rules re-render the affected
|
|
51
|
+
surface agents (the conventions slice is baked at render time — a rule without a re-render
|
|
52
|
+
is one reviewers enforce and implementers never saw) and land in the decisions journal.
|
|
53
|
+
Findings → rules → the next build never produces the finding: cheaper than any number of
|
|
54
|
+
review rounds catching it.
|
|
55
|
+
|
|
56
|
+
- **`/cohorte-fleet` — parallel features get a flight controller.** The worktree isolation
|
|
57
|
+
existed; the coordination lived in your head. `plan` builds the feature×surface overlap
|
|
58
|
+
matrix from the specs themselves (contract dependencies ⇒ merge order; same-tree writes ⇒
|
|
59
|
+
serialize or drop one), provisions the worktrees, and prints one launch line per feature.
|
|
60
|
+
`status` is one row per feature ending in the single next action. `sync` does the post-merge
|
|
61
|
+
sweep everyone forgets — rebase every survivor, report conflicts verbatim to their owner's
|
|
62
|
+
session, and say out loud that a rebase invalidates the freshness stamp. What it will never
|
|
63
|
+
do is spawn the work headless: that is the retired 2.2.0 driver's grave, and each feature's
|
|
64
|
+
loop keeps running in its own supervised session.
|
|
65
|
+
|
|
66
|
+
- **`/cohorte-review --pr <num>` — the pipeline reviews incoming work.** Same reviewers, same
|
|
67
|
+
report, none of the pipeline's certifications: the PR is fetched into a throwaway worktree
|
|
68
|
+
(your checkout untouched), reviewed in audit mode (no spec to conform to), and a red
|
|
69
|
+
preflight makes the mechanical failures the review instead of wasting reviewers on code that
|
|
70
|
+
doesn't compile. Posting the report as a PR comment always asks first — it is outward-facing
|
|
71
|
+
— and declining leaves the report on disk as a complete outcome.
|
|
72
|
+
|
|
73
|
+
- **What a feature costs, finally on the dashboard.** The workflow paths now stamp an
|
|
74
|
+
approximate `tokens` field into their metrics lines from the runtime's own counter
|
|
75
|
+
(`budget.spent()` deltas — the figure a conversational lead cannot read), the loop's history
|
|
76
|
+
carries cost per round, and the dashboard shows `~Nk tok` per feature and per phase. Marks
|
|
77
|
+
are placed around the review child so build/fix deltas never double-count it; token-less
|
|
78
|
+
conversational lines aggregate as absent, never as "free".
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
10
82
|
## 2.8.0 — 2026-08-22
|
|
11
83
|
|
|
12
84
|
- **`/cohorte-loop` is back — as a workflow, which is the whole point.** The 2.2.0 driver was
|
package/README.md
CHANGED
|
@@ -24,6 +24,37 @@ Cursor, Gemini CLI or OpenCode. Install it once globally, then one command per p
|
|
|
24
24
|
/cohorte-brainstorm → /cohorte-spec → (design) → /cohorte-build <id> → /cohorte-review → (/cohorte-fix) → /cohorte-ship
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
<div align="center">
|
|
28
|
+
<img src="https://raw.githubusercontent.com/TheBidouilleAgency/cohorte/main/assets/demo-cli.gif" alt="cohorte doctor reporting a green pipeline, the spec board, and the gate denying a chained destructive command" width="760">
|
|
29
|
+
<br>
|
|
30
|
+
<sub><code>cohorte doctor</code> · the spec board · the gate refusing a hard-denied command chained behind a benign one.<br>
|
|
31
|
+
Recorded from the real CLI by <code>scripts/demo/record-cli.sh</code> — no output is hand-edited.</sub>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
## Why a pipeline at all
|
|
35
|
+
|
|
36
|
+
Your coding agent is already good at the first prompt of a feature. It gets worse at every one
|
|
37
|
+
after — because what it knows lives in a conversation, and a conversation gets summarised,
|
|
38
|
+
truncated, and re-sent at input price on every turn. Cohorte is the set of constraints that stop
|
|
39
|
+
that degradation. Each one is a failure you have already had:
|
|
40
|
+
|
|
41
|
+
| The failure | The constraint |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| *"It forgot what we decided."* | The spec is **frozen to disk** and re-read by every stateless agent. Nothing is remembered, so nothing is forgotten — and `/clear` between stages is always safe. |
|
|
44
|
+
| *"The API and the UI don't fit."* | The **contract is authored before any implementer is dispatched**. Surfaces import it read-only and never talk to each other — which is what makes building them in parallel safe. |
|
|
45
|
+
| *"It edited a file I didn't want it to."* | **One owner per tree**, enforced in the rendered agent's frontmatter — not requested politely in a prompt. |
|
|
46
|
+
| *"It ran something it shouldn't have."* | `gate.py` is a **real blocking hook**, branch-aware, hard-denying destructive commands from every agent — subagents included. |
|
|
47
|
+
| *"The review said it was fine."* | Review is a **separate read-only agent** that sees the spec and the diff, never the conversation that produced them. A dead reviewer's zero findings can't read as ship. |
|
|
48
|
+
| *"Four agents just told me it doesn't compile."* | A **deterministic preflight** runs typecheck/lint/tests first. Red ⇒ **zero agents spawned**. |
|
|
49
|
+
| *"We fixed this same thing last week."* | `/cohorte-retro` turns repeated findings into ratified conventions **baked into the implementers**, so the next build never produces them. |
|
|
50
|
+
|
|
51
|
+
None of this is intelligence added to your agent — it's the opposite. It moves the decisions an
|
|
52
|
+
agent makes badly under context pressure into files, hooks and ownership boundaries, where they're
|
|
53
|
+
cheap and don't degrade with conversation length. Which is why it's markdown, shell and one Python
|
|
54
|
+
hook: **your app code never imports anything from Cohorte.**
|
|
55
|
+
|
|
56
|
+
[**The long version — including the four cases where you shouldn't use it →**](https://thebidouilleagency.github.io/cohorte/guide/why-cohorte)
|
|
57
|
+
|
|
27
58
|
## How it works — three layers
|
|
28
59
|
|
|
29
60
|
| Layer | What it holds | Lives in | Scope |
|
|
@@ -206,45 +237,6 @@ decisions), surface agents are re-rendered, settings are patched additively, new
|
|
|
206
237
|
wired. **`/cohorte-init-pipeline` is one-time per project** — after init, `/cohorte-update-pipeline` is the only
|
|
207
238
|
maintenance command you ever run (`/cohorte-build` auto-grows surfaces as specs need them).
|
|
208
239
|
|
|
209
|
-
## Dashboard — a local web cockpit
|
|
210
|
-
|
|
211
|
-
A browser view of pipeline state, for when a checklist beats scanning files:
|
|
212
|
-
|
|
213
|
-
```sh
|
|
214
|
-
cohorte dashboard # serves http://localhost:4317 (Ctrl-C to stop)
|
|
215
|
-
cohorte dashboard <path> # start focused on another project
|
|
216
|
-
cohorte dashboard --port=4400 --open # custom port, open the browser
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
**Bound to `127.0.0.1` by default** — the dashboard's actions execute code (install/update/reset,
|
|
220
|
-
and `/cohorte-init-pipeline`·`/cohorte-update-pipeline`·`/cohorte-audit` via headless Claude), so it must stay on loopback. Each user
|
|
221
|
-
runs their own agent and drives only their own machine. `--host=0.0.0.0` exposes it to the network
|
|
222
|
-
(it prints a security warning) — only on a trusted network, since anyone who reaches the port can run
|
|
223
|
-
those actions.
|
|
224
|
-
|
|
225
|
-
- **Fleet overview** — the global core version vs npm latest, plus every tracked project's freshness
|
|
226
|
-
and health at a glance. Add a project by absolute path or with the **folder picker** (Browse…); the
|
|
227
|
-
set is remembered in `~/.claude/cohorte-dashboard.json`.
|
|
228
|
-
- **Per-project drill-down** — Freshness (installed core vs npm), `/cohorte-doctor` rendered as a live
|
|
229
|
-
✅/⚠️/❌ checklist (each failure with its fix), the **Surfaces ↔ agents** map from `PIPELINE.md`,
|
|
230
|
-
and one board: a **Kanban** if the project has a linked Obsidian board (columns + cards from the
|
|
231
|
-
vault, with clickable PR links + live open/merged/closed status and a ship-date-sorted Shipped
|
|
232
|
-
column, via `gh`), otherwise a **Specs board** from `specs/*.md` (by `draft · frozen · in-progress ·
|
|
233
|
-
in-review · blocked · shipped`). The Kanban supersedes the Specs board when both would apply.
|
|
234
|
-
- **Actions** (stream their output live) — **Update / Install core** (the shared global core, or a
|
|
235
|
-
repo's bundled core); **Init-pipeline / Update-pipeline / Audit**, which run those Claude Code
|
|
236
|
-
commands **headless** (`claude -p`, autonomous — Init skips the interactive interview, so review
|
|
237
|
-
the result; Audit writes `specs/refactor-backlog.md`; headless runs start without any prompt and
|
|
238
|
-
have **no resume** if the session dies); and **Reset pipeline**, which backs up then wipes a
|
|
239
|
-
project's pipeline footprint and reinstalls a fresh core. Buttons render only when they apply
|
|
240
|
-
(e.g. Init only when there's no profile). The drill-down's health checklist also shows the
|
|
241
|
-
**workflows** state (scripts + profile-reader installed, which path a session will take).
|
|
242
|
-
|
|
243
|
-
Runtime is **dependency-free** — node's built-in `http` server serves a prebuilt React app (the app
|
|
244
|
-
source lives in `dashboard/app/`, built to `dashboard/dist/` at publish time). The `/cohorte-doctor` checks
|
|
245
|
-
are reimplemented in JS, so the dashboard needs no Claude session to compute state. See
|
|
246
|
-
[`dashboard/README.md`](dashboard/README.md) for the architecture.
|
|
247
|
-
|
|
248
240
|
## Reading a project without an agent
|
|
249
241
|
|
|
250
242
|
The two read-only halves of the pipeline, in the shell:
|
|
@@ -260,8 +252,8 @@ extension below renders.
|
|
|
260
252
|
|
|
261
253
|
`doctor`'s exit code makes it a CI step as-is. Add `--porcelain` for one record per line with
|
|
262
254
|
`U+001F` between fields (a spec title with a space in it never misaligns a column), or `--json`
|
|
263
|
-
for the native document. Both
|
|
264
|
-
|
|
255
|
+
for the native document. Both read through `lib/`, so every consumer — the CLI, a Francois
|
|
256
|
+
panel — answers from one implementation and the repo never gets two verdicts.
|
|
265
257
|
|
|
266
258
|
`--panel` — on `specs`, `doctor` and `metrics` — emits the payload shape a
|
|
267
259
|
[Francois](https://github.com/antoine-gmnz/francois) extension panel expects. It is the one
|
|
@@ -301,15 +293,18 @@ so a CLI pinned at an old version cannot quietly re-lay an old core.
|
|
|
301
293
|
| Command | Role |
|
|
302
294
|
| -------------------- | ------------------------------------------------------------------------------------- |
|
|
303
295
|
| `/cohorte-init-pipeline` | Detect stack → interview → generate the profile + agents. Run once per project. |
|
|
296
|
+
| `/cohorte-intake [paste]` | Triage anything that arrives (ticket, email, trace, thread) into a `/cohorte-patch` handoff or a brainstorm seed — staged to disk, carded in Ideas. |
|
|
304
297
|
| `/cohorte-brainstorm` | Interactive persona panel that pressure-tests a feature idea. |
|
|
305
298
|
| `/cohorte-spec` | Freeze the feature spec + contract into `specs/<id>.md` (UI features also get a standalone design brief at `specs/design/<id>.md`). Also applies review returns. |
|
|
306
299
|
| `/cohorte-patch [bug]` | Bug-fix entry: triage a bug and freeze a ~60-line patch spec (`specs/patch-<slug>.md`) whose regression test replaces the contract. Then the normal `/cohorte-build → review → ship`. |
|
|
307
300
|
| `/cohorte-build <id>` | Readiness gate on the frozen spec, then the lead authors the contract and dispatches one implementer per surface in parallel. |
|
|
308
|
-
| `/cohorte-review <id>` | Read-only review agents (one per touched surface, parallel) audit the diff vs the spec; out-of-scope findings go to the refactor backlog. |
|
|
301
|
+
| `/cohorte-review <id>` | Read-only review agents (one per touched surface, parallel) audit the diff vs the spec; out-of-scope findings go to the refactor backlog. `--pr <num>` reviews an **incoming** GitHub PR in a throwaway worktree and offers to post the report as a comment. |
|
|
309
302
|
| `/cohorte-fix <id>` | Apply a review report: remediation into the spec, re-dispatch only the surfaces with findings. |
|
|
303
|
+
| `/cohorte-fleet plan\|status\|sync` | Fly several features in parallel: overlap matrix + merge order, one worktree per feature, live status, post-merge rebase sweep. Coordination only — never headless execution. |
|
|
310
304
|
| `/cohorte-ship <id>` | Release agent commits, pushes, opens the PR; watches CI; proposes worktree teardown. |
|
|
311
305
|
| `/cohorte-audit [path]` | Prioritized refactor backlog for existing code. |
|
|
312
306
|
| `/cohorte-refactor <domain>` | Apply the backlog for one surface, TDD-first. |
|
|
307
|
+
| `/cohorte-retro [last n]` | Mine the accumulated review findings for patterns; ratified ones become §Conventions rules and the surface agents are re-rendered — the next build never produces the finding. |
|
|
313
308
|
| `/cohorte-align-ds` | Align the code UI kit to the design system (no-op if none configured). |
|
|
314
309
|
| `/cohorte-update-pipeline` | Refresh the installed core (global or bundled) to the latest published version. |
|
|
315
310
|
| `/cohorte-doctor` | Diagnose the installation (core, agents↔surfaces, hooks, gate, retrieval, worktrees). |
|
|
@@ -415,7 +410,10 @@ See `profile/SCHEMA.md` for every field in `PIPELINE.md` and how the pipeline us
|
|
|
415
410
|
|
|
416
411
|
```
|
|
417
412
|
package.json # npm package (cohorte) — semver source of truth
|
|
418
|
-
bin/cli.js # the npm CLI: install / update /
|
|
413
|
+
bin/cli.js # the npm CLI: install / update / specs / doctor / metrics / version (cross-platform, no deps)
|
|
414
|
+
bin/report.js # the four renderings of `specs` / `doctor` (human, porcelain, json, panel)
|
|
415
|
+
lib/ # shared readers behind those verbs — doctor.js (the JS port of
|
|
416
|
+
# /cohorte-doctor) + runtime.js / versions.js / yaml.js. No deps.
|
|
419
417
|
install.sh # script installer (fresh + --update) for npm-less setups — still needs Node
|
|
420
418
|
install.ps1 # same installer for Windows PowerShell (fresh + -Update)
|
|
421
419
|
core/ # copied verbatim into ~/.claude (global) or <project>/.claude (bundled)
|
|
@@ -429,7 +427,4 @@ profile/
|
|
|
429
427
|
SCHEMA.md # field reference
|
|
430
428
|
cohorte.config.template.yaml # seeds ~/.claude/cohorte.config.yaml (kanban)
|
|
431
429
|
scripts/ # worktree-isolation templates + the shipped preflight/kanban scripts
|
|
432
|
-
dashboard/ # local web cockpit (cohorte dashboard) — see dashboard/README.md
|
|
433
|
-
server/ # dependency-free node runtime (serves the built app + JSON/stream API)
|
|
434
|
-
app/ # Vite + React source (built to dashboard/dist/ at publish time)
|
|
435
430
|
```
|
package/bin/cli.js
CHANGED
|
@@ -40,7 +40,6 @@ function usage(code) {
|
|
|
40
40
|
Usage:
|
|
41
41
|
cohorte install [target] [--global] [--runtime=a,b | --all-runtimes]
|
|
42
42
|
cohorte update [target] [--global] [--runtime=a,b | --all-runtimes]
|
|
43
|
-
cohorte dashboard [target] [--port=N] [--host=ADDR] [--open]
|
|
44
43
|
cohorte metrics [target] [--days=N] [--since=ISO] [--runs] [--json]
|
|
45
44
|
cohorte specs [target] [--porcelain | --json | --panel]
|
|
46
45
|
cohorte doctor [target] [--porcelain | --json | --panel]
|
|
@@ -53,11 +52,6 @@ Commands:
|
|
|
53
52
|
update Refresh the stack-agnostic core only. PIPELINE.md, rendered surface
|
|
54
53
|
agents, gate-config.json, settings.json and your filled
|
|
55
54
|
~/.claude/cohorte.config.yaml are never touched.
|
|
56
|
-
dashboard Serve a local web cockpit for the pipeline (freshness, /cohorte-doctor
|
|
57
|
-
health, specs board, install/update actions). Binds 127.0.0.1:4317
|
|
58
|
-
by default (loopback only — its actions execute code). --host=ADDR
|
|
59
|
-
to expose (e.g. --host=0.0.0.0, prints a security warning). --open
|
|
60
|
-
to launch the browser.
|
|
61
55
|
metrics Cost and runtime per command, reconstructed from Claude Code's own
|
|
62
56
|
transcripts (tokens, USD, wall/active time, subagent count). Reads
|
|
63
57
|
~/.claude/projects — nothing to enable, and it covers runs that
|
|
@@ -90,11 +84,6 @@ const args = process.argv.slice(2);
|
|
|
90
84
|
let mode = null;
|
|
91
85
|
let scope = 'project';
|
|
92
86
|
let target = process.cwd();
|
|
93
|
-
let port = parseInt(process.env.COHORTE_DASHBOARD_PORT, 10) || 4317;
|
|
94
|
-
// Bind to loopback by default — the dashboard's action endpoints execute code (install/update/
|
|
95
|
-
// reset/claude), so it must NOT be reachable from the network unless the user explicitly opts in.
|
|
96
|
-
let host = process.env.COHORTE_DASHBOARD_HOST || '127.0.0.1';
|
|
97
|
-
let openBrowser = false;
|
|
98
87
|
|
|
99
88
|
// Flags that belong to `metrics` and are forwarded verbatim to the collector. They are
|
|
100
89
|
// listed here rather than parsed, so the collector stays the single source of truth for
|
|
@@ -112,7 +101,7 @@ let format = 'human';
|
|
|
112
101
|
let wantRuntimes = [];
|
|
113
102
|
|
|
114
103
|
for (const a of args) {
|
|
115
|
-
if (a === 'install' || a === 'update' || a === '
|
|
104
|
+
if (a === 'install' || a === 'update' || a === 'metrics'
|
|
116
105
|
|| a === 'specs' || a === 'doctor') mode = a;
|
|
117
106
|
else if (a === '--porcelain' || a === '--panel') format = a.slice(2);
|
|
118
107
|
else if (isMetricsFlag(a)) { if (a === '--json') format = 'json'; metricsFlags.push(a); }
|
|
@@ -131,40 +120,18 @@ for (const a of args) {
|
|
|
131
120
|
}
|
|
132
121
|
else if (a === 'version' || a === '--version' || a === '-v') { console.log(VERSION); process.exit(0); }
|
|
133
122
|
else if (a === '--global' || a === '-g') scope = 'global';
|
|
134
|
-
else if (a.startsWith('--port=')) {
|
|
135
|
-
// A bad value used to land as NaN, which http.listen() silently treats as
|
|
136
|
-
// "any free port" — the banner then printed `localhost:NaN` and nothing worked.
|
|
137
|
-
port = parseInt(a.slice(7), 10);
|
|
138
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
139
|
-
console.error(`error: --port must be an integer 1-65535 (got "${a.slice(7)}")`);
|
|
140
|
-
process.exit(2);
|
|
141
|
-
}
|
|
142
|
-
} else if (a.startsWith('--host=')) {
|
|
143
|
-
host = a.slice(7).trim();
|
|
144
|
-
if (!host) { console.error('error: --host= needs an address (e.g. --host=0.0.0.0)'); process.exit(2); }
|
|
145
|
-
}
|
|
146
|
-
else if (a === '--open') { openBrowser = true; }
|
|
147
123
|
else if (a === 'help' || a === '--help' || a === '-h') usage(0);
|
|
148
124
|
else if (a.startsWith('-')) { console.error(`error: unknown flag: ${a}`); usage(2); }
|
|
149
125
|
else target = path.resolve(a);
|
|
150
126
|
}
|
|
151
127
|
if (!mode) usage(args.length ? 2 : 0);
|
|
152
128
|
|
|
153
|
-
// --- dashboard: local web cockpit -------------------------------------------
|
|
154
|
-
// Short-circuits before the install/update machinery (CommonJS wraps the module,
|
|
155
|
-
// so a top-level return is valid here). Runtime is dependency-free node `http`.
|
|
156
|
-
if (mode === 'dashboard') {
|
|
157
|
-
const globalDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
158
|
-
require('../dashboard/server')({ projectRoot: target, globalDir, port, host, openBrowser, pkgRoot, version: VERSION });
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
129
|
// --- specs / doctor: read-only reports on <target> ---------------------------
|
|
163
|
-
// Both
|
|
164
|
-
//
|
|
130
|
+
// Both read through lib/, so every consumer of the pipeline's state — the CLI, a
|
|
131
|
+
// Francois panel — answers from one implementation. Nothing here writes or spawns.
|
|
165
132
|
if (mode === 'specs' || mode === 'doctor') {
|
|
166
133
|
const report = require('./report.js');
|
|
167
|
-
const { state, scanSpecs } = require('../
|
|
134
|
+
const { state, scanSpecs } = require('../lib/doctor.js');
|
|
168
135
|
const globalDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
169
136
|
|
|
170
137
|
if (mode === 'specs') {
|
|
@@ -342,8 +309,8 @@ function copyCore() {
|
|
|
342
309
|
paths: {
|
|
343
310
|
core: paths.core, commands: paths.commands,
|
|
344
311
|
agents: paths.agents || path.join(paths.core, 'agents'),
|
|
345
|
-
// Where the gate registration lives.
|
|
346
|
-
// from a missing one without re-deriving each runtime's config layout itself.
|
|
312
|
+
// Where the gate registration lives. `cohorte doctor` needs it to tell a registered
|
|
313
|
+
// hook from a missing one without re-deriving each runtime's config layout itself.
|
|
347
314
|
hooks_config: paths.hooks_config,
|
|
348
315
|
state: adapter.stateDir(runtime), config: adapter.configPath(runtime),
|
|
349
316
|
},
|
|
@@ -565,7 +532,7 @@ async function seedConfig() {
|
|
|
565
532
|
//
|
|
566
533
|
// CLAUDE_CONFIG_DIR moves the whole `~/.claude` tree (globalDir already follows it), but
|
|
567
534
|
// adapter.configPath() speaks in literal `~/.claude/…` — expanding through HOME alone
|
|
568
|
-
// seeded a file at a path no reader probes (kanban-move.sh
|
|
535
|
+
// seeded a file at a path no reader probes (kanban-move.sh follows
|
|
569
536
|
// CLAUDE_CONFIG_DIR), missed a filled config there (re-seeding disabled defaults beside
|
|
570
537
|
// it — the exact two-file fork this function's comments forbid), and printed a banner
|
|
571
538
|
// path that did not exist. Re-root the claude-shaped path onto globalDir so the seed,
|
|
@@ -705,7 +672,7 @@ async function staleVersionNotice() {
|
|
|
705
672
|
if (process.env.COHORTE_NO_VERSION_CHECK || process.env.CI) return;
|
|
706
673
|
let latest = null;
|
|
707
674
|
try {
|
|
708
|
-
const v = require('../
|
|
675
|
+
const v = require('../lib/versions.js');
|
|
709
676
|
latest = await v.latestNpm({ timeoutMs: 2500, fallback: false });
|
|
710
677
|
if (!latest || v.cmpSemver(VERSION, latest) >= 0) return;
|
|
711
678
|
} catch { return; }
|
package/bin/report.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
// Machine-readable reports for `cohorte specs` and `cohorte doctor`.
|
|
3
3
|
//
|
|
4
|
-
// Both commands read what
|
|
5
|
-
//
|
|
4
|
+
// Both commands read what lib/doctor.js computes (`scanSpecs` and `state`) and
|
|
5
|
+
// render it four ways:
|
|
6
6
|
//
|
|
7
7
|
// default a human table on a terminal
|
|
8
8
|
// --porcelain one record per line, fields separated by U+001F (the ASCII unit
|
|
@@ -31,7 +31,11 @@ Idea (may be empty): **$ARGUMENTS**
|
|
|
31
31
|
|
|
32
32
|
If the idea is empty: when a board is configured and its **Ideas** column has cards, list them (with any
|
|
33
33
|
sub-bullet notes as seed context) and let the human pick one — otherwise ask **"What are we building?"**.
|
|
34
|
-
Either way, wait. If the idea is non-empty, restate it in one line and confirm you've got it
|
|
34
|
+
Either way, wait. If the idea is non-empty, restate it in one line and confirm you've got it — and
|
|
35
|
+
**if `specs/reports/intake-<idea>.md` exists, read it FIRST**: it is `/cohorte-intake`'s staged seed
|
|
36
|
+
(who is asking with verbatim quotes, goals/non-goals, an open-questions list that is this panel's
|
|
37
|
+
agenda, prior art incl. any `_decisions.md` line the request contradicts). The panel argues against
|
|
38
|
+
the distillate, never against the bare slug.
|
|
35
39
|
|
|
36
40
|
## Run the panel
|
|
37
41
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
model: sonnet
|
|
3
|
+
description: Fly several features in parallel — overlap analysis and merge order, one isolated worktree per feature, a live status board, and the post-merge rebase sweep nobody remembers to do.
|
|
4
|
+
argument-hint: plan <id> <id> [...] | status | sync
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the **fleet controller**. Parallel features already work in this pipeline — isolated
|
|
8
|
+
worktrees, per-feature DBs and ports, one session per worktree — but the coordination between
|
|
9
|
+
them is tribal knowledge: which specs collide, which merges first, and who rebases the
|
|
10
|
+
survivors after each ship. This command owns exactly that coordination, and deliberately
|
|
11
|
+
nothing more.
|
|
12
|
+
|
|
13
|
+
**What this command will never do: spawn the work headless.** The obvious "cool" version —
|
|
14
|
+
fire a loop per worktree from here — is the 2.2.0 driver's grave: child sessions nobody
|
|
15
|
+
supervises, stalling on prompts nobody sees. Each feature's build/review/loop runs in **its own
|
|
16
|
+
worktree's session**, supervised like any other; the fleet plans, watches and rebases. State
|
|
17
|
+
lives in `specs/reports/fleet.json` (the main checkout's gitignored buffer — this repo's
|
|
18
|
+
multi-*feature* flight plan).
|
|
19
|
+
|
|
20
|
+
> Read `PIPELINE.md` §`pipeline-profile` first: `surfaces` (paths — the overlap analysis keys
|
|
21
|
+
> on them), `contract`, `vcs.default_branch`, and the `isolation` block. _Skip the re-read if
|
|
22
|
+
> already in context and unmodified since._ **`isolation.enabled: false` ⇒ stop at `plan`**:
|
|
23
|
+
> N features in one checkout is a merge-conflict generator with extra steps — name the fix
|
|
24
|
+
> (`/cohorte-init-pipeline` wires isolation) and go no further.
|
|
25
|
+
|
|
26
|
+
## `plan <id> <id> [...]` — collide, order, provision
|
|
27
|
+
|
|
28
|
+
1. **Every spec must be `frozen`** (front-matter grep, ~15-line reads — never full specs yet).
|
|
29
|
+
Anything else (`draft`, `in-review`, missing) ⇒ name it and stop; a fleet of half-frozen
|
|
30
|
+
specs is N problems flying in formation.
|
|
31
|
+
2. **Overlap analysis** — read each spec's §5 contract entries and §6 surface tasks, then build
|
|
32
|
+
the matrix *feature × surface*, and flag the two collision classes:
|
|
33
|
+
- **Contract dependency** — spec B's §5 references shapes spec A introduces ⇒ B ships
|
|
34
|
+
**after** A, and B's worktree must rebase once A merges (the `sync` mode's job). This is
|
|
35
|
+
an ordering, not a blocker.
|
|
36
|
+
- **Same-tree writes** — two specs whose §6 tasks land in the same `surfaces[].path` (worse:
|
|
37
|
+
the same module). This is where parallel merges bleed; propose either an order (lighter
|
|
38
|
+
feature first) or — when the overlap is one file both must edit — say plainly that these
|
|
39
|
+
two should not fly together, and let the human drop one from the fleet.
|
|
40
|
+
3. **Propose the merge order** from those edges (dependencies first, then ascending overlap),
|
|
41
|
+
show the matrix + order in a compact table, and get the human's go-ahead — the order is a
|
|
42
|
+
plan they will live with for days.
|
|
43
|
+
4. **Provision worktrees** — for each feature without one: `scripts/new-feature.sh <id>` (the
|
|
44
|
+
rendered isolation script: worktree + branch + DB + port slot). Relay each script's output
|
|
45
|
+
line; a script failure stops the plan for that feature, never silently.
|
|
46
|
+
5. **Write `specs/reports/fleet.json`** (overwrite): `{"ts":"<ISO>","order":[...ids in merge
|
|
47
|
+
order...],"features":{"<id>":{"worktree":"<path>","branch":"<branch>","dependsOn":[...]}}}`.
|
|
48
|
+
6. **Print the launch plan** — one line per feature, in order: the worktree path to open a
|
|
49
|
+
session in, and the first command to run there (`/cohorte-build <id>`, or "ask for the loop
|
|
50
|
+
workflow: `{feature: \"<id>\"}`" on a runtime that has it). The human launches them; the
|
|
51
|
+
fleet does not.
|
|
52
|
+
|
|
53
|
+
## `status` — one table, no archaeology
|
|
54
|
+
|
|
55
|
+
Read `fleet.json` (absent ⇒ say `plan` comes first, stop). For each feature, **mechanical reads
|
|
56
|
+
only, redirected — always from THAT feature's worktree**, never the main checkout (each worktree
|
|
57
|
+
carries its own copy of `specs/<id>.md` and `specs/reports/`, and that copy is the one its run
|
|
58
|
+
has been writing): `<worktree>/specs/<id>.md` front-matter `status` · the worktree's
|
|
59
|
+
`specs/reports/<id>.loop.json` (`phase`/`round`/`outcome`) and `<id>.verdict.json`
|
|
60
|
+
(`verdict`/`blocking`) when present · then ONE
|
|
61
|
+
`git -C <worktree> fetch --quiet origin <default_branch> || true` and
|
|
62
|
+
`git -C <worktree> rev-list --count origin/<default_branch>..HEAD` and `..origin/<default_branch>`
|
|
63
|
+
(ahead / **behind** — behind is the number that matters, and against the *remote* ref: the local
|
|
64
|
+
one goes stale the moment a PR merges on the host, which is precisely when status gets asked).
|
|
65
|
+
One row per feature, in merge order:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
<id> · <status> · loop: <phase> r<round> | <outcome> · blocking: <n> · ↑<ahead> ↓<behind> · next: <the one action>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`next` is the whole point of the mode: the single action per feature (a command to run in its
|
|
72
|
+
worktree, "waiting on <dep> to merge", "ready to ship — its turn in the order", or "rebase
|
|
73
|
+
needed — run sync"). A worktree registered in `fleet.json` but gone from
|
|
74
|
+
`git worktree list` is reported as such, never silently dropped.
|
|
75
|
+
|
|
76
|
+
## `sync` — the post-merge sweep
|
|
77
|
+
|
|
78
|
+
Run after every merge (the human says which feature shipped, or you detect it: spec
|
|
79
|
+
`status: shipped` + branch merged into `<default_branch>`):
|
|
80
|
+
|
|
81
|
+
1. Drop the shipped feature from `fleet.json` (rewrite, keep order of the rest) — and if its
|
|
82
|
+
worktree still exists, remind the teardown `/cohorte-ship` proposes:
|
|
83
|
+
`scripts/remove-feature.sh <id>` (`--drop-db` at the human's call). Never run it unasked.
|
|
84
|
+
2. For each surviving worktree, in merge order — `git -C <worktree> fetch origin
|
|
85
|
+
<default_branch>` **always**, then decide whether the rebase is THIS session's to run:
|
|
86
|
+
- **Tree dirty** (`git -C <worktree> status --porcelain` non-empty — the NORMAL mid-flight
|
|
87
|
+
state: feature work stays uncommitted until `/cohorte-ship`, so `git rebase` would refuse
|
|
88
|
+
with "unstaged changes" before any conflict even exists) **or a run in flight** (the
|
|
89
|
+
worktree's `loop.json` has no `outcome`): **do not touch it.** Report the row as
|
|
90
|
+
`rebase needed — run \`git rebase origin/<default_branch>\` from that worktree's own
|
|
91
|
+
session` (mutating a branch from outside its session, mid-run, is how work disappears).
|
|
92
|
+
- **Clean and idle**: rebase it, output redirected to `specs/reports/fleet-sync.txt`. A
|
|
93
|
+
conflict is reported verbatim and left for its owner (`git rebase --abort` restores) —
|
|
94
|
+
never resolved, never `--force`-anything from here.
|
|
95
|
+
3. **Say the consequence out loud, per rebased (or rebase-needed) worktree:** the rebase moves
|
|
96
|
+
every commit, so any `reviewed_base`/`reviewed_digest` in that spec then describes a tree
|
|
97
|
+
that no longer exists — `/cohorte-ship` will (rightly) refuse until a fresh
|
|
98
|
+
`/cohorte-review` re-stamps it. A clean rebase is not a re-verdict; the re-review is.
|
|
99
|
+
4. Reprint the `status` table.
|
|
100
|
+
|
|
101
|
+
In chat, every mode prints its table/plan and nothing else — the matrix evidence and rebase
|
|
102
|
+
logs live in `specs/reports/`. **Recommend a `/clear`** after `plan` (the flight plan is on
|
|
103
|
+
disk; the sessions doing the flying are elsewhere anyway).
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Triage anything that arrives — a ticket, a client email, a stack trace, a Slack thread — into the pipeline's entry point: a patch handoff or a brainstorm seed.
|
|
3
|
+
argument-hint: [paste the raw material — ticket, email, trace, thread — or empty to be asked]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are the **intake triager**. Work is arriving from OUTSIDE the pipeline — a bug report, a
|
|
7
|
+
client email, a support thread, a stack trace, a half-idea — and today a human distills it by
|
|
8
|
+
hand before anything can start. Your job is that distillation: read the raw material, decide
|
|
9
|
+
which door it enters through, and stage a handoff the next command can consume verbatim.
|
|
10
|
+
|
|
11
|
+
> Read `PIPELINE.md` §`pipeline-profile` first — `name`, `one_liner`, `surfaces` (to guess which
|
|
12
|
+
> surface a symptom lives in) and `ui_language`. _Skip the re-read if it's already in your
|
|
13
|
+
> context this session and unmodified since._ Read `specs/_decisions.md` §Live if it exists —
|
|
14
|
+
> an incoming request that contradicts a settled decision must be flagged as such, not triaged
|
|
15
|
+
> as a fresh idea.
|
|
16
|
+
|
|
17
|
+
## 1. Ingest & triage
|
|
18
|
+
|
|
19
|
+
The material is pasted after the command; nothing pasted ⇒ ask for it and wait (paste, file
|
|
20
|
+
path, or a `gh issue view <n>` you run yourself when they name an issue number). Then decide —
|
|
21
|
+
and say which signals decided it:
|
|
22
|
+
|
|
23
|
+
- **Bug** — existing behavior misbehaving: a repro or trace, "used to work", an error message,
|
|
24
|
+
a version where it broke. → §2.
|
|
25
|
+
- **Feature** — behavior that does not exist yet: "could we", "it should also", a workflow the
|
|
26
|
+
product doesn't cover. → §3.
|
|
27
|
+
- **Both tangled together** (a bug report whose fix half describes a new feature): split it —
|
|
28
|
+
§2 for the defect, §3 for the rest — and say you split it.
|
|
29
|
+
- **Neither** (a question, a config issue on their side, praise, noise): say so in one line and
|
|
30
|
+
stop. Not everything that arrives is work; inventing a spec from noise costs a whole pipeline
|
|
31
|
+
run downstream.
|
|
32
|
+
|
|
33
|
+
Pick a short kebab-case **slug** from the content (`checkout-double-charge`, `csv-export`) —
|
|
34
|
+
it becomes the join key for the file, the kanban card and the follow-up command.
|
|
35
|
+
|
|
36
|
+
## 2. Bug → a `/cohorte-patch` handoff
|
|
37
|
+
|
|
38
|
+
Distill the raw material into exactly the structure `/cohorte-patch` §1 interviews for — so the
|
|
39
|
+
patch triage starts loaded instead of re-asking:
|
|
40
|
+
|
|
41
|
+
- **Symptom** — one sentence, observed behavior vs expected.
|
|
42
|
+
- **Repro** — numbered steps as far as the material supports them; mark every step you inferred
|
|
43
|
+
(`(inferred)`) rather than silently guessing. A trace with no steps ⇒ the trace IS the repro
|
|
44
|
+
material, say so.
|
|
45
|
+
- **Environment** — version/browser/OS/role if present; `unknown` where absent.
|
|
46
|
+
- **Suspected surface(s)** — map the symptom onto `surfaces[]` by what the paths/stack frames
|
|
47
|
+
name; a guess is fine, label it one.
|
|
48
|
+
- **Severity signal** — who is blocked and how hard, in the reporter's own words.
|
|
49
|
+
|
|
50
|
+
**Stage it** to `specs/reports/intake-<slug>.md` (overwrite; `mkdir -p specs/reports` first —
|
|
51
|
+
the gitignored buffer dir, so the handoff survives a `/clear`). Then:
|
|
52
|
+
|
|
53
|
+
> **Kanban** (SCHEMA.md §Kanban): run
|
|
54
|
+
> `<core>/pipeline/scripts/kanban-move.sh auto patch-<slug> ideas --title "[patch] <one-line title>"`.
|
|
55
|
+
> The id is **`patch-<slug>`, prefix included, and the title prefix is `[patch]`** — that is the
|
|
56
|
+
> exact join key and the exact title `/cohorte-patch` §1 looks for, so its later move finds THIS
|
|
57
|
+
> card instead of creating a duplicate and stranding this one in Ideas (the failure SCHEMA
|
|
58
|
+
> §Kanban's "tag before you move" exists to prevent). `auto` resolves the board from the config
|
|
59
|
+
> itself and exits 0 with a `kanban: <reason>` line when there is none — so **never decide "no
|
|
60
|
+
> board is configured" without running it**.
|
|
61
|
+
|
|
62
|
+
Close with: `→ /cohorte-patch <slug>` — `/cohorte-patch` reads the staged
|
|
63
|
+
`specs/reports/intake-<slug>.md` when it exists, so the triage starts loaded. Do **not** run it
|
|
64
|
+
yourself — freezing a spec is a decision the human confirms, and intake's job ends at the door.
|
|
65
|
+
|
|
66
|
+
## 3. Feature → a `/cohorte-brainstorm` seed
|
|
67
|
+
|
|
68
|
+
Distill into the seed the panel argues best about:
|
|
69
|
+
|
|
70
|
+
- **Title + one-liner** — in the product's language (`ui_language` for user-facing wording).
|
|
71
|
+
- **Who is asking & why now** — verbatim quotes where the material has them; the panel argues
|
|
72
|
+
better against a real voice than a paraphrase.
|
|
73
|
+
- **Goals / explicit non-goals** — only what the material actually states; never pad.
|
|
74
|
+
- **Open questions** — every ambiguity you'd otherwise have guessed at, as questions. This list
|
|
75
|
+
is the seed's real value: it is the brainstorm's agenda.
|
|
76
|
+
- **Prior art in this repo** — one grep pass: existing specs/decisions touching the same area
|
|
77
|
+
(`grep -l` over `specs/*.md`, redirected — never a file read per name). Contradicts a
|
|
78
|
+
`_decisions.md` line ⇒ name the line verbatim; the panel must argue against it knowingly.
|
|
79
|
+
|
|
80
|
+
**Stage it** to `specs/reports/intake-<slug>.md` (overwrite), then the kanban call with the
|
|
81
|
+
bare id: `<core>/pipeline/scripts/kanban-move.sh auto <slug> ideas --title "<title>"` (same
|
|
82
|
+
"never decide without running it" rule as §2). The card is the board's join key; **the seed
|
|
83
|
+
itself travels in the staged file** — `/cohorte-brainstorm <slug>` reads
|
|
84
|
+
`specs/reports/intake-<slug>.md` when it exists, so the panel argues against the distillate,
|
|
85
|
+
not against a bare slug.
|
|
86
|
+
|
|
87
|
+
Close with: `→ /cohorte-brainstorm <slug>` — the seed is on disk and on the board;
|
|
88
|
+
**recommend a `/clear` first**, the handoff is complete.
|
|
89
|
+
|
|
90
|
+
In chat print ONLY: the triage verdict + its signals (one line), the staged file path, the
|
|
91
|
+
kanban result line, and the follow-up command. Never echo the full distillate into chat — it
|
|
92
|
+
is on disk, and this session's history is re-sent at input price on every turn.
|
|
@@ -28,7 +28,12 @@ Bug (may be empty): **$ARGUMENTS**
|
|
|
28
28
|
|
|
29
29
|
## 1. Get the bug
|
|
30
30
|
|
|
31
|
-
If `$ARGUMENTS` is non-empty, restate it in one line and confirm you've got it
|
|
31
|
+
If `$ARGUMENTS` is non-empty, restate it in one line and confirm you've got it — and if it is
|
|
32
|
+
(or names) a slug with a staged `specs/reports/intake-<slug>.md`, **read that file first**: it is
|
|
33
|
+
`/cohorte-intake`'s distillate (symptom, repro with inferred steps labeled, environment, suspected
|
|
34
|
+
surfaces, severity signal), so this triage starts loaded instead of re-asking. Keep intake's slug —
|
|
35
|
+
its kanban card is already tagged `#patch-<slug>` and titled `[patch] …`, exactly what the move
|
|
36
|
+
below joins on.
|
|
32
37
|
|
|
33
38
|
If it is empty: when a board is configured and its **Ideas** column has cards, list them (with any
|
|
34
39
|
sub-bullet notes as context) — **cards titled `[patch]` first**, since those are the ones a human
|