cclaw-cli 0.48.6 → 0.48.8

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.
package/dist/install.js CHANGED
@@ -24,8 +24,9 @@ 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 { sessionStartScript, stopCheckpointScript, runHookDispatcherScript, stageCompleteScript, preCompactScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
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";
@@ -630,6 +631,7 @@ async function writeHooks(projectRoot, config) {
630
631
  mode: config.ironLaws?.mode,
631
632
  strictLaws: config.ironLaws?.strictLaws
632
633
  }), null, 2)}\n`);
634
+ await writeFileSafe(path.join(hooksDir, "_lib.sh"), hookLibScript());
633
635
  await writeFileSafe(path.join(hooksDir, "session-start.sh"), sessionStartScript());
634
636
  await writeFileSafe(path.join(hooksDir, "stop-checkpoint.sh"), stopCheckpointScript());
635
637
  await writeFileSafe(path.join(hooksDir, "run-hook.cmd"), runHookDispatcherScript());
@@ -645,10 +647,18 @@ async function writeHooks(projectRoot, config) {
645
647
  tddProductionPathPatterns: config.tdd?.productionPathPatterns
646
648
  }));
647
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
+ }));
648
657
  const opencodePluginSource = opencodePluginJs();
649
658
  await writeFileSafe(path.join(hooksDir, "opencode-plugin.mjs"), opencodePluginSource);
650
659
  try {
651
660
  for (const script of [
661
+ "_lib.sh",
652
662
  "session-start.sh",
653
663
  "stop-checkpoint.sh",
654
664
  "run-hook.cmd",
@@ -657,6 +667,7 @@ async function writeHooks(projectRoot, config) {
657
667
  "prompt-guard.sh",
658
668
  "workflow-guard.sh",
659
669
  "context-monitor.sh",
670
+ "run-hook.mjs",
660
671
  "opencode-plugin.mjs"
661
672
  ]) {
662
673
  await fs.chmod(path.join(hooksDir, script), 0o755);
@@ -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";
@@ -12,6 +14,7 @@ import { readFlowState, writeFlowState } from "../runs.js";
12
14
  import { FLOW_STAGES } from "../types.js";
13
15
  import { runEnvelopeValidateCommand } from "./envelope-validate.js";
14
16
  import { runKnowledgeDigestCommand } from "./knowledge-digest.js";
17
+ import { runTddRedEvidenceCommand } from "./tdd-red-evidence.js";
15
18
  function unique(values) {
16
19
  return [...new Set(values)];
17
20
  }
@@ -305,6 +308,17 @@ function parseVerifyCurrentStateArgs(tokens) {
305
308
  }
306
309
  return { quiet };
307
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
+ }
308
322
  async function buildValidationReport(projectRoot, flowState) {
309
323
  const delegation = await checkMandatoryDelegations(projectRoot, flowState.currentStage);
310
324
  const gates = await verifyCurrentStageGateEvidence(projectRoot, flowState);
@@ -620,10 +634,45 @@ async function runVerifyCurrentState(projectRoot, args, io) {
620
634
  }
621
635
  return validation.ok ? 0 : 1;
622
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
+ }
623
672
  export async function runInternalCommand(projectRoot, argv, io) {
624
673
  const [subcommand, ...tokens] = argv;
625
674
  if (!subcommand) {
626
- io.stderr.write("cclaw internal requires a subcommand: advance-stage | verify-flow-state-diff | verify-current-state | knowledge-digest | envelope-validate\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");
627
676
  return 1;
628
677
  }
629
678
  try {
@@ -642,7 +691,13 @@ export async function runInternalCommand(projectRoot, argv, io) {
642
691
  if (subcommand === "envelope-validate") {
643
692
  return await runEnvelopeValidateCommand(projectRoot, tokens, io);
644
693
  }
645
- io.stderr.write(`Unknown internal subcommand: ${subcommand}. Expected advance-stage | verify-flow-state-diff | verify-current-state | knowledge-digest | envelope-validate\n`);
694
+ if (subcommand === "tdd-red-evidence") {
695
+ return await runTddRedEvidenceCommand(projectRoot, tokens, io);
696
+ }
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`);
646
701
  return 1;
647
702
  }
