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.
Files changed (67) hide show
  1. package/README.md +4 -3
  2. package/dist/cli.d.ts +8 -0
  3. package/dist/cli.js +311 -10
  4. package/dist/config.js +19 -0
  5. package/dist/constants.d.ts +2 -2
  6. package/dist/constants.js +13 -1
  7. package/dist/content/core-agents.d.ts +44 -0
  8. package/dist/content/core-agents.js +225 -0
  9. package/dist/content/diff-command.d.ts +2 -0
  10. package/dist/content/diff-command.js +83 -0
  11. package/dist/content/doctor-references.d.ts +2 -0
  12. package/dist/content/doctor-references.js +144 -0
  13. package/dist/content/examples.js +1 -1
  14. package/dist/content/feature-command.d.ts +2 -0
  15. package/dist/content/feature-command.js +120 -0
  16. package/dist/content/harnesses-doc.d.ts +1 -0
  17. package/dist/content/harnesses-doc.js +103 -0
  18. package/dist/content/hook-events.d.ts +4 -0
  19. package/dist/content/hook-events.js +42 -0
  20. package/dist/content/hooks.js +47 -1
  21. package/dist/content/meta-skill.js +3 -2
  22. package/dist/content/next-command.js +8 -6
  23. package/dist/content/observe.d.ts +5 -1
  24. package/dist/content/observe.js +134 -2
  25. package/dist/content/protocols.js +34 -6
  26. package/dist/content/research-playbooks.d.ts +8 -0
  27. package/dist/content/research-playbooks.js +135 -0
  28. package/dist/content/retro-command.d.ts +2 -0
  29. package/dist/content/retro-command.js +77 -0
  30. package/dist/content/rewind-command.d.ts +3 -0
  31. package/dist/content/rewind-command.js +120 -0
  32. package/dist/content/skills.js +20 -0
  33. package/dist/content/stage-schema.d.ts +3 -1
  34. package/dist/content/stage-schema.js +20 -51
  35. package/dist/content/status-command.js +43 -35
  36. package/dist/content/subagents.d.ts +1 -1
  37. package/dist/content/subagents.js +23 -38
  38. package/dist/content/tdd-log-command.d.ts +2 -0
  39. package/dist/content/tdd-log-command.js +75 -0
  40. package/dist/content/templates.d.ts +1 -1
  41. package/dist/content/templates.js +84 -16
  42. package/dist/content/tree-command.d.ts +2 -0
  43. package/dist/content/tree-command.js +91 -0
  44. package/dist/delegation.d.ts +1 -0
  45. package/dist/delegation.js +27 -1
  46. package/dist/doctor-registry.d.ts +8 -0
  47. package/dist/doctor-registry.js +127 -0
  48. package/dist/doctor.d.ts +5 -0
  49. package/dist/doctor.js +261 -7
  50. package/dist/feature-system.d.ts +18 -0
  51. package/dist/feature-system.js +247 -0
  52. package/dist/flow-state.d.ts +25 -0
  53. package/dist/flow-state.js +8 -1
  54. package/dist/harness-adapters.d.ts +7 -0
  55. package/dist/harness-adapters.js +127 -13
  56. package/dist/init-detect.d.ts +2 -0
  57. package/dist/init-detect.js +45 -0
  58. package/dist/install.js +98 -3
  59. package/dist/policy.js +27 -0
  60. package/dist/runs.d.ts +33 -1
  61. package/dist/runs.js +365 -6
  62. package/dist/tdd-cycle.d.ts +22 -0
  63. package/dist/tdd-cycle.js +82 -0
  64. package/dist/types.d.ts +4 -0
  65. package/package.json +2 -1
  66. package/dist/content/agents.d.ts +0 -48
  67. 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
- - **Cross-harness parity:** same behavior model across Claude Code, Cursor, Codex, OpenCode.
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.md # append-only rule/pattern/lesson log
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 install surface, feature matrix, and lifecycle details live in
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 on failure.
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: parsed.harnesses,
152
- track: parsed.track,
153
- profile: parsed.profile
429
+ harnesses: effectiveHarnesses,
430
+ track: effectiveTrack,
431
+ profile: effectiveProfile
154
432
  });
155
- const profileNote = parsed.profile ? ` profile=${parsed.profile}` : "";
156
- const trackNote = parsed.track ? ` track=${parsed.track}` : "";
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
- for (const check of checks) {
171
- ctx.stdout.write(`${check.ok ? "PASS" : "FAIL"} ${check.name} :: ${check.details}\n`);
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,
@@ -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 = ["learn", "next", "status"];
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;