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/dist/doctor.js CHANGED
@@ -4,7 +4,7 @@ import { execFile } from "node:child_process";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { promisify } from "node:util";
6
6
  import { COMMAND_FILE_ORDER, REQUIRED_DIRS, RUNTIME_ROOT } from "./constants.js";
7
- import { CCLAW_AGENTS } from "./content/agents.js";
7
+ import { CCLAW_AGENTS } from "./content/core-agents.js";
8
8
  import { readConfig } from "./config.js";
9
9
  import { exists } from "./fs-utils.js";
10
10
  import { gitignoreHasRequiredPatterns } from "./gitignore.js";
@@ -17,10 +17,13 @@ import { checkMandatoryDelegations } from "./delegation.js";
17
17
  import { buildTraceMatrix } from "./trace-matrix.js";
18
18
  import { reconcileAndWriteCurrentStageGateCatalog, verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence } from "./gate-evidence.js";
19
19
  import { stageSkillFolder } from "./content/skills.js";
20
+ import { doctorCheckMetadata } from "./doctor-registry.js";
20
21
  import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LEGACY_LANGUAGE_RULE_PACK_FOLDERS, UTILITY_SKILL_FOLDERS } from "./content/utility-skills.js";
21
22
  import { CONTEXT_MODES, DEFAULT_CONTEXT_MODE } from "./content/contexts.js";
23
+ import { DOCTOR_REFERENCE_MARKDOWN } from "./content/doctor-references.js";
22
24
  import { validateHookDocument } from "./hook-schema.js";
23
25
  const execFileAsync = promisify(execFile);
26
+ const PREAMBLE_COOLDOWN_MS = 15 * 60 * 1000;
24
27
  async function isGitRepo(projectRoot) {
25
28
  try {
26
29
  await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: projectRoot });
@@ -345,6 +348,20 @@ export async function doctorChecks(projectRoot, options = {}) {
345
348
  details: refPath
346
349
  });
347
350
  }
