cclaw-cli 0.48.23 → 0.48.25

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.
@@ -124,7 +124,8 @@ Reference docs for \`cclaw doctor\` checks.
124
124
 
125
125
  Earlier releases relied on \`bash\` to execute generated shell hooks and on
126
126
  \`python3\`/\`jq\` as JSON fallback parsers. Node-only mode removes both: hooks
127
- dispatch through \`node .cclaw/hooks/run-hook.mjs <hook-name>\`, so these tools
127
+ dispatch through \`.cclaw/hooks/run-hook.cmd <hook-name>\` (which forwards to
128
+ Node), so these tools
128
129
  are no longer part of the supported runtime contract.
129
130
 
130
131
  ## Typical fixes
@@ -1,4 +1,5 @@
1
1
  export declare function stageCompleteScript(): string;
2
+ export declare function runHookCmdScript(): string;
2
3
  export { claudeHooksJsonWithObservation as claudeHooksJson } from "./observe.js";
3
4
  export { cursorHooksJsonWithObservation as cursorHooksJson } from "./observe.js";
4
5
  export { codexHooksJsonWithObservation as codexHooksJson } from "./observe.js";
@@ -120,6 +120,36 @@ async function main() {
120
120
  void main();
121
121
  `;
122
122
  }
123
+ export function runHookCmdScript() {
124
+ return `: << 'CMDBLOCK'
125
+ @echo off
126
+ REM Cross-platform wrapper for cclaw Node hook runtime.
127
+ REM Windows executes this batch block; Unix shells treat it as a heredoc comment.
128
+ if "%~1"=="" (
129
+ echo [cclaw] run-hook.cmd: missing hook name >&2
130
+ exit /b 1
131
+ )
132
+ set "HOOK_DIR=%~dp0"
133
+ set "RUNTIME=%HOOK_DIR%run-hook.mjs"
134
+ where node >nul 2>nul
135
+ if %ERRORLEVEL% neq 0 (
136
+ REM Best-effort: missing node should not block harness execution loops.
137
+ exit /b 0
138
+ )
139
+ node "%RUNTIME%" %*
140
+ exit /b %ERRORLEVEL%
141
+ CMDBLOCK
142
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
143
+ if [ "$#" -lt 1 ]; then
144
+ echo "[cclaw] run-hook.cmd: missing hook name" >&2
145
+ exit 1
146
+ fi
147
+ if ! command -v node >/dev/null 2>&1; then
148
+ exit 0
149
+ fi
150
+ exec node "\${SCRIPT_DIR}/run-hook.mjs" "$@"
151
+ `;
152
+ }
123
153
  export { claudeHooksJsonWithObservation as claudeHooksJson } from "./observe.js";
124
154
  export { cursorHooksJsonWithObservation as cursorHooksJson } from "./observe.js";
125
155
  export { codexHooksJsonWithObservation as codexHooksJson } from "./observe.js";
@@ -40,6 +40,15 @@ If the current stage is ambiguous because \`flow-state.json\` is missing
40
40
  or corrupt, stop and route through \`/cc\` before any substantive
41
41
  response.
42
42
 
43
+ ## Red Flags (stop and re-route)
44
+
45
+ If you think any of these, stop and follow the routing flow:
46
+
47
+ - "This looks simple, I can skip the stage." -> No. Route through \`/cc\`.
48
+ - "I can answer from memory without loading the active stage skill." -> No. Load the skill first.
49
+ - "Hook guard warned, but I can ignore it." -> No. Resolve the warning before continuing.
50
+ - "I'll edit \`.cclaw/state\` directly to move faster." -> No. Use managed commands only.
51
+
43
52
  ## Routing flow
44
53
 
45
54
  \`\`\`
@@ -72,6 +81,12 @@ Before stage work:
72
81
  2. If active stage exists, continue with \`/cc\` or \`/cc-next\`.
73
82
  3. Do not jump directly to stage-specific commands.
74
83
 
84
+ ## Platform reliability notes
85
+
86
+ - Managed hook dispatch uses \`.cclaw/hooks/run-hook.cmd\` (cross-platform wrapper).
87
+ - If hooks fail due missing runtime deps (for example \`node\` not on \`PATH\`), run \`cclaw doctor\` before continuing.
88
+ - Prefer cross-platform commands in artifacts/examples (\`npm test\`, \`pnpm test\`, \`python -m pytest\`, etc.) over shell-specific aliases whenever possible.
89
+
75
90
  ## Stage quick map
76
91
 
77
92
  brainstorm -> scope -> design -> spec -> plan -> tdd -> review -> ship
@@ -307,10 +307,12 @@ async function readStdin() {
307
307
  });
308
308
  }
309
309
 
310
- async function runCclawInternal(root, args) {
310
+ async function runCclawInternal(root, args, options = {}) {
311
311
  return await new Promise((resolve) => {
312
312
  const isWindows = process.platform === "win32";
313
+ const captureStdout = options && options.captureStdout === true;
313
314
  let settled = false;
315
+ let stdout = "";
314
316
  let stderr = "";
315
317
  const finalize = (value) => {
316
318
  if (settled) return;
@@ -325,18 +327,27 @@ async function runCclawInternal(root, args) {
325
327
  {
326
328
  cwd: root,
327
329
  env: process.env,
328
- stdio: ["ignore", "ignore", "pipe"]
330
+ stdio: ["ignore", captureStdout ? "pipe" : "ignore", "pipe"]
329
331
  }
330
332
  );
331
333
  } catch (error) {
332
334
  const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
333
335
  finalize({
334
336
  code: 1,
337
+ stdout,
335
338
  stderr,
336
339
  missingBinary: code === "ENOENT" || (isWindows && code === "EINVAL")
337
340
  });
338
341
  return;
339
342
  }
343
+ if (captureStdout) {
344
+ child.stdout?.on("data", (chunk) => {
345
+ stdout += String(chunk ?? "");
346
+ if (stdout.length > 16000) {
347
+ stdout = stdout.slice(-16000);
348
+ }
349
+ });
350
+ }
340
351
  child.stderr?.on("data", (chunk) => {
341
352
  stderr += String(chunk ?? "");
342
353
  if (stderr.length > 8000) {
@@ -347,6 +358,7 @@ async function runCclawInternal(root, args) {
347
358
  const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
348
359
  finalize({
349
360
  code: 1,
361
+ stdout,
350
362
  stderr,
351
363
  missingBinary: code === "ENOENT" || (isWindows && code === "EINVAL")
352
364
  });
@@ -355,6 +367,7 @@ async function runCclawInternal(root, args) {
355
367
  if (signal) {
356
368
  finalize({
357
369
  code: 1,
370
+ stdout,
358
371
  stderr,
359
372
  missingBinary: false
360
373
  });
@@ -366,6 +379,7 @@ async function runCclawInternal(root, args) {
366
379
  : false;
367
380
  finalize({
368
381
  code: typeof code === "number" ? code : 1,
382
+ stdout,
369
383
  stderr,
370
384
  missingBinary
371
385
  });
@@ -380,6 +394,32 @@ function detectHarness(env) {
380
394
  return "codex";
381
395
  }
382
396
 
397
+ function hookEventNameForOutput(hookName) {
398
+ if (hookName === "session-start") return "SessionStart";
399
+ if (hookName === "prompt-guard") return "PreToolUse";
400
+ if (hookName === "workflow-guard") return "PreToolUse";
401
+ if (hookName === "context-monitor") return "PostToolUse";
402
+ if (hookName === "stop-checkpoint") return "Stop";
403
+ if (hookName === "pre-compact") return "PreCompact";
404
+ if (hookName === "verify-current-state") return "UserPromptSubmit";
405
+ return "SessionStart";
406
+ }
407
+
408
+ function emitAdvisoryContext(runtime, hookName, note) {
409
+ const normalized = normalizeText(note);
410
+ if (normalized.length === 0) return;
411
+ if (runtime.harness === "claude" || runtime.harness === "codex") {
412
+ runtime.writeJson({
413
+ hookSpecificOutput: {
414
+ hookEventName: hookEventNameForOutput(hookName),
415
+ additionalContext: normalized
416
+ }
417
+ });
418
+ return;
419
+ }
420
+ runtime.writeJson({ additional_context: normalized });
421
+ }
422
+
383
423
  async function detectRoot(env) {
384
424
  const candidates = [
385
425
  env.CCLAW_PROJECT_ROOT,
@@ -976,21 +1016,55 @@ async function handleSessionStart(runtime) {
976
1016
  // where lifting becomes relevant; earlier stages update the file silently.
977
1017
  let compoundReadinessLine = "";
978
1018
  try {
979
- const archivedRunsCount = await countArchivedRunsInline(runtime.root);
980
- const readiness = await computeCompoundReadinessInline(runtime.root, {
981
- prereadRaw: knowledgeRaw,
982
- ...(typeof archivedRunsCount === "number" ? { archivedRunsCount } : {})
983
- });
984
- await writeJsonFile(path.join(stateDir, "compound-readiness.json"), readiness);
1019
+ let readiness = null;
1020
+ const internalReadiness = await runCclawInternal(
1021
+ runtime.root,
1022
+ ["compound-readiness", "--json"],
1023
+ { captureStdout: true }
1024
+ );
1025
+ if (internalReadiness.code === 0 && internalReadiness.stdout.trim().length > 0) {
1026
+ try {
1027
+ const parsed = JSON.parse(internalReadiness.stdout);
1028
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1029
+ readiness = parsed;
1030
+ }
1031
+ } catch {
1032
+ readiness = null;
1033
+ }
1034
+ }
1035
+ if (!readiness) {
1036
+ const archivedRunsCount = await countArchivedRunsInline(runtime.root);
1037
+ readiness = await computeCompoundReadinessInline(runtime.root, {
1038
+ prereadRaw: knowledgeRaw,
1039
+ ...(typeof archivedRunsCount === "number" ? { archivedRunsCount } : {})
1040
+ });
1041
+ await writeJsonFile(path.join(stateDir, "compound-readiness.json"), readiness);
1042
+ }
1043
+ const readinessObj = toObject(readiness) || {};
1044
+ const ready = Array.isArray(readinessObj.ready) ? readinessObj.ready : [];
1045
+ const readyCount =
1046
+ typeof readinessObj.readyCount === "number" && Number.isFinite(readinessObj.readyCount)
1047
+ ? Math.trunc(readinessObj.readyCount)
1048
+ : ready.length;
1049
+ const clusterCount =
1050
+ typeof readinessObj.clusterCount === "number" && Number.isFinite(readinessObj.clusterCount)
1051
+ ? Math.trunc(readinessObj.clusterCount)
1052
+ : 0;
1053
+ const threshold =
1054
+ typeof readinessObj.threshold === "number" && Number.isFinite(readinessObj.threshold)
1055
+ ? Math.trunc(readinessObj.threshold)
1056
+ : COMPOUND_RECURRENCE_THRESHOLD;
985
1057
  if (state.currentStage === "review" || state.currentStage === "ship") {
986
- if (readiness.readyCount === 0) {
1058
+ if (readyCount === 0) {
987
1059
  compoundReadinessLine = "Compound readiness: no candidates (clusters=" +
988
- String(readiness.clusterCount) + ", threshold=" + String(readiness.threshold) + ")";
1060
+ String(clusterCount) + ", threshold=" + String(threshold) + ")";
989
1061
  } else {
990
- const critical = readiness.ready.filter((entry) => entry.severity === "critical").length;
1062
+ const critical = ready.filter(
1063
+ (entry) => entry && typeof entry === "object" && entry.severity === "critical"
1064
+ ).length;
991
1065
  const criticalSuffix = critical > 0 ? " (critical=" + String(critical) + ")" : "";
992
- compoundReadinessLine = "Compound readiness: clusters=" + String(readiness.clusterCount) +
993
- ", ready=" + String(readiness.readyCount) + criticalSuffix;
1066
+ compoundReadinessLine = "Compound readiness: clusters=" + String(clusterCount) +
1067
+ ", ready=" + String(readyCount) + criticalSuffix;
994
1068
  }
995
1069
  }
996
1070
  } catch (err) {
@@ -1342,7 +1416,9 @@ async function handlePromptGuard(runtime) {
1342
1416
  const reasons = [];
1343
1417
 
1344
1418
  if (/^(write|edit|multiedit|multi_edit|delete|applypatch|runcommand|shell|terminal|execcommand)$/u.test(toolLower)) {
1345
- if (/\\.cclaw\\/(state|artifacts|hooks|skills|commands|agents|runs|knowledge)/u.test(payloadLower)) {
1419
+ // Artifacts, runs, and knowledge writes are part of normal stage flow.
1420
+ // Guard only managed internals that should be mutated via installer/CLI.
1421
+ if (/\\.cclaw\\/(state|hooks|skills|commands|agents)/u.test(payloadLower)) {
1346
1422
  reasons.push("write_to_cclaw_runtime");
1347
1423
  }
1348
1424
  }
@@ -1356,7 +1432,7 @@ async function handlePromptGuard(runtime) {
1356
1432
  RUNTIME_ROOT +
1357
1433
  " runtime (" +
1358
1434
  reasons.join(",") +
1359
- "). Prefer installer commands or explicit confirmation before mutating runtime internals.";
1435
+ "). Prefer installer commands before mutating managed runtime internals (.cclaw/state|hooks|skills|commands|agents).";
1360
1436
  await appendJsonLine(guardLog, {
1361
1437
  ts: new Date().toISOString(),
1362
1438
  harness: runtime.harness,
@@ -1364,6 +1440,8 @@ async function handlePromptGuard(runtime) {
1364
1440
  reasons,
1365
1441
  note
1366
1442
  });
1443
+ const advisoryNote = mode === "strict" ? note + " Blocked by strict mode." : note;
1444
+ emitAdvisoryContext(runtime, "prompt-guard", advisoryNote);
1367
1445
  if (mode === "strict") {
1368
1446
  process.stderr.write("[cclaw] " + note + " (blocked by strict mode)\\n");
1369
1447
  return 1;
@@ -1877,9 +1955,11 @@ async function handleWorkflowGuard(runtime) {
1877
1955
  }
1878
1956
 
1879
1957
  if (shouldBlock) {
1958
+ emitAdvisoryContext(runtime, "workflow-guard", note + " Blocked by workflow guard.");
1880
1959
  process.stderr.write("[cclaw] " + note + " (blocked by workflow guard)\\n");
1881
1960
  return 1;
1882
1961
  }
1962
+ emitAdvisoryContext(runtime, "workflow-guard", note);
1883
1963
  process.stderr.write("[cclaw] " + note + "\\n");
1884
1964
  }
1885
1965
 
@@ -1978,6 +2058,7 @@ async function handleContextMonitor(runtime) {
1978
2058
  remainingPercent,
1979
2059
  note
1980
2060
  });
2061
+ emitAdvisoryContext(runtime, "context-monitor", note);
1981
2062
  process.stderr.write("[cclaw] " + note + "\\n");
1982
2063
  nextAdvisoryBand = band;
1983
2064
  nextAdvisoryAt = now.toISOString();
@@ -1998,10 +2079,24 @@ async function handleVerifyCurrentState(runtime) {
1998
2079
  const mode = resolveStrictness();
1999
2080
  const result = await runCclawInternal(runtime.root, ["verify-current-state", "--quiet"]);
2000
2081
  if (result.missingBinary) {
2082
+ emitAdvisoryContext(
2083
+ runtime,
2084
+ "verify-current-state",
2085
+ "Cclaw verify-current-state requires cclaw binary on PATH."
2086
+ );
2001
2087
  process.stderr.write("[cclaw] hook: cclaw binary is required for verify-current-state\\n");
2002
2088
  return 1;
2003
2089
  }
2004
2090
  if (mode === "strict") {
2091
+ if (result.code !== 0) {
2092
+ emitAdvisoryContext(
2093
+ runtime,
2094
+ "verify-current-state",
2095
+ result.stderr.trim().length > 0
2096
+ ? result.stderr.trim()
2097
+ : "Cclaw verify-current-state failed in strict mode."
2098
+ );
2099
+ }
2005
2100
  if (result.code !== 0 && result.stderr.trim().length > 0) {
2006
2101
  process.stderr.write(result.stderr);
2007
2102
  }
@@ -1,11 +1,9 @@
1
1
  import { RUNTIME_ROOT } from "../constants.js";
2
2
  import { HOOK_MANIFEST, groupBindingsByEvent } from "./hook-manifest.js";
3
3
  function hookDispatcherCommand(hookName) {
4
- // RUNTIME_ROOT is a relative path (".cclaw") that currently contains no
5
- // whitespace, so quoting is unnecessary inside the JSON-encoded command
6
- // string. If RUNTIME_ROOT ever becomes configurable, wrap the path with
7
- // JSON.stringify to survive spaces.
8
- return `node ${RUNTIME_ROOT}/hooks/run-hook.mjs ${hookName}`;
4
+ // Dispatch through the polyglot .cmd wrapper so Windows harnesses can run
5
+ // hooks even when command execution happens under CMD-style shells.
6
+ return `${RUNTIME_ROOT}/hooks/run-hook.cmd ${hookName}`;
9
7
  }
10
8
  /**
11
9
  * Claude / Codex share the same outer envelope: each event is an
@@ -297,6 +297,34 @@ After T-3 REFACTOR, before declaring Batch 1 done:
297
297
  export function stageSkillFolder(stage) {
298
298
  return STAGE_TO_SKILL_FOLDER[stage];
299
299
  }
300
+ function normalizedGuidanceKey(value) {
301
+ return value
302
+ .replace(/`[^`]+`/gu, " ")
303
+ .replace(/[*_]/gu, " ")
304
+ .replace(/[^a-z0-9]+/giu, " ")
305
+ .replace(/\s+/gu, " ")
306
+ .trim()
307
+ .toLowerCase();
308
+ }
309
+ function dedupeGuidance(items, blockedBy) {
310
+ const blocked = new Set(blockedBy
311
+ .map((item) => normalizedGuidanceKey(item))
312
+ .filter((item) => item.length > 0));
313
+ const seen = new Set();
314
+ const result = [];
315
+ for (const item of items) {
316
+ const key = normalizedGuidanceKey(item);
317
+ if (key.length === 0)
318
+ continue;
319
+ if (blocked.has(key))
320
+ continue;
321
+ if (seen.has(key))
322
+ continue;
323
+ seen.add(key);
324
+ result.push(item);
325
+ }
326
+ return result;
327
+ }
300
328
  export function stageSkillMarkdown(stage, track = "standard") {
301
329
  const schema = stageSchema(stage, track);
302
330
  const gateList = schema.requiredGates
@@ -308,6 +336,11 @@ export function stageSkillMarkdown(stage, track = "standard") {
308
336
  const checklistItems = schema.checklist
309
337
  .map((item, i) => `${i + 1}. ${item}`)
310
338
  .join("\n");
339
+ const interactionFocus = dedupeGuidance(schema.interactionProtocol, [...schema.checklist, ...schema.process]).slice(0, 5);
340
+ const processSummary = dedupeGuidance(schema.process, schema.checklist).slice(0, 5);
341
+ const processNote = schema.process.length > processSummary.length
342
+ ? `- Follow the Checklist above for remaining execution detail (+${schema.process.length - processSummary.length} condensed step${schema.process.length - processSummary.length === 1 ? "" : "s"}).`
343
+ : "";
311
344
  const stageRefs = stageSpecificSeeAlso(stage);
312
345
  return `---
313
346
  name: ${schema.skillName}
@@ -360,7 +393,7 @@ ${stageDomainExamples(stage)}
360
393
  ${stageExamples(stage)}
361
394
 
362
395
  ## Interaction Protocol
363
- ${schema.interactionProtocol.map((item, i) => `${i + 1}. ${item}`).join("\n")}
396
+ ${interactionFocus.length > 0 ? interactionFocus.map((item, i) => `${i + 1}. ${item}`).join("\n") : "- Keep communication concise and decision-focused; rely on the Checklist for execution order."}
364
397
 
365
398
  Shared decision/ask-user protocol:
366
399
  \`${DECISION_PROTOCOL_PATH}\`
@@ -373,7 +406,8 @@ ${gateList}
373
406
  ${evidenceList}
374
407
 
375
408
  ## Process
376
- ${schema.process.map((item, i) => `${i + 1}. ${item}`).join("\n")}
409
+ ${processSummary.length > 0 ? processSummary.map((item, i) => `${i + 1}. ${item}`).join("\n") : "1. Execute the Checklist in order.\n2. Satisfy every required gate.\n3. Complete verification before stage closeout."}
410
+ ${processNote.length > 0 ? `\n${processNote}` : ""}
377
411
 
378
412
  ${reviewSectionsBlock(stage, track)}
379
413
  ${verificationBlock(stage)}
@@ -85,7 +85,7 @@ const RULES = [
85
85
  }
86
86
  },
87
87
  {
88
- test: /^(meta_skill:|protocol:|stage_skill:|context_mode:|reference:)/,
88
+ test: /^(meta_skill:|protocol:|stage_skill:|context_mode:)/,
89
89
  metadata: {
90
90
  severity: "error",
91
91
  summary: "Routing skill and protocol integrity check.",
@@ -93,6 +93,21 @@ const RULES = [
93
93
  docRef: ref("harness-and-routing.md")
94
94
  }
95
95
  },
96
+ {
97
+ // `reference:*` checks (flow-map.md and similar overview documents)
98
+ // are useful to detect drift from the generated baseline, but they
99
+ // document the surface rather than gate it. A missing section here
100
+ // means the map is out of date, not that a runtime contract is
101
+ // broken — so they report as a warning instead of hard-failing
102
+ // doctor / CI. `cclaw sync` rewrites the file.
103
+ test: /^reference:/,
104
+ metadata: {
105
+ severity: "warning",
106
+ summary: "Reference/overview doc integrity (non-blocking).",
107
+ fix: "Run `cclaw sync` to regenerate the reference doc from the canonical source.",
108
+ docRef: ref("harness-and-routing.md")
109
+ }
110
+ },
96
111
  {
97
112
  test: /^delegation:/,
98
113
  metadata: {
package/dist/doctor.js CHANGED
@@ -674,6 +674,7 @@ export async function doctorChecks(projectRoot, options = {}) {
674
674
  // Hook scripts
675
675
  for (const script of [
676
676
  "run-hook.mjs",
677
+ "run-hook.cmd",
677
678
  "stage-complete.mjs",
678
679
  "opencode-plugin.mjs"
679
680
  ]) {
@@ -1021,7 +1022,7 @@ export async function doctorChecks(projectRoot, options = {}) {
1021
1022
  if (!(await exists(candidate)))
1022
1023
  continue;
1023
1024
  const content = await fs.readFile(candidate, "utf8");
1024
- if (/run-hook\.cmd|bash\s+\.cclaw\/hooks\//u.test(content)) {
1025
+ if (/bash\s+\.cclaw\/hooks\/|\.cclaw\/hooks\/(?:session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor)\.sh/u.test(content)) {
1025
1026
  legacyDispatchFiles.push(path.relative(projectRoot, candidate));
1026
1027
  }
1027
1028
  }
@@ -1029,7 +1030,7 @@ export async function doctorChecks(projectRoot, options = {}) {
1029
1030
  name: "warning:windows:hook_dispatch_node_only",
1030
1031
  ok: legacyDispatchFiles.length === 0,
1031
1032
  details: legacyDispatchFiles.length === 0
1032
- ? "hook configs use node-dispatched .cclaw/hooks/run-hook.mjs commands"
1033
+ ? "hook configs use managed .cclaw/hooks/run-hook.cmd dispatch commands"
1033
1034
  : `warning: legacy shell hook dispatch remains in ${legacyDispatchFiles.join(", ")}`
1034
1035
  });
1035
1036
  // Knowledge store exists (canonical JSONL, no markdown mirror)
package/dist/install.js CHANGED
@@ -24,7 +24,7 @@ import { rewindCommandContract, rewindCommandSkillMarkdown } from "./content/rew
24
24
  import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
25
25
  import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
26
26
  import { ironLawRuntimeDocument, ironLawsSkillMarkdown } from "./content/iron-laws.js";
27
- import { stageCompleteScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
27
+ import { stageCompleteScript, runHookCmdScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
28
28
  import { nodeHookRuntimeScript } from "./content/node-hooks.js";
29
29
  import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
30
30
  import { decisionProtocolMarkdown, completionProtocolMarkdown, ethosProtocolMarkdown } from "./content/protocols.js";
@@ -734,12 +734,14 @@ async function writeHooks(projectRoot, config) {
734
734
  tddProductionPathPatterns: config.tdd?.productionPathPatterns,
735
735
  compoundRecurrenceThreshold: config.compound?.recurrenceThreshold
736
736
  }));
737
+ await writeFileSafe(path.join(hooksDir, "run-hook.cmd"), runHookCmdScript());
737
738
  const opencodePluginSource = opencodePluginJs();
738
739
  await writeFileSafe(path.join(hooksDir, "opencode-plugin.mjs"), opencodePluginSource);
739
740
  try {
740
741
  for (const script of [
741
742
  "stage-complete.mjs",
742
743
  "run-hook.mjs",
744
+ "run-hook.cmd",
743
745
  "opencode-plugin.mjs"
744
746
  ]) {
745
747
  await fs.chmod(path.join(hooksDir, script), 0o755);
@@ -1440,7 +1442,7 @@ function isManagedRuntimeHookCommand(command) {
1440
1442
  // (e.g. `node .cclaw\hooks\run-hook.mjs ...`) still round-trip through
1441
1443
  // sync without being duplicated alongside freshly generated entries.
1442
1444
  const normalized = command.trim().replace(/\s+/gu, " ").replace(/\\/gu, "/");
1443
- if (/(^|\s)(?:node\s+)?(?:"|')?(?:\.\/)?\.cclaw\/hooks\/run-hook\.mjs(?:"|')?\s+(?:session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor|verify-current-state)(?:\s|$)/u.test(normalized)) {
1445
+ if (/(^|\s)(?:node\s+)?(?:"|')?(?:\.\/)?\.cclaw\/hooks\/run-hook\.(?:mjs|cmd)(?:"|')?\s+(?:session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor|verify-current-state)(?:\s|$)/u.test(normalized)) {
1444
1446
  return true;
1445
1447
  }
1446
1448
  // Codex UserPromptSubmit non-blocking state nudge.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.48.23",
3
+ "version": "0.48.25",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {