cclaw-cli 0.51.29 → 0.55.2

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 (151) hide show
  1. package/README.md +22 -16
  2. package/dist/artifact-linter/brainstorm.d.ts +2 -0
  3. package/dist/artifact-linter/brainstorm.js +245 -0
  4. package/dist/artifact-linter/design.d.ts +2 -0
  5. package/dist/artifact-linter/design.js +323 -0
  6. package/dist/artifact-linter/plan.d.ts +2 -0
  7. package/dist/artifact-linter/plan.js +162 -0
  8. package/dist/artifact-linter/review-army.d.ts +24 -0
  9. package/dist/artifact-linter/review-army.js +365 -0
  10. package/dist/artifact-linter/review.d.ts +2 -0
  11. package/dist/artifact-linter/review.js +65 -0
  12. package/dist/artifact-linter/scope.d.ts +2 -0
  13. package/dist/artifact-linter/scope.js +115 -0
  14. package/dist/artifact-linter/shared.d.ts +246 -0
  15. package/dist/artifact-linter/shared.js +1488 -0
  16. package/dist/artifact-linter/ship.d.ts +2 -0
  17. package/dist/artifact-linter/ship.js +46 -0
  18. package/dist/artifact-linter/spec.d.ts +2 -0
  19. package/dist/artifact-linter/spec.js +108 -0
  20. package/dist/artifact-linter/tdd.d.ts +2 -0
  21. package/dist/artifact-linter/tdd.js +124 -0
  22. package/dist/artifact-linter.d.ts +4 -76
  23. package/dist/artifact-linter.js +56 -2949
  24. package/dist/cli.d.ts +2 -18
  25. package/dist/cli.js +8 -246
  26. package/dist/codex-feature-flag.d.ts +1 -1
  27. package/dist/codex-feature-flag.js +1 -1
  28. package/dist/config.d.ts +3 -2
  29. package/dist/config.js +67 -3
  30. package/dist/constants.d.ts +1 -7
  31. package/dist/constants.js +9 -15
  32. package/dist/content/cancel-command.js +2 -2
  33. package/dist/content/closeout-guidance.js +13 -10
  34. package/dist/content/core-agents.d.ts +18 -0
  35. package/dist/content/core-agents.js +51 -7
  36. package/dist/content/decision-protocol.d.ts +1 -1
  37. package/dist/content/decision-protocol.js +1 -1
  38. package/dist/content/examples.js +6 -6
  39. package/dist/content/harness-doc.js +20 -2
  40. package/dist/content/hook-inline-snippets.d.ts +17 -4
  41. package/dist/content/hook-inline-snippets.js +218 -5
  42. package/dist/content/hook-manifest.d.ts +2 -2
  43. package/dist/content/hook-manifest.js +2 -2
  44. package/dist/content/hooks.d.ts +1 -0
  45. package/dist/content/hooks.js +32 -137
  46. package/dist/content/idea-command.d.ts +8 -0
  47. package/dist/content/{ideate-command.js → idea-command.js} +57 -50
  48. package/dist/content/idea-frames.d.ts +31 -0
  49. package/dist/content/{ideate-frames.js → idea-frames.js} +9 -9
  50. package/dist/content/idea-ranking.d.ts +25 -0
  51. package/dist/content/{ideate-ranking.js → idea-ranking.js} +5 -5
  52. package/dist/content/iron-laws.d.ts +0 -1
  53. package/dist/content/iron-laws.js +31 -16
  54. package/dist/content/learnings.js +1 -1
  55. package/dist/content/meta-skill.js +11 -13
  56. package/dist/content/node-hooks.d.ts +10 -0
  57. package/dist/content/node-hooks.js +45 -11
  58. package/dist/content/opencode-plugin.js +3 -3
  59. package/dist/content/session-hooks.js +1 -1
  60. package/dist/content/skills.js +19 -7
  61. package/dist/content/stage-command.js +1 -1
  62. package/dist/content/stage-schema.js +44 -2
  63. package/dist/content/stages/_lint-metadata/index.js +26 -2
  64. package/dist/content/stages/brainstorm.js +13 -7
  65. package/dist/content/stages/design.js +16 -11
  66. package/dist/content/stages/plan.js +9 -6
  67. package/dist/content/stages/review.js +4 -4
  68. package/dist/content/stages/schema-types.d.ts +1 -1
  69. package/dist/content/stages/scope.js +15 -12
  70. package/dist/content/stages/ship.js +2 -2
  71. package/dist/content/stages/spec.js +9 -3
  72. package/dist/content/stages/tdd.js +14 -4
  73. package/dist/content/start-command.d.ts +2 -2
  74. package/dist/content/start-command.js +24 -21
  75. package/dist/content/status-command.js +8 -8
  76. package/dist/content/subagents.js +61 -7
  77. package/dist/content/templates.d.ts +1 -1
  78. package/dist/content/templates.js +104 -152
  79. package/dist/content/tree-command.js +2 -2
  80. package/dist/content/utility-skills.d.ts +2 -2
  81. package/dist/content/utility-skills.js +2 -2
  82. package/dist/content/view-command.js +4 -2
  83. package/dist/delegation.d.ts +2 -0
  84. package/dist/delegation.js +2 -1
  85. package/dist/early-loop.d.ts +66 -0
  86. package/dist/early-loop.js +275 -0
  87. package/dist/flow-state.d.ts +1 -1
  88. package/dist/flow-state.js +1 -1
  89. package/dist/gate-evidence.d.ts +8 -0
  90. package/dist/gate-evidence.js +141 -5
  91. package/dist/harness-adapters.d.ts +2 -2
  92. package/dist/harness-adapters.js +54 -122
  93. package/dist/harness-selection.d.ts +31 -0
  94. package/dist/harness-selection.js +214 -0
  95. package/dist/install.js +166 -38
  96. package/dist/internal/advance-stage/advance.d.ts +50 -0
  97. package/dist/internal/advance-stage/advance.js +480 -0
  98. package/dist/internal/advance-stage/cancel-run.d.ts +8 -0
  99. package/dist/internal/advance-stage/cancel-run.js +19 -0
  100. package/dist/internal/advance-stage/flow-state-coercion.d.ts +3 -0
  101. package/dist/internal/advance-stage/flow-state-coercion.js +81 -0
  102. package/dist/internal/advance-stage/helpers.d.ts +14 -0
  103. package/dist/internal/advance-stage/helpers.js +145 -0
  104. package/dist/internal/advance-stage/hook.d.ts +8 -0
  105. package/dist/internal/advance-stage/hook.js +40 -0
  106. package/dist/internal/advance-stage/parsers.d.ts +54 -0
  107. package/dist/internal/advance-stage/parsers.js +307 -0
  108. package/dist/internal/advance-stage/review-loop.d.ts +7 -0
  109. package/dist/internal/advance-stage/review-loop.js +170 -0
  110. package/dist/internal/advance-stage/rewind.d.ts +14 -0
  111. package/dist/internal/advance-stage/rewind.js +108 -0
  112. package/dist/internal/advance-stage/start-flow.d.ts +11 -0
  113. package/dist/internal/advance-stage/start-flow.js +136 -0
  114. package/dist/internal/advance-stage/verify.d.ts +29 -0
  115. package/dist/internal/advance-stage/verify.js +225 -0
  116. package/dist/internal/advance-stage.js +21 -1470
  117. package/dist/internal/compound-readiness.d.ts +1 -1
  118. package/dist/internal/compound-readiness.js +2 -2
  119. package/dist/internal/early-loop-status.d.ts +7 -0
  120. package/dist/internal/early-loop-status.js +90 -0
  121. package/dist/internal/runtime-integrity.d.ts +7 -0
  122. package/dist/internal/runtime-integrity.js +288 -0
  123. package/dist/internal/tdd-red-evidence.js +1 -1
  124. package/dist/knowledge-store.d.ts +3 -8
  125. package/dist/knowledge-store.js +16 -29
  126. package/dist/managed-resources.js +24 -2
  127. package/dist/policy.js +5 -7
  128. package/dist/run-archive.d.ts +1 -1
  129. package/dist/run-archive.js +16 -16
  130. package/dist/run-persistence.js +112 -12
  131. package/dist/tdd-cycle.d.ts +3 -3
  132. package/dist/tdd-cycle.js +1 -1
  133. package/dist/types.d.ts +18 -10
  134. package/package.json +1 -1
  135. package/dist/content/finish-command.d.ts +0 -2
  136. package/dist/content/finish-command.js +0 -26
  137. package/dist/content/ideate-command.d.ts +0 -8
  138. package/dist/content/ideate-frames.d.ts +0 -31
  139. package/dist/content/ideate-ranking.d.ts +0 -25
  140. package/dist/content/next-command.d.ts +0 -20
  141. package/dist/content/next-command.js +0 -298
  142. package/dist/content/seed-shelf.d.ts +0 -36
  143. package/dist/content/seed-shelf.js +0 -301
  144. package/dist/content/stage-common-guidance.d.ts +0 -1
  145. package/dist/content/stage-common-guidance.js +0 -106
  146. package/dist/doctor-registry.d.ts +0 -10
  147. package/dist/doctor-registry.js +0 -186
  148. package/dist/doctor.d.ts +0 -17
  149. package/dist/doctor.js +0 -2206
  150. package/dist/internal/hook-manifest.d.ts +0 -16
  151. package/dist/internal/hook-manifest.js +0 -77
