cclaw-cli 0.46.2 → 0.46.4

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.
@@ -11,6 +11,7 @@ export interface HookRuntimeOptions {
11
11
  export declare const RUNTIME_SHELL_DETECT_ROOT = "HARNESS=\"codex\"\nif [ -n \"${CLAUDE_PROJECT_DIR:-}\" ]; then\n HARNESS=\"claude\"\nelif [ -n \"${CURSOR_PROJECT_DIR:-}\" ] || [ -n \"${CURSOR_PROJECT_ROOT:-}\" ]; then\n HARNESS=\"cursor\"\nelif [ -n \"${OPENCODE_PROJECT_DIR:-}\" ] || [ -n \"${OPENCODE_PROJECT_ROOT:-}\" ]; then\n HARNESS=\"opencode\"\nfi\n\nROOT=\"\"\nfor candidate in \"${CCLAW_PROJECT_ROOT:-}\" \"${CLAUDE_PROJECT_DIR:-}\" \"${CURSOR_PROJECT_DIR:-}\" \"${CURSOR_PROJECT_ROOT:-}\" \"${OPENCODE_PROJECT_DIR:-}\" \"${OPENCODE_PROJECT_ROOT:-}\" \"${PWD:-}\"; do\n if [ -n \"$candidate\" ] && [ -d \"$candidate/.cclaw\" ]; then\n ROOT=\"$candidate\"\n break\n fi\ndone\nif [ -z \"$ROOT\" ]; then\n ROOT=\"${CCLAW_PROJECT_ROOT:-${CLAUDE_PROJECT_DIR:-${CURSOR_PROJECT_DIR:-${CURSOR_PROJECT_ROOT:-${OPENCODE_PROJECT_DIR:-${OPENCODE_PROJECT_ROOT:-${PWD}}}}}}}\"\nfi";
12
12
  export declare function sessionStartScript(_options?: HookRuntimeOptions): string;
13
13
  export declare function stopCheckpointScript(): string;
14
+ export declare function runHookDispatcherScript(): string;
14
15
  export declare function stageCompleteScript(): string;
15
16
  export declare function preCompactScript(): string;
16
17
  export { claudeHooksJsonWithObservation as claudeHooksJson } from "./observe.js";
@@ -769,6 +769,56 @@ case "$HARNESS" in
769
769
  esac
770
770
  `;
771
771
  }
772
+ export function runHookDispatcherScript() {
773
+ return `#!/usr/bin/env bash
774
+ # cclaw hook dispatcher — generated by cclaw sync
775
+ # Single entrypoint used by harness hook JSON wiring.
776
+ set -euo pipefail
777
+
778
+ ${DETECT_ROOT}
779
+
780
+ if [ "$#" -lt 1 ]; then
781
+ printf 'Usage: bash ${RUNTIME_ROOT}/hooks/run-hook.cmd <session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor>\\n' >&2
782
+ exit 1
783
+ fi
784
+
785
+ HOOK_NAME="$1"
786
+ shift || true
787
+
788
+ case "$HOOK_NAME" in
789
+ session-start|session-start.sh)
790
+ HOOK_FILE="session-start.sh"
791
+ ;;
792
+ stop-checkpoint|stop-checkpoint.sh)
793
+ HOOK_FILE="stop-checkpoint.sh"
794
+ ;;
795
+ pre-compact|pre-compact.sh)
796
+ HOOK_FILE="pre-compact.sh"
797
+ ;;
798
+ prompt-guard|prompt-guard.sh)
799
+ HOOK_FILE="prompt-guard.sh"
800
+ ;;
801
+ workflow-guard|workflow-guard.sh)
802
+ HOOK_FILE="workflow-guard.sh"
803
+ ;;
804
+ context-monitor|context-monitor.sh)
805
+ HOOK_FILE="context-monitor.sh"
806
+ ;;
807
+ *)
808
+ printf '[cclaw] run-hook: unsupported hook "%s".\\n' "$HOOK_NAME" >&2
809
+ exit 1
810
+ ;;
811
+ esac
812
+
813
+ HOOK_PATH="$ROOT/${RUNTIME_ROOT}/hooks/$HOOK_FILE"
814
+ if [ ! -f "$HOOK_PATH" ]; then
815
+ printf '[cclaw] run-hook: hook script not found at %s\\n' "$HOOK_PATH" >&2
816
+ exit 1
817
+ fi
818
+
819
+ exec bash "$HOOK_PATH" "$@"
820
+ `;
821
+ }
772
822
  export function stageCompleteScript() {
773
823
  return `#!/usr/bin/env bash
