cclaw-cli 0.48.1 → 0.48.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/README.md +10 -3
- package/dist/artifact-linter.js +2 -8
- package/dist/cli.js +8 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +13 -3
- package/dist/constants.d.ts +6 -0
- package/dist/constants.js +11 -0
- package/dist/content/contracts.d.ts +2 -2
- package/dist/content/contracts.js +2 -2
- package/dist/content/core-agents.d.ts +1 -1
- package/dist/content/core-agents.js +1 -1
- package/dist/content/hooks.js +16 -15
- package/dist/content/next-command.js +4 -2
- package/dist/content/observe.d.ts +2 -2
- package/dist/content/observe.js +83 -13
- package/dist/content/opencode-plugin.js +227 -45
- package/dist/content/stage-schema.js +1 -1
- package/dist/content/stages/ship.js +2 -5
- package/dist/content/templates.js +3 -6
- package/dist/delegation.d.ts +5 -1
- package/dist/delegation.js +12 -8
- package/dist/doctor.js +132 -15
- package/dist/eval/runner.js +36 -4
- package/dist/feature-system.d.ts +11 -4
- package/dist/feature-system.js +54 -10
- package/dist/flow-state.d.ts +2 -0
- package/dist/flow-state.js +19 -2
- package/dist/fs-utils.d.ts +4 -1
- package/dist/fs-utils.js +20 -4
- package/dist/gate-evidence.d.ts +2 -0
- package/dist/gate-evidence.js +13 -4
- package/dist/install.js +25 -23
- package/dist/internal/advance-stage.js +49 -10
- package/dist/knowledge-store.d.ts +8 -0
- package/dist/knowledge-store.js +113 -33
- package/dist/retro-gate.js +33 -23
- package/dist/run-archive.js +166 -128
- package/dist/run-persistence.d.ts +8 -1
- package/dist/run-persistence.js +7 -6
- package/dist/trace-matrix.js +7 -7
- package/package.json +1 -1
|
@@ -2,7 +2,8 @@ import { RUNTIME_ROOT } from "../constants.js";
|
|
|
2
2
|
import { META_SKILL_NAME } from "./meta-skill.js";
|
|
3
3
|
export function opencodePluginJs(_options = {}) {
|
|
4
4
|
return `// cclaw OpenCode plugin — generated by cclaw sync
|
|
5
|
-
import { existsSync, mkdirSync
|
|
5
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
6
|
+
import { readFile, stat } from "node:fs/promises";
|
|
6
7
|
import { join } from "node:path";
|
|
7
8
|
|
|
8
9
|
export default function cclawPlugin(ctx) {
|
|
@@ -33,9 +34,9 @@ export default function cclawPlugin(ctx) {
|
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
function readFlowState() {
|
|
37
|
+
async function readFlowState() {
|
|
37
38
|
try {
|
|
38
|
-
const raw =
|
|
39
|
+
const raw = await readFile(flowStatePath, "utf8");
|
|
39
40
|
const state = JSON.parse(raw);
|
|
40
41
|
return {
|
|
41
42
|
stage: typeof state.currentStage === "string" ? state.currentStage : "none",
|
|
@@ -47,23 +48,23 @@ export default function cclawPlugin(ctx) {
|
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
function readFileText(filePath) {
|
|
51
|
+
async function readFileText(filePath) {
|
|
51
52
|
try {
|
|
52
|
-
return
|
|
53
|
+
return await readFile(filePath, "utf8");
|
|
53
54
|
} catch {
|
|
54
55
|
return "";
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
function readTailLines(filePath, maxLines) {
|
|
59
|
-
const text = readFileText(filePath).trim();
|
|
59
|
+
async function readTailLines(filePath, maxLines) {
|
|
60
|
+
const text = (await readFileText(filePath)).trim();
|
|
60
61
|
if (!text) return [];
|
|
61
62
|
return text.split(/\\r?\\n/).slice(-maxLines);
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
function readCheckpointSummary() {
|
|
65
|
+
async function readCheckpointSummary() {
|
|
65
66
|
try {
|
|
66
|
-
const raw = readFileText(checkpointPath);
|
|
67
|
+
const raw = await readFileText(checkpointPath);
|
|
67
68
|
if (!raw) return "";
|
|
68
69
|
const cp = JSON.parse(raw);
|
|
69
70
|
return \`Checkpoint: stage=\${cp.stage || "none"}, status=\${cp.status || "unknown"}, run=\${cp.runId || "none"}, at=\${cp.timestamp || "unknown"}\`;
|
|
@@ -72,10 +73,10 @@ export default function cclawPlugin(ctx) {
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
function readContextMode() {
|
|
76
|
+
async function readContextMode() {
|
|
76
77
|
let mode = "default";
|
|
77
78
|
try {
|
|
78
|
-
const parsed = JSON.parse(readFileText(contextModePath));
|
|
79
|
+
const parsed = JSON.parse(await readFileText(contextModePath));
|
|
79
80
|
if (parsed && typeof parsed.activeMode === "string" && parsed.activeMode.trim().length > 0) {
|
|
80
81
|
mode = parsed.activeMode.trim();
|
|
81
82
|
}
|
|
@@ -87,9 +88,9 @@ export default function cclawPlugin(ctx) {
|
|
|
87
88
|
return { mode, guide };
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
function readRecentActivity() {
|
|
91
|
+
async function readRecentActivity() {
|
|
91
92
|
try {
|
|
92
|
-
const lines = readTailLines(activityPath, 5);
|
|
93
|
+
const lines = await readTailLines(activityPath, 5);
|
|
93
94
|
if (lines.length === 0) return [];
|
|
94
95
|
return lines
|
|
95
96
|
.map((line) => {
|
|
@@ -106,9 +107,9 @@ export default function cclawPlugin(ctx) {
|
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
function readLatestContextWarning() {
|
|
110
|
+
async function readLatestContextWarning() {
|
|
110
111
|
try {
|
|
111
|
-
const line = readTailLines(contextWarningsPath, 1)[0];
|
|
112
|
+
const line = (await readTailLines(contextWarningsPath, 1))[0];
|
|
112
113
|
if (!line) return "";
|
|
113
114
|
try {
|
|
114
115
|
const parsed = JSON.parse(line);
|
|
@@ -122,8 +123,8 @@ export default function cclawPlugin(ctx) {
|
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
function readKnowledgeDigest() {
|
|
126
|
-
const digest = readFileText(knowledgeDigestPath).trim();
|
|
126
|
+
async function readKnowledgeDigest() {
|
|
127
|
+
const digest = (await readFileText(knowledgeDigestPath)).trim();
|
|
127
128
|
if (!digest) {
|
|
128
129
|
return readTailLines(knowledgePath, 12);
|
|
129
130
|
}
|
|
@@ -134,68 +135,219 @@ export default function cclawPlugin(ctx) {
|
|
|
134
135
|
.filter((line) => !line.startsWith("#"));
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
const BOOTSTRAP_MARKER = "<!-- cclaw-bootstrap-v1 -->";
|
|
139
|
+
|
|
140
|
+
async function buildBootstrap() {
|
|
141
|
+
const flow = await readFlowState();
|
|
139
142
|
const parts = [
|
|
143
|
+
BOOTSTRAP_MARKER,
|
|
140
144
|
\`cclaw loaded. Flow: stage=\${flow.stage} (\${flow.completed}/8 completed, run=\${flow.activeRunId}). Active artifacts: ${RUNTIME_ROOT}/artifacts/\`
|
|
141
145
|
];
|
|
142
|
-
const contextMode = readContextMode();
|
|
146
|
+
const contextMode = await readContextMode();
|
|
143
147
|
parts.push(
|
|
144
148
|
contextMode.guide
|
|
145
149
|
? \`Context mode: \${contextMode.mode} (guide: \${contextMode.guide})\`
|
|
146
150
|
: \`Context mode: \${contextMode.mode}\`
|
|
147
151
|
);
|
|
148
152
|
|
|
149
|
-
const checkpoint = readCheckpointSummary();
|
|
153
|
+
const checkpoint = await readCheckpointSummary();
|
|
150
154
|
if (checkpoint) parts.push(checkpoint);
|
|
151
155
|
|
|
152
|
-
const digest = readFileText(sessionDigestPath).trim();
|
|
156
|
+
const digest = (await readFileText(sessionDigestPath)).trim();
|
|
153
157
|
if (digest) parts.push("Last session:", digest);
|
|
154
158
|
|
|
155
|
-
const activity = readRecentActivity();
|
|
159
|
+
const activity = await readRecentActivity();
|
|
156
160
|
if (activity.length > 0) parts.push("Recent stage activity:", ...activity);
|
|
157
161
|
|
|
158
|
-
const warning = readLatestContextWarning();
|
|
162
|
+
const warning = await readLatestContextWarning();
|
|
159
163
|
if (warning) parts.push("Latest context warning:", warning);
|
|
160
164
|
|
|
161
|
-
const knowledge = readKnowledgeDigest();
|
|
165
|
+
const knowledge = await readKnowledgeDigest();
|
|
162
166
|
if (knowledge.length > 0) parts.push("Knowledge digest (top relevant entries):", ...knowledge);
|
|
163
167
|
|
|
164
168
|
parts.push(
|
|
165
169
|
"If you discover a non-obvious rule or pattern, append one strict-schema JSON line to .cclaw/knowledge.jsonl using type: rule, pattern, lesson, or compound."
|
|
166
170
|
);
|
|
167
171
|
|
|
168
|
-
const meta = readFileText(metaSkillPath).trim();
|
|
172
|
+
const meta = (await readFileText(metaSkillPath)).trim();
|
|
169
173
|
if (meta) parts.push("", meta);
|
|
170
174
|
return parts.join("\\n");
|
|
171
175
|
}
|
|
172
176
|
|
|
173
177
|
let bootstrapCache = "";
|
|
178
|
+
let bootstrapMtimes = new Map();
|
|
179
|
+
let bootstrapRefreshPromise = null;
|
|
180
|
+
const BOOTSTRAP_SOURCE_PATHS = [
|
|
181
|
+
flowStatePath,
|
|
182
|
+
checkpointPath,
|
|
183
|
+
activityPath,
|
|
184
|
+
contextWarningsPath,
|
|
185
|
+
contextModePath,
|
|
186
|
+
sessionDigestPath,
|
|
187
|
+
knowledgePath,
|
|
188
|
+
knowledgeDigestPath,
|
|
189
|
+
metaSkillPath
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
async function readMtimeMs(filePath) {
|
|
193
|
+
try {
|
|
194
|
+
const st = await stat(filePath);
|
|
195
|
+
return Number.isFinite(st.mtimeMs) ? st.mtimeMs : 0;
|
|
196
|
+
} catch {
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function snapshotBootstrapMtimes() {
|
|
202
|
+
const next = new Map();
|
|
203
|
+
for (const filePath of BOOTSTRAP_SOURCE_PATHS) {
|
|
204
|
+
next.set(filePath, await readMtimeMs(filePath));
|
|
205
|
+
}
|
|
206
|
+
return next;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function bootstrapNeedsRefresh() {
|
|
210
|
+
if (!bootstrapCache) return true;
|
|
211
|
+
for (const filePath of BOOTSTRAP_SOURCE_PATHS) {
|
|
212
|
+
const prev = bootstrapMtimes.get(filePath) ?? 0;
|
|
213
|
+
const now = await readMtimeMs(filePath);
|
|
214
|
+
if (prev !== now) return true;
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
174
218
|
|
|
175
|
-
function refreshBootstrapCache() {
|
|
176
|
-
|
|
219
|
+
async function refreshBootstrapCache(force = false) {
|
|
220
|
+
if (!force && !(await bootstrapNeedsRefresh())) {
|
|
221
|
+
return bootstrapCache;
|
|
222
|
+
}
|
|
223
|
+
if (bootstrapRefreshPromise) {
|
|
224
|
+
return bootstrapRefreshPromise;
|
|
225
|
+
}
|
|
226
|
+
bootstrapRefreshPromise = (async () => {
|
|
227
|
+
const nextBootstrap = await buildBootstrap();
|
|
228
|
+
const nextMtimes = await snapshotBootstrapMtimes();
|
|
229
|
+
bootstrapCache = nextBootstrap;
|
|
230
|
+
bootstrapMtimes = nextMtimes;
|
|
231
|
+
return bootstrapCache;
|
|
232
|
+
})();
|
|
233
|
+
try {
|
|
234
|
+
return await bootstrapRefreshPromise;
|
|
235
|
+
} finally {
|
|
236
|
+
bootstrapRefreshPromise = null;
|
|
237
|
+
}
|
|
177
238
|
}
|
|
178
239
|
|
|
179
240
|
function getBootstrap() {
|
|
180
|
-
if (!bootstrapCache) refreshBootstrapCache();
|
|
181
241
|
return bootstrapCache;
|
|
182
242
|
}
|
|
183
243
|
|
|
244
|
+
const MAX_CONCURRENT_HOOKS = 2;
|
|
245
|
+
let runningHookTasks = 0;
|
|
246
|
+
const pendingHookTasks = [];
|
|
247
|
+
|
|
248
|
+
function runNextHookTask() {
|
|
249
|
+
if (runningHookTasks >= MAX_CONCURRENT_HOOKS) return;
|
|
250
|
+
const queued = pendingHookTasks.shift();
|
|
251
|
+
if (!queued) return;
|
|
252
|
+
runningHookTasks += 1;
|
|
253
|
+
queued()
|
|
254
|
+
.catch(() => false)
|
|
255
|
+
.finally(() => {
|
|
256
|
+
runningHookTasks -= 1;
|
|
257
|
+
runNextHookTask();
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function scheduleHookTask(task) {
|
|
262
|
+
return new Promise((resolve) => {
|
|
263
|
+
const wrapped = async () => {
|
|
264
|
+
try {
|
|
265
|
+
resolve(await task());
|
|
266
|
+
} catch {
|
|
267
|
+
resolve(false);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
pendingHookTasks.push(wrapped);
|
|
271
|
+
runNextHookTask();
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
184
275
|
async function runHookScript(scriptFileName, payload = {}) {
|
|
185
|
-
const {
|
|
276
|
+
const { spawn } = await import("node:child_process");
|
|
186
277
|
const scriptPath = join(root, "${RUNTIME_ROOT}/hooks/" + scriptFileName);
|
|
187
278
|
const input = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
279
|
+
return scheduleHookTask(() => new Promise((resolve) => {
|
|
280
|
+
let stderr = "";
|
|
281
|
+
let settled = false;
|
|
282
|
+
const finish = (ok) => {
|
|
283
|
+
if (settled) return;
|
|
284
|
+
settled = true;
|
|
285
|
+
resolve(ok);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
let child;
|
|
289
|
+
try {
|
|
290
|
+
child = spawn("bash", [scriptPath], {
|
|
291
|
+
cwd: root,
|
|
292
|
+
stdio: ["pipe", "ignore", "pipe"]
|
|
293
|
+
});
|
|
294
|
+
} catch {
|
|
295
|
+
finish(false);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const timer = setTimeout(() => {
|
|
300
|
+
child.kill("SIGKILL");
|
|
301
|
+
if (stderr.length > 0) {
|
|
302
|
+
console.error("[cclaw] opencode hook timeout: " + scriptFileName + " stderr=" + stderr.slice(-1200));
|
|
303
|
+
}
|
|
304
|
+
finish(false);
|
|
305
|
+
}, 20_000);
|
|
306
|
+
|
|
307
|
+
child.stderr?.on("data", (chunk) => {
|
|
308
|
+
stderr += String(chunk ?? "");
|
|
309
|
+
if (stderr.length > 4000) {
|
|
310
|
+
stderr = stderr.slice(-4000);
|
|
311
|
+
}
|
|
194
312
|
});
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
313
|
+
child.on("error", () => {
|
|
314
|
+
clearTimeout(timer);
|
|
315
|
+
finish(false);
|
|
316
|
+
});
|
|
317
|
+
child.on("close", (code) => {
|
|
318
|
+
clearTimeout(timer);
|
|
319
|
+
const ok = code === 0;
|
|
320
|
+
if (!ok && stderr.length > 0) {
|
|
321
|
+
console.error("[cclaw] opencode hook failed: " + scriptFileName + " stderr=" + stderr.slice(-1200));
|
|
322
|
+
}
|
|
323
|
+
finish(ok);
|
|
324
|
+
});
|
|
325
|
+
if (child.stdin) {
|
|
326
|
+
child.stdin.on("error", (error) => {
|
|
327
|
+
const code =
|
|
328
|
+
error && typeof error === "object" && "code" in error
|
|
329
|
+
? String(error.code)
|
|
330
|
+
: "";
|
|
331
|
+
if (code === "EPIPE" || code === "ERR_STREAM_DESTROYED") {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
clearTimeout(timer);
|
|
335
|
+
finish(false);
|
|
336
|
+
});
|
|
337
|
+
try {
|
|
338
|
+
child.stdin.end(input);
|
|
339
|
+
} catch (error) {
|
|
340
|
+
const code =
|
|
341
|
+
error && typeof error === "object" && "code" in error
|
|
342
|
+
? String(error.code)
|
|
343
|
+
: "";
|
|
344
|
+
if (code !== "EPIPE" && code !== "ERR_STREAM_DESTROYED") {
|
|
345
|
+
clearTimeout(timer);
|
|
346
|
+
finish(false);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}));
|
|
199
351
|
}
|
|
200
352
|
|
|
201
353
|
function normalizeToolPayload(input, output) {
|
|
@@ -207,9 +359,15 @@ export default function cclawPlugin(ctx) {
|
|
|
207
359
|
if (typeof payload === "string") return payload;
|
|
208
360
|
if (payload && typeof payload === "object") {
|
|
209
361
|
if (typeof payload.type === "string") return payload.type;
|
|
362
|
+
if (typeof payload.eventType === "string") return payload.eventType;
|
|
363
|
+
if (typeof payload.kind === "string") return payload.kind;
|
|
364
|
+
if (typeof payload.topic === "string") return payload.topic;
|
|
210
365
|
if (typeof payload.name === "string") return payload.name;
|
|
211
366
|
if (payload.event && typeof payload.event === "object") {
|
|
212
367
|
if (typeof payload.event.type === "string") return payload.event.type;
|
|
368
|
+
if (typeof payload.event.eventType === "string") return payload.event.eventType;
|
|
369
|
+
if (typeof payload.event.kind === "string") return payload.event.kind;
|
|
370
|
+
if (typeof payload.event.topic === "string") return payload.event.topic;
|
|
213
371
|
if (typeof payload.event.name === "string") return payload.event.name;
|
|
214
372
|
}
|
|
215
373
|
}
|
|
@@ -217,18 +375,40 @@ export default function cclawPlugin(ctx) {
|
|
|
217
375
|
}
|
|
218
376
|
|
|
219
377
|
function resolveEventData(payload) {
|
|
220
|
-
if (payload && typeof payload === "object"
|
|
221
|
-
|
|
378
|
+
if (payload && typeof payload === "object") {
|
|
379
|
+
if (payload.event && typeof payload.event === "object") {
|
|
380
|
+
if (payload.event.data && typeof payload.event.data === "object") {
|
|
381
|
+
return payload.event.data;
|
|
382
|
+
}
|
|
383
|
+
if (payload.event.payload && typeof payload.event.payload === "object") {
|
|
384
|
+
return payload.event.payload;
|
|
385
|
+
}
|
|
386
|
+
return payload.event;
|
|
387
|
+
}
|
|
388
|
+
if (payload.data && typeof payload.data === "object") {
|
|
389
|
+
return payload.data;
|
|
390
|
+
}
|
|
391
|
+
if (payload.payload && typeof payload.payload === "object") {
|
|
392
|
+
return payload.payload;
|
|
393
|
+
}
|
|
222
394
|
}
|
|
223
395
|
return payload;
|
|
224
396
|
}
|
|
225
397
|
|
|
226
398
|
ensureRuntimeDirs();
|
|
399
|
+
void refreshBootstrapCache(true);
|
|
227
400
|
|
|
228
401
|
return {
|
|
229
402
|
event: async (payload) => {
|
|
230
403
|
const eventType = resolveEventType(payload);
|
|
231
404
|
const eventData = resolveEventData(payload);
|
|
405
|
+
if (!eventType) {
|
|
406
|
+
const keys =
|
|
407
|
+
payload && typeof payload === "object"
|
|
408
|
+
? Object.keys(payload).slice(0, 10).join(", ")
|
|
409
|
+
: typeof payload;
|
|
410
|
+
console.error("[cclaw] opencode unknown event payload keys: " + keys);
|
|
411
|
+
}
|
|
232
412
|
if (
|
|
233
413
|
eventType === "session.created" ||
|
|
234
414
|
eventType === "session.resumed" ||
|
|
@@ -242,7 +422,7 @@ export default function cclawPlugin(ctx) {
|
|
|
242
422
|
// session.updated covers config reloads and artifact/rules edits
|
|
243
423
|
// that happen mid-session; without it the cache would stay stale
|
|
244
424
|
// until the next compaction or restart.
|
|
245
|
-
refreshBootstrapCache();
|
|
425
|
+
await refreshBootstrapCache(true);
|
|
246
426
|
}
|
|
247
427
|
if (eventType === "session.compacted") {
|
|
248
428
|
await runHookScript("pre-compact.sh", eventData ?? {});
|
|
@@ -264,14 +444,16 @@ export default function cclawPlugin(ctx) {
|
|
|
264
444
|
"tool.execute.after": async (input, output) => {
|
|
265
445
|
const payload = normalizeToolPayload(input, output);
|
|
266
446
|
await runHookScript("context-monitor.sh", payload);
|
|
447
|
+
void refreshBootstrapCache(false);
|
|
267
448
|
},
|
|
268
449
|
"experimental.chat.system.transform": (payload) => {
|
|
269
450
|
const bootstrap = getBootstrap();
|
|
451
|
+
if (!bootstrap) return payload;
|
|
270
452
|
if (typeof payload === "string") {
|
|
271
|
-
return payload.includes(
|
|
453
|
+
return payload.includes(BOOTSTRAP_MARKER) ? payload : \`\${payload}\\n\\n\${bootstrap}\`;
|
|
272
454
|
}
|
|
273
455
|
if (payload && typeof payload === "object" && typeof payload.system === "string") {
|
|
274
|
-
if (payload.system.includes(
|
|
456
|
+
if (payload.system.includes(BOOTSTRAP_MARKER)) return payload;
|
|
275
457
|
return { ...payload, system: \`\${payload.system}\\n\\n\${bootstrap}\` };
|
|
276
458
|
}
|
|
277
459
|
return payload;
|
|
@@ -198,7 +198,7 @@ const STAGE_AUTO_SUBAGENT_DISPATCH = {
|
|
|
198
198
|
{
|
|
199
199
|
agent: "doc-updater",
|
|
200
200
|
mode: "proactive",
|
|
201
|
-
when: "
|
|
201
|
+
when: "Proactive in tdd when public behavior, APIs, or config surfaces change.",
|
|
202
202
|
purpose: "Prevent code/docs drift before review and ship.",
|
|
203
203
|
requiresUserGate: false
|
|
204
204
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SHIP_FINALIZATION_MODES } from "../../constants.js";
|
|
1
2
|
// ---------------------------------------------------------------------------
|
|
2
3
|
// SHIP — reference: superpowers finishing-a-development-branch + gstack /ship
|
|
3
4
|
// ---------------------------------------------------------------------------
|
|
@@ -93,11 +94,7 @@ export const SHIP = {
|
|
|
93
94
|
"Pre-Ship Checks",
|
|
94
95
|
"Release Notes",
|
|
95
96
|
"Rollback Plan",
|
|
96
|
-
|
|
97
|
-
"FINALIZE_OPEN_PR",
|
|
98
|
-
"FINALIZE_KEEP_BRANCH",
|
|
99
|
-
"FINALIZE_DISCARD_BRANCH",
|
|
100
|
-
"FINALIZE_NO_VCS"
|
|
97
|
+
...SHIP_FINALIZATION_MODES
|
|
101
98
|
],
|
|
102
99
|
artifactFile: "08-ship.md",
|
|
103
100
|
// `done` exits the stage pipeline. Archive semantics are handled by the
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { CCLAW_VERSION } from "../constants.js";
|
|
1
|
+
import { CCLAW_VERSION, SHIP_FINALIZATION_MODES } from "../constants.js";
|
|
2
2
|
import { orderedStageSchemas } from "./stage-schema.js";
|
|
3
3
|
import { FLOW_STAGES } from "../types.js";
|
|
4
|
+
const SHIP_FINALIZATION_ENUM_LINES = SHIP_FINALIZATION_MODES.map((mode) => ` - ${mode}`).join("\n");
|
|
4
5
|
export const ARTIFACT_TEMPLATES = {
|
|
5
6
|
"01-brainstorm.md": `---
|
|
6
7
|
stage: brainstorm
|
|
@@ -645,11 +646,7 @@ inputs_hash: sha256:pending
|
|
|
645
646
|
|
|
646
647
|
## Finalization
|
|
647
648
|
- Selected enum (exactly one):
|
|
648
|
-
|
|
649
|
-
- FINALIZE_OPEN_PR
|
|
650
|
-
- FINALIZE_KEEP_BRANCH
|
|
651
|
-
- FINALIZE_DISCARD_BRANCH
|
|
652
|
-
- FINALIZE_NO_VCS
|
|
649
|
+
${SHIP_FINALIZATION_ENUM_LINES}
|
|
653
650
|
- Selected label (A/B/C/D/E):
|
|
654
651
|
- Execution result:
|
|
655
652
|
- PR URL / merge commit / kept branch / discard confirmation:
|
package/dist/delegation.d.ts
CHANGED
|
@@ -53,6 +53,8 @@ export type DelegationEntry = {
|
|
|
53
53
|
retryCount?: number;
|
|
54
54
|
/** Optional references to evidence anchors in artifacts. */
|
|
55
55
|
evidenceRefs?: string[];
|
|
56
|
+
/** Optional skill marker used for role-specific mandatory checks. */
|
|
57
|
+
skill?: string;
|
|
56
58
|
/**
|
|
57
59
|
* Fulfillment mode this entry was executed under. Omitted on legacy rows
|
|
58
60
|
* (treated as `"isolated"` for Claude, otherwise inferred from the active
|
|
@@ -85,7 +87,9 @@ export declare function appendDelegation(projectRoot: string, entry: DelegationE
|
|
|
85
87
|
* strongest guarantee.
|
|
86
88
|
*/
|
|
87
89
|
export declare function expectedFulfillmentMode(fallbacks: SubagentFallback[]): DelegationFulfillmentMode;
|
|
88
|
-
export declare function checkMandatoryDelegations(projectRoot: string, stage: FlowStage
|
|
90
|
+
export declare function checkMandatoryDelegations(projectRoot: string, stage: FlowStage, options?: {
|
|
91
|
+
repairFeatureSystem?: boolean;
|
|
92
|
+
}): Promise<{
|
|
89
93
|
satisfied: boolean;
|
|
90
94
|
missing: string[];
|
|
91
95
|
waived: string[];
|
package/dist/delegation.js
CHANGED
|
@@ -154,6 +154,7 @@ function isDelegationEntry(value) {
|
|
|
154
154
|
(o.tokens === undefined || isDelegationTokenUsage(o.tokens)) &&
|
|
155
155
|
retryOk &&
|
|
156
156
|
(o.evidenceRefs === undefined || (Array.isArray(o.evidenceRefs) && o.evidenceRefs.every((item) => typeof item === "string"))) &&
|
|
157
|
+
(o.skill === undefined || typeof o.skill === "string") &&
|
|
157
158
|
(o.schemaVersion === undefined || o.schemaVersion === 1));
|
|
158
159
|
}
|
|
159
160
|
function parseLedger(raw, runId) {
|
|
@@ -237,7 +238,7 @@ export async function appendDelegation(projectRoot, entry) {
|
|
|
237
238
|
runId: activeRunId,
|
|
238
239
|
entries: [...prior.entries, stamped]
|
|
239
240
|
};
|
|
240
|
-
await writeFileSafe(filePath, `${JSON.stringify(ledger, null, 2)}\n
|
|
241
|
+
await writeFileSafe(filePath, `${JSON.stringify(ledger, null, 2)}\n`, { mode: 0o600 });
|
|
241
242
|
});
|
|
242
243
|
}
|
|
243
244
|
/**
|
|
@@ -257,9 +258,11 @@ export function expectedFulfillmentMode(fallbacks) {
|
|
|
257
258
|
return "role-switch";
|
|
258
259
|
return "harness-waiver";
|
|
259
260
|
}
|
|
260
|
-
export async function checkMandatoryDelegations(projectRoot, stage) {
|
|
261
|
+
export async function checkMandatoryDelegations(projectRoot, stage, options = {}) {
|
|
261
262
|
const mandatory = stageSchema(stage).mandatoryDelegations;
|
|
262
|
-
const { activeRunId } = await readFlowState(projectRoot
|
|
263
|
+
const { activeRunId } = await readFlowState(projectRoot, {
|
|
264
|
+
repairFeatureSystem: options.repairFeatureSystem
|
|
265
|
+
});
|
|
263
266
|
const ledger = await readDelegationLedger(projectRoot);
|
|
264
267
|
const forStage = ledger.entries.filter((e) => e.stage === stage);
|
|
265
268
|
const forRun = forStage.filter((e) => e.runId === activeRunId);
|
|
@@ -279,14 +282,15 @@ export async function checkMandatoryDelegations(projectRoot, stage) {
|
|
|
279
282
|
const rows = forRun.filter((e) => e.agent === agent);
|
|
280
283
|
const completedRows = rows.filter((e) => e.status === "completed");
|
|
281
284
|
const waivedRows = rows.filter((e) => e.status === "waived");
|
|
282
|
-
const
|
|
285
|
+
const adversarialReviewerRequired = stage === "review" &&
|
|
283
286
|
agent === "reviewer" &&
|
|
284
|
-
reviewTriggers?.requireAdversarialReviewer
|
|
285
|
-
|
|
286
|
-
: 1;
|
|
287
|
+
reviewTriggers?.requireAdversarialReviewer === true;
|
|
288
|
+
const requiredCompletedCount = adversarialReviewerRequired ? 2 : 1;
|
|
287
289
|
const hasCompleted = completedRows.length >= requiredCompletedCount;
|
|
288
290
|
const hasWaived = waivedRows.length > 0;
|
|
289
|
-
const
|
|
291
|
+
const hasAdversarialSkill = !adversarialReviewerRequired ||
|
|
292
|
+
completedRows.some((row) => row.skill === "adversarial-review");
|
|
293
|
+
const ok = hasWaived || (hasCompleted && hasAdversarialSkill);
|
|
290
294
|
if (!ok) {
|
|
291
295
|
missing.push(agent);
|
|
292
296
|
continue;
|