648
703
  catch (err) {
@@ -0,0 +1,7 @@
1
+ import type { Writable } from "node:stream";
2
+ interface InternalIo {
3
+ stdout: Writable;
4
+ stderr: Writable;
5
+ }
6
+ export declare function runTddRedEvidenceCommand(projectRoot: string, tokens: string[], io: InternalIo): Promise<number>;
7
+ export {};
@@ -0,0 +1,130 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { RUNTIME_ROOT } from "../constants.js";
4
+ import { readFlowState } from "../runs.js";
5
+ import { hasFailingTestForPath, parseTddCycleLog } from "../tdd-cycle.js";
6
+ function normalizePath(value) {
7
+ return value.replace(/\\/gu, "/").toLowerCase();
8
+ }
9
+ function parseArgs(tokens) {
10
+ const args = { quiet: false };
11
+ for (const token of tokens) {
12
+ if (token === "--quiet") {
13
+ args.quiet = true;
14
+ continue;
15
+ }
16
+ if (token.startsWith("--path=")) {
17
+ const value = token.slice("--path=".length).trim();
18
+ if (!value) {
19
+ throw new Error("--path must not be empty.");
20
+ }
21
+ args.targetPath = value;
22
+ continue;
23
+ }
24
+ if (token.startsWith("--run-id=")) {
25
+ const value = token.slice("--run-id=".length).trim();
26
+ if (value) {
27
+ args.runId = value;
28
+ }
29
+ continue;
30
+ }
31
+ throw new Error(`Unknown flag for tdd-red-evidence: ${token}`);
32
+ }
33
+ if (!args.targetPath) {
34
+ throw new Error("Missing required flag: --path=<production-file-path>");
35
+ }
36
+ return args;
37
+ }
38
+ function parseAutoEvidence(text) {
39
+ const out = [];
40
+ for (const rawLine of text.split(/\r?\n/gu)) {
41
+ const line = rawLine.trim();
42
+ if (!line)
43
+ continue;
44
+ try {
45
+ const parsed = JSON.parse(line);
46
+ const exitCode = parsed.exitCode;
47
+ if (typeof exitCode !== "number" || exitCode === 0)
48
+ continue;
49
+ const runId = typeof parsed.runId === "string" && parsed.runId.length > 0
50
+ ? parsed.runId
51
+ : "active";
52
+ const rawPaths = Array.isArray(parsed.paths)
53
+ ? parsed.paths
54
+ : typeof parsed.path === "string"
55
+ ? [parsed.path]
56
+ : [];
57
+ const paths = rawPaths
58
+ .filter((value) => typeof value === "string")
59
+ .map((value) => value.trim())
60
+ .filter((value) => value.length > 0);
61
+ if (paths.length === 0)
62
+ continue;
63
+ out.push({
64
+ runId,
65
+ exitCode,
66
+ paths
67
+ });
68
+ }
69
+ catch {
70
+ // ignore malformed lines
71
+ }
72
+ }
73
+ return out;
74
+ }
75
+ function hasFailingAutoEvidenceForPath(entries, targetPath, options = {}) {
76
+ const normalizedTarget = normalizePath(targetPath);
77
+ for (const entry of entries) {
78
+ if (options.runId && entry.runId !== options.runId)
79
+ continue;
80
+ for (const filePath of entry.paths) {
81
+ const normalized = normalizePath(filePath);
82
+ if (normalized === normalizedTarget || normalized.endsWith(`/${normalizedTarget}`)) {
83
+ return true;
84
+ }
85
+ }
86
+ }
87
+ return false;
88
+ }
89
+ export async function runTddRedEvidenceCommand(projectRoot, tokens, io) {
90
+ const args = parseArgs(tokens);
91
+ const flowState = await readFlowState(projectRoot).catch(() => null);
92
+ const effectiveRunId = args.runId ?? flowState?.activeRunId;
93
+ const tddLogPath = path.join(projectRoot, RUNTIME_ROOT, "state", "tdd-cycle-log.jsonl");
94
+ const autoEvidencePath = path.join(projectRoot, RUNTIME_ROOT, "state", "tdd-red-evidence.jsonl");
95
+ let cycleLogHasRed = false;
96
+ let autoEvidenceHasRed = false;
97
+ try {
98
+ const raw = await fs.readFile(tddLogPath, "utf8");
99
+ const entries = parseTddCycleLog(raw);
100
+ cycleLogHasRed = hasFailingTestForPath(entries, args.targetPath, {
101
+ runId: effectiveRunId
102
+ });
103
+ }
104
+ catch {
105
+ cycleLogHasRed = false;
106
+ }
107
+ try {
108
+ const raw = await fs.readFile(autoEvidencePath, "utf8");
109
+ const entries = parseAutoEvidence(raw);
110
+ autoEvidenceHasRed = hasFailingAutoEvidenceForPath(entries, args.targetPath, {
111
+ runId: effectiveRunId
112
+ });
113
+ }
114
+ catch {
115
+ autoEvidenceHasRed = false;
116
+ }
117
+ const hasRed = cycleLogHasRed || autoEvidenceHasRed;
118
+ if (!args.quiet) {
119
+ io.stdout.write(`${JSON.stringify({
120
+ ok: hasRed,
121
+ path: args.targetPath,
122
+ runId: effectiveRunId ?? null,
123
+ sources: {
124
+ tddCycleLog: cycleLogHasRed,
125
+ autoEvidence: autoEvidenceHasRed
126
+ }
127
+ }, null, 2)}\n`);
128
+ }
129
+ return hasRed ? 0 : 2;
130
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.48.6",
3
+ "version": "0.48.8",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {