cclaw-cli 0.51.24 → 0.51.26

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 (45) hide show
  1. package/README.md +135 -414
  2. package/dist/artifact-linter.js +10 -6
  3. package/dist/config.d.ts +1 -1
  4. package/dist/config.js +28 -3
  5. package/dist/content/core-agents.d.ts +110 -0
  6. package/dist/content/core-agents.js +255 -3
  7. package/dist/content/examples.js +8 -5
  8. package/dist/content/harness-doc.d.ts +1 -0
  9. package/dist/content/harness-doc.js +3 -0
  10. package/dist/content/hooks.d.ts +1 -0
  11. package/dist/content/hooks.js +189 -0
  12. package/dist/content/next-command.js +10 -6
  13. package/dist/content/reference-patterns.d.ts +18 -0
  14. package/dist/content/reference-patterns.js +391 -0
  15. package/dist/content/skills.js +42 -36
  16. package/dist/content/stage-common-guidance.js +19 -3
  17. package/dist/content/stage-schema.d.ts +12 -0
  18. package/dist/content/stage-schema.js +184 -28
  19. package/dist/content/stages/_lint-metadata/index.js +3 -2
  20. package/dist/content/stages/brainstorm.js +7 -3
  21. package/dist/content/stages/design.js +12 -3
  22. package/dist/content/stages/review.js +7 -5
  23. package/dist/content/stages/schema-types.d.ts +9 -2
  24. package/dist/content/stages/scope.js +8 -2
  25. package/dist/content/stages/ship.js +3 -2
  26. package/dist/content/stages/tdd.js +18 -13
  27. package/dist/content/start-command.js +3 -2
  28. package/dist/content/status-command.js +17 -6
  29. package/dist/content/subagents.js +286 -40
  30. package/dist/content/templates.js +64 -3
  31. package/dist/content/tree-command.js +7 -1
  32. package/dist/delegation.d.ts +34 -1
  33. package/dist/delegation.js +168 -8
  34. package/dist/doctor-registry.js +9 -0
  35. package/dist/doctor.js +121 -6
  36. package/dist/gate-evidence.js +25 -2
  37. package/dist/harness-adapters.d.ts +6 -0
  38. package/dist/harness-adapters.js +28 -4
  39. package/dist/install.js +5 -10
  40. package/dist/internal/advance-stage.js +179 -26
  41. package/dist/run-persistence.js +21 -3
  42. package/dist/tdd-verification-evidence.d.ts +17 -0
  43. package/dist/tdd-verification-evidence.js +43 -0
  44. package/dist/types.d.ts +10 -0
  45. package/package.json +1 -1
package/dist/doctor.js CHANGED
@@ -13,7 +13,7 @@ import { policyChecks } from "./policy.js";
13
13
  import { CorruptFlowStateError, readFlowState } from "./runs.js";
14
14
  import { createInitialFlowState, skippedStagesForTrack } from "./flow-state.js";
15
15
  import { FLOW_STAGES, TRACK_STAGES } from "./types.js";
16
- import { checkMandatoryDelegations } from "./delegation.js";
16
+ import { checkMandatoryDelegations, readDelegationEvents } from "./delegation.js";
17
17
  import { buildTraceMatrix } from "./trace-matrix.js";
18
18
  import { classifyReconciliationNotices, reconcileAndWriteCurrentStageGateCatalog, readReconciliationNotices, RECONCILIATION_NOTICES_REL_PATH, verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence } from "./gate-evidence.js";
19
19
  import { parseTddCycleLog, validateTddCycleOrder } from "./tdd-cycle.js";
@@ -131,6 +131,20 @@ async function generatedCliEntrypointsOk(projectRoot) {
131
131
  : "local CLI entrypoint check skipped because generated hook scripts are absent"
132
132
  };
133
133
  }
134
+ function expectedArtifactPrefix(stage) {
135
+ const index = FLOW_STAGES.indexOf(stage);
136
+ return `${String(index + 1).padStart(2, "0")}-`;
137
+ }
138
+ function artifactStageFromFileName(fileName) {
139
+ if (!fileName.endsWith(".md"))
140
+ return null;
141
+ for (const stage of FLOW_STAGES) {
142
+ if (fileName.startsWith(expectedArtifactPrefix(stage))) {
143
+ return stage;
144
+ }
145
+ }
146
+ return null;
147
+ }
134
148
  function extractUserPromptFromIdeaArtifact(markdown) {
135
149
  const normalized = markdown.replace(/\r\n?/gu, "\n");
136
150
  const heading = /^##\s+User prompt\s*$/imu.exec(normalized);
@@ -291,6 +305,20 @@ function normalizeOpenCodePluginEntry(entry) {
291
305
  }
292
306
  return null;
293
307
  }
