cclaw-cli 0.42.0 → 0.44.0
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 +25 -4
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +17 -1
- package/dist/config.d.ts +63 -2
- package/dist/config.js +182 -8
- package/dist/content/harness-playbooks.js +9 -4
- package/dist/content/harnesses-doc.js +5 -0
- package/dist/content/hooks.d.ts +1 -0
- package/dist/content/hooks.js +46 -6
- package/dist/content/next-command.js +6 -3
- package/dist/content/observe.d.ts +1 -0
- package/dist/content/observe.js +101 -2
- package/dist/content/protocols.js +14 -9
- package/dist/content/skills.js +3 -0
- package/dist/content/stages/design.js +1 -0
- package/dist/content/stages/plan.js +3 -2
- package/dist/content/stages/scope.js +2 -1
- package/dist/content/stages/tdd.js +1 -8
- package/dist/install.d.ts +5 -6
- package/dist/install.js +35 -13
- package/dist/internal/advance-stage.d.ts +7 -0
- package/dist/internal/advance-stage.js +425 -0
- package/dist/types.d.ts +23 -2
- package/package.json +1 -1
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { stageSchema } from "../content/stage-schema.js";
|
|
3
|
+
import { appendDelegation, checkMandatoryDelegations } from "../delegation.js";
|
|
4
|
+
import { verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence } from "../gate-evidence.js";
|
|
5
|
+
import { isFlowTrack, nextStage } from "../flow-state.js";
|
|
6
|
+
import { readFlowState, writeFlowState } from "../runs.js";
|
|
7
|
+
import { FLOW_STAGES } from "../types.js";
|
|
8
|
+
function unique(values) {
|
|
9
|
+
return [...new Set(values)];
|
|
10
|
+
}
|
|
11
|
+
function parseStringList(raw) {
|
|
12
|
+
if (!Array.isArray(raw))
|
|
13
|
+
return [];
|
|
14
|
+
return raw
|
|
15
|
+
.filter((item) => typeof item === "string")
|
|
16
|
+
.map((item) => item.trim())
|
|
17
|
+
.filter((item) => item.length > 0);
|
|
18
|
+
}
|
|
19
|
+
function isFlowStageValue(value) {
|
|
20
|
+
return typeof value === "string" && FLOW_STAGES.includes(value);
|
|
21
|
+
}
|
|
22
|
+
function parseGuardEvidence(value) {
|
|
23
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
const next = {};
|
|
27
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
28
|
+
if (typeof raw !== "string")
|
|
29
|
+
continue;
|
|
30
|
+
const trimmed = raw.trim();
|
|
31
|
+
if (trimmed.length === 0)
|
|
32
|
+
continue;
|
|
33
|
+
next[key] = trimmed;
|
|
34
|
+
}
|
|
35
|
+
return next;
|
|
36
|
+
}
|
|
37
|
+
function parseCandidateGateCatalog(value, fallback) {
|
|
38
|
+
const next = {};
|
|
39
|
+
for (const stage of FLOW_STAGES) {
|
|
40
|
+
const base = fallback[stage];
|
|
41
|
+
next[stage] = {
|
|
42
|
+
required: [...base.required],
|
|
43
|
+
recommended: [...base.recommended],
|
|
44
|
+
conditional: [...base.conditional],
|
|
45
|
+
triggered: [...base.triggered],
|
|
46
|
+
passed: [...base.passed],
|
|
47
|
+
blocked: [...base.blocked]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
51
|
+
return next;
|
|
52
|
+
}
|
|
53
|
+
const rawCatalog = value;
|
|
54
|
+
for (const stage of FLOW_STAGES) {
|
|
55
|
+
const rawStage = rawCatalog[stage];
|
|
56
|
+
if (!rawStage || typeof rawStage !== "object" || Array.isArray(rawStage)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const typed = rawStage;
|
|
60
|
+
const base = fallback[stage];
|
|
61
|
+
const allowed = new Set([...base.required, ...base.recommended, ...base.conditional]);
|
|
62
|
+
const conditional = new Set(base.conditional);
|
|
63
|
+
const passed = unique(parseStringList(typed.passed)).filter((gateId) => allowed.has(gateId));
|
|
64
|
+
const blocked = unique(parseStringList(typed.blocked)).filter((gateId) => allowed.has(gateId));
|
|
65
|
+
const triggered = unique([
|
|
66
|
+
...parseStringList(typed.triggered).filter((gateId) => conditional.has(gateId)),
|
|
67
|
+
...passed.filter((gateId) => conditional.has(gateId)),
|
|
68
|
+
...blocked.filter((gateId) => conditional.has(gateId))
|
|
69
|
+
]);
|
|
70
|
+
next[stage] = {
|
|
71
|
+
required: [...base.required],
|
|
72
|
+
recommended: [...base.recommended],
|
|
73
|
+
conditional: [...base.conditional],
|
|
74
|
+
triggered,
|
|
75
|
+
passed,
|
|
76
|
+
blocked
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return next;
|
|
80
|
+
}
|
|
81
|
+
function coerceCandidateFlowState(raw, fallback) {
|
|
82
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
83
|
+
return fallback;
|
|
84
|
+
}
|
|
85
|
+
const typed = raw;
|
|
86
|
+
const track = isFlowTrack(typed.track) ? typed.track : fallback.track;
|
|
87
|
+
const currentStage = isFlowStageValue(typed.currentStage)
|
|
88
|
+
? typed.currentStage
|
|
89
|
+
: fallback.currentStage;
|
|
90
|
+
const completedStages = unique(parseStringList(typed.completedStages).filter((stage) => isFlowStageValue(stage)));
|
|
91
|
+
const skippedStagesRaw = parseStringList(typed.skippedStages).filter((stage) => isFlowStageValue(stage));
|
|
92
|
+
const skippedStages = skippedStagesRaw.length > 0 ? skippedStagesRaw : fallback.skippedStages;
|
|
93
|
+
return {
|
|
94
|
+
...fallback,
|
|
95
|
+
currentStage,
|
|
96
|
+
completedStages,
|
|
97
|
+
track,
|
|
98
|
+
skippedStages,
|
|
99
|
+
guardEvidence: parseGuardEvidence(typed.guardEvidence),
|
|
100
|
+
stageGateCatalog: parseCandidateGateCatalog(typed.stageGateCatalog, fallback.stageGateCatalog)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function parseEvidenceByGate(raw) {
|
|
104
|
+
if (!raw || raw.trim().length === 0) {
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
107
|
+
let parsed;
|
|
108
|
+
try {
|
|
109
|
+
parsed = JSON.parse(raw);
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
throw new Error(`--evidence-json must be valid JSON object: ${err instanceof Error ? err.message : String(err)}`);
|
|
113
|
+
}
|
|
114
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
115
|
+
throw new Error("--evidence-json must deserialize to an object.");
|
|
116
|
+
}
|
|
117
|
+
const next = {};
|
|
118
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
119
|
+
if (typeof value !== "string")
|
|
120
|
+
continue;
|
|
121
|
+
const trimmed = value.trim();
|
|
122
|
+
if (trimmed.length === 0)
|
|
123
|
+
continue;
|
|
124
|
+
next[key] = trimmed;
|
|
125
|
+
}
|
|
126
|
+
return next;
|
|
127
|
+
}
|
|
128
|
+
function parseCsv(raw) {
|
|
129
|
+
if (!raw)
|
|
130
|
+
return [];
|
|
131
|
+
return raw
|
|
132
|
+
.split(",")
|
|
133
|
+
.map((item) => item.trim())
|
|
134
|
+
.filter((item) => item.length > 0);
|
|
135
|
+
}
|
|
136
|
+
function parseAdvanceStageArgs(tokens) {
|
|
137
|
+
const [stageRaw, ...flagTokens] = tokens;
|
|
138
|
+
if (!isFlowStageValue(stageRaw)) {
|
|
139
|
+
throw new Error(`internal advance-stage requires a stage positional argument (${FLOW_STAGES.join(", ")}).`);
|
|
140
|
+
}
|
|
141
|
+
let evidenceJson;
|
|
142
|
+
let passed = [];
|
|
143
|
+
let waiveDelegations = [];
|
|
144
|
+
let waiverReason;
|
|
145
|
+
let quiet = false;
|
|
146
|
+
for (const token of flagTokens) {
|
|
147
|
+
if (token === "--quiet") {
|
|
148
|
+
quiet = true;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (token.startsWith("--evidence-json=")) {
|
|
152
|
+
evidenceJson = token.replace("--evidence-json=", "");
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (token.startsWith("--passed=")) {
|
|
156
|
+
passed = [...passed, ...parseCsv(token.replace("--passed=", ""))];
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (token.startsWith("--waive-delegation=")) {
|
|
160
|
+
waiveDelegations = [
|
|
161
|
+
...waiveDelegations,
|
|
162
|
+
...parseCsv(token.replace("--waive-delegation=", ""))
|
|
163
|
+
];
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (token.startsWith("--waiver-reason=")) {
|
|
167
|
+
waiverReason = token.replace("--waiver-reason=", "").trim();
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
throw new Error(`Unknown flag for internal advance-stage: ${token}`);
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
stage: stageRaw,
|
|
174
|
+
passedGateIds: unique(passed),
|
|
175
|
+
evidenceByGate: parseEvidenceByGate(evidenceJson),
|
|
176
|
+
waiveDelegations: unique(waiveDelegations),
|
|
177
|
+
waiverReason,
|
|
178
|
+
quiet
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function parseVerifyFlowStateDiffArgs(tokens) {
|
|
182
|
+
let afterJson;
|
|
183
|
+
let afterFile;
|
|
184
|
+
let quiet = false;
|
|
185
|
+
for (const token of tokens) {
|
|
186
|
+
if (token === "--quiet") {
|
|
187
|
+
quiet = true;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (token.startsWith("--after-json=")) {
|
|
191
|
+
afterJson = token.replace("--after-json=", "");
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (token.startsWith("--after-file=")) {
|
|
195
|
+
afterFile = token.replace("--after-file=", "");
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
throw new Error(`Unknown flag for internal verify-flow-state-diff: ${token}`);
|
|
199
|
+
}
|
|
200
|
+
if (!afterJson && !afterFile) {
|
|
201
|
+
throw new Error("internal verify-flow-state-diff requires --after-json=<json> or --after-file=<path>.");
|
|
202
|
+
}
|
|
203
|
+
return { afterJson, afterFile, quiet };
|
|
204
|
+
}
|
|
205
|
+
function parseVerifyCurrentStateArgs(tokens) {
|
|
206
|
+
let quiet = false;
|
|
207
|
+
for (const token of tokens) {
|
|
208
|
+
if (token === "--quiet") {
|
|
209
|
+
quiet = true;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
throw new Error(`Unknown flag for internal verify-current-state: ${token}`);
|
|
213
|
+
}
|
|
214
|
+
return { quiet };
|
|
215
|
+
}
|
|
216
|
+
async function buildValidationReport(projectRoot, flowState) {
|
|
217
|
+
const delegation = await checkMandatoryDelegations(projectRoot, flowState.currentStage);
|
|
218
|
+
const gates = await verifyCurrentStageGateEvidence(projectRoot, flowState);
|
|
219
|
+
const completedStages = verifyCompletedStagesGateClosure(flowState);
|
|
220
|
+
const ok = delegation.satisfied && gates.ok && gates.complete && completedStages.ok;
|
|
221
|
+
return {
|
|
222
|
+
ok,
|
|
223
|
+
stage: flowState.currentStage,
|
|
224
|
+
delegation: {
|
|
225
|
+
satisfied: delegation.satisfied,
|
|
226
|
+
missing: delegation.missing,
|
|
227
|
+
waived: delegation.waived,
|
|
228
|
+
missingEvidence: delegation.missingEvidence,
|
|
229
|
+
expectedMode: delegation.expectedMode
|
|
230
|
+
},
|
|
231
|
+
gates: {
|
|
232
|
+
ok: gates.ok,
|
|
233
|
+
complete: gates.complete,
|
|
234
|
+
issues: gates.issues,
|
|
235
|
+
missingRequired: gates.missingRequired,
|
|
236
|
+
missingTriggeredConditional: gates.missingTriggeredConditional
|
|
237
|
+
},
|
|
238
|
+
completedStages: {
|
|
239
|
+
ok: completedStages.ok,
|
|
240
|
+
issues: completedStages.issues
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
async function runAdvanceStage(projectRoot, args, io) {
|
|
245
|
+
const flowState = await readFlowState(projectRoot);
|
|
246
|
+
if (flowState.currentStage !== args.stage) {
|
|
247
|
+
io.stderr.write(`cclaw internal advance-stage: current stage is "${flowState.currentStage}", not "${args.stage}".\n`);
|
|
248
|
+
return 1;
|
|
249
|
+
}
|
|
250
|
+
const schema = stageSchema(args.stage);
|
|
251
|
+
const requiredGateIds = schema.requiredGates
|
|
252
|
+
.filter((gate) => gate.tier === "required")
|
|
253
|
+
.map((gate) => gate.id);
|
|
254
|
+
const allowedGateIds = new Set(schema.requiredGates.map((gate) => gate.id));
|
|
255
|
+
const selectedGateIds = args.passedGateIds.length > 0
|
|
256
|
+
? args.passedGateIds.filter((gateId) => allowedGateIds.has(gateId))
|
|
257
|
+
: requiredGateIds;
|
|
258
|
+
const missingRequired = requiredGateIds.filter((gateId) => !selectedGateIds.includes(gateId));
|
|
259
|
+
if (missingRequired.length > 0) {
|
|
260
|
+
io.stderr.write(`cclaw internal advance-stage: required gates not selected as passed: ${missingRequired.join(", ")}.\n`);
|
|
261
|
+
return 1;
|
|
262
|
+
}
|
|
263
|
+
const mandatory = new Set(schema.mandatoryDelegations);
|
|
264
|
+
for (const agent of args.waiveDelegations) {
|
|
265
|
+
if (!mandatory.has(agent)) {
|
|
266
|
+
io.stderr.write(`cclaw internal advance-stage: cannot waive "${agent}" for stage "${args.stage}" (not mandatory).\n`);
|
|
267
|
+
return 1;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (args.waiveDelegations.length > 0) {
|
|
271
|
+
const waiverReason = args.waiverReason && args.waiverReason.length > 0
|
|
272
|
+
? args.waiverReason
|
|
273
|
+
: "manual_waiver";
|
|
274
|
+
for (const agent of args.waiveDelegations) {
|
|
275
|
+
await appendDelegation(projectRoot, {
|
|
276
|
+
stage: args.stage,
|
|
277
|
+
agent,
|
|
278
|
+
mode: "mandatory",
|
|
279
|
+
status: "waived",
|
|
280
|
+
waiverReason,
|
|
281
|
+
fulfillmentMode: "role-switch",
|
|
282
|
+
ts: new Date().toISOString()
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const catalog = flowState.stageGateCatalog[args.stage];
|
|
287
|
+
const nextPassed = unique([...catalog.passed, ...selectedGateIds]).filter((gateId) => allowedGateIds.has(gateId));
|
|
288
|
+
const nextBlocked = unique(catalog.blocked.filter((gateId) => !nextPassed.includes(gateId))).filter((gateId) => allowedGateIds.has(gateId));
|
|
289
|
+
const conditional = new Set(catalog.conditional);
|
|
290
|
+
const nextTriggered = unique([
|
|
291
|
+
...catalog.triggered.filter((gateId) => conditional.has(gateId)),
|
|
292
|
+
...nextPassed.filter((gateId) => conditional.has(gateId)),
|
|
293
|
+
...nextBlocked.filter((gateId) => conditional.has(gateId))
|
|
294
|
+
]);
|
|
295
|
+
const nextGuardEvidence = { ...flowState.guardEvidence };
|
|
296
|
+
for (const gateId of nextPassed) {
|
|
297
|
+
const existing = nextGuardEvidence[gateId];
|
|
298
|
+
if (typeof existing === "string" && existing.trim().length > 0)
|
|
299
|
+
continue;
|
|
300
|
+
const provided = args.evidenceByGate[gateId];
|
|
301
|
+
nextGuardEvidence[gateId] = provided && provided.trim().length > 0
|
|
302
|
+
? provided.trim()
|
|
303
|
+
: `stage-complete helper auto-evidence for ${gateId} @ ${new Date().toISOString()} (${schema.artifactFile})`;
|
|
304
|
+
}
|
|
305
|
+
const nextStageCatalog = {
|
|
306
|
+
required: [...catalog.required],
|
|
307
|
+
recommended: [...catalog.recommended],
|
|
308
|
+
conditional: [...catalog.conditional],
|
|
309
|
+
triggered: nextTriggered,
|
|
310
|
+
passed: nextPassed,
|
|
311
|
+
blocked: nextBlocked
|
|
312
|
+
};
|
|
313
|
+
const candidateState = {
|
|
314
|
+
...flowState,
|
|
315
|
+
guardEvidence: nextGuardEvidence,
|
|
316
|
+
stageGateCatalog: {
|
|
317
|
+
...flowState.stageGateCatalog,
|
|
318
|
+
[args.stage]: nextStageCatalog
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
const validation = await buildValidationReport(projectRoot, candidateState);
|
|
322
|
+
if (!validation.ok) {
|
|
323
|
+
io.stderr.write(`cclaw internal advance-stage: validation failed for stage "${args.stage}".\n`);
|
|
324
|
+
if (validation.delegation.missing.length > 0) {
|
|
325
|
+
io.stderr.write(`- missing delegations: ${validation.delegation.missing.join(", ")}\n`);
|
|
326
|
+
}
|
|
327
|
+
if (validation.delegation.missingEvidence.length > 0) {
|
|
328
|
+
io.stderr.write(`- role-switch evidence missing: ${validation.delegation.missingEvidence.join(", ")}\n`);
|
|
329
|
+
}
|
|
330
|
+
if (validation.gates.issues.length > 0) {
|
|
331
|
+
io.stderr.write(`- gate issues: ${validation.gates.issues.join(" | ")}\n`);
|
|
332
|
+
}
|
|
333
|
+
if (validation.completedStages.issues.length > 0) {
|
|
334
|
+
io.stderr.write(`- completed-stage closure issues: ${validation.completedStages.issues.join(" | ")}\n`);
|
|
335
|
+
}
|
|
336
|
+
return 1;
|
|
337
|
+
}
|
|
338
|
+
const successor = nextStage(args.stage, flowState.track);
|
|
339
|
+
const completedStages = flowState.completedStages.includes(args.stage)
|
|
340
|
+
? [...flowState.completedStages]
|
|
341
|
+
: [...flowState.completedStages, args.stage];
|
|
342
|
+
const finalState = {
|
|
343
|
+
...candidateState,
|
|
344
|
+
completedStages,
|
|
345
|
+
currentStage: successor ?? args.stage
|
|
346
|
+
};
|
|
347
|
+
await writeFlowState(projectRoot, finalState);
|
|
348
|
+
if (!args.quiet) {
|
|
349
|
+
io.stdout.write(`${JSON.stringify({
|
|
350
|
+
ok: true,
|
|
351
|
+
command: "advance-stage",
|
|
352
|
+
stage: args.stage,
|
|
353
|
+
nextStage: successor,
|
|
354
|
+
currentStage: finalState.currentStage,
|
|
355
|
+
completedStages: finalState.completedStages
|
|
356
|
+
}, null, 2)}\n`);
|
|
357
|
+
}
|
|
358
|
+
return 0;
|
|
359
|
+
}
|
|
360
|
+
async function runVerifyFlowStateDiff(projectRoot, args, io) {
|
|
361
|
+
let raw = args.afterJson;
|
|
362
|
+
if (!raw && args.afterFile) {
|
|
363
|
+
raw = await fs.readFile(args.afterFile, "utf8");
|
|
364
|
+
}
|
|
365
|
+
if (!raw) {
|
|
366
|
+
io.stderr.write("cclaw internal verify-flow-state-diff: no candidate state payload.\n");
|
|
367
|
+
return 1;
|
|
368
|
+
}
|
|
369
|
+
let parsed;
|
|
370
|
+
try {
|
|
371
|
+
parsed = JSON.parse(raw);
|
|
372
|
+
}
|
|
373
|
+
catch (err) {
|
|
374
|
+
io.stderr.write(`cclaw internal verify-flow-state-diff: invalid JSON payload (${err instanceof Error ? err.message : String(err)}).\n`);
|
|
375
|
+
return 1;
|
|
376
|
+
}
|
|
377
|
+
const current = await readFlowState(projectRoot);
|
|
378
|
+
const candidate = coerceCandidateFlowState(parsed, current);
|
|
379
|
+
const validation = await buildValidationReport(projectRoot, candidate);
|
|
380
|
+
if (!args.quiet) {
|
|
381
|
+
io.stdout.write(`${JSON.stringify(validation, null, 2)}\n`);
|
|
382
|
+
}
|
|
383
|
+
if (!validation.ok) {
|
|
384
|
+
io.stderr.write(`cclaw internal verify-flow-state-diff: candidate state is invalid for stage "${validation.stage}".\n`);
|
|
385
|
+
}
|
|
386
|
+
return validation.ok ? 0 : 1;
|
|
387
|
+
}
|
|
388
|
+
async function runVerifyCurrentState(projectRoot, args, io) {
|
|
389
|
+
const current = await readFlowState(projectRoot);
|
|
390
|
+
const validation = await buildValidationReport(projectRoot, current);
|
|
391
|
+
if (!args.quiet) {
|
|
392
|
+
io.stdout.write(`${JSON.stringify(validation, null, 2)}\n`);
|
|
393
|
+
}
|
|
394
|
+
if (!validation.ok) {
|
|
395
|
+
const unmetDelegations = validation.delegation.missing.length + validation.delegation.missingEvidence.length;
|
|
396
|
+
const gatesWithoutEvidence = validation.gates.issues.filter((issue) => issue.includes("missing guardEvidence entry")).length;
|
|
397
|
+
io.stderr.write(`cclaw: current stage has ${unmetDelegations} unmet mandatory delegations and ${gatesWithoutEvidence} gates without evidence.\n`);
|
|
398
|
+
io.stderr.write(`cclaw internal verify-current-state: unresolved stage constraints for "${validation.stage}".\n`);
|
|
399
|
+
}
|
|
400
|
+
return validation.ok ? 0 : 1;
|
|
401
|
+
}
|
|
402
|
+
export async function runInternalCommand(projectRoot, argv, io) {
|
|
403
|
+
const [subcommand, ...tokens] = argv;
|
|
404
|
+
if (!subcommand) {
|
|
405
|
+
io.stderr.write("cclaw internal requires a subcommand: advance-stage | verify-flow-state-diff | verify-current-state\n");
|
|
406
|
+
return 1;
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
if (subcommand === "advance-stage") {
|
|
410
|
+
return await runAdvanceStage(projectRoot, parseAdvanceStageArgs(tokens), io);
|
|
411
|
+
}
|
|
412
|
+
if (subcommand === "verify-flow-state-diff") {
|
|
413
|
+
return await runVerifyFlowStateDiff(projectRoot, parseVerifyFlowStateDiffArgs(tokens), io);
|
|
414
|
+
}
|
|
415
|
+
if (subcommand === "verify-current-state") {
|
|
416
|
+
return await runVerifyCurrentState(projectRoot, parseVerifyCurrentStateArgs(tokens), io);
|
|
417
|
+
}
|
|
418
|
+
io.stderr.write(`Unknown internal subcommand: ${subcommand}. Expected advance-stage | verify-flow-state-diff | verify-current-state\n`);
|
|
419
|
+
return 1;
|
|
420
|
+
}
|
|
421
|
+
catch (err) {
|
|
422
|
+
io.stderr.write(`cclaw internal ${subcommand} failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
423
|
+
return 1;
|
|
424
|
+
}
|
|
425
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -92,9 +92,30 @@ export interface VibyConfig {
|
|
|
92
92
|
version: string;
|
|
93
93
|
flowVersion: string;
|
|
94
94
|
harnesses: HarnessId[];
|
|
95
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Single-knob strictness for both guard families. When set, cclaw derives
|
|
97
|
+
* `promptGuardMode` and `tddEnforcement` from this value unless the legacy
|
|
98
|
+
* fields are explicitly provided. Default: "advisory".
|
|
99
|
+
*
|
|
100
|
+
* Added in v0.43.0 to collapse two fields that always moved together for
|
|
101
|
+
* ~99% of users. Power users who want asymmetric strictness (e.g. strict
|
|
102
|
+
* prompt guard, advisory TDD) can still set the legacy fields directly —
|
|
103
|
+
* explicit per-axis values override the derived strictness.
|
|
104
|
+
*/
|
|
105
|
+
strictness?: "advisory" | "strict";
|
|
106
|
+
/**
|
|
107
|
+
* Prompt guard behavior for runtime write-risk detection hooks.
|
|
108
|
+
*
|
|
109
|
+
* Since v0.43.0 this is an advanced override. Prefer `strictness` in new
|
|
110
|
+
* configs; set this explicitly only when you need strict prompt guarding
|
|
111
|
+
* while keeping TDD advisory, or vice versa.
|
|
112
|
+
*/
|
|
96
113
|
promptGuardMode?: "advisory" | "strict";
|
|
97
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* TDD red->green->refactor enforcement mode used by workflow guard hooks.
|
|
116
|
+
*
|
|
117
|
+
* Since v0.43.0 this is an advanced override — see `strictness`.
|
|
118
|
+
*/
|
|
98
119
|
tddEnforcement?: "advisory" | "strict";
|
|
99
120
|
/** Optional test file globs used by guard guidance and /cc-ops tdd-log docs. */
|
|
100
121
|
tddTestGlobs?: string[];
|