cohorte 2.6.0 → 2.8.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 +110 -0
- package/README.md +11 -9
- package/bin/cli.js +15 -2
- package/core/agents/review.md +4 -2
- package/core/commands/cohorte-build.md +3 -1
- package/core/commands/cohorte-doctor.md +27 -4
- package/core/commands/cohorte-fix.md +6 -5
- package/core/commands/cohorte-review.md +32 -22
- package/core/commands/cohorte-ship.md +2 -1
- package/core/commands/cohorte-spec.md +1 -1
- package/core/hooks/gate.py +10 -4
- package/core/runtimes/claude.json +1 -0
- package/core/runtimes/codex.json +1 -0
- package/core/runtimes/cursor.json +1 -0
- package/core/runtimes/gemini.json +1 -0
- package/core/runtimes/opencode.json +1 -0
- package/core/templates/design-brief.md +12 -3
- package/core/workflows/audit.js +20 -5
- package/core/workflows/loop.js +617 -0
- package/core/workflows/refactor.js +21 -8
- package/core/workflows/review.js +81 -12
- package/dashboard/dist/assets/{index-D1rsbLat.js → index-DO3_nq2Q.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/server/doctor.js +11 -3
- package/dashboard/server/index.js +6 -1
- package/dashboard/server/kanban.js +15 -4
- package/dashboard/server/runtime.js +20 -1
- package/install.ps1 +16 -333
- package/install.sh +27 -297
- package/package.json +1 -1
- package/profile/SCHEMA.md +34 -10
- package/profile/cohorte.config.template.yaml +1 -1
- package/scripts/kanban-move.sh +15 -5
- package/scripts/metrics/prices.json +6 -3
- package/scripts/new-feature.sh.template +8 -1
- package/scripts/preflight.sh +10 -2
- package/scripts/remove-feature.sh.template +3 -1
- package/scripts/test-dashboard.mjs +42 -1
- package/scripts/test-gate.mjs +6 -0
- package/scripts/test-metrics.mjs +2 -2
- package/scripts/test-workflows.mjs +386 -6
- package/scripts/validate-core.mjs +64 -31
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,116 @@ 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.8.0 — 2026-08-22
|
|
11
|
+
|
|
12
|
+
- **`/cohorte-loop` is back — as a workflow, which is the whole point.** The 2.2.0 driver was
|
|
13
|
+
retired because it spawned headless child processes it could not supervise: it stalled on a
|
|
14
|
+
permission prompt it could not see, read a missing `build.json` as an empty one, and had a
|
|
15
|
+
session killed mid-write with nothing noticing. `core/workflows/loop.js` runs
|
|
16
|
+
build → review → [fix → review]* for one feature inside the Workflow runtime instead — the
|
|
17
|
+
runtime holds control flow, a dead agent resolves to `null` rather than to silence, and there
|
|
18
|
+
is no prompt to stall on.
|
|
19
|
+
|
|
20
|
+
What it will not do is as designed as what it does: it *verifies* `/cohorte-build`'s outputs
|
|
21
|
+
(frozen spec, fresh `readiness.json`, the lead-authored contract) as preconditions and aborts —
|
|
22
|
+
naming the gap — on anything that is a human's call: a `NOT-READY` spec, a surface the profile
|
|
23
|
+
doesn't own, a blocking finding on the contract file itself. A dead reviewer's zero findings
|
|
24
|
+
can never read as ship (`unreviewed` is checked before `blocking`, in that order on purpose);
|
|
25
|
+
identical blocking findings two rounds running abort as treading water instead of burning the
|
|
26
|
+
remaining rounds; `maxRounds` (default 5) is the last net, never the first. State is four files
|
|
27
|
+
in `specs/reports/` — re-invoking resumes, and every file older than the spec is treated as
|
|
28
|
+
absent. There is **no** `core/commands/cohorte-loop.md` and `validate-core.mjs` now fails if
|
|
29
|
+
one appears: without the Workflow runtime the loop refuses explicitly rather than degrading to
|
|
30
|
+
a lead re-reasoning the fan-out every round at session prices. It stamps `in-progress` /
|
|
31
|
+
`in-review` / `blocked` on the spec — the driver states SCHEMA.md kept alive since 2.2.0 have
|
|
32
|
+
a producer again.
|
|
33
|
+
|
|
34
|
+
To serve it, the review workflow now writes the same `specs/reports/<id>.verdict.json` the
|
|
35
|
+
conversational `/cohorte-review` §3 guarantees (blocking count, normalized `blocking_items`,
|
|
36
|
+
sha256 fingerprint, the degraded `aborted: "preflight"` form) — one machine contract, two
|
|
37
|
+
producers.
|
|
38
|
+
|
|
39
|
+
- **A deny behind an ask was reachable with one click.** `gate.py` scanned command segments in
|
|
40
|
+
order and returned on the first match, so `git commit -m x && node ace migration:fresh`
|
|
41
|
+
surfaced only the benign `git commit` confirm — and the human's single "yes" ran the
|
|
42
|
+
hard-denied migration behind it. Deny patterns are now matched across the whole chain before
|
|
43
|
+
any ask is offered. Global installs pick this up with `cohorte update`; bundled repos via
|
|
44
|
+
`/cohorte-update-pipeline`.
|
|
45
|
+
|
|
46
|
+
- **The SHIP stamp is now earned, not implied.** The conversational `/cohorte-review` ticked the
|
|
47
|
+
DoD and wrote the freshness stamp on every SHIP — including one carrying HIGH findings, which
|
|
48
|
+
`/cohorte-ship`'s gates then happily certified, while the workflow variant refused the same
|
|
49
|
+
state. Both paths now agree: tick + stamp only when nothing above LOW survived; surviving
|
|
50
|
+
HIGH/MEDIUM routes to `/cohorte-fix` (or an explicit park) first.
|
|
51
|
+
|
|
52
|
+
- **A dozen bugs the release audit surfaced**, the sharper ones being: `cohorte install` seeded
|
|
53
|
+
`cohorte.config.yaml` under `~/.claude` even when `CLAUDE_CONFIG_DIR` pointed elsewhere — a
|
|
54
|
+
config no reader ever probed (CI now asserts the seed lands where the readers look);
|
|
55
|
+
the refactor workflow's retry round discarded the items its first verify had already cleared,
|
|
56
|
+
so the backlog re-dispatched finished work — and invoked with a bare `"backend"` it silently
|
|
57
|
+
refactored *every* big domain; the dashboard trusted the absolute paths in a committed
|
|
58
|
+
`runtimes.json`, going all-red on any cloned or moved checkout; `preflight.sh` fed a UTC
|
|
59
|
+
timestamp to a local-time `touch -t`, future-dating the throwaway index west of UTC (and an
|
|
60
|
+
all-empty command list stamped a green preflight that had verified nothing);
|
|
61
|
+
`new-feature.sh` branched worktrees off the *local* default branch its own fetch never
|
|
62
|
+
updated; a CRLF kanban board failed with "column not found" on a column that exists; and a
|
|
63
|
+
dead mechanical-gates agent read as "0 failures" in the audit workflow. Plus a sweep of doc
|
|
64
|
+
drift (the first-feature walkthrough skipped build *and* review; "Node-less" installers that
|
|
65
|
+
require Node; stale counts of agents, scripts and board columns).
|
|
66
|
+
|
|
67
|
+
- **The shell installers are now the thin delegators they already were.** Since 2.2.0 both
|
|
68
|
+
scripts handed everything to `bin/cli.js` and then carried ~300 lines of unreachable legacy
|
|
69
|
+
copy code below the hand-off — dead text that `validate-core`'s "does the installer copy X"
|
|
70
|
+
checks were vacuously matching, which is worse than no check: it reads as coverage. The dead
|
|
71
|
+
code is gone; the checks now assert CI's install dry-run postconditions — tests against the
|
|
72
|
+
copy that actually runs. Along the way: `sh install.sh` from inside a checkout silently
|
|
73
|
+
*cloned the remote* instead of installing the local tree (`$0` arrives with no slash and the
|
|
74
|
+
self-locate case missed it); both installers now refuse a Node older than 18 up front instead
|
|
75
|
+
of half-installing before `fs.cpSync` crashes; and the review agent's `tools:` names both
|
|
76
|
+
retrieval providers (`mcp__serena`, `mcp__graphify`) — one fixed agent file, shared across
|
|
77
|
+
projects, only the wired provider is live in a session, and graphify projects' reviewers were
|
|
78
|
+
silently cut off from their own index.
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
## 2.7.0 — 2026-08-22
|
|
82
|
+
|
|
83
|
+
- **The metrics panel billed Sonnet 5 fifty percent over.** `prices.json` carried $3/$15 because
|
|
84
|
+
that was the rate scheduled to take effect on 2026-09-01. It never will: Anthropic made the
|
|
85
|
+
$2/$10 introductory rate the standard one. Every run costed since the entry was written reads
|
|
86
|
+
high, and the further back a run is, the more confidently wrong the number looks.
|
|
87
|
+
|
|
88
|
+
Sonnet 5 is now $2/$10, and the file says in prose why the increase must not be reinstated —
|
|
89
|
+
the next person to "correct" this back will at least have to argue with a comment first. Sonnet
|
|
90
|
+
4.6 and earlier stay at $3/$15: same tier, different price, and the longest-prefix lookup keeps
|
|
91
|
+
them apart on its own.
|
|
92
|
+
|
|
93
|
+
- **A concurrency ceiling that moved while the comments stood still.** `audit.js` described the
|
|
94
|
+
runtime as capping concurrent agents at "~16". It is 20, it is named
|
|
95
|
+
(`CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS`), and raising it raises the ceiling without changing this
|
|
96
|
+
script — the queue belongs to the runtime, and a queued domain was never a lost one. Comments
|
|
97
|
+
only; no dispatch logic moved.
|
|
98
|
+
|
|
99
|
+
- **The design brief was the one step the pipeline handed back to you.** `/cohorte-spec` freezes a
|
|
100
|
+
brief to `specs/design/<feature_id>.md` and then said, in effect, paste this somewhere else. With
|
|
101
|
+
`design.inline: true` it can instead hand that file to `/design`, which reads the codebase,
|
|
102
|
+
matches the existing UI style, and returns editable artboards without leaving the session.
|
|
103
|
+
|
|
104
|
+
The flag is off by default and degrades rather than fails: an unmet floor falls back to the
|
|
105
|
+
paste-it-yourself path with a note, never an error. It needs `provider: claude-design`, the new
|
|
106
|
+
`inline_design` runtime capability (Claude Code only), and a CLI ≥ 2.1.234 — a **higher floor than
|
|
107
|
+
workflows' 2.1.154**, which is why `/cohorte-doctor` grew a separate check `8b` instead of raising
|
|
108
|
+
the existing one. Raising the shared floor would have made every install between the two versions
|
|
109
|
+
read as broken while its workflows ran fine.
|
|
110
|
+
|
|
111
|
+
Understand what inline does and does not change: it changes who does the pasting. The brief is
|
|
112
|
+
still written to disk first and is still what `/cohorte-build` reads — artboards are an aid to the
|
|
113
|
+
human, not an input to the pipeline. And `/design` is a research preview that **does not save them
|
|
114
|
+
for you**, so check 8b says so on every run, including the runs where everything passes. An
|
|
115
|
+
artboard nobody exported dies with the session, and there is no way to notice afterwards.
|
|
116
|
+
|
|
117
|
+
- This release adds a runtime capability, a profile flag, and a command section: run
|
|
118
|
+
`/cohorte-update-pipeline` after updating.
|
|
119
|
+
|
|
10
120
|
## 2.6.0 — 2026-08-14
|
|
11
121
|
|
|
12
122
|
- **Two ways to type the same command, and one of them silently doesn't work.** The docs wrote
|
package/README.md
CHANGED
|
@@ -229,8 +229,8 @@ those actions.
|
|
|
229
229
|
✅/⚠️/❌ checklist (each failure with its fix), the **Surfaces ↔ agents** map from `PIPELINE.md`,
|
|
230
230
|
and one board: a **Kanban** if the project has a linked Obsidian board (columns + cards from the
|
|
231
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-
|
|
233
|
-
shipped`). The Kanban supersedes the Specs board when both would apply.
|
|
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
234
|
- **Actions** (stream their output live) — **Update / Install core** (the shared global core, or a
|
|
235
235
|
repo's bundled core); **Init-pipeline / Update-pipeline / Audit**, which run those Claude Code
|
|
236
236
|
commands **headless** (`claude -p`, autonomous — Init skips the interactive interview, so review
|
|
@@ -276,7 +276,7 @@ will do:
|
|
|
276
276
|
|
|
277
277
|
```sh
|
|
278
278
|
npm i -g cohorte
|
|
279
|
-
francois ext install TheBidouilleAgency/cohorte
|
|
279
|
+
francois ext install TheBidouilleAgency/francois-plugin-cohorte
|
|
280
280
|
```
|
|
281
281
|
|
|
282
282
|
## Releasing (maintainers)
|
|
@@ -365,15 +365,17 @@ Rules that make it safe:
|
|
|
365
365
|
|
|
366
366
|
### Workflows — deterministic multi-agent runs (opt-in)
|
|
367
367
|
|
|
368
|
-
Four
|
|
369
|
-
|
|
370
|
-
|
|
368
|
+
Four **workflow scripts** ship for the Claude Code Workflow runtime — the same fan-out the
|
|
369
|
+
commands orchestrate, but driven by a deterministic script instead of the lead reasoning it out
|
|
370
|
+
turn by turn. Three are opt-in variants of their same-named commands; the fourth, `loop.js`,
|
|
371
|
+
exists **only** as a workflow:
|
|
371
372
|
|
|
372
373
|
| Script | What it runs |
|
|
373
374
|
| ----------------------- | ----------------------------------------------------------------------------------------- |
|
|
374
375
|
| `workflows/review.js` | Preflight gate (aborts while red — zero agents), one reviewer per touched surface, adversarial cross-check of CRITICAL/security findings, merged verdict only. |
|
|
375
376
|
| `workflows/audit.js` | One auditor per domain (every surface + shared) concurrently, prioritized `specs/refactor-backlog.md`. |
|
|
376
377
|
| `workflows/refactor.js` | Big domains only: `shared` first and alone, then parallel surface implementers, per-domain verify + one retry. |
|
|
378
|
+
| `workflows/loop.js` | `/cohorte-loop` — build → review → [fix → review]* for one feature, unattended and resumable; exits on zero blocking findings, treading water, maxRounds, or anything that needs a human (contract change, unreviewed surface). No conversational fallback, on purpose. |
|
|
377
379
|
|
|
378
380
|
The essentials:
|
|
379
381
|
|
|
@@ -413,15 +415,15 @@ See `profile/SCHEMA.md` for every field in `PIPELINE.md` and how the pipeline us
|
|
|
413
415
|
|
|
414
416
|
```
|
|
415
417
|
package.json # npm package (cohorte) — semver source of truth
|
|
416
|
-
bin/cli.js # the npm CLI: install / update / dashboard / version (cross-platform, no deps)
|
|
417
|
-
install.sh # script installer (fresh + --update) for
|
|
418
|
+
bin/cli.js # the npm CLI: install / update / dashboard / specs / doctor / metrics / version (cross-platform, no deps)
|
|
419
|
+
install.sh # script installer (fresh + --update) for npm-less setups — still needs Node
|
|
418
420
|
install.ps1 # same installer for Windows PowerShell (fresh + -Update)
|
|
419
421
|
core/ # copied verbatim into ~/.claude (global) or <project>/.claude (bundled)
|
|
420
422
|
agents/ # implementer.template.md (rendered per surface) + review / release / profile-reader
|
|
421
423
|
commands/ # init-pipeline + the pipeline commands + /cohorte-update-pipeline
|
|
422
424
|
hooks/ # gate.py (destructive-command gate; branch-aware; preflight phase gate)
|
|
423
425
|
templates/ # handoff / brainstorm-return / design-brief / review-feedback / pr-body / spec
|
|
424
|
-
workflows/ #
|
|
426
|
+
workflows/ # Workflow-runtime scripts: review.js / audit.js / refactor.js (opt-in variants) + loop.js (workflow-only)
|
|
425
427
|
profile/
|
|
426
428
|
PIPELINE.template.md # the profile skeleton /cohorte-init-pipeline fills
|
|
427
429
|
SCHEMA.md # field reference
|
package/bin/cli.js
CHANGED
|
@@ -562,7 +562,20 @@ async function seedConfig() {
|
|
|
562
562
|
// being told: `~/.claude` for Claude Code (unchanged — existing files stay authoritative),
|
|
563
563
|
// `~/.cohorte` for every other runtime. The shipped scripts probe both, in that order, so
|
|
564
564
|
// a human who drives one repo from two agents still has a single board and a single consent.
|
|
565
|
-
|
|
565
|
+
//
|
|
566
|
+
// CLAUDE_CONFIG_DIR moves the whole `~/.claude` tree (globalDir already follows it), but
|
|
567
|
+
// adapter.configPath() speaks in literal `~/.claude/…` — expanding through HOME alone
|
|
568
|
+
// seeded a file at a path no reader probes (kanban-move.sh and the dashboard follow
|
|
569
|
+
// CLAUDE_CONFIG_DIR), missed a filled config there (re-seeding disabled defaults beside
|
|
570
|
+
// it — the exact two-file fork this function's comments forbid), and printed a banner
|
|
571
|
+
// path that did not exist. Re-root the claude-shaped path onto globalDir so the seed,
|
|
572
|
+
// the existing-file check, the banner and every reader agree on one file.
|
|
573
|
+
const homeClaude = path.join(os.homedir(), '.claude');
|
|
574
|
+
const rerootClaude = (p) =>
|
|
575
|
+
p === homeClaude || p.startsWith(homeClaude + path.sep)
|
|
576
|
+
? path.join(globalDir, p.slice(homeClaude.length + 1) || '.')
|
|
577
|
+
: p;
|
|
578
|
+
const cfg = rerootClaude(adapter.expandHome(adapter.configPath(runtime)));
|
|
566
579
|
fs.mkdirSync(path.dirname(cfg), { recursive: true });
|
|
567
580
|
// Pre-rename names, newest first — read as a fallback so upgrades don't lose the config.
|
|
568
581
|
const legacy = ['thebidouille.config.yaml']
|
|
@@ -571,7 +584,7 @@ async function seedConfig() {
|
|
|
571
584
|
// A second runtime must not fork the config: two files means two boards and two consent
|
|
572
585
|
// records, and the human edits whichever one they happen to open. The scripts read
|
|
573
586
|
// `~/.cohorte` first, so seeding it here would SHADOW a filled `~/.claude` copy.
|
|
574
|
-
const claudeCfg = adapter.expandHome(adapter.configPath({ id: 'claude' }));
|
|
587
|
+
const claudeCfg = rerootClaude(adapter.expandHome(adapter.configPath({ id: 'claude' })));
|
|
575
588
|
if (cfg !== claudeCfg && fs.existsSync(claudeCfg)) {
|
|
576
589
|
console.log(` · reusing your existing ${claudeCfg} (the scripts read it as a fallback)`);
|
|
577
590
|
return;
|
package/core/agents/review.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: review
|
|
3
3
|
description: Read-only reviewer. Compares the implementation against the frozen spec, then audits code quality, security, and (if the profile declares it) mobile-first. Emits the REVIEW REPORT. Dispatched by /cohorte-review — one per touched surface on multi-surface diffs. Cannot modify anything.
|
|
4
|
-
tools: Read, Grep, Glob, mcp__serena
|
|
4
|
+
tools: Read, Grep, Glob, mcp__serena, mcp__graphify
|
|
5
5
|
model: sonnet
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -40,7 +40,9 @@ be precise and self-contained.
|
|
|
40
40
|
a default.
|
|
41
41
|
- If `retrieval.provider` in `PIPELINE.md` is not `none`, its MCP tools are in your toolset —
|
|
42
42
|
prefer them over Grep/Glob + whole-file Reads: locate code by symbol, read only the definitions
|
|
43
|
-
you need. Fall back to Grep/Read when they are unavailable or come up empty.
|
|
43
|
+
you need. Fall back to Grep/Read when they are unavailable or come up empty. (Your `tools:`
|
|
44
|
+
list names every provider this agent file is shared across — only the one the project actually
|
|
45
|
+
wired is live in your session; the others simply are not there.)
|
|
44
46
|
|
|
45
47
|
## What you check, in order
|
|
46
48
|
|
|
@@ -143,7 +143,9 @@ _Only if `contract.enabled`._ From §5 of the spec, write/update the feature's c
|
|
|
143
143
|
`<contract.path>/$ARGUMENTS.<contract.ext>` in the profile's `mechanism` (e.g. Zod v4 schemas + inferred
|
|
144
144
|
types for `shared-types-zod`). Export it from `contract.index` if set. This is the ONLY file the agents
|
|
145
145
|
share; they import it read-only and must not edit it. If `contract.enabled` is false, the spec prose is
|
|
146
|
-
the sync channel — say so and skip
|
|
146
|
+
the sync channel — say so and skip, but still run `date +%s` on its own (same as the patch skip
|
|
147
|
+
above: §4's wall-clock start comes from here, and a build with no start epoch writes a metrics
|
|
148
|
+
line with no duration). **Postcondition (if `contract.enabled`):**
|
|
147
149
|
`test -f <contract.path>/$ARGUMENTS.<contract.ext> && date +%s` — the contract file must exist before
|
|
148
150
|
you dispatch §3, or the stateless agents have nothing to build against (the epoch output is §4's
|
|
149
151
|
wall-clock start — no separate timing call).
|
|
@@ -85,7 +85,7 @@ fix only with the human's go-ahead (or hand them the command).
|
|
|
85
85
|
check: CLI resolvable from PATH, `.mcp.json` entry present in PATH-proof launcher form,
|
|
86
86
|
`.serena/` gitignored, server actually connects.
|
|
87
87
|
5. **Design** (if `design.enabled`). `snapshot_dir` exists and is committed; `ui_kit_path` +
|
|
88
|
-
`tokens_path` exist; if `provider: claude-design`, `DesignSync` responds (`
|
|
88
|
+
`tokens_path` exist; if `provider: claude-design`, `DesignSync` responds (`list_files` on the `design_system_project`) and
|
|
89
89
|
`design_system_project` is reachable. Recall: spec `design_files` are full
|
|
90
90
|
`…/design/p/<projectId>?file=<file>` links that carry their own project + page; `design_project` is
|
|
91
91
|
only a legacy fallback for old bare-filename specs (default `none`).
|
|
@@ -110,9 +110,14 @@ fix only with the human's go-ahead (or hand them the command).
|
|
|
110
110
|
stay the default, so failures here are ⚠️ at most, never ❌). Report which path this machine will
|
|
111
111
|
take and why:
|
|
112
112
|
- **Claude Code version** ≥ 2.1.154 (`claude --version 2>/dev/null | head -1`) — older or no CLI
|
|
113
|
-
on PATH ⇒ conversational only.
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
on PATH ⇒ conversational only. This is the **workflow** floor and the only one that gates this
|
|
114
|
+
check; do NOT raise it to match a newer feature's floor, or every install between the two
|
|
115
|
+
versions reads as broken while its workflows run fine. When `design.inline` is on, report the
|
|
116
|
+
design floor (≥ 2.1.234) as its own line under check 8b — separate prerequisite, separate verdict.
|
|
117
|
+
- **Scripts present:** `<core>/workflows/review.js` + `audit.js` + `refactor.js` + `loop.js` —
|
|
118
|
+
missing on a current core ⇒ half-done install, re-run install/update. (`/cohorte-loop` is
|
|
119
|
+
**workflow-only** — no command file exists on purpose; without this runtime it refuses
|
|
120
|
+
rather than degrading to a conversational loop.)
|
|
116
121
|
- **Phase-0 agent present:** `<agents>/profile-reader.md` — the workflows abort without it.
|
|
117
122
|
- **Workflows enabled in this session** — the `Workflow` tool is in your own toolset right now;
|
|
118
123
|
absent ⇒ disabled for this session (a setting or an old client), conversational path.
|
|
@@ -121,6 +126,24 @@ fix only with the human's go-ahead (or hand them the command).
|
|
|
121
126
|
End the check with ONE summary line, e.g.
|
|
122
127
|
`workflows: available (opt-in — ask to "run the review workflow")` or
|
|
123
128
|
`workflows: unavailable (<first failing prerequisite>) — conversational commands (the default)`.
|
|
129
|
+
<!-- cohorte:if inline_design -->
|
|
130
|
+
8b. **Inline design** (`design.inline: true` — the `/design` artboard step between spec and build).
|
|
131
|
+
A research preview, so every failure here is ⚠️, never ❌: the design brief still exists on disk
|
|
132
|
+
at `specs/design/<feature_id>.md` and can be carried to the design tool by hand, which is what
|
|
133
|
+
every install did before this flag. Report:
|
|
134
|
+
- **Claude Code version** ≥ 2.1.234 — `/design` ships as a skill and is simply absent below it.
|
|
135
|
+
Older CLI ⇒ say so and name `npm i -g cohorte@latest`'s sibling, `claude update`.
|
|
136
|
+
- **The `/design` skill resolves in this session** — absent ⇒ the preview is off for this account
|
|
137
|
+
or plan (Pro/Max/Team/Enterprise are the eligible ones), not a cohorte defect.
|
|
138
|
+
- **`design.enabled` is true and `provider` is `claude-design`** — `inline` on top of a `figma` or
|
|
139
|
+
`none` provider is a profile contradiction; name it and point at `/cohorte-update-pipeline`.
|
|
140
|
+
- **Artboards are not persisted for you.** State it every run, unconditionally, even when all
|
|
141
|
+
three checks pass: the preview hands designs to the build step but does not save them, so an
|
|
142
|
+
artboard nobody exported dies with the session. This is the single thing most likely to lose
|
|
143
|
+
work, and it is not detectable after the fact.
|
|
144
|
+
One summary line: `inline design: available (preview — export artboards yourself)` or
|
|
145
|
+
`inline design: unavailable (<first failing prerequisite>) — design brief on disk, carry it over by hand`.
|
|
146
|
+
<!-- cohorte:endif -->
|
|
124
147
|
<!-- cohorte:else -->
|
|
125
148
|
8. **Preflight wiring.** `<core>/pipeline/scripts/preflight.sh` is executable and
|
|
126
149
|
`gate-config.json` carries the `preflight` block — mismatch ⇒ regenerate from the profile.
|
|
@@ -31,8 +31,8 @@ that change the *contract*; `/cohorte-fix` is for everything else.
|
|
|
31
31
|
re-author the contract file yourself now (lead-only, per `/cohorte-build` §2) — agents never edit it. If
|
|
32
32
|
the contract change ripples into surfaces *without* findings, fall back to full `/cohorte-build` instead
|
|
33
33
|
and say so.
|
|
34
|
-
- **Note the epoch** (`date +%s`) in the first Bash call you make here — §3's metrics line
|
|
35
|
-
|
|
34
|
+
- **Note the epoch** (`date +%s`) in the first Bash call you make here — §3's metrics line carries
|
|
35
|
+
`seconds`, and there is no separate timing call.
|
|
36
36
|
|
|
37
37
|
## 2. Scope the re-dispatch — only surfaces with findings
|
|
38
38
|
|
|
@@ -42,10 +42,11 @@ that change the *contract*; `/cohorte-fix` is for everything else.
|
|
|
42
42
|
to the most relevant surface — say which.
|
|
43
43
|
- Re-dispatch **ONLY the surfaces owning ≥1 item**, in parallel, in a **single message** — the exact
|
|
44
44
|
dispatch template from `/cohorte-build` §3 (one byte-stable template for builds and fix loops; you do NOT
|
|
45
|
-
paste a diff — the agent computes its own, scoped to its tree). Fill the template's
|
|
46
|
-
slot with that surface's open `- [ ]` item lines **verbatim**, so the agent needs no spec re-read to
|
|
45
|
+
paste a diff — the agent computes its own, scoped to its tree). Fill the template's **Remediation
|
|
46
|
+
slot** with that surface's open `- [ ]` item lines **verbatim**, so the agent needs no spec re-read to
|
|
47
47
|
find its work; fill the design slot with `none` when a `uses_design` surface's open items are all
|
|
48
|
-
non-visual (no DesignSync re-fetch for a type fix)
|
|
48
|
+
non-visual (no DesignSync re-fetch for a type fix), and the readiness-gaps slot with `none` (a fix
|
|
49
|
+
loop has no fresh §1.6 verdict). Surfaces without findings are NOT re-dispatched —
|
|
49
50
|
that is the point.
|
|
50
51
|
|
|
51
52
|
## 3. Integrate & check off what's fixed
|
|
@@ -136,9 +136,11 @@ between the pipeline and any automated driver, which parses no prose:
|
|
|
136
136
|
|
|
137
137
|
- **`blocking` = CRITICAL findings + `security` findings, deduplicated** (a finding that is both
|
|
138
138
|
counts once). That is exactly the agent's existing verdict rule restated as a number, so
|
|
139
|
-
`blocking == 0` ⟺ `verdict == SHIP`. HIGH/MEDIUM/LOW quality findings are **not** blocking
|
|
140
|
-
|
|
141
|
-
-
|
|
139
|
+
`blocking == 0` ⟺ `verdict == SHIP`. HIGH/MEDIUM/LOW quality findings are **not** blocking and
|
|
140
|
+
must never cost a driver an iteration — a *human* routes them in the verdict branch below
|
|
141
|
+
(a `/cohorte-fix` pass, or a deliberate park in the backlog); a driver ignores them.
|
|
142
|
+
- **`blocking_items`** — one normalized string per **distinct** blocking finding (deduplicated —
|
|
143
|
+
two findings sharing surface, file and problem head are one identity), `<surface>|<file>|<problem>`:
|
|
142
144
|
the file path **without the `:line`** (a fix that inserts lines shifts every line below it — a
|
|
143
145
|
line-bearing identity would change every pass and the drift detection would never fire), and the
|
|
144
146
|
**problem**, not the fix, cut to its first 8 words, lowercased, every run of non-alphanumerics
|
|
@@ -181,25 +183,33 @@ In chat print ONLY: the verdict, the severity-count table, a one-line digest of
|
|
|
181
183
|
finding, and `Full report: specs/reports/$ARGUMENTS.md` — never echo the findings body into chat (it
|
|
182
184
|
would sit in this session's history, re-sent every turn). Then:
|
|
183
185
|
|
|
184
|
-
- **SHIP** → only reachable with `unreviewed` empty (the roll call above forbids it otherwise).
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
186
|
+
- **SHIP** → only reachable with `unreviewed` empty (the roll call above forbids it otherwise).
|
|
187
|
+
**Split on what survived — the DoD tick and the freshness stamp are earned by a clean bill, not
|
|
188
|
+
by the verdict alone** (the workflow variant enforces the same rule; certifying surviving
|
|
189
|
+
HIGH/MEDIUM findings for `/cohorte-ship` would ship known defects):
|
|
190
|
+
- **Nothing above LOW survived** — the clean bill. **Tick the DoD**: in `specs/$ARGUMENTS.md`
|
|
191
|
+
§`Acceptance criteria / DoD`, flip each `- [ ]` → `- [x]` for the criteria the pipeline has
|
|
192
|
+
actually verified — spec conformance + `ui_language` copy (this review), tests · lint ·
|
|
193
|
+
typecheck (a green `/cohorte-build`), mobile-first as far as the code shows it (this review).
|
|
194
|
+
**Leave `- [ ]` (and say which) any item no stage actually verified** — nothing in the pipeline
|
|
195
|
+
*runs* the feature, so any criterion that needs the app up (runtime flows, a visual check
|
|
196
|
+
against the design) stays open unless the human says they exercised it by hand and it held.
|
|
197
|
+
Ticking is the lead's job (the reviewer is read-only). **Then stamp the freshness gate** so
|
|
198
|
+
`/cohorte-ship` can refuse to ship code edited after this verdict: compute
|
|
199
|
+
`BASE=$(git merge-base <default_branch> HEAD)` and write into the spec front-matter
|
|
200
|
+
`reviewed_base: $BASE` plus `reviewed_digest: $(git diff $BASE -- . ':(exclude)specs/' | sha256sum | cut -c1-16)`
|
|
201
|
+
(`shasum -a 256` then the first 16 hex chars where there is no `sha256sum` — macOS) — the
|
|
202
|
+
fingerprint of exactly the source you just reviewed (specs excluded, so DoD ticks + the ship
|
|
203
|
+
status flip don't trip it). Park the leftover LOWs through §3.5's exact route (the backlog,
|
|
204
|
+
under their surface's domain heading, tagged `deferred:$ARGUMENTS` — never as open
|
|
205
|
+
`## Remediation` items, which would re-trigger the fix loop). Then tell the human they can
|
|
206
|
+
`/cohorte-ship` — **recommend a `/clear` first**, the handoff is fully on disk.
|
|
207
|
+
- **HIGH or MEDIUM findings survived** — the verdict stays SHIP (they are not blocking), but
|
|
208
|
+
**no DoD tick and no freshness stamp**. Route them: `/cohorte-fix $ARGUMENTS` for what should
|
|
209
|
+
be fixed now; MEDIUMs the human *explicitly* chooses to live with go through §3.5's park
|
|
210
|
+
instead — and once everything left is LOW, apply the clean-bill branch above (tick, stamp,
|
|
211
|
+
ship). The stamp is the pipeline certifying what ships; it is never written over known
|
|
212
|
+
HIGH/MEDIUM defects.
|
|
203
213
|
- **REVISE / BLOCK**, or any CRITICAL/HIGH/security finding → tell the human to run
|
|
204
214
|
**`/cohorte-fix $ARGUMENTS`** — it appends the report to the spec's `## Remediation` and re-dispatches ONLY
|
|
205
215
|
the surfaces with findings. The full path (`/cohorte-spec` Mode B then `/cohorte-build`) remains for findings that
|
|
@@ -23,7 +23,8 @@ You are the **lead**. Ship feature **$ARGUMENTS**. This is the outward-facing ga
|
|
|
23
23
|
verdict was REVISE/BLOCK, stop and say so.
|
|
24
24
|
- **Freshness gate** — the reviewed code must be exactly what ships. If the spec front-matter carries
|
|
25
25
|
`reviewed_base` + `reviewed_digest`, recompute
|
|
26
|
-
`git diff <reviewed_base> -- . ':(exclude)specs/' | sha256sum | cut -c1-16`
|
|
26
|
+
`git diff <reviewed_base> -- . ':(exclude)specs/' | sha256sum | cut -c1-16` (`shasum -a 256`
|
|
27
|
+
then the first 16 hex chars where there is no `sha256sum` — macOS) and compare to
|
|
27
28
|
`reviewed_digest`. **Match** ⇒ source unchanged since the SHIP verdict, proceed. **Mismatch** ⇒ source
|
|
28
29
|
(or the contract) was edited after review — the verdict is **stale**: stop and tell the human to re-run
|
|
29
30
|
`/cohorte-review $ARGUMENTS` before shipping. Missing fields (spec predates the gate) ⇒ skip, don't block.
|
|
@@ -91,7 +91,7 @@ Detect the mode from the pasted content:
|
|
|
91
91
|
`specs/reports/<id>.md`, where `/cohorte-review` stages its last report. Identify `feature_id` from its
|
|
92
92
|
header; open `specs/<id>.md`.
|
|
93
93
|
2. Append each finding to the spec's **`## Remediation`**, one per line:
|
|
94
|
-
`- [ ] <severity> · <file:line> · <spec-violation|quality|security> · <concrete fix>`
|
|
94
|
+
`- [ ] <severity> · <file:line> · <spec-violation|quality|security|complexity> · <concrete fix>`
|
|
95
95
|
(Keep prior items; add the new round under a dated/numbered subheading.)
|
|
96
96
|
3. If a finding implies the **contract** must change, update §5 and flag it so the lead re-authors the
|
|
97
97
|
contract file.
|
package/core/hooks/gate.py
CHANGED
|
@@ -454,13 +454,19 @@ def check_bash(payload: dict, cfg: dict) -> int:
|
|
|
454
454
|
branch_cache[directory] = branch is None or branch == default
|
|
455
455
|
return branch_cache[directory]
|
|
456
456
|
|
|
457
|
-
for raw in SPLIT.split(command)
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
457
|
+
segments = [s for s in (norm(raw) for raw in SPLIT.split(command)) if s]
|
|
458
|
+
|
|
459
|
+
# Deny wins over ask across the WHOLE chain, not just in segment order. Scanning
|
|
460
|
+
# segment-by-segment returned on the first match, so `git commit -m x && node ace
|
|
461
|
+
# migration:fresh` surfaced only the benign `git commit` ask — and the human's one
|
|
462
|
+
# confirmation ran the hard-denied command behind it. Scan every segment for deny
|
|
463
|
+
# patterns first; only a fully deny-free chain gets an ask.
|
|
464
|
+
for seg in segments:
|
|
461
465
|
for pat in deny:
|
|
462
466
|
if norm(pat) in seg:
|
|
463
467
|
return decide("deny", f"`{pat}` is forbidden by the project's PIPELINE.md gate.")
|
|
468
|
+
|
|
469
|
+
for seg in segments:
|
|
464
470
|
for pat in ask:
|
|
465
471
|
if norm(pat) in seg:
|
|
466
472
|
return decide(ask_decision,
|
package/core/runtimes/codex.json
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
# DESIGN BRIEF — <feature title> (`<feature_id>`)
|
|
2
2
|
|
|
3
|
-
> The "spec return".
|
|
4
|
-
>
|
|
5
|
-
>
|
|
3
|
+
> The "spec return". This is §8 of the frozen spec, standalone — `/cohorte-spec` writes it to
|
|
4
|
+
> `specs/design/<feature_id>.md` on freeze. Omit entirely if the project has no UI.
|
|
5
|
+
>
|
|
6
|
+
> **Getting it into the design tool.** By default: paste it there yourself (see `PIPELINE.md` §design).
|
|
7
|
+
> With `design.inline: true`, `/cohorte-spec` instead offers to hand this file straight to `/design`,
|
|
8
|
+
> which reads the codebase, matches the existing UI style, and returns editable artboards in-session.
|
|
9
|
+
>
|
|
10
|
+
> Inline changes who does the pasting, and nothing else. This file is still written to disk first and
|
|
11
|
+
> is still what `/cohorte-build` reads — the artboards are an aid to the human, not an input to the
|
|
12
|
+
> pipeline, and `/design` is a research preview that does not save them for you. Export anything worth
|
|
13
|
+
> keeping before the session ends. If the design floor is unmet, the flag degrades to the paste-it-
|
|
14
|
+
> yourself path with a note, never to an error: a brief that exists is worth more than a step that ran.
|
|
6
15
|
|
|
7
16
|
**Goal:** <one line — what the user accomplishes>
|
|
8
17
|
|
package/core/workflows/audit.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// Shape (SCHEMA.md §Workflows): profile via profile-reader (phase 0), the
|
|
7
7
|
// mechanical gates staged to disk by one haiku agent, then ONE auditor per
|
|
8
8
|
// domain (each surface + `shared`) running concurrently — the runtime caps
|
|
9
|
-
// concurrency at
|
|
9
|
+
// concurrency at 20 (CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS since 2026-08), extra
|
|
10
|
+
// domains queue — and a merge phase that writes the
|
|
10
11
|
// prioritized specs/refactor-backlog.md. Only the summary comes back.
|
|
11
12
|
|
|
12
13
|
export const meta = {
|
|
@@ -16,7 +17,7 @@ export const meta = {
|
|
|
16
17
|
phases: [
|
|
17
18
|
{ title: 'Profile', detail: 'PIPELINE.md → JSON via profile-reader', model: 'haiku' },
|
|
18
19
|
{ title: 'Gates', detail: 'format/lint/typecheck/tests → specs/reports/audit-gates.txt', model: 'haiku' },
|
|
19
|
-
{ title: 'Audit', detail: 'one review-in-audit-mode agent per domain (concurrent, runtime-capped
|
|
20
|
+
{ title: 'Audit', detail: 'one review-in-audit-mode agent per domain (concurrent, runtime-capped 20)' },
|
|
20
21
|
{ title: 'Backlog', detail: 'merge + write specs/refactor-backlog.md', model: 'haiku' },
|
|
21
22
|
],
|
|
22
23
|
}
|
|
@@ -156,12 +157,21 @@ const gates = await agent(
|
|
|
156
157
|
'the file and return each failure as file/line/kind/one-line summary, capped at 40 (set overflow for the rest).',
|
|
157
158
|
{ model: 'haiku', label: 'gates', schema: GATES, effort: 'low' },
|
|
158
159
|
)
|
|
160
|
+
// A dead gates agent is NOT "zero mechanical failures" — silence would read as the
|
|
161
|
+
// cleanest possible inventory from a phase that never ran (SCHEMA.md §Dead agents).
|
|
162
|
+
// The domain auditors still run (they read the staged gates file, which then simply
|
|
163
|
+
// isn't there), but the run must say the mechanical sweep is uncovered.
|
|
164
|
+
const gatesCovered = gates != null
|
|
159
165
|
const mech = (gates && gates.failures) || []
|
|
160
|
-
log(
|
|
166
|
+
log(gatesCovered
|
|
167
|
+
? `Mechanical failures: ${mech.length}${gates.overflow ? ` (+${gates.overflow} overflow)` : ''}`
|
|
168
|
+
: 'the gates agent died — mechanical checks NOT covered this run')
|
|
161
169
|
|
|
162
170
|
// ── Phase 2 — one auditor per domain, concurrent ─────────────────────────────
|
|
163
171
|
// Domains = every surface + `shared` (contract package + anything outside the
|
|
164
|
-
// surface trees). The runtime caps concurrent agents
|
|
172
|
+
// surface trees). The runtime caps concurrent agents at 20 by default; more domains
|
|
173
|
+
// queue. Raising CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS raises the ceiling, it does not
|
|
174
|
+
// change this script — the queue is the runtime's, and a queued domain is not a lost one.
|
|
165
175
|
phase('Audit')
|
|
166
176
|
const domains = surfaces.map(s => ({ key: s.key, path: s.path }))
|
|
167
177
|
.concat([{ key: 'shared', path: (profile.contract && profile.contract.path) || '(everything outside the surface trees)' }])
|
|
@@ -192,6 +202,10 @@ if (deadDomains.length) {
|
|
|
192
202
|
body.push('', `> ⚠ NOT audited (the auditor died): ${deadDomains.join(', ')} — absence of items below`,
|
|
193
203
|
'> for those domains means "not looked at", not "clean". Re-run the audit for them.')
|
|
194
204
|
}
|
|
205
|
+
if (!gatesCovered) {
|
|
206
|
+
body.push('', '> ⚠ Mechanical gates NOT run (the gates agent died) — lint/typecheck/test failures',
|
|
207
|
+
'> are uncounted below. Re-run the audit for the mechanical sweep.')
|
|
208
|
+
}
|
|
195
209
|
let total = 0
|
|
196
210
|
for (const d of perDomain) {
|
|
197
211
|
const items = [...d.items].sort((a, b) => SEV[a.severity] - SEV[b.severity])
|
|
@@ -211,7 +225,8 @@ const backlogOk = written != null && /done/i.test(String(written))
|
|
|
211
225
|
return {
|
|
212
226
|
backlog: backlogOk ? 'specs/refactor-backlog.md' : '(NOT written — the backlog writer died)',
|
|
213
227
|
notAudited: deadDomains, // absence of findings here means "not looked at"
|
|
214
|
-
mechanicalFailures: mech.length,
|
|
228
|
+
mechanicalFailures: gatesCovered ? mech.length : null, // null = gates agent died, NOT zero
|
|
229
|
+
gatesCovered,
|
|
215
230
|
domains: Object.fromEntries(perDomain.map(d => [d.key, d.items.length + (d.overflow || 0)])),
|
|
216
231
|
total,
|
|
217
232
|
top: perDomain.flatMap(d => d.items.map(it => ({ ...it, domain: d.key })))
|