cclaw-cli 0.48.10 → 0.48.11

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.
@@ -13,7 +13,7 @@ Reference docs for \`cclaw doctor\` checks.
13
13
  - \`state-and-gates.md\` - flow-state integrity and gate evidence contracts
14
14
  - \`delegation-and-preamble.md\` - mandatory delegations and lightweight announce discipline
15
15
  - \`traceability.md\` - spec/plan/tdd trace matrix expectations
16
- - \`tooling-capabilities.md\` - local runtime prerequisites (bash/node/python/jq)
16
+ - \`tooling-capabilities.md\` - local runtime prerequisites (node only)
17
17
  - \`config-and-policy.md\` - config schema, rules policy, and validation references
18
18
  `,
19
19
  "runtime-layout.md": `# Runtime Layout
@@ -116,17 +116,21 @@ Reference docs for \`cclaw doctor\` checks.
116
116
 
117
117
  ## Required
118
118
 
119
- - \`bash\` for runtime hook scripts
120
- - \`node\` for generated runtime scripts/plugins
119
+ - \`node\` (>=20) — the only runtime dependency. All hooks, git-hook relays, and the
120
+ \`cclaw\` CLI itself run on Node.js. No \`bash\`, \`python3\`, or \`jq\` required.
121
+ - \`git\` — needed for worktree and pre-commit/pre-push relays.
121
122
 
122
- ## Optional fallback
123
+ ## Not required (removed)
123
124
 
124
- - at least one of \`python3\` or \`jq\` for JSON parsing fallback paths
125
+ Earlier releases relied on \`bash\` to execute generated shell hooks and on
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
128
+ are no longer part of the supported runtime contract.
125
129
 
126
130
  ## Typical fixes
127
131
 
128
- 1. Install missing runtime tools.
129
- 2. Keep at least one JSON fallback parser available (\`python3\` or \`jq\`).
132
+ 1. Install Node.js 20 or newer (matches \`package.json\` \`engines\`) and ensure \`node\` is on \`PATH\`.
133
+ 2. Re-run \`cclaw sync\` to regenerate hook configs after upgrading Node.
130
134
  `,
