cclaw-cli 0.5.16 → 0.6.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/dist/artifact-linter.d.ts +13 -0
- package/dist/artifact-linter.js +182 -13
- package/dist/cli.d.ts +4 -2
- package/dist/cli.js +18 -4
- package/dist/config.d.ts +2 -2
- package/dist/config.js +19 -5
- package/dist/constants.d.ts +2 -2
- package/dist/constants.js +3 -2
- package/dist/content/agents.js +2 -2
- package/dist/content/examples.js +71 -62
- package/dist/content/hooks.d.ts +1 -0
- package/dist/content/hooks.js +145 -0
- package/dist/content/learnings.js +25 -5
- package/dist/content/meta-skill.js +12 -0
- package/dist/content/next-command.js +8 -0
- package/dist/content/observe.js +18 -0
- package/dist/content/session-hooks.js +1 -1
- package/dist/content/stage-schema.js +12 -2
- package/dist/content/status-command.d.ts +9 -0
- package/dist/content/status-command.js +132 -0
- package/dist/content/templates.js +18 -19
- package/dist/content/utility-skills.d.ts +6 -2
- package/dist/content/utility-skills.js +431 -3
- package/dist/delegation.d.ts +6 -0
- package/dist/delegation.js +12 -4
- package/dist/doctor.js +37 -1
- package/dist/flow-state.d.ts +16 -4
- package/dist/flow-state.js +50 -11
- package/dist/gate-evidence.d.ts +14 -0
- package/dist/gate-evidence.js +65 -3
- package/dist/harness-adapters.js +1 -0
- package/dist/install.d.ts +2 -1
- package/dist/install.js +107 -6
- package/dist/runs.d.ts +13 -1
- package/dist/runs.js +73 -7
- package/dist/types.d.ts +13 -0
- package/dist/types.js +13 -0
- package/package.json +1 -1
|
@@ -18,3 +18,16 @@ export declare function validateReviewArmy(projectRoot: string): Promise<{
|
|
|
18
18
|
valid: boolean;
|
|
19
19
|
errors: string[];
|
|
20
20
|
}>;
|
|
21
|
+
export interface ReviewVerdictConsistencyResult {
|
|
22
|
+
ok: boolean;
|
|
23
|
+
errors: string[];
|
|
24
|
+
finalVerdict: "APPROVED" | "APPROVED_WITH_CONCERNS" | "BLOCKED" | "UNKNOWN";
|
|
25
|
+
openCriticalCount: number;
|
|
26
|
+
shipBlockerCount: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Ensure the narrative verdict in 07-review.md is consistent with the
|
|
30
|
+
* structured review-army reconciliation. A review cannot declare
|
|
31
|
+
* APPROVED while open Critical findings or shipBlockers remain.
|
|
32
|
+
*/
|
|
33
|
+
export declare function checkReviewVerdictConsistency(projectRoot: string): Promise<ReviewVerdictConsistencyResult>;
|
package/dist/artifact-linter.js
CHANGED
|
@@ -134,7 +134,61 @@ function extractRequiredKeywords(rule) {
|
|
|
134
134
|
return [];
|
|
135
135
|
return phrases;
|
|
136
136
|
}
|
|
137
|
-
|
|
137
|
+
const VAGUE_AC_ADJECTIVES = [
|
|
138
|
+
"fast",
|
|
139
|
+
"quick",
|
|
140
|
+
"slow",
|
|
141
|
+
"fast enough",
|
|
142
|
+
"quickly",
|
|
143
|
+
"intuitive",
|
|
144
|
+
"robust",
|
|
145
|
+
"reliable",
|
|
146
|
+
"scalable",
|
|
147
|
+
"simple",
|
|
148
|
+
"easy",
|
|
149
|
+
"user-friendly",
|
|
150
|
+
"user friendly",
|
|
151
|
+
"nice",
|
|
152
|
+
"good",
|
|
153
|
+
"clean",
|
|
154
|
+
"secure enough",
|
|
155
|
+
"responsive",
|
|
156
|
+
"efficient",
|
|
157
|
+
"performant",
|
|
158
|
+
"smooth",
|
|
159
|
+
"seamless",
|
|
160
|
+
"modern"
|
|
161
|
+
];
|
|
162
|
+
function isSeparatorRow(line) {
|
|
163
|
+
return /^\|[-:| ]+\|$/u.test(line);
|
|
164
|
+
}
|
|
165
|
+
function getMarkdownTableRows(sectionBody) {
|
|
166
|
+
const lines = sectionBody.split(/\r?\n/).map((line) => line.trim());
|
|
167
|
+
const rows = [];
|
|
168
|
+
let sawSeparator = false;
|
|
169
|
+
for (const line of lines) {
|
|
170
|
+
if (!/^\|.*\|$/u.test(line))
|
|
171
|
+
continue;
|
|
172
|
+
if (isSeparatorRow(line)) {
|
|
173
|
+
sawSeparator = true;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (!sawSeparator)
|
|
177
|
+
continue;
|
|
178
|
+
rows.push(parseMarkdownTableRow(line));
|
|
179
|
+
}
|
|
180
|
+
return rows;
|
|
181
|
+
}
|
|
182
|
+
function lineContainsVagueAdjective(text) {
|
|
183
|
+
const lower = text.toLowerCase();
|
|
184
|
+
for (const adjective of VAGUE_AC_ADJECTIVES) {
|
|
185
|
+
const pattern = new RegExp(`(?:^|[^A-Za-z])${adjective.replace(/ /g, "\\s+")}(?:[^A-Za-z]|$)`, "iu");
|
|
186
|
+
if (pattern.test(lower))
|
|
187
|
+
return adjective;
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
function validateSectionBody(sectionBody, rule, sectionName) {
|
|
138
192
|
const bodyLines = sectionBody.split(/\r?\n/).map((line) => line.trim());
|
|
139
193
|
const meaningful = meaningfulLineCount(sectionBody);
|
|
140
194
|
if (meaningful === 0) {
|
|
@@ -231,6 +285,29 @@ function validateSectionBody(sectionBody, rule) {
|
|
|
231
285
|
};
|
|
232
286
|
}
|
|
233
287
|
}
|
|
288
|
+
if (normalizeHeadingTitle(sectionName).toLowerCase() === "acceptance criteria" &&
|
|
289
|
+
/observable[\s,]*measurable[\s,]+(and )?falsifiable/iu.test(rule)) {
|
|
290
|
+
const rows = getMarkdownTableRows(sectionBody);
|
|
291
|
+
for (const row of rows) {
|
|
292
|
+
const criterionText = row[1] ?? row[0] ?? "";
|
|
293
|
+
const adjective = lineContainsVagueAdjective(criterionText);
|
|
294
|
+
if (adjective) {
|
|
295
|
+
return {
|
|
296
|
+
ok: false,
|
|
297
|
+
details: `Acceptance criterion uses vague adjective "${adjective}" without a measurable predicate: "${criterionText.slice(0, 140)}". Rewrite with a numeric threshold or boolean outcome.`
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
const hasDigit = /\d/u.test(criterionText);
|
|
301
|
+
const hasMeasurableVerb = /\b(blocks?|rejects?|returns?|matches?|equals?|emits?|succeeds?|fails?|publishes?|logs?|persists?|reads?|writes?|creates?|deletes?|throws?|contains?|restores?|exceeds?|responds?|warns?|quarantines?|includes?|raises?|passes?|denies|refuses|exits|succeeds|completes|prevents|allows|maps|points|signals|surfaces|records|produces|accepts|requires)\b/iu.test(criterionText);
|
|
302
|
+
const hasMeaningfulText = /[A-Za-z]/u.test(criterionText) && criterionText.trim().length >= 12;
|
|
303
|
+
if (hasMeaningfulText && !hasDigit && !hasMeasurableVerb) {
|
|
304
|
+
return {
|
|
305
|
+
ok: false,
|
|
306
|
+
details: `Acceptance criterion lacks a measurable predicate (no numeric threshold, no observable verb like blocks/returns/publishes/matches): "${criterionText.slice(0, 140)}". Rewrite so the criterion is falsifiable by a single test.`
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
234
311
|
return {
|
|
235
312
|
ok: true,
|
|
236
313
|
details: "Section heading and content satisfy lint heuristics."
|
|
@@ -273,7 +350,7 @@ export async function lintArtifact(projectRoot, stage) {
|
|
|
273
350
|
const body = hasHeading ? sectionBodyByName(sections, v.section) : null;
|
|
274
351
|
const validation = body === null
|
|
275
352
|
? { ok: false, details: `No ## heading matching required section "${v.section}".` }
|
|
276
|
-
: validateSectionBody(body, v.validationRule);
|
|
353
|
+
: validateSectionBody(body, v.validationRule, v.section);
|
|
277
354
|
const found = hasHeading && validation.ok;
|
|
278
355
|
findings.push({
|
|
279
356
|
section: v.section,
|
|
@@ -384,18 +461,19 @@ export async function validateReviewArmy(projectRoot) {
|
|
|
384
461
|
if (!isStringArray(o.reportedBy) || o.reportedBy.length === 0) {
|
|
385
462
|
errors.push(`findings[${i}].reportedBy must be a non-empty string array.`);
|
|
386
463
|
}
|
|
387
|
-
if (o.location
|
|
388
|
-
|
|
389
|
-
|
|
464
|
+
if (o.location === undefined || o.location === null) {
|
|
465
|
+
errors.push(`findings[${i}].location is required and must be an object with file + line.`);
|
|
466
|
+
}
|
|
467
|
+
else if (typeof o.location !== "object" || Array.isArray(o.location)) {
|
|
468
|
+
errors.push(`findings[${i}].location must be an object with file + line.`);
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
const loc = o.location;
|
|
472
|
+
if (!isNonEmptyString(loc.file)) {
|
|
473
|
+
errors.push(`findings[${i}].location.file must be a non-empty string.`);
|
|
390
474
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
if (!isNonEmptyString(loc.file)) {
|
|
394
|
-
errors.push(`findings[${i}].location.file must be a non-empty string.`);
|
|
395
|
-
}
|
|
396
|
-
if (!isFiniteNumber(loc.line) || loc.line < 1) {
|
|
397
|
-
errors.push(`findings[${i}].location.line must be a positive number.`);
|
|
398
|
-
}
|
|
475
|
+
if (!isFiniteNumber(loc.line) || loc.line < 1) {
|
|
476
|
+
errors.push(`findings[${i}].location.line must be a positive number.`);
|
|
399
477
|
}
|
|
400
478
|
}
|
|
401
479
|
if (o.recommendation !== undefined && !isNonEmptyString(o.recommendation)) {
|
|
@@ -445,6 +523,21 @@ export async function validateReviewArmy(projectRoot) {
|
|
|
445
523
|
for (const msId of rec.multiSpecialistConfirmed) {
|
|
446
524
|
if (!findingIds.has(msId)) {
|
|
447
525
|
errors.push(`reconciliation.multiSpecialistConfirmed references unknown finding id "${msId}".`);
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (Array.isArray(root.findings)) {
|
|
529
|
+
const finding = root.findings.find((f) => {
|
|
530
|
+
return f && typeof f === "object" && !Array.isArray(f) && f.id === msId;
|
|
531
|
+
});
|
|
532
|
+
if (finding && typeof finding === "object" && !Array.isArray(finding)) {
|
|
533
|
+
const reportedBy = finding.reportedBy;
|
|
534
|
+
const count = Array.isArray(reportedBy)
|
|
535
|
+
? new Set(reportedBy.filter((v) => typeof v === "string")).size
|
|
536
|
+
: 0;
|
|
537
|
+
if (count < 2) {
|
|
538
|
+
errors.push(`reconciliation.multiSpecialistConfirmed entry "${msId}" must be confirmed by at least 2 distinct reviewers (found ${count}).`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
448
541
|
}
|
|
449
542
|
}
|
|
450
543
|
}
|
|
@@ -474,3 +567,79 @@ export async function validateReviewArmy(projectRoot) {
|
|
|
474
567
|
}
|
|
475
568
|
return { valid: errors.length === 0, errors };
|
|
476
569
|
}
|
|
570
|
+
/**
|
|
571
|
+
* Ensure the narrative verdict in 07-review.md is consistent with the
|
|
572
|
+
* structured review-army reconciliation. A review cannot declare
|
|
573
|
+
* APPROVED while open Critical findings or shipBlockers remain.
|
|
574
|
+
*/
|
|
575
|
+
export async function checkReviewVerdictConsistency(projectRoot) {
|
|
576
|
+
const errors = [];
|
|
577
|
+
const reviewMdPath = path.join(projectRoot, RUNTIME_ROOT, "artifacts", "07-review.md");
|
|
578
|
+
const armyJsonPath = path.join(projectRoot, RUNTIME_ROOT, "artifacts", "07-review-army.json");
|
|
579
|
+
let finalVerdict = "UNKNOWN";
|
|
580
|
+
if (await exists(reviewMdPath)) {
|
|
581
|
+
const raw = await fs.readFile(reviewMdPath, "utf8");
|
|
582
|
+
const sections = extractH2Sections(raw);
|
|
583
|
+
const verdictBody = sectionBodyByName(sections, "Final Verdict");
|
|
584
|
+
if (verdictBody) {
|
|
585
|
+
const chosen = [];
|
|
586
|
+
for (const token of ["APPROVED_WITH_CONCERNS", "APPROVED", "BLOCKED"]) {
|
|
587
|
+
const regex = new RegExp(`\\b${token}\\b`, "u");
|
|
588
|
+
if (regex.test(verdictBody)) {
|
|
589
|
+
// APPROVED would match inside APPROVED_WITH_CONCERNS; prefer the longer match first.
|
|
590
|
+
if (token === "APPROVED" && /\bAPPROVED_WITH_CONCERNS\b/u.test(verdictBody))
|
|
591
|
+
continue;
|
|
592
|
+
chosen.push(token);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (chosen.length === 1) {
|
|
596
|
+
finalVerdict = chosen[0];
|
|
597
|
+
}
|
|
598
|
+
else if (chosen.length > 1) {
|
|
599
|
+
errors.push(`Final Verdict section lists multiple verdict tokens (${chosen.join(", ")}). Select exactly one.`);
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
errors.push('Final Verdict section does not select APPROVED, APPROVED_WITH_CONCERNS, or BLOCKED.');
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
errors.push('07-review.md is missing the "## Final Verdict" section.');
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
let openCriticalCount = 0;
|
|
610
|
+
let shipBlockerCount = 0;
|
|
611
|
+
if (await exists(armyJsonPath)) {
|
|
612
|
+
try {
|
|
613
|
+
const raw = await fs.readFile(armyJsonPath, "utf8");
|
|
614
|
+
const parsed = JSON.parse(raw);
|
|
615
|
+
const findings = Array.isArray(parsed.findings) ? parsed.findings : [];
|
|
616
|
+
for (const f of findings) {
|
|
617
|
+
if (!f || typeof f !== "object" || Array.isArray(f))
|
|
618
|
+
continue;
|
|
619
|
+
const o = f;
|
|
620
|
+
if (o.severity === "Critical" && o.status === "open") {
|
|
621
|
+
openCriticalCount++;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const rec = parsed.reconciliation && typeof parsed.reconciliation === "object" && !Array.isArray(parsed.reconciliation)
|
|
625
|
+
? parsed.reconciliation
|
|
626
|
+
: null;
|
|
627
|
+
if (rec && Array.isArray(rec.shipBlockers)) {
|
|
628
|
+
shipBlockerCount = rec.shipBlockers.filter((v) => typeof v === "string").length;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
catch {
|
|
632
|
+
// JSON validity is the concern of validateReviewArmy; skip silently here.
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (finalVerdict === "APPROVED" && (openCriticalCount > 0 || shipBlockerCount > 0)) {
|
|
636
|
+
errors.push(`Final Verdict is APPROVED but review-army has ${openCriticalCount} open Critical finding(s) and ${shipBlockerCount} shipBlocker(s). Use BLOCKED or APPROVED_WITH_CONCERNS.`);
|
|
637
|
+
}
|
|
638
|
+
return {
|
|
639
|
+
ok: errors.length === 0,
|
|
640
|
+
errors,
|
|
641
|
+
finalVerdict,
|
|
642
|
+
openCriticalCount,
|
|
643
|
+
shipBlockerCount
|
|
644
|
+
};
|
|
645
|
+
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import type { HarnessId } from "./types.js";
|
|
2
|
+
import type { FlowTrack, HarnessId } from "./types.js";
|
|
3
3
|
type CommandName = "init" | "sync" | "doctor" | "upgrade" | "uninstall" | "archive";
|
|
4
4
|
interface ParsedArgs {
|
|
5
5
|
command?: CommandName;
|
|
6
6
|
harnesses?: HarnessId[];
|
|
7
|
+
track?: FlowTrack;
|
|
7
8
|
reconcileGates?: boolean;
|
|
8
9
|
archiveName?: string;
|
|
9
10
|
showHelp?: boolean;
|
|
@@ -11,5 +12,6 @@ interface ParsedArgs {
|
|
|
11
12
|
}
|
|
12
13
|
export declare function usage(): string;
|
|
13
14
|
declare function parseHarnesses(raw: string): HarnessId[];
|
|
15
|
+
declare function parseTrack(raw: string): FlowTrack;
|
|
14
16
|
declare function parseArgs(argv: string[]): ParsedArgs;
|
|
15
|
-
export { parseArgs, parseHarnesses };
|
|
17
|
+
export { parseArgs, parseHarnesses, parseTrack };
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFileSync, realpathSync } from "node:fs";
|
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { HARNESS_IDS } from "./types.js";
|
|
6
|
+
import { FLOW_TRACKS, HARNESS_IDS } from "./types.js";
|
|
7
7
|
import { doctorChecks, doctorSucceeded } from "./doctor.js";
|
|
8
8
|
import { initCclaw, syncCclaw, uninstallCclaw, upgradeCclaw } from "./install.js";
|
|
9
9
|
import { error, info } from "./logger.js";
|
|
@@ -20,6 +20,7 @@ Usage:
|
|
|
20
20
|
Commands:
|
|
21
21
|
init Bootstrap .cclaw runtime, state, and harness shims in this project.
|
|
22
22
|
Flags: --harnesses=<list> Comma list of harnesses (claude,cursor,opencode,codex).
|
|
23
|
+
--track=<id> Flow track for new runs (standard | quick). Default: standard.
|
|
23
24
|
sync Regenerate harness shim files from the current .cclaw config (non-destructive).
|
|
24
25
|
doctor Run health checks against the local .cclaw runtime. Exit code 2 on failure.
|
|
25
26
|
Flags: --reconcile-gates Recompute current-stage gate evidence before checks.
|
|
@@ -77,6 +78,13 @@ function parseHarnesses(raw) {
|
|
|
77
78
|
}
|
|
78
79
|
return requested;
|
|
79
80
|
}
|
|
81
|
+
function parseTrack(raw) {
|
|
82
|
+
const trimmed = raw.trim();
|
|
83
|
+
if (!FLOW_TRACKS.includes(trimmed)) {
|
|
84
|
+
throw new Error(`Unknown track: ${trimmed}. Supported: ${FLOW_TRACKS.join(", ")}`);
|
|
85
|
+
}
|
|
86
|
+
return trimmed;
|
|
87
|
+
}
|
|
80
88
|
function parseArgs(argv) {
|
|
81
89
|
const parsed = {};
|
|
82
90
|
const helpFlag = argv.find((arg) => arg === "--help" || arg === "-h");
|
|
@@ -96,6 +104,10 @@ function parseArgs(argv) {
|
|
|
96
104
|
parsed.harnesses = parseHarnesses(flag.replace("--harnesses=", ""));
|
|
97
105
|
continue;
|
|
98
106
|
}
|
|
107
|
+
if (flag.startsWith("--track=")) {
|
|
108
|
+
parsed.track = parseTrack(flag.replace("--track=", ""));
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
99
111
|
if (flag === "--reconcile-gates") {
|
|
100
112
|
parsed.reconcileGates = true;
|
|
101
113
|
continue;
|
|
@@ -123,9 +135,11 @@ async function runCommand(parsed, ctx) {
|
|
|
123
135
|
if (command === "init") {
|
|
124
136
|
await initCclaw({
|
|
125
137
|
projectRoot: ctx.cwd,
|
|
126
|
-
harnesses: parsed.harnesses
|
|
138
|
+
harnesses: parsed.harnesses,
|
|
139
|
+
track: parsed.track
|
|
127
140
|
});
|
|
128
|
-
|
|
141
|
+
const trackNote = parsed.track ? ` (track: ${parsed.track})` : "";
|
|
142
|
+
info(ctx, `Initialized .cclaw runtime and generated harness shims${trackNote}`);
|
|
129
143
|
return 0;
|
|
130
144
|
}
|
|
131
145
|
if (command === "sync") {
|
|
@@ -190,4 +204,4 @@ function isDirectExecution() {
|
|
|
190
204
|
if (isDirectExecution()) {
|
|
191
205
|
void main();
|
|
192
206
|
}
|
|
193
|
-
export { parseArgs, parseHarnesses };
|
|
207
|
+
export { parseArgs, parseHarnesses, parseTrack };
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { HarnessId, VibyConfig } from "./types.js";
|
|
1
|
+
import type { FlowTrack, HarnessId, VibyConfig } from "./types.js";
|
|
2
2
|
export declare function configPath(projectRoot: string): string;
|
|
3
|
-
export declare function createDefaultConfig(harnesses?: HarnessId[]): VibyConfig;
|
|
3
|
+
export declare function createDefaultConfig(harnesses?: HarnessId[], defaultTrack?: FlowTrack): VibyConfig;
|
|
4
4
|
export declare function readConfig(projectRoot: string): Promise<VibyConfig>;
|
|
5
5
|
export declare function writeConfig(projectRoot: string, config: VibyConfig): Promise<void>;
|
package/dist/config.js
CHANGED
|
@@ -3,17 +3,20 @@ import path from "node:path";
|
|
|
3
3
|
import { parse, stringify } from "yaml";
|
|
4
4
|
import { CCLAW_VERSION, DEFAULT_HARNESSES, FLOW_VERSION, RUNTIME_ROOT } from "./constants.js";
|
|
5
5
|
import { exists, writeFileSafe } from "./fs-utils.js";
|
|
6
|
-
import { HARNESS_IDS } from "./types.js";
|
|
6
|
+
import { FLOW_TRACKS, HARNESS_IDS } from "./types.js";
|
|
7
7
|
const CONFIG_PATH = `${RUNTIME_ROOT}/config.yaml`;
|
|
8
8
|
const HARNESS_ID_SET = new Set(HARNESS_IDS);
|
|
9
|
+
const FLOW_TRACK_SET = new Set(FLOW_TRACKS);
|
|
9
10
|
const SUPPORTED_HARNESSES_TEXT = HARNESS_IDS.join(", ");
|
|
11
|
+
const SUPPORTED_TRACKS_TEXT = FLOW_TRACKS.join(", ");
|
|
10
12
|
const ALLOWED_CONFIG_KEYS = new Set([
|
|
11
13
|
"version",
|
|
12
14
|
"flowVersion",
|
|
13
15
|
"harnesses",
|
|
14
16
|
"autoAdvance",
|
|
15
17
|
"promptGuardMode",
|
|
16
|
-
"gitHookGuards"
|
|
18
|
+
"gitHookGuards",
|
|
19
|
+
"defaultTrack"
|
|
17
20
|
]);
|
|
18
21
|
function configFixExample() {
|
|
19
22
|
return `harnesses:
|
|
@@ -23,20 +26,22 @@ function configFixExample() {
|
|
|
23
26
|
function configValidationError(configFilePath, reason) {
|
|
24
27
|
return new Error(`Invalid cclaw config at ${configFilePath}: ${reason}\n` +
|
|
25
28
|
`Supported harnesses: ${SUPPORTED_HARNESSES_TEXT}\n` +
|
|
29
|
+
`Supported tracks: ${SUPPORTED_TRACKS_TEXT}\n` +
|
|
26
30
|
`Example config:\n${configFixExample()}\n` +
|
|
27
31
|
`After fixing, run: cclaw sync`);
|
|
28
32
|
}
|
|
29
33
|
export function configPath(projectRoot) {
|
|
30
34
|
return path.join(projectRoot, CONFIG_PATH);
|
|
31
35
|
}
|
|
32
|
-
export function createDefaultConfig(harnesses = DEFAULT_HARNESSES) {
|
|
36
|
+
export function createDefaultConfig(harnesses = DEFAULT_HARNESSES, defaultTrack = "standard") {
|
|
33
37
|
return {
|
|
34
38
|
version: CCLAW_VERSION,
|
|
35
39
|
flowVersion: FLOW_VERSION,
|
|
36
40
|
harnesses,
|
|
37
41
|
autoAdvance: false,
|
|
38
42
|
promptGuardMode: "advisory",
|
|
39
|
-
gitHookGuards: false
|
|
43
|
+
gitHookGuards: false,
|
|
44
|
+
defaultTrack
|
|
40
45
|
};
|
|
41
46
|
}
|
|
42
47
|
export async function readConfig(projectRoot) {
|
|
@@ -89,13 +94,22 @@ export async function readConfig(projectRoot) {
|
|
|
89
94
|
throw configValidationError(fullPath, `"gitHookGuards" must be a boolean`);
|
|
90
95
|
}
|
|
91
96
|
const gitHookGuards = typeof gitHookGuardsRaw === "boolean" ? gitHookGuardsRaw : false;
|
|
97
|
+
const defaultTrackRaw = parsed.defaultTrack;
|
|
98
|
+
if (Object.prototype.hasOwnProperty.call(parsed, "defaultTrack") &&
|
|
99
|
+
(typeof defaultTrackRaw !== "string" || !FLOW_TRACK_SET.has(defaultTrackRaw))) {
|
|
100
|
+
throw configValidationError(fullPath, `"defaultTrack" must be one of: ${SUPPORTED_TRACKS_TEXT}`);
|
|
101
|
+
}
|
|
102
|
+
const defaultTrack = typeof defaultTrackRaw === "string" && FLOW_TRACK_SET.has(defaultTrackRaw)
|
|
103
|
+
? defaultTrackRaw
|
|
104
|
+
: "standard";
|
|
92
105
|
return {
|
|
93
106
|
version: parsed.version ?? CCLAW_VERSION,
|
|
94
107
|
flowVersion: parsed.flowVersion ?? FLOW_VERSION,
|
|
95
108
|
harnesses,
|
|
96
109
|
autoAdvance,
|
|
97
110
|
promptGuardMode,
|
|
98
|
-
gitHookGuards
|
|
111
|
+
gitHookGuards,
|
|
112
|
+
defaultTrack
|
|
99
113
|
};
|
|
100
114
|
}
|
|
101
115
|
export async function writeConfig(projectRoot, config) {
|
package/dist/constants.d.ts
CHANGED
|
@@ -4,9 +4,9 @@ export declare const RUNTIME_ROOT = ".cclaw";
|
|
|
4
4
|
export declare const CCLAW_VERSION = "0.1.1";
|
|
5
5
|
export declare const FLOW_VERSION = "1.0.0";
|
|
6
6
|
export declare const DEFAULT_HARNESSES: HarnessId[];
|
|
7
|
-
export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/contexts", ".cclaw/templates", ".cclaw/artifacts", ".cclaw/state", ".cclaw/runs", ".cclaw/rules", ".cclaw/adapters", ".cclaw/agents", ".cclaw/hooks"];
|
|
7
|
+
export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/contexts", ".cclaw/templates", ".cclaw/artifacts", ".cclaw/state", ".cclaw/runs", ".cclaw/rules", ".cclaw/adapters", ".cclaw/agents", ".cclaw/hooks", ".cclaw/custom-skills"];
|
|
8
8
|
export declare const REQUIRED_GITIGNORE_PATTERNS: readonly ["# cclaw generated artifacts", ".cclaw/", ".claude/commands/cc-*.md", ".claude/commands/cc.md", ".cursor/commands/cc-*.md", ".cursor/commands/cc.md", ".opencode/commands/cc-*.md", ".opencode/commands/cc.md", ".codex/commands/cc-*.md", ".codex/commands/cc.md", ".claude/hooks/hooks.json", ".cursor/hooks.json", ".codex/hooks.json", ".opencode/plugins/cclaw-plugin.mjs", ".cursor/rules/cclaw-workflow.mdc"];
|
|
9
9
|
export declare const COMMAND_FILE_ORDER: FlowStage[];
|
|
10
|
-
export declare const UTILITY_COMMANDS: readonly ["learn", "next"];
|
|
10
|
+
export declare const UTILITY_COMMANDS: readonly ["learn", "next", "status"];
|
|
11
11
|
export declare const SUBAGENT_SKILL_FOLDERS: readonly ["subagent-dev", "parallel-dispatch"];
|
|
12
12
|
export type UtilityCommand = (typeof UTILITY_COMMANDS)[number];
|
package/dist/constants.js
CHANGED
|
@@ -20,7 +20,8 @@ export const REQUIRED_DIRS = [
|
|
|
20
20
|
`${RUNTIME_ROOT}/rules`,
|
|
21
21
|
`${RUNTIME_ROOT}/adapters`,
|
|
22
22
|
`${RUNTIME_ROOT}/agents`,
|
|
23
|
-
`${RUNTIME_ROOT}/hooks
|
|
23
|
+
`${RUNTIME_ROOT}/hooks`,
|
|
24
|
+
`${RUNTIME_ROOT}/custom-skills`
|
|
24
25
|
];
|
|
25
26
|
export const REQUIRED_GITIGNORE_PATTERNS = [
|
|
26
27
|
"# cclaw generated artifacts",
|
|
@@ -49,7 +50,7 @@ export const COMMAND_FILE_ORDER = [
|
|
|
49
50
|
"review",
|
|
50
51
|
"ship"
|
|
51
52
|
];
|
|
52
|
-
export const UTILITY_COMMANDS = ["learn", "next"];
|
|
53
|
+
export const UTILITY_COMMANDS = ["learn", "next", "status"];
|
|
53
54
|
export const SUBAGENT_SKILL_FOLDERS = [
|
|
54
55
|
"subagent-dev",
|
|
55
56
|
"parallel-dispatch"
|
package/dist/content/agents.js
CHANGED
|
@@ -94,10 +94,10 @@ export const CCLAW_AGENTS = [
|
|
|
94
94
|
},
|
|
95
95
|
{
|
|
96
96
|
name: "security-reviewer",
|
|
97
|
-
description: "
|
|
97
|
+
description: "MANDATORY during every review stage. Even when no auth, crypto, secrets, parsers, or sensitive data paths changed, produce an explicit 'no-change' security attestation. MUST BE USED when trust boundaries move, new external inputs arrive, or LLM/tool output influences privileged actions.",
|
|
98
98
|
tools: ["Read", "Grep", "Glob"],
|
|
99
99
|
model: "balanced",
|
|
100
|
-
activation: "
|
|
100
|
+
activation: "mandatory",
|
|
101
101
|
relatedStages: ["review", "design"],
|
|
102
102
|
body: [
|
|
103
103
|
"You are a **security vulnerability detection** specialist focused on practical exploitability.",
|