@cloverleaf/reference-impl 0.8.6 → 0.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.
@@ -95,48 +95,69 @@ function buildMissingNodeModulesError(mainRoot: string): Error {
95
95
  }
96
96
 
97
97
  export function prepWorktree(mainRoot: string, worktreePath: string): void {
98
- const wtStandardPkg = join(worktreePath, 'standard', 'package.json');
99
- const wtRefImplPkg = join(worktreePath, 'reference-impl', 'package.json');
98
+ const embedded =
99
+ existsSync(join(worktreePath, 'standard', 'package.json')) &&
100
+ existsSync(join(worktreePath, 'reference-impl', 'package.json'));
100
101
 
101
- if (!existsSync(wtStandardPkg)) {
102
- throw new Error(`worktree missing standard/package.json at ${wtStandardPkg}`);
103
- }
104
- if (!existsSync(wtRefImplPkg)) {
105
- throw new Error(`worktree missing reference-impl/package.json at ${wtRefImplPkg}`);
102
+ // configRoot is where .cloverleaf/config/discovery.json is read from. Embedded mode
103
+ // walks up to the primary repo (which holds node_modules + the config); a non-monorepo
104
+ // consumer uses mainRoot directly.
105
+ let configRoot: string;
106
+ if (embedded) {
107
+ const resolvedMain = findPrimaryRoot(mainRoot);
108
+ if (resolvedMain === null) {
109
+ throw buildMissingNodeModulesError(mainRoot);
110
+ }
111
+ configRoot = resolvedMain;
112
+ } else {
113
+ configRoot = mainRoot;
106
114
  }
107
115
 
108
- // Resolve the actual primary repo root: start from mainRoot and walk up until we find a
109
- // directory containing both standard/node_modules and reference-impl/node_modules.
110
- const resolvedMain = findPrimaryRoot(mainRoot);
111
- if (resolvedMain === null) {
112
- throw buildMissingNodeModulesError(mainRoot);
116
+ const config = loadDiscoveryConfig(configRoot);
117
+
118
+ if (embedded) {
119
+ // Embedded / monorepo mode: prime the cloverleaf TS tooling (unchanged behavior).
120
+ copyEmbeddedArtifacts(configRoot, worktreePath);
113
121
  }
114
122
 
115
- const mainStandardNm = join(resolvedMain, 'standard', 'node_modules');
116
- const mainRefImplNm = join(resolvedMain, 'reference-impl', 'node_modules');
123
+ // Both modes: copy any gitignored dirs the consumer's tests/briefs reference.
124
+ copyPrepDirs(configRoot, config.prep_copy_dirs, worktreePath);
117
125
 
118
- const wtStandardNm = join(worktreePath, 'standard', 'node_modules');
119
- const wtRefImplNm = join(worktreePath, 'reference-impl', 'node_modules');
126
+ if (embedded) {
127
+ // Build standard/ fresh from the worktree's own sources.
128
+ execSync('npm run build', {
129
+ cwd: join(worktreePath, 'standard'),
130
+ stdio: 'pipe',
131
+ });
132
+ }
133
+
134
+ // Both modes: run the consumer's worktree setup command, if configured.
135
+ if (config.worktree_setup_command.trim() !== '') {
136
+ execSync(config.worktree_setup_command, {
137
+ cwd: worktreePath,
138
+ stdio: 'pipe',
139
+ });
140
+ }
141
+ }
120
142
 
121
- // verbatimSymlinks keeps relative symlink targets byte-identical, so the @cloverleaf/standard
122
- // link in reference-impl/node_modules/ resolves against the worktree after copy.
123
- //
124
- // primeCopy wipes the destination before cpSync. Two reasons:
125
- // 1. Idempotence: a partial prior run (or a re-invocation after a test failure) may
126
- // leave partial state; we must not trip on it.
127
- // 2. cpSync with verbatimSymlinks: true does not reliably overwrite an existing
128
- // symlink at the destination even with force: true (CLV-20 Reviewer repro was
129
- // EEXIST on vite/node_modules/.bin on second invocation).
130
- primeCopy(mainStandardNm, wtStandardNm);
131
- primeCopy(mainRefImplNm, wtRefImplNm);
143
+ /**
144
+ * Embedded / monorepo mode: copy the primary repo's installed cloverleaf TS deps + built
145
+ * dist into the worktree, preserving the @cloverleaf/standard relative symlink. (See the
146
+ * file header for the CLV-16/17/37/52 history.)
147
+ */
148
+ function copyEmbeddedArtifacts(resolvedMain: string, worktreePath: string): void {
149
+ primeCopy(join(resolvedMain, 'standard', 'node_modules'), join(worktreePath, 'standard', 'node_modules'));
150
+ primeCopy(join(resolvedMain, 'reference-impl', 'node_modules'), join(worktreePath, 'reference-impl', 'node_modules'));
132
151
  primeCopy(join(resolvedMain, 'reference-impl', 'dist'), join(worktreePath, 'reference-impl', 'dist'));
152
+ }
133
153
 
134
- // Honor discovery_config.prep_copy_dirs: copy each listed gitignored directory
135
- // (e.g., docs/superpowers) from mainRoot into the worktree. Walker briefs reference
136
- // these paths but git checkouts of main don't carry gitignored content.
137
- const discoveryConfig = loadDiscoveryConfig(resolvedMain);
138
- for (const dir of discoveryConfig.prep_copy_dirs) {
139
- const srcPath = join(resolvedMain, dir);
154
+ /**
155
+ * Honor discovery_config.prep_copy_dirs: copy each listed gitignored directory from
156
+ * configRoot into the worktree. Missing entries warn and are skipped.
157
+ */
158
+ function copyPrepDirs(configRoot: string, dirs: string[], worktreePath: string): void {
159
+ for (const dir of dirs) {
160
+ const srcPath = join(configRoot, dir);
140
161
  const dstPath = join(worktreePath, dir);
141
162
  if (!existsSync(srcPath)) {
142
163
  process.stderr.write(`prep-worktree: prep_copy_dirs entry '${dir}' not found at ${srcPath} — skipping.\n`);
@@ -144,11 +165,6 @@ export function prepWorktree(mainRoot: string, worktreePath: string): void {
144
165
  }
145
166
  primeCopy(srcPath, dstPath);
146
167
  }
147
-
148
- execSync('npm run build', {
149
- cwd: join(worktreePath, 'standard'),
150
- stdio: 'pipe',
151
- });
152
168
  }
153
169
 
154
170
  function primeCopy(src: string, dst: string): void {
package/lib/qa-report.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+
1
4
  export interface QaRunResult {
2
5
  ruleId: string;
3
6
  command: string;
@@ -39,6 +42,18 @@ function renderRow(r: QaRunResult): string {
39
42
  `;
40
43
  }
41
44
 
45
+ /**
46
+ * Read a QA runs JSON file (array of QaRunResult), render the HTML report, and write it to
47
+ * outHtmlPath, creating the parent directory if needed. Lets QA write its report via the
48
+ * cloverleaf-cli bin instead of importing a monorepo dist path.
49
+ */
50
+ export function writeQaReportFromFile(runsJsonPath: string, outHtmlPath: string): void {
51
+ const runs = JSON.parse(readFileSync(runsJsonPath, 'utf-8')) as QaRunResult[];
52
+ const html = renderQaReport(runs);
53
+ mkdirSync(dirname(outHtmlPath), { recursive: true });
54
+ writeFileSync(outHtmlPath, html, 'utf-8');
55
+ }
56
+
42
57
  export function renderQaReport(runs: QaRunResult[]): string {
43
58
  const empty = runs.length === 0
44
59
  ? `<p class="empty">No runs / results.</p>`
package/lib/rfc-tasks.ts CHANGED
@@ -6,8 +6,7 @@ import type { PlanDoc } from './plan.js';
6
6
 
7
7
  /**
8
8
  * A task is "standalone" (RFC-direct) iff it has no parent (absent or null)
9
- * AND it has a non-empty context.rfc.id. See
10
- * docs/superpowers/specs/2026-05-12-rfc-direct-tasks-design.md §"Discriminator".
9
+ * AND it has a non-empty context.rfc.id.
11
10
  */
12
11
  export function isStandaloneTask(task: TaskDoc): boolean {
13
12
  const parent = (task as Record<string, unknown>).parent;
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloverleaf/reference-impl",
3
- "version": "0.8.6",
3
+ "version": "0.10.0",
4
4
  "description": "Reference implementation of the Cloverleaf methodology as Claude Code skills. Implements the Tight Loop (Implementer + Reviewer).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -8,6 +8,7 @@ You are the Cloverleaf Implementer agent. Your job: take a Task and produce work
8
8
  - `feedback`: optional — the most recent feedback envelope from a prior Reviewer bounce. If present, address every finding before re-submitting.
9
9
  - `repo_root`: absolute path to the consumer repo.
10
10
  - `base_branch`: the branch to branch off (default: `main`).
11
+ - `test_rules`: a JSON object `{ rules: [...] }` whose `rules` is a list of `{cwd, match, command}` entries (from `qa-rules.json`).
11
12
 
12
13
  ## Your process
13
14
 
@@ -26,7 +27,7 @@ You are the Cloverleaf Implementer agent. Your job: take a Task and produce work
26
27
  2. If `feedback` is present, re-read each finding; plan how to address them.
27
28
  3. Create a new branch named `cloverleaf/<task.id>` from `base_branch` using `git checkout -b cloverleaf/<task.id>`.
28
29
  4. Implement the code + tests needed to satisfy every acceptance criterion.
29
- 5. Run the project's test command (typically `npm test` — check package.json `scripts.test`). All tests must pass.
30
+ 5. Run the project's tests. Your test rules are provided as `{{test_rules}}`a JSON object `{ rules: [...] }` whose `rules` is a list of `{cwd, match, command}` entries; each `match` is a list of glob patterns. For each rule whose `match` covers a file you changed, run its `command` in its `cwd`. All must pass. (If no rule matches your changes, there is nothing to run.)
30
31
  6. Stage and commit your changes with message `feat: <task.title> [<task.id>]`.
31
32
  7. Return a structured JSON result to stdout:
32
33
 
@@ -54,5 +55,5 @@ If you cannot complete the task:
54
55
  - Do NOT open a PR.
55
56
  - Do NOT modify `.cloverleaf/` — state transitions are the skill's job.
56
57
  - Do NOT skip tests or write placeholder tests. Every acceptance criterion must be covered by a real, meaningful test.
57
- - Work within the existing project patterns. If the repo has a tsconfig, package.json scripts, or test conventions, follow them.
58
+ - Work within the existing project patterns. Follow the repo's existing conventions — its configuration, scripts, and test layout.
58
59
  - Small, focused commits are preferred but a single well-scoped commit is acceptable for this task.
package/prompts/qa.md CHANGED
@@ -25,11 +25,11 @@ The Standard's QA contract requires a `preview_uri`. You were passed the sentine
25
25
 
26
26
  Run this as the first executable step before anything else. Session B sessions may inherit an arbitrary `cwd` from the walker harness; this anchors you at the repo root.
27
27
 
28
- 1. Set up isolated worktree and prepare its node_modules + standard/dist. The `prep-worktree`
29
- helper copies main's `standard/node_modules` and `reference-impl/node_modules` into the
30
- worktree and runs the standard build script so the @cloverleaf/standard symlink resolves
31
- correctly inside the worktree. (Without this, `tsc` fails with `Cannot find module
32
- '@cloverleaf/standard/validators/index.js'` because git worktrees don't inherit node_modules.)
28
+ 1. Set up an isolated worktree and prime it so the project's tests can run. The
29
+ `prep-worktree` helper prepares the worktree's dependencies (for a TypeScript monorepo it
30
+ copies the built tooling; for other projects it runs the configured `worktree_setup_command`
31
+ and copies any `prep_copy_dirs`). Without it, a fresh worktree may lack the dependencies the
32
+ tests need.
33
33
  ```bash
34
34
  TMPDIR=$(mktemp -d)
35
35
  SHA=$(git rev-parse {{branch}})
@@ -50,8 +50,8 @@ The Standard's QA contract requires a `preview_uri`. You were passed the sentine
50
50
  - Run it in `"$TMPDIR/<cwd>"`
51
51
  - Capture stdout, stderr, exit code
52
52
  - Parse test output to extract `passed`, `failed`, `total`:
53
- - Vitest: `Tests N passed | M failed (T)` or similar
54
- - npm build: treat exit 0 as `{passed: 1, failed: 0, total: 1}`, non-zero as `{passed: 0, failed: 1, total: 1}`
53
+ - Exit code is the universal signal: exit 0 = the command's checks passed; non-zero = failed.
54
+ - When the output format is recognized, also extract counts, e.g. Vitest (`Tests N passed | M failed`), pytest (`N passed, M failed`), or a plain build/lint (exit 0 → `{passed: 1, failed: 0, total: 1}`).
55
55
  - On failure, collect up to 10 failure names/messages as findings with `severity: "error"` and `rule: "qa.<suite>.<test-name>"`
56
56
 
57
57
  5. Aggregate results: sum `passed`, `failed`, `total` across all runs.
@@ -59,7 +59,7 @@ The Standard's QA contract requires a `preview_uri`. You were passed the sentine
59
59
  6. Compute verdict:
60
60
  - `pass` — every command exited 0 AND aggregated `failed === 0`
61
61
  - `bounce` — any command exited non-zero OR `failed > 0`; findings list the first ~10 failures
62
- - `escalate` — any command failed deterministically on 3 consecutive retries (attempt the rerun yourself), OR `npm ci` itself failed (infrastructure problem)
62
+ - `escalate` — any command failed deterministically on 3 consecutive retries (attempt the rerun yourself), OR the worktree setup itself failed (infrastructure problem)
63
63
 
64
64
  7. Teardown:
65
65
  ```bash
@@ -72,17 +72,25 @@ The Standard's QA contract requires a `preview_uri`. You were passed the sentine
72
72
  - Read-only. Do NOT edit source files.
73
73
  - Use `git worktree`: do NOT `git checkout` in the main working directory.
74
74
  - Always teardown the worktree, even on error.
75
- - **Loading or running a module directly.** Do not improvise `node -e "import('./lib/x.js')"` to spot-check a module — sources are `.ts` and the build emits `.mjs`, so a bare `.js` import resolves to neither and fails with `ERR_MODULE_NOT_FOUND`. Use `npx tsx` instead (already in the worktree's `node_modules`): it resolves `.ts` sources **and** the project's `.js`-style import specifiers, so the natural import works. Run it from the worktree's `reference-impl/` directory; for anything the test suite already covers, prefer `npm test`.
75
+ - **Loading or running a module directly (TypeScript projects).** If your project is TypeScript, do not improvise `node -e "import('./lib/x.js')"` to spot-check a module — sources are `.ts` and the build emits `.mjs`, so a bare `.js` import resolves to neither. Use `npx tsx` instead (resolves `.ts` sources and `.js`-style import specifiers):
76
76
 
77
77
  ```bash
78
78
  npx tsx -e "import('./lib/<module>.js').then(m => console.log(Object.keys(m)))"
79
79
  ```
80
80
 
81
+ For other ecosystems, use your language's module-load or REPL equivalent. For anything the test suite already covers, prefer running the tests.
82
+
81
83
  ## QA Report (v0.4)
82
84
 
83
85
  After executing all matched QA rules, write an HTML report summarizing each run to `<repoRoot>/.cloverleaf/runs/{taskId}/qa/report.html` (substitute `{taskId}` with the `id` field from the task input, e.g., `{{task.id}}`).
84
86
 
85
- Use `renderQaReport(runs)` from `lib/qa-report.ts` to produce the HTML. The compiled artifact is at `<repoRoot>/reference-impl/dist/qa-report.mjs` invoke via `node --input-type=module` or import from there. Ensure the output directory exists first (`mkdir -p`).
87
+ Write the runs array (one `{ruleId, command, cwd, durationMs, passed, stdoutTail, stderrTail}` object per executed command) to a temp JSON file, then generate the report via the CLI:
88
+
89
+ ```bash
90
+ cloverleaf-cli qa-report /tmp/cl-qa-runs-{taskId}.json "<repoRoot>/.cloverleaf/runs/{taskId}/qa/report.html"
91
+ ```
92
+
93
+ The CLI creates the output directory.
86
94
 
87
95
  In the feedback you emit, include the report as an attachment on a single info-level finding (or on whichever summary finding you already emit):
88
96
 
@@ -8,6 +8,7 @@ You are the Cloverleaf Reviewer agent. Your job: perform a fresh-eyes review of
8
8
  - `branch`: the branch name the Implementer produced (e.g., `cloverleaf/DEMO-001`).
9
9
  - `base_branch`: the branch to diff against (default: `main`).
10
10
  - `repo_root`: absolute path to the consumer repo.
11
+ - `test_rules`: a JSON object `{ rules: [...] }` whose `rules` is a list of `{cwd, match, command}` entries (from `qa-rules.json`).
11
12
 
12
13
  ## Your process
13
14
 
@@ -49,15 +50,16 @@ A `pass` verdict MAY have an empty `findings` array or omit it. A `bounce` verdi
49
50
  - You are a fresh pair of eyes. Do not rubber-stamp. If you have substantive doubts, bounce.
50
51
  - Check that tests actually cover the AC; a passing test suite with no AC coverage is a bounce.
51
52
  - Do NOT modify any files. You are read-only.
52
- - Do NOT use `git checkout` or `git switch`. Read files via `git show <branch>:<path>`. If you need a live checkout to run tests, use a worktree and prime it with `cloverleaf-cli prep-worktree` (copies main's node_modules + builds standard/dist inside the worktree without this, `tsc` fails with `Cannot find module '@cloverleaf/standard/validators/index.js'`):
53
+ - Do NOT use `git checkout` or `git switch`. Read files via `git show <branch>:<path>`. If you need a live checkout to run tests, use a worktree and prime it with `cloverleaf-cli prep-worktree` (prepares the worktree so the project's tests can run):
53
54
 
54
55
  ```bash
55
56
  MAIN=$(pwd)
56
57
  SHA=$(git rev-parse cloverleaf/<task-id>)
57
58
  git worktree add --detach /tmp/cl-review-<task-id> "$SHA"
58
59
  cloverleaf-cli prep-worktree "$MAIN" /tmp/cl-review-<task-id>
59
- cd /tmp/cl-review-<task-id>/reference-impl
60
- npm test
60
+ # Run the project's tests. Your rules are in {{test_rules}} (JSON object { rules: [{cwd, match, command}, ...] }).
61
+ # For each rule whose match globs cover a changed file, run its command in
62
+ # /tmp/cl-review-<task-id>/<cwd>.
61
63
  cd -
62
64
  git worktree remove /tmp/cl-review-<task-id>
63
65
  ```
@@ -65,10 +67,12 @@ A `pass` verdict MAY have an empty `findings` array or omit it. A `bounce` verdi
65
67
  Use `--detach` with a SHA rather than a branch name: when running inside a walker worktree, the feature branch (and main) may already be checked out in another worktree, causing `git worktree add` to fail with "fatal: branch … is already checked out". Detaching at a SHA bypasses this constraint entirely.
66
68
 
67
69
  This keeps `.cloverleaf/` on main intact.
68
- - **Loading or running a module directly.** Do not improvise `node -e "import('./lib/x.js')"` to spot-check a module — sources are `.ts` and the build emits `.mjs`, so a bare `.js` import resolves to neither and fails with `ERR_MODULE_NOT_FOUND`. Use `npx tsx` instead (already in the worktree's `node_modules`): it resolves `.ts` sources **and** the project's `.js`-style import specifiers, so the natural import works. Run it from the worktree's `reference-impl/` directory; for anything the test suite already covers, prefer `npm test`.
70
+ - **Loading or running a module directly (TypeScript projects).** If your project is TypeScript, do not improvise `node -e "import('./lib/x.js')"` to spot-check a module — sources are `.ts` and the build emits `.mjs`, so a bare `.js` import resolves to neither. Use `npx tsx` instead (resolves `.ts` sources and `.js`-style import specifiers):
69
71
 
70
72
  ```bash
71
73
  npx tsx -e "import('./lib/<module>.js').then(m => console.log(Object.keys(m)))"
72
74
  ```
75
+
76
+ For other ecosystems, use your language's module-load or REPL equivalent. For anything the test suite already covers, prefer running the tests.
73
77
  - Severities (per the Cloverleaf feedback schema): `blocker` = wrong behavior / missing AC / broken tests; `error` = notable defect that should be fixed but doesn't break AC; `warning` = should fix; `info` = nit / style. Use `blocker` and `error` for bounces.
74
78
  - If a criterion is subjective, lean toward pass — the task author chose those words deliberately.
@@ -23,6 +23,16 @@ The user has invoked this skill with a TASK-ID (e.g., `DEMO-001`).
23
23
  ```
24
24
  Capture the output. If present and the latest verdict is `bounce`, pass it into the subagent.
25
25
 
26
+ 3b. Load the project's test rules (consumer override or shipped default):
27
+ ```bash
28
+ if [ -f "<repo_root>/.cloverleaf/config/qa-rules.json" ]; then
29
+ cat "<repo_root>/.cloverleaf/config/qa-rules.json"
30
+ else
31
+ cat "$(cloverleaf-cli plugin-root)/config/qa-rules.json"
32
+ fi
33
+ ```
34
+ Capture the output as `test_rules`.
35
+
26
36
  4. Dispatch the Implementer subagent via the Task tool:
27
37
  - `subagent_type`: `general-purpose`
28
38
  - `model`: `sonnet`
@@ -31,6 +41,7 @@ The user has invoked this skill with a TASK-ID (e.g., `DEMO-001`).
31
41
  - `{{feedback}}` → the feedback JSON if present, else the literal string `null`
32
42
  - `{{repo_root}}` → absolute path to the current repo
33
43
  - `{{base_branch}}` → `main` (or the current default branch)
44
+ - `{{test_rules}}` → the test rules JSON captured in step 3b
34
45
 
35
46
  **Dispatch conventions:** invoke the Task tool in foreground mode (its default — do NOT pass `run_in_background: true`). The Task tool returns the subagent's final message as a string in the result. Do NOT use Bash `sleep` to poll an output file — the harness blocks foreground `sleep`, and background dispatch is unnecessary here because the foreground Task tool already blocks until the subagent finishes.
36
47
 
@@ -37,10 +37,20 @@ description: Run the Reviewer agent on a task in the `review` state. Emits a fee
37
37
  ```
38
38
  Capture this output for the subagent.
39
39
 
40
+ 4b. Load the project's test rules (consumer override or shipped default):
41
+ ```bash
42
+ if [ -f "<repo_root>/.cloverleaf/config/qa-rules.json" ]; then
43
+ cat "<repo_root>/.cloverleaf/config/qa-rules.json"
44
+ else
45
+ cat "$(cloverleaf-cli plugin-root)/config/qa-rules.json"
46
+ fi
47
+ ```
48
+ Capture the output as `test_rules`.
49
+
40
50
  5. Dispatch the Reviewer subagent via the Task tool:
41
51
  - `subagent_type`: `general-purpose`
42
52
  - `model`: `sonnet`
43
- - Prompt: contents of `$(cloverleaf-cli plugin-root)/prompts/reviewer.md` with substitutions for `{{task}}`, `{{branch}}`, `{{base_branch}}`, `{{repo_root}}`, `{{diff}}`.
53
+ - Prompt: contents of `$(cloverleaf-cli plugin-root)/prompts/reviewer.md` with substitutions for `{{task}}`, `{{branch}}`, `{{base_branch}}`, `{{repo_root}}`, `{{diff}}`, `{{test_rules}}`.
44
54
 
45
55
  **Dispatch conventions:** invoke the Task tool in foreground mode (its default — do NOT pass `run_in_background: true`). The Task tool returns the subagent's final message as a string in the result. Do NOT use Bash `sleep` to poll an output file — the harness blocks foreground `sleep`, and background dispatch is unnecessary here because the foreground Task tool already blocks until the subagent finishes.
46
56
 
@@ -57,9 +57,11 @@ Recovery sequence:
57
57
 
58
58
  2. Load task: `cloverleaf-cli load-task <repo_root> <TASK-ID>`. Verify `status === "pending"`. If not, report and stop.
59
59
 
60
- 3. Read `task.risk_class`:
61
- - `"low"` → go to section 4 (Fast Lane)
62
- - `"high"` → go to section 5 (Full Pipeline)
60
+ 3. **Council gate detection (opt-in).** Run `cloverleaf-cli council-plan <repo_root> <TASK-ID> task.review` and parse the JSON plan. If `plan.source === "consumer"` **and** `plan.profile !== null`, the project has opted into a configured review council — drive the review phase via **section 7 (Council review path)** instead of the hardcoded reviewer/security/ui/qa steps in sections 4/5. Otherwise (`source: "default"`, or no `task.review` binding) proceed exactly as today:
61
+ - `task.risk_class === "low"` → section 4 (Fast Lane)
62
+ - `task.risk_class === "high"` → section 5 (Full Pipeline)
63
+
64
+ When the council path is active it still uses the Implementer (and, for the full pipeline, the Documenter) to produce the branch; only the review→merge portion is council-driven.
63
65
 
64
66
  ### 4. Fast Lane
65
67
 
@@ -122,6 +124,35 @@ Loop:
122
124
  - Commit: `git add .cloverleaf/ && git commit -m "cloverleaf: <TASK-ID> escalated (bounce budget exhausted)"`.
123
125
  - Report: "✗ Escalated `<TASK-ID>`. Review `.cloverleaf/feedback/` and either refine the task or take over manually. Counters: reviewer=<N>, ui_reviewer=<N>, qa=<N>, security=<N>."
124
126
 
127
+ ### 7. Council review path (opt-in; active when council-plan source is "consumer")
128
+
129
+ Initialize `council_bounces = 0`.
130
+
131
+ 7.1 **Produce the branch.** Run the Implementer (`/cloverleaf-implement <TASK-ID>` steps); for `risk_class: "high"` also run the Documenter (`/cloverleaf-document <TASK-ID>` steps). The task reaches `review`.
132
+
133
+ 7.2 **Run the council members (verdict-only).** Re-run `cloverleaf-cli council-plan <repo_root> <TASK-ID> task.review` to get `plan.rounds`, `plan.aggregation`, `plan.on_round_bounce`. For each round **in order**, for each member in the round, dispatch the member's prompt as a **read-only** subagent and capture its `{verdict, summary, findings}` envelope — do **not** advance state:
134
+ - `reviewer` → `prompts/reviewer.md`, feedback prefix `r`
135
+ - `security` → `prompts/security-reviewer.md`, prefix `s`
136
+ - `ui` → `prompts/ui-reviewer.md`, prefix `u`
137
+ - `qa` → `prompts/qa.md`, prefix `q`
138
+
139
+ **Dispatch conventions:** invoke the Task tool in foreground (default — never `run_in_background`); do not poll with foreground `sleep`. Substitute `{{task}}`, `{{branch}}` (`cloverleaf/<TASK-ID>`), `{{base_branch}}` (`main`), `{{repo_root}}`, `{{diff}}` (`git diff main..cloverleaf/<TASK-ID> -- ':(exclude).cloverleaf/'`).
140
+
141
+ Persist each member's envelope: `echo '<envelope>' > /tmp/clv-council-<member>.json && cloverleaf-cli write-feedback <repo_root> <TASK-ID> /tmp/clv-council-<member>.json --prefix=<r|s|u|q>`. Collect a members array `[{ "member": "<id>", "verdict": "<pass|bounce|escalate>", "blocking": <plan member blocking>, "weight": <plan member weight> }]`.
142
+
143
+ **Short-circuit:** if any member returns `escalate`, stop immediately. Otherwise, after each round, if `plan.on_round_bounce === "stop"` and any **blocking** member in that round returned `bounce`, stop before the next round. Always finish the members already running in the current round (batched).
144
+
145
+ 7.3 **Aggregate.** Map `plan.aggregation` to the CLI rule argument: a string passes through; `{ "quorum": k }` → `quorum:k`. Run `cloverleaf-cli aggregate-verdicts '<members-json>' <rule>` and capture the council verdict JSON.
146
+
147
+ 7.4 **Apply.** Run `cloverleaf-cli apply-council-verdict <repo_root> <TASK-ID> task.review '<council-verdict-json>'`. The FSM walk may self-commit some transitions (e.g. `security_class → high`, the rework verdict-reset), so the wrap-up commit can find nothing staged — that is expected. Commit the remainder: `git add .cloverleaf/ && (git diff --cached --quiet || git commit -m "cloverleaf: <TASK-ID> council review (<verdict>)")`.
148
+
149
+ 7.5 **Branch on the task's new status (reload with `load-task`):**
150
+ - `automated-gates` (fast lane pass) or `final-gate` (full pipeline pass) → proceed to the merge: inline `/cloverleaf-merge <TASK-ID>`.
151
+ - `implementing` (bounce) → `council_bounces += 1`. If `council_bounces >= 3`, escalate (section 6). Else return to 7.1.
152
+ - `escalated` → stop and surface to the user (review `.cloverleaf/feedback/` and `.cloverleaf/runs/<TASK-ID>/council/task.review.json`).
153
+
154
+ The council result artifact at `.cloverleaf/runs/<TASK-ID>/council/task.review.json` records per-member verdicts, the aggregate, and the security basis (incl. an omitted or out-voted `security` member). On any member-dispatch failure or unparseable envelope, stop and report — never treat a failed member as a pass.
155
+
125
156
  ## Rules
126
157
 
127
158
  - Each agent has its own 3-bounce budget. Bounces from different agents do NOT share counters.