@cloverleaf/reference-impl 0.13.1 → 0.13.3
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/dist/cli.mjs +58 -45
- package/lib/cli.ts +62 -47
- package/package.json +1 -1
- package/prompts/ui-reviewer.md +31 -7
|
@@ -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, release).",
|
|
4
|
-
"version": "0.13.
|
|
4
|
+
"version": "0.13.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Renato D'Arrigo",
|
|
7
7
|
"email": "renato.darrigo@gmail.com"
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.13.
|
|
1
|
+
0.13.3
|
package/dist/cli.mjs
CHANGED
|
@@ -87,57 +87,70 @@ function die(msg, code = 1) {
|
|
|
87
87
|
process.stderr.write(msg + '\n');
|
|
88
88
|
process.exit(code);
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* The command list, shared by both exits below. `--help` is a request and its
|
|
92
|
+
* answer is this text, so it goes to stdout and exits 0; every error path keeps
|
|
93
|
+
* stderr and exit 2, so a caller piping stdout gets the list or nothing.
|
|
94
|
+
*/
|
|
95
|
+
const USAGE_TEXT = 'Usage: cloverleaf-cli <command> [args...]\n' +
|
|
96
|
+
'Commands:\n' +
|
|
97
|
+
' load-task <repoRoot> <taskId>\n' +
|
|
98
|
+
' infer-project <repoRoot>\n' +
|
|
99
|
+
' next-task-id <repoRoot> [--project=<p>]\n' +
|
|
100
|
+
' advance-status <repoRoot> <taskId> <toStatus> <actor> [gate]\n' +
|
|
101
|
+
' write-feedback <repoRoot> <taskId> <envelopeJsonPath>\n' +
|
|
102
|
+
' latest-feedback <repoRoot> <taskId>\n' +
|
|
103
|
+
' emit-gate-decision <repoRoot> <workItemId> <gate> <decision> <actor> [--comment=<str>]\n' +
|
|
104
|
+
' ui-review-config --repo-root <repoRoot>\n' +
|
|
105
|
+
' read-ui-review-state <repoRoot> <taskId>\n' +
|
|
106
|
+
' write-ui-review-state <repoRoot> <taskId> <baselines_pending>\n' +
|
|
107
|
+
' write-baseline <repoRoot> <taskId> <browser> <slug> <viewport> <sourceFile>\n' +
|
|
108
|
+
' plugin-root\n' +
|
|
109
|
+
' load-rfc <repoRoot> <id>\n' +
|
|
110
|
+
' save-rfc <repoRoot> <filePath>\n' +
|
|
111
|
+
' advance-rfc <repoRoot> <id> <toStatus> <agent|human> [gate]\n' +
|
|
112
|
+
' rfc-tasks <repoRoot> <rfcId> [--pretty]\n' +
|
|
113
|
+
' load-spike <repoRoot> <id>\n' +
|
|
114
|
+
' save-spike <repoRoot> <filePath>\n' +
|
|
115
|
+
' advance-spike <repoRoot> <id> <toStatus> <agent|human>\n' +
|
|
116
|
+
' load-plan <repoRoot> <id>\n' +
|
|
117
|
+
' save-plan <repoRoot> <filePath>\n' +
|
|
118
|
+
' advance-plan <repoRoot> <id> <toStatus> <agent|human> [gate]\n' +
|
|
119
|
+
' materialise-tasks <repoRoot> <planId>\n' +
|
|
120
|
+
' next-work-item-id <repoRoot> <project>\n' +
|
|
121
|
+
' discovery-config --repo-root <repoRoot>\n' +
|
|
122
|
+
' prep-worktree <mainRoot> <worktreePath>\n' +
|
|
123
|
+
' qa-report <runs.json> <out.html>\n' +
|
|
124
|
+
' dag-ready-tasks <repoRoot> <planId> <maxConcurrent>\n' +
|
|
125
|
+
' dag-detect-cycle <repoRoot> <planId>\n' +
|
|
126
|
+
' walk-state-read <repoRoot> <planId>\n' +
|
|
127
|
+
' walk-state-write <repoRoot> <walkStateJsonPath>\n' +
|
|
128
|
+
' walker-default-concurrency [--explain]\n' +
|
|
129
|
+
' check-scope <repoRoot> <taskId> --branch <branchName>\n' +
|
|
130
|
+
' extend-scope <repoRoot> <taskId> --add <file>... --reason <text>\n' +
|
|
131
|
+
' secret-scan <repoRoot> --branch <branch>\n' +
|
|
132
|
+
' classify-security <repoRoot> <taskId> [--branch <branch>]\n' +
|
|
133
|
+
' council-plan <repoRoot> <taskId> [gateKey] [--changed-files=a,b,c]\n' +
|
|
134
|
+
' aggregate-verdicts <membersJson> <rule> [--weighted-threshold=N]\n' +
|
|
135
|
+
' apply-council-verdict <repoRoot> <taskId> <gate> <councilVerdictJson>\n' +
|
|
136
|
+
' chair-context <chairMemberInputsJson>\n' +
|
|
137
|
+
' chair-verdict <chairRawJson> <membersJson>\n' +
|
|
138
|
+
' set-task-field <repoRoot> <taskId> <field> <value>\n' +
|
|
139
|
+
' validate-council <repoRoot>\n';
|
|
90
140
|
function usage(msg) {
|
|
91
141
|
if (msg)
|
|
92
142
|
process.stderr.write(msg + '\n');
|
|
93
|
-
process.stderr.write(
|
|
94
|
-
'Commands:\n' +
|
|
95
|
-
' load-task <repoRoot> <taskId>\n' +
|
|
96
|
-
' infer-project <repoRoot>\n' +
|
|
97
|
-
' next-task-id <repoRoot> [--project=<p>]\n' +
|
|
98
|
-
' advance-status <repoRoot> <taskId> <toStatus> <actor> [gate]\n' +
|
|
99
|
-
' write-feedback <repoRoot> <taskId> <envelopeJsonPath>\n' +
|
|
100
|
-
' latest-feedback <repoRoot> <taskId>\n' +
|
|
101
|
-
' emit-gate-decision <repoRoot> <workItemId> <gate> <decision> <actor> [--comment=<str>]\n' +
|
|
102
|
-
' ui-review-config --repo-root <repoRoot>\n' +
|
|
103
|
-
' read-ui-review-state <repoRoot> <taskId>\n' +
|
|
104
|
-
' write-ui-review-state <repoRoot> <taskId> <baselines_pending>\n' +
|
|
105
|
-
' write-baseline <repoRoot> <taskId> <browser> <slug> <viewport> <sourceFile>\n' +
|
|
106
|
-
' plugin-root\n' +
|
|
107
|
-
' load-rfc <repoRoot> <id>\n' +
|
|
108
|
-
' save-rfc <repoRoot> <filePath>\n' +
|
|
109
|
-
' advance-rfc <repoRoot> <id> <toStatus> <agent|human> [gate]\n' +
|
|
110
|
-
' rfc-tasks <repoRoot> <rfcId> [--pretty]\n' +
|
|
111
|
-
' load-spike <repoRoot> <id>\n' +
|
|
112
|
-
' save-spike <repoRoot> <filePath>\n' +
|
|
113
|
-
' advance-spike <repoRoot> <id> <toStatus> <agent|human>\n' +
|
|
114
|
-
' load-plan <repoRoot> <id>\n' +
|
|
115
|
-
' save-plan <repoRoot> <filePath>\n' +
|
|
116
|
-
' advance-plan <repoRoot> <id> <toStatus> <agent|human> [gate]\n' +
|
|
117
|
-
' materialise-tasks <repoRoot> <planId>\n' +
|
|
118
|
-
' next-work-item-id <repoRoot> <project>\n' +
|
|
119
|
-
' discovery-config --repo-root <repoRoot>\n' +
|
|
120
|
-
' prep-worktree <mainRoot> <worktreePath>\n' +
|
|
121
|
-
' qa-report <runs.json> <out.html>\n' +
|
|
122
|
-
' dag-ready-tasks <repoRoot> <planId> <maxConcurrent>\n' +
|
|
123
|
-
' dag-detect-cycle <repoRoot> <planId>\n' +
|
|
124
|
-
' walk-state-read <repoRoot> <planId>\n' +
|
|
125
|
-
' walk-state-write <repoRoot> <walkStateJsonPath>\n' +
|
|
126
|
-
' walker-default-concurrency [--explain]\n' +
|
|
127
|
-
' check-scope <repoRoot> <taskId> --branch <branchName>\n' +
|
|
128
|
-
' extend-scope <repoRoot> <taskId> --add <file>... --reason <text>\n' +
|
|
129
|
-
' secret-scan <repoRoot> --branch <branch>\n' +
|
|
130
|
-
' classify-security <repoRoot> <taskId> [--branch <branch>]\n' +
|
|
131
|
-
' council-plan <repoRoot> <taskId> [gateKey] [--changed-files=a,b,c]\n' +
|
|
132
|
-
' aggregate-verdicts <membersJson> <rule> [--weighted-threshold=N]\n' +
|
|
133
|
-
' apply-council-verdict <repoRoot> <taskId> <gate> <councilVerdictJson>\n' +
|
|
134
|
-
' chair-context <chairMemberInputsJson>\n' +
|
|
135
|
-
' chair-verdict <chairRawJson> <membersJson>\n' +
|
|
136
|
-
' set-task-field <repoRoot> <taskId> <field> <value>\n' +
|
|
137
|
-
' validate-council <repoRoot>\n');
|
|
143
|
+
process.stderr.write(USAGE_TEXT);
|
|
138
144
|
process.exit(2);
|
|
139
145
|
}
|
|
146
|
+
function help() {
|
|
147
|
+
process.stdout.write(USAGE_TEXT);
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
140
150
|
const [, , command, ...rest] = process.argv;
|
|
151
|
+
if (command === '--help' || command === '-h') {
|
|
152
|
+
help();
|
|
153
|
+
}
|
|
141
154
|
if (!command) {
|
|
142
155
|
usage('Error: no command given');
|
|
143
156
|
}
|
package/lib/cli.ts
CHANGED
|
@@ -92,60 +92,75 @@ function die(msg: string, code = 1): never {
|
|
|
92
92
|
process.exit(code);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* The command list, shared by both exits below. `--help` is a request and its
|
|
97
|
+
* answer is this text, so it goes to stdout and exits 0; every error path keeps
|
|
98
|
+
* stderr and exit 2, so a caller piping stdout gets the list or nothing.
|
|
99
|
+
*/
|
|
100
|
+
const USAGE_TEXT =
|
|
101
|
+
'Usage: cloverleaf-cli <command> [args...]\n' +
|
|
102
|
+
'Commands:\n' +
|
|
103
|
+
' load-task <repoRoot> <taskId>\n' +
|
|
104
|
+
' infer-project <repoRoot>\n' +
|
|
105
|
+
' next-task-id <repoRoot> [--project=<p>]\n' +
|
|
106
|
+
' advance-status <repoRoot> <taskId> <toStatus> <actor> [gate]\n' +
|
|
107
|
+
' write-feedback <repoRoot> <taskId> <envelopeJsonPath>\n' +
|
|
108
|
+
' latest-feedback <repoRoot> <taskId>\n' +
|
|
109
|
+
' emit-gate-decision <repoRoot> <workItemId> <gate> <decision> <actor> [--comment=<str>]\n' +
|
|
110
|
+
' ui-review-config --repo-root <repoRoot>\n' +
|
|
111
|
+
' read-ui-review-state <repoRoot> <taskId>\n' +
|
|
112
|
+
' write-ui-review-state <repoRoot> <taskId> <baselines_pending>\n' +
|
|
113
|
+
' write-baseline <repoRoot> <taskId> <browser> <slug> <viewport> <sourceFile>\n' +
|
|
114
|
+
' plugin-root\n' +
|
|
115
|
+
' load-rfc <repoRoot> <id>\n' +
|
|
116
|
+
' save-rfc <repoRoot> <filePath>\n' +
|
|
117
|
+
' advance-rfc <repoRoot> <id> <toStatus> <agent|human> [gate]\n' +
|
|
118
|
+
' rfc-tasks <repoRoot> <rfcId> [--pretty]\n' +
|
|
119
|
+
' load-spike <repoRoot> <id>\n' +
|
|
120
|
+
' save-spike <repoRoot> <filePath>\n' +
|
|
121
|
+
' advance-spike <repoRoot> <id> <toStatus> <agent|human>\n' +
|
|
122
|
+
' load-plan <repoRoot> <id>\n' +
|
|
123
|
+
' save-plan <repoRoot> <filePath>\n' +
|
|
124
|
+
' advance-plan <repoRoot> <id> <toStatus> <agent|human> [gate]\n' +
|
|
125
|
+
' materialise-tasks <repoRoot> <planId>\n' +
|
|
126
|
+
' next-work-item-id <repoRoot> <project>\n' +
|
|
127
|
+
' discovery-config --repo-root <repoRoot>\n' +
|
|
128
|
+
' prep-worktree <mainRoot> <worktreePath>\n' +
|
|
129
|
+
' qa-report <runs.json> <out.html>\n' +
|
|
130
|
+
' dag-ready-tasks <repoRoot> <planId> <maxConcurrent>\n' +
|
|
131
|
+
' dag-detect-cycle <repoRoot> <planId>\n' +
|
|
132
|
+
' walk-state-read <repoRoot> <planId>\n' +
|
|
133
|
+
' walk-state-write <repoRoot> <walkStateJsonPath>\n' +
|
|
134
|
+
' walker-default-concurrency [--explain]\n' +
|
|
135
|
+
' check-scope <repoRoot> <taskId> --branch <branchName>\n' +
|
|
136
|
+
' extend-scope <repoRoot> <taskId> --add <file>... --reason <text>\n' +
|
|
137
|
+
' secret-scan <repoRoot> --branch <branch>\n' +
|
|
138
|
+
' classify-security <repoRoot> <taskId> [--branch <branch>]\n' +
|
|
139
|
+
' council-plan <repoRoot> <taskId> [gateKey] [--changed-files=a,b,c]\n' +
|
|
140
|
+
' aggregate-verdicts <membersJson> <rule> [--weighted-threshold=N]\n' +
|
|
141
|
+
' apply-council-verdict <repoRoot> <taskId> <gate> <councilVerdictJson>\n' +
|
|
142
|
+
' chair-context <chairMemberInputsJson>\n' +
|
|
143
|
+
' chair-verdict <chairRawJson> <membersJson>\n' +
|
|
144
|
+
' set-task-field <repoRoot> <taskId> <field> <value>\n' +
|
|
145
|
+
' validate-council <repoRoot>\n';
|
|
146
|
+
|
|
95
147
|
function usage(msg?: string): never {
|
|
96
148
|
if (msg) process.stderr.write(msg + '\n');
|
|
97
|
-
process.stderr.write(
|
|
98
|
-
'Usage: cloverleaf-cli <command> [args...]\n' +
|
|
99
|
-
'Commands:\n' +
|
|
100
|
-
' load-task <repoRoot> <taskId>\n' +
|
|
101
|
-
' infer-project <repoRoot>\n' +
|
|
102
|
-
' next-task-id <repoRoot> [--project=<p>]\n' +
|
|
103
|
-
' advance-status <repoRoot> <taskId> <toStatus> <actor> [gate]\n' +
|
|
104
|
-
' write-feedback <repoRoot> <taskId> <envelopeJsonPath>\n' +
|
|
105
|
-
' latest-feedback <repoRoot> <taskId>\n' +
|
|
106
|
-
' emit-gate-decision <repoRoot> <workItemId> <gate> <decision> <actor> [--comment=<str>]\n' +
|
|
107
|
-
' ui-review-config --repo-root <repoRoot>\n' +
|
|
108
|
-
' read-ui-review-state <repoRoot> <taskId>\n' +
|
|
109
|
-
' write-ui-review-state <repoRoot> <taskId> <baselines_pending>\n' +
|
|
110
|
-
' write-baseline <repoRoot> <taskId> <browser> <slug> <viewport> <sourceFile>\n' +
|
|
111
|
-
' plugin-root\n' +
|
|
112
|
-
' load-rfc <repoRoot> <id>\n' +
|
|
113
|
-
' save-rfc <repoRoot> <filePath>\n' +
|
|
114
|
-
' advance-rfc <repoRoot> <id> <toStatus> <agent|human> [gate]\n' +
|
|
115
|
-
' rfc-tasks <repoRoot> <rfcId> [--pretty]\n' +
|
|
116
|
-
' load-spike <repoRoot> <id>\n' +
|
|
117
|
-
' save-spike <repoRoot> <filePath>\n' +
|
|
118
|
-
' advance-spike <repoRoot> <id> <toStatus> <agent|human>\n' +
|
|
119
|
-
' load-plan <repoRoot> <id>\n' +
|
|
120
|
-
' save-plan <repoRoot> <filePath>\n' +
|
|
121
|
-
' advance-plan <repoRoot> <id> <toStatus> <agent|human> [gate]\n' +
|
|
122
|
-
' materialise-tasks <repoRoot> <planId>\n' +
|
|
123
|
-
' next-work-item-id <repoRoot> <project>\n' +
|
|
124
|
-
' discovery-config --repo-root <repoRoot>\n' +
|
|
125
|
-
' prep-worktree <mainRoot> <worktreePath>\n' +
|
|
126
|
-
' qa-report <runs.json> <out.html>\n' +
|
|
127
|
-
' dag-ready-tasks <repoRoot> <planId> <maxConcurrent>\n' +
|
|
128
|
-
' dag-detect-cycle <repoRoot> <planId>\n' +
|
|
129
|
-
' walk-state-read <repoRoot> <planId>\n' +
|
|
130
|
-
' walk-state-write <repoRoot> <walkStateJsonPath>\n' +
|
|
131
|
-
' walker-default-concurrency [--explain]\n' +
|
|
132
|
-
' check-scope <repoRoot> <taskId> --branch <branchName>\n' +
|
|
133
|
-
' extend-scope <repoRoot> <taskId> --add <file>... --reason <text>\n' +
|
|
134
|
-
' secret-scan <repoRoot> --branch <branch>\n' +
|
|
135
|
-
' classify-security <repoRoot> <taskId> [--branch <branch>]\n' +
|
|
136
|
-
' council-plan <repoRoot> <taskId> [gateKey] [--changed-files=a,b,c]\n' +
|
|
137
|
-
' aggregate-verdicts <membersJson> <rule> [--weighted-threshold=N]\n' +
|
|
138
|
-
' apply-council-verdict <repoRoot> <taskId> <gate> <councilVerdictJson>\n' +
|
|
139
|
-
' chair-context <chairMemberInputsJson>\n' +
|
|
140
|
-
' chair-verdict <chairRawJson> <membersJson>\n' +
|
|
141
|
-
' set-task-field <repoRoot> <taskId> <field> <value>\n' +
|
|
142
|
-
' validate-council <repoRoot>\n'
|
|
143
|
-
);
|
|
149
|
+
process.stderr.write(USAGE_TEXT);
|
|
144
150
|
process.exit(2);
|
|
145
151
|
}
|
|
146
152
|
|
|
153
|
+
function help(): never {
|
|
154
|
+
process.stdout.write(USAGE_TEXT);
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
157
|
+
|
|
147
158
|
const [, , command, ...rest] = process.argv;
|
|
148
159
|
|
|
160
|
+
if (command === '--help' || command === '-h') {
|
|
161
|
+
help();
|
|
162
|
+
}
|
|
163
|
+
|
|
149
164
|
if (!command) {
|
|
150
165
|
usage('Error: no command given');
|
|
151
166
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloverleaf/reference-impl",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.3",
|
|
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
|
@@ -145,10 +145,18 @@ Do not attempt to launch a missing engine — fail fast with `verdict: "escalate
|
|
|
145
145
|
# to see why and return verdict `escalate` — do NOT continue with a broken install.
|
|
146
146
|
npx astro preferences disable devToolbar > /tmp/ui-devtoolbar.log 2>&1; echo "EXIT=$?"
|
|
147
147
|
# Non-zero EXIT means this UI directory is not an Astro project — see below.
|
|
148
|
-
npm run dev -- --port={{preview_port}} &
|
|
148
|
+
setsid npm run dev -- --port={{preview_port}} < /dev/null > /tmp/ui-dev-server.log 2>&1 &
|
|
149
149
|
SERVER_PID=$!
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
+
`setsid` is load-bearing, not decoration. `$SERVER_PID` is **npm's** PID and
|
|
153
|
+
npm runs the dev server as a child, so `kill $SERVER_PID` reaps npm and leaves
|
|
154
|
+
the server holding the port, reparented to init. `setsid` puts the whole tree
|
|
155
|
+
in its own process group whose id equals `$SERVER_PID`, which is what lets
|
|
156
|
+
step 13 kill the group. Keep the two together: without `setsid` the group kill
|
|
157
|
+
silently matches nothing, because the job would otherwise sit in your shell's
|
|
158
|
+
group and `$SERVER_PID` would not be a group id at all.
|
|
159
|
+
|
|
152
160
|
`astro preferences disable devToolbar` is **project-scoped** by default: it writes into `$WT/site/.astro/`, which step 13 deletes along with the worktree, so it turns the toolbar off for this capture alone and nothing outside this run changes. Never pass `--global` — that writes to the operator's home directory and silently changes every other Astro project on the machine. Disable it before backgrounding the server, not after: Astro decides whether to inject the toolbar when the dev server boots.
|
|
153
161
|
|
|
154
162
|
If that command exits non-zero, this UI directory is **not an Astro project**. Disable that toolchain's own dev overlay instead. If it has none you can turn off, emit an `info` finding recording that the baseline may contain dev-only UI rather than capturing a contaminated one silently.
|
|
@@ -159,13 +167,27 @@ Do not attempt to launch a missing engine — fail fast with `verdict: "escalate
|
|
|
159
167
|
node "$DRIVER" > /tmp/ui-driver.log 2>&1; echo "EXIT=$?"
|
|
160
168
|
```
|
|
161
169
|
|
|
162
|
-
4.
|
|
163
|
-
|
|
164
|
-
5. Determine the site base path:
|
|
165
|
-
1. Check `{{repo_root}}/.cloverleaf/config/astro-base.json`. Expected shape: `{ "base": "<path>" }`. If present, use the `base` field verbatim and skip to step 6. (Consumer override — checked before parsing astro config.)
|
|
170
|
+
4. Determine the site base path. This step runs **before** the readiness gate because the gate probes the base, and it needs no server at all — every branch below is pure filesystem.
|
|
171
|
+
1. Check `{{repo_root}}/.cloverleaf/config/astro-base.json`. Expected shape: `{ "base": "<path>" }`. If present, use the `base` field verbatim and skip to step 5. (Consumer override — checked before parsing astro config.)
|
|
166
172
|
2. Otherwise, attempt to locate and parse an astro config file (common locations: `site/astro.config.mjs`, `astro.config.mjs` at repo root, `apps/web/astro.config.mjs`). Best-effort fallback.
|
|
167
173
|
3. If both fail, treat base as empty string.
|
|
168
174
|
|
|
175
|
+
5. Wait up to 30s for the dev server to answer on `http://localhost:{{preview_port}}<base>/`. Keep the trailing slash: a site configured `trailingSlash: 'always'` serves `<base>/` and returns 404 for `<base>`.
|
|
176
|
+
|
|
177
|
+
**Readiness means the server ANSWERS — not that it answers 200.** A dev server for a based site returns 404 on every path outside its base, so a probe that insists on 200 can burn the entire 30s budget against a server that is up and serving. Judge the connection, not the status:
|
|
178
|
+
|
|
179
|
+
- **Any HTTP status — 404 included — means the server is up.** Stop waiting and go on.
|
|
180
|
+
- **A refused connection is the only "not up yet."** Keep waiting until the budget is spent.
|
|
181
|
+
- **An answer that is not 200 means the server is up but the base is probably wrong.** Go on — step 8's navigation already retries without the base — and emit one `severity: "warning"` finding with `rule: "ui-review-base"` naming the probed URL and the status. A wrong base then shows up as a finding instead of only as surprising pixels. Keep it at `warning`: step 11 gates on `blocker`/`error` only, so this reports without bouncing the task.
|
|
182
|
+
|
|
183
|
+
Probe with the status as the payload — a refused connection prints `000`, which is what distinguishes it from a real 404:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
curl -s -o /dev/null -w '%{http_code}' "http://localhost:{{preview_port}}<base>/"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
If the server fails to start in 30s, read `/tmp/ui-dev-server.log` to see why, run teardown (step 13) — `kill -- -$SERVER_PID`, never a command-line pattern — and return verdict `escalate`.
|
|
190
|
+
|
|
169
191
|
6. **Apply maxCombinations cap** (when `affected_routes` is a list, not `"all"`):
|
|
170
192
|
- Compute `routes × viewports × browsers`. Use diff line counts as proxy for route diff size.
|
|
171
193
|
- Call `applyMaxCombinationsCap` from `lib/ui-browser.ts`.
|
|
@@ -252,13 +274,15 @@ Do not attempt to launch a missing engine — fail fast with `verdict: "escalate
|
|
|
252
274
|
|
|
253
275
|
13. Teardown:
|
|
254
276
|
```bash
|
|
255
|
-
kill
|
|
277
|
+
kill -- -$SERVER_PID 2>/dev/null || true
|
|
256
278
|
rm -f "$DRIVER"
|
|
257
279
|
cd {{repo_root}}
|
|
258
280
|
git worktree remove --force "$WT"
|
|
259
281
|
```
|
|
260
282
|
|
|
261
|
-
Kill the
|
|
283
|
+
Kill the process **group** you created in step 3, never by command-line pattern. The leading `-` in `-$SERVER_PID` is what makes this a group kill; `kill $SERVER_PID` without it reaps only npm and leaves the dev server orphaned on the port, which the next run then fails to bind.
|
|
284
|
+
|
|
285
|
+
`pkill -f "astro dev"` also matches the command line of the shell running it, so the shell kills itself: exit 144, and every command after it in the same compound statement — the `rm -f` and `git worktree remove` above — silently never runs.
|
|
262
286
|
|
|
263
287
|
Delete every driver script you wrote, wherever you put it. `git worktree remove --force` only clears what lives inside `$WT`; a driver written anywhere else survives the run and leaks into the next one.
|
|
264
288
|
|