131
135
  "config-and-policy.md": `# Config And Policy
132
136
 
@@ -186,12 +186,12 @@ async function detectRoot(env) {
186
186
  try {
187
187
  const runtimePath = path.join(candidate, RUNTIME_ROOT);
188
188
  const stat = await fs.stat(runtimePath);
189
- if (stat.isDirectory()) return candidate;
189
+ if (stat.isDirectory()) return { root: candidate, foundRuntime: true };
190
190
  } catch {
191
191
  // continue
192
192
  }
193
193
  }
194
- return candidates[0] || process.cwd();
194
+ return { root: candidates[0] || process.cwd(), foundRuntime: false };
195
195
  }
196
196
 
197
197
  function toLower(value) {
@@ -1518,7 +1518,7 @@ async function handleVerifyCurrentState(runtime) {
1518
1518
  : DEFAULT_WORKFLOW_GUARD_MODE;
1519
1519
  const result = await runCclawInternal(runtime.root, ["verify-current-state", "--quiet"]);
1520
1520
  if (result.missingBinary) {
1521
- process.stderr.write("[cclaw] codex hook: cclaw binary is required for verify-current-state\\n");
1521
+ process.stderr.write("[cclaw] hook: cclaw binary is required for verify-current-state\\n");
1522
1522
  return 1;
1523
1523
  }
1524
1524
  if (mode === "strict") {
@@ -1555,7 +1555,14 @@ async function main() {
1555
1555
  }
1556
1556
 
1557
1557
  const harness = detectHarness(process.env);
1558
- const root = await detectRoot(process.env);
1558
+ const { root, foundRuntime } = await detectRoot(process.env);
1559
+ if (!foundRuntime) {
1560
+ // No .cclaw/ runtime in any candidate root — this directory is not
1561
+ // initialized for cclaw. Exit 0 silently so hooks never block harnesses
1562
+ // that run in unrelated repos; users initialize with \`cclaw init\`.
1563
+ process.exitCode = 0;
1564
+ return;
1565
+ }
1559
1566
  const inputRaw = await readStdin();
1560
1567
  const inputData = safeParseJson(inputRaw, {});
1561
1568
  const runtime = {
@@ -1,5 +1,9 @@
1
1
  import { RUNTIME_ROOT } from "../constants.js";
2
2
  function hookDispatcherCommand(hookName) {
3
+ // RUNTIME_ROOT is a relative path (".cclaw") that currently contains no
4
+ // whitespace, so quoting is unnecessary inside the JSON-encoded command
5
+ // string. If RUNTIME_ROOT ever becomes configurable, wrap the path with
6
+ // JSON.stringify to survive spaces.
3
7
  return `node ${RUNTIME_ROOT}/hooks/run-hook.mjs ${hookName}`;
4
8
  }
5
9
  export function claudeHooksJsonWithObservation() {
@@ -409,6 +409,12 @@ export default function cclawPlugin(ctx) {
409
409
  : typeof payload;
410
410
  console.error("[cclaw] opencode unknown event payload keys: " + keys);
411
411
  }
412
+ // session.compacted must run pre-compact BEFORE refreshing the bootstrap
413
+ // cache, otherwise the injected system prompt still shows the pre-compact
414
+ // digest/state until the next lifecycle event.
415
+ if (eventType === "session.compacted") {
416
+ await runHookScript("pre-compact", eventData ?? {});
417
+ }
412
418
  if (
413
419
  eventType === "session.created" ||
414
420
  eventType === "session.resumed" ||
@@ -424,9 +430,6 @@ export default function cclawPlugin(ctx) {
424
430
  // until the next compaction or restart.
425
431
  await refreshBootstrapCache(true);
426
432
  }
427
- if (eventType === "session.compacted") {
428
- await runHookScript("pre-compact", eventData ?? {});
429
- }
430
433
  if (eventType === "session.idle") {
431
434
  await runHookScript("stop-checkpoint", { loop_count: 0 });
432
435
  }
@@ -30,15 +30,6 @@ const RULES = [
30
30
  docRef: ref("runtime-layout.md")
31
31
  }
32
32
  },
33
- {
34
- test: /^capability:runtime:json_parser$/,
35
- metadata: {
36
- severity: "warning",
37
- summary: "Optional JSON fallback parser availability.",
38
- fix: "Install at least one of `python3` or `jq` for resilient fallback parsing.",
39
- docRef: ref("tooling-capabilities.md")
40
- }
41
- },
42
33
  {
43
34
  test: /^capability:required:/,
44
35
  metadata: {
package/dist/doctor.js CHANGED
@@ -743,12 +743,9 @@ export async function doctorChecks(projectRoot, options = {}) {
743
743
  }
744
744
  }
745
745
  }
746
- // OpenCode plugin source + deployed path
747
- checks.push({
748
- name: "hook:opencode_plugin_source",
749
- ok: await exists(path.join(projectRoot, RUNTIME_ROOT, "hooks", "opencode-plugin.mjs")),
750
- details: `${RUNTIME_ROOT}/hooks/opencode-plugin.mjs`
751
- });
746
+ // OpenCode plugin deployed path. (Presence of the source under
747
+ // `${RUNTIME_ROOT}/hooks/opencode-plugin.mjs` is already asserted by the
748
+ // generic `hook:script:opencode-plugin.mjs` check above; avoid a duplicate.)
752
749
  const opencodeEnabled = configuredHarnesses.includes("opencode");
753
750
  const opencodeDeployed = await exists(path.join(projectRoot, ".opencode/plugins/cclaw-plugin.mjs"));
754
751
  checks.push({
@@ -1009,27 +1006,11 @@ export async function doctorChecks(projectRoot, options = {}) {
1009
1006
  });
1010
1007
  }
1011
1008
  const hasNode = await commandAvailable("node");
1012
- const hasPython = await commandAvailable("python3");
1013
- const hasJq = await commandAvailable("jq");
1014
1009
  checks.push({
1015
1010
  name: "capability:required:node",
1016
1011
  ok: hasNode,
1017
1012
  details: "node is required for cclaw runtime scripts and CLI wiring"
1018
1013
  });
1019
- checks.push({
1020
- name: "warning:capability:jq",
1021
- ok: true,
1022
- details: hasJq
1023
- ? "jq available (optional)"
1024
- : "warning: jq not found; Node hook runtime no longer depends on jq"
1025
- });
1026
- checks.push({
1027
- name: "warning:capability:python3",
1028
- ok: true,
1029
- details: hasPython
1030
- ? "python3 available (optional)"
1031
- : "warning: python3 not found; Node hook runtime no longer depends on python3"
1032
- });
1033
1014
  const windowsHookConfigCandidates = [
1034
1015
  path.join(projectRoot, ".claude/hooks/hooks.json"),
1035
1016
  path.join(projectRoot, ".cursor/hooks.json"),
package/dist/install.js CHANGED
@@ -119,6 +119,11 @@ function resolveChangedFiles(root) {
119
119
  const root = resolveRepoRoot();
120
120
  const runtimeHook = path.join(root, RUNTIME_ROOT, "hooks", "run-hook.mjs");
121
121
  if (!fs.existsSync(runtimeHook)) {
122
+ // cclaw git relay is installed but the runtime entrypoint is missing —
123
+ // warn visibly (without blocking the commit) so the drift is noticed.
124
+ process.stderr.write(
125
+ "[cclaw] " + HOOK_NAME + ": " + runtimeHook + " not found; run \`cclaw sync\` to reinstall\\n"
126
+ );
122
127
  process.exit(0);
123
128
  }
124
129
 
@@ -1428,7 +1433,11 @@ function stripManagedHookCommands(value) {
1428
1433
  return { updated: root, changed: true };
1429
1434
  }
1430
1435
  function isManagedRuntimeHookCommand(command) {
1431
- const normalized = command.trim().replace(/\s+/gu, " ");
1436
+ // Normalize whitespace and collapse any Windows-style backslash path
1437
+ // separators to forward slashes so user-edited hook configs on Windows
1438
+ // (e.g. `node .cclaw\hooks\run-hook.mjs ...`) still round-trip through
1439
+ // sync without being duplicated alongside freshly generated entries.
1440
+ const normalized = command.trim().replace(/\s+/gu, " ").replace(/\\/gu, "/");
1432
1441
  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)) {
1433
1442
  return true;
1434
1443
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.48.10",
3
+ "version": "0.48.11",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {