cclaw-cli 7.7.1 → 8.1.1

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 (284) hide show
  1. package/README.md +211 -134
  2. package/dist/artifact-frontmatter.d.ts +51 -0
  3. package/dist/artifact-frontmatter.js +131 -0
  4. package/dist/artifact-paths.d.ts +7 -27
  5. package/dist/artifact-paths.js +20 -249
  6. package/dist/cancel.d.ts +16 -0
  7. package/dist/cancel.js +66 -0
  8. package/dist/cli.d.ts +2 -27
  9. package/dist/cli.js +107 -511
  10. package/dist/compound.d.ts +26 -0
  11. package/dist/compound.js +96 -0
  12. package/dist/config.d.ts +14 -51
  13. package/dist/config.js +23 -359
  14. package/dist/constants.d.ts +11 -18
  15. package/dist/constants.js +19 -106
  16. package/dist/content/antipatterns.d.ts +1 -0
  17. package/dist/content/antipatterns.js +109 -0
  18. package/dist/content/artifact-templates.d.ts +10 -0
  19. package/dist/content/artifact-templates.js +550 -0
  20. package/dist/content/cancel-command.d.ts +2 -2
  21. package/dist/content/cancel-command.js +25 -17
  22. package/dist/content/core-agents.d.ts +9 -233
  23. package/dist/content/core-agents.js +39 -768
  24. package/dist/content/decision-protocol.d.ts +1 -12
  25. package/dist/content/decision-protocol.js +27 -20
  26. package/dist/content/examples.d.ts +8 -42
  27. package/dist/content/examples.js +293 -425
  28. package/dist/content/idea-command.d.ts +2 -0
  29. package/dist/content/idea-command.js +38 -0
  30. package/dist/content/iron-laws.d.ts +4 -138
  31. package/dist/content/iron-laws.js +18 -197
  32. package/dist/content/meta-skill.d.ts +1 -3
  33. package/dist/content/meta-skill.js +57 -134
  34. package/dist/content/node-hooks.d.ts +12 -8
  35. package/dist/content/node-hooks.js +188 -838
  36. package/dist/content/recovery.d.ts +8 -0
  37. package/dist/content/recovery.js +179 -0
  38. package/dist/content/reference-patterns.d.ts +4 -13
  39. package/dist/content/reference-patterns.js +260 -389
  40. package/dist/content/research-playbooks.d.ts +8 -8
  41. package/dist/content/research-playbooks.js +108 -121
  42. package/dist/content/review-loop.d.ts +6 -192
  43. package/dist/content/review-loop.js +29 -731
  44. package/dist/content/skills.d.ts +8 -38
  45. package/dist/content/skills.js +681 -732
  46. package/dist/content/specialist-prompts/architect.d.ts +1 -0
  47. package/dist/content/specialist-prompts/architect.js +225 -0
  48. package/dist/content/specialist-prompts/brainstormer.d.ts +1 -0
  49. package/dist/content/specialist-prompts/brainstormer.js +168 -0
  50. package/dist/content/specialist-prompts/index.d.ts +2 -0
  51. package/dist/content/specialist-prompts/index.js +14 -0
  52. package/dist/content/specialist-prompts/planner.d.ts +1 -0
  53. package/dist/content/specialist-prompts/planner.js +182 -0
  54. package/dist/content/specialist-prompts/reviewer.d.ts +1 -0
  55. package/dist/content/specialist-prompts/reviewer.js +193 -0
  56. package/dist/content/specialist-prompts/security-reviewer.d.ts +1 -0
  57. package/dist/content/specialist-prompts/security-reviewer.js +133 -0
  58. package/dist/content/specialist-prompts/slice-builder.d.ts +1 -0
  59. package/dist/content/specialist-prompts/slice-builder.js +232 -0
  60. package/dist/content/stage-playbooks.d.ts +8 -0
  61. package/dist/content/stage-playbooks.js +404 -0
  62. package/dist/content/start-command.d.ts +2 -12
  63. package/dist/content/start-command.js +221 -207
  64. package/dist/flow-state.d.ts +21 -178
  65. package/dist/flow-state.js +67 -170
  66. package/dist/fs-utils.d.ts +6 -26
  67. package/dist/fs-utils.js +29 -162
  68. package/dist/gitignore.d.ts +2 -1
  69. package/dist/gitignore.js +51 -34
  70. package/dist/harness-detect.d.ts +10 -0
  71. package/dist/harness-detect.js +29 -0
  72. package/dist/harness-prompt.d.ts +26 -0
  73. package/dist/harness-prompt.js +142 -0
  74. package/dist/install.d.ts +35 -15
  75. package/dist/install.js +238 -1347
  76. package/dist/knowledge-store.d.ts +19 -163
  77. package/dist/knowledge-store.js +56 -590
  78. package/dist/logger.d.ts +8 -3
  79. package/dist/logger.js +13 -4
  80. package/dist/orchestrator-routing.d.ts +29 -0
  81. package/dist/orchestrator-routing.js +156 -0
  82. package/dist/run-persistence.d.ts +7 -118
  83. package/dist/run-persistence.js +29 -845
  84. package/dist/runtime/run-hook.entry.d.ts +1 -3
  85. package/dist/runtime/run-hook.entry.js +19 -4
  86. package/dist/runtime/run-hook.mjs +13 -1024
  87. package/dist/types.d.ts +25 -261
  88. package/dist/types.js +8 -36
  89. package/package.json +6 -3
  90. package/dist/artifact-linter/brainstorm.d.ts +0 -2
  91. package/dist/artifact-linter/brainstorm.js +0 -353
  92. package/dist/artifact-linter/design.d.ts +0 -18
  93. package/dist/artifact-linter/design.js +0 -444
  94. package/dist/artifact-linter/findings-dedup.d.ts +0 -56
  95. package/dist/artifact-linter/findings-dedup.js +0 -232
  96. package/dist/artifact-linter/plan.d.ts +0 -2
  97. package/dist/artifact-linter/plan.js +0 -826
  98. package/dist/artifact-linter/review-army.d.ts +0 -49
  99. package/dist/artifact-linter/review-army.js +0 -520
  100. package/dist/artifact-linter/review.d.ts +0 -2
  101. package/dist/artifact-linter/review.js +0 -113
  102. package/dist/artifact-linter/scope.d.ts +0 -2
  103. package/dist/artifact-linter/scope.js +0 -158
  104. package/dist/artifact-linter/shared.d.ts +0 -637
  105. package/dist/artifact-linter/shared.js +0 -2163
  106. package/dist/artifact-linter/ship.d.ts +0 -2
  107. package/dist/artifact-linter/ship.js +0 -250
  108. package/dist/artifact-linter/spec.d.ts +0 -2
  109. package/dist/artifact-linter/spec.js +0 -176
  110. package/dist/artifact-linter/tdd.d.ts +0 -118
  111. package/dist/artifact-linter/tdd.js +0 -1404
  112. package/dist/artifact-linter.d.ts +0 -15
  113. package/dist/artifact-linter.js +0 -517
  114. package/dist/codex-feature-flag.d.ts +0 -58
  115. package/dist/codex-feature-flag.js +0 -193
  116. package/dist/content/closeout-guidance.d.ts +0 -14
  117. package/dist/content/closeout-guidance.js +0 -44
  118. package/dist/content/diff-command.d.ts +0 -1
  119. package/dist/content/diff-command.js +0 -43
  120. package/dist/content/harness-doc.d.ts +0 -1
  121. package/dist/content/harness-doc.js +0 -65
  122. package/dist/content/hook-events.d.ts +0 -9
  123. package/dist/content/hook-events.js +0 -23
  124. package/dist/content/hook-manifest.d.ts +0 -81
  125. package/dist/content/hook-manifest.js +0 -156
  126. package/dist/content/hooks.d.ts +0 -11
  127. package/dist/content/hooks.js +0 -1972
  128. package/dist/content/idea.d.ts +0 -60
  129. package/dist/content/idea.js +0 -416
  130. package/dist/content/language-policy.d.ts +0 -2
  131. package/dist/content/language-policy.js +0 -13
  132. package/dist/content/learnings.d.ts +0 -6
  133. package/dist/content/learnings.js +0 -141
  134. package/dist/content/observe.d.ts +0 -19
  135. package/dist/content/observe.js +0 -86
  136. package/dist/content/opencode-plugin.d.ts +0 -1
  137. package/dist/content/opencode-plugin.js +0 -635
  138. package/dist/content/review-prompts.d.ts +0 -1
  139. package/dist/content/review-prompts.js +0 -104
  140. package/dist/content/runtime-shared-snippets.d.ts +0 -8
  141. package/dist/content/runtime-shared-snippets.js +0 -80
  142. package/dist/content/session-hooks.d.ts +0 -7
  143. package/dist/content/session-hooks.js +0 -107
  144. package/dist/content/skills-elicitation.d.ts +0 -1
  145. package/dist/content/skills-elicitation.js +0 -167
  146. package/dist/content/stage-command.d.ts +0 -2
  147. package/dist/content/stage-command.js +0 -17
  148. package/dist/content/stage-schema.d.ts +0 -117
  149. package/dist/content/stage-schema.js +0 -955
  150. package/dist/content/stages/_lint-metadata/index.d.ts +0 -2
  151. package/dist/content/stages/_lint-metadata/index.js +0 -97
  152. package/dist/content/stages/brainstorm.d.ts +0 -2
  153. package/dist/content/stages/brainstorm.js +0 -184
  154. package/dist/content/stages/design.d.ts +0 -2
  155. package/dist/content/stages/design.js +0 -288
  156. package/dist/content/stages/index.d.ts +0 -8
  157. package/dist/content/stages/index.js +0 -11
  158. package/dist/content/stages/plan.d.ts +0 -2
  159. package/dist/content/stages/plan.js +0 -191
  160. package/dist/content/stages/review.d.ts +0 -2
  161. package/dist/content/stages/review.js +0 -240
  162. package/dist/content/stages/schema-types.d.ts +0 -203
  163. package/dist/content/stages/schema-types.js +0 -1
  164. package/dist/content/stages/scope.d.ts +0 -2
  165. package/dist/content/stages/scope.js +0 -254
  166. package/dist/content/stages/ship.d.ts +0 -2
  167. package/dist/content/stages/ship.js +0 -159
  168. package/dist/content/stages/spec.d.ts +0 -2
  169. package/dist/content/stages/spec.js +0 -170
  170. package/dist/content/stages/tdd.d.ts +0 -4
  171. package/dist/content/stages/tdd.js +0 -273
  172. package/dist/content/state-contracts.d.ts +0 -1
  173. package/dist/content/state-contracts.js +0 -63
  174. package/dist/content/status-command.d.ts +0 -4
  175. package/dist/content/status-command.js +0 -109
  176. package/dist/content/subagent-context-skills.d.ts +0 -4
  177. package/dist/content/subagent-context-skills.js +0 -279
  178. package/dist/content/subagents.d.ts +0 -3
  179. package/dist/content/subagents.js +0 -997
  180. package/dist/content/templates.d.ts +0 -26
  181. package/dist/content/templates.js +0 -1692
  182. package/dist/content/track-render-context.d.ts +0 -18
  183. package/dist/content/track-render-context.js +0 -53
  184. package/dist/content/tree-command.d.ts +0 -1
  185. package/dist/content/tree-command.js +0 -64
  186. package/dist/content/utility-skills.d.ts +0 -30
  187. package/dist/content/utility-skills.js +0 -160
  188. package/dist/content/view-command.d.ts +0 -2
  189. package/dist/content/view-command.js +0 -92
  190. package/dist/delegation.d.ts +0 -649
  191. package/dist/delegation.js +0 -1539
  192. package/dist/early-loop.d.ts +0 -70
  193. package/dist/early-loop.js +0 -302
  194. package/dist/execution-topology.d.ts +0 -44
  195. package/dist/execution-topology.js +0 -95
  196. package/dist/gate-evidence.d.ts +0 -85
  197. package/dist/gate-evidence.js +0 -631
  198. package/dist/harness-adapters.d.ts +0 -151
  199. package/dist/harness-adapters.js +0 -756
  200. package/dist/harness-selection.d.ts +0 -31
  201. package/dist/harness-selection.js +0 -214
  202. package/dist/hook-schema.d.ts +0 -6
  203. package/dist/hook-schema.js +0 -114
  204. package/dist/hook-schemas/claude-hooks.v1.json +0 -10
  205. package/dist/hook-schemas/codex-hooks.v1.json +0 -10
  206. package/dist/hook-schemas/cursor-hooks.v1.json +0 -13
  207. package/dist/init-detect.d.ts +0 -2
  208. package/dist/init-detect.js +0 -50
  209. package/dist/internal/advance-stage/advance.d.ts +0 -89
  210. package/dist/internal/advance-stage/advance.js +0 -655
  211. package/dist/internal/advance-stage/cancel-run.d.ts +0 -8
  212. package/dist/internal/advance-stage/cancel-run.js +0 -19
  213. package/dist/internal/advance-stage/flow-state-coercion.d.ts +0 -3
  214. package/dist/internal/advance-stage/flow-state-coercion.js +0 -81
  215. package/dist/internal/advance-stage/helpers.d.ts +0 -14
  216. package/dist/internal/advance-stage/helpers.js +0 -145
  217. package/dist/internal/advance-stage/hook.d.ts +0 -8
  218. package/dist/internal/advance-stage/hook.js +0 -40
  219. package/dist/internal/advance-stage/parsers.d.ts +0 -72
  220. package/dist/internal/advance-stage/parsers.js +0 -357
  221. package/dist/internal/advance-stage/proactive-delegation-trace.d.ts +0 -24
  222. package/dist/internal/advance-stage/proactive-delegation-trace.js +0 -56
  223. package/dist/internal/advance-stage/review-loop.d.ts +0 -16
  224. package/dist/internal/advance-stage/review-loop.js +0 -199
  225. package/dist/internal/advance-stage/rewind.d.ts +0 -14
  226. package/dist/internal/advance-stage/rewind.js +0 -108
  227. package/dist/internal/advance-stage/start-flow.d.ts +0 -13
  228. package/dist/internal/advance-stage/start-flow.js +0 -241
  229. package/dist/internal/advance-stage/verify.d.ts +0 -21
  230. package/dist/internal/advance-stage/verify.js +0 -185
  231. package/dist/internal/advance-stage.d.ts +0 -7
  232. package/dist/internal/advance-stage.js +0 -138
  233. package/dist/internal/cohesion-contract-stub.d.ts +0 -24
  234. package/dist/internal/cohesion-contract-stub.js +0 -148
  235. package/dist/internal/compound-readiness.d.ts +0 -23
  236. package/dist/internal/compound-readiness.js +0 -102
  237. package/dist/internal/detect-public-api-changes.d.ts +0 -5
  238. package/dist/internal/detect-public-api-changes.js +0 -45
  239. package/dist/internal/detect-supply-chain-changes.d.ts +0 -6
  240. package/dist/internal/detect-supply-chain-changes.js +0 -138
  241. package/dist/internal/early-loop-status.d.ts +0 -7
  242. package/dist/internal/early-loop-status.js +0 -93
  243. package/dist/internal/envelope-validate.d.ts +0 -7
  244. package/dist/internal/envelope-validate.js +0 -66
  245. package/dist/internal/flow-state-repair.d.ts +0 -20
  246. package/dist/internal/flow-state-repair.js +0 -104
  247. package/dist/internal/plan-split-waves.d.ts +0 -190
  248. package/dist/internal/plan-split-waves.js +0 -764
  249. package/dist/internal/runtime-integrity.d.ts +0 -7
  250. package/dist/internal/runtime-integrity.js +0 -268
  251. package/dist/internal/slice-commit.d.ts +0 -7
  252. package/dist/internal/slice-commit.js +0 -619
  253. package/dist/internal/tdd-loop-status.d.ts +0 -14
  254. package/dist/internal/tdd-loop-status.js +0 -68
  255. package/dist/internal/tdd-red-evidence.d.ts +0 -7
  256. package/dist/internal/tdd-red-evidence.js +0 -153
  257. package/dist/internal/waiver-grant.d.ts +0 -62
  258. package/dist/internal/waiver-grant.js +0 -294
  259. package/dist/internal/wave-status.d.ts +0 -74
  260. package/dist/internal/wave-status.js +0 -506
  261. package/dist/managed-resources.d.ts +0 -53
  262. package/dist/managed-resources.js +0 -313
  263. package/dist/policy.d.ts +0 -10
  264. package/dist/policy.js +0 -167
  265. package/dist/retro-gate.d.ts +0 -9
  266. package/dist/retro-gate.js +0 -47
  267. package/dist/run-archive.d.ts +0 -61
  268. package/dist/run-archive.js +0 -391
  269. package/dist/runs.d.ts +0 -2
  270. package/dist/runs.js +0 -2
  271. package/dist/stack-detection.d.ts +0 -116
  272. package/dist/stack-detection.js +0 -489
  273. package/dist/streaming/event-stream.d.ts +0 -31
  274. package/dist/streaming/event-stream.js +0 -114
  275. package/dist/tdd-cycle.d.ts +0 -107
  276. package/dist/tdd-cycle.js +0 -289
  277. package/dist/tdd-verification-evidence.d.ts +0 -17
  278. package/dist/tdd-verification-evidence.js +0 -122
  279. package/dist/track-heuristics.d.ts +0 -27
  280. package/dist/track-heuristics.js +0 -154
  281. package/dist/util/slice-id.d.ts +0 -58
  282. package/dist/util/slice-id.js +0 -89
  283. package/dist/worktree-manager.d.ts +0 -20
  284. package/dist/worktree-manager.js +0 -108
