cclaw-cli 7.7.1 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/README.md +210 -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 +90 -508
  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/install.d.ts +27 -15
  73. package/dist/install.js +230 -1342
  74. package/dist/knowledge-store.d.ts +19 -163
  75. package/dist/knowledge-store.js +56 -590
  76. package/dist/logger.d.ts +8 -3
  77. package/dist/logger.js +13 -4
  78. package/dist/orchestrator-routing.d.ts +29 -0
  79. package/dist/orchestrator-routing.js +156 -0
  80. package/dist/run-persistence.d.ts +7 -118
  81. package/dist/run-persistence.js +29 -845
  82. package/dist/runtime/run-hook.entry.d.ts +1 -3
  83. package/dist/runtime/run-hook.entry.js +19 -4
  84. package/dist/runtime/run-hook.mjs +13 -1024
  85. package/dist/types.d.ts +25 -261
  86. package/dist/types.js +8 -36
  87. package/package.json +6 -3
  88. package/dist/artifact-linter/brainstorm.d.ts +0 -2
  89. package/dist/artifact-linter/brainstorm.js +0 -353
  90. package/dist/artifact-linter/design.d.ts +0 -18
  91. package/dist/artifact-linter/design.js +0 -444
  92. package/dist/artifact-linter/findings-dedup.d.ts +0 -56
  93. package/dist/artifact-linter/findings-dedup.js +0 -232
  94. package/dist/artifact-linter/plan.d.ts +0 -2
  95. package/dist/artifact-linter/plan.js +0 -826
  96. package/dist/artifact-linter/review-army.d.ts +0 -49
  97. package/dist/artifact-linter/review-army.js +0 -520
  98. package/dist/artifact-linter/review.d.ts +0 -2
  99. package/dist/artifact-linter/review.js +0 -113
  100. package/dist/artifact-linter/scope.d.ts +0 -2
  101. package/dist/artifact-linter/scope.js +0 -158
  102. package/dist/artifact-linter/shared.d.ts +0 -637
  103. package/dist/artifact-linter/shared.js +0 -2163
  104. package/dist/artifact-linter/ship.d.ts +0 -2
  105. package/dist/artifact-linter/ship.js +0 -250
  106. package/dist/artifact-linter/spec.d.ts +0 -2
  107. package/dist/artifact-linter/spec.js +0 -176
  108. package/dist/artifact-linter/tdd.d.ts +0 -118
  109. package/dist/artifact-linter/tdd.js +0 -1404
  110. package/dist/artifact-linter.d.ts +0 -15
  111. package/dist/artifact-linter.js +0 -517
  112. package/dist/codex-feature-flag.d.ts +0 -58
  113. package/dist/codex-feature-flag.js +0 -193
  114. package/dist/content/closeout-guidance.d.ts +0 -14
  115. package/dist/content/closeout-guidance.js +0 -44
  116. package/dist/content/diff-command.d.ts +0 -1
  117. package/dist/content/diff-command.js +0 -43
  118. package/dist/content/harness-doc.d.ts +0 -1
  119. package/dist/content/harness-doc.js +0 -65
  120. package/dist/content/hook-events.d.ts +0 -9
  121. package/dist/content/hook-events.js +0 -23
  122. package/dist/content/hook-manifest.d.ts +0 -81
  123. package/dist/content/hook-manifest.js +0 -156
  124. package/dist/content/hooks.d.ts +0 -11
  125. package/dist/content/hooks.js +0 -1972
  126. package/dist/content/idea.d.ts +0 -60
  127. package/dist/content/idea.js +0 -416
  128. package/dist/content/language-policy.d.ts +0 -2
  129. package/dist/content/language-policy.js +0 -13
  130. package/dist/content/learnings.d.ts +0 -6
  131. package/dist/content/learnings.js +0 -141
  132. package/dist/content/observe.d.ts +0 -19
  133. package/dist/content/observe.js +0 -86
  134. package/dist/content/opencode-plugin.d.ts +0 -1
  135. package/dist/content/opencode-plugin.js +0 -635
  136. package/dist/content/review-prompts.d.ts +0 -1
  137. package/dist/content/review-prompts.js +0 -104
  138. package/dist/content/runtime-shared-snippets.d.ts +0 -8
  139. package/dist/content/runtime-shared-snippets.js +0 -80
  140. package/dist/content/session-hooks.d.ts +0 -7
  141. package/dist/content/session-hooks.js +0 -107
  142. package/dist/content/skills-elicitation.d.ts +0 -1
  143. package/dist/content/skills-elicitation.js +0 -167
  144. package/dist/content/stage-command.d.ts +0 -2
  145. package/dist/content/stage-command.js +0 -17
  146. package/dist/content/stage-schema.d.ts +0 -117
  147. package/dist/content/stage-schema.js +0 -955
  148. package/dist/content/stages/_lint-metadata/index.d.ts +0 -2
  149. package/dist/content/stages/_lint-metadata/index.js +0 -97
  150. package/dist/content/stages/brainstorm.d.ts +0 -2
  151. package/dist/content/stages/brainstorm.js +0 -184
  152. package/dist/content/stages/design.d.ts +0 -2
  153. package/dist/content/stages/design.js +0 -288
  154. package/dist/content/stages/index.d.ts +0 -8
  155. package/dist/content/stages/index.js +0 -11
  156. package/dist/content/stages/plan.d.ts +0 -2
  157. package/dist/content/stages/plan.js +0 -191
  158. package/dist/content/stages/review.d.ts +0 -2
  159. package/dist/content/stages/review.js +0 -240
  160. package/dist/content/stages/schema-types.d.ts +0 -203
  161. package/dist/content/stages/schema-types.js +0 -1
  162. package/dist/content/stages/scope.d.ts +0 -2
  163. package/dist/content/stages/scope.js +0 -254
  164. package/dist/content/stages/ship.d.ts +0 -2
  165. package/dist/content/stages/ship.js +0 -159
  166. package/dist/content/stages/spec.d.ts +0 -2
  167. package/dist/content/stages/spec.js +0 -170
  168. package/dist/content/stages/tdd.d.ts +0 -4
  169. package/dist/content/stages/tdd.js +0 -273
  170. package/dist/content/state-contracts.d.ts +0 -1
  171. package/dist/content/state-contracts.js +0 -63
  172. package/dist/content/status-command.d.ts +0 -4
  173. package/dist/content/status-command.js +0 -109
  174. package/dist/content/subagent-context-skills.d.ts +0 -4
  175. package/dist/content/subagent-context-skills.js +0 -279
  176. package/dist/content/subagents.d.ts +0 -3
  177. package/dist/content/subagents.js +0 -997
  178. package/dist/content/templates.d.ts +0 -26
  179. package/dist/content/templates.js +0 -1692
  180. package/dist/content/track-render-context.d.ts +0 -18
  181. package/dist/content/track-render-context.js +0 -53
  182. package/dist/content/tree-command.d.ts +0 -1
  183. package/dist/content/tree-command.js +0 -64
  184. package/dist/content/utility-skills.d.ts +0 -30
  185. package/dist/content/utility-skills.js +0 -160
  186. package/dist/content/view-command.d.ts +0 -2
  187. package/dist/content/view-command.js +0 -92
  188. package/dist/delegation.d.ts +0 -649
  189. package/dist/delegation.js +0 -1539
  190. package/dist/early-loop.d.ts +0 -70
  191. package/dist/early-loop.js +0 -302
  192. package/dist/execution-topology.d.ts +0 -44
  193. package/dist/execution-topology.js +0 -95
  194. package/dist/gate-evidence.d.ts +0 -85
  195. package/dist/gate-evidence.js +0 -631
  196. package/dist/harness-adapters.d.ts +0 -151
  197. package/dist/harness-adapters.js +0 -756
  198. package/dist/harness-selection.d.ts +0 -31
  199. package/dist/harness-selection.js +0 -214
  200. package/dist/hook-schema.d.ts +0 -6
  201. package/dist/hook-schema.js +0 -114
  202. package/dist/hook-schemas/claude-hooks.v1.json +0 -10
  203. package/dist/hook-schemas/codex-hooks.v1.json +0 -10
  204. package/dist/hook-schemas/cursor-hooks.v1.json +0 -13
  205. package/dist/init-detect.d.ts +0 -2
  206. package/dist/init-detect.js +0 -50
  207. package/dist/internal/advance-stage/advance.d.ts +0 -89
  208. package/dist/internal/advance-stage/advance.js +0 -655
  209. package/dist/internal/advance-stage/cancel-run.d.ts +0 -8
  210. package/dist/internal/advance-stage/cancel-run.js +0 -19
  211. package/dist/internal/advance-stage/flow-state-coercion.d.ts +0 -3
  212. package/dist/internal/advance-stage/flow-state-coercion.js +0 -81
  213. package/dist/internal/advance-stage/helpers.d.ts +0 -14
  214. package/dist/internal/advance-stage/helpers.js +0 -145
  215. package/dist/internal/advance-stage/hook.d.ts +0 -8
  216. package/dist/internal/advance-stage/hook.js +0 -40
  217. package/dist/internal/advance-stage/parsers.d.ts +0 -72
  218. package/dist/internal/advance-stage/parsers.js +0 -357
  219. package/dist/internal/advance-stage/proactive-delegation-trace.d.ts +0 -24
  220. package/dist/internal/advance-stage/proactive-delegation-trace.js +0 -56
  221. package/dist/internal/advance-stage/review-loop.d.ts +0 -16
  222. package/dist/internal/advance-stage/review-loop.js +0 -199
  223. package/dist/internal/advance-stage/rewind.d.ts +0 -14
  224. package/dist/internal/advance-stage/rewind.js +0 -108
  225. package/dist/internal/advance-stage/start-flow.d.ts +0 -13
  226. package/dist/internal/advance-stage/start-flow.js +0 -241
  227. package/dist/internal/advance-stage/verify.d.ts +0 -21
  228. package/dist/internal/advance-stage/verify.js +0 -185
  229. package/dist/internal/advance-stage.d.ts +0 -7
  230. package/dist/internal/advance-stage.js +0 -138
  231. package/dist/internal/cohesion-contract-stub.d.ts +0 -24
  232. package/dist/internal/cohesion-contract-stub.js +0 -148
  233. package/dist/internal/compound-readiness.d.ts +0 -23
  234. package/dist/internal/compound-readiness.js +0 -102
  235. package/dist/internal/detect-public-api-changes.d.ts +0 -5
  236. package/dist/internal/detect-public-api-changes.js +0 -45
  237. package/dist/internal/detect-supply-chain-changes.d.ts +0 -6
  238. package/dist/internal/detect-supply-chain-changes.js +0 -138
  239. package/dist/internal/early-loop-status.d.ts +0 -7
  240. package/dist/internal/early-loop-status.js +0 -93
  241. package/dist/internal/envelope-validate.d.ts +0 -7
  242. package/dist/internal/envelope-validate.js +0 -66
  243. package/dist/internal/flow-state-repair.d.ts +0 -20
  244. package/dist/internal/flow-state-repair.js +0 -104
  245. package/dist/internal/plan-split-waves.d.ts +0 -190
  246. package/dist/internal/plan-split-waves.js +0 -764
  247. package/dist/internal/runtime-integrity.d.ts +0 -7
  248. package/dist/internal/runtime-integrity.js +0 -268
  249. package/dist/internal/slice-commit.d.ts +0 -7
  250. package/dist/internal/slice-commit.js +0 -619
  251. package/dist/internal/tdd-loop-status.d.ts +0 -14
  252. package/dist/internal/tdd-loop-status.js +0 -68
  253. package/dist/internal/tdd-red-evidence.d.ts +0 -7
  254. package/dist/internal/tdd-red-evidence.js +0 -153
  255. package/dist/internal/waiver-grant.d.ts +0 -62
  256. package/dist/internal/waiver-grant.js +0 -294
  257. package/dist/internal/wave-status.d.ts +0 -74
  258. package/dist/internal/wave-status.js +0 -506
  259. package/dist/managed-resources.d.ts +0 -53
  260. package/dist/managed-resources.js +0 -313
  261. package/dist/policy.d.ts +0 -10
  262. package/dist/policy.js +0 -167
  263. package/dist/retro-gate.d.ts +0 -9
  264. package/dist/retro-gate.js +0 -47
  265. package/dist/run-archive.d.ts +0 -61
  266. package/dist/run-archive.js +0 -391
  267. package/dist/runs.d.ts +0 -2
  268. package/dist/runs.js +0 -2
  269. package/dist/stack-detection.d.ts +0 -116
  270. package/dist/stack-detection.js +0 -489
  271. package/dist/streaming/event-stream.d.ts +0 -31
  272. package/dist/streaming/event-stream.js +0 -114
  273. package/dist/tdd-cycle.d.ts +0 -107
  274. package/dist/tdd-cycle.js +0 -289
  275. package/dist/tdd-verification-evidence.d.ts +0 -17
  276. package/dist/tdd-verification-evidence.js +0 -122
  277. package/dist/track-heuristics.d.ts +0 -27
  278. package/dist/track-heuristics.js +0 -154
  279. package/dist/util/slice-id.d.ts +0 -58
  280. package/dist/util/slice-id.js +0 -89
  281. package/dist/worktree-manager.d.ts +0 -20
  282. 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>;