308
+ function generatedAgentShape(content, kind, agentName) {
309
+ if (kind === "opencode") {
310
+ return content.includes("mode: subagent") && content.includes(`# ${agentName}`) && content.includes("STRICT_RETURN_SCHEMA");
311
+ }
312
+ return content.includes(`name = "${agentName}"`) && content.includes("developer_instructions") && content.includes("STRICT_RETURN_SCHEMA");
313
+ }
314
+ function harnessRealityLabel(harness) {
315
+ const adapter = HARNESS_ADAPTERS[harness];
316
+ const declaredSupport = adapter.capabilities.nativeSubagentDispatch;
317
+ const runtimeLaunch = harness === "opencode" || harness === "codex" ? "prompt-level launch" : declaredSupport === "generic" ? "generic Task launch" : "native tool launch";
318
+ const proofRequired = adapter.capabilities.subagentFallback === "native" ? "dispatchId+spanId+ack" : "evidenceRefs";
319
+ const proofSource = harness === "opencode" ? ".opencode/agents + delegation-events.jsonl" : harness === "codex" ? ".codex/agents + delegation-events.jsonl" : ".cclaw/state/delegation-log.json";
320
+ return `declaredSupport=${declaredSupport}; runtimeLaunch=${runtimeLaunch}; proofRequired=${proofRequired}; proofSource=${proofSource}`;
321
+ }
294
322
  const OPENCODE_PLUGIN_REL_PATH = ".opencode/plugins/cclaw-plugin.mjs";
295
323
  function opencodeConfigCandidates(projectRoot) {
296
324
  return [
@@ -766,6 +794,14 @@ export async function doctorChecks(projectRoot, options = {}) {
766
794
  }
767
795
  }
768
796
  }
797
+ for (const harness of configuredHarnesses) {
798
+ checks.push({
799
+ name: `harness:reality:${harness}`,
800
+ ok: true,
801
+ severity: "info",
802
+ details: harnessRealityLabel(harness)
803
+ });
804
+ }
769
805
  const agentsFile = path.join(projectRoot, "AGENTS.md");
770
806
  let agentsBlockOk = false;
771
807
  if (await exists(agentsFile)) {
@@ -870,12 +906,39 @@ export async function doctorChecks(projectRoot, options = {}) {
870
906
  details: agentPath
871
907
  });
872
908
  }
909
+ for (const agent of CCLAW_AGENTS) {
910
+ if (configuredHarnesses.includes("opencode")) {
911
+ const agentPath = path.join(projectRoot, ".opencode", "agents", `${agent.name}.md`);
912
+ let ok = false;
913
+ if (await exists(agentPath)) {
914
+ ok = generatedAgentShape(await fs.readFile(agentPath, "utf8"), "opencode", agent.name);
915
+ }
916
+ checks.push({
917
+ name: `agent:opencode:${agent.name}:shape`,
918
+ ok,
919
+ details: `${agentPath} must be a generated OpenCode subagent with mode: subagent and strict return schema`
920
+ });
921
+ }
922
+ if (configuredHarnesses.includes("codex")) {
923
+ const agentPath = path.join(projectRoot, ".codex", "agents", `${agent.name}.toml`);
924
+ let ok = false;
925
+ if (await exists(agentPath)) {
926
+ ok = generatedAgentShape(await fs.readFile(agentPath, "utf8"), "codex", agent.name);
927
+ }
928
+ checks.push({
929
+ name: `agent:codex:${agent.name}:shape`,
930
+ ok,
931
+ details: `${agentPath} must be a generated Codex custom agent TOML with developer_instructions and strict return schema`
932
+ });
933
+ }
934
+ }
873
935
  // Hook scripts