774
824
  # cclaw stage-complete helper — generated by cclaw sync
@@ -19,9 +19,6 @@ export declare function observeScript(): string;
19
19
  export declare function contextMonitorScript(): string;
20
20
  export declare function summarizeObservationsRuntimeModule(): string;
21
21
  export declare function summarizeObservationsScript(): string;
22
- /**
23
- * Updated hooks.json generators with PreToolUse/PostToolUse observation.
24
- */
25
22
  export declare function claudeHooksJsonWithObservation(): string;
26
23
  export declare function cursorHooksJsonWithObservation(): string;
27
24
  /**
@@ -1808,6 +1808,9 @@ exit 0
1808
1808
  /**
1809
1809
  * Updated hooks.json generators with PreToolUse/PostToolUse observation.
1810
1810
  */
1811
+ function hookDispatcherCommand(scriptName) {
1812
+ return `bash ${RUNTIME_ROOT}/hooks/run-hook.cmd ${scriptName}`;
1813
+ }
1811
1814
  export function claudeHooksJsonWithObservation() {
1812
1815
  return JSON.stringify({
1813
1816
  cclawHookSchemaVersion: 1,
@@ -1816,30 +1819,30 @@ export function claudeHooksJsonWithObservation() {
1816
1819
  matcher: "startup|resume|clear|compact",
1817
1820
  hooks: [{
1818
1821
  type: "command",
1819
- command: `bash ${RUNTIME_ROOT}/hooks/session-start.sh`
1822
+ command: hookDispatcherCommand("session-start.sh")
1820
1823
  }]
1821
1824
  }],
1822
1825
  PreToolUse: [{
1823
1826
  matcher: "*",
1824
1827
  hooks: [{
1825
1828
  type: "command",
1826
- command: `bash ${RUNTIME_ROOT}/hooks/prompt-guard.sh`
1829
+ command: hookDispatcherCommand("prompt-guard.sh")
1827
1830
  }, {
1828
1831
  type: "command",
1829
- command: `bash ${RUNTIME_ROOT}/hooks/workflow-guard.sh`
1832
+ command: hookDispatcherCommand("workflow-guard.sh")
1830
1833
  }]
1831
1834
  }],
1832
1835
  PostToolUse: [{
1833
1836
  matcher: "*",
1834
1837
  hooks: [{
1835
1838
  type: "command",
1836
- command: `bash ${RUNTIME_ROOT}/hooks/context-monitor.sh`
1839
+ command: hookDispatcherCommand("context-monitor.sh")
1837
1840
  }]
1838
1841
  }],
1839
1842
  Stop: [{
1840
1843
  hooks: [{
1841
1844
  type: "command",
1842
- command: `bash ${RUNTIME_ROOT}/hooks/stop-checkpoint.sh`,
1845
+ command: hookDispatcherCommand("stop-checkpoint.sh"),
1843
1846
  timeout: 10
1844
1847
  }]
1845
1848
  }],
@@ -1847,7 +1850,7 @@ export function claudeHooksJsonWithObservation() {
1847
1850
  matcher: "manual|auto",
1848
1851
  hooks: [{
1849
1852
  type: "command",
1850
- command: `bash ${RUNTIME_ROOT}/hooks/pre-compact.sh`,
1853
+ command: hookDispatcherCommand("pre-compact.sh"),
1851
1854
  timeout: 10
1852
1855
  }]
1853
1856
  }]
@@ -1860,31 +1863,31 @@ export function cursorHooksJsonWithObservation() {
1860
1863
  version: 1,
1861
1864
  hooks: {
1862
1865
  sessionStart: [{
1863
- command: `bash ${RUNTIME_ROOT}/hooks/session-start.sh`
1866
+ command: hookDispatcherCommand("session-start.sh")
1864
1867
  }],
1865
1868
  sessionResume: [{
1866
- command: `bash ${RUNTIME_ROOT}/hooks/session-start.sh`
1869
+ command: hookDispatcherCommand("session-start.sh")
1867
1870
  }],
1868
1871
  sessionClear: [{
1869
- command: `bash ${RUNTIME_ROOT}/hooks/session-start.sh`
1872
+ command: hookDispatcherCommand("session-start.sh")
1870
1873
  }],
1871
1874
  sessionCompact: [{
1872
- command: `bash ${RUNTIME_ROOT}/hooks/pre-compact.sh`
1875
+ command: hookDispatcherCommand("pre-compact.sh")
1873
1876
  }, {
1874
- command: `bash ${RUNTIME_ROOT}/hooks/session-start.sh`
1877
+ command: hookDispatcherCommand("session-start.sh")
1875
1878
  }],
1876
1879
  preToolUse: [{
1877
1880
  matcher: "*",
1878
- command: `bash ${RUNTIME_ROOT}/hooks/prompt-guard.sh`
1881
+ command: hookDispatcherCommand("prompt-guard.sh")
1879
1882
  }, {
1880
1883
  matcher: "*",
1881
- command: `bash ${RUNTIME_ROOT}/hooks/workflow-guard.sh`
1884
+ command: hookDispatcherCommand("workflow-guard.sh")
1882
1885
  }],
1883
1886
  postToolUse: [{
1884
1887
  matcher: "*",
1885
- command: `bash ${RUNTIME_ROOT}/hooks/context-monitor.sh`
1888
+ command: hookDispatcherCommand("context-monitor.sh")
1886
1889
  }],
1887
- stop: [{ command: `bash ${RUNTIME_ROOT}/hooks/stop-checkpoint.sh`, timeout: 10 }]
1890
+ stop: [{ command: hookDispatcherCommand("stop-checkpoint.sh"), timeout: 10 }]
1888
1891
  }
1889
1892
  }, null, 2);