package/dist/install.js CHANGED
@@ -1,1393 +1,284 @@
1
- import { execFile } from "node:child_process";
2
1
  import fs from "node:fs/promises";
3
2
  import path from "node:path";
4
- import { promisify } from "node:util";
5
- import { CCLAW_VERSION, FLOW_VERSION, REQUIRED_DIRS, RUNTIME_ROOT } from "./constants.js";
6
- import { writeConfig, createDefaultConfig, readConfig, configPath, detectAdvancedKeys } from "./config.js";
7
- import { learnSkillMarkdown } from "./content/learnings.js";
8
- import { stageCommandShimMarkdown } from "./content/stage-command.js";
9
- import { ideaCommandContract, ideaCommandSkillMarkdown } from "./content/idea.js";
10
- import { startCommandContract, startCommandSkillMarkdown } from "./content/start-command.js";
11
- import { viewCommandContract, viewCommandSkillMarkdown } from "./content/view-command.js";
12
- import { cancelCommandContract, cancelCommandSkillMarkdown } from "./content/cancel-command.js";
13
- import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
14
- import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
15
- import { ironLawsSkillMarkdown } from "./content/iron-laws.js";
16
- import { stageCompleteScript, startFlowScript, cancelRunScript, runHookCmdScript, delegationRecordScript, sliceCommitScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
17
- import { nodeHookRuntimeScript } from "./content/node-hooks.js";
18
- import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
19
- import { ARTIFACT_TEMPLATES, CURSOR_GUIDELINES_RULE_MDC, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
20
- import { STATE_CONTRACTS } from "./content/state-contracts.js";
21
- import { REVIEW_PROMPTS } from "./content/review-prompts.js";
22
- import { stageSkillFolder, stageSkillMarkdown, executingWavesSkillMarkdown } from "./content/skills.js";
23
- import { adaptiveElicitationSkillMarkdown } from "./content/skills-elicitation.js";
24
- import { LANGUAGE_RULE_PACK_DIR, LEGACY_LANGUAGE_RULE_PACK_FOLDERS } from "./content/utility-skills.js";
25
- import { RESEARCH_PLAYBOOKS } from "./content/research-playbooks.js";
26
- import { SUBAGENT_CONTEXT_SKILLS } from "./content/subagent-context-skills.js";
27
- import { CCLAW_AGENTS } from "./content/core-agents.js";
28
- import { createInitialFlowState } from "./flow-state.js";
29
- import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
30
- import { ManagedResourceSession, readManagedResourceManifest, setActiveManagedResourceSession } from "./managed-resources.js";
31
- import { ensureGitignore, removeGitignorePatterns } from "./gitignore.js";
32
- import { HARNESS_ADAPTERS, harnessShimFileNames, harnessShimSkillNames, syncHarnessShims, removeCclawFromAgentsMd } from "./harness-adapters.js";
33
- import { validateHookDocument } from "./hook-schema.js";
34
- import { detectHarnesses } from "./init-detect.js";
35
- import { classifyCodexHooksFlag, codexConfigPath, readCodexConfig } from "./codex-feature-flag.js";
36
- import { CorruptFlowStateError, ensureRunSystem } from "./runs.js";
37
- import { formatNextParallelWaveSyncHint, mergeParallelWaveDefinitions, parseParallelExecutionPlanWaves, parseWavePlanDirectory } from "./internal/plan-split-waves.js";
38
- import { FLOW_STAGES } from "./types.js";
39
- const OPENCODE_PLUGIN_REL_PATH = ".opencode/plugins/cclaw-plugin.mjs";
40
- const CURSOR_RULE_REL_PATH = ".cursor/rules/cclaw-workflow.mdc";
41
- const CURSOR_GUIDELINES_REL_PATH = ".cursor/rules/cclaw-guidelines.mdc";
42
- const INIT_SENTINEL_FILE = ".init-in-progress";
43
- const execFileAsync = promisify(execFile);
44
- function runtimePath(projectRoot, ...segments) {
45
- return path.join(projectRoot, RUNTIME_ROOT, ...segments);
46
- }
47
- async function writeInitSentinel(projectRoot, operation) {
48
- const sentinelPath = runtimePath(projectRoot, "state", INIT_SENTINEL_FILE);
49
- await ensureDir(path.dirname(sentinelPath));
50
- await writeFileSafe(sentinelPath, `${JSON.stringify({ operation, startedAt: new Date().toISOString() }, null, 2)}\n`);
51
- return sentinelPath;
52
- }
53
- async function warnStaleInitSentinel(projectRoot, operation) {
54
- const sentinelPath = runtimePath(projectRoot, "state", INIT_SENTINEL_FILE);
55
- if (!(await exists(sentinelPath)))
56
- return;
57
- let startedAt = "unknown time";
58
- try {
59
- const raw = await fs.readFile(sentinelPath, "utf8");
60
- const parsed = JSON.parse(raw);
61
- if (parsed && typeof parsed.startedAt === "string" && parsed.startedAt.trim().length > 0) {
62
- startedAt = parsed.startedAt;
63
- }
64
- }
65
- catch {
66
- // best-effort parse of stale sentinel metadata
67
- }
68
- process.stderr.write(`[${operation}] Detected stale .init-in-progress sentinel from ${startedAt}; previous run may have crashed. Continuing.\n`);
69
- }
70
- async function removeBestEffort(targetPath, recursive = false) {
71
- try {
72
- await fs.rm(targetPath, { recursive, force: true });
73
- }
74
- catch {
75
- // best-effort cleanup
76
- }
77
- }
78
- const DEPRECATED_UTILITY_SKILL_FOLDERS = [
79
- "project-learnings",
80
- "auto-orchestration",
81
- "autoplan",
82
- "red-first-testing",
83
- "incremental-implementation",
84
- "subagent-driven-development",
85
- "dispatching-parallel-agents",
86
- "session-guidelines",
87
- "security-review",
88
- "documentation",
89
- "browser-qa-testing",
90
- "feature-workspaces",
91
- "security",
92
- "debugging",
93
- "performance",
94
- "ci-cd",
95
- "docs",
96
- "executing-plans",
97
- "verification-before-completion",
98
- "finishing-a-development-branch",
99
- "context-engineering",
100
- "source-driven-development",
101
- "frontend-accessibility",
102
- "landscape-check",
103
- "knowledge-curation",
104
- "retrospective",
105
- "document-review",
106
- "flow-status",
107
- "flow-tree",
108
- "flow-diff"
109
- ];
110
- const DEPRECATED_STAGE_SKILL_FOLDERS = [
111
- "brainstorming",
112
- "scope-shaping",
113
- "engineering-design-lock",
114
- "specification-authoring",
115
- "planning-and-task-breakdown",
116
- "test-driven-development",
117
- "two-layer-review",
118
- "shipping-and-handoff"
119
- ];
120
- const DEPRECATED_AGENT_FILES = [
121
- "securityer.md",
122
- "spec-reviewer.md",
123
- "code-reviewer.md",
124
- "repo-research-analyst.md",
125
- "learnings-researcher.md",
126
- "framework-docs-researcher.md",
127
- "best-practices-researcher.md",
128
- "git-history-analyzer.md",
129
- "test-author.md",
130
- "slice-implementer.md",
131
- "slice-documenter.md"
132
- ];
133
- const DEPRECATED_COMMAND_FILES = [
134
- "learn.md",
135
- "finish.md",
136
- "status.md",
137
- "tree.md",
138
- "diff.md",
139
- "feature.md",
140
- "ops.md",
141
- "tdd-log.md",
142
- "retro.md",
143
- "compound.md",
144
- "archive.md",
145
- "rewind.md"
146
- ];
147
- const DEPRECATED_SKILL_FILES = [
148
- ["flow-finish", "SKILL.md"],
149
- ["flow-ops", "SKILL.md"],
150
- ["flow-retro", "SKILL.md"],
151
- ["flow-compound", "SKILL.md"],
152
- ["flow-archive", "SKILL.md"],
153
- ["flow-rewind", "SKILL.md"],
154
- ["using-git-worktrees", "SKILL.md"]
155
- ];
156
- // Skill folders whose entire directory should be removed on sync so the
157
- // abandoned tree doesn't linger in user projects.
158
- const DEPRECATED_SKILL_FOLDERS_FULL = [
159
- "tdd-cycle-log"
160
- ];
161
- const DEPRECATED_STATE_FILES = [
162
- "checkpoint.json",
163
- "flow-state.snapshot.json",
164
- "stage-activity.jsonl",
165
- "knowledge-digest.md",
166
- "suggestion-memory.json",
167
- "harness-gaps.json",
168
- "context-mode.json",
169
- "session-digest.md",
170
- "context-warnings.jsonl",
171
- "tdd-cycle-log.jsonl"
172
- ];
173
- // Files under `<runtime>/artifacts/` that older releases generated and
174
- // the current release removes. `cclaw-cli sync` deletes each so existing
175
- // installs lose the obsolete sidecar without requiring manual cleanup.
176
- const DEPRECATED_ARTIFACT_FILES = [
177
- "06-tdd-slices.jsonl"
178
- ];
179
- const DEPRECATED_HOOK_FILES = [
180
- "observe.sh",
181
- "summarize-observations.sh",
182
- "summarize-observations.mjs",
183
- "_lib.sh",
184
- "session-start.sh",
185
- "stop-checkpoint.sh",
186
- "stage-complete.sh",
187
- "pre-compact.sh",
188
- "prompt-guard.sh",
189
- "workflow-guard.sh",
190
- "context-monitor.sh"
191
- ];
192
- const DEPRECATED_RUNTIME_ROOT_FILES = ["learnings.jsonl", "observations.jsonl"];
193
- const DEPRECATED_RUNTIME_DIRS = ["evals", "references", "contexts"];
194
- async function resolveGitHooksDir(projectRoot) {
195
- try {
196
- const { stdout } = await execFileAsync("git", ["rev-parse", "--git-path", "hooks"], {
197
- cwd: projectRoot
198
- });
199
- const rel = stdout.trim();
200
- if (rel.length === 0) {
201
- return null;
202
- }
203
- return path.resolve(projectRoot, rel);
204
- }
205
- catch {
206
- return null;
207
- }
208
- }
209
- // Older versions installed Node-based git pre-commit/pre-push relays under
210
- // `.git/hooks/*` and a runtime tree at `.cclaw/hooks/git/`. cclaw no longer
211
- // manages git hooks; the cleanup below stays so existing installs shed the
212
- // leftover files on next sync/uninstall.
213
- const LEGACY_GIT_HOOK_MANAGED_MARKER = "cclaw-managed-git-hook";
214
- const LEGACY_GIT_HOOK_RUNTIME_REL_DIR = `${RUNTIME_ROOT}/hooks/git`;
215
- async function cleanupLegacyManagedGitHookRelays(projectRoot) {
216
- const hooksDir = await resolveGitHooksDir(projectRoot);
217
- if (hooksDir) {
218
- for (const hookName of ["pre-commit", "pre-push"]) {
219
- const hookPath = path.join(hooksDir, hookName);
220
- if (!(await exists(hookPath)))
221
- continue;
222
- let content = "";
223
- try {
224
- content = await fs.readFile(hookPath, "utf8");
225
- }
226
- catch {
227
- content = "";
228
- }
229
- if (!content.includes(LEGACY_GIT_HOOK_MANAGED_MARKER))
230
- continue;
231
- await fs.rm(hookPath, { force: true });
232
- }
233
- }
234
- try {
235
- await fs.rm(path.join(projectRoot, LEGACY_GIT_HOOK_RUNTIME_REL_DIR), { recursive: true, force: true });
236
- }
237
- catch {
238
- // best-effort cleanup
239
- }
240
- }
241
- async function ensureStructure(projectRoot) {
242
- for (const dir of REQUIRED_DIRS) {
3
+ import { CANCELLED_DIR_REL_PATH, CCLAW_VERSION, FLOWS_ROOT, HOOKS_REL_PATH, LIB_ROOT, RUNTIME_ROOT, SHIPPED_DIR_REL_PATH, STATE_REL_PATH } from "./constants.js";
4
+ import { CORE_AGENTS, renderAgentMarkdown } from "./content/core-agents.js";
5
+ import { ARTIFACT_TEMPLATES, planTemplateForSlug, templateBody } from "./content/artifact-templates.js";
6
+ import { AUTO_TRIGGER_SKILLS } from "./content/skills.js";
7
+ import { EXAMPLES, EXAMPLES_INDEX } from "./content/examples.js";
8
+ import { REFERENCE_PATTERNS, REFERENCE_PATTERNS_INDEX } from "./content/reference-patterns.js";
9
+ import { STAGE_PLAYBOOKS, STAGE_PLAYBOOKS_INDEX } from "./content/stage-playbooks.js";
10
+ import { RESEARCH_PLAYBOOKS, RESEARCH_PLAYBOOKS_INDEX } from "./content/research-playbooks.js";
11
+ import { RECOVERY_PLAYBOOKS, RECOVERY_INDEX } from "./content/recovery.js";
12
+ import { ANTIPATTERNS } from "./content/antipatterns.js";
13
+ import { DECISION_PROTOCOL } from "./content/decision-protocol.js";
14
+ import { META_SKILL } from "./content/meta-skill.js";
15
+ import { COMMIT_HELPER_HOOK_SPEC, NODE_HOOKS, SESSION_START_HOOK_SPEC, STOP_HANDOFF_HOOK_SPEC } from "./content/node-hooks.js";
16
+ import { renderStartCommand } from "./content/start-command.js";
17
+ import { renderCancelCommand } from "./content/cancel-command.js";
18
+ import { renderIdeaCommand } from "./content/idea-command.js";
19
+ import { ensureDir, exists, removePath, writeFileSafe } from "./fs-utils.js";
20
+ import { ensureRunSystem } from "./run-persistence.js";
21
+ import { createDefaultConfig, readConfig, renderConfig } from "./config.js";
22
+ import { detectHarnesses, NO_HARNESS_DETECTED_MESSAGE } from "./harness-detect.js";
23
+ import { ensureGitignorePatterns, removeGitignorePatterns } from "./gitignore.js";
24
+ import { isInteractive, runPicker } from "./harness-prompt.js";
25
+ import { HARNESS_IDS } from "./types.js";
26
+ import { ironLawsMarkdown } from "./content/iron-laws.js";
27
+ const HARNESS_LAYOUTS = {
28
+ claude: {
29
+ id: "claude",
30
+ commandsDir: ".claude/commands",
31
+ agentsDir: ".claude/agents",
32
+ skillsDir: ".claude/skills/cclaw",
33
+ hooksConfig: { dir: ".claude/hooks", fileName: "hooks.json" }
34
+ },
35
+ cursor: {
36
+ id: "cursor",
37
+ commandsDir: ".cursor/commands",
38
+ agentsDir: ".cursor/agents",
39
+ skillsDir: ".cursor/skills/cclaw",
40
+ hooksConfig: { dir: ".cursor", fileName: "hooks.json" }
41
+ },
42
+ opencode: {
43
+ id: "opencode",
44
+ commandsDir: ".opencode/commands",
45
+ agentsDir: ".opencode/agents",
46
+ skillsDir: ".opencode/skills/cclaw",
47
+ hooksConfig: { dir: ".opencode/plugins", fileName: "cclaw-plugin.mjs" }
48
+ },
49
+ codex: {
50
+ id: "codex",
51
+ commandsDir: ".codex/commands",
52
+ agentsDir: ".codex/agents",
53
+ skillsDir: ".codex/skills/cclaw",
54
+ hooksConfig: { dir: ".codex", fileName: "hooks.json" }
55
+ }
56
+ };
57
+ export async function ensureRuntimeRoot(projectRoot) {
58
+ const root = path.join(projectRoot, RUNTIME_ROOT);
59
+ for (const dir of [
60
+ STATE_REL_PATH,
61
+ HOOKS_REL_PATH,
62
+ FLOWS_ROOT,
63
+ SHIPPED_DIR_REL_PATH,
64
+ CANCELLED_DIR_REL_PATH,
65
+ LIB_ROOT,
66
+ path.join(LIB_ROOT, "agents"),
67
+ path.join(LIB_ROOT, "skills"),
68
+ path.join(LIB_ROOT, "templates"),
69
+ path.join(LIB_ROOT, "runbooks"),
70
+ path.join(LIB_ROOT, "patterns"),
71
+ path.join(LIB_ROOT, "research"),
72
+ path.join(LIB_ROOT, "recovery"),
73
+ path.join(LIB_ROOT, "examples")
74
+ ]) {
243
75
  await ensureDir(path.join(projectRoot, dir));
244
76
  }
77
+ await writeFileSafe(path.join(root, ".gitkeep"), "cclaw runtime root. Generated by cclaw-cli; safe to keep in version control.\n");
245
78
  }
246
- async function writeArtifactTemplates(projectRoot) {
247
- await Promise.all(Object.entries(ARTIFACT_TEMPLATES).map(async ([fileName, content]) => {
248
- await writeFileSafe(runtimePath(projectRoot, "templates", fileName), content);
249
- }));
250
- await Promise.all(Object.entries(STATE_CONTRACTS).map(async ([fileName, content]) => {
251
- await writeFileSafe(runtimePath(projectRoot, "templates", "state-contracts", fileName), content);
252
- }));
253
- }
254
- async function writeWavePlansScaffold(projectRoot) {
255
- await writeFileSafe(runtimePath(projectRoot, "wave-plans", ".gitkeep"), "");
79
+ async function writeHookFile(projectRoot, hook) {
80
+ const hookPath = path.join(projectRoot, HOOKS_REL_PATH, hook.fileName);
81
+ await writeFileSafe(hookPath, hook.body);
82
+ await fs.chmod(hookPath, 0o755);
256
83
  }
257
- async function writeSkills(projectRoot, config) {
258
- void config;
259
- const manifest = await readManagedResourceManifest(projectRoot).catch(() => null);
260
- const packageVersion = manifest?.packageVersion ?? null;
261
- const skillTrack = "standard";
262
- for (const stage of FLOW_STAGES) {
263
- const folder = stageSkillFolder(stage);
264
- await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), stageSkillMarkdown(stage, skillTrack, packageVersion));
265
- }
266
- // Utility skills (not flow stages)
267
- await writeFileSafe(runtimePath(projectRoot, "skills", "learnings", "SKILL.md"), learnSkillMarkdown());
268
- await writeFileSafe(runtimePath(projectRoot, "skills", "flow-idea", "SKILL.md"), ideaCommandSkillMarkdown());
269
- await writeFileSafe(runtimePath(projectRoot, "skills", "flow-start", "SKILL.md"), startCommandSkillMarkdown());
270
- await writeFileSafe(runtimePath(projectRoot, "skills", "flow-view", "SKILL.md"), viewCommandSkillMarkdown());
271
- await writeFileSafe(runtimePath(projectRoot, "skills", "flow-cancel", "SKILL.md"), cancelCommandSkillMarkdown());
272
- await writeFileSafe(runtimePath(projectRoot, "skills", "subagent-dev", "SKILL.md"), subagentDrivenDevSkill());
273
- await writeFileSafe(runtimePath(projectRoot, "skills", "parallel-dispatch", "SKILL.md"), parallelAgentsSkill());
274
- await writeFileSafe(runtimePath(projectRoot, "skills", "session", "SKILL.md"), sessionHooksSkillMarkdown());
275
- await writeFileSafe(runtimePath(projectRoot, "skills", "iron-laws", "SKILL.md"), ironLawsSkillMarkdown());
276
- await writeFileSafe(runtimePath(projectRoot, "skills", "executing-waves", "SKILL.md"), executingWavesSkillMarkdown());
277
- await writeFileSafe(runtimePath(projectRoot, "skills", "adaptive-elicitation", "SKILL.md"), adaptiveElicitationSkillMarkdown());
278
- await writeFileSafe(runtimePath(projectRoot, "skills", META_SKILL_NAME, "SKILL.md"), usingCclawSkillMarkdown());
279
- // In-thread research procedures (no YAML frontmatter, not delegated personas).
280
- for (const [fileName, markdown] of Object.entries(RESEARCH_PLAYBOOKS)) {
281
- await writeFileSafe(runtimePath(projectRoot, "skills", "research", fileName), markdown);
282
- }
283
- for (const [fileName, markdown] of Object.entries(REVIEW_PROMPTS)) {
284
- await writeFileSafe(runtimePath(projectRoot, "skills", "review-prompts", fileName), markdown);
285
- }
286
- for (const [folderName, markdown] of Object.entries(SUBAGENT_CONTEXT_SKILLS)) {
287
- await writeFileSafe(runtimePath(projectRoot, "skills", folderName, "SKILL.md"), markdown);
288
- }
289
- await fs.rm(runtimePath(projectRoot, ...LANGUAGE_RULE_PACK_DIR), { recursive: true, force: true });
290
- for (const legacyFolder of LEGACY_LANGUAGE_RULE_PACK_FOLDERS) {
291
- const legacyPath = runtimePath(projectRoot, "skills", legacyFolder);
292
- if (await exists(legacyPath)) {
293
- await fs.rm(legacyPath, { recursive: true, force: true });
294
- }
84
+ async function writeAgentFiles(projectRoot) {
85
+ for (const agent of CORE_AGENTS) {
86
+ const agentPath = path.join(projectRoot, LIB_ROOT, "agents", `${agent.id}.md`);
87
+ await writeFileSafe(agentPath, renderAgentMarkdown(agent));
295
88
  }
296
89
  }
297
- async function writeEntryCommands(projectRoot) {
298
- await writeFileSafe(runtimePath(projectRoot, "commands", "start.md"), startCommandContract());
299
- await writeFileSafe(runtimePath(projectRoot, "commands", "idea.md"), ideaCommandContract());
300
- await writeFileSafe(runtimePath(projectRoot, "commands", "view.md"), viewCommandContract());
301
- await writeFileSafe(runtimePath(projectRoot, "commands", "cancel.md"), cancelCommandContract());
302
- for (const stage of FLOW_STAGES) {
303
- await writeFileSafe(runtimePath(projectRoot, "commands", `${stage}.md`), stageCommandShimMarkdown(stage));
90
+ async function writeRuntimeSkills(projectRoot) {
91
+ for (const skill of AUTO_TRIGGER_SKILLS) {
92
+ const target = path.join(projectRoot, LIB_ROOT, "skills", skill.fileName);
93
+ await writeFileSafe(target, skill.body);
304
94
  }
305
95
  }
306
- function toObject(value) {
307
- if (!value || typeof value !== "object" || Array.isArray(value)) {
308
- return null;
96
+ async function writeTemplates(projectRoot) {
97
+ for (const template of ARTIFACT_TEMPLATES) {
98
+ const target = path.join(projectRoot, LIB_ROOT, "templates", template.fileName);
99
+ await writeFileSafe(target, template.body);
309
100
  }
310
- return value;
101
+ await writeFileSafe(path.join(projectRoot, LIB_ROOT, "templates", "iron-laws.md"), ironLawsMarkdown());
311
102
  }
312
- /**
313
- * Removes // and /* *\/ comments only outside JSON strings (double-quoted).
314
- * Used for recovering user-edited hook JSON without corrupting string contents.
315
- */
316
- function stripJsonCommentsOutsideStrings(input) {
317
- let out = "";
318
- let i = 0;
319
- let inString = false;
320
- let escape = false;
321
- while (i < input.length) {
322
- const c = input[i];
323
- if (inString) {
324
- out += c;
325
- if (escape) {
326
- escape = false;
327
- }
328
- else if (c === "\\") {
329
- escape = true;
330
- }
331
- else if (c === '"') {
332
- inString = false;
333
- }
334
- i += 1;
335
- continue;
336
- }
337
- if (c === '"') {
338
- inString = true;
339
- out += c;
340
- i += 1;
341
- continue;
342
- }
343
- const next = input[i + 1];
344
- if (c === "/" && next === "/") {
345
- while (i < input.length && input[i] !== "\n" && input[i] !== "\r")
346
- i += 1;
347
- continue;
348
- }
349
- if (c === "/" && next === "*") {
350
- i += 2;
351
- while (i < input.length - 1 && !(input[i] === "*" && input[i + 1] === "/"))
352
- i += 1;
353
- i = Math.min(i + 2, input.length);
354
- continue;
355
- }
356
- out += c;
357
- i += 1;
358
- }
359
- return out;
360
- }
361
- function normalizeJsonLike(raw) {
362
- return stripJsonCommentsOutsideStrings(raw).replace(/,\s*([}\]])/gu, "$1");
103
+ async function writeIdeasSeed(projectRoot) {
104
+ const target = path.join(projectRoot, RUNTIME_ROOT, "ideas.md");
105
+ if (await exists(target))
106
+ return;
107
+ await writeFileSafe(target, templateBody("ideas"));
363
108
  }
364
- function tryParseHookDocument(raw) {
365
- try {
366
- return { parsed: JSON.parse(raw), recovered: false };
367
- }
368
- catch {
369
- // continue with relaxed parse
370
- }
371
- try {
372
- return { parsed: JSON.parse(normalizeJsonLike(raw)), recovered: true };
109
+ async function writeStageRunbooks(projectRoot) {
110
+ const dir = path.join(projectRoot, LIB_ROOT, "runbooks");
111
+ for (const playbook of STAGE_PLAYBOOKS) {
112
+ await writeFileSafe(path.join(dir, playbook.fileName), playbook.body);
373
113
  }
374
- catch {
375
- return null;
376
- }
377
- }
378
- function opencodeConfigCandidates(projectRoot) {
379
- return [
380
- path.join(projectRoot, "opencode.json"),
381
- path.join(projectRoot, "opencode.jsonc"),
382
- path.join(projectRoot, ".opencode", "opencode.json"),
383
- path.join(projectRoot, ".opencode", "opencode.jsonc")
384
- ];
114
+ await writeFileSafe(path.join(dir, "index.md"), STAGE_PLAYBOOKS_INDEX);
385
115
  }
386
- function normalizeOpenCodePluginEntry(entry) {
387
- if (typeof entry === "string" && entry.trim().length > 0) {
388
- return entry.trim();
116
+ async function writeReferencePatterns(projectRoot) {
117
+ const dir = path.join(projectRoot, LIB_ROOT, "patterns");
118
+ for (const pattern of REFERENCE_PATTERNS) {
119
+ await writeFileSafe(path.join(dir, pattern.fileName), pattern.body);
389
120
  }
390
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
391
- return null;
392
- }
393
- const obj = entry;
394
- for (const key of ["path", "src", "plugin"]) {
395
- const value = obj[key];
396
- if (typeof value === "string" && value.trim().length > 0) {
397
- return value.trim();
398
- }
399
- }
400
- return null;
121
+ await writeFileSafe(path.join(dir, "index.md"), REFERENCE_PATTERNS_INDEX);
401
122
  }
402
- function mergeOpenCodePluginConfig(existingDoc, pluginRelPath) {
403
- const root = toObject(existingDoc) ?? {};
404
- const pluginsRaw = Array.isArray(root.plugin) ? [...root.plugin] : [];
405
- const normalized = new Set(pluginsRaw.map((entry) => normalizeOpenCodePluginEntry(entry)).filter(Boolean));
406
- if (!normalized.has(pluginRelPath)) {
407
- pluginsRaw.push(pluginRelPath);
123
+ async function writeResearchPlaybooks(projectRoot) {
124
+ const dir = path.join(projectRoot, LIB_ROOT, "research");
125
+ for (const playbook of RESEARCH_PLAYBOOKS) {
126
+ await writeFileSafe(path.join(dir, playbook.fileName), playbook.body);
408
127
  }
409
- const permission = toObject(root.permission) ?? {};
410
- const permissionChanged = permission.question !== "allow";
411
- const changed = !normalized.has(pluginRelPath) ||
412
- !Array.isArray(root.plugin) ||
413
- permissionChanged ||
414
- !toObject(root.permission);
415
- return {
416
- merged: {
417
- ...root,
418
- plugin: pluginsRaw,
419
- permission: {
420
- ...permission,
421
- question: "allow"
422
- }
423
- },
424
- changed
425
- };
128
+ await writeFileSafe(path.join(dir, "index.md"), RESEARCH_PLAYBOOKS_INDEX);
426
129
  }
427
- async function resolveOpenCodeConfigPath(projectRoot) {
428
- for (const candidate of opencodeConfigCandidates(projectRoot)) {
429
- if (await exists(candidate)) {
430
- return candidate;
431
- }
130
+ async function writeRecoveryPlaybooks(projectRoot) {
131
+ const dir = path.join(projectRoot, LIB_ROOT, "recovery");
132
+ for (const playbook of RECOVERY_PLAYBOOKS) {
133
+ await writeFileSafe(path.join(dir, playbook.fileName), playbook.body);
432
134
  }
433
- return path.join(projectRoot, "opencode.json");
135
+ await writeFileSafe(path.join(dir, "index.md"), RECOVERY_INDEX);
434
136
  }
435
- async function writeMergedOpenCodePluginConfig(projectRoot, pluginRelPath) {
436
- const configPath = await resolveOpenCodeConfigPath(projectRoot);
437
- await ensureDir(path.dirname(configPath));
438
- let existingDoc = {};
439
- if (await exists(configPath)) {
440
- try {
441
- const raw = await fs.readFile(configPath, "utf8");
442
- const parsed = tryParseHookDocument(raw);
443
- existingDoc = parsed?.parsed ?? {};
444
- }
445
- catch {
446
- existingDoc = {};
447
- }
448
- }
449
- const { merged, changed } = mergeOpenCodePluginConfig(existingDoc, pluginRelPath);
450
- if (changed || !(await exists(configPath))) {
451
- await writeFileSafe(configPath, `${JSON.stringify(merged, null, 2)}\n`);
137
+ async function writeExamples(projectRoot) {
138
+ const dir = path.join(projectRoot, LIB_ROOT, "examples");
139
+ for (const example of EXAMPLES) {
140
+ await writeFileSafe(path.join(dir, example.fileName), example.body);
452
141
  }
142
+ await writeFileSafe(path.join(dir, "index.md"), EXAMPLES_INDEX);
453
143
  }
454
- async function removeManagedOpenCodePluginConfig(projectRoot, pluginRelPath) {
455
- for (const configPath of opencodeConfigCandidates(projectRoot)) {
456
- if (!(await exists(configPath)))
457
- continue;
458
- let parsed = null;
459
- try {
460
- const raw = await fs.readFile(configPath, "utf8");
461
- parsed = tryParseHookDocument(raw)?.parsed ?? null;
462
- }
463
- catch {
464
- parsed = null;
465
- }
466
- const root = toObject(parsed);
467
- if (!root || !Array.isArray(root.plugin))
468
- continue;
469
- const filtered = root.plugin.filter((entry) => normalizeOpenCodePluginEntry(entry) !== pluginRelPath);
470
- if (filtered.length === root.plugin.length) {
471
- continue;
472
- }
473
- root.plugin = filtered;
474
- const remainingKeys = Object.keys(root).filter((k) => k !== "plugin" || filtered.length > 0);
475
- if (remainingKeys.length === 0 || (remainingKeys.length === 1 && remainingKeys[0] === "plugin" && filtered.length === 0)) {
476
- await fs.rm(configPath, { force: true });
477
- }
478
- else {
479
- if (filtered.length === 0) {
480
- delete root.plugin;
481
- }
482
- await writeFileSafe(configPath, `${JSON.stringify(root, null, 2)}\n`);
483
- }
484
- }
485
- }
486
- function backupFileNameForHook(projectRoot, hookFilePath) {
487
- const rel = path.relative(projectRoot, hookFilePath).replace(/[\\/]/gu, "__");
488
- const ts = new Date().toISOString().replace(/[:.]/gu, "-");
489
- return `${rel}.${ts}.bak`;
490
- }
491
- function harnessForHookFile(projectRoot, hookFilePath) {
492
- const rel = path.relative(projectRoot, hookFilePath).replace(/\\/gu, "/");
493
- if (rel === ".claude/hooks/hooks.json")
494
- return "claude";
495
- if (rel === ".cursor/hooks.json")
496
- return "cursor";
497
- if (rel === ".codex/hooks.json")
498
- return "codex";
499
- return null;
500
- }
501
- async function pruneOldHookBackups(backupsDir, maxBackups = 20) {
502
- let entries = [];
503
- try {
504
- entries = await fs.readdir(backupsDir);
505
- }
506
- catch {
507
- entries = [];
508
- }
509
- if (entries.length <= maxBackups)
510
- return;
511
- const withStats = await Promise.all(entries.map(async (entry) => {
512
- const fullPath = path.join(backupsDir, entry);
513
- const stat = await fs.stat(fullPath);
514
- return { fullPath, mtimeMs: stat.mtimeMs };
515
- }));
516
- withStats.sort((a, b) => b.mtimeMs - a.mtimeMs);
517
- const stale = withStats.slice(maxBackups);
518
- await Promise.all(stale.map(async (item) => {
519
- await fs.rm(item.fullPath, { force: true });
520
- }));
144
+ async function writeAntipatterns(projectRoot) {
145
+ await writeFileSafe(path.join(projectRoot, LIB_ROOT, "antipatterns.md"), ANTIPATTERNS);
521
146
  }
522
- async function backupHookFile(projectRoot, hookFilePath, rawContent) {
523
- const backupsDir = runtimePath(projectRoot, "backups", "hooks");
524
- await ensureDir(backupsDir);
525
- const fileName = backupFileNameForHook(projectRoot, hookFilePath);
526
- const backupPath = path.join(backupsDir, fileName);
527
- await writeFileSafe(backupPath, rawContent);
528
- await pruneOldHookBackups(backupsDir);
529
- return backupPath;
147
+ async function writeDecisionProtocol(projectRoot) {
148
+ await writeFileSafe(path.join(projectRoot, LIB_ROOT, "decision-protocol.md"), DECISION_PROTOCOL);
530
149
  }
531
- function normalizeHookCommandForDedupe(command) {
532
- return command.trim().replace(/\s+/gu, " ").replace(/\\/gu, "/");
150
+ async function writeMetaSkill(projectRoot) {
151
+ await writeFileSafe(path.join(projectRoot, LIB_ROOT, "skills", "cclaw-meta.md"), META_SKILL);
533
152
  }
534
- function dedupeHookEntryByCommand(entry, seenCommands) {
535
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
536
- return entry;
537
- }
538
- const obj = entry;
539
- let changed = false;
540
- if (typeof obj.command === "string") {
541
- const normalized = normalizeHookCommandForDedupe(obj.command);
542
- if (seenCommands.has(normalized)) {
543
- return undefined;
544
- }
545
- seenCommands.add(normalized);
546
- }
547
- if (Array.isArray(obj.hooks)) {
548
- const hooks = [];
549
- for (const nested of obj.hooks) {
550
- const deduped = dedupeHookEntryByCommand(nested, seenCommands);
551
- if (deduped !== undefined) {
552
- hooks.push(deduped);
553
- }
554
- else {
555
- changed = true;
556
- }
557
- }
558
- if (hooks.length !== obj.hooks.length) {
559
- changed = true;
560
- }
561
- if (hooks.length === 0 && typeof obj.command !== "string") {
562
- return undefined;
563
- }
564
- return changed ? { ...obj, hooks } : entry;
153
+ async function writeHarnessAssets(projectRoot, layout) {
154
+ await ensureDir(path.join(projectRoot, layout.commandsDir));
155
+ await writeFileSafe(path.join(projectRoot, layout.commandsDir, "cc.md"), renderStartCommand());
156
+ await writeFileSafe(path.join(projectRoot, layout.commandsDir, "cc-cancel.md"), renderCancelCommand());
157
+ await writeFileSafe(path.join(projectRoot, layout.commandsDir, "cc-idea.md"), renderIdeaCommand());
158
+ await ensureDir(path.join(projectRoot, layout.agentsDir));
159
+ for (const agent of CORE_AGENTS) {
160
+ await writeFileSafe(path.join(projectRoot, layout.agentsDir, `${agent.id}.md`), renderAgentMarkdown(agent));
565
161
  }
566
- return entry;
567
- }
568
- function dedupeHookEntriesByCommand(entries) {
569
- const seenCommands = new Set();
570
- const deduped = [];
571
- for (const entry of entries) {
572
- const next = dedupeHookEntryByCommand(entry, seenCommands);
573
- if (next !== undefined) {
574
- deduped.push(next);
575
- }
162
+ await ensureDir(path.join(projectRoot, layout.skillsDir));
163
+ for (const skill of AUTO_TRIGGER_SKILLS) {
164
+ await writeFileSafe(path.join(projectRoot, layout.skillsDir, skill.fileName), skill.body);
576
165
  }
577
- return deduped;
578
- }
579
- function mergeHookDocuments(existingDoc, generatedDoc) {
580
- const generatedRoot = toObject(generatedDoc) ?? {};
581
- const generatedHooks = toObject(generatedRoot.hooks) ?? {};
582
- const strippedExisting = stripManagedHookCommands(existingDoc).updated;
583
- const existingRoot = toObject(strippedExisting) ?? {};
584
- const existingHooks = toObject(existingRoot.hooks) ?? {};
585
- const mergedHooks = { ...existingHooks };
586
- for (const [eventName, generatedEntries] of Object.entries(generatedHooks)) {
587
- const existingEntries = existingHooks[eventName];
588
- if (Array.isArray(generatedEntries)) {
589
- const preservedEntries = Array.isArray(existingEntries) ? existingEntries : [];
590
- mergedHooks[eventName] = dedupeHookEntriesByCommand([...generatedEntries, ...preservedEntries]);
591
- continue;
592
- }
593
- // Defensive: malformed generated event payload must not wipe user hooks.
594
- if (Array.isArray(existingEntries)) {
595
- mergedHooks[eventName] = existingEntries;
166
+ if (layout.hooksConfig) {
167
+ const { dir, fileName } = layout.hooksConfig;
168
+ await ensureDir(path.join(projectRoot, dir));
169
+ if (fileName.endsWith(".mjs")) {
170
+ const moduleBody = `// cclaw opencode plugin (minimal). Wires session-start and stop-handoff hooks.\nimport { spawn } from \"node:child_process\";\nimport path from \"node:path\";\nfunction run(filePath) {\n return () => spawn(process.execPath, [path.join(\"${HOOKS_REL_PATH}\", filePath)], { stdio: \"inherit\" });\n}\nexport default {\n events: {\n \"session.start\": run(\"session-start.mjs\"),\n \"session.stop\": run(\"stop-handoff.mjs\")\n }\n};\n`;
171
+ await writeFileSafe(path.join(projectRoot, dir, fileName), moduleBody);
596
172
  }
597
173
  else {
598
- mergedHooks[eventName] = generatedEntries;
599
- }
600
- }
601
- const mergedRoot = {
602
- ...existingRoot,
603
- hooks: mergedHooks
604
- };
605
- for (const [key, value] of Object.entries(generatedRoot)) {
606
- if (key === "hooks")
607
- continue;
608
- if (!(key in mergedRoot)) {
609
- mergedRoot[key] = value;
610
- }
611
- }
612
- return mergedRoot;
613
- }
614
- async function writeMergedHookJson(projectRoot, hookFilePath, generatedJson) {
615
- let existingDoc = {};
616
- if (await exists(hookFilePath)) {
617
- try {
618
- const raw = await fs.readFile(hookFilePath, "utf8");
619
- const parsed = tryParseHookDocument(raw);
620
- if (parsed) {
621
- existingDoc = parsed.parsed;
622
- if (parsed.recovered) {
623
- await backupHookFile(projectRoot, hookFilePath, raw);
174
+ const hooksJson = {
175
+ version: 1,
176
+ generatedBy: `cclaw-cli@${CCLAW_VERSION}`,
177
+ events: {
178
+ "session.start": [
179
+ { command: "node", args: [`./${HOOKS_REL_PATH}/${SESSION_START_HOOK_SPEC.fileName}`] }
180
+ ],
181
+ "session.stop": [
182
+ { command: "node", args: [`./${HOOKS_REL_PATH}/${STOP_HANDOFF_HOOK_SPEC.fileName}`] }
183
+ ]
624
184
  }
625
- }
626
- else {
627
- await backupHookFile(projectRoot, hookFilePath, raw);
628
- existingDoc = {};
629
- }
630
- }
631
- catch {
632
- existingDoc = {};
633
- }
634
- }
635
- const generatedDoc = JSON.parse(generatedJson);
636
- const harness = harnessForHookFile(projectRoot, hookFilePath);
637
- if (harness) {
638
- const generatedSchema = validateHookDocument(harness, generatedDoc);
639
- if (!generatedSchema.ok) {
640
- throw new Error(`[sync fail-fast] Hook document drift detected for ${harness}: generated hook document is invalid (${generatedSchema.errors.join("; ")}). ` +
641
- "Run `npx cclaw-cli sync` to regenerate managed hooks or repair the generated hook shape manually.");
642
- }
643
- }
644
- const mergedDoc = mergeHookDocuments(existingDoc, generatedDoc);
645
- if (harness) {
646
- const mergedSchema = validateHookDocument(harness, mergedDoc);
647
- if (!mergedSchema.ok) {
648
- throw new Error(`[sync fail-fast] Hook document drift detected for ${harness}: merged hook document is invalid (${mergedSchema.errors.join("; ")}). ` +
649
- "Run `npx cclaw-cli sync` after fixing the custom hook entry or remove the malformed user-authored hook block.");
650
- }
651
- }
652
- await writeFileSafe(hookFilePath, `${JSON.stringify(mergedDoc, null, 2)}\n`);
653
- }
654
- async function readBundledRunHookRuntimeScript(options) {
655
- const bundleUrl = new URL("./runtime/run-hook.mjs", import.meta.url);
656
- try {
657
- await fs.stat(bundleUrl);
658
- }
659
- catch {
660
- return null;
661
- }
662
- try {
663
- const moduleUrl = `${bundleUrl.href}?ts=${Date.now()}`;
664
- const loaded = await import(moduleUrl);
665
- const factory = typeof loaded.buildRunHookRuntimeScript === "function"
666
- ? loaded.buildRunHookRuntimeScript
667
- : typeof loaded.default === "function"
668
- ? loaded.default
669
- : null;
670
- if (!factory)
671
- return null;
672
- const script = factory(options);
673
- if (typeof script !== "string")
674
- return null;
675
- return script.trim().length > 0 ? script : null;
676
- }
677
- catch {
678
- return null;
679
- }
680
- }
681
- async function writeHooks(projectRoot, config) {
682
- const harnesses = config.harnesses;
683
- const hooksDir = runtimePath(projectRoot, "hooks");
684
- const stateDir = runtimePath(projectRoot, "state");
685
- await ensureDir(hooksDir);
686
- await ensureDir(stateDir);
687
- await writeFileSafe(path.join(hooksDir, "stage-complete.mjs"), stageCompleteScript());
688
- await writeFileSafe(path.join(hooksDir, "start-flow.mjs"), startFlowScript());
689
- await writeFileSafe(path.join(hooksDir, "cancel-run.mjs"), cancelRunScript());
690
- const hookRuntimeOptions = {};
691
- const bundledHookRuntime = await readBundledRunHookRuntimeScript(hookRuntimeOptions);
692
- await writeFileSafe(path.join(hooksDir, "run-hook.mjs"), bundledHookRuntime ?? nodeHookRuntimeScript(hookRuntimeOptions));
693
- await writeFileSafe(path.join(hooksDir, "run-hook.cmd"), runHookCmdScript());
694
- await writeFileSafe(path.join(hooksDir, "delegation-record.mjs"), delegationRecordScript());
695
- await writeFileSafe(path.join(hooksDir, "slice-commit.mjs"), sliceCommitScript());
696
- const opencodePluginSource = opencodePluginJs();
697
- await writeFileSafe(path.join(hooksDir, "opencode-plugin.mjs"), opencodePluginSource);
698
- try {
699
- for (const script of [
700
- "stage-complete.mjs",
701
- "start-flow.mjs",
702
- "run-hook.mjs",
703
- "run-hook.cmd",
704
- "delegation-record.mjs",
705
- "slice-commit.mjs",
706
- "opencode-plugin.mjs",
707
- "cancel-run.mjs"
708
- ]) {
709
- await fs.chmod(path.join(hooksDir, script), 0o755);
710
- }
711
- }
712
- catch {
713
- // chmod may fail on some filesystems
714
- }
715
- if (harnesses.includes("opencode")) {
716
- const opencodePluginsDir = path.join(projectRoot, ".opencode/plugins");
717
- const opencodePluginPath = path.join(projectRoot, OPENCODE_PLUGIN_REL_PATH);
718
- await ensureDir(opencodePluginsDir);
719
- await writeFileSafe(opencodePluginPath, opencodePluginSource);
720
- await writeMergedOpenCodePluginConfig(projectRoot, OPENCODE_PLUGIN_REL_PATH);
721
- try {
722
- await fs.chmod(opencodePluginPath, 0o755);
723
- }
724
- catch {
725
- // chmod may fail on some filesystems
726
- }
727
- }
185
+ };
186
+ await writeFileSafe(path.join(projectRoot, dir, fileName), `${JSON.stringify(hooksJson, null, 2)}\n`);
187
+ }
188
+ }
189
+ }
190
+ async function writeConfig(projectRoot, config) {
191
+ const configPath = path.join(projectRoot, RUNTIME_ROOT, "config.yaml");
192
+ await writeFileSafe(configPath, renderConfig(config));
193
+ return configPath;
194
+ }
195
+ async function resolveHarnesses(projectRoot, fromOptions, fromConfig, interactive) {
196
+ if (fromOptions && fromOptions.length > 0)
197
+ return fromOptions;
198
+ if (fromConfig && fromConfig.length > 0)
199
+ return fromConfig;
200
+ const detected = await detectHarnesses(projectRoot);
201
+ if (interactive && isInteractive()) {
202
+ return runPicker({ detected });
203
+ }
204
+ if (detected.length > 0)
205
+ return detected;
206
+ throw new Error(NO_HARNESS_DETECTED_MESSAGE);
207
+ }
208
+ export async function syncCclaw(options) {
209
+ const projectRoot = options.cwd;
210
+ await ensureRuntimeRoot(projectRoot);
211
+ const existing = await readConfig(projectRoot);
212
+ const harnesses = await resolveHarnesses(projectRoot, options.harnesses, existing?.harnesses, options.interactive ?? false);
728
213
  for (const harness of harnesses) {
729
- if (harness === "claude") {
730
- const dir = path.join(projectRoot, ".claude/hooks");
731
- await ensureDir(dir);
732
- await writeMergedHookJson(projectRoot, path.join(dir, "hooks.json"), claudeHooksJson());
733
- }
734
- else if (harness === "cursor") {
735
- const cursorDir = path.join(projectRoot, ".cursor");
736
- await ensureDir(cursorDir);
737
- await writeMergedHookJson(projectRoot, path.join(cursorDir, "hooks.json"), cursorHooksJson());
738
- }
739
- else if (harness === "codex") {
740
- // Codex CLI lifecycle hooks live at `.codex/hooks.json`, gated by
741
- // `[features] codex_hooks = true` in `~/.codex/config.toml`. cclaw
742
- // always writes the file so the moment the flag flips on, cclaw
743
- // hooks start firing. PreToolUse/PostToolUse remain Bash-only on
744
- // Codex; if the feature flag is off, hooks stay inert until the
745
- // user enables `codex_hooks` in `~/.codex/config.toml`.
746
- const codexDir = path.join(projectRoot, ".codex");
747
- await ensureDir(codexDir);
748
- await writeMergedHookJson(projectRoot, path.join(codexDir, "hooks.json"), codexHooksJson());
749
- }
750
- // OpenCode registration is auto-managed via opencode.json/opencode.jsonc.
751
- }
752
- }
753
- async function canonicalHookScripts() {
754
- const hookRuntimeOptions = {};
755
- const bundledHookRuntime = await readBundledRunHookRuntimeScript(hookRuntimeOptions);
756
- return {
757
- "stage-complete.mjs": stageCompleteScript(),
758
- "start-flow.mjs": startFlowScript(),
759
- "cancel-run.mjs": cancelRunScript(),
760
- "run-hook.mjs": bundledHookRuntime ?? nodeHookRuntimeScript(hookRuntimeOptions),
761
- "run-hook.cmd": runHookCmdScript(),
762
- "delegation-record.mjs": delegationRecordScript(),
763
- "slice-commit.mjs": sliceCommitScript(),
764
- "opencode-plugin.mjs": opencodePluginJs()
765
- };
766
- }
767
- async function checkManagedHookDrift(projectRoot) {
768
- const hooksDir = runtimePath(projectRoot, "hooks");
769
- const canonical = await canonicalHookScripts();
770
- const findings = [];
771
- for (const [fileName, expectedSource] of Object.entries(canonical)) {
772
- const targetPath = path.join(hooksDir, fileName);
773
- let actual;
774
- try {
775
- actual = await fs.readFile(targetPath);
776
- }
777
- catch {
778
- findings.push({ file: fileName, reason: "missing" });
779
- continue;
780
- }
781
- const expected = Buffer.from(expectedSource, "utf8");
782
- if (!actual.equals(expected)) {
783
- findings.push({ file: fileName, reason: "content_mismatch" });
784
- }
785
- }
786
- return findings;
787
- }
788
- function formatManagedHookDriftError(findings) {
789
- const details = findings
790
- .map((finding) => `- .cclaw/hooks/${finding.file}: ${finding.reason === "missing" ? "missing" : "content differs from canonical renderer"}`)
791
- .join("\n");
792
- return ("[sync --check] Managed hook drift detected.\n" +
793
- `${details}\n` +
794
- "Re-run `npx cclaw-cli sync` to rewrite managed hooks.");
795
- }
796
- async function ensureKnowledgeStore(projectRoot) {
797
- const storePath = runtimePath(projectRoot, "knowledge.jsonl");
798
- if (!(await exists(storePath))) {
799
- await writeFileSafe(storePath, "", { mode: 0o600 });
800
- }
801
- const legacyMdPath = runtimePath(projectRoot, "knowledge.md");
802
- if (await exists(legacyMdPath)) {
803
- await fs.rm(legacyMdPath, { force: true });
804
- }
805
- }
806
- async function writeRulebook(projectRoot) {
807
- await writeFileSafe(runtimePath(projectRoot, "rules", "RULES.md"), RULEBOOK_MARKDOWN);
808
- await writeFileSafe(runtimePath(projectRoot, "rules", "rules.json"), `${JSON.stringify(buildRulesJson(), null, 2)}\n`);
809
- }
810
- async function writeCursorWorkflowRule(projectRoot, harnesses) {
811
- const rulePath = path.join(projectRoot, CURSOR_RULE_REL_PATH);
812
- const guidelinesPath = path.join(projectRoot, CURSOR_GUIDELINES_REL_PATH);
813
- if (!harnesses.includes("cursor")) {
814
- for (const target of [rulePath, guidelinesPath]) {
815
- try {
816
- await fs.rm(target, { force: true });
817
- }
818
- catch {
819
- // best-effort cleanup
820
- }
821
- }
822
- return;
823
- }
824
- await ensureDir(path.dirname(rulePath));
825
- await writeFileSafe(rulePath, CURSOR_WORKFLOW_RULE_MDC);
826
- await ensureDir(path.dirname(guidelinesPath));
827
- await writeFileSafe(guidelinesPath, CURSOR_GUIDELINES_RULE_MDC);
828
- }
829
- async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
830
- const enabled = new Set(harnesses);
831
- const managedHookFiles = [
832
- { harness: "claude", hookPath: path.join(projectRoot, ".claude/hooks/hooks.json") },
833
- { harness: "cursor", hookPath: path.join(projectRoot, ".cursor/hooks.json") },
834
- { harness: "codex", hookPath: path.join(projectRoot, ".codex/hooks.json") }
835
- ];
836
- for (const entry of managedHookFiles) {
837
- if (enabled.has(entry.harness))
838
- continue;
839
- await removeManagedHookEntries(entry.hookPath, { failOnParseError: true });
840
- }
841
- if (!enabled.has("opencode")) {
842
- try {
843
- await fs.rm(path.join(projectRoot, OPENCODE_PLUGIN_REL_PATH), { force: true });
844
- }
845
- catch {
846
- // best-effort cleanup
847
- }
848
- try {
849
- await fs.rm(path.join(projectRoot, ".opencode/agents"), { recursive: true, force: true });
850
- }
851
- catch {
852
- // best-effort cleanup
853
- }
854
- await removeManagedOpenCodePluginConfig(projectRoot, OPENCODE_PLUGIN_REL_PATH);
855
- }
856
- if (!enabled.has("codex")) {
857
- try {
858
- await fs.rm(path.join(projectRoot, ".codex/agents"), { recursive: true, force: true });
859
- }
860
- catch {
861
- // best-effort cleanup
862
- }
863
- }
864
- }
865
- async function writeState(projectRoot, config, forceReset = false) {
866
- void config;
867
- // Fresh init no longer materializes flow-state.json. The first managed
868
- // `/cc <idea>` start-flow call creates the state file.
869
- if (!forceReset) {
870
- return;
871
- }
872
- const statePath = runtimePath(projectRoot, "state", "flow-state.json");
873
- if (await exists(statePath)) {
874
- return;
875
- }
876
- const state = createInitialFlowState({ track: "standard" });
877
- await writeFileSafe(statePath, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
878
- }
879
- async function cleanLegacyArtifacts(projectRoot) {
880
- for (const legacyFolder of DEPRECATED_UTILITY_SKILL_FOLDERS) {
881
- await removeBestEffort(runtimePath(projectRoot, "skills", legacyFolder), true);
882
- }
883
- for (const legacyFolder of DEPRECATED_STAGE_SKILL_FOLDERS) {
884
- await removeBestEffort(runtimePath(projectRoot, "skills", legacyFolder), true);
885
- }
886
- for (const legacyFolder of DEPRECATED_SKILL_FOLDERS_FULL) {
887
- await removeBestEffort(runtimePath(projectRoot, "skills", legacyFolder), true);
888
- }
889
- for (const legacyAgentFile of DEPRECATED_AGENT_FILES) {
890
- await removeBestEffort(runtimePath(projectRoot, "agents", legacyAgentFile));
891
- }
892
- for (const legacyPlugin of [
893
- path.join(projectRoot, ".opencode/plugins/viby-plugin.mjs"),
894
- path.join(projectRoot, ".opencode/plugins/opencode-plugin.mjs"),
895
- path.join(projectRoot, OPENCODE_PLUGIN_REL_PATH)
896
- ]) {
897
- await removeBestEffort(legacyPlugin);
898
- }
899
- for (const legacyRuntimeFile of [
900
- ...DEPRECATED_COMMAND_FILES.map((file) => runtimePath(projectRoot, "commands", file)),
901
- ...DEPRECATED_SKILL_FILES.map((segments) => runtimePath(projectRoot, "skills", ...segments)),
902
- ...DEPRECATED_STATE_FILES.map((file) => runtimePath(projectRoot, "state", file)),
903
- ...DEPRECATED_ARTIFACT_FILES.map((file) => runtimePath(projectRoot, "artifacts", file)),
904
- ...DEPRECATED_RUNTIME_ROOT_FILES.map((file) => runtimePath(projectRoot, file)),
905
- ...DEPRECATED_HOOK_FILES.map((file) => runtimePath(projectRoot, "hooks", file))
906
- ]) {
907
- await removeBestEffort(legacyRuntimeFile);
908
- }
909
- // Runtime simplification cleanup: these folders were generated in older
910
- // releases and are now intentionally removed from user projects.
911
- for (const legacyRuntimeDir of DEPRECATED_RUNTIME_DIRS) {
912
- await removeBestEffort(runtimePath(projectRoot, legacyRuntimeDir), true);
913
- }
914
- // Archive storage migration: `.cclaw/runs` is legacy and no longer a valid
915
- // archive root. Remove only when empty; otherwise keep it so users can
916
- // manually migrate or inspect old data.
917
- const legacyRunsDir = runtimePath(projectRoot, "runs");
918
- try {
919
- const entries = await fs.readdir(legacyRunsDir);
920
- if (entries.length === 0) {
921
- await fs.rm(legacyRunsDir, { recursive: true, force: true });
922
- }
923
- }
924
- catch {
925
- // missing or unreadable legacy dir; keep best-effort behavior
926
- }
927
- // D-4 terminology migration: rename historical ideation artifact prefixes to
928
- // the canonical idea-* naming without deleting user-authored content.
929
- const legacyIdeaArtifactPattern = /^ideation-(.+\.md)$/u;
930
- const artifactsDir = runtimePath(projectRoot, "artifacts");
931
- try {
932
- const entries = await fs.readdir(artifactsDir);
933
- for (const entry of entries) {
934
- const match = legacyIdeaArtifactPattern.exec(entry);
935
- if (!match)
936
- continue;
937
- const nextName = `idea-${match[1]}`;
938
- const from = path.join(artifactsDir, entry);
939
- const to = path.join(artifactsDir, nextName);
940
- if (await exists(to)) {
941
- continue;
942
- }
943
- await fs.rename(from, to);
944
- }
945
- }
946
- catch {
947
- // no artifacts directory yet (fresh init) or read-only FS
948
- }
949
- }
950
- async function cleanStaleFiles(projectRoot) {
951
- const expectedShimFiles = new Set(harnessShimFileNames());
952
- const expectedShimSkills = new Set(harnessShimFileNames().map((fileName) => fileName.replace(/\.md$/u, "")));
953
- for (const adapter of Object.values(HARNESS_ADAPTERS)) {
954
- const commandDir = path.join(projectRoot, adapter.commandDir);
955
- if (!(await exists(commandDir)))
956
- continue;
957
- let entries = [];
958
- try {
959
- entries = await fs.readdir(commandDir);
960
- }
961
- catch {
962
- entries = [];
963
- }
964
- if (adapter.shimKind === "skill") {
965
- for (const entry of entries) {
966
- if (!/^cc(?:-.*)?$/u.test(entry))
967
- continue;
968
- if (expectedShimSkills.has(entry))
969
- continue;
970
- await fs.rm(path.join(commandDir, entry), { recursive: true, force: true });
971
- }
972
- continue;
973
- }
974
- for (const entry of entries) {
975
- if (!/^cc(?:-.*)?\.md$/u.test(entry))
976
- continue;
977
- if (expectedShimFiles.has(entry))
978
- continue;
979
- await fs.rm(path.join(commandDir, entry), { force: true });
980
- }
981
- }
982
- // Keep user-owned custom assets under .cclaw/agents and .cclaw/skills.
983
- // Legacy managed removals happen in cleanLegacyArtifacts() with explicit paths.
984
- }
985
- async function assertExpectedHarnessShims(projectRoot, harnesses) {
986
- const expectedFiles = harnessShimFileNames();
987
- const expectedSkillFolders = harnessShimSkillNames();
214
+ if (!HARNESS_IDS.includes(harness)) {
215
+ throw new Error(`Unknown harness: ${harness}. Supported: ${HARNESS_IDS.join(", ")}`);
216
+ }
217
+ }
218
+ const config = existing
219
+ ? { ...existing, version: CCLAW_VERSION, flowVersion: "8", harnesses }
220
+ : createDefaultConfig(harnesses);
221
+ await ensureRunSystem(projectRoot);
222
+ await writeAgentFiles(projectRoot);
223
+ for (const hook of NODE_HOOKS) {
224
+ await writeHookFile(projectRoot, hook);
225
+ }
226
+ await writeRuntimeSkills(projectRoot);
227
+ await writeMetaSkill(projectRoot);
228
+ await writeTemplates(projectRoot);
229
+ await writeStageRunbooks(projectRoot);
230
+ await writeReferencePatterns(projectRoot);
231
+ await writeResearchPlaybooks(projectRoot);
232
+ await writeRecoveryPlaybooks(projectRoot);
233
+ await writeExamples(projectRoot);
234
+ await writeAntipatterns(projectRoot);
235
+ await writeDecisionProtocol(projectRoot);
236
+ await writeIdeasSeed(projectRoot);
988
237
  for (const harness of harnesses) {
989
- const adapter = HARNESS_ADAPTERS[harness];
990
- const base = path.join(projectRoot, adapter.commandDir);
991
- for (const fileName of expectedFiles) {
992
- const target = adapter.shimKind === "skill"
993
- ? path.join(base, fileName.replace(/\.md$/u, ""), "SKILL.md")
994
- : path.join(base, fileName);
995
- if (!(await exists(target))) {
996
- throw new Error(`[sync fail-fast] Harness shim drift detected for ${harness}: missing ${target}. ` +
997
- `Run \`npx cclaw-cli sync\` again; if the file is still missing, inspect harness permissions/paths.`);
998
- }
999
- }
1000
- if (adapter.shimKind === "skill") {
1001
- for (const folder of expectedSkillFolders) {
1002
- const skillPath = path.join(base, folder, "SKILL.md");
1003
- if (!(await exists(skillPath))) {
1004
- throw new Error(`[sync fail-fast] Harness skill shim drift detected for ${harness}: missing ${skillPath}. ` +
1005
- `Run \`npx cclaw-cli sync\` again; if the issue persists, inspect generated .agents/skills surfaces.`);
1006
- }
1007
- }
1008
- }
1009
- }
1010
- }
1011
- async function maybeLogParallelWaveDispatchHint(projectRoot) {
1012
- const flowPath = runtimePath(projectRoot, "state", "flow-state.json");
1013
- if (!(await exists(flowPath)))
1014
- return;
1015
- try {
1016
- const planPath = runtimePath(projectRoot, "artifacts", "05-plan.md");
1017
- if (!(await exists(planPath)))
1018
- return;
1019
- const planRaw = await fs.readFile(planPath, "utf8");
1020
- const merged = mergeParallelWaveDefinitions(parseParallelExecutionPlanWaves(planRaw), await parseWavePlanDirectory(runtimePath(projectRoot, "artifacts")));
1021
- const hint = formatNextParallelWaveSyncHint(merged);
1022
- if (hint) {
1023
- process.stdout.write(`cclaw: ${hint}\n`);
1024
- }
1025
- }
1026
- catch {
1027
- // best-effort note only
1028
- }
1029
- }
1030
- async function materializeRuntime(projectRoot, config, forceStateReset, operation = "sync") {
1031
- await warnStaleInitSentinel(projectRoot, operation);
1032
- const sentinelPath = await writeInitSentinel(projectRoot, operation);
1033
- const managedSession = await ManagedResourceSession.create({ projectRoot, operation });
1034
- setActiveManagedResourceSession(managedSession);
1035
- try {
1036
- const harnesses = config.harnesses;
1037
- await ensureStructure(projectRoot);
1038
- await cleanLegacyArtifacts(projectRoot);
1039
- await cleanStaleFiles(projectRoot);
1040
- await Promise.all([
1041
- writeEntryCommands(projectRoot),
1042
- writeSkills(projectRoot, config),
1043
- writeArtifactTemplates(projectRoot),
1044
- writeWavePlansScaffold(projectRoot),
1045
- writeRulebook(projectRoot)
1046
- ]);
1047
- await writeState(projectRoot, config, forceStateReset);
1048
- try {
1049
- await ensureRunSystem(projectRoot, { createIfMissing: false });
1050
- }
1051
- catch (error) {
1052
- if (error instanceof CorruptFlowStateError) {
1053
- throw new Error(`[sync fail-fast] Corrupt flow state detected: ${error.message} ` +
1054
- `Resolve the quarantined flow-state file and re-run \`npx cclaw-cli sync\`.`);
1055
- }
1056
- throw error;
1057
- }
1058
- await ensureKnowledgeStore(projectRoot);
1059
- await writeHooks(projectRoot, config);
1060
- await syncDisabledHarnessArtifacts(projectRoot, harnesses);
1061
- await cleanupLegacyManagedGitHookRelays(projectRoot);
1062
- await syncHarnessShims(projectRoot, harnesses);
1063
- await assertExpectedHarnessShims(projectRoot, harnesses);
1064
- await writeCursorWorkflowRule(projectRoot, harnesses);
1065
- await ensureGitignore(projectRoot);
1066
- if (operation === "sync" || operation === "upgrade") {
1067
- await maybeLogParallelWaveDispatchHint(projectRoot);
1068
- }
1069
- await managedSession.commit();
1070
- await fs.unlink(sentinelPath).catch(() => undefined);
1071
- }
1072
- catch (error) {
1073
- // Leave the sentinel in place so the interrupted run is visible.
1074
- throw error;
1075
- }
1076
- finally {
1077
- setActiveManagedResourceSession(null);
1078
- }
1079
- }
1080
- async function warnCodexHooksFeatureFlagIfDisabled(harnesses) {
1081
- if (!harnesses.includes("codex"))
1082
- return;
1083
- const codexTomlPath = codexConfigPath();
1084
- let existing;
1085
- try {
1086
- existing = await readCodexConfig(codexTomlPath);
1087
- }
1088
- catch (error) {
1089
- process.stderr.write(`cclaw: could not read ${codexTomlPath} to validate codex_hooks flag: ${error instanceof Error ? error.message : String(error)}\n`);
1090
- return;
238
+ await writeHarnessAssets(projectRoot, HARNESS_LAYOUTS[harness]);
1091
239
  }
1092
- if (classifyCodexHooksFlag(existing) === "enabled")
1093
- return;
1094
- 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`);
240
+ await ensureGitignorePatterns(projectRoot);
241
+ const configPath = await writeConfig(projectRoot, config);
242
+ return { installedHarnesses: harnesses, configPath };
1095
243
  }
1096
244
  export async function initCclaw(options) {
1097
- if (options.harnesses !== undefined && options.harnesses.length === 0) {
1098
- throw new Error("Select at least one harness.");
1099
- }
1100
- const config = createDefaultConfig(options.harnesses, options.track);
1101
- await writeConfig(options.projectRoot, config, { mode: "minimal" });
1102
- // Init should scaffold runtime surfaces but leave flow-state creation to the
1103
- // first managed start-flow invocation.
1104
- await materializeRuntime(options.projectRoot, config, false, "init");
1105
- }
1106
- export async function syncCclaw(projectRoot, options = {}) {
1107
- if (options.harnesses !== undefined && options.harnesses.length === 0) {
1108
- throw new Error("Select at least one harness.");
1109
- }
1110
- if (options.check === true) {
1111
- const drift = await checkManagedHookDrift(projectRoot);
1112
- if (drift.length > 0) {
1113
- throw new Error(formatManagedHookDriftError(drift));
1114
- }
1115
- return;
1116
- }
1117
- const configExists = await exists(configPath(projectRoot));
1118
- let config = await readConfig(projectRoot);
1119
- if (!configExists) {
1120
- // Prefer detected harness markers over the hardcoded default list.
1121
- // Without this, a user running `cclaw sync` in a `.claude`-only
1122
- // project ends up with a config that also enables cursor/opencode/
1123
- // codex, which then creates invalid harness expectations.
1124
- // Fall back to the previous default (config.harnesses) if no markers
1125
- // are found so brand-new projects still bootstrap cleanly.
1126
- const detected = await detectHarnesses(projectRoot);
1127
- const harnesses = options.harnesses ?? (detected.length > 0 ? detected : config.harnesses);
1128
- const defaultConfig = createDefaultConfig(harnesses);
1129
- await writeConfig(projectRoot, defaultConfig);
1130
- config = defaultConfig;
1131
- }
1132
- else if (options.harnesses !== undefined) {
1133
- config = {
1134
- ...config,
1135
- harnesses: options.harnesses
1136
- };
1137
- await writeConfig(projectRoot, config, {
1138
- mode: "minimal",
1139
- advancedKeysPresent: await detectAdvancedKeys(projectRoot)
1140
- });
1141
- }
1142
- await materializeRuntime(projectRoot, config, false, "sync");
1143
- await warnCodexHooksFeatureFlagIfDisabled(config.harnesses);
1144
- }
1145
- /**
1146
- * Refresh generated files in `.cclaw/` without touching user-authored
1147
- * artifacts or state. Config remains harness-only with managed version
1148
- * stamps.
1149
- */
1150
- export async function upgradeCclaw(projectRoot) {
1151
- const configExists = await exists(configPath(projectRoot));
1152
- const advancedKeysPresent = await detectAdvancedKeys(projectRoot);
1153
- const detectedHarnesses = configExists ? [] : await detectHarnesses(projectRoot);
1154
- const existing = configExists
1155
- ? await readConfig(projectRoot)
1156
- : createDefaultConfig(detectedHarnesses.length > 0 ? detectedHarnesses : undefined);
1157
- const upgraded = {
1158
- ...existing,
1159
- version: CCLAW_VERSION,
1160
- flowVersion: FLOW_VERSION
1161
- };
1162
- await writeConfig(projectRoot, upgraded, {
1163
- mode: "minimal",
1164
- advancedKeysPresent
1165
- });
1166
- await materializeRuntime(projectRoot, upgraded, false, "upgrade");
245
+ return syncCclaw(options);
1167
246
  }
1168
- function stripManagedHookCommands(value) {
1169
- if (!value || typeof value !== "object" || Array.isArray(value)) {
1170
- return { updated: value, changed: false };
1171
- }
1172
- const root = { ...value };
1173
- const hooks = root.hooks;
1174
- if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) {
1175
- return { updated: root, changed: false };
1176
- }
1177
- let changed = false;
1178
- const cleanedHooks = {};
1179
- for (const [eventName, entries] of Object.entries(hooks)) {
1180
- if (!Array.isArray(entries)) {
1181
- cleanedHooks[eventName] = entries;
1182
- continue;
247
+ export async function uninstallCclaw(options) {
248
+ const projectRoot = options.cwd;
249
+ const config = await readConfig(projectRoot);
250
+ const harnesses = config?.harnesses ?? HARNESS_IDS;
251
+ await removePath(path.join(projectRoot, RUNTIME_ROOT));
252
+ for (const harness of harnesses) {
253
+ const layout = HARNESS_LAYOUTS[harness];
254
+ for (const filename of ["cc.md", "cc-cancel.md", "cc-idea.md"]) {
255
+ await removePath(path.join(projectRoot, layout.commandsDir, filename));
1183
256
  }
1184
- const cleanedEntries = entries.flatMap((entry) => {
1185
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
1186
- return [entry];
1187
- }
1188
- const obj = entry;
1189
- if (typeof obj.command === "string" && isManagedRuntimeHookCommand(obj.command)) {
1190
- changed = true;
1191
- return [];
1192
- }
1193
- if (Array.isArray(obj.hooks)) {
1194
- const nested = obj.hooks.filter((nestedHook) => {
1195
- if (!nestedHook || typeof nestedHook !== "object" || Array.isArray(nestedHook))
1196
- return true;
1197
- const nestedObj = nestedHook;
1198
- return !(typeof nestedObj.command === "string" && isManagedRuntimeHookCommand(nestedObj.command));
1199
- });
1200
- if (nested.length !== obj.hooks.length) {
1201
- changed = true;
1202
- }
1203
- if (nested.length === 0) {
1204
- changed = true;
1205
- return [];
1206
- }
1207
- return [{ ...obj, hooks: nested }];
1208
- }
1209
- return [entry];
1210
- });
1211
- if (cleanedEntries.length > 0) {
1212
- cleanedHooks[eventName] = cleanedEntries;
257
+ for (const agent of CORE_AGENTS) {
258
+ await removePath(path.join(projectRoot, layout.agentsDir, `${agent.id}.md`));
1213
259
  }
1214
- else if (entries.length > 0) {
1215
- changed = true;
260
+ await removePath(path.join(projectRoot, layout.skillsDir));
261
+ if (layout.hooksConfig) {
262
+ await removePath(path.join(projectRoot, layout.hooksConfig.dir, layout.hooksConfig.fileName));
1216
263
  }
1217
- }
1218
- if (!changed) {
1219
- return { updated: root, changed: false };
1220
- }
1221
- root.hooks = cleanedHooks;
1222
- return { updated: root, changed: true };
1223
- }
1224
- function isManagedRuntimeHookCommand(command) {
1225
- // Normalize whitespace and collapse any Windows-style backslash path
1226
- // separators to forward slashes so user-edited hook configs on Windows
1227
- // (e.g. `node .cclaw\hooks\run-hook.mjs ...`) still round-trip through
1228
- // sync without being duplicated alongside freshly generated entries.
1229
- const normalized = command.trim().replace(/\s+/gu, " ").replace(/\\/gu, "/");
1230
- if (/(^|\s)(?:node\s+)?(?:"|')?(?:\.\/)?\.cclaw\/hooks\/run-hook\.(?:mjs|cmd)(?:"|')?\s+(?:session-start|stop-handoff|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|pre-tool-pipeline|prompt-pipeline|context-monitor|verify-current-state)(?:\s|$)/u.test(normalized)) {
1231
- return true;
1232
- }
1233
- // Codex UserPromptSubmit non-blocking state nudge.
1234
- return /internal verify-current-state(?:\s|$)/u.test(normalized);
1235
- }
1236
- async function removeManagedHookEntries(hookFilePath, options = {}) {
1237
- if (!(await exists(hookFilePath)))
1238
- return;
1239
- let parsed = null;
1240
- try {
1241
- const raw = await fs.readFile(hookFilePath, "utf8");
1242
- const recovered = tryParseHookDocument(raw);
1243
- if (recovered === null) {
1244
- if (options.failOnParseError === true) {
1245
- throw new Error(`[sync fail-fast] Cannot strip managed hook entries from ${hookFilePath} — JSON is unparseable. ` +
1246
- `Run \`rm ${hookFilePath}\` and rerun \`npx cclaw-cli sync\`.`);
1247
- }
1248
- return;
264
+ if (await exists(path.join(projectRoot, layout.commandsDir))) {
265
+ const remaining = await fs.readdir(path.join(projectRoot, layout.commandsDir));
266
+ if (remaining.length === 0)
267
+ await removePath(path.join(projectRoot, layout.commandsDir));
1249
268
  }
1250
- parsed = recovered.parsed;
1251
- }
1252
- catch (error) {
1253
- if (options.failOnParseError === true) {
1254
- 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\`.`);
269
+ if (await exists(path.join(projectRoot, layout.agentsDir))) {
270
+ const remaining = await fs.readdir(path.join(projectRoot, layout.agentsDir));
271
+ if (remaining.length === 0)
272
+ await removePath(path.join(projectRoot, layout.agentsDir));
1255
273
  }
1256
- return;
1257
274
  }
1258
- const { updated, changed } = stripManagedHookCommands(parsed);
1259
- if (!changed)
1260
- return;
1261
- const root = updated;
1262
- const hooks = root.hooks;
1263
- const hasHooks = typeof hooks === "object" &&
1264
- hooks !== null &&
1265
- !Array.isArray(hooks) &&
1266
- Object.keys(hooks).length > 0;
1267
- if (!hasHooks) {
1268
- const onlyHooksShell = Object.keys(root).every((key) => key === "hooks" || key === "version" || key === "cclawHookSchemaVersion");
1269
- if (onlyHooksShell) {
1270
- await fs.rm(hookFilePath, { force: true });
1271
- return;
1272
- }
1273
- root.hooks = {};
1274
- }
1275
- await writeFileSafe(hookFilePath, `${JSON.stringify(root, null, 2)}\n`);
275
+ await removeGitignorePatterns(projectRoot);
1276
276
  }
1277
- async function removeIfEmpty(dirPath) {
1278
- try {
1279
- const entries = await fs.readdir(dirPath);
1280
- if (entries.length === 0) {
1281
- await fs.rmdir(dirPath);
1282
- }
1283
- }
1284
- catch {
1285
- // directory not present or not removable
1286
- }
277
+ export async function upgradeCclaw(options) {
278
+ return syncCclaw(options);
1287
279
  }
1288
- export async function uninstallCclaw(projectRoot) {
1289
- const fullRuntimePath = path.join(projectRoot, RUNTIME_ROOT);
1290
- try {
1291
- await fs.rm(fullRuntimePath, { recursive: true, force: true });
1292
- }
1293
- catch {
1294
- // path not present
1295
- }
1296
- await removeCclawFromAgentsMd(projectRoot);
1297
- await removeGitignorePatterns(projectRoot);
1298
- await cleanupLegacyManagedGitHookRelays(projectRoot);
1299
- const hookFiles = [
1300
- ".claude/hooks/hooks.json",
1301
- ".cursor/hooks.json",
1302
- ".codex/hooks.json"
1303
- ];
1304
- for (const hf of hookFiles) {
1305
- await removeManagedHookEntries(path.join(projectRoot, hf));
1306
- }
1307
- const commandDirs = [
1308
- ".claude/commands",
1309
- ".cursor/commands",
1310
- ".opencode/commands",
1311
- ".codex/commands"
1312
- ];
1313
- for (const relDir of commandDirs) {
1314
- const fullDir = path.join(projectRoot, relDir);
1315
- try {
1316
- const entries = await fs.readdir(fullDir);
1317
- for (const entry of entries) {
1318
- if (/^(?:viby|cc)(?:-.*)?\.md$/u.test(entry)) {
1319
- await fs.rm(path.join(fullDir, entry), { force: true });
1320
- }
1321
- }
1322
- }
1323
- catch {
1324
- // directory not present
1325
- }
1326
- }
1327
- // Codex shims live at `.agents/skills/cc*/SKILL.md`, matching Codex's
1328
- // `/use cc` prompt verbatim. Older layouts (`.codex/commands/cc*.md` and
1329
- // `.agents/skills/cclaw-cc*/SKILL.md`) are purged on uninstall so orphans
1330
- // can't linger. We only touch cclaw-owned folder names — other tools
1331
- // share `.agents/skills/` with us.
1332
- const codexSkillsRoot = path.join(projectRoot, ".agents/skills");
1333
- try {
1334
- const entries = await fs.readdir(codexSkillsRoot);
1335
- for (const entry of entries) {
1336
- if (/^(?:cclaw-)?cc(?:-(?:next|view|finish|cancel|ops|idea|brainstorm|scope|design|spec|plan|tdd|review|ship))?$/u.test(entry)) {
1337
- await fs.rm(path.join(codexSkillsRoot, entry), { recursive: true, force: true });
1338
- }
1339
- }
1340
- }
1341
- catch {
1342
- // directory not present
1343
- }
1344
- await removeIfEmpty(codexSkillsRoot);
1345
- await removeIfEmpty(path.join(projectRoot, ".agents"));
1346
- const managedAgentNames = CCLAW_AGENTS.map((agent) => agent.name);
1347
- for (const agentName of managedAgentNames) {
1348
- await removeBestEffort(path.join(projectRoot, ".opencode/agents", `${agentName}.md`));
1349
- await removeBestEffort(path.join(projectRoot, ".codex/agents", `${agentName}.toml`));
1350
- }
1351
- for (const pluginPath of [
1352
- path.join(projectRoot, ".opencode/plugins/viby-plugin.mjs"),
1353
- path.join(projectRoot, ".opencode/plugins/opencode-plugin.mjs"),
1354
- path.join(projectRoot, OPENCODE_PLUGIN_REL_PATH)
1355
- ]) {
1356
- try {
1357
- await fs.rm(pluginPath, { force: true });
1358
- }
1359
- catch {
1360
- // best-effort cleanup
1361
- }
1362
- }
1363
- await removeManagedOpenCodePluginConfig(projectRoot, OPENCODE_PLUGIN_REL_PATH);
1364
- for (const target of [
1365
- path.join(projectRoot, CURSOR_RULE_REL_PATH),
1366
- path.join(projectRoot, CURSOR_GUIDELINES_REL_PATH)
1367
- ]) {
1368
- try {
1369
- await fs.rm(target, { force: true });
1370
- }
1371
- catch {
1372
- // best-effort cleanup
1373
- }
1374
- }
1375
- const managedDirs = [
1376
- ".claude/hooks",
1377
- ".claude/commands",
1378
- ".claude",
1379
- ".cursor/rules",
1380
- ".cursor/commands",
1381
- ".cursor",
1382
- ".codex/agents",
1383
- ".codex/commands",
1384
- ".codex",
1385
- ".opencode/agents",
1386
- ".opencode/plugins",
1387
- ".opencode/commands",
1388
- ".opencode"
1389
- ];
1390
- for (const relDir of managedDirs) {
1391
- await removeIfEmpty(path.join(projectRoot, relDir));
1392
- }
280
+ export function planSeedForSlug(slug) {
281
+ return planTemplateForSlug(slug);
1393
282
  }
283
+ export const HARNESS_LAYOUT_TABLE = HARNESS_LAYOUTS;
284
+ export const COMMIT_HELPER_HOOK = COMMIT_HELPER_HOOK_SPEC;