cclaw-cli 0.11.0 → 0.13.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 +8 -0
- package/dist/cli.js +311 -10
- package/dist/config.js +19 -0
- package/dist/constants.d.ts +2 -2
- package/dist/constants.js +13 -1
- package/dist/content/core-agents.d.ts +44 -0
- package/dist/content/core-agents.js +225 -0
- package/dist/content/diff-command.d.ts +2 -0
- package/dist/content/diff-command.js +83 -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/feature-command.d.ts +2 -0
- package/dist/content/feature-command.js +120 -0
- package/dist/content/harnesses-doc.d.ts +1 -0
- package/dist/content/harnesses-doc.js +103 -0
- package/dist/content/hook-events.d.ts +4 -0
- package/dist/content/hook-events.js +42 -0
- package/dist/content/hooks.js +47 -1
- package/dist/content/meta-skill.js +3 -2
- package/dist/content/next-command.js +8 -6
- package/dist/content/observe.d.ts +5 -1
- package/dist/content/observe.js +134 -2
- package/dist/content/protocols.js +34 -6
- package/dist/content/research-playbooks.d.ts +8 -0
- package/dist/content/research-playbooks.js +135 -0
- package/dist/content/retro-command.d.ts +2 -0
- package/dist/content/retro-command.js +77 -0
- package/dist/content/rewind-command.d.ts +3 -0
- package/dist/content/rewind-command.js +120 -0
- package/dist/content/skills.js +20 -0
- package/dist/content/stage-schema.d.ts +3 -1
- package/dist/content/stage-schema.js +20 -51
- package/dist/content/status-command.js +43 -35
- package/dist/content/subagents.d.ts +1 -1
- package/dist/content/subagents.js +23 -38
- package/dist/content/tdd-log-command.d.ts +2 -0
- package/dist/content/tdd-log-command.js +75 -0
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +84 -16
- package/dist/content/tree-command.d.ts +2 -0
- package/dist/content/tree-command.js +91 -0
- 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 +261 -7
- package/dist/feature-system.d.ts +18 -0
- package/dist/feature-system.js +247 -0
- package/dist/flow-state.d.ts +25 -0
- package/dist/flow-state.js +8 -1
- package/dist/harness-adapters.d.ts +7 -0
- package/dist/harness-adapters.js +127 -13
- package/dist/init-detect.d.ts +2 -0
- package/dist/init-detect.js +45 -0
- package/dist/install.js +98 -3
- package/dist/policy.js +27 -0
- package/dist/runs.d.ts +33 -1
- package/dist/runs.js +365 -6
- package/dist/tdd-cycle.d.ts +22 -0
- package/dist/tdd-cycle.js +82 -0
- package/dist/types.d.ts +4 -0
- 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,8 +6,16 @@ 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;
|
|
17
|
+
archiveSkipRetro?: boolean;
|
|
18
|
+
archiveSkipRetroReason?: string;
|
|
11
19
|
showHelp?: boolean;
|
|
12
20
|
showVersion?: boolean;
|
|
13
21
|
}
|
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,12 +26,21 @@ 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).
|
|
42
|
+
--skip-retro Bypass mandatory retro gate (requires --retro-reason).
|
|
43
|
+
--retro-reason=<t> Reason for bypassing retro gate.
|
|
31
44
|
upgrade Refresh generated files in .cclaw without modifying user artifacts.
|
|
32
45
|
uninstall Remove .cclaw runtime and the generated harness shim files.
|
|
33
46
|
|
|
@@ -94,6 +107,208 @@ function parseProfile(raw) {
|
|
|
94
107
|
}
|
|
95
108
|
return trimmed;
|
|
96
109
|
}
|
|
110
|
+
function isInitPromptAllowed(ctx) {
|
|
111
|
+
return Boolean(process.stdin.isTTY && ctx.stdout.isTTY);
|
|
112
|
+
}
|
|
113
|
+
function buildInitSurfacePreview(harnesses) {
|
|
114
|
+
const lines = [
|
|
115
|
+
".cclaw/config.yaml",
|
|
116
|
+
".cclaw/commands/*.md",
|
|
117
|
+
".cclaw/skills/*/SKILL.md",
|
|
118
|
+
".cclaw/state/*.json|*.jsonl",
|
|
119
|
+
".cclaw/references/**",
|
|
120
|
+
"AGENTS.md (managed block)"
|
|
121
|
+
];
|
|
122
|
+
for (const harness of harnesses) {
|
|
123
|
+
const adapter = HARNESS_ADAPTERS[harness];
|
|
124
|
+
lines.push(`${adapter.commandDir}/cc*.md`);
|
|
125
|
+
if (harness === "claude") {
|
|
126
|
+
lines.push(".claude/hooks/hooks.json");
|
|
127
|
+
}
|
|
128
|
+
if (harness === "cursor") {
|
|
129
|
+
lines.push(".cursor/hooks.json");
|
|
130
|
+
lines.push(".cursor/rules/cclaw-workflow.mdc");
|
|
131
|
+
}
|
|
132
|
+
if (harness === "codex") {
|
|
133
|
+
lines.push(".codex/hooks.json");
|
|
134
|
+
}
|
|
135
|
+
if (harness === "opencode") {
|
|
136
|
+
lines.push(".opencode/plugins/cclaw-plugin.mjs");
|
|
137
|
+
lines.push("opencode.json(.c) plugin registration");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return lines;
|
|
141
|
+
}
|
|
142
|
+
function inferTrackDefault(profile, track) {
|
|
143
|
+
if (track)
|
|
144
|
+
return track;
|
|
145
|
+
if (!profile)
|
|
146
|
+
return "standard";
|
|
147
|
+
return createProfileConfig(profile).defaultTrack ?? "standard";
|
|
148
|
+
}
|
|
149
|
+
async function promptInitConfig(defaults, ctx) {
|
|
150
|
+
const rl = createInterface({
|
|
151
|
+
input: process.stdin,
|
|
152
|
+
output: ctx.stdout
|
|
153
|
+
});
|
|
154
|
+
const pickSingle = async (label, options, fallback) => {
|
|
155
|
+
while (true) {
|
|
156
|
+
ctx.stdout.write(`\n${label}\n`);
|
|
157
|
+
options.forEach((option, index) => {
|
|
158
|
+
const marker = option === fallback ? " (default)" : "";
|
|
159
|
+
ctx.stdout.write(` ${index + 1}) ${option}${marker}\n`);
|
|
160
|
+
});
|
|
161
|
+
const answer = (await rl.question("> ")).trim();
|
|
162
|
+
if (answer.length === 0) {
|
|
163
|
+
return fallback;
|
|
164
|
+
}
|
|
165
|
+
const numeric = Number(answer);
|
|
166
|
+
if (Number.isInteger(numeric) && numeric >= 1 && numeric <= options.length) {
|
|
167
|
+
return options[numeric - 1];
|
|
168
|
+
}
|
|
169
|
+
if (options.includes(answer)) {
|
|
170
|
+
return answer;
|
|
171
|
+
}
|
|
172
|
+
ctx.stdout.write("Invalid selection. Use option number or value.\n");
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const pickHarnesses = async (fallback) => {
|
|
176
|
+
const fallbackText = fallback.join(",");
|
|
177
|
+
while (true) {
|
|
178
|
+
const answer = (await rl.question(`\nHarnesses (comma list from ${HARNESS_IDS.join(", ")}) [${fallbackText}]: `)).trim();
|
|
179
|
+
if (answer.length === 0) {
|
|
180
|
+
return fallback;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const parsed = parseHarnesses(answer);
|
|
184
|
+
if (parsed.length === 0) {
|
|
185
|
+
ctx.stdout.write("Select at least one harness.\n");
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
return parsed;
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
ctx.stdout.write(`${err instanceof Error ? err.message : "Invalid harness list"}\n`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
try {
|
|
196
|
+
const profile = await pickSingle("Select init profile:", INIT_PROFILES, defaults.profile);
|
|
197
|
+
const trackDefault = inferTrackDefault(profile, defaults.track);
|
|
198
|
+
const track = await pickSingle("Select default flow track:", FLOW_TRACKS, trackDefault);
|
|
199
|
+
const harnesses = await pickHarnesses(defaults.harnesses);
|
|
200
|
+
return { profile, track, harnesses };
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
rl.close();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async function resolveInitInputs(parsed, ctx) {
|
|
207
|
+
const detectedHarnesses = parsed.harnesses ? [] : await detectHarnesses(ctx.cwd);
|
|
208
|
+
const autoHarnesses = parsed.harnesses
|
|
209
|
+
? parsed.harnesses
|
|
210
|
+
: (detectedHarnesses.length > 0 ? detectedHarnesses : undefined);
|
|
211
|
+
const promptRequested = parsed.interactive === true;
|
|
212
|
+
const promptForbidden = parsed.interactive === false;
|
|
213
|
+
const implicitPrompt = !promptForbidden &&
|
|
214
|
+
isInitPromptAllowed(ctx) &&
|
|
215
|
+
parsed.profile === undefined &&
|
|
216
|
+
parsed.track === undefined &&
|
|
217
|
+
parsed.harnesses === undefined;
|
|
218
|
+
const shouldPrompt = promptRequested || implicitPrompt;
|
|
219
|
+
if (!shouldPrompt) {
|
|
220
|
+
return {
|
|
221
|
+
profile: parsed.profile,
|
|
222
|
+
track: parsed.track,
|
|
223
|
+
harnesses: autoHarnesses,
|
|
224
|
+
detectedHarnesses
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
if (!isInitPromptAllowed(ctx)) {
|
|
228
|
+
throw new Error("Interactive init requires a TTY. Remove --interactive or run in a terminal.");
|
|
229
|
+
}
|
|
230
|
+
const defaults = {
|
|
231
|
+
profile: parsed.profile ?? "standard",
|
|
232
|
+
track: inferTrackDefault(parsed.profile, parsed.track),
|
|
233
|
+
harnesses: autoHarnesses ?? HARNESS_IDS.slice()
|
|
234
|
+
};
|
|
235
|
+
const prompted = await promptInitConfig(defaults, ctx);
|
|
236
|
+
return {
|
|
237
|
+
profile: prompted.profile,
|
|
238
|
+
track: prompted.track,
|
|
239
|
+
harnesses: prompted.harnesses,
|
|
240
|
+
detectedHarnesses
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function parseDoctorOnly(raw) {
|
|
244
|
+
return raw
|
|
245
|
+
.split(",")
|
|
246
|
+
.map((item) => item.trim().toLowerCase())
|
|
247
|
+
.filter((item) => item.length > 0);
|
|
248
|
+
}
|
|
249
|
+
function filterDoctorChecks(checks, filters) {
|
|
250
|
+
if (!filters || filters.length === 0) {
|
|
251
|
+
return checks;
|
|
252
|
+
}
|
|
253
|
+
return checks.filter((check) => {
|
|
254
|
+
const name = check.name.toLowerCase();
|
|
255
|
+
return filters.some((filter) => {
|
|
256
|
+
if (filter === "error" || filter === "warning" || filter === "info") {
|
|
257
|
+
return check.severity === filter;
|
|
258
|
+
}
|
|
259
|
+
return name.includes(filter);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
function doctorCountsBySeverity(checks) {
|
|
264
|
+
const result = {
|
|
265
|
+
error: { total: 0, failing: 0 },
|
|
266
|
+
warning: { total: 0, failing: 0 },
|
|
267
|
+
info: { total: 0, failing: 0 }
|
|
268
|
+
};
|
|
269
|
+
for (const check of checks) {
|
|
270
|
+
const bucket = result[check.severity];
|
|
271
|
+
bucket.total += 1;
|
|
272
|
+
if (!check.ok) {
|
|
273
|
+
bucket.failing += 1;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
function printDoctorText(ctx, checks, options) {
|
|
279
|
+
const orderedSeverities = ["error", "warning", "info"];
|
|
280
|
+
const view = options.quiet ? checks.filter((check) => !check.ok) : checks;
|
|
281
|
+
for (const severity of orderedSeverities) {
|
|
282
|
+
const inBucket = view.filter((check) => check.severity === severity);
|
|
283
|
+
if (inBucket.length === 0)
|
|
284
|
+
continue;
|
|
285
|
+
ctx.stdout.write(`\n[${severity.toUpperCase()}]\n`);
|
|
286
|
+
for (const check of inBucket) {
|
|
287
|
+
const status = check.ok ? "PASS" : "FAIL";
|
|
288
|
+
ctx.stdout.write(`${status} ${check.name} :: ${check.summary}\n`);
|
|
289
|
+
if (!options.quiet) {
|
|
290
|
+
ctx.stdout.write(` details: ${check.details}\n`);
|
|
291
|
+
}
|
|
292
|
+
if (options.explain) {
|
|
293
|
+
ctx.stdout.write(` fix: ${check.fix}\n`);
|
|
294
|
+
if (check.docRef) {
|
|
295
|
+
ctx.stdout.write(` docs: ${check.docRef}\n`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const counts = doctorCountsBySeverity(checks);
|
|
301
|
+
const failingErrors = checks.filter((check) => check.severity === "error" && !check.ok).length;
|
|
302
|
+
ctx.stdout.write(`\nTotals: error ${counts.error.failing}/${counts.error.total} failing, ` +
|
|
303
|
+
`warning ${counts.warning.failing}/${counts.warning.total} failing, ` +
|
|
304
|
+
`info ${counts.info.failing}/${counts.info.total} failing\n`);
|
|
305
|
+
if (failingErrors > 0) {
|
|
306
|
+
ctx.stdout.write(`Doctor status: BLOCKED (${failingErrors} failing error checks)\n`);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
ctx.stdout.write("Doctor status: HEALTHY (no failing error checks)\n");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
97
312
|
function parseArgs(argv) {
|
|
98
313
|
const parsed = {};
|
|
99
314
|
const helpFlag = argv.find((arg) => arg === "--help" || arg === "-h");
|
|
@@ -121,12 +336,48 @@ function parseArgs(argv) {
|
|
|
121
336
|
parsed.profile = parseProfile(flag.replace("--profile=", ""));
|
|
122
337
|
continue;
|
|
123
338
|
}
|
|
339
|
+
if (flag === "--interactive") {
|
|
340
|
+
parsed.interactive = true;
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (flag === "--no-interactive") {
|
|
344
|
+
parsed.interactive = false;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (flag === "--dry-run") {
|
|
348
|
+
parsed.dryRun = true;
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
124
351
|
if (flag === "--reconcile-gates") {
|
|
125
352
|
parsed.reconcileGates = true;
|
|
126
353
|
continue;
|
|
127
354
|
}
|
|
355
|
+
if (flag === "--json") {
|
|
356
|
+
parsed.doctorJson = true;
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (flag === "--explain") {
|
|
360
|
+
parsed.doctorExplain = true;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (flag === "--quiet") {
|
|
364
|
+
parsed.doctorQuiet = true;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (flag.startsWith("--only=")) {
|
|
368
|
+
parsed.doctorOnly = parseDoctorOnly(flag.replace("--only=", ""));
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
128
371
|
if (flag.startsWith("--name=")) {
|
|
129
372
|
parsed.archiveName = flag.replace("--name=", "").trim();
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (flag === "--skip-retro") {
|
|
376
|
+
parsed.archiveSkipRetro = true;
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (flag.startsWith("--retro-reason=")) {
|
|
380
|
+
parsed.archiveSkipRetroReason = flag.replace("--retro-reason=", "").trim();
|
|
130
381
|
}
|
|
131
382
|
}
|
|
132
383
|
return parsed;
|
|
@@ -146,14 +397,44 @@ async function runCommand(parsed, ctx) {
|
|
|
146
397
|
return 1;
|
|
147
398
|
}
|
|
148
399
|
if (command === "init") {
|
|
400
|
+
const resolved = await resolveInitInputs(parsed, ctx);
|
|
401
|
+
const effectiveProfile = resolved.profile;
|
|
402
|
+
const effectiveTrack = resolved.track;
|
|
403
|
+
const effectiveHarnesses = resolved.harnesses;
|
|
404
|
+
if (parsed.dryRun === true) {
|
|
405
|
+
const previewConfig = effectiveProfile
|
|
406
|
+
? createProfileConfig(effectiveProfile, {
|
|
407
|
+
harnesses: effectiveHarnesses,
|
|
408
|
+
defaultTrack: effectiveTrack
|
|
409
|
+
})
|
|
410
|
+
: createDefaultConfig(effectiveHarnesses, effectiveTrack);
|
|
411
|
+
const previewSurfaces = buildInitSurfacePreview(previewConfig.harnesses);
|
|
412
|
+
info(ctx, "Dry run: no files were written.");
|
|
413
|
+
if (resolved.detectedHarnesses.length > 0 && parsed.harnesses === undefined) {
|
|
414
|
+
info(ctx, `Detected harnesses from repo: ${resolved.detectedHarnesses.join(", ")}`);
|
|
415
|
+
}
|
|
416
|
+
ctx.stdout.write(`${JSON.stringify({
|
|
417
|
+
profile: effectiveProfile ?? "standard(default)",
|
|
418
|
+
track: previewConfig.defaultTrack ?? "standard",
|
|
419
|
+
harnesses: previewConfig.harnesses,
|
|
420
|
+
promptGuardMode: previewConfig.promptGuardMode,
|
|
421
|
+
gitHookGuards: previewConfig.gitHookGuards,
|
|
422
|
+
languageRulePacks: previewConfig.languageRulePacks,
|
|
423
|
+
generatedSurfaces: previewSurfaces
|
|
424
|
+
}, null, 2)}\n`);
|
|
425
|
+
return 0;
|
|
426
|
+
}
|
|
149
427
|
await initCclaw({
|
|
150
428
|
projectRoot: ctx.cwd,
|
|
151
|
-
harnesses:
|
|
152
|
-
track:
|
|
153
|
-
profile:
|
|
429
|
+
harnesses: effectiveHarnesses,
|
|
430
|
+
track: effectiveTrack,
|
|
431
|
+
profile: effectiveProfile
|
|
154
432
|
});
|
|
155
|
-
|
|
156
|
-
|
|
433
|
+
if (resolved.detectedHarnesses.length > 0 && parsed.harnesses === undefined) {
|
|
434
|
+
info(ctx, `Detected harnesses from repo: ${resolved.detectedHarnesses.join(", ")}`);
|
|
435
|
+
}
|
|
436
|
+
const profileNote = effectiveProfile ? ` profile=${effectiveProfile}` : "";
|
|
437
|
+
const trackNote = effectiveTrack ? ` track=${effectiveTrack}` : "";
|
|
157
438
|
const suffix = profileNote || trackNote ? ` (${(profileNote + trackNote).trim()})` : "";
|
|
158
439
|
info(ctx, `Initialized .cclaw runtime and generated harness shims${suffix}`);
|
|
159
440
|
return 0;
|
|
@@ -167,8 +448,25 @@ async function runCommand(parsed, ctx) {
|
|
|
167
448
|
const checks = await doctorChecks(ctx.cwd, {
|
|
168
449
|
reconcileCurrentStageGates: parsed.reconcileGates === true
|
|
169
450
|
});
|
|
170
|
-
|
|
171
|
-
|
|
451
|
+
const filteredChecks = filterDoctorChecks(checks, parsed.doctorOnly);
|
|
452
|
+
const explain = parsed.doctorExplain === true;
|
|
453
|
+
const quiet = parsed.doctorQuiet === true;
|
|
454
|
+
if (parsed.doctorJson === true) {
|
|
455
|
+
const counts = doctorCountsBySeverity(filteredChecks);
|
|
456
|
+
ctx.stdout.write(`${JSON.stringify({
|
|
457
|
+
ok: doctorSucceeded(checks),
|
|
458
|
+
filters: parsed.doctorOnly ?? [],
|
|
459
|
+
counts,
|
|
460
|
+
checks: filteredChecks
|
|
461
|
+
}, null, 2)}\n`);
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
if (filteredChecks.length === 0) {
|
|
465
|
+
ctx.stdout.write("No checks matched the --only filter.\n");
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
printDoctorText(ctx, filteredChecks, { explain, quiet });
|
|
469
|
+
}
|
|
172
470
|
}
|
|
173
471
|
return doctorSucceeded(checks) ? 0 : 2;
|
|
174
472
|
}
|
|
@@ -178,7 +476,10 @@ async function runCommand(parsed, ctx) {
|
|
|
178
476
|
return 0;
|
|
179
477
|
}
|
|
180
478
|
if (command === "archive") {
|
|
181
|
-
const archived = await archiveRun(ctx.cwd, parsed.archiveName
|
|
479
|
+
const archived = await archiveRun(ctx.cwd, parsed.archiveName, {
|
|
480
|
+
skipRetro: parsed.archiveSkipRetro === true,
|
|
481
|
+
skipRetroReason: parsed.archiveSkipRetroReason
|
|
482
|
+
});
|
|
182
483
|
const snapshotSummary = archived.snapshottedStateFiles.length > 0
|
|
183
484
|
? ` Snapshotted ${archived.snapshottedStateFiles.length} state file(s) under ${archived.archivePath}/state and wrote archive-manifest.json.`
|
|
184
485
|
: "";
|
package/dist/config.js
CHANGED
|
@@ -17,6 +17,8 @@ const ALLOWED_CONFIG_KEYS = new Set([
|
|
|
17
17
|
"harnesses",
|
|
18
18
|
"autoAdvance",
|
|
19
19
|
"promptGuardMode",
|
|
20
|
+
"tddEnforcement",
|
|
21
|
+
"tddTestGlobs",
|
|
20
22
|
"gitHookGuards",
|
|
21
23
|
"defaultTrack",
|
|
22
24
|
"languageRulePacks",
|
|
@@ -60,6 +62,8 @@ export function createDefaultConfig(harnesses = DEFAULT_HARNESSES, defaultTrack
|
|
|
60
62
|
harnesses,
|
|
61
63
|
autoAdvance: false,
|
|
62
64
|
promptGuardMode: "advisory",
|
|
65
|
+
tddEnforcement: "advisory",
|
|
66
|
+
tddTestGlobs: ["**/*.test.*", "**/*.spec.*", "**/test/**"],
|
|
63
67
|
gitHookGuards: false,
|
|
64
68
|
defaultTrack,
|
|
65
69
|
languageRulePacks: []
|
|
@@ -79,6 +83,7 @@ export function createProfileConfig(profile, overrides = {}) {
|
|
|
79
83
|
harnesses: overrides.harnesses ?? ["claude"],
|
|
80
84
|
autoAdvance: false,
|
|
81
85
|
promptGuardMode: "advisory",
|
|
86
|
+
tddEnforcement: "advisory",
|
|
82
87
|
gitHookGuards: false,
|
|
83
88
|
defaultTrack: overrides.defaultTrack ?? "medium",
|
|
84
89
|
languageRulePacks: overrides.languageRulePacks ?? []
|
|
@@ -89,6 +94,7 @@ export function createProfileConfig(profile, overrides = {}) {
|
|
|
89
94
|
harnesses: overrides.harnesses ?? DEFAULT_HARNESSES,
|
|
90
95
|
autoAdvance: false,
|
|
91
96
|
promptGuardMode: "advisory",
|
|
97
|
+
tddEnforcement: "advisory",
|
|
92
98
|
gitHookGuards: false,
|
|
93
99
|
defaultTrack: overrides.defaultTrack ?? "standard",
|
|
94
100
|
languageRulePacks: overrides.languageRulePacks ?? []
|
|
@@ -99,6 +105,7 @@ export function createProfileConfig(profile, overrides = {}) {
|
|
|
99
105
|
harnesses: overrides.harnesses ?? DEFAULT_HARNESSES,
|
|
100
106
|
autoAdvance: false,
|
|
101
107
|
promptGuardMode: "strict",
|
|
108
|
+
tddEnforcement: "strict",
|
|
102
109
|
gitHookGuards: true,
|
|
103
110
|
defaultTrack: overrides.defaultTrack ?? "standard",
|
|
104
111
|
languageRulePacks: overrides.languageRulePacks ?? [...LANGUAGE_RULE_PACKS]
|
|
@@ -149,6 +156,16 @@ export async function readConfig(projectRoot) {
|
|
|
149
156
|
throw configValidationError(fullPath, `"promptGuardMode" must be "advisory" or "strict"`);
|
|
150
157
|
}
|
|
151
158
|
const promptGuardMode = promptGuardModeRaw === "strict" ? "strict" : "advisory";
|
|
159
|
+
const tddEnforcementRaw = parsed.tddEnforcement;
|
|
160
|
+
if (Object.prototype.hasOwnProperty.call(parsed, "tddEnforcement") &&
|
|
161
|
+
tddEnforcementRaw !== "advisory" &&
|
|
162
|
+
tddEnforcementRaw !== "strict") {
|
|
163
|
+
throw configValidationError(fullPath, `"tddEnforcement" must be "advisory" or "strict"`);
|
|
164
|
+
}
|
|
165
|
+
const tddEnforcement = tddEnforcementRaw === "strict" ? "strict" : "advisory";
|
|
166
|
+
const tddTestGlobsRaw = parsed.tddTestGlobs;
|
|
167
|
+
const tddTestGlobs = validateStringArray(tddTestGlobsRaw, "tddTestGlobs", fullPath)
|
|
168
|
+
?? ["**/*.test.*", "**/*.spec.*", "**/test/**"];
|
|
152
169
|
const gitHookGuardsRaw = parsed.gitHookGuards;
|
|
153
170
|
if (Object.prototype.hasOwnProperty.call(parsed, "gitHookGuards") &&
|
|
154
171
|
typeof gitHookGuardsRaw !== "boolean") {
|
|
@@ -246,6 +263,8 @@ export async function readConfig(projectRoot) {
|
|
|
246
263
|
harnesses,
|
|
247
264
|
autoAdvance,
|
|
248
265
|
promptGuardMode,
|
|
266
|
+
tddEnforcement,
|
|
267
|
+
tddTestGlobs,
|
|
249
268
|
gitHookGuards,
|
|
250
269
|
defaultTrack,
|
|
251
270
|
languageRulePacks,
|
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", ".cclaw/custom-skills"];
|
|
7
|
+
export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/contexts", ".cclaw/templates", ".cclaw/artifacts", ".cclaw/features", ".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", "status"];
|
|
10
|
+
export declare const UTILITY_COMMANDS: readonly ["learn", "next", "status", "tree", "diff", "feature", "tdd-log", "retro", "rewind", "rewind-ack"];
|
|
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
|
@@ -15,6 +15,7 @@ export const REQUIRED_DIRS = [
|
|
|
15
15
|
`${RUNTIME_ROOT}/contexts`,
|
|
16
16
|
`${RUNTIME_ROOT}/templates`,
|
|
17
17
|
`${RUNTIME_ROOT}/artifacts`,
|
|
18
|
+
`${RUNTIME_ROOT}/features`,
|
|
18
19
|
`${RUNTIME_ROOT}/state`,
|
|
19
20
|
`${RUNTIME_ROOT}/runs`,
|
|
20
21
|
`${RUNTIME_ROOT}/rules`,
|
|
@@ -50,7 +51,18 @@ export const COMMAND_FILE_ORDER = [
|
|
|
50
51
|
"review",
|
|
51
52
|
"ship"
|
|
52
53
|
];
|
|
53
|
-
export const UTILITY_COMMANDS = [
|
|
54
|
+
export const UTILITY_COMMANDS = [
|
|
55
|
+
"learn",
|
|
56
|
+
"next",
|
|
57
|
+
"status",
|
|
58
|
+
"tree",
|
|
59
|
+
"diff",
|
|
60
|
+
"feature",
|
|
61
|
+
"tdd-log",
|
|
62
|
+
"retro",
|
|
63
|
+
"rewind",
|
|
64
|
+
"rewind-ack"
|
|
65
|
+
];
|
|
54
66
|
export const SUBAGENT_SKILL_FOLDERS = [
|
|
55
67
|
"subagent-dev",
|
|
56
68
|
"parallel-dispatch"
|
|
@@ -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;
|