cclaw-cli 1.0.0 → 3.0.0

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.
Files changed (52) hide show
  1. package/dist/artifact-linter/brainstorm.js +15 -1
  2. package/dist/artifact-linter/design.js +14 -0
  3. package/dist/artifact-linter/scope.js +14 -0
  4. package/dist/artifact-linter/shared.d.ts +1 -0
  5. package/dist/artifact-linter/shared.js +32 -0
  6. package/dist/artifact-linter.js +13 -5
  7. package/dist/cli.js +2 -9
  8. package/dist/config.d.ts +11 -67
  9. package/dist/config.js +59 -649
  10. package/dist/content/hook-events.js +1 -5
  11. package/dist/content/hook-manifest.d.ts +6 -4
  12. package/dist/content/hook-manifest.js +16 -65
  13. package/dist/content/hooks.js +54 -14
  14. package/dist/content/meta-skill.js +4 -3
  15. package/dist/content/node-hooks.d.ts +0 -26
  16. package/dist/content/node-hooks.js +459 -157
  17. package/dist/content/observe.js +5 -4
  18. package/dist/content/opencode-plugin.js +1 -78
  19. package/dist/content/skills-elicitation.d.ts +1 -0
  20. package/dist/content/skills-elicitation.js +123 -0
  21. package/dist/content/skills.js +6 -4
  22. package/dist/content/stages/brainstorm.js +7 -3
  23. package/dist/content/stages/design.js +6 -2
  24. package/dist/content/stages/plan.js +2 -2
  25. package/dist/content/stages/scope.js +9 -5
  26. package/dist/content/stages/tdd.js +11 -11
  27. package/dist/content/start-command.js +4 -4
  28. package/dist/content/templates.js +21 -0
  29. package/dist/flow-state.d.ts +7 -0
  30. package/dist/flow-state.js +1 -0
  31. package/dist/gate-evidence.js +1 -5
  32. package/dist/hook-schema.js +3 -0
  33. package/dist/hook-schemas/claude-hooks.v1.json +2 -5
  34. package/dist/hook-schemas/codex-hooks.v1.json +1 -4
  35. package/dist/hook-schemas/cursor-hooks.v1.json +1 -3
  36. package/dist/install.d.ts +2 -7
  37. package/dist/install.js +32 -123
  38. package/dist/internal/advance-stage/advance.js +22 -1
  39. package/dist/internal/advance-stage/parsers.d.ts +1 -0
  40. package/dist/internal/advance-stage/parsers.js +6 -0
  41. package/dist/internal/compound-readiness.js +1 -16
  42. package/dist/internal/early-loop-status.js +1 -3
  43. package/dist/internal/runtime-integrity.js +0 -20
  44. package/dist/policy.js +6 -9
  45. package/dist/run-persistence.d.ts +1 -1
  46. package/dist/run-persistence.js +29 -2
  47. package/dist/runtime/run-hook.mjs +459 -265
  48. package/dist/tdd-verification-evidence.js +6 -18
  49. package/dist/track-heuristics.d.ts +7 -1
  50. package/dist/track-heuristics.js +12 -0
  51. package/dist/types.d.ts +0 -56
  52. package/package.json +1 -1
@@ -8,11 +8,7 @@ export { HOOK_SEMANTIC_EVENTS } from "./hook-manifest.js";
8
8
  */
9
9
  const OPENCODE_SEMANTIC_COVERAGE = {
10
10
  session_rehydrate: "plugin event handlers + transform rehydration",
11
- pre_tool_prompt_guard: "plugin tool.execute.before -> prompt-guard",
12
- pre_tool_workflow_guard: "plugin tool.execute.before -> workflow-guard",
13
- post_tool_context_monitor: "plugin tool.execute.after -> context-monitor",
14
- stop_handoff: "plugin session.idle -> stop-handoff",
15
- precompact_compat: "plugin session.compacted -> pre-compact"
11
+ stop_handoff: "plugin session.idle -> stop-handoff"
16
12
  };
17
13
  /**
18
14
  * Public semantic coverage map derived from `HOOK_MANIFEST` for
@@ -21,7 +21,7 @@
21
21
  */
