@cloverleaf/reference-impl 0.5.3 → 0.5.4
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/README.md +2 -1
- package/VERSION +1 -1
- package/dist/cli.mjs +21 -0
- package/dist/index.mjs +1 -0
- package/dist/paths.mjs +6 -0
- package/dist/ui-review-state.mjs +40 -0
- package/lib/cli.ts +22 -0
- package/lib/index.ts +1 -0
- package/lib/paths.ts +8 -0
- package/lib/ui-review-state.ts +52 -0
- package/package.json +1 -1
- package/prompts/ui-reviewer.md +17 -3
- package/skills/cloverleaf-approve-baselines/SKILL.md +85 -0
- package/skills/cloverleaf-ui-review/SKILL.md +28 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloverleaf",
|
|
3
3
|
"description": "Cloverleaf reference implementation — Claude Code skills for task scaffolding and the Delivery pipeline (implementer, documenter, reviewer, UI reviewer with multi-viewport visual diff, QA, merge).",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.4",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Renato D'Arrigo",
|
|
7
7
|
"email": "renato.darrigo@gmail.com"
|
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ v0.2 implements both paths of the Delivery track:
|
|
|
41
41
|
- `/cloverleaf-document` — run Documenter *(new in v0.2)*
|
|
42
42
|
- `/cloverleaf-review` — run Reviewer
|
|
43
43
|
- `/cloverleaf-ui-review` — run UI Reviewer *(new in v0.2)*
|
|
44
|
+
- `/cloverleaf-approve-baselines` — human baseline-approval gate; clears `baselines_pending` and advances `ui-review → qa` *(new in CLV-19)*
|
|
44
45
|
- `/cloverleaf-qa` — run QA *(new in v0.2)*
|
|
45
46
|
- `/cloverleaf-merge` — human gate (branches on state)
|
|
46
47
|
- `/cloverleaf-run` — orchestrator (dispatches by `risk_class`)
|
|
@@ -145,7 +146,7 @@ The Reviewer never switches branches. It reads files via `git show` and runs tes
|
|
|
145
146
|
|
|
146
147
|
## Package layout
|
|
147
148
|
|
|
148
|
-
- `lib/` — TypeScript library used by the CLI. State, events, feedback, IDs, paths. Includes `buildBaselinePath(repoRoot, browser, slug, viewport)` (`lib/visual-diff.ts`) for constructing canonical baseline paths under `.cloverleaf/baselines/{browser}/`. `lib/ui-browser.ts` exports `buildBrowserEscalationFinding` and `applyMaxCombinationsCap` (used by the UI Reviewer prompt for per-engine escalation and combination-count capping).
|
|
149
|
+
- `lib/` — TypeScript library used by the CLI. State, events, feedback, IDs, paths. Includes `buildBaselinePath(repoRoot, browser, slug, viewport)` (`lib/visual-diff.ts`) for constructing canonical baseline paths under `.cloverleaf/baselines/{browser}/`. `lib/ui-browser.ts` exports `buildBrowserEscalationFinding` and `applyMaxCombinationsCap` (used by the UI Reviewer prompt for per-engine escalation and combination-count capping). `lib/ui-review-state.ts` exports `readUiReviewState`, `writeUiReviewState`, and `uiReviewStatePath` — the baseline-approval sidecar API for `.cloverleaf/runs/{taskId}/ui-review/state.json`.
|
|
149
150
|
- `skills/` — Claude Code skill markdown files.
|
|
150
151
|
- `prompts/` — Implementer/Reviewer subagent system prompts.
|
|
151
152
|
- `examples/toy-repo/` — standalone demo repo.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.5.
|
|
1
|
+
0.5.4
|
package/dist/cli.mjs
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* latest-feedback <repoRoot> <taskId>
|
|
14
14
|
* emit-gate-decision <repoRoot> <workItemId> <gate> <decision> <actor> [--comment=<str>]
|
|
15
15
|
* ui-review-config --repo-root <repoRoot>
|
|
16
|
+
* read-ui-review-state <repoRoot> <taskId>
|
|
17
|
+
* write-ui-review-state <repoRoot> <taskId> <baselines_pending>
|
|
16
18
|
* plugin-root
|
|
17
19
|
* load-rfc <repoRoot> <id>
|
|
18
20
|
* save-rfc <repoRoot> <filePath>
|
|
@@ -46,6 +48,7 @@ import { loadSpike, saveSpike, advanceSpikeStatus } from './spike.mjs';
|
|
|
46
48
|
import { loadPlan, savePlan, advancePlanStatus, materialiseTasksFromPlan } from './plan.mjs';
|
|
47
49
|
import { loadDiscoveryConfig } from './discovery-config.mjs';
|
|
48
50
|
import { prepWorktree } from './prep-worktree.mjs';
|
|
51
|
+
import { readUiReviewState, writeUiReviewState } from './ui-review-state.mjs';
|
|
49
52
|
function die(msg, code = 1) {
|
|
50
53
|
process.stderr.write(msg + '\n');
|
|
51
54
|
process.exit(code);
|
|
@@ -63,6 +66,8 @@ function usage(msg) {
|
|
|
63
66
|
' latest-feedback <repoRoot> <taskId>\n' +
|
|
64
67
|
' emit-gate-decision <repoRoot> <workItemId> <gate> <decision> <actor> [--comment=<str>]\n' +
|
|
65
68
|
' ui-review-config --repo-root <repoRoot>\n' +
|
|
69
|
+
' read-ui-review-state <repoRoot> <taskId>\n' +
|
|
70
|
+
' write-ui-review-state <repoRoot> <taskId> <baselines_pending>\n' +
|
|
66
71
|
' plugin-root\n' +
|
|
67
72
|
' load-rfc <repoRoot> <id>\n' +
|
|
68
73
|
' save-rfc <repoRoot> <filePath>\n' +
|
|
@@ -269,6 +274,22 @@ try {
|
|
|
269
274
|
process.stdout.write(JSON.stringify(config, null, 2));
|
|
270
275
|
process.exit(0);
|
|
271
276
|
}
|
|
277
|
+
case 'read-ui-review-state': {
|
|
278
|
+
const [repoRoot, taskId] = rest;
|
|
279
|
+
if (!repoRoot || !taskId)
|
|
280
|
+
usage('read-ui-review-state requires <repoRoot> <taskId>');
|
|
281
|
+
const state = readUiReviewState(repoRoot, taskId);
|
|
282
|
+
process.stdout.write(JSON.stringify(state, null, 2) + '\n');
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
case 'write-ui-review-state': {
|
|
286
|
+
const [repoRoot, taskId, pendingArg] = rest;
|
|
287
|
+
if (!repoRoot || !taskId || pendingArg === undefined)
|
|
288
|
+
usage('write-ui-review-state requires <repoRoot> <taskId> <baselines_pending>');
|
|
289
|
+
const baselines_pending = pendingArg === 'true' || pendingArg === '1';
|
|
290
|
+
writeUiReviewState(repoRoot, taskId, { baselines_pending });
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
272
293
|
case 'plugin-root': {
|
|
273
294
|
process.stdout.write(getPluginRoot());
|
|
274
295
|
process.exit(0);
|
package/dist/index.mjs
CHANGED
package/dist/paths.mjs
CHANGED
|
@@ -24,3 +24,9 @@ export function spikesDir(repoRoot) {
|
|
|
24
24
|
export function plansDir(repoRoot) {
|
|
25
25
|
return resolve(cloverleafDir(repoRoot), 'plans');
|
|
26
26
|
}
|
|
27
|
+
export function runsDir(repoRoot) {
|
|
28
|
+
return resolve(cloverleafDir(repoRoot), 'runs');
|
|
29
|
+
}
|
|
30
|
+
export function uiReviewRunDir(repoRoot, taskId) {
|
|
31
|
+
return resolve(runsDir(repoRoot), taskId, 'ui-review');
|
|
32
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { uiReviewRunDir } from './paths.mjs';
|
|
4
|
+
const STATE_FILENAME = 'state.json';
|
|
5
|
+
/**
|
|
6
|
+
* Returns the canonical path for the ui-review sidecar state file:
|
|
7
|
+
* .cloverleaf/runs/{taskId}/ui-review/state.json
|
|
8
|
+
*/
|
|
9
|
+
export function uiReviewStatePath(repoRoot, taskId) {
|
|
10
|
+
return join(uiReviewRunDir(repoRoot, taskId), STATE_FILENAME);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Reads the ui-review state sidecar from disk.
|
|
14
|
+
*
|
|
15
|
+
* Returns `{ baselines_pending: false }` when the file is absent — the
|
|
16
|
+
* absence of the file is treated as "no pending baselines", which lets the
|
|
17
|
+
* ui-review → qa transition proceed normally.
|
|
18
|
+
*/
|
|
19
|
+
export function readUiReviewState(repoRoot, taskId) {
|
|
20
|
+
const path = uiReviewStatePath(repoRoot, taskId);
|
|
21
|
+
if (!existsSync(path)) {
|
|
22
|
+
return { baselines_pending: false };
|
|
23
|
+
}
|
|
24
|
+
const raw = JSON.parse(readFileSync(path, 'utf-8'));
|
|
25
|
+
return { baselines_pending: Boolean(raw.baselines_pending) };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Writes the ui-review state sidecar to disk, creating intermediate directories
|
|
29
|
+
* as needed.
|
|
30
|
+
*
|
|
31
|
+
* @param repoRoot Absolute path to the repository root.
|
|
32
|
+
* @param taskId Task identifier (e.g. "CLV-42").
|
|
33
|
+
* @param state The state to persist.
|
|
34
|
+
*/
|
|
35
|
+
export function writeUiReviewState(repoRoot, taskId, state) {
|
|
36
|
+
const dir = uiReviewRunDir(repoRoot, taskId);
|
|
37
|
+
mkdirSync(dir, { recursive: true });
|
|
38
|
+
const path = join(dir, STATE_FILENAME);
|
|
39
|
+
writeFileSync(path, JSON.stringify(state, null, 2) + '\n');
|
|
40
|
+
}
|
package/lib/cli.ts
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* latest-feedback <repoRoot> <taskId>
|
|
14
14
|
* emit-gate-decision <repoRoot> <workItemId> <gate> <decision> <actor> [--comment=<str>]
|
|
15
15
|
* ui-review-config --repo-root <repoRoot>
|
|
16
|
+
* read-ui-review-state <repoRoot> <taskId>
|
|
17
|
+
* write-ui-review-state <repoRoot> <taskId> <baselines_pending>
|
|
16
18
|
* plugin-root
|
|
17
19
|
* load-rfc <repoRoot> <id>
|
|
18
20
|
* save-rfc <repoRoot> <filePath>
|
|
@@ -48,6 +50,7 @@ import { loadSpike, saveSpike, advanceSpikeStatus, type SpikeDoc } from './spike
|
|
|
48
50
|
import { loadPlan, savePlan, advancePlanStatus, materialiseTasksFromPlan, type PlanDoc } from './plan.js';
|
|
49
51
|
import { loadDiscoveryConfig } from './discovery-config.js';
|
|
50
52
|
import { prepWorktree } from './prep-worktree.js';
|
|
53
|
+
import { readUiReviewState, writeUiReviewState } from './ui-review-state.js';
|
|
51
54
|
|
|
52
55
|
function die(msg: string, code = 1): never {
|
|
53
56
|
process.stderr.write(msg + '\n');
|
|
@@ -67,6 +70,8 @@ function usage(msg?: string): never {
|
|
|
67
70
|
' latest-feedback <repoRoot> <taskId>\n' +
|
|
68
71
|
' emit-gate-decision <repoRoot> <workItemId> <gate> <decision> <actor> [--comment=<str>]\n' +
|
|
69
72
|
' ui-review-config --repo-root <repoRoot>\n' +
|
|
73
|
+
' read-ui-review-state <repoRoot> <taskId>\n' +
|
|
74
|
+
' write-ui-review-state <repoRoot> <taskId> <baselines_pending>\n' +
|
|
70
75
|
' plugin-root\n' +
|
|
71
76
|
' load-rfc <repoRoot> <id>\n' +
|
|
72
77
|
' save-rfc <repoRoot> <filePath>\n' +
|
|
@@ -278,6 +283,23 @@ try {
|
|
|
278
283
|
process.exit(0);
|
|
279
284
|
}
|
|
280
285
|
|
|
286
|
+
case 'read-ui-review-state': {
|
|
287
|
+
const [repoRoot, taskId] = rest;
|
|
288
|
+
if (!repoRoot || !taskId) usage('read-ui-review-state requires <repoRoot> <taskId>');
|
|
289
|
+
const state = readUiReviewState(repoRoot, taskId);
|
|
290
|
+
process.stdout.write(JSON.stringify(state, null, 2) + '\n');
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
case 'write-ui-review-state': {
|
|
295
|
+
const [repoRoot, taskId, pendingArg] = rest;
|
|
296
|
+
if (!repoRoot || !taskId || pendingArg === undefined)
|
|
297
|
+
usage('write-ui-review-state requires <repoRoot> <taskId> <baselines_pending>');
|
|
298
|
+
const baselines_pending = pendingArg === 'true' || pendingArg === '1';
|
|
299
|
+
writeUiReviewState(repoRoot, taskId, { baselines_pending });
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
|
|
281
303
|
case 'plugin-root': {
|
|
282
304
|
process.stdout.write(getPluginRoot());
|
|
283
305
|
process.exit(0);
|
package/lib/index.ts
CHANGED
package/lib/paths.ts
CHANGED
|
@@ -33,3 +33,11 @@ export function spikesDir(repoRoot: string): string {
|
|
|
33
33
|
export function plansDir(repoRoot: string): string {
|
|
34
34
|
return resolve(cloverleafDir(repoRoot), 'plans');
|
|
35
35
|
}
|
|
36
|
+
|
|
37
|
+
export function runsDir(repoRoot: string): string {
|
|
38
|
+
return resolve(cloverleafDir(repoRoot), 'runs');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function uiReviewRunDir(repoRoot: string, taskId: string): string {
|
|
42
|
+
return resolve(runsDir(repoRoot), taskId, 'ui-review');
|
|
43
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { uiReviewRunDir } from './paths.js';
|
|
4
|
+
|
|
5
|
+
export interface UiReviewState {
|
|
6
|
+
baselines_pending: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const STATE_FILENAME = 'state.json';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Returns the canonical path for the ui-review sidecar state file:
|
|
13
|
+
* .cloverleaf/runs/{taskId}/ui-review/state.json
|
|
14
|
+
*/
|
|
15
|
+
export function uiReviewStatePath(repoRoot: string, taskId: string): string {
|
|
16
|
+
return join(uiReviewRunDir(repoRoot, taskId), STATE_FILENAME);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Reads the ui-review state sidecar from disk.
|
|
21
|
+
*
|
|
22
|
+
* Returns `{ baselines_pending: false }` when the file is absent — the
|
|
23
|
+
* absence of the file is treated as "no pending baselines", which lets the
|
|
24
|
+
* ui-review → qa transition proceed normally.
|
|
25
|
+
*/
|
|
26
|
+
export function readUiReviewState(repoRoot: string, taskId: string): UiReviewState {
|
|
27
|
+
const path = uiReviewStatePath(repoRoot, taskId);
|
|
28
|
+
if (!existsSync(path)) {
|
|
29
|
+
return { baselines_pending: false };
|
|
30
|
+
}
|
|
31
|
+
const raw = JSON.parse(readFileSync(path, 'utf-8')) as UiReviewState;
|
|
32
|
+
return { baselines_pending: Boolean(raw.baselines_pending) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Writes the ui-review state sidecar to disk, creating intermediate directories
|
|
37
|
+
* as needed.
|
|
38
|
+
*
|
|
39
|
+
* @param repoRoot Absolute path to the repository root.
|
|
40
|
+
* @param taskId Task identifier (e.g. "CLV-42").
|
|
41
|
+
* @param state The state to persist.
|
|
42
|
+
*/
|
|
43
|
+
export function writeUiReviewState(
|
|
44
|
+
repoRoot: string,
|
|
45
|
+
taskId: string,
|
|
46
|
+
state: UiReviewState,
|
|
47
|
+
): void {
|
|
48
|
+
const dir = uiReviewRunDir(repoRoot, taskId);
|
|
49
|
+
mkdirSync(dir, { recursive: true });
|
|
50
|
+
const path = join(dir, STATE_FILENAME);
|
|
51
|
+
writeFileSync(path, JSON.stringify(state, null, 2) + '\n');
|
|
52
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloverleaf/reference-impl",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
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/ui-reviewer.md
CHANGED
|
@@ -100,7 +100,7 @@ Do not attempt to launch a missing engine — fail fast with `verdict: "escalate
|
|
|
100
100
|
7. **Verify browser binaries** — before starting any browser session:
|
|
101
101
|
- Check each engine in `{{ui_review_config}}.browsers` against `PLAYWRIGHT_BROWSERS_PATH`.
|
|
102
102
|
- Collect all missing engines.
|
|
103
|
-
- If any engine is missing, call `buildBrowserEscalationFinding(engine, process.platform)` for each, teardown the worktree (step
|
|
103
|
+
- If any engine is missing, call `buildBrowserEscalationFinding(engine, process.platform)` for each, teardown the worktree (step 13), and return `verdict: "escalate"` with those findings.
|
|
104
104
|
|
|
105
105
|
8. **Per-browser outer loop** — for each `browser` in `{{ui_review_config}}.browsers`:
|
|
106
106
|
|
|
@@ -156,7 +156,21 @@ Do not attempt to launch a missing engine — fail fast with `verdict: "escalate
|
|
|
156
156
|
- `bounce` — ≥1 non-visual-diff, non-cap finding with severity `blocker` or `error`
|
|
157
157
|
- `escalate` — preview server failed to start, OR axe threw ≥3 consecutive times, OR any required browser binary was absent.
|
|
158
158
|
|
|
159
|
-
12.
|
|
159
|
+
12. **Write ui-review state sidecar** — after all browser passes complete and before teardown, determine whether any `compareVisual` call returned `new-baseline` or `dimension-mismatch` across all routes, viewports, and browsers in this run.
|
|
160
|
+
|
|
161
|
+
- If **yes**: write `{{repo_root}}/.cloverleaf/runs/{{taskId}}/ui-review/state.json` containing:
|
|
162
|
+
```json
|
|
163
|
+
{"baselines_pending": true}
|
|
164
|
+
```
|
|
165
|
+
(Create intermediate directories as needed.)
|
|
166
|
+
- If **no**: write `{{repo_root}}/.cloverleaf/runs/{{taskId}}/ui-review/state.json` containing:
|
|
167
|
+
```json
|
|
168
|
+
{"baselines_pending": false}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
This sidecar is the baseline-approval gate read by the `cloverleaf-ui-review` skill. Writing `baselines_pending: false` explicitly (rather than omitting the file) lets the skill distinguish "no new baselines" from "reviewer did not run at all".
|
|
172
|
+
|
|
173
|
+
13. Teardown:
|
|
160
174
|
```bash
|
|
161
175
|
kill $SERVER_PID 2>/dev/null || true
|
|
162
176
|
cd {{repo_root}}
|
|
@@ -166,7 +180,7 @@ Do not attempt to launch a missing engine — fail fast with `verdict: "escalate
|
|
|
166
180
|
## Tool constraints
|
|
167
181
|
|
|
168
182
|
- Read-only for source files and tests.
|
|
169
|
-
- You MAY write under `{{repo_root}}/.cloverleaf/baselines/` and `{{repo_root}}/.cloverleaf/runs/{taskId}/ui-review/` on the feature branch — these are the baselines and
|
|
183
|
+
- You MAY write under `{{repo_root}}/.cloverleaf/baselines/` and `{{repo_root}}/.cloverleaf/runs/{taskId}/ui-review/` on the feature branch — these are the baselines, artifacts, and the `state.json` sidecar.
|
|
170
184
|
- Use `git worktree`: do NOT `git checkout` in the main working directory.
|
|
171
185
|
- Always teardown the server and worktree, even on error.
|
|
172
186
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cloverleaf-approve-baselines
|
|
3
|
+
description: Human baseline-approval gate for the Cloverleaf UI Review pipeline. When the UI Reviewer captures new or resized visual baselines it sets baselines_pending=true in .cloverleaf/runs/{taskId}/ui-review/state.json and blocks the ui-review → qa transition. Run this skill after inspecting the new baseline images to approve them and allow the task to advance to qa. Usage — /cloverleaf-approve-baselines <TASK-ID>.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cloverleaf — approve-baselines
|
|
7
|
+
|
|
8
|
+
## Trigger condition
|
|
9
|
+
|
|
10
|
+
This skill is invoked **only** when the `cloverleaf-ui-review` skill reports that `baselines_pending` is `true` — i.e., the UI Reviewer captured at least one `new-baseline` or `dimension-mismatch` result during its run, meaning one or more baseline PNGs under `.cloverleaf/baselines/{browser}/` were created or replaced.
|
|
11
|
+
|
|
12
|
+
Do not run this skill if the task is not in `ui-review` status or if `state.json` already has `baselines_pending: false`.
|
|
13
|
+
|
|
14
|
+
## Effect
|
|
15
|
+
|
|
16
|
+
1. Writes `baselines_pending: false` to `.cloverleaf/runs/{taskId}/ui-review/state.json`.
|
|
17
|
+
2. Advances the task from `ui-review` → `qa` via the normal agent transition.
|
|
18
|
+
3. Commits the updated state and status to the feature branch.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Steps
|
|
23
|
+
|
|
24
|
+
0. Pre-flight:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cd <repo_root>
|
|
28
|
+
current=$(git rev-parse --abbrev-ref HEAD)
|
|
29
|
+
if [ "$current" != "main" ]; then git checkout main; fi
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If main has uncommitted changes, stop and report.
|
|
33
|
+
|
|
34
|
+
1. Capture the TASK-ID argument.
|
|
35
|
+
|
|
36
|
+
2. Load the task and verify status:
|
|
37
|
+
```bash
|
|
38
|
+
cloverleaf-cli load-task <repo_root> <TASK-ID>
|
|
39
|
+
```
|
|
40
|
+
Verify `status === "ui-review"`. If not, report and stop.
|
|
41
|
+
|
|
42
|
+
3. Read the current ui-review state:
|
|
43
|
+
```bash
|
|
44
|
+
cloverleaf-cli read-ui-review-state <repo_root> <TASK-ID>
|
|
45
|
+
```
|
|
46
|
+
If `baselines_pending` is already `false` (or the file is absent), report that no approval is needed and stop.
|
|
47
|
+
|
|
48
|
+
4. Present the new baseline images to the human for review. The baselines live at:
|
|
49
|
+
```
|
|
50
|
+
<repo_root>/.cloverleaf/baselines/{browser}/{slug}-{viewport}.png
|
|
51
|
+
```
|
|
52
|
+
List the files that were modified since the last commit on the feature branch:
|
|
53
|
+
```bash
|
|
54
|
+
git diff --name-only main..cloverleaf/<TASK-ID> -- .cloverleaf/baselines/
|
|
55
|
+
```
|
|
56
|
+
Display the list. Ask the human to confirm they have reviewed the images and approve the baselines before proceeding.
|
|
57
|
+
|
|
58
|
+
5. Once approved, write `baselines_pending: false`:
|
|
59
|
+
```bash
|
|
60
|
+
cloverleaf-cli write-ui-review-state <repo_root> <TASK-ID> false
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
6. Advance the task to qa:
|
|
64
|
+
```bash
|
|
65
|
+
cloverleaf-cli advance-status <repo_root> <TASK-ID> qa agent '' full_pipeline
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
7. Commit the changes to the feature branch:
|
|
69
|
+
```bash
|
|
70
|
+
cd <repo_root>
|
|
71
|
+
git add .cloverleaf/
|
|
72
|
+
git commit -m "cloverleaf: <TASK-ID> baselines approved → qa"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
8. Report:
|
|
76
|
+
> "✓ Baselines approved. `baselines_pending` cleared. State → qa. Next: `/cloverleaf-qa <TASK-ID>`."
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Rules
|
|
81
|
+
|
|
82
|
+
- Never push.
|
|
83
|
+
- Do not modify source code or test files.
|
|
84
|
+
- Do not skip step 4 — the human must acknowledge the baseline images before approval is recorded.
|
|
85
|
+
- On illegal state transition, report and stop without partial commits.
|
|
@@ -74,14 +74,36 @@ description: Run the UI Reviewer agent on a task in the `ui-review` state (full
|
|
|
74
74
|
|
|
75
75
|
11. Parse the subagent's response. Expect `{"verdict": "pass"|"bounce"|"escalate", "summary": "...", "findings": [...]}`.
|
|
76
76
|
|
|
77
|
-
12.
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
12. **Read the baseline-approval sidecar** (after the subagent completes, regardless of verdict):
|
|
78
|
+
```bash
|
|
79
|
+
UI_STATE=$(cloverleaf-cli read-ui-review-state <repo_root> <TASK-ID>)
|
|
80
|
+
BASELINES_PENDING=$(echo "$UI_STATE" | node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).baselines_pending ? 'true' : 'false')")
|
|
80
81
|
```
|
|
81
|
-
|
|
82
|
+
Or more concisely:
|
|
83
|
+
```bash
|
|
84
|
+
BASELINES_PENDING=$(cloverleaf-cli read-ui-review-state <repo_root> <TASK-ID> | node -e "const s=require('fs').readFileSync('/dev/stdin','utf-8'); process.stdout.write(JSON.parse(s).baselines_pending?'true':'false')")
|
|
82
85
|
```
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
|
|
87
|
+
13. Branch on verdict:
|
|
88
|
+
|
|
89
|
+
**Pass:**
|
|
90
|
+
|
|
91
|
+
Check `BASELINES_PENDING`:
|
|
92
|
+
|
|
93
|
+
- If `BASELINES_PENDING` is `true`:
|
|
94
|
+
- Do NOT advance to `qa`.
|
|
95
|
+
- Commit artifacts: `git add .cloverleaf/ && git commit -m "cloverleaf: <TASK-ID> ui-review passed (baselines pending approval)"`.
|
|
96
|
+
- Report:
|
|
97
|
+
> "✓ UI Review passed (no a11y errors), but **baselines_pending** is true: one or more new or resized visual baselines were captured and require human approval before advancing to qa.
|
|
98
|
+
> Run `/cloverleaf-approve-baselines <TASK-ID>` to review the new baseline images and approve them, which will clear the flag and advance the task to qa."
|
|
99
|
+
- Stop here (task remains in `ui-review` status).
|
|
100
|
+
|
|
101
|
+
- If `BASELINES_PENDING` is `false` (or state.json is absent):
|
|
102
|
+
```
|
|
103
|
+
cloverleaf-cli advance-status <repo_root> <TASK-ID> qa agent '' full_pipeline
|
|
104
|
+
```
|
|
105
|
+
Commit: `git add .cloverleaf/ && git commit -m "cloverleaf: <TASK-ID> ui-review passed → qa"`.
|
|
106
|
+
Report: "✓ UI Review passed. State → qa. Next: `/cloverleaf-qa <TASK-ID>`."
|
|
85
107
|
|
|
86
108
|
**Bounce:**
|
|
87
109
|
1. Write feedback: `echo '<envelope-json>' > /tmp/cloverleaf-fb-u.json`
|