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
@@ -1,635 +0,0 @@
1
- import { RUNTIME_ROOT } from "../constants.js";
2
- import { META_SKILL_NAME } from "./meta-skill.js";
3
- import { SHARED_FLOW_AND_KNOWLEDGE_SNIPPETS, SHARED_STAGE_SUPPORT_SNIPPETS } from "./runtime-shared-snippets.js";
4
- export function opencodePluginJs(_options = {}) {
5
- return `// cclaw OpenCode plugin — generated by npx cclaw-cli sync
6
- import { appendFileSync, existsSync, mkdirSync } from "node:fs";
7
- import { readFile, stat } from "node:fs/promises";
8
- import { basename, join } from "node:path";
9
-
10
- export default function cclawPlugin(ctx) {
11
- const root = ctx.directory || process.cwd();
12
- const runtimeDir = join(root, "${RUNTIME_ROOT}");
13
- const stateDir = join(runtimeDir, "state");
14
- const logsDir = join(runtimeDir, "logs");
15
- const pluginLogPath = join(logsDir, "opencode-plugin.log");
16
- const configPath = join(runtimeDir, "config.yaml");
17
- const flowStatePath = join(stateDir, "flow-state.json");
18
- const knowledgePath = join(runtimeDir, "knowledge.jsonl");
19
- const metaSkillPath = join(runtimeDir, "skills/${META_SKILL_NAME}/SKILL.md");
20
- ${SHARED_FLOW_AND_KNOWLEDGE_SNIPPETS}
21
- ${SHARED_STAGE_SUPPORT_SNIPPETS}
22
-
23
- function ensureRuntimeDirs() {
24
- try {
25
- mkdirSync(runtimeDir, { recursive: true });
26
- } catch {
27
- // ignore
28
- }
29
- try {
30
- mkdirSync(stateDir, { recursive: true });
31
- } catch {
32
- // ignore
33
- }
34
- }
35
-
36
- /**
37
- * Diagnostic log used instead of console.error on the hot path.
38
- * Writing to a file keeps hook failures and unknown events from
39
- * racing OpenCode's TUI renderer (which causes the overlapping-text
40
- * artifact users have reported). Best-effort: any I/O failure is
41
- * swallowed so logging never itself blocks or throws.
42
- */
43
- function logToFile(line) {
44
- try {
45
- mkdirSync(logsDir, { recursive: true });
46
- } catch {
47
- return;
48
- }
49
- const timestamp = new Date().toISOString();
50
- try {
51
- appendFileSync(pluginLogPath, timestamp + " " + line + "\\n", "utf8");
52
- } catch {
53
- // ignore — never let logging fail the hook
54
- }
55
- }
56
-
57
- async function readFlowState() {
58
- try {
59
- const raw = await readFile(flowStatePath, "utf8");
60
- return summarizeFlowState(JSON.parse(raw));
61
- } catch {
62
- return summarizeFlowState({});
63
- }
64
- }
65
-
66
- async function readFileText(filePath) {
67
- try {
68
- return await readFile(filePath, "utf8");
69
- } catch {
70
- return "";
71
- }
72
- }
73
-
74
- async function readKnowledgeDigest(stage) {
75
- const raw = await readFileText(knowledgePath);
76
- return parseKnowledgeDigest(raw, stage, 6).lines;
77
- }
78
-
79
- async function readStageSupportContext(stage) {
80
- if (!isKnownStageId(stage)) return [];
81
- const parts = [];
82
- const contract = (await readFileText(join(runtimeDir, "templates/state-contracts", stage + ".json"))).trim();
83
- if (contract.length > 0) {
84
- parts.push(
85
- "Current stage state contract (read before drafting or editing the stage artifact):\\n" +
86
- contract
87
- );
88
- }
89
- const reviewPromptName = reviewPromptFileName(stage);
90
- if (reviewPromptName) {
91
- const prompt = (await readFileText(join(runtimeDir, "skills/review-prompts", reviewPromptName))).trim();
92
- if (prompt.length > 0) {
93
- parts.push(
94
- "Current stage calibrated review prompt (use before asking for approval/completion):\\n" +
95
- prompt
96
- );
97
- }
98
- }
99
- return parts;
100
- }
101
-
102
- const BOOTSTRAP_MARKER = "<!-- cclaw-bootstrap-v1 -->";
103
-
104
- async function buildBootstrap() {
105
- const flow = await readFlowState();
106
- const parts = [
107
- BOOTSTRAP_MARKER,
108
- \`cclaw loaded. Flow: stage=\${flow.stage} (\${flow.completed}/8 completed, run=\${flow.activeRunId}). Active artifacts: \${activeArtifactsPathLabel("${RUNTIME_ROOT}")}\`
109
- ];
110
-
111
-
112
-
113
- const knowledge = await readKnowledgeDigest(flow.stage);
114
- if (knowledge.length > 0) parts.push("Knowledge digest (top relevant entries):", ...knowledge);
115
-
116
- const stageSupport = await readStageSupportContext(flow.stage);
117
- if (stageSupport.length > 0) parts.push(...stageSupport);
118
-
119
- parts.push(
120
- "If you discover a non-obvious rule or pattern during stage work, add it to the current artifact ## Learnings section; stage-complete harvests it into .cclaw/knowledge.jsonl. If this plugin does not load, run \`npx cclaw-cli sync\`, verify opencode.json(.c) includes the cclaw plugin registration, then run \`npx cclaw-cli sync\`. Direct JSONL append is only for explicit manual learnings operations."
121
- );
122
-
123
- const meta = (await readFileText(metaSkillPath)).trim();
124
- if (meta) parts.push("", meta);
125
- return parts.join("\\n");
126
- }
127
-
128
- let bootstrapCache = "";
129
- let bootstrapMtimes = new Map();
130
- let bootstrapRefreshPromise = null;
131
- const BOOTSTRAP_SOURCE_PATHS = [
132
- flowStatePath,
133
- knowledgePath,
134
- metaSkillPath,
135
- ...STAGE_IDS.map((stage) => join(runtimeDir, "templates/state-contracts", stage + ".json")),
136
- ...REVIEW_PROMPT_FILES.map((file) => join(runtimeDir, "skills/review-prompts", file))
137
- ];
138
-
139
- async function readMtimeMs(filePath) {
140
- try {
141
- const st = await stat(filePath);
142
- return Number.isFinite(st.mtimeMs) ? st.mtimeMs : 0;
143
- } catch {
144
- return 0;
145
- }
146
- }
147
-
148
- async function snapshotBootstrapMtimes() {
149
- const next = new Map();
150
- for (const filePath of BOOTSTRAP_SOURCE_PATHS) {
151
- next.set(filePath, await readMtimeMs(filePath));
152
- }
153
- return next;
154
- }
155
-
156
- async function bootstrapNeedsRefresh() {
157
- if (!bootstrapCache) return true;
158
- for (const filePath of BOOTSTRAP_SOURCE_PATHS) {
159
- const prev = bootstrapMtimes.get(filePath) ?? 0;
160
- const now = await readMtimeMs(filePath);
161
- if (prev !== now) return true;
162
- }
163
- return false;
164
- }
165
-
166
- async function refreshBootstrapCache(force = false) {
167
- if (!force && !(await bootstrapNeedsRefresh())) {
168
- return bootstrapCache;
169
- }
170
- if (bootstrapRefreshPromise) {
171
- return bootstrapRefreshPromise;
172
- }
173
- bootstrapRefreshPromise = (async () => {
174
- const nextBootstrap = await buildBootstrap();
175
- const nextMtimes = await snapshotBootstrapMtimes();
176
- bootstrapCache = nextBootstrap;
177
- bootstrapMtimes = nextMtimes;
178
- return bootstrapCache;
179
- })();
180
- try {
181
- return await bootstrapRefreshPromise;
182
- } finally {
183
- bootstrapRefreshPromise = null;
184
- }
185
- }
186
-
187
- function getBootstrap() {
188
- return bootstrapCache;
189
- }
190
-
191
- const MAX_CONCURRENT_HOOKS = 2;
192
- let runningHookTasks = 0;
193
- const pendingHookTasks = [];
194
-
195
- function runNextHookTask() {
196
- if (runningHookTasks >= MAX_CONCURRENT_HOOKS) return;
197
- const queued = pendingHookTasks.shift();
198
- if (!queued) return;
199
- runningHookTasks += 1;
200
- queued()
201
- .catch(() => false)
202
- .finally(() => {
203
- runningHookTasks -= 1;
204
- runNextHookTask();
205
- });
206
- }
207
-
208
- function scheduleHookTask(task) {
209
- return new Promise((resolve) => {
210
- const wrapped = async () => {
211
- try {
212
- resolve(await task());
213
- } catch {
214
- resolve(false);
215
- }
216
- };
217
- pendingHookTasks.push(wrapped);
218
- runNextHookTask();
219
- });
220
- }
221
-
222
- const lastHookStderr = new Map();
223
- function recordHookStderr(hookName, stderr) {
224
- if (typeof hookName !== "string" || hookName.length === 0) return;
225
- const trimmed = typeof stderr === "string" ? stderr.trim() : "";
226
- if (trimmed.length === 0) {
227
- lastHookStderr.delete(hookName);
228
- return;
229
- }
230
- lastHookStderr.set(hookName, trimmed);
231
- }
232
-
233
- /**
234
- * A hook process can exit non-zero for two very different reasons:
235
- * (a) the guard legitimately decided to refuse an operation
236
- * (strict-mode refusal — this is the *only* case we ever want
237
- * to surface as a block to the user), or
238
- * (b) the hook infrastructure itself failed — the runtime crashed,
239
- * a child binary was missing, stderr is a chunk of yargs help
240
- * from some unrelated process, timeout, etc.
241
- *
242
- * Treating (b) as a block is what produces the "guard blocked
243
- * tool.execute.before" error on a user who did nothing wrong. The
244
- * heuristic below trims guaranteed-infra signals so only cleanly
245
- * structured guard output is eligible for a real strict-mode block.
246
- */
247
- const INFRA_NOISE_PATTERNS = [
248
- /^\\s*(Usage|Options|Commands|Examples|Positionals|Aliases):/im,
249
- /^\\s*--[a-z][a-z0-9-]*\\b.*\\[(string|boolean|number|array)\\]/im,
250
- /\\bcommand (not found|failed)\\b/i,
251
- /\\bno such file or directory\\b/i,
252
- /\\bCannot find module\\b/i,
253
- /\\bThrowsCompletion\\b/,
254
- /\\b(Reference|Syntax|Type|Range)Error\\b/,
255
- /^\\s*at [^\\n]+\\([^)]*:\\d+:\\d+\\)/im,
256
- /^\\s*node:internal\\b/im
257
- ];
258
- function looksLikeInfrastructureFailure(stderr) {
259
- if (typeof stderr !== "string") return true;
260
- const trimmed = stderr.trim();
261
- if (trimmed.length === 0) return true;
262
- for (const pattern of INFRA_NOISE_PATTERNS) {
263
- if (pattern.test(trimmed)) return true;
264
- }
265
- return false;
266
- }
267
-
268
- function resolveNodeExecutable() {
269
- const override = typeof process.env.CCLAW_NODE_EXECUTABLE === "string"
270
- ? process.env.CCLAW_NODE_EXECUTABLE.trim()
271
- : "";
272
- if (override.length > 0) return override;
273
-
274
- const execName = basename(process.execPath || "").toLowerCase();
275
- if (execName === "node" || execName === "node.exe") {
276
- return process.execPath;
277
- }
278
-
279
- // OpenCode can host plugins from its own CLI binary, making
280
- // process.execPath point at opencode instead of Node. Fall back to the
281
- // user's Node on PATH so generated cclaw hooks execute as JavaScript.
282
- return "node";
283
- }
284
-
285
- async function runHookScript(hookName, payload = {}) {
286
- const { spawn } = await import("node:child_process");
287
- const hookRuntimePath = join(root, "${RUNTIME_ROOT}/hooks/run-hook.mjs");
288
- const input = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
289
- return scheduleHookTask(() => new Promise((resolve) => {
290
- let stderr = "";
291
- let settled = false;
292
- const finish = (ok) => {
293
- if (settled) return;
294
- settled = true;
295
- recordHookStderr(hookName, stderr);
296
- resolve(ok);
297
- };
298
-
299
- let child;
300
- try {
301
- child = spawn(resolveNodeExecutable(), [hookRuntimePath, hookName], {
302
- cwd: root,
303
- stdio: ["pipe", "ignore", "pipe"]
304
- });
305
- } catch {
306
- finish(false);
307
- return;
308
- }
309
-
310
- // Tool.execute.before is a user-facing hot path: 20s is far too
311
- // long to wait on a guard. 5s gives the hook real breathing room
312
- // (typical runtime is well under 500ms) while capping the worst-
313
- // case stall at a number the user will still tolerate.
314
- const timer = setTimeout(() => {
315
- child.kill("SIGKILL");
316
- if (stderr.length > 0) {
317
- logToFile("hook timeout: " + hookName + " stderr=" + stderr.slice(-1200));
318
- } else {
319
- logToFile("hook timeout: " + hookName + " (no stderr)");
320
- }
321
- finish(false);
322
- }, 5_000);
323
-
324
- child.stderr?.on("data", (chunk) => {
325
- stderr += String(chunk ?? "");
326
- if (stderr.length > 4000) {
327
- stderr = stderr.slice(-4000);
328
- }
329
- });
330
- child.on("error", () => {
331
- clearTimeout(timer);
332
- finish(false);
333
- });
334
- child.on("close", (code) => {
335
- clearTimeout(timer);
336
- const ok = code === 0;
337
- if (!ok && stderr.length > 0) {
338
- logToFile("hook failed: " + hookName + " exit=" + code + " stderr=" + stderr.slice(-1200));
339
- }
340
- finish(ok);
341
- });
342
- if (child.stdin) {
343
- child.stdin.on("error", (error) => {
344
- const code =
345
- error && typeof error === "object" && "code" in error
346
- ? String(error.code)
347
- : "";
348
- if (code === "EPIPE" || code === "ERR_STREAM_DESTROYED") {
349
- return;
350
- }
351
- clearTimeout(timer);
352
- finish(false);
353
- });
354
- try {
355
- child.stdin.end(input);
356
- } catch (error) {
357
- const code =
358
- error && typeof error === "object" && "code" in error
359
- ? String(error.code)
360
- : "";
361
- if (code !== "EPIPE" && code !== "ERR_STREAM_DESTROYED") {
362
- clearTimeout(timer);
363
- finish(false);
364
- }
365
- }
366
- }
367
- }));
368
- }
369
-
370
- function normalizeToolPayload(input, output) {
371
- if (typeof output === "undefined") return input ?? {};
372
- return { input: input ?? {}, output: output ?? {} };
373
- }
374
-
375
- /**
376
- * Read-only tools cannot mutate state or execute arbitrary code, so
377
- * running prompt/workflow guards on them is pure overhead and — worse —
378
- * surfaces a hard block when guards are misconfigured. OpenCode tool
379
- * names vary (Claude/Codex use PascalCase; opencode-native often uses
380
- * lowercase), so we normalize and match against a tight allow-list.
381
- * Anything not in this list (bash, edit, write, patch, task, run, …)
382
- * still runs through guards.
383
- */
384
- const SAFE_READONLY_TOOLS = new Set([
385
- // Filesystem / search reads — no state mutation possible.
386
- "read",
387
- "glob",
388
- "grep",
389
- "list",
390
- "ls",
391
- "view",
392
- "find",
393
- // Network reads — no local state mutation.
394
- "webfetch",
395
- "websearch",
396
- // User-facing question / ask tools: they only ask the human for
397
- // input and cannot touch the filesystem or execute code. Blocking
398
- // them strands the plugin mid-decision (see OpenCode's \`question\`).
399
- "question",
400
- "ask",
401
- "askuser",
402
- "askquestion",
403
- "ask_question",
404
- "ask_user",
405
- "ask_user_question",
406
- "askuserquestion",
407
- "request_user_input",
408
- "requestuserinput",
409
- "prompt",
410
- // Thinking / scratchpad tools — pure reasoning with no side effects.
411
- "think",
412
- "thinking",
413
- // Todo bookkeeping tools — they write only inside the harness's own
414
- // session state, not project files, and blocking them breaks agent
415
- // planning without protecting anything.
416
- "todo",
417
- "todoread",
418
- "todowrite",
419
- "todo_read",
420
- "todo_write"
421
- ]);
422
-
423
- function isSafeReadOnlyTool(payload) {
424
- if (!payload || typeof payload !== "object") return false;
425
- const candidates = [
426
- payload.tool,
427
- payload.name,
428
- payload.tool_name,
429
- payload.toolName
430
- ];
431
- const inner = payload.input;
432
- if (inner && typeof inner === "object") {
433
- candidates.push(inner.tool, inner.name, inner.tool_name, inner.toolName);
434
- }
435
- for (const candidate of candidates) {
436
- if (typeof candidate !== "string" || candidate.length === 0) continue;
437
- if (SAFE_READONLY_TOOLS.has(candidate.toLowerCase())) return true;
438
- }
439
- return false;
440
- }
441
-
442
- /**
443
- * Strictness derived from (in order of precedence): CCLAW_STRICTNESS
444
- * env override, \`.cclaw/config.yaml\` key \`strictness\`, or the
445
- * library default of "advisory". The plugin only ever *blocks* tool
446
- * execution when strictness resolves to "strict"; in advisory mode
447
- * guard failures are logged and the tool call proceeds. This mirrors
448
- * the Ralph-loop / hook-runtime semantics of
449
- * \`DEFAULT_STRICTNESS = advisory\`, so the plugin can no longer
450
- * accidentally be the stricter half of a mismatched pair.
451
- */
452
- async function readConfigStrictness() {
453
- try {
454
- if (!existsSync(configPath)) return "";
455
- const raw = await readFileText(configPath);
456
- if (typeof raw !== "string" || raw.length === 0) return "";
457
- const match = raw.match(/^\\s*strictness\\s*:\\s*([A-Za-z0-9_-]+)/m);
458
- return match && typeof match[1] === "string" ? match[1].trim().toLowerCase() : "";
459
- } catch {
460
- return "";
461
- }
462
- }
463
-
464
- async function resolveStrictness() {
465
- const envRaw = typeof process.env.CCLAW_STRICTNESS === "string"
466
- ? process.env.CCLAW_STRICTNESS.trim().toLowerCase()
467
- : "";
468
- if (envRaw === "strict") return "strict";
469
- if (envRaw === "advisory" || envRaw === "off" || envRaw === "disabled" || envRaw === "none") {
470
- return "advisory";
471
- }
472
- const fileRaw = await readConfigStrictness();
473
- if (fileRaw === "strict") return "strict";
474
- return "advisory";
475
- }
476
-
477
- /**
478
- * cclaw considers itself "active" in a project when both the state
479
- * file and the hook runtime script exist. If either is missing the
480
- * plugin behaves as a no-op for guards — this project hasn't been
481
- * initialized (or the install is corrupt) and blocking every tool
482
- * call would strand the user.
483
- */
484
- function isCclawInitialized() {
485
- try {
486
- const hookRuntimePath = join(runtimeDir, "hooks/run-hook.mjs");
487
- return existsSync(flowStatePath) && existsSync(hookRuntimePath);
488
- } catch {
489
- return false;
490
- }
491
- }
492
-
493
- let notInitializedAdvised = false;
494
- function noteNotInitialized() {
495
- if (notInitializedAdvised) return;
496
- notInitializedAdvised = true;
497
- logToFile(
498
- "guards skipped: cclaw is not initialized in this project. " +
499
- "Run \`npx cclaw-cli init\` in " + root + " to activate flow enforcement."
500
- );
501
- }
502
-
503
- /**
504
- * Escape hatch for a user stuck behind a misbehaving guard chain.
505
- * Reads a small set of env vars that all mean "turn cclaw off for this
506
- * session": CCLAW_DISABLE=1 (primary), CCLAW_STRICTNESS=off, or
507
- * CCLAW_GUARDS=off. Anything truthy disables both guards and the
508
- * advisory path. Logged once so users can confirm the bypass is in
509
- * effect without cluttering the TUI.
510
- */
511
- const DISABLE_ENV_KEYS = ["CCLAW_DISABLE", "CCLAW_GUARDS", "CCLAW_STRICTNESS"];
512
- let disabledAdvised = false;
513
- function isCclawDisabled() {
514
- for (const key of DISABLE_ENV_KEYS) {
515
- const raw = process.env[key];
516
- if (typeof raw !== "string") continue;
517
- const value = raw.trim().toLowerCase();
518
- if (value.length === 0) continue;
519
- if (key === "CCLAW_STRICTNESS") {
520
- if (value === "off" || value === "disabled" || value === "none") {
521
- return { disabled: true, key, value };
522
- }
523
- continue;
524
- }
525
- if (
526
- value === "1" ||
527
- value === "true" ||
528
- value === "yes" ||
529
- value === "on" ||
530
- value === "off" ||
531
- value === "disabled"
532
- ) {
533
- if (key === "CCLAW_GUARDS" && (value === "on" || value === "true" || value === "yes" || value === "1")) {
534
- continue;
535
- }
536
- return { disabled: true, key, value };
537
- }
538
- }
539
- return { disabled: false, key: "", value: "" };
540
- }
541
- function noteDisabled(reason) {
542
- if (disabledAdvised) return;
543
- disabledAdvised = true;
544
- logToFile(
545
- "guards disabled by env " + reason.key + "=" + reason.value + ". " +
546
- "All tool calls will pass through without prompt/workflow checks."
547
- );
548
- }
549
-
550
- function resolveEventType(payload) {
551
- if (typeof payload === "string") return payload;
552
- if (payload && typeof payload === "object") {
553
- if (typeof payload.type === "string") return payload.type;
554
- if (typeof payload.eventType === "string") return payload.eventType;
555
- if (typeof payload.kind === "string") return payload.kind;
556
- if (typeof payload.topic === "string") return payload.topic;
557
- if (typeof payload.name === "string") return payload.name;
558
- if (payload.event && typeof payload.event === "object") {
559
- if (typeof payload.event.type === "string") return payload.event.type;
560
- if (typeof payload.event.eventType === "string") return payload.event.eventType;
561
- if (typeof payload.event.kind === "string") return payload.event.kind;
562
- if (typeof payload.event.topic === "string") return payload.event.topic;
563
- if (typeof payload.event.name === "string") return payload.event.name;
564
- }
565
- }
566
- return "";
567
- }
568
-
569
- function resolveEventData(payload) {
570
- if (payload && typeof payload === "object") {
571
- if (payload.event && typeof payload.event === "object") {
572
- if (payload.event.data && typeof payload.event.data === "object") {
573
- return payload.event.data;
574
- }
575
- if (payload.event.payload && typeof payload.event.payload === "object") {
576
- return payload.event.payload;
577
- }
578
- return payload.event;
579
- }
580
- if (payload.data && typeof payload.data === "object") {
581
- return payload.data;
582
- }
583
- if (payload.payload && typeof payload.payload === "object") {
584
- return payload.payload;
585
- }
586
- }
587
- return payload;
588
- }
589
-
590
- ensureRuntimeDirs();
591
- void refreshBootstrapCache(true);
592
-
593
- return {
594
- event: async (payload) => {
595
- const eventType = resolveEventType(payload);
596
- const eventData = resolveEventData(payload);
597
- if (!eventType) {
598
- const keys =
599
- payload && typeof payload === "object"
600
- ? Object.keys(payload).slice(0, 10).join(", ")
601
- : typeof payload;
602
- logToFile("unknown event payload keys: " + keys);
603
- }
604
- const isSessionLifecycle =
605
- eventType === "session.created" ||
606
- eventType === "session.resumed" ||
607
- eventType === "session.compacted" ||
608
- eventType === "session.cleared" ||
609
- eventType === "session.updated";
610
- if (isSessionLifecycle) {
611
- // Keep OpenCode aligned with Claude/Cursor/Codex: session-start is
612
- // the canonical rehydrate path.
613
- await runHookScript("session-start", eventData ?? {});
614
- await refreshBootstrapCache(true);
615
- }
616
- if (eventType === "session.idle") {
617
- await runHookScript("stop-handoff", { loop_count: 0 });
618
- }
619
- },
620
- "experimental.chat.system.transform": (payload) => {
621
- const bootstrap = getBootstrap();
622
- if (!bootstrap) return payload;
623
- if (typeof payload === "string") {
624
- return payload.includes(BOOTSTRAP_MARKER) ? payload : \`\${payload}\\n\\n\${bootstrap}\`;
625
- }
626
- if (payload && typeof payload === "object" && typeof payload.system === "string") {
627
- if (payload.system.includes(BOOTSTRAP_MARKER)) return payload;
628
- return { ...payload, system: \`\${payload.system}\\n\\n\${bootstrap}\` };
629
- }
630
- return payload;
631
- }
632
- };
633
- }
634
- `;
635
- }
@@ -1 +0,0 @@
1
- export declare const REVIEW_PROMPTS: Record<string, string>;