cclaw-cli 0.51.30 → 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 (142) 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 +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 +9 -15
  32. package/dist/content/cancel-command.js +2 -2
  33. package/dist/content/closeout-guidance.js +10 -7
  34. package/dist/content/core-agents.d.ts +18 -0
  35. package/dist/content/core-agents.js +46 -2
  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 +7 -7
  56. package/dist/content/node-hooks.d.ts +10 -0
  57. package/dist/content/node-hooks.js +43 -9
  58. package/dist/content/opencode-plugin.js +3 -3
  59. package/dist/content/skills.js +19 -7
  60. package/dist/content/stage-schema.js +44 -2
  61. package/dist/content/stages/_lint-metadata/index.js +26 -2
  62. package/dist/content/stages/brainstorm.js +13 -7
  63. package/dist/content/stages/design.js +16 -11
  64. package/dist/content/stages/plan.js +7 -4
  65. package/dist/content/stages/review.js +4 -4
  66. package/dist/content/stages/schema-types.d.ts +1 -1
  67. package/dist/content/stages/scope.js +15 -12
  68. package/dist/content/stages/ship.js +2 -2
  69. package/dist/content/stages/spec.js +9 -3
  70. package/dist/content/stages/tdd.js +14 -4
  71. package/dist/content/start-command.js +11 -10
  72. package/dist/content/status-command.js +3 -3
  73. package/dist/content/subagents.js +60 -6
  74. package/dist/content/templates.d.ts +1 -1
  75. package/dist/content/templates.js +102 -150
  76. package/dist/content/tree-command.js +2 -2
  77. package/dist/content/utility-skills.d.ts +2 -2
  78. package/dist/content/utility-skills.js +2 -2
  79. package/dist/content/view-command.js +4 -2
  80. package/dist/delegation.d.ts +2 -0
  81. package/dist/delegation.js +2 -1
  82. package/dist/early-loop.d.ts +66 -0
  83. package/dist/early-loop.js +275 -0
  84. package/dist/gate-evidence.d.ts +8 -0
  85. package/dist/gate-evidence.js +141 -5
  86. package/dist/harness-adapters.d.ts +2 -2
  87. package/dist/harness-adapters.js +47 -18
  88. package/dist/install.js +153 -29
  89. package/dist/internal/advance-stage/advance.d.ts +50 -0
  90. package/dist/internal/advance-stage/advance.js +480 -0
  91. package/dist/internal/advance-stage/cancel-run.d.ts +8 -0
  92. package/dist/internal/advance-stage/cancel-run.js +19 -0
  93. package/dist/internal/advance-stage/flow-state-coercion.d.ts +3 -0
  94. package/dist/internal/advance-stage/flow-state-coercion.js +81 -0
  95. package/dist/internal/advance-stage/helpers.d.ts +14 -0
  96. package/dist/internal/advance-stage/helpers.js +145 -0
  97. package/dist/internal/advance-stage/hook.d.ts +8 -0
  98. package/dist/internal/advance-stage/hook.js +40 -0
  99. package/dist/internal/advance-stage/parsers.d.ts +54 -0
  100. package/dist/internal/advance-stage/parsers.js +307 -0
  101. package/dist/internal/advance-stage/review-loop.d.ts +7 -0
  102. package/dist/internal/advance-stage/review-loop.js +170 -0
  103. package/dist/internal/advance-stage/rewind.d.ts +14 -0
  104. package/dist/internal/advance-stage/rewind.js +108 -0
  105. package/dist/internal/advance-stage/start-flow.d.ts +11 -0
  106. package/dist/internal/advance-stage/start-flow.js +136 -0
  107. package/dist/internal/advance-stage/verify.d.ts +29 -0
  108. package/dist/internal/advance-stage/verify.js +225 -0
  109. package/dist/internal/advance-stage.js +21 -1470
  110. package/dist/internal/compound-readiness.d.ts +1 -1
  111. package/dist/internal/compound-readiness.js +2 -2
  112. package/dist/internal/early-loop-status.d.ts +7 -0
  113. package/dist/internal/early-loop-status.js +90 -0
  114. package/dist/internal/runtime-integrity.d.ts +7 -0
  115. package/dist/internal/runtime-integrity.js +288 -0
  116. package/dist/internal/tdd-red-evidence.js +1 -1
  117. package/dist/knowledge-store.d.ts +3 -8
  118. package/dist/knowledge-store.js +16 -29
  119. package/dist/managed-resources.js +24 -2
  120. package/dist/policy.js +4 -6
  121. package/dist/run-archive.d.ts +1 -1
  122. package/dist/run-archive.js +12 -12
  123. package/dist/run-persistence.js +111 -11
  124. package/dist/tdd-cycle.d.ts +3 -3
  125. package/dist/tdd-cycle.js +1 -1
  126. package/dist/types.d.ts +18 -10
  127. package/package.json +1 -1
  128. package/dist/content/ideate-command.d.ts +0 -8
  129. package/dist/content/ideate-frames.d.ts +0 -31
  130. package/dist/content/ideate-ranking.d.ts +0 -25
  131. package/dist/content/next-command.d.ts +0 -20
  132. package/dist/content/next-command.js +0 -298
  133. package/dist/content/seed-shelf.d.ts +0 -36
  134. package/dist/content/seed-shelf.js +0 -301
  135. package/dist/content/stage-common-guidance.d.ts +0 -1
  136. package/dist/content/stage-common-guidance.js +0 -106
  137. package/dist/doctor-registry.d.ts +0 -10
  138. package/dist/doctor-registry.js +0 -186
  139. package/dist/doctor.d.ts +0 -17
  140. package/dist/doctor.js +0 -2201
  141. package/dist/internal/hook-manifest.d.ts +0 -16
  142. package/dist/internal/hook-manifest.js +0 -77
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { RUNTIME_ROOT } from "./constants.js";
4
4
  import { conversationLanguagePolicyMarkdown } from "./content/language-policy.js";
5
5
  import { CCLAW_AGENTS, agentMarkdown } from "./content/core-agents.js";
6
- import { ironLawsAgentsMdBlock } from "./content/iron-laws.js";
6
+ import { IRON_LAWS } from "./content/iron-laws.js";
7
7
  import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
8
8
  export const CCLAW_MARKER_START = "<!-- cclaw-start -->";
9
9
  export const CCLAW_MARKER_END = "<!-- cclaw-end -->";
@@ -15,11 +15,11 @@ const RUNTIME_AGENTS_BLOCK_PATTERN = new RegExp(RUNTIME_AGENTS_BLOCK_SOURCE, "u"
15
15
  const RUNTIME_AGENTS_BLOCK_GLOBAL_PATTERN = new RegExp(RUNTIME_AGENTS_BLOCK_SOURCE, "gu");
16
16
  const UTILITY_SHIMS = [
17
17
  {
18
- fileName: "cc-ideate.md",
19
- skillName: "cc-ideate",
20
- command: "ideate",
21
- skillFolder: "flow-ideate",
22
- commandFile: "ideate.md"
18
+ fileName: "cc-idea.md",
19
+ skillName: "cc-idea",
20
+ command: "idea",
21
+ skillFolder: "flow-idea",
22
+ commandFile: "idea.md"
23
23
  },
24
24
  {
25
25
  fileName: "cc-cancel.md",
@@ -163,9 +163,9 @@ export function harnessDispatchSurface(harnessId) {
163
163
  case "cursor":
164
164
  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.";
165
165
  case "opencode":
166
- return "Use OpenCode subagents: invoke the generated .opencode/agents/<agent>.md agent via Task or @<agent>; if agents or plugin registration are missing, run `cclaw sync` and check opencode.json(.c) plugin registration with `cclaw doctor --explain`; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
166
+ return "Use OpenCode subagents: invoke the generated .opencode/agents/<agent>.md agent via Task or @<agent>; if agents or plugin registration are missing, run `cclaw sync` and check opencode.json(.c) plugin registration with `npx cclaw-cli sync`; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
167
167
  case "codex":
168
- return "Use Codex native subagents: ask Codex to spawn the generated .codex/agents/<agent>.toml agent(s) by name; if hooks are inert, set `[features] codex_hooks = true` in ~/.codex/config.toml or rerun init/sync repair, then `cclaw doctor --explain`; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
168
+ return "Use Codex native subagents: ask Codex to spawn the generated .codex/agents/<agent>.toml agent(s) by name; if hooks are inert, set `[features] codex_hooks = true` in ~/.codex/config.toml or rerun init/sync repair, then `npx cclaw-cli sync`; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
169
169
  }
170
170
  }
171
171
  /**
@@ -292,6 +292,35 @@ export function harnessesByTier() {
292
292
  return tierOrder[harnessTier(a)] - tierOrder[harnessTier(b)];
293
293
  });
294
294
  }
295
+ function ironLawsAgentsMdBlock() {
296
+ const enforcedLawIds = new Set([
297
+ "stop-clean-or-handoff",
298
+ "review-coverage-complete-before-ship"
299
+ ]);
300
+ const enforcedRows = IRON_LAWS
301
+ .filter((law) => enforcedLawIds.has(law.id))
302
+ .map((law) => `| \`${law.id}\` | ${law.rule} | ${law.enforcement} |`)
303
+ .join("\n");
304
+ const advisoryRows = IRON_LAWS
305
+ .filter((law) => !enforcedLawIds.has(law.id))
306
+ .map((law) => {
307
+ const appliesTo = law.appliesTo === "all" ? "all stages" : law.appliesTo.join(", ");
308
+ return `- \`${law.id}\` (applies to: ${appliesTo})`;
309
+ })
310
+ .join("\n");
311
+ return `### Iron Laws
312
+
313
+ These rules are always-on. The hook-enforced runtime laws are:
314
+
315
+ | ID | Rule | Enforced by |
316
+ |---|---|---|
317
+ ${enforcedRows}
318
+
319
+ Advisory laws are stage-owned through each stage's HARD-GATE block:
320
+
321
+ ${advisoryRows}
322
+ `;
323
+ }
295
324
  function agentsMdBlock() {
296
325
  return `${CCLAW_MARKER_START}
297
326
  ## Cclaw — Workflow Adapter
@@ -343,7 +372,7 @@ When in doubt, prefer **non-trivial** — the quick track is opt-in and only saf
343
372
  | Command | Purpose |
344
373
  |---|---|
345
374
  | \`/cc\` | **Entry point.** No args = resume or progress current flow. With prompt = classify task and start the right flow. |
346
- | \`/cc-ideate\` | **Ideate mode.** Generates a ranked repo-improvement backlog before implementation. |
375
+ | \`/cc-idea\` | **Idea mode.** Generates a ranked repo-improvement backlog before implementation. |
347
376
  | \`/cc-cancel\` | **Non-completion closeout.** Archives a cancelled/abandoned run with a required reason. |
348
377
 
349
378
  Knowledge capture and curation run automatically as part of stage completion
@@ -372,11 +401,11 @@ If the same approach fails three times in a row (same command, same finding, sam
372
401
  ### Codex users
373
402
 
374
403
  OpenAI Codex CLI has **no native \`/cc\` slash command** (custom prompts
375
- were deprecated in v0.89, Jan 2026). The \`/cc\`, \`/cc-ideate\`, and
404
+ were deprecated in v0.89, Jan 2026). The \`/cc\`, \`/cc-idea\`, and
376
405
  \`/cc-cancel\` tokens above describe intent — in Codex they map onto skills cclaw installs at
377
406
  \`.agents/skills/cc*/SKILL.md\`. Activate one of two ways:
378
407
 
379
- - Type \`/use cc\` (or \`cc-ideate\` / \`cc-cancel\`) at Codex's prompt.
408
+ - Type \`/use cc\` (or \`cc-idea\` / \`cc-cancel\`) at Codex's prompt.
380
409
  - Type \`/cc …\` as plain text — Codex matches the skill \`description\`
381
410
  frontmatter (which spells out the token verbatim) and loads the right
382
411
  skill body automatically.
@@ -385,9 +414,9 @@ Codex CLI v0.114+ (Mar 2026) **does** expose lifecycle hooks via
385
414
  \`.codex/hooks.json\`, gated by the \`[features] codex_hooks = true\` flag
386
415
  in \`~/.codex/config.toml\`. cclaw generates \`.codex/hooks.json\` on
387
416
  sync; if the feature flag is off, hooks are inert and cclaw's
388
- session-start rehydration simply does not fire. Run \`cclaw doctor\` to
417
+ session-start rehydration simply does not fire. Run \`npx cclaw-cli sync\` to
389
418
  see if the flag is missing. \`.codex/commands/*\` is still unused by
390
- Codex CLI and is removed on every sync. Run \`cclaw doctor --explain\` for
419
+ Codex CLI and is removed on every sync. Run \`npx cclaw-cli sync\` for
391
420
  hook coverage details (Bash-only \`PreToolUse\`/\`PostToolUse\`; other events are full).
392
421
  ${CCLAW_MARKER_END}`;
393
422
  }
@@ -449,7 +478,7 @@ function utilityShimBehavior(command) {
449
478
  switch (command) {
450
479
  case "cc":
451
480
  return "This is the entry command, not a flow stage. It may initialize or resume flow state after confirmation.";
452
- case "ideate":
481
+ case "idea":
453
482
  return "This is an ideation command, not a flow stage. It may write ideation artifacts/seeds but does not advance flow state.";
454
483
  case "cancel":
455
484
  return "This is a non-completion closeout utility, not a flow stage. It requires a reason and archives cancelled or abandoned work without presenting it as completed.";
@@ -484,8 +513,8 @@ function codexSkillDescription(command) {
484
513
  switch (command) {
485
514
  case "cc":
486
515
  return `Entry point for the cclaw track-aware workflow ending in ship plus auto-closeout (retro → compound → archive). Use whenever the user types \`/cc\`, \`/cclaw\`, or asks to "start the flow", "begin cclaw", "kick off the workflow", "classify this task", or wants to start/resume a non-trivial software change. No args = resume the active stage from \`.cclaw/state/flow-state.json\`. With a prompt = classify and pick a track (quick/medium/standard).`;
487
- case "ideate":
488
- return `Read-only repo-improvement ideate mode for cclaw. Use when the user types \`/cc-ideate\` or asks to "ideate", "scan the repo for TODOs/tech debt", "generate a backlog", or wants a ranked list of candidate ideas before committing to a single flow. Does not mutate \`.cclaw/state/flow-state.json\`.`;
516
+ case "idea":
517
+ return `Read-only repo-improvement idea mode for cclaw. Use when the user types \`/cc-idea\` or asks to "scan the repo for TODOs/tech debt", "generate a backlog", "brainstorm improvement ideas", or wants a ranked list of candidate ideas before committing to a single flow. Does not mutate \`.cclaw/state/flow-state.json\`.`;
489
518
  case "cancel":
490
519
  return `Cancel or abandon the active cclaw run. Use when the user types \`/cc-cancel\` or asks to cancel, abandon, stop, discard, or reset an unfinished run. Requires a reason and archives with cancelled/abandoned disposition.`;
491
520
  default:
@@ -518,7 +547,7 @@ under \`.agents/skills/${skillSlug}/\` so the user can either:
518
547
 
519
548
  Lifecycle hooks **are** available in Codex CLI v0.114+ (behind the
520
549
  \`[features] codex_hooks = true\` flag in \`~/.codex/config.toml\`) and
521
- cclaw installs a matching \`.codex/hooks.json\`; run \`cclaw doctor --explain\`
550
+ cclaw installs a matching \`.codex/hooks.json\`; run \`npx cclaw-cli sync\`
522
551
  for the current hook surface and limitations.
523
552
 
524
553
  ## Protocol
@@ -542,7 +571,7 @@ for the current hook surface and limitations.
542
571
  unavailable or disabled, and then include non-empty \`evidenceRefs\`.
543
572
  - Codex's \`PreToolUse\` / \`PostToolUse\` hooks currently only intercept
544
573
  the \`Bash\` tool. \`Write\`, \`Edit\`, \`WebSearch\`, and MCP tool calls
545
- are **not** gated by hooks — use \`cclaw doctor --explain\` for what cclaw
574
+ are **not** gated by hooks — use \`npx cclaw-cli sync\` for what cclaw
546
575
  substitutes with in-turn agent steps for those call classes.
547
576
  - Codex's \`SessionStart\` matcher only supports \`startup|resume\`. Claude
548
577
  and Cursor also fire on \`clear\` and \`compact\`, so mid-session
package/dist/install.js CHANGED
@@ -5,16 +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
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";
@@ -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",
@@ -451,8 +478,7 @@ async function writeSkills(projectRoot, config) {
451
478
  }
452
479
  // Utility skills (not flow stages)
453
480
  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());
481
+ await writeFileSafe(runtimePath(projectRoot, "skills", "flow-idea", "SKILL.md"), ideaCommandSkillMarkdown());
456
482
  await writeFileSafe(runtimePath(projectRoot, "skills", "flow-start", "SKILL.md"), startCommandSkillMarkdown());
457
483
  await writeFileSafe(runtimePath(projectRoot, "skills", "flow-view", "SKILL.md"), viewCommandSkillMarkdown());
458
484
  await writeFileSafe(runtimePath(projectRoot, "skills", "flow-cancel", "SKILL.md"), cancelCommandSkillMarkdown());
@@ -516,8 +542,7 @@ 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
547
  await writeFileSafe(runtimePath(projectRoot, "commands", "cancel.md"), cancelCommandContract());
523
548
  for (const stage of FLOW_STAGES) {
@@ -858,14 +883,16 @@ async function writeMergedHookJson(projectRoot, hookFilePath, generatedJson) {
858
883
  if (harness) {
859
884
  const generatedSchema = validateHookDocument(harness, generatedDoc);
860
885
  if (!generatedSchema.ok) {
861
- 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.");
862
888
  }
863
889
  }
864
890
  const mergedDoc = mergeHookDocuments(existingDoc, generatedDoc);
865
891
  if (harness) {
866
892
  const mergedSchema = validateHookDocument(harness, mergedDoc);
867
893
  if (!mergedSchema.ok) {
868
- 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.");
869
896
  }
870
897
  }
871
898
  await writeFileSafe(hookFilePath, `${JSON.stringify(mergedDoc, null, 2)}\n`);
@@ -883,11 +910,14 @@ async function writeHooks(projectRoot, config) {
883
910
  }), null, 2)}\n`);
884
911
  await writeFileSafe(path.join(hooksDir, "stage-complete.mjs"), stageCompleteScript());
885
912
  await writeFileSafe(path.join(hooksDir, "start-flow.mjs"), startFlowScript());
913
+ await writeFileSafe(path.join(hooksDir, "cancel-run.mjs"), cancelRunScript());
886
914
  await writeFileSafe(path.join(hooksDir, "run-hook.mjs"), nodeHookRuntimeScript({
887
915
  strictness: effectiveStrictness,
888
916
  tddTestPathPatterns: config.tdd?.testPathPatterns ?? config.tddTestGlobs,
889
917
  tddProductionPathPatterns: config.tdd?.productionPathPatterns,
890
- compoundRecurrenceThreshold: config.compound?.recurrenceThreshold
918
+ compoundRecurrenceThreshold: config.compound?.recurrenceThreshold,
919
+ earlyLoopEnabled: config.earlyLoop?.enabled,
920
+ earlyLoopMaxIterations: config.earlyLoop?.maxIterations
891
921
  }));
892
922
  await writeFileSafe(path.join(hooksDir, "run-hook.cmd"), runHookCmdScript());
893
923
  await writeFileSafe(path.join(hooksDir, "delegation-record.mjs"), delegationRecordScript());
@@ -900,7 +930,8 @@ async function writeHooks(projectRoot, config) {
900
930
  "run-hook.mjs",
901
931
  "run-hook.cmd",
902
932
  "delegation-record.mjs",
903
- "opencode-plugin.mjs"
933
+ "opencode-plugin.mjs",
934
+ "cancel-run.mjs"
904
935
  ]) {
905
936
  await fs.chmod(path.join(hooksDir, script), 0o755);
906
937
  }
@@ -938,8 +969,8 @@ async function writeHooks(projectRoot, config) {
938
969
  // flag in `~/.codex/config.toml`. cclaw always writes the file so
939
970
  // the moment the flag flips on, the cclaw hooks start firing. See
940
971
  // `codexHooksJsonWithObservation` for the Bash-only caveat on
941
- // PreToolUse/PostToolUse. `cclaw doctor` warns if the feature flag
942
- // 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.
943
974
  const codexDir = path.join(projectRoot, ".codex");
944
975
  await ensureDir(codexDir);
945
976
  await writeMergedHookJson(projectRoot, path.join(codexDir, "hooks.json"), codexHooksJson());
@@ -989,7 +1020,7 @@ async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
989
1020
  for (const entry of managedHookFiles) {
990
1021
  if (enabled.has(entry.harness))
991
1022
  continue;
992
- await removeManagedHookEntries(entry.hookPath);
1023
+ await removeManagedHookEntries(entry.hookPath, { failOnParseError: true });
993
1024
  }
994
1025
  if (!enabled.has("opencode")) {
995
1026
  try {
@@ -998,8 +1029,22 @@ async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
998
1029
  catch {
999
1030
  // best-effort cleanup
1000
1031
  }
1032
+ try {
1033
+ await fs.rm(path.join(projectRoot, ".opencode/agents"), { recursive: true, force: true });
1034
+ }
1035
+ catch {
1036
+ // best-effort cleanup
1037
+ }
1001
1038
  await removeManagedOpenCodePluginConfig(projectRoot, OPENCODE_PLUGIN_REL_PATH);
1002
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
+ }
1003
1048
  }
1004
1049
  async function writeState(projectRoot, config, forceReset = false) {
1005
1050
  const statePath = runtimePath(projectRoot, "state", "flow-state.json");
@@ -1013,6 +1058,9 @@ async function cleanLegacyArtifacts(projectRoot) {
1013
1058
  for (const legacyFolder of DEPRECATED_UTILITY_SKILL_FOLDERS) {
1014
1059
  await removeBestEffort(runtimePath(projectRoot, "skills", legacyFolder), true);
1015
1060
  }
1061
+ for (const legacyFolder of DEPRECATED_STAGE_SKILL_FOLDERS) {
1062
+ await removeBestEffort(runtimePath(projectRoot, "skills", legacyFolder), true);
1063
+ }
1016
1064
  for (const legacyAgentFile of DEPRECATED_AGENT_FILES) {
1017
1065
  await removeBestEffort(runtimePath(projectRoot, "agents", legacyAgentFile));
1018
1066
  }
@@ -1037,16 +1085,30 @@ async function cleanLegacyArtifacts(projectRoot) {
1037
1085
  for (const legacyRuntimeDir of DEPRECATED_RUNTIME_DIRS) {
1038
1086
  await removeBestEffort(runtimePath(projectRoot, legacyRuntimeDir), true);
1039
1087
  }
1040
- // D-4 terminology migration: rename historical ideation artifacts to the
1041
- // 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;
1042
1104
  const artifactsDir = runtimePath(projectRoot, "artifacts");
1043
1105
  try {
1044
1106
  const entries = await fs.readdir(artifactsDir);
1045
1107
  for (const entry of entries) {
1046
- const match = /^ideation-(.+\.md)$/u.exec(entry);
1108
+ const match = legacyIdeaArtifactPattern.exec(entry);
1047
1109
  if (!match)
1048
1110
  continue;
1049
- const nextName = `ideate-${match[1]}`;
1111
+ const nextName = `idea-${match[1]}`;
1050
1112
  const from = path.join(artifactsDir, entry);
1051
1113
  const to = path.join(artifactsDir, nextName);
1052
1114
  if (await exists(to)) {
@@ -1094,7 +1156,34 @@ async function cleanStaleFiles(projectRoot) {
1094
1156
  // Keep user-owned custom assets under .cclaw/agents and .cclaw/skills.
1095
1157
  // Legacy managed removals happen in cleanLegacyArtifacts() with explicit paths.
1096
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
+ }
1097
1185
  async function materializeRuntime(projectRoot, config, forceStateReset, operation = "sync") {
1186
+ await warnStaleInitSentinel(projectRoot, operation);
1098
1187
  const sentinelPath = await writeInitSentinel(projectRoot, operation);
1099
1188
  const managedSession = await ManagedResourceSession.create({ projectRoot, operation });
1100
1189
  setActiveManagedResourceSession(managedSession);
@@ -1110,25 +1199,51 @@ async function materializeRuntime(projectRoot, config, forceStateReset, operatio
1110
1199
  writeRulebook(projectRoot)
1111
1200
  ]);
1112
1201
  await writeState(projectRoot, config, forceStateReset);
1113
- 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
+ }
1114
1212
  await ensureKnowledgeStore(projectRoot);
1115
1213
  await writeHooks(projectRoot, config);
1116
1214
  await syncDisabledHarnessArtifacts(projectRoot, harnesses);
1117
1215
  await syncManagedGitHooks(projectRoot, config);
1118
1216
  await syncHarnessShims(projectRoot, harnesses);
1217
+ await assertExpectedHarnessShims(projectRoot, harnesses);
1119
1218
  await writeCursorWorkflowRule(projectRoot, harnesses);
1120
1219
  await ensureGitignore(projectRoot);
1121
1220
  await managedSession.commit();
1122
1221
  await fs.unlink(sentinelPath).catch(() => undefined);
1123
1222
  }
1124
1223
  catch (error) {
1125
- // Leave the sentinel in place so doctor can surface the interrupted run.
1224
+ // Leave the sentinel in place so the interrupted run is visible.
1126
1225
  throw error;
1127
1226
  }
1128
1227
  finally {
1129
1228
  setActiveManagedResourceSession(null);
1130
1229
  }
1131
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
+ }
1132
1247
  export async function initCclaw(options) {
1133
1248
  if (options.harnesses !== undefined && options.harnesses.length === 0) {
1134
1249
  throw new Error("Select at least one harness.");
@@ -1157,7 +1272,7 @@ export async function syncCclaw(projectRoot, options = {}) {
1157
1272
  // Prefer detected harness markers over the hardcoded default list.
1158
1273
  // Without this, a user running `cclaw sync` in a `.claude`-only
1159
1274
  // project ends up with a config that also enables cursor/opencode/
1160
- // codex, which then fails doctor checks for missing shim folders.
1275
+ // codex, which then creates invalid harness expectations.
1161
1276
  // Fall back to the previous default (config.harnesses) if no markers
1162
1277
  // are found so brand-new projects still bootstrap cleanly.
1163
1278
  const detected = await detectHarnesses(projectRoot);
@@ -1177,6 +1292,7 @@ export async function syncCclaw(projectRoot, options = {}) {
1177
1292
  });
1178
1293
  }
1179
1294
  await materializeRuntime(projectRoot, config, false, "sync");
1295
+ await warnCodexHooksFeatureFlagIfDisabled(config.harnesses);
1180
1296
  }
1181
1297
  /**
1182
1298
  * Refresh generated files in `.cclaw/` without touching user-authored
@@ -1274,20 +1390,28 @@ function isManagedRuntimeHookCommand(command) {
1274
1390
  // Codex UserPromptSubmit non-blocking state nudge.
1275
1391
  return /internal verify-current-state(?:\s|$)/u.test(normalized);
1276
1392
  }
1277
- async function removeManagedHookEntries(hookFilePath) {
1393
+ async function removeManagedHookEntries(hookFilePath, options = {}) {
1278
1394
  if (!(await exists(hookFilePath)))
1279
1395
  return;
1280
1396
  let parsed = null;
1281
1397
  try {
1282
1398
  const raw = await fs.readFile(hookFilePath, "utf8");
1283
1399
  const recovered = tryParseHookDocument(raw);
1284
- 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;
1285
1408
  }
1286
- 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
+ }
1287
1413
  return;
1288
1414
  }
1289
- if (parsed === null)
1290
- return;
1291
1415
  const { updated, changed } = stripManagedHookCommands(parsed);
1292
1416
  if (!changed)
1293
1417
  return;
@@ -1369,7 +1493,7 @@ export async function uninstallCclaw(projectRoot) {
1369
1493
  try {
1370
1494
  const entries = await fs.readdir(codexSkillsRoot);
1371
1495
  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)) {
1496
+ if (/^(?:cclaw-)?cc(?:-(?:next|view|finish|cancel|ops|idea|brainstorm|scope|design|spec|plan|tdd|review|ship))?$/u.test(entry)) {
1373
1497
  await fs.rm(path.join(codexSkillsRoot, entry), { recursive: true, force: true });
1374
1498
  }
1375
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 {};