cclaw-cli 0.51.30 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +24 -18
  2. package/dist/artifact-linter/brainstorm.d.ts +2 -0
  3. package/dist/artifact-linter/brainstorm.js +289 -0
  4. package/dist/artifact-linter/design.d.ts +2 -0
  5. package/dist/artifact-linter/design.js +354 -0
  6. package/dist/artifact-linter/plan.d.ts +2 -0
  7. package/dist/artifact-linter/plan.js +183 -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 +99 -0
  12. package/dist/artifact-linter/scope.d.ts +2 -0
  13. package/dist/artifact-linter/scope.js +125 -0
  14. package/dist/artifact-linter/shared.d.ts +247 -0
  15. package/dist/artifact-linter/shared.js +1517 -0
  16. package/dist/artifact-linter/ship.d.ts +2 -0
  17. package/dist/artifact-linter/ship.js +82 -0
  18. package/dist/artifact-linter/spec.d.ts +2 -0
  19. package/dist/artifact-linter/spec.js +130 -0
  20. package/dist/artifact-linter/tdd.d.ts +2 -0
  21. package/dist/artifact-linter/tdd.js +198 -0
  22. package/dist/artifact-linter.d.ts +4 -76
  23. package/dist/artifact-linter.js +56 -2949
  24. package/dist/cli.d.ts +1 -6
  25. package/dist/cli.js +4 -159
  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 +10 -15
  32. package/dist/content/cancel-command.js +2 -2
  33. package/dist/content/closeout-guidance.d.ts +1 -1
  34. package/dist/content/closeout-guidance.js +15 -13
  35. package/dist/content/core-agents.d.ts +46 -29
  36. package/dist/content/core-agents.js +216 -82
  37. package/dist/content/decision-protocol.d.ts +1 -1
  38. package/dist/content/decision-protocol.js +1 -1
  39. package/dist/content/diff-command.js +1 -1
  40. package/dist/content/examples.d.ts +0 -3
  41. package/dist/content/examples.js +197 -752
  42. package/dist/content/harness-doc.js +20 -2
  43. package/dist/content/hook-manifest.d.ts +2 -2
  44. package/dist/content/hook-manifest.js +2 -2
  45. package/dist/content/hooks.d.ts +1 -0
  46. package/dist/content/hooks.js +32 -137
  47. package/dist/content/idea.d.ts +60 -0
  48. package/dist/content/idea.js +404 -0
  49. package/dist/content/iron-laws.d.ts +0 -1
  50. package/dist/content/iron-laws.js +31 -16
  51. package/dist/content/learnings.d.ts +2 -4
  52. package/dist/content/learnings.js +11 -27
  53. package/dist/content/meta-skill.js +7 -7
  54. package/dist/content/node-hooks.d.ts +10 -0
  55. package/dist/content/node-hooks.js +163 -95
  56. package/dist/content/opencode-plugin.js +15 -29
  57. package/dist/content/reference-patterns.js +2 -2
  58. package/dist/content/runtime-shared-snippets.d.ts +8 -0
  59. package/dist/content/runtime-shared-snippets.js +80 -0
  60. package/dist/content/session-hooks.js +1 -1
  61. package/dist/content/skills.d.ts +1 -0
  62. package/dist/content/skills.js +69 -7
  63. package/dist/content/stage-schema.js +147 -61
  64. package/dist/content/stages/_lint-metadata/index.js +26 -2
  65. package/dist/content/stages/brainstorm.js +13 -7
  66. package/dist/content/stages/design.js +16 -11
  67. package/dist/content/stages/plan.js +7 -4
  68. package/dist/content/stages/review.js +12 -12
  69. package/dist/content/stages/schema-types.d.ts +2 -2
  70. package/dist/content/stages/scope.js +15 -12
  71. package/dist/content/stages/ship.js +3 -3
  72. package/dist/content/stages/spec.js +9 -3
  73. package/dist/content/stages/tdd.js +14 -4
  74. package/dist/content/start-command.js +11 -10
  75. package/dist/content/status-command.js +5 -5
  76. package/dist/content/subagent-context-skills.js +156 -1
  77. package/dist/content/subagents.d.ts +0 -5
  78. package/dist/content/subagents.js +65 -81
  79. package/dist/content/templates.d.ts +1 -1
  80. package/dist/content/templates.js +187 -154
  81. package/dist/content/tree-command.js +2 -2
  82. package/dist/content/utility-skills.d.ts +2 -2
  83. package/dist/content/utility-skills.js +28 -99
  84. package/dist/content/view-command.js +4 -2
  85. package/dist/delegation.d.ts +2 -0
  86. package/dist/delegation.js +2 -1
  87. package/dist/early-loop.d.ts +66 -0
  88. package/dist/early-loop.js +275 -0
  89. package/dist/flow-state.d.ts +5 -6
  90. package/dist/flow-state.js +4 -6
  91. package/dist/gate-evidence.d.ts +0 -23
  92. package/dist/gate-evidence.js +111 -153
  93. package/dist/harness-adapters.d.ts +2 -2
  94. package/dist/harness-adapters.js +48 -19
  95. package/dist/install.js +190 -32
  96. package/dist/internal/advance-stage/advance.d.ts +50 -0
  97. package/dist/internal/advance-stage/advance.js +479 -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 +161 -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 +5 -28
  125. package/dist/knowledge-store.js +57 -84
  126. package/dist/managed-resources.js +24 -2
  127. package/dist/policy.js +7 -9
  128. package/dist/retro-gate.js +8 -90
  129. package/dist/run-archive.d.ts +1 -1
  130. package/dist/run-archive.js +13 -16
  131. package/dist/run-persistence.js +20 -15
  132. package/dist/runtime/run-hook.entry.d.ts +3 -0
  133. package/dist/runtime/run-hook.entry.js +5 -0
  134. package/dist/runtime/run-hook.mjs +9477 -0
  135. package/dist/tdd-cycle.d.ts +3 -3
  136. package/dist/tdd-cycle.js +1 -1
  137. package/dist/types.d.ts +18 -10
  138. package/package.json +4 -2
  139. package/dist/content/hook-inline-snippets.d.ts +0 -83
  140. package/dist/content/hook-inline-snippets.js +0 -302
  141. package/dist/content/ideate-command.d.ts +0 -8
  142. package/dist/content/ideate-command.js +0 -315
  143. package/dist/content/ideate-frames.d.ts +0 -31
  144. package/dist/content/ideate-frames.js +0 -140
  145. package/dist/content/ideate-ranking.d.ts +0 -25
  146. package/dist/content/ideate-ranking.js +0 -65
  147. package/dist/content/next-command.d.ts +0 -20
  148. package/dist/content/next-command.js +0 -298
  149. package/dist/content/seed-shelf.d.ts +0 -36
  150. package/dist/content/seed-shelf.js +0 -301
  151. package/dist/content/stage-common-guidance.d.ts +0 -1
  152. package/dist/content/stage-common-guidance.js +0 -106
  153. package/dist/doctor-registry.d.ts +0 -10
  154. package/dist/doctor-registry.js +0 -186
  155. package/dist/doctor.d.ts +0 -17
  156. package/dist/doctor.js +0 -2201
  157. package/dist/internal/hook-manifest.d.ts +0 -16
  158. package/dist/internal/hook-manifest.js +0 -77
  159. package/dist/trace-matrix.d.ts +0 -27
  160. package/dist/trace-matrix.js +0 -226
