@cloverleaf/reference-impl 0.6.0 → 0.6.2
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/README.md +8 -2
- package/dist/cli.mjs +24 -1
- package/dist/prep-worktree.mjs +73 -8
- package/install.sh +24 -3
- package/lib/cli.ts +29 -1
- package/lib/prep-worktree.ts +82 -8
- package/package.json +1 -1
- package/prompts/documenter.md +8 -0
- package/prompts/implementer.md +8 -0
- package/prompts/qa.md +12 -1
- package/prompts/reviewer.md +12 -1
- package/prompts/ui-reviewer.md +18 -8
- package/skills/cloverleaf-run-plan/SKILL.md +34 -5
package/README.md
CHANGED
|
@@ -96,10 +96,16 @@ The installer (`install.sh`) automatically runs the Playwright browser install s
|
|
|
96
96
|
|
|
97
97
|
npx playwright install chromium webkit firefox
|
|
98
98
|
|
|
99
|
-
On **Linux**, webkit additionally requires system-level dependencies:
|
|
99
|
+
On **Linux**, webkit additionally requires system-level dependencies. By default `install.sh` installs webkit deps only:
|
|
100
100
|
|
|
101
101
|
npx playwright install-deps webkit
|
|
102
102
|
|
|
103
|
+
To also install firefox system deps (enabling the full chromium + webkit + firefox browser matrix for the UI Reviewer), pass `--with-cross-browser` to the installer:
|
|
104
|
+
|
|
105
|
+
./install.sh --with-cross-browser
|
|
106
|
+
|
|
107
|
+
This runs `npx playwright install-deps webkit firefox` instead. When the flag is omitted, `install.sh` prints a note reminding you how to enable cross-browser support later.
|
|
108
|
+
|
|
103
109
|
**Disk footprint:** approximately 600–650 MB total across all three browsers in the default `PLAYWRIGHT_BROWSERS_PATH` location (`~/.cache/ms-playwright/`).
|
|
104
110
|
|
|
105
111
|
| Browser | Approx. size |
|
|
@@ -146,7 +152,7 @@ The Reviewer never switches branches. It reads files via `git show` and runs tes
|
|
|
146
152
|
|
|
147
153
|
## Package layout
|
|
148
154
|
|
|
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`.
|
|
155
|
+
- `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`. The CLI exposes `write-baseline <repoRoot> <taskId> <browser> <slug> <viewport> <sourceFile>` as the safe write path for baselines; it enforces the `baselines_pending` guard and uses `buildBaselinePath` internally.
|
|
150
156
|
- `skills/` — Claude Code skill markdown files.
|
|
151
157
|
- `prompts/` — Implementer/Reviewer subagent system prompts.
|
|
152
158
|
- `examples/toy-repo/` — standalone demo repo.
|
package/dist/cli.mjs
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* ui-review-config --repo-root <repoRoot>
|
|
16
16
|
* read-ui-review-state <repoRoot> <taskId>
|
|
17
17
|
* write-ui-review-state <repoRoot> <taskId> <baselines_pending>
|
|
18
|
+
* write-baseline <repoRoot> <taskId> <browser> <slug> <viewport> <sourceFile>
|
|
18
19
|
* plugin-root
|
|
19
20
|
* load-rfc <repoRoot> <id>
|
|
20
21
|
* save-rfc <repoRoot> <filePath>
|
|
@@ -34,7 +35,8 @@
|
|
|
34
35
|
* walk-state-read <repoRoot> <planId>
|
|
35
36
|
* walk-state-write <repoRoot> <walkStateJsonPath>
|
|
36
37
|
*/
|
|
37
|
-
import { readFileSync } from 'node:fs';
|
|
38
|
+
import { readFileSync, mkdirSync, copyFileSync } from 'node:fs';
|
|
39
|
+
import { dirname } from 'node:path';
|
|
38
40
|
import { execSync } from 'node:child_process';
|
|
39
41
|
import { loadTask } from './task.mjs';
|
|
40
42
|
import { advanceStatus } from './task.mjs';
|
|
@@ -53,6 +55,7 @@ import { loadPlan, savePlan, advancePlanStatus, materialiseTasksFromPlan } from
|
|
|
53
55
|
import { loadDiscoveryConfig } from './discovery-config.mjs';
|
|
54
56
|
import { prepWorktree } from './prep-worktree.mjs';
|
|
55
57
|
import { readUiReviewState, writeUiReviewState } from './ui-review-state.mjs';
|
|
58
|
+
import { buildBaselinePath } from './visual-diff.mjs';
|
|
56
59
|
import { computeReadyTasks, detectCycle } from './dag-walker.mjs';
|
|
57
60
|
import { readWalkState, writeWalkState, walkStatePath } from './walk-state.mjs';
|
|
58
61
|
function die(msg, code = 1) {
|
|
@@ -74,6 +77,7 @@ function usage(msg) {
|
|
|
74
77
|
' ui-review-config --repo-root <repoRoot>\n' +
|
|
75
78
|
' read-ui-review-state <repoRoot> <taskId>\n' +
|
|
76
79
|
' write-ui-review-state <repoRoot> <taskId> <baselines_pending>\n' +
|
|
80
|
+
' write-baseline <repoRoot> <taskId> <browser> <slug> <viewport> <sourceFile>\n' +
|
|
77
81
|
' plugin-root\n' +
|
|
78
82
|
' load-rfc <repoRoot> <id>\n' +
|
|
79
83
|
' save-rfc <repoRoot> <filePath>\n' +
|
|
@@ -300,6 +304,25 @@ try {
|
|
|
300
304
|
writeUiReviewState(repoRoot, taskId, { baselines_pending });
|
|
301
305
|
break;
|
|
302
306
|
}
|
|
307
|
+
case 'write-baseline': {
|
|
308
|
+
const [repoRoot, taskId, browser, slug, viewport, sourceFile] = rest;
|
|
309
|
+
if (!repoRoot || !taskId || !browser || !slug || !viewport || !sourceFile)
|
|
310
|
+
usage('write-baseline requires <repoRoot> <taskId> <browser> <slug> <viewport> <sourceFile>');
|
|
311
|
+
// Guard: refuse writes under .cloverleaf/baselines/ when baselines_pending is true.
|
|
312
|
+
// This prevents the UI Reviewer from bypassing the human baseline-approval gate.
|
|
313
|
+
const uiState = readUiReviewState(repoRoot, taskId);
|
|
314
|
+
if (uiState.baselines_pending) {
|
|
315
|
+
die(`write-baseline refused: baselines_pending is true for task ${taskId}.\n` +
|
|
316
|
+
`A human must approve the pending baselines via the baseline-approval gate before new baselines can be written.\n` +
|
|
317
|
+
`Run: cloverleaf-cli write-ui-review-state <repoRoot> ${taskId} false` +
|
|
318
|
+
` after the human approves the baselines.`);
|
|
319
|
+
}
|
|
320
|
+
const destPath = buildBaselinePath(repoRoot, browser, slug, viewport);
|
|
321
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
322
|
+
copyFileSync(sourceFile, destPath);
|
|
323
|
+
process.stdout.write(destPath + '\n');
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
303
326
|
case 'plugin-root': {
|
|
304
327
|
process.stdout.write(getPluginRoot());
|
|
305
328
|
process.exit(0);
|
package/dist/prep-worktree.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cpSync, existsSync, rmSync } from 'node:fs';
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
|
-
import { join } from 'node:path';
|
|
3
|
+
import { join, dirname } from 'node:path';
|
|
4
4
|
/**
|
|
5
5
|
* Prepare a freshly-created git worktree of the cloverleaf monorepo for running reference-impl
|
|
6
6
|
* tests. Addresses the v0.5 dogfood finding (CLV-16, CLV-17 Delivery runs) where Reviewer/QA
|
|
@@ -19,10 +19,73 @@ import { join } from 'node:path';
|
|
|
19
19
|
* - Copy `<main>/reference-impl/node_modules` → `<wt>/reference-impl/node_modules`
|
|
20
20
|
* The `@cloverleaf/standard → ../../../standard` relative symlink is preserved verbatim so
|
|
21
21
|
* it resolves to the worktree's OWN standard/, not main's.
|
|
22
|
+
*
|
|
23
|
+
* Walker-mode resilience (CLV-37): when `mainRoot` is itself a walker worktree without
|
|
24
|
+
* node_modules, walk up ancestor directories until one is found that contains both
|
|
25
|
+
* `standard/node_modules` and `reference-impl/node_modules`. This allows the orchestrator to
|
|
26
|
+
* pass the current walker worktree path without needing to know the actual primary repo root.
|
|
22
27
|
*/
|
|
23
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Walk up the directory tree from `startDir` until a directory is found that contains both
|
|
30
|
+
* `standard/node_modules` and `reference-impl/node_modules`. Returns that directory, or null
|
|
31
|
+
* if the filesystem root is reached without finding one.
|
|
32
|
+
*/
|
|
33
|
+
function findPrimaryRoot(startDir) {
|
|
34
|
+
let candidate = startDir;
|
|
35
|
+
while (true) {
|
|
36
|
+
if (existsSync(join(candidate, 'standard', 'node_modules')) &&
|
|
37
|
+
existsSync(join(candidate, 'reference-impl', 'node_modules'))) {
|
|
38
|
+
return candidate;
|
|
39
|
+
}
|
|
40
|
+
const parent = dirname(candidate);
|
|
41
|
+
if (parent === candidate) {
|
|
42
|
+
// Reached filesystem root without finding a match.
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
candidate = parent;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Walk up from `startDir` to find the nearest ancestor where the given `subdir` exists.
|
|
50
|
+
* Returns the ancestor path, or null if the filesystem root is reached without a match.
|
|
51
|
+
*/
|
|
52
|
+
function findNearestAncestorWithSubdir(startDir, subdir) {
|
|
53
|
+
let candidate = startDir;
|
|
54
|
+
while (true) {
|
|
55
|
+
if (existsSync(join(candidate, subdir))) {
|
|
56
|
+
return candidate;
|
|
57
|
+
}
|
|
58
|
+
const parent = dirname(candidate);
|
|
59
|
+
if (parent === candidate) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
candidate = parent;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Build a diagnostic error message when `findPrimaryRoot` fails to find an ancestor with
|
|
67
|
+
* both `standard/node_modules` and `reference-impl/node_modules`. Walks up separately for
|
|
68
|
+
* each subdirectory to produce a precise message naming the specific missing directory.
|
|
69
|
+
*/
|
|
70
|
+
function buildMissingNodeModulesError(mainRoot) {
|
|
71
|
+
const hasStandard = findNearestAncestorWithSubdir(mainRoot, join('standard', 'node_modules'));
|
|
72
|
+
const hasRefImpl = findNearestAncestorWithSubdir(mainRoot, join('reference-impl', 'node_modules'));
|
|
73
|
+
if (hasStandard !== null && hasRefImpl === null) {
|
|
74
|
+
// standard/node_modules exists somewhere in the tree but reference-impl/node_modules does not.
|
|
75
|
+
const missing = join(hasStandard, 'reference-impl', 'node_modules');
|
|
76
|
+
return new Error(`main missing reference-impl/node_modules at ${missing} — run \`npm ci\` in main's reference-impl/ first`);
|
|
77
|
+
}
|
|
78
|
+
if (hasRefImpl !== null && hasStandard === null) {
|
|
79
|
+
// reference-impl/node_modules exists somewhere in the tree but standard/node_modules does not.
|
|
80
|
+
const missing = join(hasRefImpl, 'standard', 'node_modules');
|
|
81
|
+
return new Error(`main missing standard/node_modules at ${missing} — run \`npm ci\` in main's standard/ first`);
|
|
82
|
+
}
|
|
83
|
+
// Neither found (or both missing): fall back to reporting standard/node_modules against the
|
|
84
|
+
// original mainRoot argument (preserves prior behaviour for the truly-empty case).
|
|
24
85
|
const mainStandardNm = join(mainRoot, 'standard', 'node_modules');
|
|
25
|
-
|
|
86
|
+
return new Error(`main missing standard/node_modules at ${mainStandardNm} — run \`npm ci\` in main's standard/ first`);
|
|
87
|
+
}
|
|
88
|
+
export function prepWorktree(mainRoot, worktreePath) {
|
|
26
89
|
const wtStandardPkg = join(worktreePath, 'standard', 'package.json');
|
|
27
90
|
const wtRefImplPkg = join(worktreePath, 'reference-impl', 'package.json');
|
|
28
91
|
if (!existsSync(wtStandardPkg)) {
|
|
@@ -31,12 +94,14 @@ export function prepWorktree(mainRoot, worktreePath) {
|
|
|
31
94
|
if (!existsSync(wtRefImplPkg)) {
|
|
32
95
|
throw new Error(`worktree missing reference-impl/package.json at ${wtRefImplPkg}`);
|
|
33
96
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
throw
|
|
97
|
+
// Resolve the actual primary repo root: start from mainRoot and walk up until we find a
|
|
98
|
+
// directory containing both standard/node_modules and reference-impl/node_modules.
|
|
99
|
+
const resolvedMain = findPrimaryRoot(mainRoot);
|
|
100
|
+
if (resolvedMain === null) {
|
|
101
|
+
throw buildMissingNodeModulesError(mainRoot);
|
|
39
102
|
}
|
|
103
|
+
const mainStandardNm = join(resolvedMain, 'standard', 'node_modules');
|
|
104
|
+
const mainRefImplNm = join(resolvedMain, 'reference-impl', 'node_modules');
|
|
40
105
|
const wtStandardNm = join(worktreePath, 'standard', 'node_modules');
|
|
41
106
|
const wtRefImplNm = join(worktreePath, 'reference-impl', 'node_modules');
|
|
42
107
|
// verbatimSymlinks keeps relative symlink targets byte-identical, so the @cloverleaf/standard
|
package/install.sh
CHANGED
|
@@ -7,10 +7,23 @@ set -euo pipefail
|
|
|
7
7
|
# `claude plugin` CLI. Point Claude Code at the cloverleaf repo root (where
|
|
8
8
|
# .claude-plugin/marketplace.json lives), then install the plugin from the
|
|
9
9
|
# resulting marketplace.
|
|
10
|
+
#
|
|
11
|
+
# Flags:
|
|
12
|
+
# --with-cross-browser On Linux, also install firefox system deps (in
|
|
13
|
+
# addition to webkit) so the UI Reviewer can run the
|
|
14
|
+
# full chromium + webkit + firefox browser matrix.
|
|
10
15
|
|
|
11
16
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
17
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
13
18
|
|
|
19
|
+
# Parse flags from "$@".
|
|
20
|
+
WITH_CROSS_BROWSER=0
|
|
21
|
+
for arg in "$@"; do
|
|
22
|
+
case "$arg" in
|
|
23
|
+
--with-cross-browser) WITH_CROSS_BROWSER=1 ;;
|
|
24
|
+
esac
|
|
25
|
+
done
|
|
26
|
+
|
|
14
27
|
if ! command -v claude >/dev/null 2>&1; then
|
|
15
28
|
echo "error: 'claude' CLI not found on PATH."
|
|
16
29
|
echo "Install Claude Code first: https://docs.claude.com/claude-code"
|
|
@@ -50,11 +63,19 @@ echo ""
|
|
|
50
63
|
|
|
51
64
|
npx playwright install chromium webkit firefox
|
|
52
65
|
|
|
53
|
-
# On Linux, system deps
|
|
66
|
+
# On Linux, system deps are also required.
|
|
54
67
|
if [ "$(uname -s)" = "Linux" ]; then
|
|
55
68
|
echo ""
|
|
56
|
-
|
|
57
|
-
|
|
69
|
+
if [ "$WITH_CROSS_BROWSER" = "1" ]; then
|
|
70
|
+
echo "Linux detected — installing webkit + firefox system dependencies (--with-cross-browser)..."
|
|
71
|
+
npx playwright install-deps webkit firefox
|
|
72
|
+
else
|
|
73
|
+
echo "Linux detected — installing webkit system dependencies..."
|
|
74
|
+
npx playwright install-deps webkit
|
|
75
|
+
echo ""
|
|
76
|
+
echo "Note: to also install firefox system dependencies for full cross-browser"
|
|
77
|
+
echo " UI review support, re-run with: ./install.sh --with-cross-browser"
|
|
78
|
+
fi
|
|
58
79
|
fi
|
|
59
80
|
|
|
60
81
|
echo ""
|
package/lib/cli.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* ui-review-config --repo-root <repoRoot>
|
|
16
16
|
* read-ui-review-state <repoRoot> <taskId>
|
|
17
17
|
* write-ui-review-state <repoRoot> <taskId> <baselines_pending>
|
|
18
|
+
* write-baseline <repoRoot> <taskId> <browser> <slug> <viewport> <sourceFile>
|
|
18
19
|
* plugin-root
|
|
19
20
|
* load-rfc <repoRoot> <id>
|
|
20
21
|
* save-rfc <repoRoot> <filePath>
|
|
@@ -35,7 +36,8 @@
|
|
|
35
36
|
* walk-state-write <repoRoot> <walkStateJsonPath>
|
|
36
37
|
*/
|
|
37
38
|
|
|
38
|
-
import { readFileSync } from 'node:fs';
|
|
39
|
+
import { readFileSync, mkdirSync, copyFileSync } from 'node:fs';
|
|
40
|
+
import { dirname } from 'node:path';
|
|
39
41
|
import { execSync } from 'node:child_process';
|
|
40
42
|
import { loadTask } from './task.js';
|
|
41
43
|
import { advanceStatus } from './task.js';
|
|
@@ -55,6 +57,7 @@ import { loadPlan, savePlan, advancePlanStatus, materialiseTasksFromPlan, type P
|
|
|
55
57
|
import { loadDiscoveryConfig } from './discovery-config.js';
|
|
56
58
|
import { prepWorktree } from './prep-worktree.js';
|
|
57
59
|
import { readUiReviewState, writeUiReviewState } from './ui-review-state.js';
|
|
60
|
+
import { buildBaselinePath } from './visual-diff.js';
|
|
58
61
|
import { computeReadyTasks, detectCycle } from './dag-walker.js';
|
|
59
62
|
import { readWalkState, writeWalkState, walkStatePath } from './walk-state.js';
|
|
60
63
|
|
|
@@ -78,6 +81,7 @@ function usage(msg?: string): never {
|
|
|
78
81
|
' ui-review-config --repo-root <repoRoot>\n' +
|
|
79
82
|
' read-ui-review-state <repoRoot> <taskId>\n' +
|
|
80
83
|
' write-ui-review-state <repoRoot> <taskId> <baselines_pending>\n' +
|
|
84
|
+
' write-baseline <repoRoot> <taskId> <browser> <slug> <viewport> <sourceFile>\n' +
|
|
81
85
|
' plugin-root\n' +
|
|
82
86
|
' load-rfc <repoRoot> <id>\n' +
|
|
83
87
|
' save-rfc <repoRoot> <filePath>\n' +
|
|
@@ -310,6 +314,30 @@ try {
|
|
|
310
314
|
break;
|
|
311
315
|
}
|
|
312
316
|
|
|
317
|
+
case 'write-baseline': {
|
|
318
|
+
const [repoRoot, taskId, browser, slug, viewport, sourceFile] = rest;
|
|
319
|
+
if (!repoRoot || !taskId || !browser || !slug || !viewport || !sourceFile)
|
|
320
|
+
usage(
|
|
321
|
+
'write-baseline requires <repoRoot> <taskId> <browser> <slug> <viewport> <sourceFile>'
|
|
322
|
+
);
|
|
323
|
+
// Guard: refuse writes under .cloverleaf/baselines/ when baselines_pending is true.
|
|
324
|
+
// This prevents the UI Reviewer from bypassing the human baseline-approval gate.
|
|
325
|
+
const uiState = readUiReviewState(repoRoot, taskId);
|
|
326
|
+
if (uiState.baselines_pending) {
|
|
327
|
+
die(
|
|
328
|
+
`write-baseline refused: baselines_pending is true for task ${taskId}.\n` +
|
|
329
|
+
`A human must approve the pending baselines via the baseline-approval gate before new baselines can be written.\n` +
|
|
330
|
+
`Run: cloverleaf-cli write-ui-review-state <repoRoot> ${taskId} false` +
|
|
331
|
+
` after the human approves the baselines.`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
const destPath = buildBaselinePath(repoRoot, browser, slug, viewport);
|
|
335
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
336
|
+
copyFileSync(sourceFile, destPath);
|
|
337
|
+
process.stdout.write(destPath + '\n');
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
|
|
313
341
|
case 'plugin-root': {
|
|
314
342
|
process.stdout.write(getPluginRoot());
|
|
315
343
|
process.exit(0);
|
package/lib/prep-worktree.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cpSync, existsSync, rmSync } from 'node:fs';
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
|
-
import { join } from 'node:path';
|
|
3
|
+
import { join, dirname } from 'node:path';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Prepare a freshly-created git worktree of the cloverleaf monorepo for running reference-impl
|
|
@@ -20,10 +20,80 @@ import { join } from 'node:path';
|
|
|
20
20
|
* - Copy `<main>/reference-impl/node_modules` → `<wt>/reference-impl/node_modules`
|
|
21
21
|
* The `@cloverleaf/standard → ../../../standard` relative symlink is preserved verbatim so
|
|
22
22
|
* it resolves to the worktree's OWN standard/, not main's.
|
|
23
|
+
*
|
|
24
|
+
* Walker-mode resilience (CLV-37): when `mainRoot` is itself a walker worktree without
|
|
25
|
+
* node_modules, walk up ancestor directories until one is found that contains both
|
|
26
|
+
* `standard/node_modules` and `reference-impl/node_modules`. This allows the orchestrator to
|
|
27
|
+
* pass the current walker worktree path without needing to know the actual primary repo root.
|
|
23
28
|
*/
|
|
24
|
-
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Walk up the directory tree from `startDir` until a directory is found that contains both
|
|
32
|
+
* `standard/node_modules` and `reference-impl/node_modules`. Returns that directory, or null
|
|
33
|
+
* if the filesystem root is reached without finding one.
|
|
34
|
+
*/
|
|
35
|
+
function findPrimaryRoot(startDir: string): string | null {
|
|
36
|
+
let candidate = startDir;
|
|
37
|
+
while (true) {
|
|
38
|
+
if (
|
|
39
|
+
existsSync(join(candidate, 'standard', 'node_modules')) &&
|
|
40
|
+
existsSync(join(candidate, 'reference-impl', 'node_modules'))
|
|
41
|
+
) {
|
|
42
|
+
return candidate;
|
|
43
|
+
}
|
|
44
|
+
const parent = dirname(candidate);
|
|
45
|
+
if (parent === candidate) {
|
|
46
|
+
// Reached filesystem root without finding a match.
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
candidate = parent;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Walk up from `startDir` to find the nearest ancestor where the given `subdir` exists.
|
|
55
|
+
* Returns the ancestor path, or null if the filesystem root is reached without a match.
|
|
56
|
+
*/
|
|
57
|
+
function findNearestAncestorWithSubdir(startDir: string, subdir: string): string | null {
|
|
58
|
+
let candidate = startDir;
|
|
59
|
+
while (true) {
|
|
60
|
+
if (existsSync(join(candidate, subdir))) {
|
|
61
|
+
return candidate;
|
|
62
|
+
}
|
|
63
|
+
const parent = dirname(candidate);
|
|
64
|
+
if (parent === candidate) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
candidate = parent;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Build a diagnostic error message when `findPrimaryRoot` fails to find an ancestor with
|
|
73
|
+
* both `standard/node_modules` and `reference-impl/node_modules`. Walks up separately for
|
|
74
|
+
* each subdirectory to produce a precise message naming the specific missing directory.
|
|
75
|
+
*/
|
|
76
|
+
function buildMissingNodeModulesError(mainRoot: string): Error {
|
|
77
|
+
const hasStandard = findNearestAncestorWithSubdir(mainRoot, join('standard', 'node_modules'));
|
|
78
|
+
const hasRefImpl = findNearestAncestorWithSubdir(mainRoot, join('reference-impl', 'node_modules'));
|
|
79
|
+
|
|
80
|
+
if (hasStandard !== null && hasRefImpl === null) {
|
|
81
|
+
// standard/node_modules exists somewhere in the tree but reference-impl/node_modules does not.
|
|
82
|
+
const missing = join(hasStandard, 'reference-impl', 'node_modules');
|
|
83
|
+
return new Error(`main missing reference-impl/node_modules at ${missing} — run \`npm ci\` in main's reference-impl/ first`);
|
|
84
|
+
}
|
|
85
|
+
if (hasRefImpl !== null && hasStandard === null) {
|
|
86
|
+
// reference-impl/node_modules exists somewhere in the tree but standard/node_modules does not.
|
|
87
|
+
const missing = join(hasRefImpl, 'standard', 'node_modules');
|
|
88
|
+
return new Error(`main missing standard/node_modules at ${missing} — run \`npm ci\` in main's standard/ first`);
|
|
89
|
+
}
|
|
90
|
+
// Neither found (or both missing): fall back to reporting standard/node_modules against the
|
|
91
|
+
// original mainRoot argument (preserves prior behaviour for the truly-empty case).
|
|
25
92
|
const mainStandardNm = join(mainRoot, 'standard', 'node_modules');
|
|
26
|
-
|
|
93
|
+
return new Error(`main missing standard/node_modules at ${mainStandardNm} — run \`npm ci\` in main's standard/ first`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function prepWorktree(mainRoot: string, worktreePath: string): void {
|
|
27
97
|
const wtStandardPkg = join(worktreePath, 'standard', 'package.json');
|
|
28
98
|
const wtRefImplPkg = join(worktreePath, 'reference-impl', 'package.json');
|
|
29
99
|
|
|
@@ -33,13 +103,17 @@ export function prepWorktree(mainRoot: string, worktreePath: string): void {
|
|
|
33
103
|
if (!existsSync(wtRefImplPkg)) {
|
|
34
104
|
throw new Error(`worktree missing reference-impl/package.json at ${wtRefImplPkg}`);
|
|
35
105
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
106
|
+
|
|
107
|
+
// Resolve the actual primary repo root: start from mainRoot and walk up until we find a
|
|
108
|
+
// directory containing both standard/node_modules and reference-impl/node_modules.
|
|
109
|
+
const resolvedMain = findPrimaryRoot(mainRoot);
|
|
110
|
+
if (resolvedMain === null) {
|
|
111
|
+
throw buildMissingNodeModulesError(mainRoot);
|
|
41
112
|
}
|
|
42
113
|
|
|
114
|
+
const mainStandardNm = join(resolvedMain, 'standard', 'node_modules');
|
|
115
|
+
const mainRefImplNm = join(resolvedMain, 'reference-impl', 'node_modules');
|
|
116
|
+
|
|
43
117
|
const wtStandardNm = join(worktreePath, 'standard', 'node_modules');
|
|
44
118
|
const wtRefImplNm = join(worktreePath, 'reference-impl', 'node_modules');
|
|
45
119
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloverleaf/reference-impl",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
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/documenter.md
CHANGED
|
@@ -10,6 +10,14 @@ You are the Cloverleaf Documenter. Your job: produce doc-only commits that updat
|
|
|
10
10
|
- **Repo root**: {{repo_root}}
|
|
11
11
|
- **Diff from base**: {{diff}}
|
|
12
12
|
|
|
13
|
+
## Pre-flight — ensure correct working directory
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cd "$(git rev-parse --show-toplevel)"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
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.
|
|
20
|
+
|
|
13
21
|
## Tool constraints
|
|
14
22
|
|
|
15
23
|
- Use `git worktree add <temp> {{branch}}` to work on an isolated checkout. Do NOT `git checkout` in the main working directory.
|
package/prompts/implementer.md
CHANGED
|
@@ -11,6 +11,14 @@ You are the Cloverleaf Implementer agent. Your job: take a Task and produce work
|
|
|
11
11
|
|
|
12
12
|
## Your process
|
|
13
13
|
|
|
14
|
+
0. **Pre-flight — ensure correct working directory.**
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd "$(git rev-parse --show-toplevel)"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
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.
|
|
21
|
+
|
|
14
22
|
1. Read the task's `title`, `acceptance_criteria`, `definition_of_done`, and `context`. Read any referenced files.
|
|
15
23
|
2. If `feedback` is present, re-read each finding; plan how to address them.
|
|
16
24
|
3. Create a new branch named `cloverleaf/<task.id>` from `base_branch` using `git checkout -b cloverleaf/<task.id>`.
|
package/prompts/qa.md
CHANGED
|
@@ -17,6 +17,14 @@ The Standard's QA contract requires a `preview_uri`. You were passed the sentine
|
|
|
17
17
|
|
|
18
18
|
## Runtime procedure
|
|
19
19
|
|
|
20
|
+
0. **Pre-flight — ensure correct working directory.**
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
cd "$(git rev-parse --show-toplevel)"
|
|
24
|
+
```
|
|
25
|
+
|
|
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
|
+
|
|
20
28
|
1. Set up isolated worktree and prepare its node_modules + standard/dist. The `prep-worktree`
|
|
21
29
|
helper copies main's `standard/node_modules` and `reference-impl/node_modules` into the
|
|
22
30
|
worktree and runs the standard build script so the @cloverleaf/standard symlink resolves
|
|
@@ -24,10 +32,13 @@ The Standard's QA contract requires a `preview_uri`. You were passed the sentine
|
|
|
24
32
|
'@cloverleaf/standard/validators/index.js'` because git worktrees don't inherit node_modules.)
|
|
25
33
|
```bash
|
|
26
34
|
TMPDIR=$(mktemp -d)
|
|
27
|
-
git
|
|
35
|
+
SHA=$(git rev-parse {{branch}})
|
|
36
|
+
git worktree add --detach "$TMPDIR" "$SHA"
|
|
28
37
|
cloverleaf-cli prep-worktree {{repo_root}} "$TMPDIR"
|
|
29
38
|
```
|
|
30
39
|
|
|
40
|
+
Use `--detach` with a SHA rather than a branch name: the calling context (e.g., the walker) may already have `{{branch}}` 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.
|
|
41
|
+
|
|
31
42
|
2. Inspect the changed files (from the diff). For each QA rule whose `match` patterns match ≥1 changed file, queue its command.
|
|
32
43
|
|
|
33
44
|
3. If no rules match (e.g., the diff only changes `.cloverleaf/**` or tests unrelated to any package), skip with a `pass` verdict — nothing testable in this diff:
|
package/prompts/reviewer.md
CHANGED
|
@@ -11,6 +11,14 @@ You are the Cloverleaf Reviewer agent. Your job: perform a fresh-eyes review of
|
|
|
11
11
|
|
|
12
12
|
## Your process
|
|
13
13
|
|
|
14
|
+
0. **Pre-flight — ensure correct working directory.**
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd "$(git rev-parse --show-toplevel)"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
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.
|
|
21
|
+
|
|
14
22
|
1. Read the task's `acceptance_criteria` and `definition_of_done`.
|
|
15
23
|
2. Run `git diff <base_branch>..<branch> --stat` and `git diff <base_branch>..<branch>` to see the change.
|
|
16
24
|
3. For each acceptance criterion, determine whether the diff satisfies it. Note any unsatisfied criteria as findings.
|
|
@@ -45,7 +53,8 @@ A `pass` verdict MAY have an empty `findings` array or omit it. A `bounce` verdi
|
|
|
45
53
|
|
|
46
54
|
```bash
|
|
47
55
|
MAIN=$(pwd)
|
|
48
|
-
git
|
|
56
|
+
SHA=$(git rev-parse cloverleaf/<task-id>)
|
|
57
|
+
git worktree add --detach /tmp/cl-review-<task-id> "$SHA"
|
|
49
58
|
cloverleaf-cli prep-worktree "$MAIN" /tmp/cl-review-<task-id>
|
|
50
59
|
cd /tmp/cl-review-<task-id>/reference-impl
|
|
51
60
|
npm test
|
|
@@ -53,6 +62,8 @@ A `pass` verdict MAY have an empty `findings` array or omit it. A `bounce` verdi
|
|
|
53
62
|
git worktree remove /tmp/cl-review-<task-id>
|
|
54
63
|
```
|
|
55
64
|
|
|
65
|
+
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
|
+
|
|
56
67
|
This keeps `.cloverleaf/` on main intact.
|
|
57
68
|
- 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.
|
|
58
69
|
- If a criterion is subjective, lean toward pass — the task author chose those words deliberately.
|
package/prompts/ui-reviewer.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
You are the Cloverleaf UI Reviewer. Your job: review a task's UI changes at multiple viewports and browser engines for accessibility violations (axe-core) and visual regressions (pixelmatch) using headless Playwright browsers. You are read-only for source code and tests — but you DO write baseline/diff artifacts under `.cloverleaf/` on the feature branch.
|
|
4
4
|
|
|
5
|
+
## Pre-flight — ensure correct working directory
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd "$(git rev-parse --show-toplevel)"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
5
13
|
## Input
|
|
6
14
|
|
|
7
15
|
- **Task**: {{task}}
|
|
@@ -17,10 +25,10 @@ You are the Cloverleaf UI Reviewer. Your job: review a task's UI changes at mult
|
|
|
17
25
|
|
|
18
26
|
You operate in two filesystem locations — keep them straight:
|
|
19
27
|
|
|
20
|
-
- `<worktree>` — the ephemeral worktree at `$
|
|
28
|
+
- `<worktree>` — the ephemeral worktree at `$WT` (set up in step 2 of the Runtime procedure). You run the dev server here and execute Playwright here. Any standalone `.mjs` driver scripts must be placed INSIDE `$WT/site/` so that Node can resolve `playwright` from `$WT/site/node_modules/`; do NOT write them outside the worktree where no `node_modules` is present.
|
|
21
29
|
- `<repoRoot>` — the main repository root at `{{repo_root}}` (always an absolute path). This is the ONLY location where baselines, diff PNGs, candidate PNGs, and artifacts are written.
|
|
22
30
|
|
|
23
|
-
**All `compareVisual` paths MUST be rooted at `{{repo_root}}`, NOT at `$
|
|
31
|
+
**All `compareVisual` paths MUST be rooted at `{{repo_root}}`, NOT at `$WT`.**
|
|
24
32
|
|
|
25
33
|
The rationale: baselines on `{{repo_root}}/.cloverleaf/baselines/` get picked up by subsequent `git add` + `git commit` steps in the UI Reviewer, which run on the feature branch. The merge skill (v0.4.1+) then merges those commits to main via `git merge --no-ff`. Writing to the worktree's `.cloverleaf/` would strand the files and `git worktree remove --force` would discard them on teardown.
|
|
26
34
|
|
|
@@ -71,19 +79,21 @@ Do not attempt to launch a missing engine — fail fast with `verdict: "escalate
|
|
|
71
79
|
|
|
72
80
|
2. Set up an isolated worktree of the feature branch:
|
|
73
81
|
```bash
|
|
74
|
-
|
|
75
|
-
git worktree add "$
|
|
76
|
-
npx cloverleaf-cli prep-worktree {{repo_root}} "$
|
|
82
|
+
WT=$(mktemp -d)
|
|
83
|
+
git worktree add "$WT" {{branch}}
|
|
84
|
+
npx cloverleaf-cli prep-worktree {{repo_root}} "$WT"
|
|
77
85
|
```
|
|
78
86
|
|
|
79
87
|
3. For this repo, UI lives in `site/` (or another directory if ui-paths.json scopes it elsewhere). Install dependencies and start the dev server:
|
|
80
88
|
```bash
|
|
81
|
-
cd "$
|
|
89
|
+
cd "$WT/site"
|
|
82
90
|
npm ci
|
|
83
91
|
npm run dev -- --port={{preview_port}} &
|
|
84
92
|
SERVER_PID=$!
|
|
85
93
|
```
|
|
86
94
|
|
|
95
|
+
> **Playwright script placement (Bug #3 fix):** If you need to write a standalone `.mjs` driver script at any point, place it **inside the worktree** (e.g., `$WT/site/playwright-driver.mjs`) and run it from there (`node "$WT/site/playwright-driver.mjs"`). Node's ESM module resolution walks up from the script's own directory — a script placed outside the worktree (where `node_modules/playwright` was installed by `npm ci`) cannot resolve the `playwright` import and will fail.
|
|
96
|
+
|
|
87
97
|
4. Wait up to 30s for `http://localhost:{{preview_port}}/` to respond 200. If the server fails to start in 30s, kill it and return verdict `escalate`.
|
|
88
98
|
|
|
89
99
|
5. Determine the site base path:
|
|
@@ -113,7 +123,7 @@ Do not attempt to launch a missing engine — fail fast with `verdict: "escalate
|
|
|
113
123
|
- Navigate to `http://localhost:{{preview_port}}<base><route>`. If 404, retry without the base.
|
|
114
124
|
- `page.screenshot({ fullPage: false })` → candidate PNG buffer.
|
|
115
125
|
- Compute slug for the route (lowercase, strip leading/trailing slashes, replace slashes with hyphens; `/` → `index`).
|
|
116
|
-
- Note: use `{{repo_root}}` (the absolute main-repo path), NOT `$
|
|
126
|
+
- Note: use `{{repo_root}}` (the absolute main-repo path), NOT `$WT` or the worktree. See the "Paths" section.
|
|
117
127
|
- Call `compareVisual` (from `lib/visual-diff.ts`) with:
|
|
118
128
|
- `baselinePath = {{repo_root}}/.cloverleaf/baselines/{browser}/{slug}-{viewport}.png`
|
|
119
129
|
- `candidateBuf = <candidate PNG>`
|
|
@@ -174,7 +184,7 @@ Do not attempt to launch a missing engine — fail fast with `verdict: "escalate
|
|
|
174
184
|
```bash
|
|
175
185
|
kill $SERVER_PID 2>/dev/null || true
|
|
176
186
|
cd {{repo_root}}
|
|
177
|
-
git worktree remove --force "$
|
|
187
|
+
git worktree remove --force "$WT"
|
|
178
188
|
```
|
|
179
189
|
|
|
180
190
|
## Tool constraints
|
|
@@ -77,7 +77,8 @@ description: Autonomous DAG walker for Cloverleaf Plans. Given a PLAN-ID in stat
|
|
|
77
77
|
Per ready task:
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
|
-
WT="/
|
|
80
|
+
WT="${XDG_CACHE_HOME:-$HOME/.cache}/cloverleaf/walker/<PLAN-ID>-<TASK-ID>"
|
|
81
|
+
mkdir -p "$(dirname "$WT")"
|
|
81
82
|
rm -rf "$WT" # idempotent: clean any leftover from a prior run
|
|
82
83
|
git -C <repo_root> worktree add "$WT" -b cloverleaf/<TASK-ID> main
|
|
83
84
|
```
|
|
@@ -118,12 +119,24 @@ description: Autonomous DAG walker for Cloverleaf Plans. Given a PLAN-ID in stat
|
|
|
118
119
|
```
|
|
119
120
|
2. Read the user's response.
|
|
120
121
|
3. If it matches `^y(es)?$|^Y(ES)?$` → perform the merge in the primary repo:
|
|
122
|
+
|
|
123
|
+
First, **guard against conflict markers** — scan every file changed on the task branch for unresolved conflict markers before attempting the merge:
|
|
121
124
|
```bash
|
|
122
125
|
cd <repo_root>
|
|
123
126
|
git checkout main
|
|
124
|
-
git
|
|
127
|
+
CHANGED_FILES=$(git diff --name-only main..cloverleaf/<TASK-ID>)
|
|
128
|
+
if [ -n "$CHANGED_FILES" ] && echo "$CHANGED_FILES" | xargs grep -l -E '^(<{7}|={7}|>{7})' 2>/dev/null | grep -q .; then
|
|
129
|
+
echo "ERROR: conflict markers found in changed files — aborting merge for <TASK-ID>"
|
|
130
|
+
echo "$CHANGED_FILES" | xargs grep -l -E '^(<{7}|={7}|>{7})' 2>/dev/null
|
|
131
|
+
# Do NOT proceed; mark task escalated and surface to user
|
|
132
|
+
else
|
|
133
|
+
git merge --no-ff cloverleaf/<TASK-ID> -m "cloverleaf: <TASK-ID> merged (<fast_lane | full_pipeline>)"
|
|
134
|
+
fi
|
|
125
135
|
```
|
|
126
|
-
|
|
136
|
+
|
|
137
|
+
If conflict markers are found, abort the merge: mark task `state: "escalated"` in walk-state, surface to the user with the list of affected files, and do NOT advance state. Continue with the next queued task.
|
|
138
|
+
|
|
139
|
+
After a **successful** `git merge --no-ff`, advance state and commit:
|
|
127
140
|
```bash
|
|
128
141
|
# Fast lane:
|
|
129
142
|
cloverleaf-cli emit-gate-decision <repo_root> <TASK-ID> human_merge approve human
|
|
@@ -135,7 +148,19 @@ description: Autonomous DAG walker for Cloverleaf Plans. Given a PLAN-ID in stat
|
|
|
135
148
|
```bash
|
|
136
149
|
git add .cloverleaf/ && git commit -m "cloverleaf: <TASK-ID> merged"
|
|
137
150
|
```
|
|
138
|
-
Capture the merge commit SHA
|
|
151
|
+
Capture the merge commit SHA:
|
|
152
|
+
```bash
|
|
153
|
+
MERGE_COMMIT=$(git rev-parse HEAD)
|
|
154
|
+
```
|
|
155
|
+
Immediately update walk-state to record the successful merge (bug #7 fix — walk-state must reflect `merged` state):
|
|
156
|
+
```bash
|
|
157
|
+
# Write a temporary walk-state JSON with state: "merged" and merge_commit, then persist atomically
|
|
158
|
+
# (build the updated walk-state object in-memory and call walk-state-write)
|
|
159
|
+
cloverleaf-cli walk-state-write <repo_root> <updated-walk-state-json-path>
|
|
160
|
+
# The updated walk-state sets tasks["<TASK-ID>"].state = "merged"
|
|
161
|
+
# and tasks["<TASK-ID>"].merge_commit = "$MERGE_COMMIT"
|
|
162
|
+
```
|
|
163
|
+
Send `y` (informational) back to Session B so it can record the outcome and exit, but the walker is the authoritative merge-performer.
|
|
139
164
|
**Tear down the worktree**: `git -C <repo_root> worktree remove --force <worktree_path>`. Delete the branch is optional (keep if useful for post-hoc inspection).
|
|
140
165
|
4. If it matches `^n(o)?$|^N(O)?$` → mark task `state: "awaiting_final_gate"`. Send `n` to Session B. **Keep the worktree** so the user can re-run `/cloverleaf-merge <TASK-ID>` manually pointing at it, or fix and retry. Continue with the next queued task.
|
|
141
166
|
5. Otherwise → forward the user's text as a user turn to Session B via `mcp__claw-drive__send_turn` (it's a question). Wait for the session's next `turn_completed`. Print the answer. **Re-surface the same y/N prompt** (with the Q&A appended to shown context). Loop until step 3 or 4 fires.
|
|
@@ -159,9 +184,13 @@ The walker constructs a per-task `scenario_brief` roughly like:
|
|
|
159
184
|
|
|
160
185
|
```
|
|
161
186
|
You are driving <TASK-ID> Delivery via /cloverleaf-run inside a dedicated
|
|
162
|
-
git worktree at
|
|
187
|
+
git worktree rooted at $WORKTREE_ROOT. The worktree is checked out to branch
|
|
163
188
|
cloverleaf/<TASK-ID> (already created from main). Task risk_class: <class>.
|
|
164
189
|
|
|
190
|
+
**Pre-flight: before any task steps, run `cd "$WORKTREE_ROOT"` to ensure your
|
|
191
|
+
working directory is the worktree root, not whatever directory the session
|
|
192
|
+
inherited.**
|
|
193
|
+
|
|
165
194
|
Plan: invoke `/cloverleaf-run <TASK-ID>`.
|
|
166
195
|
|
|
167
196
|
**DO NOT invoke `/cloverleaf-merge`**. Fast lane stops after `/cloverleaf-review`
|