874
936
  for (const script of [
875
937
  "run-hook.mjs",
876
938
  "run-hook.cmd",
877
939
  "stage-complete.mjs",
878
940
  "start-flow.mjs",
941
+ "delegation-record.mjs",
879
942
  "opencode-plugin.mjs"
880
943
  ]) {
881
944
  const scriptPath = path.join(projectRoot, RUNTIME_ROOT, "hooks", script);
@@ -1630,13 +1693,21 @@ export async function doctorChecks(projectRoot, options = {}) {
1630
1693
  });
1631
1694
  const artifactsRoot = path.join(projectRoot, RUNTIME_ROOT, "artifacts");
1632
1695
  let artifactPlaceholderHits = [];
1696
+ let duplicateArtifactGroups = [];
1633
1697
  if (await exists(artifactsRoot)) {
1634
1698
  try {
1635
1699
  const entries = await fs.readdir(artifactsRoot, { withFileTypes: true });
1636
1700
  const placeholderPattern = /\b(?:TODO|TBD|FIXME)\b|<fill-in>|<your-.*-here>/giu;
1701
+ const stageArtifactFiles = new Map();
1637
1702
  for (const entry of entries) {
1638
1703
  if (!entry.isFile() || !entry.name.endsWith(".md"))
1639
1704
  continue;
1705
+ const stageForArtifact = artifactStageFromFileName(entry.name);
1706
+ if (stageForArtifact) {
1707
+ const files = stageArtifactFiles.get(stageForArtifact) ?? [];
1708
+ files.push(entry.name);
1709
+ stageArtifactFiles.set(stageForArtifact, files);
1710
+ }
1640
1711
  const filePath = path.join(artifactsRoot, entry.name);
1641
1712
  const content = await fs.readFile(filePath, "utf8");
1642
1713
  const matchCount = (content.match(placeholderPattern) ?? []).length;
@@ -1644,9 +1715,13 @@ export async function doctorChecks(projectRoot, options = {}) {
1644
1715
  artifactPlaceholderHits.push(`${entry.name}:${matchCount}`);
1645
1716
  }
1646
1717
  }
1718
+ duplicateArtifactGroups = [...stageArtifactFiles.entries()]
1719
+ .filter(([, files]) => files.length > 1)
1720
+ .map(([stageName, files]) => `${stageName}: ${files.sort().join(", ")}`);
1647
1721
  }
1648
1722
  catch {
1649
1723
  artifactPlaceholderHits = [];
1724
+ duplicateArtifactGroups = [];
1650
1725
  }
1651
1726
  }