package/dist/install.js CHANGED
@@ -5,22 +5,21 @@ 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.js";
11
10
  import { startCommandContract, startCommandSkillMarkdown } from "./content/start-command.js";
12
11
  import { viewCommandContract, viewCommandSkillMarkdown } from "./content/view-command.js";
13
12
  import { cancelCommandContract, cancelCommandSkillMarkdown } from "./content/cancel-command.js";
14
13
  import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
15
14
  import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
16
15
  import { ironLawRuntimeDocument, ironLawsSkillMarkdown } from "./content/iron-laws.js";
17
- 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";
18
17
  import { nodeHookRuntimeScript } from "./content/node-hooks.js";
19
18
  import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
20
19
  import { ARTIFACT_TEMPLATES, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
21
20
  import { STATE_CONTRACTS } from "./content/state-contracts.js";
22
21
  import { REVIEW_PROMPTS } from "./content/review-prompts.js";
23
- import { stageSkillFolder, stageSkillMarkdown } from "./content/skills.js";
22
+ import { stageSkillFolder, stageSkillMarkdown, executingWavesSkillMarkdown } from "./content/skills.js";
24
23
  import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LANGUAGE_RULE_PACK_GENERATORS, LEGACY_LANGUAGE_RULE_PACK_FOLDERS } from "./content/utility-skills.js";
25
24
  import { RESEARCH_PLAYBOOKS } from "./content/research-playbooks.js";
26
25
  import { SUBAGENT_CONTEXT_SKILLS } from "./content/subagent-context-skills.js";
@@ -29,10 +28,11 @@ import { createInitialFlowState } from "./flow-state.js";
29
28
  import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
30
29
  import { ManagedResourceSession, setActiveManagedResourceSession } from "./managed-resources.js";
31
30
  import { ensureGitignore, removeGitignorePatterns } from "./gitignore.js";
32
- import { HARNESS_ADAPTERS, harnessShimFileNames, syncHarnessShims, removeCclawFromAgentsMd } from "./harness-adapters.js";
31
+ import { HARNESS_ADAPTERS, harnessShimFileNames, harnessShimSkillNames, syncHarnessShims, removeCclawFromAgentsMd } from "./harness-adapters.js";
33
32
  import { validateHookDocument } from "./hook-schema.js";
34
33
  import { detectHarnesses } from "./init-detect.js";
35
- import { ensureRunSystem } from "./runs.js";
34
+ import { classifyCodexHooksFlag, codexConfigPath, readCodexConfig } from "./codex-feature-flag.js";
35
+ import { CorruptFlowStateError, ensureRunSystem } from "./runs.js";
36
36
  import { FLOW_STAGES } from "./types.js";
37
37
  const OPENCODE_PLUGIN_REL_PATH = ".opencode/plugins/cclaw-plugin.mjs";
38
38
  const CURSOR_RULE_REL_PATH = ".cursor/rules/cclaw-workflow.mdc";
@@ -49,6 +49,23 @@ async function writeInitSentinel(projectRoot, operation) {
49
49
  await writeFileSafe(sentinelPath, `${JSON.stringify({ operation, startedAt: new Date().toISOString() }, null, 2)}\n`);
50
50
  return sentinelPath;
51
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
+ }
52
69
  async function removeBestEffort(targetPath, recursive = false) {
53
70
  try {
54
71
  await fs.rm(targetPath, { recursive, force: true });
@@ -89,6 +106,16 @@ const DEPRECATED_UTILITY_SKILL_FOLDERS = [
89
106
  "flow-tree",
90
107
  "flow-diff"
91
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
+ ];
92
119
  const DEPRECATED_AGENT_FILES = [
93
120
  "securityer.md",
94
121
  "spec-reviewer.md",
@@ -443,6 +470,9 @@ async function writeArtifactTemplates(projectRoot) {
443
470
  await writeFileSafe(runtimePath(projectRoot, "templates", "state-contracts", fileName), content);
444
471
  }));
445
472
  }
473
+ async function writeWavePlansScaffold(projectRoot) {
474
+ await writeFileSafe(runtimePath(projectRoot, "wave-plans", ".gitkeep"), "");
475
+ }
446
476
  async function writeSkills(projectRoot, config) {
447
477
  const skillTrack = config?.defaultTrack ?? "standard";
448
478
  for (const stage of FLOW_STAGES) {
@@ -451,8 +481,7 @@ async function writeSkills(projectRoot, config) {
451
481
  }
452
482
  // Utility skills (not flow stages)
453
483
  await writeFileSafe(runtimePath(projectRoot, "skills", "learnings", "SKILL.md"), learnSkillMarkdown());
454
- await writeFileSafe(runtimePath(projectRoot, "skills", "flow-next-step", "SKILL.md"), nextCommandSkillMarkdown());
455
- await writeFileSafe(runtimePath(projectRoot, "skills", "flow-ideate", "SKILL.md"), ideateCommandSkillMarkdown());
484
+ await writeFileSafe(runtimePath(projectRoot, "skills", "flow-idea", "SKILL.md"), ideaCommandSkillMarkdown());
456
485
  await writeFileSafe(runtimePath(projectRoot, "skills", "flow-start", "SKILL.md"), startCommandSkillMarkdown());
457
486
  await writeFileSafe(runtimePath(projectRoot, "skills", "flow-view", "SKILL.md"), viewCommandSkillMarkdown());
458
487
  await writeFileSafe(runtimePath(projectRoot, "skills", "flow-cancel", "SKILL.md"), cancelCommandSkillMarkdown());
@@ -460,6 +489,7 @@ async function writeSkills(projectRoot, config) {
460
489
  await writeFileSafe(runtimePath(projectRoot, "skills", "parallel-dispatch", "SKILL.md"), parallelAgentsSkill());
461
490
  await writeFileSafe(runtimePath(projectRoot, "skills", "session", "SKILL.md"), sessionHooksSkillMarkdown());
462
491
  await writeFileSafe(runtimePath(projectRoot, "skills", "iron-laws", "SKILL.md"), ironLawsSkillMarkdown());
492
+ await writeFileSafe(runtimePath(projectRoot, "skills", "executing-waves", "SKILL.md"), executingWavesSkillMarkdown());
463
493
  await writeFileSafe(runtimePath(projectRoot, "skills", META_SKILL_NAME, "SKILL.md"), usingCclawSkillMarkdown());
464
494
  // In-thread research procedures (no YAML frontmatter, not delegated personas).
465
495
  for (const [fileName, markdown] of Object.entries(RESEARCH_PLAYBOOKS)) {
@@ -516,8 +546,7 @@ async function writeSkills(projectRoot, config) {
516
546
  }
517
547
  async function writeEntryCommands(projectRoot) {
518
548
  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());
549
+ await writeFileSafe(runtimePath(projectRoot, "commands", "idea.md"), ideaCommandContract());
521
550
  await writeFileSafe(runtimePath(projectRoot, "commands", "view.md"), viewCommandContract());
522
551
  await writeFileSafe(runtimePath(projectRoot, "commands", "cancel.md"), cancelCommandContract());
523
552
  for (const stage of FLOW_STAGES) {
@@ -858,18 +887,47 @@ async function writeMergedHookJson(projectRoot, hookFilePath, generatedJson) {
858
887
  if (harness) {
859
888
  const generatedSchema = validateHookDocument(harness, generatedDoc);
860
889
  if (!generatedSchema.ok) {
861
- throw new Error(`Generated ${harness} hook document failed schema validation: ${generatedSchema.errors.join("; ")}`);
890
+ throw new Error(`[sync fail-fast] Hook document drift detected for ${harness}: generated hook document is invalid (${generatedSchema.errors.join("; ")}). ` +
891
+ "Run `npx cclaw-cli sync` to regenerate managed hooks or repair the generated hook shape manually.");
862
892
  }
863
893
  }
864
894
  const mergedDoc = mergeHookDocuments(existingDoc, generatedDoc);
865
895
  if (harness) {
866
896
  const mergedSchema = validateHookDocument(harness, mergedDoc);
867
897
  if (!mergedSchema.ok) {
868
- throw new Error(`Merged ${harness} hook document failed schema validation: ${mergedSchema.errors.join("; ")}`);
898
+ throw new Error(`[sync fail-fast] Hook document drift detected for ${harness}: merged hook document is invalid (${mergedSchema.errors.join("; ")}). ` +
899
+ "Run `npx cclaw-cli sync` after fixing the custom hook entry or remove the malformed user-authored hook block.");
869
900
  }
870
901
  }
871
902
  await writeFileSafe(hookFilePath, `${JSON.stringify(mergedDoc, null, 2)}\n`);
872
903
  }
904
+ async function readBundledRunHookRuntimeScript(options) {
905
+ const bundleUrl = new URL("./runtime/run-hook.mjs", import.meta.url);
906
+ try {
907
+ await fs.stat(bundleUrl);
908
+ }
909
+ catch {
910
+ return null;
911
+ }
912
+ try {
913
+ const moduleUrl = `${bundleUrl.href}?ts=${Date.now()}`;
914
+ const loaded = await import(moduleUrl);
915
+ const factory = typeof loaded.buildRunHookRuntimeScript === "function"
916
+ ? loaded.buildRunHookRuntimeScript
917
+ : typeof loaded.default === "function"
918
+ ? loaded.default
919
+ : null;
920
+ if (!factory)
921
+ return null;
922
+ const script = factory(options);
923
+ if (typeof script !== "string")
924
+ return null;
925
+ return script.trim().length > 0 ? script : null;
926
+ }
927
+ catch {
928
+ return null;
929
+ }
930
+ }
873
931
  async function writeHooks(projectRoot, config) {
874
932
  const harnesses = config.harnesses;
875
933
  const hooksDir = runtimePath(projectRoot, "hooks");
@@ -883,12 +941,17 @@ async function writeHooks(projectRoot, config) {
883
941
  }), null, 2)}\n`);
884
942
  await writeFileSafe(path.join(hooksDir, "stage-complete.mjs"), stageCompleteScript());
885
943
  await writeFileSafe(path.join(hooksDir, "start-flow.mjs"), startFlowScript());
886
- await writeFileSafe(path.join(hooksDir, "run-hook.mjs"), nodeHookRuntimeScript({
944
+ await writeFileSafe(path.join(hooksDir, "cancel-run.mjs"), cancelRunScript());
945
+ const hookRuntimeOptions = {
887
946
  strictness: effectiveStrictness,
888
947
  tddTestPathPatterns: config.tdd?.testPathPatterns ?? config.tddTestGlobs,
889
948
  tddProductionPathPatterns: config.tdd?.productionPathPatterns,
890
- compoundRecurrenceThreshold: config.compound?.recurrenceThreshold
891
- }));
949
+ compoundRecurrenceThreshold: config.compound?.recurrenceThreshold,
950
+ earlyLoopEnabled: config.earlyLoop?.enabled,
951
+ earlyLoopMaxIterations: config.earlyLoop?.maxIterations
952
+ };
953
+ const bundledHookRuntime = await readBundledRunHookRuntimeScript(hookRuntimeOptions);
954
+ await writeFileSafe(path.join(hooksDir, "run-hook.mjs"), bundledHookRuntime ?? nodeHookRuntimeScript(hookRuntimeOptions));
892
955
  await writeFileSafe(path.join(hooksDir, "run-hook.cmd"), runHookCmdScript());
893
956
  await writeFileSafe(path.join(hooksDir, "delegation-record.mjs"), delegationRecordScript());
894
957
  const opencodePluginSource = opencodePluginJs();
@@ -900,7 +963,8 @@ async function writeHooks(projectRoot, config) {
900
963
  "run-hook.mjs",
901
964
  "run-hook.cmd",
902
965
  "delegation-record.mjs",
903
- "opencode-plugin.mjs"
966
+ "opencode-plugin.mjs",
967
+ "cancel-run.mjs"
904
968
  ]) {
905
969
  await fs.chmod(path.join(hooksDir, script), 0o755);
906
970
  }
@@ -938,8 +1002,8 @@ async function writeHooks(projectRoot, config) {
938
1002
  // flag in `~/.codex/config.toml`. cclaw always writes the file so
939
1003
  // the moment the flag flips on, the cclaw hooks start firing. See
940
1004
  // `codexHooksJsonWithObservation` for the Bash-only caveat on
941
- // PreToolUse/PostToolUse. `cclaw doctor` warns if the feature flag
942
- // is not set.
1005
+ // PreToolUse/PostToolUse. If the feature flag is off, hooks remain
1006
+ // inert until the user enables codex_hooks in ~/.codex/config.toml.
943
1007
  const codexDir = path.join(projectRoot, ".codex");
944
1008
  await ensureDir(codexDir);
945
1009
  await writeMergedHookJson(projectRoot, path.join(codexDir, "hooks.json"), codexHooksJson());
@@ -989,7 +1053,7 @@ async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
989
1053
  for (const entry of managedHookFiles) {
990
1054
  if (enabled.has(entry.harness))
991
1055
  continue;
992
- await removeManagedHookEntries(entry.hookPath);
1056
+ await removeManagedHookEntries(entry.hookPath, { failOnParseError: true });
993
1057
  }
994
1058
  if (!enabled.has("opencode")) {
995
1059
  try {
@@ -998,8 +1062,22 @@ async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
998
1062
  catch {
999
1063
  // best-effort cleanup
1000
1064
  }
1065
+ try {
1066
+ await fs.rm(path.join(projectRoot, ".opencode/agents"), { recursive: true, force: true });
1067
+ }
1068
+ catch {
1069
+ // best-effort cleanup
1070
+ }
1001
1071
  await removeManagedOpenCodePluginConfig(projectRoot, OPENCODE_PLUGIN_REL_PATH);
1002
1072
  }
1073
+ if (!enabled.has("codex")) {
1074
+ try {
1075
+ await fs.rm(path.join(projectRoot, ".codex/agents"), { recursive: true, force: true });
1076
+ }
1077
+ catch {
1078
+ // best-effort cleanup
1079
+ }
1080
+ }
1003
1081
  }
1004
1082
  async function writeState(projectRoot, config, forceReset = false) {
1005
1083
  const statePath = runtimePath(projectRoot, "state", "flow-state.json");
@@ -1013,6 +1091,9 @@ async function cleanLegacyArtifacts(projectRoot) {
1013
1091
  for (const legacyFolder of DEPRECATED_UTILITY_SKILL_FOLDERS) {
1014
1092
  await removeBestEffort(runtimePath(projectRoot, "skills", legacyFolder), true);
1015
1093
  }
1094
+ for (const legacyFolder of DEPRECATED_STAGE_SKILL_FOLDERS) {
1095
+ await removeBestEffort(runtimePath(projectRoot, "skills", legacyFolder), true);
1096
+ }
1016
1097
  for (const legacyAgentFile of DEPRECATED_AGENT_FILES) {
1017
1098
  await removeBestEffort(runtimePath(projectRoot, "agents", legacyAgentFile));
1018
1099
  }
@@ -1037,16 +1118,30 @@ async function cleanLegacyArtifacts(projectRoot) {
1037
1118
  for (const legacyRuntimeDir of DEPRECATED_RUNTIME_DIRS) {
1038
1119
  await removeBestEffort(runtimePath(projectRoot, legacyRuntimeDir), true);
1039
1120
  }
1040
- // D-4 terminology migration: rename historical ideation artifacts to the
1041
- // canonical ideate-* naming without deleting user-authored content.
1121
+ // Archive storage migration: `.cclaw/runs` is legacy and no longer a valid
1122
+ // archive root. Remove only when empty; otherwise keep it so users can
1123
+ // manually migrate or inspect old data.
1124
+ const legacyRunsDir = runtimePath(projectRoot, "runs");
1125
+ try {
1126
+ const entries = await fs.readdir(legacyRunsDir);
1127
+ if (entries.length === 0) {
1128
+ await fs.rm(legacyRunsDir, { recursive: true, force: true });
1129
+ }
1130
+ }
1131
+ catch {
1132
+ // missing or unreadable legacy dir; keep best-effort behavior
1133
+ }
1134
+ // D-4 terminology migration: rename historical ideation artifact prefixes to
1135
+ // the canonical idea-* naming without deleting user-authored content.
1136
+ const legacyIdeaArtifactPattern = /^ideation-(.+\.md)$/u;
1042
1137
  const artifactsDir = runtimePath(projectRoot, "artifacts");
1043
1138
  try {
1044
1139
  const entries = await fs.readdir(artifactsDir);
1045
1140
  for (const entry of entries) {
1046
- const match = /^ideation-(.+\.md)$/u.exec(entry);
1141
+ const match = legacyIdeaArtifactPattern.exec(entry);
1047
1142
  if (!match)
1048
1143
  continue;
1049
- const nextName = `ideate-${match[1]}`;
1144
+ const nextName = `idea-${match[1]}`;
1050
1145
  const from = path.join(artifactsDir, entry);
1051
1146
  const to = path.join(artifactsDir, nextName);
1052
1147
  if (await exists(to)) {
@@ -1094,7 +1189,34 @@ async function cleanStaleFiles(projectRoot) {
1094
1189
  // Keep user-owned custom assets under .cclaw/agents and .cclaw/skills.
1095
1190
  // Legacy managed removals happen in cleanLegacyArtifacts() with explicit paths.
1096
1191
  }
1192
+ async function assertExpectedHarnessShims(projectRoot, harnesses) {
1193
+ const expectedFiles = harnessShimFileNames();
1194
+ const expectedSkillFolders = harnessShimSkillNames();
1195
+ for (const harness of harnesses) {
1196
+ const adapter = HARNESS_ADAPTERS[harness];
1197
+ const base = path.join(projectRoot, adapter.commandDir);
1198
+ for (const fileName of expectedFiles) {
1199
+ const target = adapter.shimKind === "skill"
1200
+ ? path.join(base, fileName.replace(/\.md$/u, ""), "SKILL.md")
1201
+ : path.join(base, fileName);
1202
+ if (!(await exists(target))) {
1203
+ throw new Error(`[sync fail-fast] Harness shim drift detected for ${harness}: missing ${target}. ` +
1204
+ `Run \`npx cclaw-cli sync\` again; if the file is still missing, inspect harness permissions/paths.`);
1205
+ }
1206
+ }
1207
+ if (adapter.shimKind === "skill") {
1208
+ for (const folder of expectedSkillFolders) {
1209
+ const skillPath = path.join(base, folder, "SKILL.md");
1210
+ if (!(await exists(skillPath))) {
1211
+ throw new Error(`[sync fail-fast] Harness skill shim drift detected for ${harness}: missing ${skillPath}. ` +
1212
+ `Run \`npx cclaw-cli sync\` again; if the issue persists, inspect generated .agents/skills surfaces.`);
1213
+ }
1214
+ }
1215
+ }
1216
+ }
1217
+ }
1097
1218
  async function materializeRuntime(projectRoot, config, forceStateReset, operation = "sync") {
1219
+ await warnStaleInitSentinel(projectRoot, operation);
1098
1220
  const sentinelPath = await writeInitSentinel(projectRoot, operation);
1099
1221
  const managedSession = await ManagedResourceSession.create({ projectRoot, operation });
1100
1222
  setActiveManagedResourceSession(managedSession);
@@ -1107,28 +1229,55 @@ async function materializeRuntime(projectRoot, config, forceStateReset, operatio
1107
1229
  writeEntryCommands(projectRoot),
1108
1230
  writeSkills(projectRoot, config),
1109
1231
  writeArtifactTemplates(projectRoot),
1232
+ writeWavePlansScaffold(projectRoot),
1110
1233
  writeRulebook(projectRoot)
1111
1234
  ]);
1112
1235
  await writeState(projectRoot, config, forceStateReset);
1113
- await ensureRunSystem(projectRoot, { createIfMissing: false });
1236
+ try {
1237
+ await ensureRunSystem(projectRoot, { createIfMissing: false });
1238
+ }
1239
+ catch (error) {
1240
+ if (error instanceof CorruptFlowStateError) {
1241
+ throw new Error(`[sync fail-fast] Corrupt flow state detected: ${error.message} ` +
1242
+ `Resolve the quarantined flow-state file and re-run \`npx cclaw-cli sync\`.`);
1243
+ }
1244
+ throw error;
1245
+ }
1114
1246
  await ensureKnowledgeStore(projectRoot);
1115
1247
  await writeHooks(projectRoot, config);
1116
1248
  await syncDisabledHarnessArtifacts(projectRoot, harnesses);
1117
1249
  await syncManagedGitHooks(projectRoot, config);
1118
1250
  await syncHarnessShims(projectRoot, harnesses);
1251
+ await assertExpectedHarnessShims(projectRoot, harnesses);
1119
1252
  await writeCursorWorkflowRule(projectRoot, harnesses);
1120
1253
  await ensureGitignore(projectRoot);
1121
1254
  await managedSession.commit();
1122
1255
  await fs.unlink(sentinelPath).catch(() => undefined);