package/dist/install.js CHANGED
@@ -5,17 +5,15 @@ import { promisify } from "node:util";
5
5
  import { CCLAW_VERSION, FLOW_VERSION, REQUIRED_DIRS, RUNTIME_ROOT } from "./constants.js";
6
6
  import { writeConfig, createDefaultConfig, readConfig, configPath, detectLanguageRulePacks, detectAdvancedKeys } from "./config.js";
7
7
  import { learnSkillMarkdown } from "./content/learnings.js";
8
- import { nextCommandContract, nextCommandSkillMarkdown } from "./content/next-command.js";
9
8
  import { stageCommandShimMarkdown } from "./content/stage-command.js";
10
- import { ideateCommandContract, ideateCommandSkillMarkdown } from "./content/ideate-command.js";
9
+ import { ideaCommandContract, ideaCommandSkillMarkdown } from "./content/idea-command.js";
11
10
  import { startCommandContract, startCommandSkillMarkdown } from "./content/start-command.js";
12
11
  import { viewCommandContract, viewCommandSkillMarkdown } from "./content/view-command.js";
13
- import { finishCommandContract, finishCommandSkillMarkdown } from "./content/finish-command.js";
14
12
  import { cancelCommandContract, cancelCommandSkillMarkdown } from "./content/cancel-command.js";