22
22
  export declare const HOOK_MANIFEST_HARNESSES: readonly ["claude", "cursor", "codex"];
23
23
  export type HookManifestHarness = (typeof HOOK_MANIFEST_HARNESSES)[number];
24
- export declare const HOOK_HANDLERS: readonly ["session-start", "prompt-guard", "workflow-guard", "context-monitor", "stop-handoff", "pre-compact", "verify-current-state"];
24
+ export declare const HOOK_HANDLERS: readonly ["session-start", "stop-handoff"];
25
25
  export type HookHandlerId = (typeof HOOK_HANDLERS)[number];
26
26
  export interface HookBinding {
27
27
  /**
@@ -31,12 +31,13 @@ export interface HookBinding {
31
31
  event: string;
32
32
  matcher?: string;
33
33
  timeout?: number;
34
+ /** Optional harness UI status line while this hook runs. */
35
+ statusMessage?: string;
34
36
  /**
35
37
  * Within a single (harness, event) group, entries are sorted by
36
38
  * `priority` ASC, ties broken by manifest-declaration order. Use
37
39
  * this to express "this handler must run BEFORE/AFTER that handler
38
- * on the same event" (e.g. pre-compact must run before session-start
39
- * on cursor `sessionCompact`). Default `0`.
40
+ * on the same event". Default `0`.
40
41
  */
41
42
  priority?: number;
42
43
  }
@@ -50,7 +51,7 @@ export interface HookHandlerSpec {
50
51
  semantic: HookSemanticEvent | null;
51
52
  bindings: Partial<Record<HookManifestHarness, HookBinding[]>>;
52
53
  }
53
- export declare const HOOK_SEMANTIC_EVENTS: readonly ["session_rehydrate", "pre_tool_prompt_guard", "pre_tool_workflow_guard", "post_tool_context_monitor", "stop_handoff", "precompact_compat", "strict_state_verify"];
54
+ export declare const HOOK_SEMANTIC_EVENTS: readonly ["session_rehydrate", "stop_handoff"];
54
55
  export type HookSemanticEvent = (typeof HOOK_SEMANTIC_EVENTS)[number];
55
56
  export declare const HOOK_MANIFEST: readonly HookHandlerSpec[];
56
57
  export interface EventGroup {
@@ -63,6 +64,7 @@ export interface EventGroup {
63
64
  handler: HookHandlerId;
64
65
  matcher?: string;
65
66
  timeout?: number;
67
+ statusMessage?: string;
66
68
  }>;
67
69
  }