351
+ checks.push({
352
+ name: "harness_ref:matrix",
353
+ ok: await exists(path.join(projectRoot, RUNTIME_ROOT, "references", "harnesses.md")),
354
+ details: `${RUNTIME_ROOT}/references/harnesses.md`
355
+ });
356
+ const doctorRefDir = path.join(projectRoot, RUNTIME_ROOT, "references", "doctor");
357
+ for (const fileName of Object.keys(DOCTOR_REFERENCE_MARKDOWN)) {
358
+ const refPath = path.join(doctorRefDir, fileName);
359
+ checks.push({
360
+ name: `doctor_ref:${fileName.replace(/\.md$/, "")}`,
361
+ ok: await exists(refPath),
362
+ details: refPath
363
+ });
364
+ }
348
365
  checks.push({
349
366
  name: "gitignore:required_patterns",
350
367
  ok: await gitignoreHasRequiredPatterns(projectRoot),
@@ -810,6 +827,11 @@ export async function doctorChecks(projectRoot, options = {}) {
810
827
  ok: await exists(path.join(projectRoot, RUNTIME_ROOT, "state", "suggestion-memory.json")),
811
828
  details: `${RUNTIME_ROOT}/state/suggestion-memory.json must exist for proactive suggestion memory`
812
829
  });
830
+ checks.push({
831
+ name: "state:harness_gaps_exists",
832
+ ok: await exists(path.join(projectRoot, RUNTIME_ROOT, "state", "harness-gaps.json")),
833
+ details: `${RUNTIME_ROOT}/state/harness-gaps.json must exist for tiered harness capability tracking`
834
+ });
813
835
  const contextModeStatePath = path.join(projectRoot, RUNTIME_ROOT, "state", "context-mode.json");
814
836
  checks.push({
815
837
  name: "state:context_mode_exists",
@@ -840,6 +862,81 @@ export async function doctorChecks(projectRoot, options = {}) {
840
862
  details: modePath
841
863
  });
842
864
  }
865
+ const preambleLogPath = path.join(projectRoot, RUNTIME_ROOT, "state", "preamble-log.jsonl");
866
+ const preambleLogExists = await exists(preambleLogPath);
867
+ checks.push({
868
+ name: "state:preamble_log_exists",
869
+ ok: preambleLogExists,
870
+ details: `${RUNTIME_ROOT}/state/preamble-log.jsonl must exist for preamble budget tracking`
871
+ });
872
+ if (preambleLogExists) {
873
+ let duplicateHits = 0;
874
+ let parsedEntries = 0;
875
+ let malformedEntries = 0;
876
+ try {
877
+ const now = Date.now();
878
+ const byKey = new Map();
879
+ const raw = await fs.readFile(preambleLogPath, "utf8");
880
+ const lines = raw
881
+ .split("\n")
882
+ .map((line) => line.trim())
883
+ .filter((line) => line.length > 0);
884
+ for (const line of lines) {
885
+ try {
886
+ const parsed = JSON.parse(line);
887
+ const tsRaw = parsed.ts;
888
+ const stageRaw = parsed.stage;
889
+ const triggerRaw = parsed.trigger;
890
+ const hashRaw = parsed.hash;
891
+ if (typeof tsRaw !== "string" ||
892
+ typeof stageRaw !== "string" ||
893
+ typeof triggerRaw !== "string" ||
894
+ typeof hashRaw !== "string") {
895
+ malformedEntries += 1;
896
+ continue;
897
+ }
898
+ const stamp = Date.parse(tsRaw);
899
+ if (!Number.isFinite(stamp)) {
900
+ malformedEntries += 1;
901
+ continue;
902
+ }
903
+ if (now - stamp > 24 * 60 * 60 * 1000) {
904
+ continue;
905
+ }
906
+ parsedEntries += 1;
907
+ const key = `${stageRaw}|${triggerRaw}|${hashRaw}`;
908
+ const bucket = byKey.get(key) ?? [];
909
+ bucket.push(stamp);
910
+ byKey.set(key, bucket);
911
+ }
912
+ catch {
913
+ malformedEntries += 1;
914
+ }
915
+ }
916
+ for (const stamps of byKey.values()) {
917
+ stamps.sort((a, b) => a - b);
918
+ for (let i = 1; i < stamps.length; i += 1) {
919
+ if (stamps[i] - stamps[i - 1] < PREAMBLE_COOLDOWN_MS) {
920
+ duplicateHits += 1;
921
+ }
922
+ }
923
+ }
924
+ }
925
+ catch {
926
+ malformedEntries += 1;
927
+ }
928
+ checks.push({
929
+ name: "warning:preamble:dedup",
930
+ ok: true,
931
+ details: duplicateHits > 0
932
+ ? `warning: detected ${duplicateHits} repeated preamble emission(s) inside ${Math.floor(PREAMBLE_COOLDOWN_MS / 60000)}m cooldown window`
933
+ : parsedEntries > 0
934
+ ? `preamble budget healthy (${parsedEntries} recent preamble entry/entries checked)`
935
+ : malformedEntries > 0
936
+ ? `warning: preamble log exists but entries are malformed (${malformedEntries} line(s) ignored)`
937
+ : "preamble log is empty; no recent preamble emissions recorded"
938
+ });
939
+ }
843
940
  let flowState = await readFlowState(projectRoot);
844
941
  if (options.reconcileCurrentStageGates === true) {
845
942
  const reconciliation = await reconcileAndWriteCurrentStageGateCatalog(projectRoot);
@@ -911,7 +1008,9 @@ export async function doctorChecks(projectRoot, options = {}) {
911
1008
  name: "warning:delegation:waived",
912
1009
  ok: true,
913
1010
  details: delegation.waived.length > 0
914
- ? `warning: waived mandatory delegations for stage "${flowState.currentStage}": ${delegation.waived.join(", ")}`
1011
+ ? `warning: waived mandatory delegations for stage "${flowState.currentStage}": ${delegation.waived.join(", ")}${delegation.autoWaived.length > 0
1012
+ ? ` (auto-waived due to harness limitation: ${delegation.autoWaived.join(", ")})`
1013
+ : ""}`
915
1014
  : "no waived mandatory delegations for current stage"
916
1015
  });
917
1016
  checks.push({
@@ -1025,8 +1124,17 @@ export async function doctorChecks(projectRoot, options = {}) {
1025
1124
  });
1026
1125
  const policy = await policyChecks(projectRoot, { harnesses: configuredHarnesses });
1027
1126
  checks.push(...policy);
1028
- return checks;
1127
+ return checks.map((check) => {
1128
+ const metadata = doctorCheckMetadata(check.name);
1129
+ return {
1130
+ ...check,
1131
+ severity: check.severity ?? metadata.severity,
1132
+ summary: check.summary ?? metadata.summary,
1133
+ fix: check.fix ?? metadata.fix,
1134
+ docRef: check.docRef ?? metadata.docRef
1135
+ };
1136
+ });
1029
1137
  }
1030
1138
  export function doctorSucceeded(checks) {
1031
- return checks.every((check) => check.ok);
1139
+ return checks.every((check) => check.ok || check.severity !== "error");
1032
1140
  }
@@ -4,8 +4,15 @@ export declare const CCLAW_MARKER_END = "<!-- cclaw-end -->";
4
4
  export interface HarnessAdapter {
5
5
  id: HarnessId;
6
6
  commandDir: string;
7
+ capabilities: {
8
+ nativeSubagentDispatch: "full" | "partial" | "none";
9
+ hookSurface: "full" | "plugin" | "limited" | "none";
10
+ structuredAsk: "AskUserQuestion" | "AskQuestion" | "plain-text";
11
+ };
7
12
  }
8
13
  export declare const HARNESS_ADAPTERS: Record<HarnessId, HarnessAdapter>;
14
+ export type HarnessTier = "tier1" | "tier2" | "tier3";
15
+ export declare function harnessTier(harnessId: HarnessId): HarnessTier;
9
16
  /** Removes the cclaw AGENTS.md block. */
10
17
  export declare function stripCclawBlock(content: string): string;
11
18
  export declare function removeCclawFromAgentsMd(projectRoot: string): Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { RUNTIME_ROOT } from "./constants.js";
4
- import { CCLAW_AGENTS, agentMarkdown } from "./content/agents.js";
4
+ import { CCLAW_AGENTS, agentMarkdown } from "./content/core-agents.js";
5
5
  import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
6
6
  export const CCLAW_MARKER_START = "<!-- cclaw-start -->";
7
7
  export const CCLAW_MARKER_END = "<!-- cclaw-end -->";
@@ -12,11 +12,57 @@ const RUNTIME_AGENTS_BLOCK_SOURCE = `${escapeRegExp(CCLAW_MARKER_START)}[\\s\\S]
12
12
  const RUNTIME_AGENTS_BLOCK_PATTERN = new RegExp(RUNTIME_AGENTS_BLOCK_SOURCE, "u");
13
13
  const RUNTIME_AGENTS_BLOCK_GLOBAL_PATTERN = new RegExp(RUNTIME_AGENTS_BLOCK_SOURCE, "gu");
14
14
  export const HARNESS_ADAPTERS = {
15
- claude: { id: "claude", commandDir: ".claude/commands" },
16
- cursor: { id: "cursor", commandDir: ".cursor/commands" },
17
- opencode: { id: "opencode", commandDir: ".opencode/commands" },
18
- codex: { id: "codex", commandDir: ".codex/commands" }
15
+ claude: {
16
+ id: "claude",
17
+ commandDir: ".claude/commands",
18
+ capabilities: {
19
+ nativeSubagentDispatch: "full",
20
+ hookSurface: "full",
21
+ structuredAsk: "AskUserQuestion"
22
+ }
23
+ },
24
+ cursor: {
25
+ id: "cursor",
26
+ commandDir: ".cursor/commands",
27
+ capabilities: {
28
+ nativeSubagentDispatch: "partial",
29
+ hookSurface: "full",
30
+ structuredAsk: "AskQuestion"
31
+ }
32
+ },
33
+ opencode: {
34
+ id: "opencode",
35
+ commandDir: ".opencode/commands",
36
+ capabilities: {
37
+ nativeSubagentDispatch: "partial",
38
+ hookSurface: "plugin",
39
+ structuredAsk: "plain-text"
40
+ }
41
+ },
42
+ codex: {
43
+ id: "codex",
44
+ commandDir: ".codex/commands",
45
+ capabilities: {
46
+ nativeSubagentDispatch: "none",
47
+ hookSurface: "full",
48
+ structuredAsk: "plain-text"
49
+ }
50
+ }
19
51
  };
52
+ export function harnessTier(harnessId) {
53
+ const capabilities = HARNESS_ADAPTERS[harnessId].capabilities;
54
+ if (capabilities.nativeSubagentDispatch === "full" &&
55
+ capabilities.structuredAsk !== "plain-text" &&
56
+ capabilities.hookSurface === "full") {
57
+ return "tier1";
58
+ }
59
+ if (capabilities.hookSurface === "full" ||
60
+ capabilities.hookSurface === "plugin" ||
61
+ capabilities.nativeSubagentDispatch === "partial") {
62
+ return "tier2";
63
+ }
64
+ return "tier3";
65
+ }
20
66
  function agentsMdBlock() {
21
67
  return `${CCLAW_MARKER_START}
22
68
  ## Cclaw — Workflow Adapter
@@ -63,10 +109,6 @@ When in doubt, prefer **non-trivial** — the quick track is opt-in and only saf
63
109
  **Stage order:** brainstorm > scope > design > spec > plan > tdd > review > ship.
64
110
  \`/cc-next\` loads the right stage skill automatically. Gates must pass before handoff.
65
111
 
66
- ### Invocation Preamble (non-trivial turns)
67
-
68
- Before starting substantive work, emit a one-paragraph preamble: **Stage**, **Goal**, **Plan** (next 1–3 actions), **Guardrails**. Skip for pure questions, trivial edits, and dispatched subagent invocations.
69
-
70
112
  ### Verification Discipline
71
113
 
72
114
  No completion claims without fresh evidence. No "Done" / "All good" / "Tests pass" without running the command in this message. Failed tool calls are diagnostic data, not instructions.
@@ -78,7 +120,9 @@ If the same approach fails three times in a row (same command, same finding, sam
78
120
  ### Detail Level
79
121
 
80
122
  - This managed AGENTS block is intentionally minimal for cross-project use.
123
+ - Harness coverage is tiered: Tier1 (claude), Tier2 (cursor/opencode/codex), Tier3 (fallback/manual-only).
81
124
  - Detailed operating procedures live in \`.cclaw/skills/using-cclaw/SKILL.md\`.
125
+ - Preamble budget and cooldown rules live in \`.cclaw/references/protocols/ethos.md\`.
82
126
  - Subagent orchestration patterns: \`.cclaw/skills/subagent-dev/SKILL.md\` and \`.cclaw/skills/parallel-dispatch/SKILL.md\`.
83
127
  ${CCLAW_MARKER_END}`;
84
128
  }
@@ -0,0 +1,2 @@
1
+ import type { HarnessId } from "./types.js";
2
+ export declare function detectHarnesses(projectRoot: string): Promise<HarnessId[]>;
@@ -0,0 +1,45 @@
1
+ import path from "node:path";
2
+ import { exists } from "./fs-utils.js";
3
+ export async function detectHarnesses(projectRoot) {
4
+ const detected = [];
5
+ const claudeHints = [
6
+ path.join(projectRoot, ".claude"),
7
+ path.join(projectRoot, "CLAUDE.md")
8
+ ];
9
+ if (await anyExists(claudeHints)) {
10
+ detected.push("claude");
11
+ }
12
+ const cursorHints = [
13
+ path.join(projectRoot, ".cursor"),
14
+ path.join(projectRoot, ".cursor/rules")
15
+ ];
16
+ if (await anyExists(cursorHints)) {
17
+ detected.push("cursor");
18
+ }
19
+ const opencodeHints = [
20
+ path.join(projectRoot, ".opencode"),
21
+ path.join(projectRoot, "opencode.json"),
22
+ path.join(projectRoot, "opencode.jsonc"),
23
+ path.join(projectRoot, ".opencode/opencode.json"),
24
+ path.join(projectRoot, ".opencode/opencode.jsonc")
25
+ ];
26
+ if (await anyExists(opencodeHints)) {
27
+ detected.push("opencode");
28
+ }
29
+ const codexHints = [
30
+ path.join(projectRoot, ".codex"),
31
+ path.join(projectRoot, ".codex/hooks.json")
32
+ ];
33
+ if (await anyExists(codexHints)) {
34
+ detected.push("codex");
35
+ }
36
+ return detected;
37
+ }
38
+ async function anyExists(paths) {
39
+ for (const candidate of paths) {
40
+ if (await exists(candidate)) {
41
+ return true;
42
+ }
43
+ }
44
+ return false;
45
+ }
package/dist/install.js CHANGED
@@ -21,11 +21,15 @@ import { TDD_WAVE_WALKTHROUGH_MARKDOWN, stageSkillFolder, stageSkillMarkdown } f
21
21
  import { stageCommonGuidanceMarkdown } from "./content/stage-common-guidance.js";
22
22
  import { STAGE_EXAMPLES_REFERENCE_DIR, stageExamplesReferenceMarkdown } from "./content/examples.js";
23
23
  import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LANGUAGE_RULE_PACK_GENERATORS, LEGACY_LANGUAGE_RULE_PACK_FOLDERS, UTILITY_SKILL_FOLDERS, UTILITY_SKILL_MAP } from "./content/utility-skills.js";
24
+ import { RESEARCH_PLAYBOOKS } from "./content/research-playbooks.js";
24
25
  import { HARNESS_TOOL_REFS_DIR, HARNESS_TOOL_REFS_INDEX_MD, harnessToolRefMarkdown } from "./content/harness-tool-refs.js";
26
+ import { DOCTOR_REFERENCE_MARKDOWN } from "./content/doctor-references.js";
27
+ import { harnessIntegrationDocMarkdown } from "./content/harnesses-doc.js";
28
+ import { HOOK_EVENTS_BY_HARNESS, HOOK_SEMANTIC_EVENTS } from "./content/hook-events.js";
25
29
  import { createInitialFlowState } from "./flow-state.js";
26
30
  import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
27
31
  import { ensureGitignore, removeGitignorePatterns } from "./gitignore.js";
28
- import { HARNESS_ADAPTERS, syncHarnessShims, removeCclawFromAgentsMd } from "./harness-adapters.js";
32
+ import { HARNESS_ADAPTERS, harnessTier, syncHarnessShims, removeCclawFromAgentsMd } from "./harness-adapters.js";
29
33
  import { validateHookDocument } from "./hook-schema.js";
30
34
  import { ensureRunSystem, readFlowState } from "./runs.js";
31
35
  const OPENCODE_PLUGIN_REL_PATH = ".opencode/plugins/cclaw-plugin.mjs";
@@ -204,6 +208,10 @@ async function writeSkills(projectRoot, config) {
204
208
  const generator = UTILITY_SKILL_MAP[folder];
205
209
  await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), generator());
206
210
  }
211
+ // In-thread research procedures (no YAML frontmatter, not delegated personas).
212
+ for (const [fileName, markdown] of Object.entries(RESEARCH_PLAYBOOKS)) {
213
+ await writeFileSafe(runtimePath(projectRoot, "skills", "research", fileName), markdown);
214
+ }
207
215
  // Language rule packs live under .cclaw/rules/lang/<pack>.md. They are opt-in:
208
216
  // only the packs listed in config.languageRulePacks are materialised. Any
209
217
  // legacy per-language skill folders from v0.7.0 (.cclaw/skills/language-*)
@@ -231,6 +239,11 @@ async function writeSkills(projectRoot, config) {
231
239
  for (const harness of harnessIds) {
232
240
  await writeFileSafe(runtimePath(projectRoot, ...harnessRefsDir, `${harness}.md`), harnessToolRefMarkdown(harness));
233
241
  }
242
+ const doctorRefsDir = ["references", "doctor"];
243
+ for (const [fileName, markdown] of Object.entries(DOCTOR_REFERENCE_MARKDOWN)) {
244
+ await writeFileSafe(runtimePath(projectRoot, ...doctorRefsDir, fileName), markdown);
245
+ }
246
+ await writeFileSafe(runtimePath(projectRoot, "references", "harnesses.md"), harnessIntegrationDocMarkdown());
234
247
  }
235
248
  async function writeUtilityCommands(projectRoot) {
236
249
  await writeFileSafe(runtimePath(projectRoot, "commands", "learn.md"), learnCommandContract());
@@ -783,6 +796,10 @@ async function ensureSessionStateFiles(projectRoot) {
783
796
  if (!(await exists(knowledgeDigestPath))) {
784
797
  await writeFileSafe(knowledgeDigestPath, "# Knowledge digest (auto-generated)\n\n(no entries yet)\n");
785
798
  }
799
+ const preambleLogPath = path.join(stateDir, "preamble-log.jsonl");
800
+ if (!(await exists(preambleLogPath))) {
801
+ await writeFileSafe(preambleLogPath, "");
802
+ }
786
803
  }
787
804
  async function writeRulebook(projectRoot) {
788
805
  await writeFileSafe(runtimePath(projectRoot, "rules", "RULES.md"), RULEBOOK_MARKDOWN);
@@ -846,6 +863,33 @@ async function writeAdapterManifest(projectRoot, harnesses) {
846
863
  };
847
864
  await writeFileSafe(runtimePath(projectRoot, "adapters", "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
848
865
  }
866
+ async function writeHarnessGapsState(projectRoot, harnesses) {
867
+ const report = harnesses.map((harness) => {
868
+ const capabilities = HARNESS_ADAPTERS[harness].capabilities;
869
+ const hookMap = HOOK_EVENTS_BY_HARNESS[harness];
870
+ const missingHookEvents = HOOK_SEMANTIC_EVENTS.filter((eventName) => !hookMap[eventName]);
871
+ const missingCapabilities = [];
872
+ if (capabilities.nativeSubagentDispatch !== "full") {
873
+ missingCapabilities.push(`nativeSubagentDispatch:${capabilities.nativeSubagentDispatch}`);
874
+ }
875
+ if (capabilities.hookSurface !== "full") {
876
+ missingCapabilities.push(`hookSurface:${capabilities.hookSurface}`);
877
+ }
878
+ if (capabilities.structuredAsk === "plain-text") {
879
+ missingCapabilities.push("structuredAsk:none");
880
+ }
881
+ return {
882
+ harness,
883
+ tier: harnessTier(harness),
884
+ missingCapabilities,
885
+ missingHookEvents
886
+ };
887
+ });
888
+ await writeFileSafe(runtimePath(projectRoot, "state", "harness-gaps.json"), `${JSON.stringify({
889
+ generatedAt: new Date().toISOString(),
890
+ harnesses: report
891
+ }, null, 2)}\n`);
892
+ }
849
893
  async function cleanLegacyArtifacts(projectRoot) {
850
894
  // Remove deprecated utility skill folders from older releases.
851
895
  for (const legacyFolder of [
@@ -878,6 +922,23 @@ async function cleanLegacyArtifacts(projectRoot) {
878
922
  catch {
879
923
  // best-effort cleanup
880
924
  }
925
+ // Core-5 migration: remove deprecated generated agent personas.
926
+ for (const legacyAgentFile of [
927
+ "spec-reviewer.md",
928
+ "code-reviewer.md",
929
+ "repo-research-analyst.md",
930
+ "learnings-researcher.md",
931
+ "framework-docs-researcher.md",
932
+ "best-practices-researcher.md",
933
+ "git-history-analyzer.md"
934
+ ]) {
935
+ try {
936
+ await fs.rm(runtimePath(projectRoot, "agents", legacyAgentFile), { force: true });
937
+ }
938
+ catch {
939
+ // best-effort cleanup
940
+ }
941
+ }
881
942
  for (const legacyPlugin of [
882
943
  path.join(projectRoot, ".opencode/plugins/viby-plugin.mjs"),
883
944
  path.join(projectRoot, ".opencode/plugins/opencode-plugin.mjs"),
@@ -950,6 +1011,7 @@ async function materializeRuntime(projectRoot, config, forceStateReset) {
950
1011
  await ensureRunSystem(projectRoot, { createIfMissing: false });
951
1012
  await ensureSessionStateFiles(projectRoot);
952
1013
  await writeAdapterManifest(projectRoot, harnesses);
1014
+ await writeHarnessGapsState(projectRoot, harnesses);
953
1015
  await ensureKnowledgeStore(projectRoot);
954
1016
  await ensureCustomSkillsScaffold(projectRoot);
955
1017
  await writeHooks(projectRoot, config);
package/dist/policy.js CHANGED
@@ -98,6 +98,11 @@ export async function policyChecks(projectRoot, options = {}) {
98
98
  { file: runtimeFile("skills/parallel-dispatch/SKILL.md"), needle: "Review Army", name: "utility_skill:parallel:review_army" },
99
99
  { file: runtimeFile("skills/parallel-dispatch/SKILL.md"), needle: "Reconciliation", name: "utility_skill:parallel:reconciliation" },
100
100
  { file: runtimeFile("skills/parallel-dispatch/SKILL.md"), needle: "## Model & Harness Routing Notes", name: "utility_skill:parallel:routing_notes" },
101
+ { file: runtimeFile("skills/research/repo-scan.md"), needle: "# Repo Scan Playbook", name: "utility_skill:research:repo_scan" },
102
+ { file: runtimeFile("skills/research/learnings-lookup.md"), needle: "# Learnings Lookup Playbook", name: "utility_skill:research:learnings_lookup" },
103
+ { file: runtimeFile("skills/research/framework-docs-lookup.md"), needle: "# Framework Docs Lookup Playbook", name: "utility_skill:research:framework_docs_lookup" },
104
+ { file: runtimeFile("skills/research/best-practices-lookup.md"), needle: "# Best Practices Lookup Playbook", name: "utility_skill:research:best_practices_lookup" },
105
+ { file: runtimeFile("skills/research/git-history.md"), needle: "# Git History Playbook", name: "utility_skill:research:git_history" },
101
106
  { file: runtimeFile("skills/session/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:session:hard_gate" },
102
107
  { file: runtimeFile("skills/session/SKILL.md"), needle: "## Session Start Protocol", name: "utility_skill:session:start" },
103
108
  { file: runtimeFile("skills/session/SKILL.md"), needle: "## Session Stop Protocol", name: "utility_skill:session:stop" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,6 +23,7 @@
23
23
  "test:coverage": "vitest run --coverage",
24
24
  "smoke:runtime": "npm run build && node scripts/smoke-init.mjs",
25
25
  "lint:hooks": "npm run build && node scripts/lint-generated-hooks.mjs",
26
+ "build:harness-docs": "npm run build && node scripts/build-harness-docs.mjs",
26
27
  "build:plugin-manifests": "npm run build && node scripts/build-plugin-manifests.mjs",
27
28
  "release:check": "npm run build && npm run test && node scripts/lint-generated-hooks.mjs && node scripts/build-plugin-manifests.mjs && npm pack --dry-run && node scripts/smoke-init.mjs",
28
29
  "release:bundle": "npm run release:check && npm pack"
@@ -1,48 +0,0 @@
1
- /**
2
- * Agent persona content for Cclaw.
3
- *
4
- * Cclaw emits markdown agent definitions (`.md` with YAML frontmatter) that harnesses
5
- * use for specialist delegation. Agents are isolated context windows with constrained
6
- * tools; skills remain procedural recipes.
7
- */
8
- export interface AgentDefinition {
9
- /** Kebab-case identifier, e.g. `"spec-reviewer"`. */
10
- name: string;
11
- /** When to invoke — include PROACTIVE / MUST BE USED style guidance for harnesses. */
12
- description: string;
13
- /** Allowed tools for this agent (harness-specific names). */
14
- tools: string[];
15
- /** Model tier for routing cost/latency vs depth. */
16
- model: "fast" | "balanced" | "deep";
17
- /** How the harness should treat activation relative to flow context. */
18
- activation: "proactive" | "on-demand" | "mandatory";
19
- /** Cclaw flow stages this agent is designed to support. */
20
- relatedStages: string[];
21
- /** Markdown body rendered below the YAML frontmatter. */
22
- body: string;
23
- }
24
- /**
25
- * Canonical specialist agents Cclaw can materialize under `.cclaw/agents/`.
26
- */
27
- export declare const CCLAW_AGENTS: AgentDefinition[];
28
- /**
29
- * Render a complete Cclaw agent markdown file (YAML frontmatter + body).
30
- */
31
- export declare function agentMarkdown(agent: AgentDefinition): string;
32
- /**
33
- * Markdown table mapping Cclaw stage entry points to specialist agents.
34
- */
35
- export declare function agentRoutingTable(): string;
36
- /**
37
- * Cost tier routing: keep heavy reasoning on the \`deep\` tier (planner, a
38
- * single post-review reconciliation), push read-only research and narrow
39
- * machine-only checks to the \`fast\` tier, and default review to \`balanced\`.
40
- * This table is emitted into AGENTS.md so harness users understand why
41
- * certain specialists are automatically fan-out-able without blowing the
42
- * context budget.
43
- */
44
- export declare function agentCostTierTable(): string;
45
- /**
46
- * AGENTS.md-ready section describing Cclaw’s specialist delegation model.
47
- */
48
- export declare function agentsAgentsMdBlock(): string;