1890
1893
  }
@@ -1915,13 +1918,13 @@ export function codexHooksJsonWithObservation() {
1915
1918
  matcher: "startup|resume",
1916
1919
  hooks: [{
1917
1920
  type: "command",
1918
- command: `bash ${RUNTIME_ROOT}/hooks/session-start.sh`
1921
+ command: hookDispatcherCommand("session-start.sh")
1919
1922
  }]
1920
1923
  }],
1921
1924
  UserPromptSubmit: [{
1922
1925
  hooks: [{
1923
1926
  type: "command",
1924
- command: `bash ${RUNTIME_ROOT}/hooks/prompt-guard.sh`
1927
+ command: hookDispatcherCommand("prompt-guard.sh")
1925
1928
  }, {
1926
1929
  type: "command",
1927
1930
  command: "bash -lc 'if command -v cclaw >/dev/null 2>&1; then cclaw internal verify-current-state --quiet >/dev/null || true; else npx -y cclaw-cli internal verify-current-state --quiet >/dev/null || true; fi'"
@@ -1931,23 +1934,23 @@ export function codexHooksJsonWithObservation() {
1931
1934
  matcher: "Bash",
1932
1935
  hooks: [{
1933
1936
  type: "command",
1934
- command: `bash ${RUNTIME_ROOT}/hooks/prompt-guard.sh`
1937
+ command: hookDispatcherCommand("prompt-guard.sh")
1935
1938
  }, {
1936
1939
  type: "command",
1937
- command: `bash ${RUNTIME_ROOT}/hooks/workflow-guard.sh`
1940
+ command: hookDispatcherCommand("workflow-guard.sh")
1938
1941
  }]
1939
1942
  }],
1940
1943
  PostToolUse: [{
1941
1944
  matcher: "Bash",
1942
1945
  hooks: [{
1943
1946
  type: "command",
1944
- command: `bash ${RUNTIME_ROOT}/hooks/context-monitor.sh`
1947
+ command: hookDispatcherCommand("context-monitor.sh")
1945
1948
  }]
1946
1949
  }],
1947
1950
  Stop: [{
1948
1951
  hooks: [{
1949
1952
  type: "command",
1950
- command: `bash ${RUNTIME_ROOT}/hooks/stop-checkpoint.sh`,
1953
+ command: hookDispatcherCommand("stop-checkpoint.sh"),
1951
1954
  timeout: 10
1952
1955
  }]
1953
1956
  }]
@@ -43,7 +43,7 @@ const REQUIRED_GATE_IDS = {
43
43
  "tdd_red_test_written",
44
44
  "tdd_green_full_suite",
45
45
  "tdd_refactor_completed",
46
- "tdd_traceable_to_plan"
46
+ "tdd_verified_before_complete"
47
47
  ],
48
48
  review: [
49
49
  "review_layer1_spec_compliance",
@@ -27,6 +27,7 @@ export const TDD = {
27
27
  "Dispatch `test-author` subagent in `BUILD_GREEN_REFACTOR` mode — minimal implementation + full-suite GREEN + refactor notes.",
28
28
  "GREEN: Run full suite — execute ALL tests, not just the ones you wrote. The full suite must be GREEN.",
29
29
  "GREEN: Verify no regressions — if any existing test breaks, fix the regression before proceeding.",
30
+ "Run verification-before-completion discipline for the slice — capture a fresh test command, commit SHA, and explicit PASS/FAIL status before completion claims.",
30
31
  "REFACTOR: Improve code quality — without changing behavior. Document what you changed and why.",
31
32
  "Record evidence — capture RED failure, GREEN output, and REFACTOR notes in the TDD artifact.",
32
33
  "Annotate traceability — link to plan task ID and spec criterion.",
@@ -40,6 +41,7 @@ export const TDD = {
40
41
  "Capture and store failing output as RED evidence.",
41
42
  "Apply minimal change to satisfy RED tests (GREEN).",
42
43
  "Run full suite, not partial checks, for GREEN validation.",
44
+ "Before declaring the slice complete, run a fresh verification check and record command + commit SHA + PASS/FAIL.",
43
45
  "Refactor without changing behavior and document rationale (REFACTOR).",
44
46
  "Stop if regressions appear and fix before proceeding.",
45
47
  "If a test passes unexpectedly, investigate: does the behavior already exist, or is the test wrong?",
@@ -51,6 +53,7 @@ export const TDD = {
51
53
  "Run tests and capture failure output.",
52
54
  "Dispatch `test-author` in BUILD_GREEN_REFACTOR mode and implement smallest change needed for GREEN.",
53
55
  "Run full tests and build checks.",
56
+ "Run a fresh verification-before-completion check and capture command + commit SHA + PASS/FAIL in guard evidence.",
54
57
  "Perform refactor pass preserving behavior.",
55
58
  "Record RED, GREEN, and REFACTOR evidence in artifact.",
56
59
  "Annotate traceability to plan task and spec criterion; on `sliceReview` triggers, append a Per-Slice Review entry before closing the slice."
@@ -59,12 +62,14 @@ export const TDD = {
59
62
  { id: "tdd_red_test_written", description: "Failing tests exist before implementation changes." },
60
63
  { id: "tdd_green_full_suite", description: "Full relevant suite passes in GREEN state." },
61
64
  { id: "tdd_refactor_completed", description: "Refactor pass completed with behavior preservation verified." },
65
+ { id: "tdd_verified_before_complete", description: "Fresh verification evidence includes test command, commit SHA, and explicit pass/fail status." },
62
66
  { id: "tdd_traceable_to_plan", description: "Change traceability to plan slice is explicit." }
63
67
  ],
64
68
  requiredEvidence: [
65
69
  "Artifact updated at `.cclaw/artifacts/06-tdd.md` with RED, GREEN, and REFACTOR sections.",
66
70
  "Failing command output captured (RED).",
67
71
  "Full test/build output recorded (GREEN).",
72
+ "Fresh verification evidence recorded with command, commit SHA, and PASS/FAIL status before completion.",
68
73
  "Acceptance mapping documented.",
69
74
  "Failure reason analysis recorded.",
70
75
  "Refactor rationale captured.",
package/dist/doctor.js CHANGED
@@ -622,6 +622,7 @@ export async function doctorChecks(projectRoot, options = {}) {
622
622
  for (const script of [
623
623
  "session-start.sh",
624
624
  "stop-checkpoint.sh",
625
+ "run-hook.cmd",
625
626
  "prompt-guard.sh",
626
627
  "workflow-guard.sh",
627
628
  "context-monitor.sh"
package/dist/install.js CHANGED
@@ -23,7 +23,7 @@ import { archiveCommandContract, archiveCommandSkillMarkdown } from "./content/a
23
23
  import { rewindCommandContract, rewindCommandSkillMarkdown } from "./content/rewind-command.js";
24
24
  import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
25
25
  import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
26
- import { sessionStartScript, stopCheckpointScript, stageCompleteScript, preCompactScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
26
+ import { sessionStartScript, stopCheckpointScript, runHookDispatcherScript, stageCompleteScript, preCompactScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
27
27
  import { contextMonitorScript, promptGuardScript, workflowGuardScript } from "./content/observe.js";
28
28
  import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
29
29
  import { decisionProtocolMarkdown, completionProtocolMarkdown, ethosProtocolMarkdown } from "./content/protocols.js";
@@ -616,6 +616,7 @@ async function writeHooks(projectRoot, config) {
616
616
  await ensureDir(hooksDir);
617
617
  await writeFileSafe(path.join(hooksDir, "session-start.sh"), sessionStartScript());
618
618
  await writeFileSafe(path.join(hooksDir, "stop-checkpoint.sh"), stopCheckpointScript());
619
+ await writeFileSafe(path.join(hooksDir, "run-hook.cmd"), runHookDispatcherScript());
619
620
  await writeFileSafe(path.join(hooksDir, "stage-complete.sh"), stageCompleteScript());
620
621
  await writeFileSafe(path.join(hooksDir, "pre-compact.sh"), preCompactScript());
621
622
  await writeFileSafe(path.join(hooksDir, "prompt-guard.sh"), promptGuardScript({
@@ -633,6 +634,7 @@ async function writeHooks(projectRoot, config) {
633
634
  for (const script of [
634
635
  "session-start.sh",
635
636
  "stop-checkpoint.sh",
637
+ "run-hook.cmd",
636
638
  "stage-complete.sh",
637
639
  "pre-compact.sh",
638
640
  "prompt-guard.sh",
@@ -1263,7 +1265,8 @@ function stripManagedHookCommands(value) {
1263
1265
  }
1264
1266
  function isManagedRuntimeHookCommand(command) {
1265
1267
  const normalized = command.trim().replace(/\s+/gu, " ");
1266
- if (/(^|\s)(?:bash\s+)?(?:\.\/)?\.cclaw\/hooks\/(?:session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor)\.sh(?:\s|$)/u.test(normalized)) {
1268
+ if (/(^|\s)(?:bash\s+)?(?:\.\/)?\.cclaw\/hooks\/(?:session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor)\.sh(?:\s|$)/u.test(normalized) ||
1269
+ /(^|\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)) {
1267
1270
  return true;
1268
1271
  }
1269
1272
  // Codex UserPromptSubmit non-blocking state nudge:
@@ -13,6 +13,25 @@ import { FLOW_STAGES } from "../types.js";
13
13
  function unique(values) {
14
14
  return [...new Set(values)];
15
15
  }
16
+ const TEST_COMMAND_HINT_PATTERN = /\b(?:npm test|pnpm test|yarn test|bun test|vitest|jest|pytest|go test|cargo test|mvn test|gradle test|dotnet test)\b/iu;
17
+ const SHA_WITH_LABEL_PATTERN = /\b(?:sha|commit)(?:\s*[:=]|\s+)\s*[0-9a-f]{7,40}\b/iu;
18
+ const PASS_STATUS_PATTERN = /\b(?:pass|passed|green|ok)\b/iu;
19
+ function validateGateEvidenceShape(stage, gateId, evidence) {
20
+ if (stage !== "tdd" || gateId !== "tdd_verified_before_complete") {
21
+ return null;
22
+ }
23
+ const trimmed = evidence.trim();
24
+ if (!TEST_COMMAND_HINT_PATTERN.test(trimmed)) {
25
+ return "must include the fresh verification command that was run (for example `npm test`, `pytest`, `go test`, or equivalent).";
26
+ }
27
+ if (!SHA_WITH_LABEL_PATTERN.test(trimmed)) {
28
+ return "must include a commit SHA token prefixed with `sha` or `commit` (for example `sha: abc1234`).";
29
+ }
30
+ if (!PASS_STATUS_PATTERN.test(trimmed)) {
31
+ return "must include explicit success status (for example `PASS` or `GREEN`).";
32
+ }
33
+ return null;
34
+ }
16
35
  function parseStringList(raw) {
17
36
  if (!Array.isArray(raw))
18
37
  return [];
@@ -392,6 +411,21 @@ async function runAdvanceStage(projectRoot, args, io) {
392
411
  io.stderr.write(`cclaw internal advance-stage: missing --evidence-json entries for passed gates: ${missingGuardEvidence.join(", ")}.\n`);
393
412
  return 1;
394
413
  }
414
+ const malformedGateEvidence = nextPassed.flatMap((gateId) => {
415
+ const provided = args.evidenceByGate[gateId];
416
+ const existing = flowState.guardEvidence[gateId];
417
+ const effectiveEvidence = typeof provided === "string" && provided.trim().length > 0
418
+ ? provided
419
+ : typeof existing === "string" && existing.trim().length > 0
420
+ ? existing
421
+ : "";
422
+ const issue = validateGateEvidenceShape(args.stage, gateId, effectiveEvidence);
423
+ return issue ? [`${gateId}: ${issue}`] : [];
424
+ });
425
+ if (malformedGateEvidence.length > 0) {
426
+ io.stderr.write(`cclaw internal advance-stage: gate evidence format check failed: ${malformedGateEvidence.join(" | ")}.\n`);
427
+ return 1;
428
+ }
395
429
  const nextGuardEvidence = { ...flowState.guardEvidence };
396
430
  for (const gateId of nextPassed) {
397
431
  const provided = args.evidenceByGate[gateId];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.46.2",
3
+ "version": "0.46.4",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {