cohorte 1.6.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +84 -2
- package/README.md +57 -57
- package/bin/cli.js +23 -15
- package/core/agents/implementer.template.md +3 -3
- package/core/agents/release.md +1 -1
- package/core/agents/review.md +3 -3
- package/core/commands/{audit.md → cohorte-audit.md} +3 -3
- package/core/commands/{brainstorm.md → cohorte-brainstorm.md} +4 -4
- package/core/commands/{build.md → cohorte-build.md} +15 -15
- package/core/commands/{doctor.md → cohorte-doctor.md} +17 -9
- package/core/commands/{fix.md → cohorte-fix.md} +15 -13
- package/core/commands/{init-pipeline.md → cohorte-init-pipeline.md} +1 -1
- package/core/commands/cohorte-loop.md +110 -0
- package/core/commands/{refactor.md → cohorte-refactor.md} +3 -3
- package/core/commands/{review.md → cohorte-review.md} +20 -19
- package/core/commands/{ship.md → cohorte-ship.md} +5 -5
- package/core/commands/{spec.md → cohorte-spec.md} +13 -13
- package/core/commands/{update-pipeline.md → cohorte-update-pipeline.md} +11 -6
- package/core/hooks/gate.py +101 -6
- package/core/templates/brainstorm-return.md +4 -4
- package/core/templates/decisions.template.md +1 -1
- package/core/templates/design-brief.md +1 -1
- package/core/templates/spec.template.md +7 -7
- package/core/templates/steps/init-pipeline/01-detect-stack.md +1 -1
- package/core/templates/steps/init-pipeline/02-interview-gaps.md +6 -6
- package/core/templates/steps/init-pipeline/03-draft-profile.md +1 -1
- package/core/templates/steps/init-pipeline/04-write-render.md +16 -12
- package/core/templates/steps/init-pipeline/05-report.md +5 -5
- package/core/workflows/audit.js +6 -6
- package/core/workflows/refactor.js +14 -14
- package/core/workflows/review.js +22 -22
- package/dashboard/README.md +2 -2
- package/dashboard/dist/assets/{index-DYyn4p93.js → index-P1I1JGtj.js} +2 -2
- package/dashboard/dist/index.html +1 -1
- package/dashboard/server/doctor.js +69 -19
- package/dashboard/server/index.js +5 -5
- package/dashboard/server/metrics.js +1 -1
- package/install.ps1 +23 -14
- package/install.sh +24 -14
- package/package.json +2 -2
- package/profile/PIPELINE.template.md +17 -16
- package/profile/SCHEMA.md +89 -77
- package/profile/cohorte.config.template.yaml +8 -8
- package/scripts/loop-detach.sh +153 -0
- package/scripts/loop.sh +75 -27
- package/scripts/metrics/collect.mjs +17 -8
- package/scripts/new-feature.sh.template +3 -3
- package/scripts/preflight.sh +40 -4
- package/scripts/remove-feature.sh.template +2 -2
- package/scripts/test-dashboard.mjs +34 -7
- package/scripts/test-gate.mjs +58 -0
- package/scripts/test-loop.mjs +49 -7
- package/scripts/test-metrics.mjs +23 -11
- package/scripts/test-workflows.mjs +7 -7
- package/scripts/validate-core.mjs +45 -23
- package/core/commands/drive.md +0 -80
- /package/core/commands/{align-ds.md → cohorte-align-ds.md} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
model: sonnet
|
|
3
|
-
description: Refresh the pipeline core (global ~/.claude, or a repo's bundled .claude) to the latest published cohorte version, then reconcile this repo's generated files to it — /init-pipeline stays one-time.
|
|
3
|
+
description: Refresh the pipeline core (global ~/.claude, or a repo's bundled .claude) to the latest published cohorte version, then reconcile this repo's generated files to it — /cohorte-init-pipeline stays one-time.
|
|
4
4
|
argument-hint: [path-to-local-checkout]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ You are the **pipeline updater**. Refresh the installed pipeline core to the lat
|
|
|
8
8
|
repo. The installer's `--update` mode never touches generated files: `PIPELINE.md`, rendered surface agents,
|
|
9
9
|
`gate-config.json`, `settings.json`, and the filled `~/.claude/cohorte.config.yaml` are all preserved.
|
|
10
10
|
YOU then bring those generated files up to the new core yourself (§3.5) — additively, never clobbering
|
|
11
|
-
the human's choices — so `/init-pipeline` never needs re-running for an upgrade.
|
|
11
|
+
the human's choices — so `/cohorte-init-pipeline` never needs re-running for an upgrade.
|
|
12
12
|
|
|
13
13
|
## 1. Detect the install scope + current version
|
|
14
14
|
|
|
@@ -57,7 +57,7 @@ differs from the core you just installed, rewrite that one field (leave every ot
|
|
|
57
57
|
and tell the human to commit it. In **bundled** mode the installer already did it; in **global** mode
|
|
58
58
|
**nothing does** — the installer refreshes one shared core and cannot know which repos point at it,
|
|
59
59
|
so before 1.2.5 the field simply drifted forever (a repo on a current core still claiming `1.0.0`).
|
|
60
|
-
`/doctor` check 1 requires the pointer to be coherent with the VERSION file, so a drifted field reads
|
|
60
|
+
`/cohorte-doctor` check 1 requires the pointer to be coherent with the VERSION file, so a drifted field reads
|
|
61
61
|
as a broken install when nothing is broken.
|
|
62
62
|
|
|
63
63
|
Then print **What's new**: read the installed `<core>/pipeline/CHANGELOG.md` and show the entries
|
|
@@ -82,9 +82,14 @@ health check** (SCHEMA.md §Code retrieval: CLI resolvable from PATH, `.mcp.json
|
|
|
82
82
|
upgrading a bare `serena` entry to the PATH-proof launcher form, `.serena/` gitignored, server
|
|
83
83
|
actually connected) and repair whatever fails — wiring that worked at
|
|
84
84
|
init can rot (PATH changes, uninstalls, hand-edits). Report what was reconciled; if nothing was
|
|
85
|
-
missing, say so. This is why `/init-pipeline` never needs re-running for a core upgrade.
|
|
85
|
+
missing, say so. This is why `/cohorte-init-pipeline` never needs re-running for a core upgrade.
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
Four of the §Reconcile steps matter specifically here:
|
|
88
|
+
|
|
89
|
+
- **Local-artifact hygiene** (§Reconcile step 8): gitignore + untrack the pipeline's runtime files
|
|
90
|
+
(`.claude/preflight.ok`, `.claude/pipeline-metrics.jsonl`, `specs/reports/`). A tracked
|
|
91
|
+
`preflight.ok` — what every pre-2.0.0 install ends up with once a release agent stages `.claude/` —
|
|
92
|
+
makes the phase gate ask on every single review dispatch, so fix it here and say so.
|
|
88
93
|
|
|
89
94
|
- **Spec-template top-up** (§Reconcile step 7): `specs/_template.md` was seeded at install and never
|
|
90
95
|
refreshed since, so add the front-matter fields the current `templates/spec.template.md` has and the
|
|
@@ -111,7 +116,7 @@ Three of the §Reconcile steps matter specifically here:
|
|
|
111
116
|
- **Restart / reload the Claude Code session** so it picks up updated commands, agents, and any
|
|
112
117
|
newly-registered MCP server.
|
|
113
118
|
- **Other repos using the global core:** their core is already fresh, but reconcile is per-repo — run
|
|
114
|
-
`/update-pipeline` inside each (it will skip the already-done core update and just reconcile).
|
|
119
|
+
`/cohorte-update-pipeline` inside each (it will skip the already-done core update and just reconcile).
|
|
115
120
|
- **Commit** the reconciled files (`PIPELINE.md`, `.claude/`, `.mcp.json` if added) so teammates get them.
|
|
116
121
|
- The kanban config is global and user-scoped
|
|
117
122
|
(`~/.claude/cohorte.config.yaml`) — never committed. The core update never touches it; only the
|
package/core/hooks/gate.py
CHANGED
|
@@ -6,7 +6,7 @@ inspecting the FULL command string so chained forms like
|
|
|
6
6
|
`cd apps/api && node ace migration:run` are caught — something prefix-based
|
|
7
7
|
permission rules in settings.json miss.
|
|
8
8
|
|
|
9
|
-
Patterns come from `.claude/gate-config.json` (generated by /init-pipeline from
|
|
9
|
+
Patterns come from `.claude/gate-config.json` (generated by /cohorte-init-pipeline from
|
|
10
10
|
the PIPELINE.md `gate` block):
|
|
11
11
|
|
|
12
12
|
{ "deny": ["node ace migration:fresh", ...],
|
|
@@ -16,7 +16,7 @@ A pattern matches a command segment when the segment *contains* the pattern
|
|
|
16
16
|
(after normalizing whitespace). `deny` wins over `ask`. If the config is missing
|
|
17
17
|
or unreadable, the hook stays silent (exit 0) and lets settings.json decide.
|
|
18
18
|
|
|
19
|
-
The hook fires for EVERY agent in the session — the lead, /build's implementers,
|
|
19
|
+
The hook fires for EVERY agent in the session — the lead, /cohorte-build's implementers,
|
|
20
20
|
and subagents spawned by the Workflow runtime alike. Workflow subagents run in
|
|
21
21
|
acceptEdits regardless of the session's permission mode (their Write/Edit calls
|
|
22
22
|
are auto-approved), but acceptEdits does NOT auto-approve Bash or Task, so this
|
|
@@ -31,7 +31,10 @@ Two extra duties beyond Bash patterns:
|
|
|
31
31
|
listed subagent_type (default review) requires a fresh
|
|
32
32
|
`.claude/preflight.ok` stamp, written by pipeline/scripts/preflight.sh when
|
|
33
33
|
typecheck+lint+tests are green. Stale/missing stamp => "ask" — dispatching
|
|
34
|
-
reviewers onto code that doesn't compile burns their whole run.
|
|
34
|
+
reviewers onto code that doesn't compile burns their whole run. Freshness is
|
|
35
|
+
a content digest (see tree_digest), not the HEAD sha: the reviewed tree is
|
|
36
|
+
normally dirty, so a commit must not invalidate a stamp and an uncommitted
|
|
37
|
+
edit must. The stamp is local and gitignored — never versioned.
|
|
35
38
|
|
|
36
39
|
Protocol: reads the PreToolUse payload on stdin; emits a JSON permissionDecision
|
|
37
40
|
of "deny" or "ask" on a match; otherwise exits 0 silently.
|
|
@@ -40,8 +43,10 @@ of "deny" or "ask" on a match; otherwise exits 0 silently.
|
|
|
40
43
|
import json
|
|
41
44
|
import os
|
|
42
45
|
import re
|
|
46
|
+
import shutil
|
|
43
47
|
import subprocess
|
|
44
48
|
import sys
|
|
49
|
+
import tempfile
|
|
45
50
|
import time
|
|
46
51
|
|
|
47
52
|
SPLIT = re.compile(r"&&|\|\||[;|\n]")
|
|
@@ -129,6 +134,84 @@ def known_heads(cwd: str):
|
|
|
129
134
|
return heads
|
|
130
135
|
|
|
131
136
|
|
|
137
|
+
def worktree_dirs(cwd: str):
|
|
138
|
+
"""`cwd` plus every linked worktree path — the checkouts a stamp may describe."""
|
|
139
|
+
dirs = [cwd]
|
|
140
|
+
try:
|
|
141
|
+
out = subprocess.run(
|
|
142
|
+
["git", "worktree", "list", "--porcelain"],
|
|
143
|
+
cwd=cwd, capture_output=True, text=True, timeout=3,
|
|
144
|
+
)
|
|
145
|
+
if out.returncode == 0:
|
|
146
|
+
for line in out.stdout.splitlines():
|
|
147
|
+
if line.startswith("worktree "):
|
|
148
|
+
p = line.split(" ", 1)[1].strip()
|
|
149
|
+
if p and p not in dirs:
|
|
150
|
+
dirs.append(p)
|
|
151
|
+
except Exception:
|
|
152
|
+
pass
|
|
153
|
+
return dirs
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def tree_digest(cwd: str):
|
|
157
|
+
"""The digest pipeline/scripts/preflight.sh stamps for the checkout at `cwd`: the git
|
|
158
|
+
tree id of the working tree, computed in a throwaway index (`.claude` and `specs`
|
|
159
|
+
excluded — the pipeline writes those itself). Content-addressed, so a commit of the
|
|
160
|
+
same code keeps the stamp valid and any real edit invalidates it. Must stay identical
|
|
161
|
+
to the shell side. Returns None when git can't answer, and the caller falls back."""
|
|
162
|
+
tmp = None
|
|
163
|
+
try:
|
|
164
|
+
idx = subprocess.run(
|
|
165
|
+
["git", "rev-parse", "--absolute-git-dir"],
|
|
166
|
+
cwd=cwd, capture_output=True, text=True, timeout=3,
|
|
167
|
+
)
|
|
168
|
+
if idx.returncode != 0:
|
|
169
|
+
return None
|
|
170
|
+
real_index = os.path.join(idx.stdout.strip(), "index")
|
|
171
|
+
fd, tmp = tempfile.mkstemp(prefix="cohorte-idx-")
|
|
172
|
+
os.close(fd)
|
|
173
|
+
# Seed from the real index to keep its stat cache — `add` then re-hashes only
|
|
174
|
+
# what changed instead of the whole tree on every dispatch.
|
|
175
|
+
if os.path.exists(real_index):
|
|
176
|
+
shutil.copyfile(real_index, tmp)
|
|
177
|
+
else:
|
|
178
|
+
os.unlink(tmp) # a 0-byte index is a corrupt index — let git create it
|
|
179
|
+
env = dict(os.environ, GIT_INDEX_FILE=tmp)
|
|
180
|
+
# Drop the excluded paths outright — an `add` exclude only stops them being
|
|
181
|
+
# *updated*, so already-tracked ones would still shift the tree id.
|
|
182
|
+
subprocess.run(
|
|
183
|
+
["git", "rm", "--cached", "-r", "-q", "--ignore-unmatch", "--", ".claude", "specs"],
|
|
184
|
+
cwd=cwd, capture_output=True, timeout=10, env=env,
|
|
185
|
+
)
|
|
186
|
+
add = subprocess.run(
|
|
187
|
+
["git", "add", "-A", "--", ".", ":(exclude).claude", ":(exclude)specs"],
|
|
188
|
+
cwd=cwd, capture_output=True, timeout=30, env=env,
|
|
189
|
+
)
|
|
190
|
+
if add.returncode != 0:
|
|
191
|
+
return None
|
|
192
|
+
out = subprocess.run(
|
|
193
|
+
["git", "write-tree"], cwd=cwd, capture_output=True, text=True, timeout=10, env=env,
|
|
194
|
+
)
|
|
195
|
+
if out.returncode != 0:
|
|
196
|
+
return None
|
|
197
|
+
return out.stdout.strip() or None
|
|
198
|
+
except Exception:
|
|
199
|
+
return None
|
|
200
|
+
finally:
|
|
201
|
+
for p in ((tmp, tmp + ".lock") if tmp else ()):
|
|
202
|
+
try:
|
|
203
|
+
os.unlink(p)
|
|
204
|
+
except Exception:
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def known_digests(cwd: str):
|
|
209
|
+
"""Every checkout's current digest. The preflight may have run in a feature worktree
|
|
210
|
+
while the Task dispatch fires from the main checkout (or vice versa) — same reason
|
|
211
|
+
known_heads() spans worktrees."""
|
|
212
|
+
return {d for d in (tree_digest(p) for p in worktree_dirs(cwd)) if d}
|
|
213
|
+
|
|
214
|
+
|
|
132
215
|
def check_preflight(payload: dict, cfg: dict) -> int:
|
|
133
216
|
"""Phase gate on Task dispatches: review agents need a green preflight stamp."""
|
|
134
217
|
pf = cfg.get("preflight") or {}
|
|
@@ -149,17 +232,29 @@ def check_preflight(payload: dict, cfg: dict) -> int:
|
|
|
149
232
|
why = "no preflight stamp found"
|
|
150
233
|
if raw is not None:
|
|
151
234
|
try:
|
|
152
|
-
|
|
235
|
+
fields = raw.split()
|
|
236
|
+
epoch_s = fields[0]
|
|
237
|
+
sha = fields[1] if len(fields) > 1 else ""
|
|
238
|
+
digest = fields[2] if len(fields) > 2 else ""
|
|
153
239
|
age_min = (time.time() - float(epoch_s)) / 60
|
|
154
240
|
max_age = float(pf.get("max_age_minutes", 30) or 30)
|
|
241
|
+
cwd = session_cwd(payload)
|
|
155
242
|
if age_min > max_age:
|
|
156
243
|
why = f"the preflight stamp is {age_min:.0f} min old (max {max_age:.0f})"
|
|
244
|
+
elif digest not in ("", "none"):
|
|
245
|
+
# 2.0.0+ stamp: key off the verified CONTENT, not HEAD. A commit that
|
|
246
|
+
# doesn't touch code keeps the stamp valid; an edit that never reaches a
|
|
247
|
+
# commit invalidates it — both of which the sha comparison got backwards.
|
|
248
|
+
digests = known_digests(cwd)
|
|
249
|
+
if digests and digest not in digests:
|
|
250
|
+
why = "the code changed since the preflight ran"
|
|
157
251
|
else:
|
|
158
|
-
|
|
252
|
+
# Pre-2.0.0 stamp (no digest) — fall back to the HEAD comparison.
|
|
253
|
+
heads = known_heads(cwd)
|
|
159
254
|
if heads and sha not in ("", "none") and sha not in heads:
|
|
160
255
|
why = "HEAD moved since the preflight ran"
|
|
161
256
|
except Exception:
|
|
162
|
-
why = "the preflight stamp is unreadable (expected `<epoch> <sha>`)"
|
|
257
|
+
why = "the preflight stamp is unreadable (expected `<epoch> <sha> <digest>`)"
|
|
163
258
|
if why is None:
|
|
164
259
|
return 0
|
|
165
260
|
# Same rule as the Bash gate: unattended runs have nobody to answer an "ask".
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# BRAINSTORM RETURN — <feature title>
|
|
2
2
|
|
|
3
|
-
> Paste this into `/spec`.
|
|
3
|
+
> Paste this into `/cohorte-spec`.
|
|
4
4
|
|
|
5
5
|
**One-liner:** <what we're building, in one sentence>
|
|
6
6
|
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
|
|
25
25
|
- **Data:** <entities / fields touched>
|
|
26
26
|
- **Screens:** <key screens/flows — mobile-first if the project has UI>
|
|
27
|
-
- **Interface (rough):** <endpoints / methods we'll need — refined into the contract in /spec>
|
|
27
|
+
- **Interface (rough):** <endpoints / methods we'll need — refined into the contract in /cohorte-spec>
|
|
28
28
|
|
|
29
29
|
## Risks & open questions
|
|
30
30
|
|
|
31
31
|
- <risk / unknown>
|
|
32
|
-
- <open question for /spec to resolve>
|
|
32
|
+
- <open question for /cohorte-spec to resolve>
|
|
33
33
|
|
|
34
34
|
## Panel dissent (what was contested)
|
|
35
35
|
|
|
36
|
-
<the strongest disagreement the panel surfaced — so /spec doesn't paper over it>
|
|
36
|
+
<the strongest disagreement the panel surfaced — so /cohorte-spec doesn't paper over it>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> built* (surfaces, commands, conventions); this file says *what was decided and why*.
|
|
6
6
|
>
|
|
7
7
|
> **Append-only. One line per decision. No prose blocks, no code.** The bound is the point: this file
|
|
8
|
-
> is read by `/spec`, `/brainstorm` and `/audit` — the deciding stages — and by **nothing else**.
|
|
8
|
+
> is read by `/cohorte-spec`, `/cohorte-brainstorm` and `/cohorte-audit` — the deciding stages — and by **nothing else**.
|
|
9
9
|
> Implementers and reviewers never read it: they work from the frozen contract, which already tells
|
|
10
10
|
> them what to do; handing them the rationale too would cost `surfaces × dispatches` tokens per
|
|
11
11
|
> feature for a fact they cannot act on.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# DESIGN BRIEF — <feature title> (`<feature_id>`)
|
|
2
2
|
|
|
3
3
|
> The "spec return". Paste into the design tool (see `PIPELINE.md` §design). This is §8 of the frozen
|
|
4
|
-
> spec, standalone — `/spec` writes it to `specs/design/<feature_id>.md` on freeze. Omit entirely if
|
|
4
|
+
> spec, standalone — `/cohorte-spec` writes it to `specs/design/<feature_id>.md` on freeze. Omit entirely if
|
|
5
5
|
> the project has no UI.
|
|
6
6
|
|
|
7
7
|
**Goal:** <one line — what the user accomplishes>
|
|
@@ -4,10 +4,10 @@ title: <Feature title>
|
|
|
4
4
|
status: draft # draft → frozen → in-progress → in-review → shipped · blocked (see SCHEMA.md §Spec status)
|
|
5
5
|
branch: <feature_branch_prefix><slug>
|
|
6
6
|
created: <YYYY-MM-DD>
|
|
7
|
-
loop_pass: 0 # /
|
|
8
|
-
loop_phase: # /
|
|
9
|
-
reviewed_base: # merge-base sha at the last SHIP verdict — freshness-gate anchor (written by /review)
|
|
10
|
-
reviewed_digest: # sha256 (16 hex) of the reviewed source diff vs reviewed_base, specs excluded — /ship re-checks
|
|
7
|
+
loop_pass: 0 # /cohorte-loop bookkeeping — the review pass it is on; 0 = no loop running. Written by the driver, not by hand
|
|
8
|
+
loop_phase: # /cohorte-loop bookkeeping — build | review | fix | done. With loop_pass, this is what `--resume` reads back
|
|
9
|
+
reviewed_base: # merge-base sha at the last SHIP verdict — freshness-gate anchor (written by /cohorte-review)
|
|
10
|
+
reviewed_digest: # sha256 (16 hex) of the reviewed source diff vs reviewed_base, specs excluded — /cohorte-ship re-checks
|
|
11
11
|
design_files: [] # design page links — full URLs of the form https://claude.ai/design/p/<projectId>?file=<file> (each carries its own project + page); blank until designed; omit if no UI
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -47,7 +47,7 @@ design_files: [] # design page links — full URLs of the form https://claude.ai
|
|
|
47
47
|
|
|
48
48
|
<repeat per endpoint/interface>
|
|
49
49
|
|
|
50
|
-
### Contract types — lead authors `<contract.path>/<slug>.<ext>` before /build
|
|
50
|
+
### Contract types — lead authors `<contract.path>/<slug>.<ext>` before /cohorte-build
|
|
51
51
|
|
|
52
52
|
- `<slug>...Request` / `<slug>...Response` / `<slug>Base` … (sketch the schemas here)
|
|
53
53
|
|
|
@@ -55,7 +55,7 @@ design_files: [] # design page links — full URLs of the form https://claude.ai
|
|
|
55
55
|
|
|
56
56
|
> One `###` subsection per surface in `PIPELINE.md` §surfaces (e.g. backend, frontend), each TDD.
|
|
57
57
|
> Surfaces are subsections of §6, never top-level sections — §8 and §9 are referenced by number
|
|
58
|
-
> across the whole pipeline (`/build` implements "§8 flows", `/review` checks "§9 acceptance"), so a
|
|
58
|
+
> across the whole pipeline (`/cohorte-build` implements "§8 flows", `/cohorte-review` checks "§9 acceptance"), so a
|
|
59
59
|
> third surface must not become a §8 of its own.
|
|
60
60
|
|
|
61
61
|
### <surface.key>
|
|
@@ -85,5 +85,5 @@ design_files: [] # design page links — full URLs of the form https://claude.ai
|
|
|
85
85
|
|
|
86
86
|
## Remediation
|
|
87
87
|
|
|
88
|
-
> Filled by `/spec` in review-return mode; empty otherwise. Each item:
|
|
88
|
+
> Filled by `/cohorte-spec` in review-return mode; empty otherwise. Each item:
|
|
89
89
|
> `[ ] <SEVERITY> · <file:line> · <spec-violation|quality|security> · <concrete fix>`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# /init-pipeline · 02 Interview the gaps
|
|
1
|
+
# /cohorte-init-pipeline · 02 Interview the gaps
|
|
2
2
|
|
|
3
3
|
### Phase 2 — Interview the gaps (AskUserQuestion)
|
|
4
4
|
|
|
@@ -11,7 +11,7 @@ Ask ONLY what you couldn't confidently detect. Batch related questions. Cover:
|
|
|
11
11
|
cost), `haiku` for purely mechanical surfaces (scaffolding), `inherit` only for surfaces with real
|
|
12
12
|
design decisions worth running on the lead's model.
|
|
13
13
|
- **Specialization (only if Phase 1 flagged a large + cleanly-separable surface)** — offer to split it
|
|
14
|
-
into specialized sub-surfaces (e.g. `web-checkout`, `web-billing`) so `/build` runs them in parallel,
|
|
14
|
+
into specialized sub-surfaces (e.g. `web-checkout`, `web-billing`) so `/cohorte-build` runs them in parallel,
|
|
15
15
|
per SCHEMA.md §Specialization. If the human accepts, apply the rules: **shared code (routing, global
|
|
16
16
|
state, DS kit/tokens) becomes its own single-owner surface**, and cross-slice shapes go through the
|
|
17
17
|
contract. Default to NOT splitting when boundaries are tangled or slices are tiny — coarse is fine.
|
|
@@ -20,7 +20,7 @@ Ask ONLY what you couldn't confidently detect. Batch related questions. Cover:
|
|
|
20
20
|
Recommended option: dot/failures-only reporter (`--reporter=dot` vitest/playwright, `--silent`
|
|
21
21
|
jest, `-q` pytest, `--quiet` eslint/ruff — whatever the detected runner supports). These land in
|
|
22
22
|
`test_quiet_cmd`/`lint_quiet_cmd` + `commands.test_quiet`/`lint_quiet` and are what agents and the
|
|
23
|
-
`/review` pre-flight actually run (SCHEMA.md §Output discipline). If the human declines or
|
|
23
|
+
`/cohorte-review` pre-flight actually run (SCHEMA.md §Output discipline). If the human declines or
|
|
24
24
|
the runner has no such flag, leave `""` — consumers then fall back to `<cmd> 2>&1 | tail -40`.
|
|
25
25
|
- **Contract** — mechanism (`shared-types-zod` / `openapi` / `protobuf` / `json-schema` / `none`) and
|
|
26
26
|
where feature contracts are authored. If `none`, surfaces sync by the spec prose alone.
|
|
@@ -35,11 +35,11 @@ Ask ONLY what you couldn't confidently detect. Batch related questions. Cover:
|
|
|
35
35
|
main checkout? If worktrees: DB-per-worktree? port bases? compose file?
|
|
36
36
|
- **Gate** — confirm the destructive commands to hard-deny and the ones to confirm-first (seed from the
|
|
37
37
|
detected DB/migration tooling + always git commit/push/merge/rebase/reset).
|
|
38
|
-
- **Personas** — keep the default `/brainstorm` panel, or customize members for this domain?
|
|
38
|
+
- **Personas** — keep the default `/cohorte-brainstorm` panel, or customize members for this domain?
|
|
39
39
|
|
|
40
40
|
Prefer sensible defaults from Phase 1 as the first (Recommended) option in each question.
|
|
41
41
|
|
|
42
|
-
- **Kanban** (optional) — mirror this project's pipeline (`/brainstorm`…`/ship`) onto an Obsidian
|
|
42
|
+
- **Kanban** (optional) — mirror this project's pipeline (`/cohorte-brainstorm`…`/cohorte-ship`) onto an Obsidian
|
|
43
43
|
Kanban board? If the human says yes: confirm the shared vault path (`obsidian.vault_path` in
|
|
44
44
|
`~/.claude/cohorte.config.yaml`; ask if empty) and the board's location inside it (default
|
|
45
45
|
`<ProjectName>/Tasks.md`). Phase 4 creates the board and records the link. Default: no.
|
|
@@ -50,7 +50,7 @@ Prefer sensible defaults from Phase 1 as the first (Recommended) option in each
|
|
|
50
50
|
- **Telemetry** (optional, machine-scoped — SKIP entirely if `~/.claude/cohorte.config.yaml` already
|
|
51
51
|
has a `telemetry:` block with a `consent_date`, i.e. the human already answered on this machine).
|
|
52
52
|
Ask ONE opt-in question, stating exactly: _"Share anonymous usage stats with the cohorte project?
|
|
53
|
-
One ping per pipeline phase, `/brainstorm` through `/ship`: core version, OS, phase name, duration,
|
|
53
|
+
One ping per pipeline phase, `/cohorte-brainstorm` through `/cohorte-ship`: core version, OS, phase name, duration,
|
|
54
54
|
per-surface result counts, and a hash of the feature id — never repo names, paths, code, or IPs.
|
|
55
55
|
Setup and maintenance commands never ping. Off by default; withdraw anytime
|
|
56
56
|
(`telemetry.enabled: false`); erase your history anytime (SCHEMA.md §Telemetry). Default: No."_
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# /init-pipeline · 04 Write & render
|
|
1
|
+
# /cohorte-init-pipeline · 04 Write & render
|
|
2
2
|
|
|
3
3
|
### Phase 4 — Write & render (after go-ahead)
|
|
4
4
|
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
`Bash(git status:*)`, `Bash(git diff:*)`, `Bash(git log:*)`, `Bash(git rev-parse:*)` — plus the
|
|
29
29
|
shipped pipeline scripts for BOTH cores (`Bash(.claude/pipeline/scripts/:*)` and
|
|
30
30
|
`Bash(~/.claude/pipeline/scripts/:*)` — preflight, kanban-move, telemetry-send) **plus the
|
|
31
|
-
`bash`-prefixed form the `/
|
|
31
|
+
`bash`-prefixed form the `/cohorte-loop` driver uses** (`Bash(bash .claude/pipeline/scripts/loop.sh:*)`
|
|
32
32
|
and `Bash(bash ~/.claude/pipeline/scripts/loop.sh:*)`) — those prefix rules match a command
|
|
33
|
-
*starting* with the path, so `bash <path>` needs its own entry or `/
|
|
33
|
+
*starting* with the path, so `bash <path>` needs its own entry or `/cohorte-loop` stalls on a permission
|
|
34
34
|
prompt at every launch, and the
|
|
35
35
|
retrieval provider's MCP tools when wired (e.g. `mcp__serena`). Never allowlist anything matching
|
|
36
36
|
a `gate.ask`/`gate.deny` pattern. Mention the human can widen it later with
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
Preserve any existing custom keys.
|
|
50
50
|
6. **Wire the retrieval provider** (skip if `retrieval.provider: none`):
|
|
51
51
|
- **serena:** if the `serena` CLI is missing, have the human install it (`uv tool install -p 3.13
|
|
52
|
-
serena-agent`) — or set the provider to `none` if they decline, and say `/update-pipeline` can wire
|
|
52
|
+
serena-agent`) — or set the provider to `none` if they decline, and say `/cohorte-update-pipeline` can wire
|
|
53
53
|
it later. If the binary exists (e.g. `~/.local/bin/serena`) but `command -v serena` fails,
|
|
54
54
|
recommend the PATH fix (`uv tool update-shell`, or add `~/.local/bin` to the shell profile) for
|
|
55
55
|
CLI use. Then register at **project scope** (committed `.mcp.json`, portable —
|
|
@@ -82,19 +82,23 @@
|
|
|
82
82
|
(or, without npm: curl -fsSL https://raw.githubusercontent.com/TheBidouilleAgency/cohorte/main/install.sh | sh -s -- --global;
|
|
83
83
|
Windows: install.ps1 -Global from the same repo)> " }`.
|
|
84
84
|
In **global** mode also add, near the top of `CLAUDE.md`, a one-liner:
|
|
85
|
-
`> Pipeline: global core — run the installer above if /brainstorm etc. are missing.`
|
|
85
|
+
`> Pipeline: global core — run the installer above if /cohorte-brainstorm etc. are missing.`
|
|
86
86
|
10. **CI workflow** (if `vcs.host: github` and no existing workflow already runs the profile's
|
|
87
87
|
checks): with the human's go-ahead, generate `.github/workflows/pipeline-ci.yml` — on
|
|
88
88
|
`pull_request` to `<default_branch>`: checkout, set up the `package_manager` toolchain,
|
|
89
89
|
`commands.install`, then `commands.lint` · `commands.typecheck` · `commands.test` (+ per-surface
|
|
90
90
|
`build_cmd`s that are non-empty). Derive the setup steps from the detected stack — mirror what a
|
|
91
|
-
sibling workflow does if one exists. `/ship` watches these checks before the merge.
|
|
92
|
-
11. **Metrics sink &
|
|
93
|
-
`/review` and `/fix` append per-dispatch evidence
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
sibling workflow does if one exists. `/cohorte-ship` watches these checks before the merge.
|
|
92
|
+
11. **Metrics sink, report buffer & preflight stamp:** add `.claude/pipeline-metrics.jsonl` to
|
|
93
|
+
`.gitignore` — `/cohorte-build`, `/cohorte-review` and `/cohorte-fix` append per-dispatch evidence
|
|
94
|
+
there (SCHEMA §Specialization reads it).
|
|
95
|
+
Also add `specs/reports/` — `/cohorte-review` stages its last report there so a `/cohorte-fix` (or
|
|
96
|
+
`/cohorte-spec` Mode B) survives a `/clear`; it's a derived buffer, not a versioned artifact.
|
|
97
|
+
And `.claude/preflight.ok` — a local, per-checkout freshness stamp. Versioning it breaks the phase
|
|
98
|
+
gate for good (a committed stamp describes the tree *before* its own commit, and it rides into every
|
|
99
|
+
new worktree as a green nobody earned). If it is already tracked: `git rm --cached .claude/preflight.ok`.
|
|
100
|
+
12. **Design system:** if `design.enabled` with a snapshot dir, note that `/cohorte-align-ds` is active; else the
|
|
101
|
+
`/cohorte-align-ds` command will no-op with a clear message.
|
|
98
102
|
13. **Kanban** (only if the human opted in at Phase 2): wire it per SCHEMA.md §Kanban, writing into the
|
|
99
103
|
**global** `~/.claude/cohorte.config.yaml` (never into this repo — the board points at a
|
|
100
104
|
personal vault). Create the file from `pipeline/cohorte.config.template.yaml` if absent; set
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
# /init-pipeline · 05 Report
|
|
1
|
+
# /cohorte-init-pipeline · 05 Report
|
|
2
2
|
|
|
3
3
|
### Phase 5 — Report
|
|
4
4
|
|
|
5
5
|
Print: the install mode (bundled core under `.claude/` vs global core in `~/.claude/` + the committed
|
|
6
6
|
`.claude/pipeline.json` pointer), the files written/rendered, the surface→agent mapping, and the
|
|
7
7
|
tailored workflow line, e.g.
|
|
8
|
-
`/brainstorm → /spec → (design) → /build <id> → test → /review → /ship`. Tell the human to sanity-check
|
|
9
|
-
`PIPELINE.md`, commit it, and run `/brainstorm` to start a feature. Note that this was the one-time
|
|
10
|
-
setup: from now on `/update-pipeline` both refreshes the core AND reconciles the generated files
|
|
11
|
-
(SCHEMA.md §Reconcile), and `/build` auto-grows surfaces — re-running `/init-pipeline` is only for deep
|
|
8
|
+
`/cohorte-brainstorm → /cohorte-spec → (design) → /cohorte-build <id> → test → /cohorte-review → /cohorte-ship`. Tell the human to sanity-check
|
|
9
|
+
`PIPELINE.md`, commit it, and run `/cohorte-brainstorm` to start a feature. Note that this was the one-time
|
|
10
|
+
setup: from now on `/cohorte-update-pipeline` both refreshes the core AND reconciles the generated files
|
|
11
|
+
(SCHEMA.md §Reconcile), and `/cohorte-build` auto-grows surfaces — re-running `/cohorte-init-pipeline` is only for deep
|
|
12
12
|
stack changes (package manager, contract mechanism, surface overhaul).
|
package/core/workflows/audit.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// cohorte — /audit as a deterministic workflow (opt-in; the conversational
|
|
2
|
-
// /audit command remains the default path and the fallback).
|
|
1
|
+
// cohorte — /cohorte-audit as a deterministic workflow (opt-in; the conversational
|
|
2
|
+
// /cohorte-audit command remains the default path and the fallback).
|
|
3
3
|
//
|
|
4
4
|
// Invoke with args = {target: "<path or domain>"} (optional — default whole repo).
|
|
5
5
|
//
|
|
@@ -135,7 +135,7 @@ const surfaces = Array.isArray(profile.surfaces) ? profile.surfaces : []
|
|
|
135
135
|
// guard compares against `surfaces` — an empty list makes them all vacuously
|
|
136
136
|
// pass. Fail loudly here instead of finishing with nothing done.
|
|
137
137
|
if (!surfaces.length) {
|
|
138
|
-
return { error: 'profile has no surfaces — nothing would be audited. the `yaml pipeline-profile` block in PIPELINE.md is empty or unparseable, or the profile-reader mis-returned; run /doctor' }
|
|
138
|
+
return { error: 'profile has no surfaces — nothing would be audited. the `yaml pipeline-profile` block in PIPELINE.md is empty or unparseable, or the profile-reader mis-returned; run /cohorte-doctor' }
|
|
139
139
|
}
|
|
140
140
|
const quiet = (q, full) => (q && !String(q).startsWith('<') ? q : full ? `${full} 2>&1 | tail -40` : '')
|
|
141
141
|
const scope = target || 'the whole repo'
|
|
@@ -204,7 +204,7 @@ const written = await agent(
|
|
|
204
204
|
`Write EXACTLY this content to specs/refactor-backlog.md (overwrite), then return the single word done:\n<<<BACKLOG\n${body.join('\n')}\nBACKLOG`,
|
|
205
205
|
{ model: 'haiku', label: 'write-backlog', effort: 'low' },
|
|
206
206
|
)
|
|
207
|
-
// Returning `backlog: <path>` when the writer died points /refactor at a file
|
|
207
|
+
// Returning `backlog: <path>` when the writer died points /cohorte-refactor at a file
|
|
208
208
|
// that does not exist (or, worse, at the PREVIOUS run's stale backlog).
|
|
209
209
|
const backlogOk = written != null && /done/i.test(String(written))
|
|
210
210
|
|
|
@@ -220,6 +220,6 @@ return {
|
|
|
220
220
|
next: !backlogOk
|
|
221
221
|
? 'the backlog was NEVER written (writer died) — the counts above are real but nothing is on disk; re-run the audit'
|
|
222
222
|
: deadDomains.length
|
|
223
|
-
? `re-audit ${deadDomains.join(', ')} (auditor died — not covered), then /refactor <domain>`
|
|
224
|
-
: 'refactor a domain with /refactor <domain> (or the refactor workflow for big domains)',
|
|
223
|
+
? `re-audit ${deadDomains.join(', ')} (auditor died — not covered), then /cohorte-refactor <domain>`
|
|
224
|
+
: 'refactor a domain with /cohorte-refactor <domain> (or the refactor workflow for big domains)',
|
|
225
225
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// cohorte — /refactor as a deterministic workflow (opt-in; the conversational
|
|
2
|
-
// /refactor command remains the default path and the fallback).
|
|
1
|
+
// cohorte — /cohorte-refactor as a deterministic workflow (opt-in; the conversational
|
|
2
|
+
// /cohorte-refactor command remains the default path and the fallback).
|
|
3
3
|
//
|
|
4
4
|
// BIG domains only: a domain with just a handful of open backlog items is
|
|
5
|
-
// cheaper through the conversational /refactor — this script skips it and says
|
|
5
|
+
// cheaper through the conversational /cohorte-refactor — this script skips it and says
|
|
6
6
|
// so. Invoke with args = {domains: ["backend", …]} or {domains: "all"}.
|
|
7
7
|
//
|
|
8
8
|
// Shape (SCHEMA.md §Workflows): profile via profile-reader (phase 0), the open
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
export const meta = {
|
|
15
15
|
name: 'cohorte-refactor',
|
|
16
|
-
description: 'Apply the /audit refactor backlog for big domains: shared first, then parallel surface implementers, per-domain verify + one retry',
|
|
16
|
+
description: 'Apply the /cohorte-audit refactor backlog for big domains: shared first, then parallel surface implementers, per-domain verify + one retry',
|
|
17
17
|
whenToUse: 'Only when the human explicitly asks for the refactor workflow on big domains. args = {domains: ["<surface key>", …] | "all"}.',
|
|
18
18
|
phases: [
|
|
19
19
|
{ title: 'Profile', detail: 'PIPELINE.md → JSON via profile-reader', model: 'haiku' },
|
|
@@ -26,7 +26,7 @@ export const meta = {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// A domain below this many open items is not "big" — the conversational
|
|
29
|
-
// /refactor handles it with less overhead than a workflow run.
|
|
29
|
+
// /cohorte-refactor handles it with less overhead than a workflow run.
|
|
30
30
|
const MIN_ITEMS = 5
|
|
31
31
|
|
|
32
32
|
// The Workflow runtime hands `args` to a script verbatim, so a caller that passes a
|
|
@@ -136,7 +136,7 @@ const surfaces = Array.isArray(profile.surfaces) ? profile.surfaces : []
|
|
|
136
136
|
// guard compares against `surfaces` — an empty list makes them all vacuously
|
|
137
137
|
// pass. Fail loudly here instead of finishing with nothing done.
|
|
138
138
|
if (!surfaces.length) {
|
|
139
|
-
return { error: 'profile has no surfaces — nothing would be refactored. the `yaml pipeline-profile` block in PIPELINE.md is empty or unparseable, or the profile-reader mis-returned; run /doctor' }
|
|
139
|
+
return { error: 'profile has no surfaces — nothing would be refactored. the `yaml pipeline-profile` block in PIPELINE.md is empty or unparseable, or the profile-reader mis-returned; run /cohorte-doctor' }
|
|
140
140
|
}
|
|
141
141
|
const byKey = Object.fromEntries(surfaces.map(s => [s.key, s]))
|
|
142
142
|
const contractPath = (profile.contract && profile.contract.path) || ''
|
|
@@ -153,15 +153,15 @@ const backlog = await agent(
|
|
|
153
153
|
{ model: 'haiku', label: 'read-backlog', schema: OPEN, effort: 'low' },
|
|
154
154
|
)
|
|
155
155
|
// A dead reader is not "the backlog is empty" — reporting it as such sends the
|
|
156
|
-
// human to re-run /audit on a backlog that is already there.
|
|
156
|
+
// human to re-run /cohorte-audit on a backlog that is already there.
|
|
157
157
|
if (!backlog) return { error: 'the backlog-reading agent died — nothing was read; re-run the refactor workflow' }
|
|
158
158
|
const open = (backlog.domains || []).filter(d => d.items.length)
|
|
159
|
-
if (!open.length) return { error: 'no open backlog items for the requested domains — run /audit (or the audit workflow) first' }
|
|
159
|
+
if (!open.length) return { error: 'no open backlog items for the requested domains — run /cohorte-audit (or the audit workflow) first' }
|
|
160
160
|
|
|
161
161
|
const big = open.filter(d => d.items.length >= MIN_ITEMS)
|
|
162
162
|
const small = open.filter(d => d.items.length < MIN_ITEMS)
|
|
163
|
-
for (const d of small) log(`Skipping ${d.key} (${d.items.length} open item(s) < ${MIN_ITEMS}) — use the conversational /refactor ${d.key}, it's cheaper`)
|
|
164
|
-
if (!big.length) return { skipped: Object.fromEntries(small.map(d => [d.key, d.items.length])), reason: `every requested domain is below the ${MIN_ITEMS}-item workflow threshold — use /refactor` }
|
|
163
|
+
for (const d of small) log(`Skipping ${d.key} (${d.items.length} open item(s) < ${MIN_ITEMS}) — use the conversational /cohorte-refactor ${d.key}, it's cheaper`)
|
|
164
|
+
if (!big.length) return { skipped: Object.fromEntries(small.map(d => [d.key, d.items.length])), reason: `every requested domain is below the ${MIN_ITEMS}-item workflow threshold — use /cohorte-refactor` }
|
|
165
165
|
|
|
166
166
|
const implementPrompt = d =>
|
|
167
167
|
'Refactor pass on your surface (no feature spec). Read PIPELINE.md first. Add the missing tests ' +
|
|
@@ -243,7 +243,7 @@ if (clearedAll.length) {
|
|
|
243
243
|
{ model: 'haiku', label: 'tick-backlog', effort: 'low' },
|
|
244
244
|
)
|
|
245
245
|
// Reporting items as cleared while the backlog still shows them open means the
|
|
246
|
-
// next /refactor re-dispatches work that is already done.
|
|
246
|
+
// next /cohorte-refactor re-dispatches work that is already done.
|
|
247
247
|
tickedOk = ticked != null && /done/i.test(String(ticked))
|
|
248
248
|
}
|
|
249
249
|
|
|
@@ -256,8 +256,8 @@ return {
|
|
|
256
256
|
backlogTicked: tickedOk,
|
|
257
257
|
next: !tickedOk
|
|
258
258
|
? `${clearedAll.length} item(s) were cleared in code but NOT ticked off specs/refactor-backlog.md ` +
|
|
259
|
-
'(the ticking agent died) — tick them by hand, or the next /refactor re-dispatches finished work'
|
|
259
|
+
'(the ticking agent died) — tick them by hand, or the next /cohorte-refactor re-dispatches finished work'
|
|
260
260
|
: results.some(r => r.remaining.length || !r.gatesGreen)
|
|
261
|
-
? 'items remain — finish them with the conversational /refactor <domain>'
|
|
262
|
-
: 'all dispatched domains clean — optionally close with one final /audit',
|
|
261
|
+
? 'items remain — finish them with the conversational /cohorte-refactor <domain>'
|
|
262
|
+
: 'all dispatched domains clean — optionally close with one final /cohorte-audit',
|
|
263
263
|
}
|