15
13
  import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
16
14
  import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
17
15
  import { ironLawRuntimeDocument, ironLawsSkillMarkdown } from "./content/iron-laws.js";
18
- import { stageCompleteScript, startFlowScript, runHookCmdScript, delegationRecordScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
16
+ import { stageCompleteScript, startFlowScript, cancelRunScript, runHookCmdScript, delegationRecordScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
19
17
  import { nodeHookRuntimeScript } from "./content/node-hooks.js";
20
18
  import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
21
19
  import { ARTIFACT_TEMPLATES, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
@@ -30,10 +28,11 @@ import { createInitialFlowState } from "./flow-state.js";
30
28
  import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
31
29
  import { ManagedResourceSession, setActiveManagedResourceSession } from "./managed-resources.js";
32
30
  import { ensureGitignore, removeGitignorePatterns } from "./gitignore.js";
33
- import { HARNESS_ADAPTERS, harnessShimFileNames, syncHarnessShims, removeCclawFromAgentsMd } from "./harness-adapters.js";
31
+ import { HARNESS_ADAPTERS, harnessShimFileNames, harnessShimSkillNames, syncHarnessShims, removeCclawFromAgentsMd } from "./harness-adapters.js";
34
32
  import { validateHookDocument } from "./hook-schema.js";
35
33
  import { detectHarnesses } from "./init-detect.js";
36
- import { ensureRunSystem } from "./runs.js";
34
+ import { classifyCodexHooksFlag, codexConfigPath, readCodexConfig } from "./codex-feature-flag.js";
35
+ import { CorruptFlowStateError, ensureRunSystem } from "./runs.js";
37
36
  import { FLOW_STAGES } from "./types.js";
38
37
  const OPENCODE_PLUGIN_REL_PATH = ".opencode/plugins/cclaw-plugin.mjs";
39
38
  const CURSOR_RULE_REL_PATH = ".cursor/rules/cclaw-workflow.mdc";
@@ -50,6 +49,23 @@ async function writeInitSentinel(projectRoot, operation) {
50
49
  await writeFileSafe(sentinelPath, `${JSON.stringify({ operation, startedAt: new Date().toISOString() }, null, 2)}\n`);
51
50
  return sentinelPath;
52
51
  }
52
+ async function warnStaleInitSentinel(projectRoot, operation) {
53
+ const sentinelPath = runtimePath(projectRoot, "state", INIT_SENTINEL_FILE);
54
+ if (!(await exists(sentinelPath)))
55
+ return;
56
+ let startedAt = "unknown time";
57
+ try {
58
+ const raw = await fs.readFile(sentinelPath, "utf8");
59
+ const parsed = JSON.parse(raw);
60
+ if (parsed && typeof parsed.startedAt === "string" && parsed.startedAt.trim().length > 0) {
61
+ startedAt = parsed.startedAt;
62
+ }
63
+ }
64
+ catch {
65
+ // best-effort parse of stale sentinel metadata
66
+ }
67
+ process.stderr.write(`[${operation}] Detected stale .init-in-progress sentinel from ${startedAt}; previous run may have crashed. Continuing.\n`);
68
+ }
53
69
  async function removeBestEffort(targetPath, recursive = false) {
54
70
  try {
55
71
  await fs.rm(targetPath, { recursive, force: true });
@@ -90,6 +106,16 @@ const DEPRECATED_UTILITY_SKILL_FOLDERS = [
90
106
  "flow-tree",
91
107
  "flow-diff"
92
108
  ];
109
+ const DEPRECATED_STAGE_SKILL_FOLDERS = [
110
+ "brainstorming",
111
+ "scope-shaping",
112
+ "engineering-design-lock",
113
+ "specification-authoring",
114
+ "planning-and-task-breakdown",
115
+ "test-driven-development",
116
+ "two-layer-review",
117
+ "shipping-and-handoff"
118
+ ];
93
119
  const DEPRECATED_AGENT_FILES = [
94
120
  "securityer.md",
95
121
  "spec-reviewer.md",
@@ -102,6 +128,7 @@ const DEPRECATED_AGENT_FILES = [
102
128
  ];
103
129
  const DEPRECATED_COMMAND_FILES = [
104
130
  "learn.md",
131
+ "finish.md",
105
132
  "status.md",
106
133
  "tree.md",
107
134
  "diff.md",
@@ -114,6 +141,7 @@ const DEPRECATED_COMMAND_FILES = [
114
141
  "rewind.md"
115
142
  ];
116
143
  const DEPRECATED_SKILL_FILES = [
144
+ ["flow-finish", "SKILL.md"],
117
145
  ["flow-ops", "SKILL.md"],
118
146
  ["tdd-cycle-log", "SKILL.md"],
119
147
  ["flow-retro", "SKILL.md"],
@@ -450,11 +478,9 @@ async function writeSkills(projectRoot, config) {
450
478
  }
451
479
  // Utility skills (not flow stages)
452
480
  await writeFileSafe(runtimePath(projectRoot, "skills", "learnings", "SKILL.md"), learnSkillMarkdown());
453
- await writeFileSafe(runtimePath(projectRoot, "skills", "flow-next-step", "SKILL.md"), nextCommandSkillMarkdown());
454
- await writeFileSafe(runtimePath(projectRoot, "skills", "flow-ideate", "SKILL.md"), ideateCommandSkillMarkdown());
481
+ await writeFileSafe(runtimePath(projectRoot, "skills", "flow-idea", "SKILL.md"), ideaCommandSkillMarkdown());
455
482
  await writeFileSafe(runtimePath(projectRoot, "skills", "flow-start", "SKILL.md"), startCommandSkillMarkdown());
456
483
  await writeFileSafe(runtimePath(projectRoot, "skills", "flow-view", "SKILL.md"), viewCommandSkillMarkdown());
457
- await writeFileSafe(runtimePath(projectRoot, "skills", "flow-finish", "SKILL.md"), finishCommandSkillMarkdown());
458
484
  await writeFileSafe(runtimePath(projectRoot, "skills", "flow-cancel", "SKILL.md"), cancelCommandSkillMarkdown());
459
485
  await writeFileSafe(runtimePath(projectRoot, "skills", "subagent-dev", "SKILL.md"), subagentDrivenDevSkill());
460
486
  await writeFileSafe(runtimePath(projectRoot, "skills", "parallel-dispatch", "SKILL.md"), parallelAgentsSkill());
@@ -516,10 +542,8 @@ async function writeSkills(projectRoot, config) {
516
542
  }
517
543
  async function writeEntryCommands(projectRoot) {
518
544
  await writeFileSafe(runtimePath(projectRoot, "commands", "start.md"), startCommandContract());
519
- await writeFileSafe(runtimePath(projectRoot, "commands", "next.md"), nextCommandContract());
520
- await writeFileSafe(runtimePath(projectRoot, "commands", "ideate.md"), ideateCommandContract());
545
+ await writeFileSafe(runtimePath(projectRoot, "commands", "idea.md"), ideaCommandContract());
521
546
  await writeFileSafe(runtimePath(projectRoot, "commands", "view.md"), viewCommandContract());
522
- await writeFileSafe(runtimePath(projectRoot, "commands", "finish.md"), finishCommandContract());
523
547
  await writeFileSafe(runtimePath(projectRoot, "commands", "cancel.md"), cancelCommandContract());
524
548
  for (const stage of FLOW_STAGES) {
525
549
  await writeFileSafe(runtimePath(projectRoot, "commands", `${stage}.md`), stageCommandShimMarkdown(stage));
@@ -859,14 +883,16 @@ async function writeMergedHookJson(projectRoot, hookFilePath, generatedJson) {
859
883
  if (harness) {
860
884
  const generatedSchema = validateHookDocument(harness, generatedDoc);
861
885
  if (!generatedSchema.ok) {
862
- throw new Error(`Generated ${harness} hook document failed schema validation: ${generatedSchema.errors.join("; ")}`);
886
+ throw new Error(`[sync fail-fast] Hook document drift detected for ${harness}: generated hook document is invalid (${generatedSchema.errors.join("; ")}). ` +
887
+ "Run `npx cclaw-cli sync` to regenerate managed hooks or repair the generated hook shape manually.");
863
888
  }
864
889
  }
865
890
  const mergedDoc = mergeHookDocuments(existingDoc, generatedDoc);
866
891
  if (harness) {
867
892
  const mergedSchema = validateHookDocument(harness, mergedDoc);
868
893
  if (!mergedSchema.ok) {
869
- throw new Error(`Merged ${harness} hook document failed schema validation: ${mergedSchema.errors.join("; ")}`);
894
+ throw new Error(`[sync fail-fast] Hook document drift detected for ${harness}: merged hook document is invalid (${mergedSchema.errors.join("; ")}). ` +
895
+ "Run `npx cclaw-cli sync` after fixing the custom hook entry or remove the malformed user-authored hook block.");
870
896
  }
871
897
  }
872
898
  await writeFileSafe(hookFilePath, `${JSON.stringify(mergedDoc, null, 2)}\n`);
@@ -884,11 +910,14 @@ async function writeHooks(projectRoot, config) {
884
910
  }), null, 2)}\n`);
885
911
  await writeFileSafe(path.join(hooksDir, "stage-complete.mjs"), stageCompleteScript());
886
912
  await writeFileSafe(path.join(hooksDir, "start-flow.mjs"), startFlowScript());
913
+ await writeFileSafe(path.join(hooksDir, "cancel-run.mjs"), cancelRunScript());
887
914
  await writeFileSafe(path.join(hooksDir, "run-hook.mjs"), nodeHookRuntimeScript({
888
915
  strictness: effectiveStrictness,
889
916
  tddTestPathPatterns: config.tdd?.testPathPatterns ?? config.tddTestGlobs,
890
917
  tddProductionPathPatterns: config.tdd?.productionPathPatterns,
891
- compoundRecurrenceThreshold: config.compound?.recurrenceThreshold
918
+ compoundRecurrenceThreshold: config.compound?.recurrenceThreshold,
919
+ earlyLoopEnabled: config.earlyLoop?.enabled,
920
+ earlyLoopMaxIterations: config.earlyLoop?.maxIterations
892
921
  }));
893
922
  await writeFileSafe(path.join(hooksDir, "run-hook.cmd"), runHookCmdScript());
894
923
  await writeFileSafe(path.join(hooksDir, "delegation-record.mjs"), delegationRecordScript());
@@ -901,7 +930,8 @@ async function writeHooks(projectRoot, config) {
901
930
  "run-hook.mjs",
902
931
  "run-hook.cmd",
903
932
  "delegation-record.mjs",
904
- "opencode-plugin.mjs"
933
+ "opencode-plugin.mjs",
934
+ "cancel-run.mjs"
905
935
  ]) {
906
936
  await fs.chmod(path.join(hooksDir, script), 0o755);
907
937
  }
@@ -939,8 +969,8 @@ async function writeHooks(projectRoot, config) {
939
969
  // flag in `~/.codex/config.toml`. cclaw always writes the file so
940
970
  // the moment the flag flips on, the cclaw hooks start firing. See
941
971
  // `codexHooksJsonWithObservation` for the Bash-only caveat on
942
- // PreToolUse/PostToolUse. `cclaw doctor` warns if the feature flag
943
- // is not set.
972
+ // PreToolUse/PostToolUse. If the feature flag is off, hooks remain
973
+ // inert until the user enables codex_hooks in ~/.codex/config.toml.
944
974
  const codexDir = path.join(projectRoot, ".codex");
945
975
  await ensureDir(codexDir);
946
976
  await writeMergedHookJson(projectRoot, path.join(codexDir, "hooks.json"), codexHooksJson());
@@ -990,7 +1020,7 @@ async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
990
1020
  for (const entry of managedHookFiles) {
991
1021
  if (enabled.has(entry.harness))
992
1022
  continue;
993
- await removeManagedHookEntries(entry.hookPath);
1023
+ await removeManagedHookEntries(entry.hookPath, { failOnParseError: true });
994
1024
  }
995
1025
  if (!enabled.has("opencode")) {
996
1026
  try {
@@ -999,8 +1029,22 @@ async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
999
1029
  catch {
1000
1030
  // best-effort cleanup
1001
1031
  }
1032
+ try {
1033
+ await fs.rm(path.join(projectRoot, ".opencode/agents"), { recursive: true, force: true });
1034
+ }
1035
+ catch {
1036
+ // best-effort cleanup
1037
+ }
1002
1038
  await removeManagedOpenCodePluginConfig(projectRoot, OPENCODE_PLUGIN_REL_PATH);
1003
1039
  }
1040
+ if (!enabled.has("codex")) {
1041
+ try {
1042
+ await fs.rm(path.join(projectRoot, ".codex/agents"), { recursive: true, force: true });
1043
+ }
1044
+ catch {
1045
+ // best-effort cleanup
1046
+ }
1047
+ }
1004
1048
  }
1005
1049
  async function writeState(projectRoot, config, forceReset = false) {
1006
1050
  const statePath = runtimePath(projectRoot, "state", "flow-state.json");
@@ -1014,6 +1058,9 @@ async function cleanLegacyArtifacts(projectRoot) {
1014
1058
  for (const legacyFolder of DEPRECATED_UTILITY_SKILL_FOLDERS) {
1015
1059
  await removeBestEffort(runtimePath(projectRoot, "skills", legacyFolder), true);
1016
1060
  }
1061
+ for (const legacyFolder of DEPRECATED_STAGE_SKILL_FOLDERS) {
1062
+ await removeBestEffort(runtimePath(projectRoot, "skills", legacyFolder), true);
1063
+ }
1017
1064
  for (const legacyAgentFile of DEPRECATED_AGENT_FILES) {
1018
1065
  await removeBestEffort(runtimePath(projectRoot, "agents", legacyAgentFile));
1019
1066
  }
@@ -1038,16 +1085,30 @@ async function cleanLegacyArtifacts(projectRoot) {
1038
1085
  for (const legacyRuntimeDir of DEPRECATED_RUNTIME_DIRS) {
1039
1086
  await removeBestEffort(runtimePath(projectRoot, legacyRuntimeDir), true);
1040
1087
  }
1041
- // D-4 terminology migration: rename historical ideation artifacts to the
1042
- // canonical ideate-* naming without deleting user-authored content.
1088
+ // Archive storage migration: `.cclaw/runs` is legacy and no longer a valid
1089
+ // archive root. Remove only when empty; otherwise keep it so users can
1090
+ // manually migrate or inspect old data.
1091
+ const legacyRunsDir = runtimePath(projectRoot, "runs");
1092
+ try {
1093
+ const entries = await fs.readdir(legacyRunsDir);
1094
+ if (entries.length === 0) {
1095
+ await fs.rm(legacyRunsDir, { recursive: true, force: true });
1096
+ }
1097
+ }
1098
+ catch {
1099
+ // missing or unreadable legacy dir; keep best-effort behavior
1100
+ }
1101
+ // D-4 terminology migration: rename historical ideation artifact prefixes to
1102
+ // the canonical idea-* naming without deleting user-authored content.
1103
+ const legacyIdeaArtifactPattern = /^ideation-(.+\.md)$/u;
1043
1104
  const artifactsDir = runtimePath(projectRoot, "artifacts");
1044
1105
  try {
1045
1106
  const entries = await fs.readdir(artifactsDir);
1046
1107
  for (const entry of entries) {
1047
- const match = /^ideation-(.+\.md)$/u.exec(entry);
1108
+ const match = legacyIdeaArtifactPattern.exec(entry);
1048
1109
  if (!match)
1049
1110
  continue;
1050
- const nextName = `ideate-${match[1]}`;
1111
+ const nextName = `idea-${match[1]}`;
1051
1112
  const from = path.join(artifactsDir, entry);
1052
1113
  const to = path.join(artifactsDir, nextName);
1053
1114
  if (await exists(to)) {
@@ -1062,13 +1123,8 @@ async function cleanLegacyArtifacts(projectRoot) {
1062
1123
  }
1063
1124
  async function cleanStaleFiles(projectRoot) {
1064
1125
  const expectedShimFiles = new Set(harnessShimFileNames());
1126
+ const expectedShimSkills = new Set(harnessShimFileNames().map((fileName) => fileName.replace(/\.md$/u, "")));
1065
1127
  for (const adapter of Object.values(HARNESS_ADAPTERS)) {
1066
- // Skill-kind shims (Codex) live in per-skill directories, not flat
1067
- // markdown files, so the regex-based stale sweep below would never
1068
- // match them anyway. The legacy `.codex/commands/` cleanup happens in
1069
- // `cleanupLegacyCodexSurfaces` inside syncHarnessShims().
1070
- if (adapter.shimKind === "skill")
1071
- continue;
1072
1128
  const commandDir = path.join(projectRoot, adapter.commandDir);
1073
1129
  if (!(await exists(commandDir)))
1074
1130
  continue;
@@ -1079,6 +1135,16 @@ async function cleanStaleFiles(projectRoot) {
1079
1135
  catch {
1080
1136
  entries = [];
1081
1137
  }
1138
+ if (adapter.shimKind === "skill") {
1139
+ for (const entry of entries) {
1140
+ if (!/^cc(?:-.*)?$/u.test(entry))
1141
+ continue;
1142
+ if (expectedShimSkills.has(entry))
1143
+ continue;
1144
+ await fs.rm(path.join(commandDir, entry), { recursive: true, force: true });
1145
+ }
1146
+ continue;
1147
+ }
1082
1148
  for (const entry of entries) {
1083
1149
  if (!/^cc(?:-.*)?\.md$/u.test(entry))
1084
1150
  continue;
@@ -1090,7 +1156,34 @@ async function cleanStaleFiles(projectRoot) {
1090
1156
  // Keep user-owned custom assets under .cclaw/agents and .cclaw/skills.
1091
1157
  // Legacy managed removals happen in cleanLegacyArtifacts() with explicit paths.
1092
1158
  }
1159
+ async function assertExpectedHarnessShims(projectRoot, harnesses) {
1160
+ const expectedFiles = harnessShimFileNames();
1161
+ const expectedSkillFolders = harnessShimSkillNames();
1162
+ for (const harness of harnesses) {
1163
+ const adapter = HARNESS_ADAPTERS[harness];
1164
+ const base = path.join(projectRoot, adapter.commandDir);
1165
+ for (const fileName of expectedFiles) {
1166
+ const target = adapter.shimKind === "skill"
1167
+ ? path.join(base, fileName.replace(/\.md$/u, ""), "SKILL.md")
1168
+ : path.join(base, fileName);
1169
+ if (!(await exists(target))) {
1170
+ throw new Error(`[sync fail-fast] Harness shim drift detected for ${harness}: missing ${target}. ` +
1171
+ `Run \`npx cclaw-cli sync\` again; if the file is still missing, inspect harness permissions/paths.`);
1172
+ }
1173
+ }
1174
+ if (adapter.shimKind === "skill") {
1175
+ for (const folder of expectedSkillFolders) {
1176
+ const skillPath = path.join(base, folder, "SKILL.md");
1177
+ if (!(await exists(skillPath))) {
1178
+ throw new Error(`[sync fail-fast] Harness skill shim drift detected for ${harness}: missing ${skillPath}. ` +
1179
+ `Run \`npx cclaw-cli sync\` again; if the issue persists, inspect generated .agents/skills surfaces.`);
1180
+ }
1181
+ }
1182
+ }
1183
+ }
1184
+ }
1093
1185
  async function materializeRuntime(projectRoot, config, forceStateReset, operation = "sync") {
1186
+ await warnStaleInitSentinel(projectRoot, operation);
1094
1187
  const sentinelPath = await writeInitSentinel(projectRoot, operation);
1095
1188
  const managedSession = await ManagedResourceSession.create({ projectRoot, operation });
1096
1189
  setActiveManagedResourceSession(managedSession);
@@ -1106,25 +1199,51 @@ async function materializeRuntime(projectRoot, config, forceStateReset, operatio
1106
1199
  writeRulebook(projectRoot)
1107
1200
  ]);
1108
1201
  await writeState(projectRoot, config, forceStateReset);
1109
- await ensureRunSystem(projectRoot, { createIfMissing: false });
1202
+ try {
1203
+ await ensureRunSystem(projectRoot, { createIfMissing: false });
1204
+ }
1205
+ catch (error) {
1206
+ if (error instanceof CorruptFlowStateError) {
1207
+ throw new Error(`[sync fail-fast] Corrupt flow state detected: ${error.message} ` +
1208
+ `Resolve the quarantined flow-state file and re-run \`npx cclaw-cli sync\`.`);
1209
+ }
1210
+ throw error;
1211
+ }
1110
1212
  await ensureKnowledgeStore(projectRoot);
1111
1213
  await writeHooks(projectRoot, config);
1112
1214
  await syncDisabledHarnessArtifacts(projectRoot, harnesses);
1113
1215
  await syncManagedGitHooks(projectRoot, config);
1114
1216
  await syncHarnessShims(projectRoot, harnesses);
1217
+ await assertExpectedHarnessShims(projectRoot, harnesses);
1115
1218
  await writeCursorWorkflowRule(projectRoot, harnesses);
1116
1219
  await ensureGitignore(projectRoot);
1117
1220
  await managedSession.commit();
1118
1221
  await fs.unlink(sentinelPath).catch(() => undefined);
1119
1222
  }
1120
1223
  catch (error) {
1121
- // Leave the sentinel in place so doctor can surface the interrupted run.
1224
+ // Leave the sentinel in place so the interrupted run is visible.
1122
1225
  throw error;
1123
1226
  }
1124
1227
  finally {
1125
1228
  setActiveManagedResourceSession(null);
1126
1229
  }
1127
1230
  }
1231
+ async function warnCodexHooksFeatureFlagIfDisabled(harnesses) {
1232
+ if (!harnesses.includes("codex"))
1233
+ return;
1234
+ const codexTomlPath = codexConfigPath();
1235
+ let existing;
1236
+ try {
1237
+ existing = await readCodexConfig(codexTomlPath);
1238
+ }
1239
+ catch (error) {
1240
+ process.stderr.write(`cclaw: could not read ${codexTomlPath} to validate codex_hooks flag: ${error instanceof Error ? error.message : String(error)}\n`);
1241
+ return;
1242
+ }
1243
+ if (classifyCodexHooksFlag(existing) === "enabled")
1244
+ return;
1245
+ process.stderr.write(`cclaw: Codex hooks file written, but [features] codex_hooks is not true in ${codexTomlPath} — hooks are inert until you enable it.\n`);
1246
+ }
1128
1247
  export async function initCclaw(options) {
1129
1248
  if (options.harnesses !== undefined && options.harnesses.length === 0) {
1130
1249
  throw new Error("Select at least one harness.");
@@ -1153,7 +1272,7 @@ export async function syncCclaw(projectRoot, options = {}) {
1153
1272
  // Prefer detected harness markers over the hardcoded default list.
1154
1273
  // Without this, a user running `cclaw sync` in a `.claude`-only
1155
1274
  // project ends up with a config that also enables cursor/opencode/
1156
- // codex, which then fails doctor checks for missing shim folders.
1275
+ // codex, which then creates invalid harness expectations.
1157
1276
  // Fall back to the previous default (config.harnesses) if no markers
1158
1277
  // are found so brand-new projects still bootstrap cleanly.
1159
1278
  const detected = await detectHarnesses(projectRoot);
@@ -1173,6 +1292,7 @@ export async function syncCclaw(projectRoot, options = {}) {
1173
1292
  });
1174
1293
  }
1175
1294
  await materializeRuntime(projectRoot, config, false, "sync");
1295
+ await warnCodexHooksFeatureFlagIfDisabled(config.harnesses);
1176
1296
  }
1177
1297
  /**
1178
1298
  * Refresh generated files in `.cclaw/` without touching user-authored
@@ -1270,20 +1390,28 @@ function isManagedRuntimeHookCommand(command) {
1270
1390
  // Codex UserPromptSubmit non-blocking state nudge.
1271
1391
  return /internal verify-current-state(?:\s|$)/u.test(normalized);
1272
1392
  }
1273
- async function removeManagedHookEntries(hookFilePath) {
1393
+ async function removeManagedHookEntries(hookFilePath, options = {}) {
1274
1394
  if (!(await exists(hookFilePath)))
1275
1395
  return;
1276
1396
  let parsed = null;
1277
1397
  try {
1278
1398
  const raw = await fs.readFile(hookFilePath, "utf8");
1279
1399
  const recovered = tryParseHookDocument(raw);
1280
- parsed = recovered?.parsed ?? null;
1400
+ if (recovered === null) {
1401
+ if (options.failOnParseError === true) {
1402
+ throw new Error(`[sync fail-fast] Cannot strip managed hook entries from ${hookFilePath} — JSON is unparseable. ` +
1403
+ `Run \`rm ${hookFilePath}\` and rerun \`npx cclaw-cli sync\`.`);
1404
+ }
1405
+ return;
1406
+ }
1407
+ parsed = recovered.parsed;
1281
1408
  }
1282
- catch {
1409
+ catch (error) {
1410
+ if (options.failOnParseError === true) {
1411
+ throw new Error(`[sync fail-fast] Cannot strip managed hook entries from ${hookFilePath} — ${error instanceof Error ? error.message : String(error)}. Run \`rm ${hookFilePath}\` and rerun \`npx cclaw-cli sync\`.`);
1412
+ }
1283
1413
  return;
1284
1414
  }
1285
- if (parsed === null)
1286
- return;
1287
1415
  const { updated, changed } = stripManagedHookCommands(parsed);
1288
1416
  if (!changed)
1289
1417
  return;
@@ -1365,7 +1493,7 @@ export async function uninstallCclaw(projectRoot) {
1365
1493
  try {
1366
1494
  const entries = await fs.readdir(codexSkillsRoot);
1367
1495
  for (const entry of entries) {
1368
- if (/^(?:cclaw-)?cc(?:-(?:next|view|finish|cancel|ops|ideate|brainstorm|scope|design|spec|plan|tdd|review|ship))?$/u.test(entry)) {
1496
+ if (/^(?:cclaw-)?cc(?:-(?:next|view|finish|cancel|ops|idea|brainstorm|scope|design|spec|plan|tdd|review|ship))?$/u.test(entry)) {
1369
1497
  await fs.rm(path.join(codexSkillsRoot, entry), { recursive: true, force: true });
1370
1498
  }
1371
1499
  }
@@ -0,0 +1,50 @@
1
+ import { type FlowState } from "../../flow-state.js";
2
+ import { type FlowStage } from "../../types.js";
3
+ import type { AdvanceStageArgs } from "./parsers.js";
4
+ import type { Writable } from "node:stream";
5
+ interface InternalIo {
6
+ stdout: Writable;
7
+ stderr: Writable;
8
+ }
9
+ interface InternalValidationReport {
10
+ ok: boolean;
11
+ stage: FlowStage;
12
+ delegation: {
13
+ satisfied: boolean;
14
+ missing: string[];
15
+ waived: string[];
16
+ missingEvidence: string[];
17
+ missingDispatchProof: string[];
18
+ legacyInferredCompletions: string[];
19
+ corruptEventLines: number[];
20
+ staleWorkers: string[];
21
+ expectedMode: string;
22
+ };
23
+ gates: {
24
+ ok: boolean;
25
+ complete: boolean;
26
+ issues: string[];
27
+ missingRequired: string[];
28
+ missingTriggeredConditional: string[];
29
+ };
30
+ completedStages: {
31
+ ok: boolean;
32
+ issues: string[];
33
+ };
34
+ }
35
+ export declare function hydrateReviewLoopEvidenceFromArtifact(projectRoot: string, stage: FlowStage, track: FlowState["track"], selectedGateIds: string[], evidenceByGate: Record<string, string>): Promise<void>;
36
+ export declare function buildValidationReport(projectRoot: string, flowState: FlowState, options?: {
37
+ allowBlockedReviewRoute?: boolean;
38
+ }): Promise<InternalValidationReport>;
39
+ interface HarvestLearningsResult {
40
+ ok: boolean;
41
+ markerWritten: boolean;
42
+ parsedEntries: number;
43
+ appendedEntries: number;
44
+ skippedDuplicates: number;
45
+ details: string;
46
+ }
47
+ export declare function withLearningsHarvestMarker(artifactMarkdown: string, appendedEntries: number, skippedDuplicates: number): string;
48
+ export declare function harvestStageLearnings(projectRoot: string, stage: FlowStage, track: FlowState["track"]): Promise<HarvestLearningsResult>;
49
+ export declare function runAdvanceStage(projectRoot: string, args: AdvanceStageArgs, io: InternalIo): Promise<number>;
50
+ export {};