cclaw-cli 0.11.0 → 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 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,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 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).
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: parsed.harnesses,
152
- track: parsed.track,
153
- profile: parsed.profile
419
+ harnesses: effectiveHarnesses,
420
+ track: effectiveTrack,
421
+ profile: effectiveProfile
154
422
  });
155
- const profileNote = parsed.profile ? ` profile=${parsed.profile}` : "";
156
- const trackNote = parsed.track ? ` track=${parsed.track}` : "";
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
- for (const check of checks) {
171
- ctx.stdout.write(`${check.ok ? "PASS" : "FAIL"} ${check.name} :: ${check.details}\n`);
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
  }
@@ -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;
@@ -0,0 +1,225 @@
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
+ function yamlScalarString(value) {
10
+ // JSON double-quoted strings are valid YAML scalars and escape reliably.
11
+ return JSON.stringify(value);
12
+ }
13
+ function yamlFlowSequence(values) {
14
+ return JSON.stringify(values);
15
+ }
16
+ /**
17
+ * Canonical specialist roster (core-5) materialized under `.cclaw/agents/`.
18
+ */
19
+ export const CCLAW_AGENTS = [
20
+ {
21
+ name: "planner",
22
+ description: "MANDATORY for scope/design/plan and PROACTIVE for high-ambiguity work. MUST BE USED when sequencing, dependency mapping, or risk trade-offs are required before coding.",
23
+ tools: ["Read", "Grep", "Glob", "WebSearch"],
24
+ model: "deep",
25
+ activation: "mandatory",
26
+ relatedStages: ["brainstorm", "scope", "design", "spec", "plan"],
27
+ body: [
28
+ "You are an **implementation planning specialist** (staff engineer mindset).",
29
+ "",
30
+ "When invoked:",
31
+ "1. Analyze scope and break it into concrete sub-problems.",
32
+ "2. Map each sub-problem to existing modules and reusable code.",
33
+ "3. Produce an ordered execution plan with dependencies and checks.",
34
+ "4. Highlight risks and unknowns that need user decisions.",
35
+ "",
36
+ "**Role boundary:** planning only. Do NOT write production code."
37
+ ].join("\n")
38
+ },
39
+ {
40
+ name: "reviewer",
41
+ description: "MANDATORY during review. MUST BE USED to run a two-pass audit: spec compliance first, then correctness/maintainability/performance/architecture.",
42
+ tools: ["Read", "Grep", "Glob"],
43
+ model: "balanced",
44
+ activation: "mandatory",
45
+ relatedStages: ["spec", "review", "ship"],
46
+ body: [
47
+ "You are a **combined spec + code reviewer**.",
48
+ "",
49
+ "Run two explicit passes:",
50
+ "",
51
+ "1. **Spec pass**",
52
+ " - For each acceptance criterion: PASS / PARTIAL / FAIL.",
53
+ " - Cite evidence as `file:line`.",
54
+ "",
55
+ "2. **Code-quality pass**",
56
+ " - Correctness: logic, boundaries, state transitions.",
57
+ " - Maintainability: naming, structure, complexity, debt risks.",
58
+ " - Performance: avoid obvious hot-path regressions.",
59
+ " - Architecture fit: layering and contract stability.",
60
+ "",
61
+ "For each finding include:",
62
+ "- Severity: `Critical` | `Important` | `Suggestion`",
63
+ "- Location: `file:line`",
64
+ "- Problem and concrete recommendation",
65
+ "",
66
+ "**Trust model:** never rely on implementer claims; verify by reading code."
67
+ ].join("\n")
68
+ },
69
+ {
70
+ name: "security-reviewer",
71
+ description: "MANDATORY during review; PROACTIVE during design/ship for trust-boundary changes. Always produce an explicit no-change attestation when no security-relevant surface moved.",
72
+ tools: ["Read", "Grep", "Glob"],
73
+ model: "balanced",
74
+ activation: "mandatory",
75
+ relatedStages: ["design", "review", "ship"],
76
+ body: [
77
+ "You are a **security vulnerability specialist** focused on exploitability.",
78
+ "",
79
+ "Check for (non-exhaustive):",
80
+ "- validation gaps and injection vectors",
81
+ "- authz/authn boundary violations",
82
+ "- secret leakage in code/logging",
83
+ "- unsafe file/system/network operations",
84
+ "- privilege escalation and trust-boundary misuse",
85
+ "",
86
+ "For each finding include:",
87
+ "- severity aligned to ship risk",
88
+ "- CWE ID when possible (or UNKNOWN)",
89
+ "- short proof-of-concept vector",
90
+ "- concrete control-oriented fix"
91
+ ].join("\n")
92
+ },
93
+ {
94
+ name: "test-author",
95
+ description: "MANDATORY in TDD stage. MUST BE USED for RED -> GREEN -> REFACTOR with evidence-first discipline.",
96
+ tools: ["Read", "Write", "Edit", "Grep", "Glob", "Bash"],
97
+ model: "balanced",
98
+ activation: "mandatory",
99
+ relatedStages: ["tdd"],
100
+ body: [
101
+ "You are a **test-driven development** specialist.",
102
+ "",
103
+ "**Iron law:** no production code without a failing test first.",
104
+ "",
105
+ "Process:",
106
+ "1. RED: write a failing test for the desired behavior.",
107
+ "2. Verify RED fails for the right reason.",
108
+ "3. GREEN: implement minimal code to pass.",
109
+ "4. Verify GREEN on relevant suite/full suite.",
110
+ "5. REFACTOR with behavior preserved."
111
+ ].join("\n")
112
+ },
113
+ {
114
+ name: "doc-updater",
115
+ description: "MANDATORY at ship and PROACTIVE when behavior/config/public API changes. Keep docs and runbooks in lockstep with shipped behavior.",
116
+ tools: ["Read", "Write", "Edit", "Grep", "Glob"],
117
+ model: "fast",
118
+ activation: "mandatory",
119
+ relatedStages: ["tdd", "ship"],
120
+ body: [
121
+ "You are a **documentation maintenance specialist**.",
122
+ "",
123
+ "After code changes, verify and update only stale sections in:",
124
+ "- README / setup / usage",
125
+ "- API docs and examples",
126
+ "- migration and operational notes",
127
+ "",
128
+ "Preserve existing tone and structure; avoid rewrites for style alone."
129
+ ].join("\n")
130
+ }
131
+ ];
132
+ import { enhancedAgentBody } from "./subagents.js";
133
+ /**
134
+ * Render a complete cclaw agent markdown file (YAML frontmatter + body).
135
+ */
136
+ export function agentMarkdown(agent) {
137
+ const frontmatter = [
138
+ "---",
139
+ `name: ${agent.name}`,
140
+ `description: ${yamlScalarString(agent.description)}`,
141
+ `tools: ${yamlFlowSequence(agent.tools)}`,
142
+ `model: ${agent.model}`,
143
+ "---"
144
+ ].join("\n");
145
+ const relatedStages = agent.relatedStages.length > 0 ? agent.relatedStages.join(", ") : "(none)";
146
+ const taskDelegation = enhancedAgentBody(agent.name);
147
+ return `${frontmatter}
148
+
149
+ # ${agent.name}
150
+
151
+ ${agent.body}
152
+
153
+ ## Activation
154
+
155
+ - Mode: ${agent.activation}
156
+ - Related stages: ${relatedStages}
157
+
158
+ ## Rules
159
+
160
+ - Cite file:line for every finding
161
+ - Do not make changes outside your specialist domain
162
+ - Report findings with severity classification
163
+ - If uncertain, say "UNKNOWN" - never guess
164
+
165
+ ${taskDelegation}
166
+ `;
167
+ }
168
+ /**
169
+ * Markdown table mapping cclaw stage entry points to specialist agents.
170
+ */
171
+ export function agentRoutingTable() {
172
+ return `| Stage Entry | Primary Agent(s) | Supporting guidance |
173
+ |---|---|---|
174
+ | Brainstorm (start with \`/cc <idea>\`) | planner | Run in-thread research playbooks: \`research/repo-scan.md\`, \`research/learnings-lookup.md\` |
175
+ | Scope / Design / Plan (via \`/cc-next\`) | planner | Use \`research/git-history.md\` (scope) and \`research/framework-docs-lookup.md\` + \`research/best-practices-lookup.md\` (design) as needed |
176
+ | Spec (via \`/cc-next\`) | reviewer | planner (if ambiguity or conflicts remain) |
177
+ | TDD (via \`/cc-next\`) | test-author | doc-updater on public behavior/config changes |
178
+ | Review (via \`/cc-next\`) | reviewer, security-reviewer | conditional second reviewer for high blast-radius diffs |
179
+ | Ship (via \`/cc-next\`) | doc-updater | security-reviewer when release risk is elevated |
180
+ `;
181
+ }
182
+ /**
183
+ * Cost tier routing for the core-5 agent roster.
184
+ */
185
+ export function agentCostTierTable() {
186
+ return `| Tier | Use for | Example agents |
187
+ |---|---|---|
188
+ | \`deep\` | one heavy planning pass per stage | planner |
189
+ | \`balanced\` | review and TDD specialists with stronger reasoning depth | reviewer, security-reviewer, test-author |
190
+ | \`fast\` | bounded maintenance updates with limited blast radius | doc-updater |
191
+ `;
192
+ }
193
+ /**
194
+ * AGENTS.md-ready section describing cclaw’s specialist delegation model.
195
+ */
196
+ export function agentsAgentsMdBlock() {
197
+ return `### Agent Specialists
198
+
199
+ cclaw materializes **5 core specialist agents** under \`.cclaw/agents/\`.
200
+
201
+ ${agentRoutingTable()}
202
+
203
+ ### Research Playbooks (in-thread)
204
+
205
+ Research work is no longer modeled as standalone personas. Use in-thread playbooks under \`.cclaw/skills/research/\`:
206
+
207
+ - \`repo-scan.md\`
208
+ - \`learnings-lookup.md\`
209
+ - \`framework-docs-lookup.md\`
210
+ - \`best-practices-lookup.md\`
211
+ - \`git-history.md\`
212
+
213
+ ### Activation modes
214
+
215
+ - **Mandatory:** planner (scope/design/plan), reviewer + security-reviewer (review), test-author (tdd), doc-updater (ship).
216
+ - **Proactive:** planner on ambiguity, security-reviewer on trust-boundary movement outside review, doc-updater on behavior/config drift.
217
+ - **On-demand:** none in the core-5 roster; research playbooks are in-thread procedures.
218
+
219
+ ### Cost-aware routing
220
+
221
+ ${agentCostTierTable()}
222
+
223
+ **Agent files:** \`.cclaw/agents/{name}.md\` — each contains YAML frontmatter with tools and model tier.
224
+ `;
225
+ }
@@ -0,0 +1,2 @@
1
+ export declare const DOCTOR_REFERENCE_DIR = ".cclaw/references/doctor";
2
+ export declare const DOCTOR_REFERENCE_MARKDOWN: Record<string, string>;