@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.
- package/.claude-plugin/plugin.json +1 -1
- package/VERSION +1 -1
- package/config/council.json +19 -0
- package/config/discovery.json +2 -1
- package/dist/aggregation.mjs +60 -0
- package/dist/cli.mjs +64 -0
- package/dist/council-config.mjs +47 -0
- package/dist/council-result.mjs +19 -0
- package/dist/council.mjs +159 -0
- package/dist/discovery-config.mjs +2 -0
- package/dist/paths.mjs +3 -0
- package/dist/prep-worktree.mjs +51 -36
- package/dist/qa-report.mjs +13 -0
- package/dist/rfc-tasks.mjs +1 -2
- package/dist/security-classify.mjs +0 -0
- package/lib/aggregation.ts +87 -0
- package/lib/cli.ts +67 -0
- package/lib/council-config.ts +79 -0
- package/lib/council-result.ts +45 -0
- package/lib/council.ts +203 -0
- package/lib/discovery-config.ts +5 -0
- package/lib/paths.ts +4 -0
- package/lib/prep-worktree.ts +54 -38
- package/lib/qa-report.ts +15 -0
- package/lib/rfc-tasks.ts +1 -2
- package/lib/security-classify.ts +0 -0
- package/package.json +1 -1
- package/prompts/implementer.md +3 -2
- package/prompts/qa.md +18 -10
- package/prompts/reviewer.md +8 -4
- package/skills/cloverleaf-implement/SKILL.md +11 -0
- package/skills/cloverleaf-review/SKILL.md +11 -1
- package/skills/cloverleaf-run/SKILL.md +34 -3
package/lib/prep-worktree.ts
CHANGED
|
@@ -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
|
|
99
|
-
|
|
98
|
+
const embedded =
|
|
99
|
+
existsSync(join(worktreePath, 'standard', 'package.json')) &&
|
|
100
|
+
existsSync(join(worktreePath, 'reference-impl', 'package.json'));
|
|
100
101
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
116
|
-
|
|
123
|
+
// Both modes: copy any gitignored dirs the consumer's tests/briefs reference.
|
|
124
|
+
copyPrepDirs(configRoot, config.prep_copy_dirs, worktreePath);
|
|
117
125
|
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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.
|
|
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;
|
package/lib/security-classify.ts
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloverleaf/reference-impl",
|
|
3
|
-
"version": "0.
|
|
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",
|
package/prompts/implementer.md
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
29
|
-
helper
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
-
|
|
54
|
-
-
|
|
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
|
|
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.**
|
|
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
|
-
|
|
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
|
|
package/prompts/reviewer.md
CHANGED
|
@@ -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` (
|
|
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
|
-
|
|
60
|
-
|
|
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.**
|
|
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.
|
|
61
|
-
- `"low"` →
|
|
62
|
-
- `"high"` →
|
|
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.
|