1123
1256
  }
1124
1257
  catch (error) {
1125
- // Leave the sentinel in place so doctor can surface the interrupted run.
1258
+ // Leave the sentinel in place so the interrupted run is visible.
1126
1259
  throw error;
1127
1260
  }
1128
1261
  finally {
1129
1262
  setActiveManagedResourceSession(null);
1130
1263
  }
1131
1264
  }
1265
+ async function warnCodexHooksFeatureFlagIfDisabled(harnesses) {
1266
+ if (!harnesses.includes("codex"))
1267
+ return;
1268
+ const codexTomlPath = codexConfigPath();
1269
+ let existing;
1270
+ try {
1271
+ existing = await readCodexConfig(codexTomlPath);
1272
+ }
1273
+ catch (error) {
1274
+ process.stderr.write(`cclaw: could not read ${codexTomlPath} to validate codex_hooks flag: ${error instanceof Error ? error.message : String(error)}\n`);
1275
+ return;
1276
+ }
1277
+ if (classifyCodexHooksFlag(existing) === "enabled")
1278
+ return;
1279
+ 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`);
1280
+ }
1132
1281
  export async function initCclaw(options) {
1133
1282
  if (options.harnesses !== undefined && options.harnesses.length === 0) {
1134
1283
  throw new Error("Select at least one harness.");
@@ -1157,7 +1306,7 @@ export async function syncCclaw(projectRoot, options = {}) {
1157
1306
  // Prefer detected harness markers over the hardcoded default list.
1158
1307
  // Without this, a user running `cclaw sync` in a `.claude`-only
1159
1308
  // project ends up with a config that also enables cursor/opencode/
1160
- // codex, which then fails doctor checks for missing shim folders.
1309
+ // codex, which then creates invalid harness expectations.
1161
1310
  // Fall back to the previous default (config.harnesses) if no markers
1162
1311
  // are found so brand-new projects still bootstrap cleanly.
1163
1312
  const detected = await detectHarnesses(projectRoot);
@@ -1177,6 +1326,7 @@ export async function syncCclaw(projectRoot, options = {}) {
1177
1326
  });
1178
1327
  }
1179
1328
  await materializeRuntime(projectRoot, config, false, "sync");
1329
+ await warnCodexHooksFeatureFlagIfDisabled(config.harnesses);
1180
1330
  }
1181
1331
  /**
1182
1332
  * Refresh generated files in `.cclaw/` without touching user-authored
@@ -1274,20 +1424,28 @@ function isManagedRuntimeHookCommand(command) {
1274
1424
  // Codex UserPromptSubmit non-blocking state nudge.
1275
1425
  return /internal verify-current-state(?:\s|$)/u.test(normalized);
1276
1426
  }
1277
- async function removeManagedHookEntries(hookFilePath) {
1427
+ async function removeManagedHookEntries(hookFilePath, options = {}) {
1278
1428
  if (!(await exists(hookFilePath)))
1279
1429
  return;
1280
1430
  let parsed = null;
1281
1431
  try {
1282
1432
  const raw = await fs.readFile(hookFilePath, "utf8");
1283
1433
  const recovered = tryParseHookDocument(raw);
1284
- parsed = recovered?.parsed ?? null;
1434
+ if (recovered === null) {
1435
+ if (options.failOnParseError === true) {
1436
+ throw new Error(`[sync fail-fast] Cannot strip managed hook entries from ${hookFilePath} — JSON is unparseable. ` +
1437
+ `Run \`rm ${hookFilePath}\` and rerun \`npx cclaw-cli sync\`.`);
1438
+ }
1439
+ return;
1440
+ }
1441
+ parsed = recovered.parsed;
1285
1442
  }
1286
- catch {
1443
+ catch (error) {
1444
+ if (options.failOnParseError === true) {
1445
+ 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\`.`);
1446
+ }
1287
1447
  return;
1288
1448
  }
1289
- if (parsed === null)
1290
- return;
1291
1449
  const { updated, changed } = stripManagedHookCommands(parsed);
1292
1450
  if (!changed)
1293
1451
  return;
@@ -1369,7 +1527,7 @@ export async function uninstallCclaw(projectRoot) {
1369
1527
  try {
1370
1528
  const entries = await fs.readdir(codexSkillsRoot);
1371
1529
  for (const entry of entries) {
1372
- if (/^(?:cclaw-)?cc(?:-(?:next|view|finish|cancel|ops|ideate|brainstorm|scope|design|spec|plan|tdd|review|ship))?$/u.test(entry)) {
1530
+ if (/^(?:cclaw-)?cc(?:-(?:next|view|finish|cancel|ops|idea|brainstorm|scope|design|spec|plan|tdd|review|ship))?$/u.test(entry)) {
1373
1531
  await fs.rm(path.join(codexSkillsRoot, entry), { recursive: true, force: true });
1374
1532
  }
1375
1533
  }
@@ -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 {};