1652
1727
  checks.push({
@@ -1656,13 +1731,20 @@ export async function doctorChecks(projectRoot, options = {}) {
1656
1731
  ? "no TODO/TBD/FIXME placeholder markers found in active artifacts"
1657
1732
  : `warning: placeholder markers detected in active artifacts (${artifactPlaceholderHits.join(", ")}). Clear before marking completion.`
1658
1733
  });
1734
+ checks.push({
1735
+ name: "warning:artifacts:duplicate_stage_artifacts",
1736
+ ok: duplicateArtifactGroups.length === 0,
1737
+ details: duplicateArtifactGroups.length === 0
1738
+ ? "no duplicate stage artifacts detected in active artifacts"
1739
+ : `warning: duplicate stage artifacts detected (${duplicateArtifactGroups.join("; ")}). The resolver uses the newest matching file; archive or rename stale copies to avoid ambiguous operator handoff.`
1740
+ });
1659
1741
  const staleStages = Object.keys(flowState.staleStages).filter((value) => FLOW_STAGES.includes(value));
1660
1742
  checks.push({
1661
1743
  name: "state:stale_stages_resolved",
1662
1744
  ok: staleStages.length === 0,
1663
1745
  details: staleStages.length === 0
1664
1746
  ? "no stale stages pending acknowledgement"
1665
- : `stale stages pending acknowledgement: ${staleStages.join(", ")}`
1747
+ : `stale stages pending acknowledgement: ${staleStages.join(", ")}. Re-run the current stale stage, then clear it with cclaw internal rewind --ack ${flowState.currentStage}.`
1666
1748
  });
1667
1749
  const retroGateStatus = await evaluateRetroGate(projectRoot, flowState);
1668
1750
  checks.push({
@@ -1732,18 +1814,51 @@ export async function doctorChecks(projectRoot, options = {}) {
1732
1814
  ok: archiveIntegrity.ok,
1733
1815
  details: archiveIntegrity.details
1734
1816
  });
1817
+ const currentGateState = flowState.stageGateCatalog[flowState.currentStage];
1818
+ const currentStageUntouched = flowState.completedStages.length === 0 &&
1819
+ flowState.rewinds.length === 0 &&
1820
+ Object.keys(flowState.guardEvidence).length === 0 &&
1821
+ (currentGateState?.passed.length ?? 0) === 0 &&
1822
+ (currentGateState?.blocked.length ?? 0) === 0;
1735
1823
  const delegation = await checkMandatoryDelegations(projectRoot, flowState.currentStage, {
1736
1824
  repairFeatureSystem: false
1737
1825
  });
1826
+ const delegationEvents = await readDelegationEvents(projectRoot);
1827
+ const delegationSatisfiedForDoctor = currentStageUntouched || delegation.satisfied;
1738
1828
  const missingEvidenceNote = delegation.missingEvidence && delegation.missingEvidence.length > 0
1739
1829
  ? ` (role-switch rows without evidenceRefs: ${delegation.missingEvidence.join(", ")})`
1740
1830
  : "";
1741
1831
  checks.push({
1742
1832
  name: "delegation:mandatory:current_stage",
1743
- ok: delegation.satisfied,
1744
- details: delegation.satisfied
1745
- ? `All mandatory delegations satisfied for stage "${flowState.currentStage}" (mode: ${delegation.expectedMode})`
1746
- : `Missing mandatory delegations for stage "${flowState.currentStage}": ${delegation.missing.join(", ")}${missingEvidenceNote}`
1833
+ ok: delegationSatisfiedForDoctor,
1834
+ details: currentStageUntouched
1835
+ ? `mandatory delegation check deferred for untouched stage "${flowState.currentStage}"; stage-complete enforces it when work begins`
1836
+ : delegation.satisfied
1837
+ ? `All mandatory delegations satisfied for stage "${flowState.currentStage}" (mode: ${delegation.expectedMode})`
1838
+ : `Missing mandatory delegations for stage "${flowState.currentStage}": ${delegation.missing.join(", ")}${missingEvidenceNote}; missingDispatchProof=${delegation.missingDispatchProof.join(", ")}; staleWorkers=${delegation.staleWorkers.join(", ")}; corruptEventLines=${delegation.corruptEventLines.join(", ")}`
1839
+ });
1840
+ checks.push({
1841
+ name: "delegation:events:parse",
1842
+ ok: delegationEvents.corruptLines.length === 0,
1843
+ details: delegationEvents.corruptLines.length === 0
1844
+ ? `${RUNTIME_ROOT}/state/delegation-events.jsonl parsed successfully (${delegationEvents.events.length} event(s))`
1845
+ : `corrupt delegation event line(s): ${delegationEvents.corruptLines.join(", ")}`
1846
+ });
1847
+ checks.push({
1848
+ name: "delegation:proof:current_stage",
1849
+ ok: currentStageUntouched || delegation.missingDispatchProof.length === 0,
1850
+ details: currentStageUntouched
1851
+ ? `dispatch proof check deferred for untouched stage "${flowState.currentStage}"`
1852
+ : delegation.missingDispatchProof.length === 0
1853
+ ? `no dispatch proof gaps for current stage "${flowState.currentStage}"`
1854
+ : `isolated completions missing dispatchId/dispatchSurface/agentDefinitionPath/ackTs/completedTs: ${delegation.missingDispatchProof.join(", ")}`
1855
+ });
1856
+ checks.push({
1857
+ name: "warning:delegation:legacy_inferred_completions",
1858
+ ok: true,
1859
+ details: delegation.legacyInferredCompletions.length > 0
1860
+ ? `warning: legacy inferred isolated completion rows lack event-log proof: ${delegation.legacyInferredCompletions.join(", ")}`
1861
+ : "no legacy inferred isolated completions for current stage"
1747
1862
  });
1748
1863
  checks.push({
1749
1864
  name: "warning:delegation:waived",
@@ -9,6 +9,7 @@ import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
9
9
  import { detectPublicApiChanges } from "./internal/detect-public-api-changes.js";
10
10
  import { readFlowState, writeFlowState } from "./runs.js";
11
11
  import { parseTddCycleLog, validateTddCycleOrder } from "./tdd-cycle.js";
12
+ import { validateTddVerificationEvidence } from "./tdd-verification-evidence.js";
12
13
  import { buildTraceMatrix } from "./trace-matrix.js";
13
14
  import { FLOW_STAGES } from "./types.js";
14
15
  async function currentStageArtifactExists(projectRoot, stage, track) {
@@ -36,6 +37,22 @@ async function readArtifactMarkdown(projectRoot, artifactFile) {
36
37
  }
37
38
  return null;
38
39
  }
40
+ async function readStageArtifactMarkdown(projectRoot, stage, track) {
41
+ const resolved = await resolveArtifactPath(stage, {
42
+ projectRoot,
43
+ track,
44
+ intent: "read"
45
+ });
46
+ if (!(await exists(resolved.absPath))) {
47
+ return null;
48
+ }
49
+ try {
50
+ return await fs.readFile(resolved.absPath, "utf8");
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ }
39
56
  function unique(values) {
40
57
  return [...new Set(values)];
41
58
  }
@@ -263,6 +280,12 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
263
280
  issues.push(`passed gate "${gateId}" is missing guardEvidence entry.`);
264
281
  continue;
265
282
  }
283
+ if (stage === "tdd" && gateId === "tdd_verified_before_complete") {
284
+ const verification = await validateTddVerificationEvidence(projectRoot, evidence);
285
+ if (!verification.ok) {
286
+ issues.push(`tdd verification gate blocked (${gateId}): ${verification.issues.join(" ")}`);
287
+ }
288
+ }
266
289
  const discoveredCommandIssue = await verifyDiscoveredCommandEvidence(projectRoot, stage, gateId, flowState);
267
290
  if (discoveredCommandIssue) {
268
291
  issues.push(discoveredCommandIssue);
@@ -336,7 +359,7 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
336
359
  if (stage === "design") {
337
360
  const researchGateRequired = schema.requiredGates.some((gate) => gate.id === "design_research_complete" && gate.tier === "required");
338
361
  if (researchGateRequired) {
339
- const designMarkdown = await readArtifactMarkdown(projectRoot, "03-design.md");
362
+ const designMarkdown = await readStageArtifactMarkdown(projectRoot, "design", flowState.track);
340
363
  const inlineResearchBody = designMarkdown
341
364
  ? extractMarkdownSectionBody(designMarkdown, "Research Fleet Synthesis")
342
365
  : null;
@@ -354,7 +377,7 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
354
377
  const inlineResearchComplete = inlineResearchLines.length > 0;
355
378
  const researchMarkdown = await readArtifactMarkdown(projectRoot, "02a-research.md");
356
379
  if (!inlineResearchComplete && !researchMarkdown) {
357
- issues.push("design research gate blocked (design_research_complete): fill `Research Fleet Synthesis` in `.cclaw/artifacts/03-design.md`, or write `.cclaw/artifacts/02a-research.md` for deep/high-risk research.");
380
+ issues.push("design research gate blocked (design_research_complete): fill `Research Fleet Synthesis` in the active design artifact, or write `.cclaw/artifacts/02a-research.md` for deep/high-risk research.");
358
381
  }
359
382
  else if (researchMarkdown) {
360
383
  const missingSections = [];
@@ -37,6 +37,12 @@ export type SubagentFallback =
37
37
  export type ShimKind = "command" | "skill";
38
38
  export interface HarnessAdapter {
39
39
  id: HarnessId;
40
+ reality: {
41
+ declaredSupport: "full" | "generic" | "partial" | "none";
42
+ runtimeLaunch: string;
43
+ proofRequired: string;
44
+ proofSource: string;
45
+ };
40
46
  /**
41
47
  * Root directory where cclaw writes `/cc*` entry points.
42
48
  *
@@ -71,6 +71,12 @@ export function harnessShimSkillNames() {
71
71
  export const HARNESS_ADAPTERS = {
72
72
  claude: {
73
73
  id: "claude",
74
+ reality: {
75
+ declaredSupport: "full",
76
+ runtimeLaunch: "native Task launch",
77
+ proofRequired: "spanId+dispatchId or workerRunId+ACK for isolated completion",
78
+ proofSource: ".cclaw/state/delegation-events.jsonl plus delegation-log.json"
79
+ },
74
80
  commandDir: ".claude/commands",
75
81
  shimKind: "command",
76
82
  capabilities: {
@@ -82,6 +88,12 @@ export const HARNESS_ADAPTERS = {
82
88
  },
83
89
  cursor: {
84
90
  id: "cursor",
91
+ reality: {
92
+ declaredSupport: "generic",
93
+ runtimeLaunch: "generic Task/Subagent launch with cclaw role prompt",
94
+ proofRequired: "spanId+dispatchId/evidenceRefs for generic-dispatch completion",
95
+ proofSource: ".cclaw/state/delegation-events.jsonl plus artifact evidenceRefs"
96
+ },
85
97
  commandDir: ".cursor/commands",
86
98
  shimKind: "command",
87
99
  capabilities: {
@@ -97,6 +109,12 @@ export const HARNESS_ADAPTERS = {
97
109
  },
98
110
  opencode: {
99
111
  id: "opencode",
112
+ reality: {
113
+ declaredSupport: "full",
114
+ runtimeLaunch: "prompt-level launch via Task or @agent against generated .opencode/agents",
115
+ proofRequired: "spanId+dispatchId+ackTs+completedTs before isolated completion",
116
+ proofSource: ".opencode/agents/<agent>.md and .cclaw/state/delegation-events.jsonl"
117
+ },
100
118
  commandDir: ".opencode/commands",
101
119
  shimKind: "command",
102
120
  capabilities: {
@@ -119,6 +137,12 @@ export const HARNESS_ADAPTERS = {
119
137
  },
120
138
  codex: {
121
139
  id: "codex",
140
+ reality: {
141
+ declaredSupport: "full",
142
+ runtimeLaunch: "prompt-level launch by asking Codex to spawn generated custom agents",
143
+ proofRequired: "spanId+dispatchId+ackTs+completedTs before isolated completion",
144
+ proofSource: ".codex/agents/<agent>.toml and .cclaw/state/delegation-events.jsonl"
145
+ },
122
146
  // Codex CLI reads skills from the universal `.agents/skills/` path
123
147
  // (OpenAI Codex 0.89, Jan 2026). It does NOT have a native
124
148
  // `.codex/commands/*` slash-command discovery — cclaw installs
@@ -155,9 +179,9 @@ export function harnessDispatchSurface(harnessId) {
155
179
  case "cursor":
156
180
  return "Use Cursor Subagent/Task with a generic subagent_type (explore for read-only mapping, generalPurpose for broader work, shell/browser-use when specifically needed) and paste the cclaw role prompt; record fulfillmentMode: \"generic-dispatch\" with evidenceRefs.";
157
181
  case "opencode":
158
- return "Use OpenCode subagents: invoke the generated .opencode/agents/<agent>.md agent via Task or @<agent>, run independent agents in parallel when safe, then record fulfillmentMode: \"isolated\".";
182
+ return "Use OpenCode subagents: invoke the generated .opencode/agents/<agent>.md agent via Task or @<agent>; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
159
183
  case "codex":
160
- return "Use Codex native subagents: ask Codex to spawn the generated .codex/agents/<agent>.toml agent(s) by name, wait for all results, then record fulfillmentMode: \"isolated\".";
184
+ return "Use Codex native subagents: ask Codex to spawn the generated .codex/agents/<agent>.toml agent(s) by name; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
161
185
  }
162
186
  }
163
187
  export function harnessDispatchFallback(harnessId) {
@@ -597,7 +621,7 @@ async function cleanupLegacyCodexSurfaces(projectRoot) {
597
621
  }
598
622
  }
599
623
  function codexAgentToml(agent) {
600
- const instructions = `${agent.body}\n\n${enhancedAgentInstruction(agent.name)}`.trim();
624
+ const instructions = `${agentMarkdown(agent)}\n\n${enhancedAgentInstruction(agent.name)}`.trim();
601
625
  const sandboxMode = agent.tools.some((tool) => ["Write", "Edit", "Bash"].includes(tool))
602
626
  ? "workspace-write"
603
627
  : "read-only";
@@ -625,7 +649,7 @@ permission:
625
649
  ${agentMarkdown(agent)}`;
626
650
  }
627
651
  function enhancedAgentInstruction(agentName) {
628
- return `You are the cclaw ${agentName} subagent. Follow the parent prompt as the task boundary, produce evidence suitable for .cclaw/state/delegation-log.json, and do not recursively orchestrate other agents unless the parent explicitly asks.`;
652
+ return `## Worker ACK Contract\n\nYou are the cclaw ${agentName} subagent. Follow the parent prompt as the task boundary. ACK first with JSON containing spanId, dispatchId or workerRunId, dispatchSurface, agentDefinitionPath, ackTs, and status: "ACK". Finish with the strict return schema plus the same spanId+dispatchId proof so the parent can append .cclaw/state/delegation-events.jsonl and .cclaw/state/delegation-log.json. Do not let the parent claim isolated completion without matching ACK/result proof. Do not recursively orchestrate other agents unless the parent explicitly asks.`;
629
653
  }
630
654
  async function syncAgentFiles(projectRoot, harnesses) {
631
655
  const agentsDir = path.join(projectRoot, RUNTIME_ROOT, "agents");
package/dist/install.js CHANGED
@@ -13,7 +13,7 @@ import { viewCommandContract, viewCommandSkillMarkdown } from "./content/view-co
13
13
  import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
14
14
  import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
15
15
  import { ironLawRuntimeDocument, ironLawsSkillMarkdown } from "./content/iron-laws.js";
16
- import { stageCompleteScript, startFlowScript, runHookCmdScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
16
+ import { stageCompleteScript, startFlowScript, runHookCmdScript, delegationRecordScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
17
17
  import { nodeHookRuntimeScript } from "./content/node-hooks.js";
18
18
  import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
19
19
  import { ARTIFACT_TEMPLATES, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
@@ -23,6 +23,7 @@ import { stageSkillFolder, stageSkillMarkdown } from "./content/skills.js";
23
23
  import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LANGUAGE_RULE_PACK_GENERATORS, LEGACY_LANGUAGE_RULE_PACK_FOLDERS } from "./content/utility-skills.js";
24
24
  import { RESEARCH_PLAYBOOKS } from "./content/research-playbooks.js";
25
25
  import { SUBAGENT_CONTEXT_SKILLS } from "./content/subagent-context-skills.js";
26
+ import { CCLAW_AGENTS } from "./content/core-agents.js";
26
27
  import { createInitialFlowState } from "./flow-state.js";
27
28
  import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
28
29
  import { ensureGitignore, removeGitignorePatterns } from "./gitignore.js";
@@ -883,6 +884,7 @@ async function writeHooks(projectRoot, config) {
883
884
  compoundRecurrenceThreshold: config.compound?.recurrenceThreshold
884
885
  }));
885
886
  await writeFileSafe(path.join(hooksDir, "run-hook.cmd"), runHookCmdScript());
887
+ await writeFileSafe(path.join(hooksDir, "delegation-record.mjs"), delegationRecordScript());
886
888
  const opencodePluginSource = opencodePluginJs();
887
889
  await writeFileSafe(path.join(hooksDir, "opencode-plugin.mjs"), opencodePluginSource);
888
890
  try {
@@ -891,6 +893,7 @@ async function writeHooks(projectRoot, config) {
891
893
  "start-flow.mjs",
892
894
  "run-hook.mjs",
893
895
  "run-hook.cmd",
896
+ "delegation-record.mjs",
894
897
  "opencode-plugin.mjs"
895
898
  ]) {
896
899
  await fs.chmod(path.join(hooksDir, script), 0o755);
@@ -1339,15 +1342,7 @@ export async function uninstallCclaw(projectRoot) {
1339
1342
  }
1340
1343
  await removeIfEmpty(codexSkillsRoot);
1341
1344
  await removeIfEmpty(path.join(projectRoot, ".agents"));
1342
- const managedAgentNames = [
1343
- "planner",
1344
- "product-manager",
1345
- "critic",
1346
- "reviewer",
1347
- "security-reviewer",
1348
- "test-author",
1349
- "doc-updater"
1350
- ];
1345
+ const managedAgentNames = CCLAW_AGENTS.map((agent) => agent.name);
1351
1346
  for (const agentName of managedAgentNames) {
1352
1347
  await removeBestEffort(path.join(projectRoot, ".opencode/agents", `${agentName}.md`));
1353
1348
  await removeBestEffort(path.join(projectRoot, ".codex/agents", `${agentName}.toml`));