@cloverleaf/reference-impl 0.13.0 → 0.13.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.
@@ -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.0",
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.0
1
+ 0.13.2
package/dist/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Cloverleaf CLI dispatcher.
4
4
  *
5
- * Usage: cli.ts <command> [args...]
5
+ * Usage: cloverleaf-cli <command> [args...]
6
6
  *
7
7
  * Commands:
8
8
  * load-task <repoRoot> <taskId>
@@ -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('Usage: cli.ts <command> [args...]\n' +
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
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Cloverleaf CLI dispatcher.
4
4
  *
5
- * Usage: cli.ts <command> [args...]
5
+ * Usage: cloverleaf-cli <command> [args...]
6
6
  *
7
7
  * Commands:
8
8
  * load-task <repoRoot> <taskId>
@@ -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: cli.ts <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.0",
3
+ "version": "0.13.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",
@@ -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,7 +167,7 @@ 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. Wait up to 30s for `http://localhost:{{preview_port}}/` to respond 200. If the server fails to start in 30s, run teardown (step 13) — `kill $SERVER_PID`, never a command-line pattern — and return verdict `escalate`.
170
+ 4. Wait up to 30s for `http://localhost:{{preview_port}}/` to respond 200. 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`.
163
171
 
164
172
  5. Determine the site base path:
165
173
  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.)
@@ -252,13 +260,15 @@ Do not attempt to launch a missing engine — fail fast with `verdict: "escalate
252
260
 
253
261
  13. Teardown:
254
262
  ```bash
255
- kill $SERVER_PID 2>/dev/null || true
263
+ kill -- -$SERVER_PID 2>/dev/null || true
256
264
  rm -f "$DRIVER"
257
265
  cd {{repo_root}}
258
266
  git worktree remove --force "$WT"
259
267
  ```
260
268
 
261
- Kill the server by the PID you captured in step 3, never by command-line pattern. `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.
269
+ 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.
270
+
271
+ `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
272
 
263
273
  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
274