68
70
  /**
@@ -22,26 +22,16 @@
22
22
  export const HOOK_MANIFEST_HARNESSES = ["claude", "cursor", "codex"];
23
23
  export const HOOK_HANDLERS = [
24
24
  "session-start",
25
- "prompt-guard",
26
- "workflow-guard",
27
- "context-monitor",
28
- "stop-handoff",
29
- "pre-compact",
30
- "verify-current-state"
25
+ "stop-handoff"
31
26
  ];
32
27
  export const HOOK_SEMANTIC_EVENTS = [
33
28
  "session_rehydrate",
34
- "pre_tool_prompt_guard",
35
- "pre_tool_workflow_guard",
36
- "post_tool_context_monitor",
37
- "stop_handoff",
38
- "precompact_compat",
39
- "strict_state_verify"
29
+ "stop_handoff"
40
30
  ];
41
31
  export const HOOK_MANIFEST = [
42
32
  {
43
33
  handler: "session-start",
44
- description: "Rehydrate flow state, refresh Ralph Loop + compound readiness, emit bootstrap digest.",
34
+ description: "Rehydrate flow state and emit bootstrap digest.",
45
35
  semantic: "session_rehydrate",
46
36
  bindings: {
47
37
  claude: [{ event: "SessionStart", matcher: "startup|resume|clear|compact" }],
@@ -51,42 +41,15 @@ export const HOOK_MANIFEST = [
51
41
  { event: "sessionClear" },
52
42
  { event: "sessionCompact" }
53
43
  ],
54
- codex: [{ event: "SessionStart", matcher: "startup|resume" }]
55
- }
56
- },
57
- {
58
- handler: "prompt-guard",
59
- description: "Stage-aware prompt gate (iron-laws + strictness).",
60
- semantic: "pre_tool_prompt_guard",
61
- bindings: {
62
- claude: [{ event: "PreToolUse", matcher: "*" }],
63
- cursor: [{ event: "preToolUse", matcher: "*" }],
64
44
  codex: [
65
- { event: "UserPromptSubmit" },
66
- { event: "PreToolUse", matcher: "Bash|bash" }
45
+ {
46
+ event: "SessionStart",
47
+ matcher: "startup|resume",
48
+ statusMessage: "Running cclaw session startup checks"
49
+ }
67
50
  ]
68
51
  }
69
52
  },
70
- {
71
- handler: "workflow-guard",
72
- description: "TDD and workflow gate on Write/Edit/Bash style tool invocations.",
73
- semantic: "pre_tool_workflow_guard",
74
- bindings: {
75
- claude: [{ event: "PreToolUse", matcher: "Write|Edit|MultiEdit|NotebookEdit|Bash" }],
76
- cursor: [{ event: "preToolUse", matcher: "*" }],
77
- codex: [{ event: "PreToolUse", matcher: "Bash|bash" }]
78
- }
79
- },
80
- {
81
- handler: "context-monitor",
82
- description: "Post-tool context usage + stage signal monitor.",
83
- semantic: "post_tool_context_monitor",
84
- bindings: {
85
- claude: [{ event: "PostToolUse", matcher: "*" }],
86
- cursor: [{ event: "postToolUse", matcher: "*" }],
87
- codex: [{ event: "PostToolUse", matcher: "Bash|bash" }]
88
- }
89
- },
90
53
  {
91
54
  handler: "stop-handoff",
92
55
  description: "Remind about clean handoff with stage + run context on session stop.",
@@ -94,26 +57,13 @@ export const HOOK_MANIFEST = [
94
57
  bindings: {
95
58
  claude: [{ event: "Stop", timeout: 10 }],
96
59
  cursor: [{ event: "stop", timeout: 10 }],
97
- codex: [{ event: "Stop", timeout: 10 }]
98
- }
99
- },
100
- {
101
- handler: "pre-compact",
102
- description: "No-op compatibility hook for harness pre-compact events; session-start rehydrates from flow-state, artifacts, and knowledge.",
103
- semantic: "precompact_compat",
104
- bindings: {
105
- claude: [{ event: "PreCompact", matcher: "manual|auto", timeout: 10 }],
106
- // Keep this before session-start on cursor `sessionCompact` so the
107
- // compatibility handler runs before rehydration.
108
- cursor: [{ event: "sessionCompact", priority: -10 }]
109
- }
110
- },
111
- {
112
- handler: "verify-current-state",
113
- description: "Supplementary Codex strict-mode guard that runs on UserPromptSubmit to assert the live state matches the flow.",
114
- semantic: "strict_state_verify",
115
- bindings: {
116
- codex: [{ event: "UserPromptSubmit" }]
60
+ codex: [
61
+ {
62
+ event: "Stop",
63
+ timeout: 10,
64
+ statusMessage: "Preparing cclaw handoff checklist"
65
+ }
66
+ ]
117
67
  }
118
68
  }
119
69
  ];
@@ -144,6 +94,7 @@ export function groupBindingsByEvent(harness) {
144
94
  handler: spec.handler,
145
95
  ...(binding.matcher !== undefined ? { matcher: binding.matcher } : {}),
146
96
  ...(binding.timeout !== undefined ? { timeout: binding.timeout } : {}),
97
+ ...(binding.statusMessage !== undefined ? { statusMessage: binding.statusMessage } : {}),
147
98
  priority: binding.priority ?? 0,
148
99
  seq: seq++
149
100
  });
@@ -5,6 +5,15 @@ import { RUNTIME_ROOT } from "../constants.js";
5
5
  import { DELEGATION_DISPATCH_SURFACES, DELEGATION_DISPATCH_SURFACE_PATH_PREFIXES } from "../delegation.js";
6
6
  function resolveCliRuntimeForGeneratedHook() {
7
7
  const here = fileURLToPath(import.meta.url);
8
+ // Vitest runs init/sync from src/ and expects helpers to execute the same
9
+ // source runtime, even when a stale dist/ exists in the repository.
10
+ if (process.env.VITEST === "true") {
11
+ const sourceCli = path.resolve(path.dirname(here), "..", "cli.ts");
12
+ const viteNode = path.resolve(path.dirname(here), "..", "..", "node_modules", "vite-node", "vite-node.mjs");
13
+ if (existsSync(sourceCli) && existsSync(viteNode)) {
14
+ return { entrypoint: viteNode, argsPrefix: ["--script", sourceCli] };
15
+ }
16
+ }
8
17
  const candidates = [
9
18
  path.resolve(path.dirname(here), "..", "cli.js"),
10
19
  path.resolve(path.dirname(here), "..", "..", "dist", "cli.js")
@@ -15,15 +24,6 @@ function resolveCliRuntimeForGeneratedHook() {
15
24
  if (existsSync(candidate))
16
25
  return { entrypoint: candidate, argsPrefix: [] };
17
26
  }
18
- // Vitest exercises init/sync directly from src/ without a compiled dist/.
19
- // Route that dev-only shape through vite-node so hooks still prove a local runtime.
20
- if (process.env.VITEST === "true") {
21
- const sourceCli = path.resolve(path.dirname(here), "..", "cli.ts");
22
- const viteNode = path.resolve(path.dirname(here), "..", "..", "node_modules", "vite-node", "vite-node.mjs");
23
- if (existsSync(sourceCli) && existsSync(viteNode)) {
24
- return { entrypoint: viteNode, argsPrefix: ["--script", sourceCli] };
25
- }
26
- }
27
27
  return { entrypoint: null, argsPrefix: [] };
28
28
  }
29
29
  function internalHelperScript(helperName, internalSubcommand, usage, options) {
@@ -42,6 +42,7 @@ const INTERNAL_SUBCOMMAND = ${JSON.stringify(internalSubcommand)};
42
42
  const USAGE = ${JSON.stringify(usage)};
43
43
  const POSITIONAL_ARG_NAME = ${JSON.stringify(options?.positionalArgName ?? null)};
44
44
  const POSITIONAL_ARG_REQUIRED = ${JSON.stringify(options?.positionalArgRequired === true)};
45
+ const DEFAULT_QUIET_ENV_VAR = ${JSON.stringify(options?.defaultQuietEnvVar ?? null)};
45
46
 
46
47
  async function detectRoot() {
47
48
  const candidates = [
@@ -88,6 +89,19 @@ async function main() {
88
89
  }
89
90
  }
90
91
 
92
+ if (DEFAULT_QUIET_ENV_VAR !== null) {
93
+ const envRaw = process.env[DEFAULT_QUIET_ENV_VAR];
94
+ if (typeof envRaw !== "string" || envRaw.trim().length === 0) {
95
+ process.env[DEFAULT_QUIET_ENV_VAR] = "1";
96
+ }
97
+ const quietRaw = (process.env[DEFAULT_QUIET_ENV_VAR] ?? "").trim().toLowerCase();
98
+ const quietEnabled = !/^(0|false|no|off)$/u.test(quietRaw);
99
+ const alreadyQuiet = flags.includes("--quiet");
100
+ if (quietEnabled && !alreadyQuiet) {
101
+ flags = [...flags, "--quiet"];
102
+ }
103
+ }
104
+
91
105
  const root = await detectRoot();
92
106
  const runtimePath = path.join(root, RUNTIME_ROOT);
93
107
  try {
@@ -171,13 +185,13 @@ void main();
171
185
  `;
172
186
  }
173
187
  export function startFlowScript() {
174
- return internalHelperScript("start-flow", "start-flow", "Usage: node " + RUNTIME_ROOT + "/hooks/start-flow.mjs --track=<standard|medium|quick> [--class=...] [--prompt=...] [--stack=...] [--reason=...] [--reclassify] [--force-reset]");
188
+ return internalHelperScript("start-flow", "start-flow", "Usage: node " + RUNTIME_ROOT + "/hooks/start-flow.mjs --track=<standard|medium|quick> [--class=...] [--prompt=...] [--stack=...] [--reason=...] [--reclassify] [--force-reset]", { defaultQuietEnvVar: "CCLAW_START_FLOW_QUIET" });
175
189
  }
176
190
  export function cancelRunScript() {
177
191
  return internalHelperScript("cancel-run", "cancel-run", "Usage: node " + RUNTIME_ROOT + "/hooks/cancel-run.mjs --reason=<text> [--disposition=<cancelled|abandoned>] [--name=<slug>]");
178
192
  }
179
193
  export function stageCompleteScript() {
180
- return internalHelperScript("stage-complete", "advance-stage", "Usage: node " + RUNTIME_ROOT + "/hooks/stage-complete.mjs <stage> [--passed=...] [--evidence-json=...] [--waive-delegation=...] [--waiver-reason=...] [--accept-proactive-waiver] [--accept-proactive-waiver-reason=...] [--json]", {
194
+ return internalHelperScript("stage-complete", "advance-stage", "Usage: node " + RUNTIME_ROOT + "/hooks/stage-complete.mjs <stage> [--passed=...] [--evidence-json=...] [--waive-delegation=...] [--waiver-reason=...] [--accept-proactive-waiver] [--accept-proactive-waiver-reason=...] [--skip-questions] [--json]", {
181
195
  positionalArgName: "stage",
182
196
  positionalArgRequired: true
183
197
  });
@@ -280,7 +294,7 @@ function usage() {
280
294
  process.stderr.write([
281
295
  "Usage:",
282
296
  " node .cclaw/hooks/delegation-record.mjs --stage=<stage> --agent=<agent> --mode=<mandatory|proactive> --status=<scheduled|launched|acknowledged|completed|failed|waived|stale> --span-id=<id> [--dispatch-id=<id>] [--worker-run-id=<id>] [--dispatch-surface=<surface>] [--agent-definition-path=<path>] [--ack-ts=<iso>] [--launched-ts=<iso>] [--completed-ts=<iso>] [--evidence-ref=<ref>] [--waiver-reason=<text>] [--json]",
283
- " node .cclaw/hooks/delegation-record.mjs --rerecord --span-id=<id> --dispatch-id=<id> --dispatch-surface=<surface> --agent-definition-path=<path> [--ack-ts=<iso>] [--completed-ts=<iso>] [--json]",
297
+ " node .cclaw/hooks/delegation-record.mjs --rerecord --span-id=<id> --dispatch-id=<id> --dispatch-surface=<surface> --agent-definition-path=<path> [--ack-ts=<iso>] [--completed-ts=<iso>] [--evidence-ref=<ref>] [--json]",
284
298
  "",
285
299
  "Allowed --dispatch-surface values:",
286
300
  " " + VALID_DISPATCH_SURFACES.join(", "),
@@ -322,6 +336,18 @@ async function pathExists(filePath) {
322
336
  }
323
337
  }
324
338
 
339
+ function normalizeEvidenceRefs(args) {
340
+ if (Array.isArray(args["evidence-refs"])) {
341
+ return args["evidence-refs"]
342
+ .filter((ref) => typeof ref === "string" && ref.trim().length > 0)
343
+ .map((ref) => ref.trim());
344
+ }
345
+ if (typeof args["evidence-ref"] === "string" && args["evidence-ref"].trim().length > 0) {
346
+ return [args["evidence-ref"].trim()];
347
+ }
348
+ return [];
349
+ }
350
+
325
351
  function buildRow(args, status, runId, now) {
326
352
  const fulfillmentMode = args["dispatch-surface"] === "role-switch"
327
353
  ? "role-switch"
@@ -340,7 +366,7 @@ function buildRow(args, status, runId, now) {
340
366
  agentDefinitionPath: args["agent-definition-path"],
341
367
  fulfillmentMode,
342
368
  waiverReason: args["waiver-reason"],
343
- evidenceRefs: args["evidence-ref"] ? [args["evidence-ref"]] : [],
369
+ evidenceRefs: normalizeEvidenceRefs(args),
344
370
  runId,
345
371
  startTs: now,
346
372
  ts: now,
@@ -420,6 +446,18 @@ async function runRerecord(args, json) {
420
446
  emitProblems(["no legacy ledger entry found for --span-id=" + args["span-id"]], json, 1);
421
447
  return;
422
448
  }
449
+ const explicitEvidenceRef =
450
+ typeof args["evidence-ref"] === "string" && args["evidence-ref"].trim().length > 0
451
+ ? args["evidence-ref"].trim()
452
+ : "";
453
+ const legacyEvidenceRefs = Array.isArray(legacyEntry.evidenceRefs)
454
+ ? legacyEntry.evidenceRefs
455
+ .filter((ref) => typeof ref === "string" && ref.trim().length > 0)
456
+ .map((ref) => ref.trim())
457
+ : [];
458
+ const mergedEvidenceRefs = explicitEvidenceRef.length > 0
459
+ ? [explicitEvidenceRef]
460
+ : legacyEvidenceRefs;
423
461
  if (args["dispatch-surface"] !== "role-switch") {
424
462
  if (!dispatchSurfaceMatchesPath(args["dispatch-surface"], args["agent-definition-path"])) {
425
463
  const allowedPrefixes = SURFACE_PATH_PREFIXES[args["dispatch-surface"]];
@@ -445,7 +483,9 @@ async function runRerecord(args, json) {
445
483
  "agent-definition-path": args["agent-definition-path"],
446
484
  "ack-ts": args["ack-ts"] || legacyEntry.ackTs || now,
447
485
  "completed-ts": args["completed-ts"] || legacyEntry.completedTs || now,
448
- "launched-ts": args["launched-ts"] || legacyEntry.launchedTs || now
486
+ "launched-ts": args["launched-ts"] || legacyEntry.launchedTs || now,
487
+ "evidence-ref": explicitEvidenceRef.length > 0 ? explicitEvidenceRef : undefined,
488
+ "evidence-refs": mergedEvidenceRefs
449
489
  };
450
490
  const status = "completed";
451
491
  const clean = Object.fromEntries(Object.entries(buildRow(merged, status, runId, now)).filter(([, value]) => value !== undefined));
@@ -44,9 +44,10 @@ Substantive vs. non-substantive:
44
44
  - **Non-substantive** (skill load optional): one-line acknowledgement,
45
45
  clarifying a typo, confirming a prior answer, pure conversation.
46
46
 
47
- If the current stage is ambiguous because \`flow-state.json\` is missing
48
- or corrupt, stop and route through \`/cc\` before any substantive
49
- response.
47
+ If \`.cclaw/state/flow-state.json\` is missing, treat it as a normal fresh-init
48
+ state and route through \`/cc <idea>\` to start the first tracked run.
49
+ If the file exists but is corrupt/unreadable, stop and route through \`/cc\`
50
+ before any substantive response.
50
51
 
51
52
  ## Red Flags (stop and re-route)
52
53
 
@@ -1,30 +1,4 @@
1
1
  export interface NodeHookRuntimeOptions {
2
- /**
3
- * Single enforcement knob derived from `config.strictness`. Generated hooks
4
- * embed this value as the default for every guard (prompt, workflow, TDD,
5
- * iron-laws-coupled blocks). `CCLAW_STRICTNESS` env var overrides at run
6
- * time; per-law strictness still flows through `iron-laws.json`.
7
- */
8
- strictness?: "advisory" | "strict";
9
- tddTestPathPatterns?: string[];
10
- tddProductionPathPatterns?: string[];
11
- /**
12
- * Baked-in default recurrence threshold for compound-readiness computed
13
- * by the session-start hook. Derived from
14
- * `config.compound.recurrenceThreshold` at install time; re-run
15
- * `cclaw sync` after changing the config value so hook and CLI agree.
16
- */
17
- compoundRecurrenceThreshold?: number;
18
- /**
19
- * Enables early-stage producer/critic loop diagnostics in session-start.
20
- * Defaults to true.
21
- */
22
- earlyLoopEnabled?: boolean;
23
- /**
24
- * Baked-in max iterations for brainstorm/scope/design early-loop status.
25
- * Derived from `config.earlyLoop.maxIterations`.
26
- */
27
- earlyLoopMaxIterations?: number;
28
2
  }
29
3
  /**
30
4
  * Node-only hook runtime (single entrypoint).