cclaw-cli 0.10.1 → 0.12.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 +4 -3
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +297 -9
- package/dist/config.js +83 -3
- package/dist/content/core-agents.d.ts +44 -0
- package/dist/content/core-agents.js +225 -0
- package/dist/content/doctor-references.d.ts +2 -0
- package/dist/content/doctor-references.js +144 -0
- package/dist/content/examples.js +1 -1
- package/dist/content/harnesses-doc.d.ts +1 -0
- package/dist/content/harnesses-doc.js +95 -0
- package/dist/content/hook-events.d.ts +4 -0
- package/dist/content/hook-events.js +42 -0
- package/dist/content/hooks.js +81 -11
- package/dist/content/meta-skill.d.ts +0 -8
- package/dist/content/meta-skill.js +51 -341
- package/dist/content/next-command.js +2 -1
- package/dist/content/protocols.d.ts +7 -0
- package/dist/content/protocols.js +123 -0
- package/dist/content/research-playbooks.d.ts +8 -0
- package/dist/content/research-playbooks.js +135 -0
- package/dist/content/skills.js +202 -312
- package/dist/content/stage-common-guidance.d.ts +2 -0
- package/dist/content/stage-common-guidance.js +71 -0
- package/dist/content/stage-schema.d.ts +11 -1
- package/dist/content/stage-schema.js +155 -52
- package/dist/content/start-command.js +19 -13
- package/dist/content/subagents.d.ts +1 -1
- package/dist/content/subagents.js +23 -38
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +49 -11
- package/dist/delegation.d.ts +1 -0
- package/dist/delegation.js +27 -1
- package/dist/doctor-registry.d.ts +8 -0
- package/dist/doctor-registry.js +127 -0
- package/dist/doctor.d.ts +5 -0
- package/dist/doctor.js +133 -27
- package/dist/flow-state.d.ts +4 -0
- package/dist/flow-state.js +4 -1
- package/dist/gate-evidence.d.ts +9 -1
- package/dist/gate-evidence.js +121 -17
- package/dist/harness-adapters.d.ts +7 -0
- package/dist/harness-adapters.js +53 -9
- package/dist/init-detect.d.ts +2 -0
- package/dist/init-detect.js +45 -0
- package/dist/install.js +73 -1
- package/dist/policy.js +21 -13
- package/dist/runs.js +21 -4
- package/dist/track-heuristics.d.ts +12 -0
- package/dist/track-heuristics.js +144 -0
- package/dist/types.d.ts +26 -3
- package/dist/types.js +6 -3
- package/package.json +2 -1
- package/dist/content/agents.d.ts +0 -48
- package/dist/content/agents.js +0 -411
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ sequenceDiagram
|
|
|
41
41
|
- **Low cognitive load:** one canonical stage flow instead of dozens of competing paths.
|
|
42
42
|
- **Installer-first architecture:** generates files and hooks; does not run a hidden control plane.
|
|
43
43
|
- **Hard-gated quality:** each stage has non-skippable constraints that reduce AI drift.
|
|
44
|
-
- **
|
|
44
|
+
- **Tiered harness coverage:** transparent capability tiers across Claude Code, Cursor, Codex, OpenCode.
|
|
45
45
|
- **Compounding context:** flow state + project knowledge get rehydrated on new sessions automatically.
|
|
46
46
|
- **Incremental delivery:** active artifacts stay in one place; `cclaw archive` snapshots completed features into dated run folders.
|
|
47
47
|
|
|
@@ -114,16 +114,17 @@ Required repository secret:
|
|
|
114
114
|
├── commands/
|
|
115
115
|
├── hooks/
|
|
116
116
|
├── templates/
|
|
117
|
+
├── references/
|
|
117
118
|
├── artifacts/ # active feature artifacts
|
|
118
119
|
├── state/
|
|
119
|
-
├── knowledge.
|
|
120
|
+
├── knowledge.jsonl # append-only strict-schema rule/pattern/lesson log
|
|
120
121
|
└── runs/ # archived feature snapshots (YYYY-MM-DD-feature-name)
|
|
121
122
|
```
|
|
122
123
|
|
|
123
124
|
## Harness Integration
|
|
124
125
|
|
|
125
126
|
Supported harnesses: `claude`, `cursor`, `opencode`, `codex`. The full
|
|
126
|
-
per-harness
|
|
127
|
+
per-harness tier/capability matrix, install surface, and lifecycle details live in
|
|
127
128
|
[docs/harnesses.md](./docs/harnesses.md).
|
|
128
129
|
|
|
129
130
|
## License
|
package/dist/cli.d.ts
CHANGED
|
@@ -6,7 +6,13 @@ interface ParsedArgs {
|
|
|
6
6
|
harnesses?: HarnessId[];
|
|
7
7
|
track?: FlowTrack;
|
|
8
8
|
profile?: InitProfile;
|
|
9
|
+
dryRun?: boolean;
|
|
10
|
+
interactive?: boolean;
|
|
9
11
|
reconcileGates?: boolean;
|
|
12
|
+
doctorJson?: boolean;
|
|
13
|
+
doctorExplain?: boolean;
|
|
14
|
+
doctorQuiet?: boolean;
|
|
15
|
+
doctorOnly?: string[];
|
|
10
16
|
archiveName?: string;
|
|
11
17
|
showHelp?: boolean;
|
|
12
18
|
showVersion?: boolean;
|
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { readFileSync, realpathSync } from "node:fs";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { createInterface } from "node:readline/promises";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { FLOW_TRACKS, HARNESS_IDS, INIT_PROFILES } from "./types.js";
|
|
7
8
|
import { doctorChecks, doctorSucceeded } from "./doctor.js";
|
|
@@ -9,6 +10,9 @@ import { initCclaw, syncCclaw, uninstallCclaw, upgradeCclaw } from "./install.js
|
|
|
9
10
|
import { error, info } from "./logger.js";
|
|
10
11
|
import { archiveRun } from "./runs.js";
|
|
11
12
|
import { RUNTIME_ROOT } from "./constants.js";
|
|
13
|
+
import { createDefaultConfig, createProfileConfig } from "./config.js";
|
|
14
|
+
import { detectHarnesses } from "./init-detect.js";
|
|
15
|
+
import { HARNESS_ADAPTERS } from "./harness-adapters.js";
|
|
12
16
|
const INSTALLER_COMMANDS = ["init", "sync", "doctor", "upgrade", "uninstall", "archive"];
|
|
13
17
|
export function usage() {
|
|
14
18
|
return `cclaw - installer-first flow toolkit
|
|
@@ -22,10 +26,17 @@ Commands:
|
|
|
22
26
|
init Bootstrap .cclaw runtime, state, and harness shims in this project.
|
|
23
27
|
Flags: --profile=<id> Pre-fill defaults. One of: minimal | standard | full. Default: standard.
|
|
24
28
|
--harnesses=<list> Comma list of harnesses (claude,cursor,opencode,codex). Overrides the profile default.
|
|
25
|
-
--track=<id> Flow track for new runs (standard | quick). Overrides the profile default.
|
|
29
|
+
--track=<id> Flow track for new runs (standard | medium | quick). Overrides the profile default.
|
|
30
|
+
--interactive Force interactive prompts (TTY only).
|
|
31
|
+
--no-interactive Skip interactive prompts even on TTY.
|
|
32
|
+
--dry-run Print resolved config + generated surfaces without writing files.
|
|
26
33
|
sync Regenerate harness shim files from the current .cclaw config (non-destructive).
|
|
27
|
-
doctor Run health checks against the local .cclaw runtime. Exit code 2
|
|
34
|
+
doctor Run health checks against the local .cclaw runtime. Exit code 2 when any error-severity check fails.
|
|
28
35
|
Flags: --reconcile-gates Recompute current-stage gate evidence before checks.
|
|
36
|
+
--json Emit machine-readable JSON output.
|
|
37
|
+
--only=<filter> Comma list of severities/check-name filters (error,warning,info,trace:,hook:...).
|
|
38
|
+
--explain Include fix + doc reference per check in text mode.
|
|
39
|
+
--quiet Print only failing checks (and totals).
|
|
29
40
|
archive Move .cclaw/artifacts into .cclaw/runs/<date>-<slug> and reset flow state.
|
|
30
41
|
Flags: --name=<feature> Feature slug (default: inferred from 00-idea.md).
|
|
31
42
|
upgrade Refresh generated files in .cclaw without modifying user artifacts.
|
|
@@ -94,6 +105,208 @@ function parseProfile(raw) {
|
|
|
94
105
|
}
|
|
95
106
|
return trimmed;
|
|
96
107
|
}
|
|
108
|
+
function isInitPromptAllowed(ctx) {
|
|
109
|
+
return Boolean(process.stdin.isTTY && ctx.stdout.isTTY);
|
|
110
|
+
}
|
|
111
|
+
function buildInitSurfacePreview(harnesses) {
|
|
112
|
+
const lines = [
|
|
113
|
+
".cclaw/config.yaml",
|
|
114
|
+
".cclaw/commands/*.md",
|
|
115
|
+
".cclaw/skills/*/SKILL.md",
|
|
116
|
+
".cclaw/state/*.json|*.jsonl",
|
|
117
|
+
".cclaw/references/**",
|
|
118
|
+
"AGENTS.md (managed block)"
|
|
119
|
+
];
|
|
120
|
+
for (const harness of harnesses) {
|
|
121
|
+
const adapter = HARNESS_ADAPTERS[harness];
|
|
122
|
+
lines.push(`${adapter.commandDir}/cc*.md`);
|
|
123
|
+
if (harness === "claude") {
|
|
124
|
+
lines.push(".claude/hooks/hooks.json");
|
|
125
|
+
}
|
|
126
|
+
if (harness === "cursor") {
|
|
127
|
+
lines.push(".cursor/hooks.json");
|
|
128
|
+
lines.push(".cursor/rules/cclaw-workflow.mdc");
|
|
129
|
+
}
|
|
130
|
+
if (harness === "codex") {
|
|
131
|
+
lines.push(".codex/hooks.json");
|
|
132
|
+
}
|
|
133
|
+
if (harness === "opencode") {
|
|
134
|
+
lines.push(".opencode/plugins/cclaw-plugin.mjs");
|
|
135
|
+
lines.push("opencode.json(.c) plugin registration");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return lines;
|
|
139
|
+
}
|
|
140
|
+
function inferTrackDefault(profile, track) {
|
|
141
|
+
if (track)
|
|
142
|
+
return track;
|
|
143
|
+
if (!profile)
|
|
144
|
+
return "standard";
|
|
145
|
+
return createProfileConfig(profile).defaultTrack ?? "standard";
|
|
146
|
+
}
|
|
147
|
+
async function promptInitConfig(defaults, ctx) {
|
|
148
|
+
const rl = createInterface({
|
|
149
|
+
input: process.stdin,
|
|
150
|
+
output: ctx.stdout
|
|
151
|
+
});
|
|
152
|
+
const pickSingle = async (label, options, fallback) => {
|
|
153
|
+
while (true) {
|
|
154
|
+
ctx.stdout.write(`\n${label}\n`);
|
|
155
|
+
options.forEach((option, index) => {
|
|
156
|
+
const marker = option === fallback ? " (default)" : "";
|
|
157
|
+
ctx.stdout.write(` ${index + 1}) ${option}${marker}\n`);
|
|
158
|
+
});
|
|
159
|
+
const answer = (await rl.question("> ")).trim();
|
|
160
|
+
if (answer.length === 0) {
|
|
161
|
+
return fallback;
|
|
162
|
+
}
|
|
163
|
+
const numeric = Number(answer);
|
|
164
|
+
if (Number.isInteger(numeric) && numeric >= 1 && numeric <= options.length) {
|
|
165
|
+
return options[numeric - 1];
|
|
166
|
+
}
|
|
167
|
+
if (options.includes(answer)) {
|
|
168
|
+
return answer;
|
|
169
|
+
}
|
|
170
|
+
ctx.stdout.write("Invalid selection. Use option number or value.\n");
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const pickHarnesses = async (fallback) => {
|
|
174
|
+
const fallbackText = fallback.join(",");
|
|
175
|
+
while (true) {
|
|
176
|
+
const answer = (await rl.question(`\nHarnesses (comma list from ${HARNESS_IDS.join(", ")}) [${fallbackText}]: `)).trim();
|
|
177
|
+
if (answer.length === 0) {
|
|
178
|
+
return fallback;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const parsed = parseHarnesses(answer);
|
|
182
|
+
if (parsed.length === 0) {
|
|
183
|
+
ctx.stdout.write("Select at least one harness.\n");
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
return parsed;
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
ctx.stdout.write(`${err instanceof Error ? err.message : "Invalid harness list"}\n`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
try {
|
|
194
|
+
const profile = await pickSingle("Select init profile:", INIT_PROFILES, defaults.profile);
|
|
195
|
+
const trackDefault = inferTrackDefault(profile, defaults.track);
|
|
196
|
+
const track = await pickSingle("Select default flow track:", FLOW_TRACKS, trackDefault);
|
|
197
|
+
const harnesses = await pickHarnesses(defaults.harnesses);
|
|
198
|
+
return { profile, track, harnesses };
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
rl.close();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async function resolveInitInputs(parsed, ctx) {
|
|
205
|
+
const detectedHarnesses = parsed.harnesses ? [] : await detectHarnesses(ctx.cwd);
|
|
206
|
+
const autoHarnesses = parsed.harnesses
|
|
207
|
+
? parsed.harnesses
|
|
208
|
+
: (detectedHarnesses.length > 0 ? detectedHarnesses : undefined);
|
|
209
|
+
const promptRequested = parsed.interactive === true;
|
|
210
|
+
const promptForbidden = parsed.interactive === false;
|
|
211
|
+
const implicitPrompt = !promptForbidden &&
|
|
212
|
+
isInitPromptAllowed(ctx) &&
|
|
213
|
+
parsed.profile === undefined &&
|
|
214
|
+
parsed.track === undefined &&
|
|
215
|
+
parsed.harnesses === undefined;
|
|
216
|
+
const shouldPrompt = promptRequested || implicitPrompt;
|
|
217
|
+
if (!shouldPrompt) {
|
|
218
|
+
return {
|
|
219
|
+
profile: parsed.profile,
|
|
220
|
+
track: parsed.track,
|
|
221
|
+
harnesses: autoHarnesses,
|
|
222
|
+
detectedHarnesses
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
if (!isInitPromptAllowed(ctx)) {
|
|
226
|
+
throw new Error("Interactive init requires a TTY. Remove --interactive or run in a terminal.");
|
|
227
|
+
}
|
|
228
|
+
const defaults = {
|
|
229
|
+
profile: parsed.profile ?? "standard",
|
|
230
|
+
track: inferTrackDefault(parsed.profile, parsed.track),
|
|
231
|
+
harnesses: autoHarnesses ?? HARNESS_IDS.slice()
|
|
232
|
+
};
|
|
233
|
+
const prompted = await promptInitConfig(defaults, ctx);
|
|
234
|
+
return {
|
|
235
|
+
profile: prompted.profile,
|
|
236
|
+
track: prompted.track,
|
|
237
|
+
harnesses: prompted.harnesses,
|
|
238
|
+
detectedHarnesses
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function parseDoctorOnly(raw) {
|
|
242
|
+
return raw
|
|
243
|
+
.split(",")
|
|
244
|
+
.map((item) => item.trim().toLowerCase())
|
|
245
|
+
.filter((item) => item.length > 0);
|
|
246
|
+
}
|
|
247
|
+
function filterDoctorChecks(checks, filters) {
|
|
248
|
+
if (!filters || filters.length === 0) {
|
|
249
|
+
return checks;
|
|
250
|
+
}
|
|
251
|
+
return checks.filter((check) => {
|
|
252
|
+
const name = check.name.toLowerCase();
|
|
253
|
+
return filters.some((filter) => {
|
|
254
|
+
if (filter === "error" || filter === "warning" || filter === "info") {
|
|
255
|
+
return check.severity === filter;
|
|
256
|
+
}
|
|
257
|
+
return name.includes(filter);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
function doctorCountsBySeverity(checks) {
|
|
262
|
+
const result = {
|
|
263
|
+
error: { total: 0, failing: 0 },
|
|
264
|
+
warning: { total: 0, failing: 0 },
|
|
265
|
+
info: { total: 0, failing: 0 }
|
|
266
|
+
};
|
|
267
|
+
for (const check of checks) {
|
|
268
|
+
const bucket = result[check.severity];
|
|
269
|
+
bucket.total += 1;
|
|
270
|
+
if (!check.ok) {
|
|
271
|
+
bucket.failing += 1;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
276
|
+
function printDoctorText(ctx, checks, options) {
|
|
277
|
+
const orderedSeverities = ["error", "warning", "info"];
|
|
278
|
+
const view = options.quiet ? checks.filter((check) => !check.ok) : checks;
|
|
279
|
+
for (const severity of orderedSeverities) {
|
|
280
|
+
const inBucket = view.filter((check) => check.severity === severity);
|
|
281
|
+
if (inBucket.length === 0)
|
|
282
|
+
continue;
|
|
283
|
+
ctx.stdout.write(`\n[${severity.toUpperCase()}]\n`);
|
|
284
|
+
for (const check of inBucket) {
|
|
285
|
+
const status = check.ok ? "PASS" : "FAIL";
|
|
286
|
+
ctx.stdout.write(`${status} ${check.name} :: ${check.summary}\n`);
|
|
287
|
+
if (!options.quiet) {
|
|
288
|
+
ctx.stdout.write(` details: ${check.details}\n`);
|
|
289
|
+
}
|
|
290
|
+
if (options.explain) {
|
|
291
|
+
ctx.stdout.write(` fix: ${check.fix}\n`);
|
|
292
|
+
if (check.docRef) {
|
|
293
|
+
ctx.stdout.write(` docs: ${check.docRef}\n`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const counts = doctorCountsBySeverity(checks);
|
|
299
|
+
const failingErrors = checks.filter((check) => check.severity === "error" && !check.ok).length;
|
|
300
|
+
ctx.stdout.write(`\nTotals: error ${counts.error.failing}/${counts.error.total} failing, ` +
|
|
301
|
+
`warning ${counts.warning.failing}/${counts.warning.total} failing, ` +
|
|
302
|
+
`info ${counts.info.failing}/${counts.info.total} failing\n`);
|
|
303
|
+
if (failingErrors > 0) {
|
|
304
|
+
ctx.stdout.write(`Doctor status: BLOCKED (${failingErrors} failing error checks)\n`);
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
ctx.stdout.write("Doctor status: HEALTHY (no failing error checks)\n");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
97
310
|
function parseArgs(argv) {
|
|
98
311
|
const parsed = {};
|
|
99
312
|
const helpFlag = argv.find((arg) => arg === "--help" || arg === "-h");
|
|
@@ -121,10 +334,38 @@ function parseArgs(argv) {
|
|
|
121
334
|
parsed.profile = parseProfile(flag.replace("--profile=", ""));
|
|
122
335
|
continue;
|
|
123
336
|
}
|
|
337
|
+
if (flag === "--interactive") {
|
|
338
|
+
parsed.interactive = true;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (flag === "--no-interactive") {
|
|
342
|
+
parsed.interactive = false;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (flag === "--dry-run") {
|
|
346
|
+
parsed.dryRun = true;
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
124
349
|
if (flag === "--reconcile-gates") {
|
|
125
350
|
parsed.reconcileGates = true;
|
|
126
351
|
continue;
|
|
127
352
|
}
|
|
353
|
+
if (flag === "--json") {
|
|
354
|
+
parsed.doctorJson = true;
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
if (flag === "--explain") {
|
|
358
|
+
parsed.doctorExplain = true;
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
if (flag === "--quiet") {
|
|
362
|
+
parsed.doctorQuiet = true;
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (flag.startsWith("--only=")) {
|
|
366
|
+
parsed.doctorOnly = parseDoctorOnly(flag.replace("--only=", ""));
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
128
369
|
if (flag.startsWith("--name=")) {
|
|
129
370
|
parsed.archiveName = flag.replace("--name=", "").trim();
|
|
130
371
|
}
|
|
@@ -146,14 +387,44 @@ async function runCommand(parsed, ctx) {
|
|
|
146
387
|
return 1;
|
|
147
388
|
}
|
|
148
389
|
if (command === "init") {
|
|
390
|
+
const resolved = await resolveInitInputs(parsed, ctx);
|
|
391
|
+
const effectiveProfile = resolved.profile;
|
|
392
|
+
const effectiveTrack = resolved.track;
|
|
393
|
+
const effectiveHarnesses = resolved.harnesses;
|
|
394
|
+
if (parsed.dryRun === true) {
|
|
395
|
+
const previewConfig = effectiveProfile
|
|
396
|
+
? createProfileConfig(effectiveProfile, {
|
|
397
|
+
harnesses: effectiveHarnesses,
|
|
398
|
+
defaultTrack: effectiveTrack
|
|
399
|
+
})
|
|
400
|
+
: createDefaultConfig(effectiveHarnesses, effectiveTrack);
|
|
401
|
+
const previewSurfaces = buildInitSurfacePreview(previewConfig.harnesses);
|
|
402
|
+
info(ctx, "Dry run: no files were written.");
|
|
403
|
+
if (resolved.detectedHarnesses.length > 0 && parsed.harnesses === undefined) {
|
|
404
|
+
info(ctx, `Detected harnesses from repo: ${resolved.detectedHarnesses.join(", ")}`);
|
|
405
|
+
}
|
|
406
|
+
ctx.stdout.write(`${JSON.stringify({
|
|
407
|
+
profile: effectiveProfile ?? "standard(default)",
|
|
408
|
+
track: previewConfig.defaultTrack ?? "standard",
|
|
409
|
+
harnesses: previewConfig.harnesses,
|
|
410
|
+
promptGuardMode: previewConfig.promptGuardMode,
|
|
411
|
+
gitHookGuards: previewConfig.gitHookGuards,
|
|
412
|
+
languageRulePacks: previewConfig.languageRulePacks,
|
|
413
|
+
generatedSurfaces: previewSurfaces
|
|
414
|
+
}, null, 2)}\n`);
|
|
415
|
+
return 0;
|
|
416
|
+
}
|
|
149
417
|
await initCclaw({
|
|
150
418
|
projectRoot: ctx.cwd,
|
|
151
|
-
harnesses:
|
|
152
|
-
track:
|
|
153
|
-
profile:
|
|
419
|
+
harnesses: effectiveHarnesses,
|
|
420
|
+
track: effectiveTrack,
|
|
421
|
+
profile: effectiveProfile
|
|
154
422
|
});
|
|
155
|
-
|
|
156
|
-
|
|
423
|
+
if (resolved.detectedHarnesses.length > 0 && parsed.harnesses === undefined) {
|
|
424
|
+
info(ctx, `Detected harnesses from repo: ${resolved.detectedHarnesses.join(", ")}`);
|
|
425
|
+
}
|
|
426
|
+
const profileNote = effectiveProfile ? ` profile=${effectiveProfile}` : "";
|
|
427
|
+
const trackNote = effectiveTrack ? ` track=${effectiveTrack}` : "";
|
|
157
428
|
const suffix = profileNote || trackNote ? ` (${(profileNote + trackNote).trim()})` : "";
|
|
158
429
|
info(ctx, `Initialized .cclaw runtime and generated harness shims${suffix}`);
|
|
159
430
|
return 0;
|
|
@@ -167,8 +438,25 @@ async function runCommand(parsed, ctx) {
|
|
|
167
438
|
const checks = await doctorChecks(ctx.cwd, {
|
|
168
439
|
reconcileCurrentStageGates: parsed.reconcileGates === true
|
|
169
440
|
});
|
|
170
|
-
|
|
171
|
-
|
|
441
|
+
const filteredChecks = filterDoctorChecks(checks, parsed.doctorOnly);
|
|
442
|
+
const explain = parsed.doctorExplain === true;
|
|
443
|
+
const quiet = parsed.doctorQuiet === true;
|
|
444
|
+
if (parsed.doctorJson === true) {
|
|
445
|
+
const counts = doctorCountsBySeverity(filteredChecks);
|
|
446
|
+
ctx.stdout.write(`${JSON.stringify({
|
|
447
|
+
ok: doctorSucceeded(checks),
|
|
448
|
+
filters: parsed.doctorOnly ?? [],
|
|
449
|
+
counts,
|
|
450
|
+
checks: filteredChecks
|
|
451
|
+
}, null, 2)}\n`);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
if (filteredChecks.length === 0) {
|
|
455
|
+
ctx.stdout.write("No checks matched the --only filter.\n");
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
printDoctorText(ctx, filteredChecks, { explain, quiet });
|
|
459
|
+
}
|
|
172
460
|
}
|
|
173
461
|
return doctorSucceeded(checks) ? 0 : 2;
|
|
174
462
|
}
|
package/dist/config.js
CHANGED
|
@@ -19,7 +19,8 @@ const ALLOWED_CONFIG_KEYS = new Set([
|
|
|
19
19
|
"promptGuardMode",
|
|
20
20
|
"gitHookGuards",
|
|
21
21
|
"defaultTrack",
|
|
22
|
-
"languageRulePacks"
|
|
22
|
+
"languageRulePacks",
|
|
23
|
+
"trackHeuristics"
|
|
23
24
|
]);
|
|
24
25
|
function configFixExample() {
|
|
25
26
|
return `harnesses:
|
|
@@ -34,6 +35,21 @@ function configValidationError(configFilePath, reason) {
|
|
|
34
35
|
`Example config:\n${configFixExample()}\n` +
|
|
35
36
|
`After fixing, run: cclaw sync`);
|
|
36
37
|
}
|
|
38
|
+
function isRecord(value) {
|
|
39
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40
|
+
}
|
|
41
|
+
function validateStringArray(value, fieldName, configFilePath) {
|
|
42
|
+
if (value === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
if (!Array.isArray(value)) {
|
|
45
|
+
throw configValidationError(configFilePath, `"${fieldName}" must be an array of strings`);
|
|
46
|
+
}
|
|
47
|
+
const invalid = value.filter((item) => typeof item !== "string");
|
|
48
|
+
if (invalid.length > 0) {
|
|
49
|
+
throw configValidationError(configFilePath, `"${fieldName}" must contain only strings`);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
37
53
|
export function configPath(projectRoot) {
|
|
38
54
|
return path.join(projectRoot, CONFIG_PATH);
|
|
39
55
|
}
|
|
@@ -64,7 +80,7 @@ export function createProfileConfig(profile, overrides = {}) {
|
|
|
64
80
|
autoAdvance: false,
|
|
65
81
|
promptGuardMode: "advisory",
|
|
66
82
|
gitHookGuards: false,
|
|
67
|
-
defaultTrack: overrides.defaultTrack ?? "
|
|
83
|
+
defaultTrack: overrides.defaultTrack ?? "medium",
|
|
68
84
|
languageRulePacks: overrides.languageRulePacks ?? []
|
|
69
85
|
};
|
|
70
86
|
case "standard":
|
|
@@ -161,6 +177,69 @@ export async function readConfig(projectRoot) {
|
|
|
161
177
|
throw configValidationError(fullPath, `unknown languageRulePacks id(s): ${formatted}`);
|
|
162
178
|
}
|
|
163
179
|
const languageRulePacks = [...new Set(rawPacks)];
|
|
180
|
+
const trackHeuristicsRaw = parsed.trackHeuristics;
|
|
181
|
+
let trackHeuristics = undefined;
|
|
182
|
+
if (Object.prototype.hasOwnProperty.call(parsed, "trackHeuristics")) {
|
|
183
|
+
if (!isRecord(trackHeuristicsRaw)) {
|
|
184
|
+
throw configValidationError(fullPath, `"trackHeuristics" must be an object`);
|
|
185
|
+
}
|
|
186
|
+
const fallbackRaw = trackHeuristicsRaw.fallback;
|
|
187
|
+
if (fallbackRaw !== undefined && (typeof fallbackRaw !== "string" || !FLOW_TRACK_SET.has(fallbackRaw))) {
|
|
188
|
+
throw configValidationError(fullPath, `"trackHeuristics.fallback" must be one of: ${SUPPORTED_TRACKS_TEXT}`);
|
|
189
|
+
}
|
|
190
|
+
const priorityRaw = trackHeuristicsRaw.priority;
|
|
191
|
+
let priority;
|
|
192
|
+
if (priorityRaw !== undefined) {
|
|
193
|
+
if (!Array.isArray(priorityRaw)) {
|
|
194
|
+
throw configValidationError(fullPath, `"trackHeuristics.priority" must be an array`);
|
|
195
|
+
}
|
|
196
|
+
const invalidPriority = priorityRaw.filter((value) => typeof value !== "string" || !FLOW_TRACK_SET.has(value));
|
|
197
|
+
if (invalidPriority.length > 0) {
|
|
198
|
+
throw configValidationError(fullPath, `"trackHeuristics.priority" must contain only: ${SUPPORTED_TRACKS_TEXT}`);
|
|
199
|
+
}
|
|
200
|
+
priority = [...new Set(priorityRaw)];
|
|
201
|
+
}
|
|
202
|
+
const tracksRaw = trackHeuristicsRaw.tracks;
|
|
203
|
+
let tracks = undefined;
|
|
204
|
+
if (tracksRaw !== undefined) {
|
|
205
|
+
if (!isRecord(tracksRaw)) {
|
|
206
|
+
throw configValidationError(fullPath, `"trackHeuristics.tracks" must be an object`);
|
|
207
|
+
}
|
|
208
|
+
tracks = {};
|
|
209
|
+
for (const [trackName, ruleRaw] of Object.entries(tracksRaw)) {
|
|
210
|
+
if (!FLOW_TRACK_SET.has(trackName)) {
|
|
211
|
+
throw configValidationError(fullPath, `"trackHeuristics.tracks" contains unknown track "${trackName}". Supported: ${SUPPORTED_TRACKS_TEXT}`);
|
|
212
|
+
}
|
|
213
|
+
if (!isRecord(ruleRaw)) {
|
|
214
|
+
throw configValidationError(fullPath, `"trackHeuristics.tracks.${trackName}" must be an object`);
|
|
215
|
+
}
|
|
216
|
+
const triggers = validateStringArray(ruleRaw.triggers, `trackHeuristics.tracks.${trackName}.triggers`, fullPath);
|
|
217
|
+
const patterns = validateStringArray(ruleRaw.patterns, `trackHeuristics.tracks.${trackName}.patterns`, fullPath);
|
|
218
|
+
const veto = validateStringArray(ruleRaw.veto, `trackHeuristics.tracks.${trackName}.veto`, fullPath);
|
|
219
|
+
if (patterns) {
|
|
220
|
+
for (const pattern of patterns) {
|
|
221
|
+
try {
|
|
222
|
+
// eslint-disable-next-line no-new
|
|
223
|
+
new RegExp(pattern, "iu");
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
throw configValidationError(fullPath, `"trackHeuristics.tracks.${trackName}.patterns" contains invalid regex "${pattern}"`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
tracks[trackName] = {
|
|
231
|
+
triggers,
|
|
232
|
+
patterns,
|
|
233
|
+
veto
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
trackHeuristics = {
|
|
238
|
+
fallback: fallbackRaw,
|
|
239
|
+
priority,
|
|
240
|
+
tracks
|
|
241
|
+
};
|
|
242
|
+
}
|
|
164
243
|
return {
|
|
165
244
|
version: parsed.version ?? CCLAW_VERSION,
|
|
166
245
|
flowVersion: parsed.flowVersion ?? FLOW_VERSION,
|
|
@@ -169,7 +248,8 @@ export async function readConfig(projectRoot) {
|
|
|
169
248
|
promptGuardMode,
|
|
170
249
|
gitHookGuards,
|
|
171
250
|
defaultTrack,
|
|
172
|
-
languageRulePacks
|
|
251
|
+
languageRulePacks,
|
|
252
|
+
trackHeuristics
|
|
173
253
|
};
|
|
174
254
|
}
|
|
175
255
|
export async function writeConfig(projectRoot, config) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent persona content for cclaw.
|
|
3
|
+
*
|
|
4
|
+
* cclaw materializes markdown agent definitions (`.md` with YAML frontmatter)
|
|
5
|
+
* under `.cclaw/agents/` for harness delegation. Research work that does not
|
|
6
|
+
* need isolated subagent context lives in `.cclaw/skills/research/*.md`
|
|
7
|
+
* playbooks and is executed in-thread by the primary agent.
|
|
8
|
+
*/
|
|
9
|
+
export interface AgentDefinition {
|
|
10
|
+
/** Kebab-case identifier, e.g. `"reviewer"`. */
|
|
11
|
+
name: string;
|
|
12
|
+
/** When to invoke — include PROACTIVE / MUST BE USED guidance. */
|
|
13
|
+
description: string;
|
|
14
|
+
/** Allowed tools for this agent (harness-specific names). */
|
|
15
|
+
tools: string[];
|
|
16
|
+
/** Model tier for routing cost/latency vs depth. */
|
|
17
|
+
model: "fast" | "balanced" | "deep";
|
|
18
|
+
/** How the harness should treat activation relative to flow context. */
|
|
19
|
+
activation: "proactive" | "on-demand" | "mandatory";
|
|
20
|
+
/** cclaw flow stages this agent is designed to support. */
|
|
21
|
+
relatedStages: string[];
|
|
22
|
+
/** Markdown body rendered below the YAML frontmatter. */
|
|
23
|
+
body: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Canonical specialist roster (core-5) materialized under `.cclaw/agents/`.
|
|
27
|
+
*/
|
|
28
|
+
export declare const CCLAW_AGENTS: AgentDefinition[];
|
|
29
|
+
/**
|
|
30
|
+
* Render a complete cclaw agent markdown file (YAML frontmatter + body).
|
|
31
|
+
*/
|
|
32
|
+
export declare function agentMarkdown(agent: AgentDefinition): string;
|
|
33
|
+
/**
|
|
34
|
+
* Markdown table mapping cclaw stage entry points to specialist agents.
|
|
35
|
+
*/
|
|
36
|
+
export declare function agentRoutingTable(): string;
|
|
37
|
+
/**
|
|
38
|
+
* Cost tier routing for the core-5 agent roster.
|
|
39
|
+
*/
|
|
40
|
+
export declare function agentCostTierTable(): string;
|
|
41
|
+
/**
|
|
42
|
+
* AGENTS.md-ready section describing cclaw’s specialist delegation model.
|
|
43
|
+
*/
|
|
44
|
+
export declare function agentsAgentsMdBlock(): string;
|