cclaw-cli 0.48.7 → 0.48.9

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.
@@ -1660,7 +1660,7 @@ exit 0
1660
1660
  * Updated hooks.json generators with PreToolUse/PostToolUse observation.
1661
1661
  */
1662
1662
  function hookDispatcherCommand(scriptName) {
1663
- return `bash ${RUNTIME_ROOT}/hooks/run-hook.cmd ${scriptName}`;
1663
+ return `node ${RUNTIME_ROOT}/hooks/run-hook.mjs ${scriptName}`;
1664
1664
  }
1665
1665
  export function claudeHooksJsonWithObservation() {
1666
1666
  return JSON.stringify({
@@ -1799,7 +1799,7 @@ export function codexHooksJsonWithObservation() {
1799
1799
  command: hookDispatcherCommand("workflow-guard.sh")
1800
1800
  }, {
1801
1801
  type: "command",
1802
- command: "bash -lc 'if ! command -v cclaw >/dev/null 2>&1; then echo \"[cclaw] codex hook: cclaw binary is required for verify-current-state\" >&2; exit 1; fi; MODE=\"${CCLAW_WORKFLOW_GUARD_MODE:-advisory}\"; if [ \"$MODE\" = \"strict\" ]; then cclaw internal verify-current-state --quiet >/dev/null; else cclaw internal verify-current-state --quiet >/dev/null || true; fi'"
1802
+ command: hookDispatcherCommand("verify-current-state")
1803
1803
  }]
1804
1804
  }],
1805
1805
  PreToolUse: [{
@@ -272,9 +272,9 @@ export default function cclawPlugin(ctx) {
272
272
  });
273
273
  }
274
274
 
275
- async function runHookScript(scriptFileName, payload = {}) {
275
+ async function runHookScript(hookName, payload = {}) {
276
276
  const { spawn } = await import("node:child_process");
277
- const scriptPath = join(root, "${RUNTIME_ROOT}/hooks/" + scriptFileName);
277
+ const hookRuntimePath = join(root, "${RUNTIME_ROOT}/hooks/run-hook.mjs");
278
278
  const input = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
279
279
  return scheduleHookTask(() => new Promise((resolve) => {
280
280
  let stderr = "";
@@ -287,7 +287,7 @@ export default function cclawPlugin(ctx) {
287
287
 
288
288
  let child;
289
289
  try {
290
- child = spawn("bash", [scriptPath], {
290
+ child = spawn(process.execPath, [hookRuntimePath, hookName], {
291
291
  cwd: root,
292
292
  stdio: ["pipe", "ignore", "pipe"]
293
293
  });
@@ -299,7 +299,7 @@ export default function cclawPlugin(ctx) {
299
299
  const timer = setTimeout(() => {
300
300
  child.kill("SIGKILL");
301
301
  if (stderr.length > 0) {
302
- console.error("[cclaw] opencode hook timeout: " + scriptFileName + " stderr=" + stderr.slice(-1200));
302
+ console.error("[cclaw] opencode hook timeout: " + hookName + " stderr=" + stderr.slice(-1200));
303
303
  }
304
304
  finish(false);
305
305
  }, 20_000);
@@ -318,7 +318,7 @@ export default function cclawPlugin(ctx) {
318
318
  clearTimeout(timer);
319
319
  const ok = code === 0;
320
320
  if (!ok && stderr.length > 0) {
321
- console.error("[cclaw] opencode hook failed: " + scriptFileName + " stderr=" + stderr.slice(-1200));
321
+ console.error("[cclaw] opencode hook failed: " + hookName + " stderr=" + stderr.slice(-1200));
322
322
  }
323
323
  finish(ok);
324
324
  });
package/dist/doctor.js CHANGED
@@ -879,7 +879,7 @@ export async function doctorChecks(projectRoot, options = {}) {
879
879
  const codexWiringOk = codexSessionCmds.some((cmd) => cmd.includes("session-start.sh")) &&
880
880
  codexUserPromptCmds.some((cmd) => cmd.includes("prompt-guard.sh")) &&
881
881
  codexUserPromptCmds.some((cmd) => cmd.includes("workflow-guard.sh")) &&
882
- codexUserPromptCmds.some((cmd) => cmd.includes("verify-current-state --quiet")) &&
882
+ codexUserPromptCmds.some((cmd) => cmd.includes("verify-current-state")) &&
883
883
  codexPreCmds.some((cmd) => cmd.includes("prompt-guard.sh")) &&
884
884
  codexPreCmds.some((cmd) => cmd.includes("workflow-guard.sh")) &&
885
885
  codexPostCmds.some((cmd) => cmd.includes("context-monitor.sh")) &&
package/dist/install.js CHANGED
@@ -26,6 +26,7 @@ import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
26
26
  import { ironLawRuntimeDocument, ironLawsSkillMarkdown } from "./content/iron-laws.js";
27
27
  import { hookLibScript, sessionStartScript, stopCheckpointScript, runHookDispatcherScript, stageCompleteScript, preCompactScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
28
28
  import { contextMonitorScript, promptGuardScript, workflowGuardScript } from "./content/observe.js";
29
+ import { nodeHookRuntimeScript } from "./content/node-hooks.js";
29
30
  import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
30
31
  import { decisionProtocolMarkdown, completionProtocolMarkdown, ethosProtocolMarkdown } from "./content/protocols.js";
31
32
  import { ARTIFACT_TEMPLATES, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
@@ -646,6 +647,13 @@ async function writeHooks(projectRoot, config) {
646
647
  tddProductionPathPatterns: config.tdd?.productionPathPatterns
647
648
  }));
648
649
  await writeFileSafe(path.join(hooksDir, "context-monitor.sh"), contextMonitorScript());
650
+ await writeFileSafe(path.join(hooksDir, "run-hook.mjs"), nodeHookRuntimeScript({
651
+ promptGuardMode: config.promptGuardMode ?? config.strictness ?? "advisory",
652
+ workflowGuardMode: config.strictness ?? "advisory",
653
+ tddEnforcementMode: config.tddEnforcement ?? config.strictness ?? "advisory",
654
+ tddTestPathPatterns: config.tdd?.testPathPatterns ?? config.tddTestGlobs,
655
+ tddProductionPathPatterns: config.tdd?.productionPathPatterns
656
+ }));
649
657
  const opencodePluginSource = opencodePluginJs();
650
658
  await writeFileSafe(path.join(hooksDir, "opencode-plugin.mjs"), opencodePluginSource);
651
659
  try {
@@ -659,6 +667,7 @@ async function writeHooks(projectRoot, config) {
659
667
  "prompt-guard.sh",
660
668
  "workflow-guard.sh",
661
669
  "context-monitor.sh",
670
+ "run-hook.mjs",
662
671
  "opencode-plugin.mjs"
663
672
  ]) {
664
673
  await fs.chmod(path.join(hooksDir, script), 0o755);
@@ -1347,12 +1356,13 @@ function stripManagedHookCommands(value) {
1347
1356
  function isManagedRuntimeHookCommand(command) {
1348
1357
  const normalized = command.trim().replace(/\s+/gu, " ");
1349
1358
  if (/(^|\s)(?:bash\s+)?(?:\.\/)?\.cclaw\/hooks\/(?:session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor)\.sh(?:\s|$)/u.test(normalized) ||
1350
- /(^|\s)(?:bash\s+)?(?:\.\/)?\.cclaw\/hooks\/run-hook\.cmd\s+(?:session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor)(?:\.sh)?(?:\s|$)/u.test(normalized)) {
1359
+ /(^|\s)(?:bash\s+)?(?:\.\/)?\.cclaw\/hooks\/run-hook\.cmd\s+(?:session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor)(?:\.sh)?(?:\s|$)/u.test(normalized) ||
1360
+ /(^|\s)(?:node\s+)?(?:"|')?(?:\.\/)?\.cclaw\/hooks\/run-hook\.mjs(?:"|')?\s+(?:session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor|verify-current-state)(?:\.sh)?(?:\s|$)/u.test(normalized)) {
1351
1361
  return true;
1352
1362
  }
1353
1363
  // Codex UserPromptSubmit non-blocking state nudge:
1354
- // bash -lc '... cclaw internal verify-current-state --quiet ...'
1355
- return /internal verify-current-state --quiet/u.test(normalized);
1364
+ // legacy shell and newer Node wrappers call this internal command.
1365
+ return /internal verify-current-state(?:\s|$)/u.test(normalized);
1356
1366
  }
1357
1367
  async function removeManagedHookEntries(hookFilePath) {
1358
1368
  if (!(await exists(hookFilePath)))
@@ -1,5 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { spawn } from "node:child_process";
4
+ import process from "node:process";
3
5
  import { RUNTIME_ROOT, SHIP_FINALIZATION_MODES } from "../constants.js";
4
6
  import { stageSchema } from "../content/stage-schema.js";
5
7
  import { appendDelegation, checkMandatoryDelegations } from "../delegation.js";
@@ -306,6 +308,17 @@ function parseVerifyCurrentStateArgs(tokens) {
306
308
  }
307
309
  return { quiet };
308
310
  }
311
+ function parseHookArgs(tokens) {
312
+ const [hookName, ...rest] = tokens;
313
+ const normalizedHook = typeof hookName === "string" ? hookName.trim() : "";
314
+ if (normalizedHook.length === 0) {
315
+ throw new Error("internal hook requires a hook name: cclaw internal hook <name>.");
316
+ }
317
+ if (rest.length > 0) {
318
+ throw new Error(`Unknown arguments for internal hook: ${rest.join(" ")}`);
319
+ }
320
+ return { hookName: normalizedHook };
321
+ }
309
322
  async function buildValidationReport(projectRoot, flowState) {
310
323
  const delegation = await checkMandatoryDelegations(projectRoot, flowState.currentStage);
311
324
  const gates = await verifyCurrentStageGateEvidence(projectRoot, flowState);
@@ -621,10 +634,45 @@ async function runVerifyCurrentState(projectRoot, args, io) {
621
634
  }
622
635
  return validation.ok ? 0 : 1;
623
636
  }
637
+ async function runHookCommand(projectRoot, args, io) {
638
+ const runHookPath = path.join(projectRoot, RUNTIME_ROOT, "hooks", "run-hook.mjs");
639
+ try {
640
+ await fs.access(runHookPath);
641
+ }
642
+ catch {
643
+ io.stderr.write(`cclaw internal hook: missing hook runtime at ${runHookPath}. Run \`cclaw sync\` first.\n`);
644
+ return 1;
645
+ }
646
+ return await new Promise((resolve) => {
647
+ const child = spawn(process.execPath, [runHookPath, args.hookName], {
648
+ cwd: projectRoot,
649
+ env: process.env,
650
+ stdio: ["inherit", "pipe", "pipe"]
651
+ });
652
+ child.stdout.on("data", (chunk) => {
653
+ io.stdout.write(chunk);
654
+ });
655
+ child.stderr.on("data", (chunk) => {
656
+ io.stderr.write(chunk);
657
+ });
658
+ child.on("error", (err) => {
659
+ io.stderr.write(`cclaw internal hook: failed to launch runtime (${err instanceof Error ? err.message : String(err)}).\n`);
660
+ resolve(1);
661
+ });
662
+ child.on("close", (code, signal) => {
663
+ if (signal) {
664
+ io.stderr.write(`cclaw internal hook: runtime terminated by signal ${signal}.\n`);
665
+ resolve(1);
666
+ return;
667
+ }
668
+ resolve(typeof code === "number" ? code : 1);
669
+ });
670
+ });
671
+ }
624
672
  export async function runInternalCommand(projectRoot, argv, io) {
625
673
  const [subcommand, ...tokens] = argv;
626
674
  if (!subcommand) {
627
- io.stderr.write("cclaw internal requires a subcommand: advance-stage | verify-flow-state-diff | verify-current-state | knowledge-digest | envelope-validate | tdd-red-evidence\n");
675
+ io.stderr.write("cclaw internal requires a subcommand: advance-stage | verify-flow-state-diff | verify-current-state | knowledge-digest | envelope-validate | tdd-red-evidence | hook\n");
628
676
  return 1;
629
677
  }
630
678
  try {
@@ -646,7 +694,10 @@ export async function runInternalCommand(projectRoot, argv, io) {
646
694
  if (subcommand === "tdd-red-evidence") {
647
695
  return await runTddRedEvidenceCommand(projectRoot, tokens, io);
648
696
  }
649
- io.stderr.write(`Unknown internal subcommand: ${subcommand}. Expected advance-stage | verify-flow-state-diff | verify-current-state | knowledge-digest | envelope-validate | tdd-red-evidence\n`);
697
+ if (subcommand === "hook") {
698
+ return await runHookCommand(projectRoot, parseHookArgs(tokens), io);
699
+ }
700
+ io.stderr.write(`Unknown internal subcommand: ${subcommand}. Expected advance-stage | verify-flow-state-diff | verify-current-state | knowledge-digest | envelope-validate | tdd-red-evidence | hook\n`);
650
701
  return 1;
651
702
  }
652
703
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.48.7",
3
+ "version": "0.48.9",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {