cclaw-cli 0.48.9 → 0.48.10
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/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/content/harness-doc.js +1 -1
- package/dist/content/harness-playbooks.js +7 -7
- package/dist/content/hook-events.js +19 -19
- package/dist/content/hooks.d.ts +0 -16
- package/dist/content/hooks.js +112 -1220
- package/dist/content/learnings.js +2 -2
- package/dist/content/next-command.js +2 -2
- package/dist/content/node-hooks.js +20 -11
- package/dist/content/observe.d.ts +0 -38
- package/dist/content/observe.js +27 -1718
- package/dist/content/opencode-plugin.js +5 -5
- package/dist/content/protocols.js +5 -9
- package/dist/content/skills.js +1 -1
- package/dist/content/stage-common-guidance.js +1 -1
- package/dist/content/stages/design.js +1 -1
- package/dist/content/stages/plan.js +2 -2
- package/dist/content/stages/scope.js +1 -1
- package/dist/doctor.js +63 -52
- package/dist/install.js +124 -53
- package/dist/policy.js +13 -13
- package/package.json +2 -3
|
@@ -425,16 +425,16 @@ export default function cclawPlugin(ctx) {
|
|
|
425
425
|
await refreshBootstrapCache(true);
|
|
426
426
|
}
|
|
427
427
|
if (eventType === "session.compacted") {
|
|
428
|
-
await runHookScript("pre-compact
|
|
428
|
+
await runHookScript("pre-compact", eventData ?? {});
|
|
429
429
|
}
|
|
430
430
|
if (eventType === "session.idle") {
|
|
431
|
-
await runHookScript("stop-checkpoint
|
|
431
|
+
await runHookScript("stop-checkpoint", { loop_count: 0 });
|
|
432
432
|
}
|
|
433
433
|
},
|
|
434
434
|
"tool.execute.before": async (input, output) => {
|
|
435
435
|
const payload = normalizeToolPayload(input, output);
|
|
436
|
-
const promptOk = await runHookScript("prompt-guard
|
|
437
|
-
const workflowOk = await runHookScript("workflow-guard
|
|
436
|
+
const promptOk = await runHookScript("prompt-guard", payload);
|
|
437
|
+
const workflowOk = await runHookScript("workflow-guard", payload);
|
|
438
438
|
if (!promptOk || !workflowOk) {
|
|
439
439
|
throw new Error(
|
|
440
440
|
"cclaw OpenCode guard blocked tool.execute.before (prompt/workflow guard non-zero exit)."
|
|
@@ -443,7 +443,7 @@ export default function cclawPlugin(ctx) {
|
|
|
443
443
|
},
|
|
444
444
|
"tool.execute.after": async (input, output) => {
|
|
445
445
|
const payload = normalizeToolPayload(input, output);
|
|
446
|
-
await runHookScript("context-monitor
|
|
446
|
+
await runHookScript("context-monitor", payload);
|
|
447
447
|
void refreshBootstrapCache(false);
|
|
448
448
|
},
|
|
449
449
|
"experimental.chat.system.transform": (payload) => {
|
|
@@ -101,19 +101,15 @@ Shared closeout sequence applied by every stage skill.
|
|
|
101
101
|
1. Verify mandatory delegations are completed or explicitly waived.
|
|
102
102
|
2. Persist stage artifact under \`.cclaw/artifacts/\`.
|
|
103
103
|
3. Use the canonical helper:
|
|
104
|
-
- \`
|
|
104
|
+
- \`node .cclaw/hooks/stage-complete.mjs <stage>\`
|
|
105
105
|
- helper responsibilities: validate mandatory delegations, validate
|
|
106
106
|
current-stage gate evidence/artifact lint, update
|
|
107
107
|
\`stageGateCatalog\` + \`guardEvidence\`, and advance \`currentStage\`.
|
|
108
|
-
4.
|
|
109
|
-
|
|
110
|
-
blocked gates, and update \`guardEvidence\`. This path is legacy and
|
|
111
|
-
intentionally noisy in workflow guards.
|
|
112
|
-
5. Run \`npx cclaw doctor\` and resolve failures.
|
|
113
|
-
6. **Capture through-flow learnings** — see the policy below. Knowledge
|
|
108
|
+
4. Run \`npx cclaw doctor\` and resolve failures.
|
|
109
|
+
5. **Capture through-flow learnings** — see the policy below. Knowledge
|
|
114
110
|
accrues continuously across stages, not just at retro.
|
|
115
|
-
|
|
116
|
-
|
|
111
|
+
6. Notify user with stage completion and next action (\`/cc-next\`).
|
|
112
|
+
7. Stop; do not auto-run the next stage unless user asks.
|
|
117
113
|
|
|
118
114
|
## Through-flow knowledge capture
|
|
119
115
|
|
package/dist/content/skills.js
CHANGED
|
@@ -222,7 +222,7 @@ function completionParametersBlock(schema, track) {
|
|
|
222
222
|
- \`gates\`: ${gateList}
|
|
223
223
|
- \`artifact\`: \`${RUNTIME_ROOT}/artifacts/${schema.artifactFile}\`
|
|
224
224
|
- \`mandatory delegations\`: ${mandatory}
|
|
225
|
-
- \`completion helper\`: \`
|
|
225
|
+
- \`completion helper\`: \`node .cclaw/hooks/stage-complete.mjs ${schema.stage}\`
|
|
226
226
|
- Fill \`## Learnings\` before closeout: either \`- None this stage.\` or JSON bullets with required keys \`type\`, \`trigger\`, \`action\`, \`confidence\` (knowledge-schema compatible).
|
|
227
227
|
- Record mandatory delegation completion/waiver in \`${RUNTIME_ROOT}/state/delegation-log.json\` with rationale as needed.
|
|
228
228
|
- Use the completion helper instead of raw \`flow-state.json\` edits (legacy direct edits trigger workflow-guard warnings or strict-mode blocks).
|
|
@@ -63,7 +63,7 @@ Before closeout, fill the artifact \`## Learnings\` section (do not write
|
|
|
63
63
|
- \`- None this stage.\` when nothing reusable emerged.
|
|
64
64
|
- Or 1-3 JSON bullets with required keys \`type\`, \`trigger\`, \`action\`,
|
|
65
65
|
\`confidence\` (optional fields may mirror knowledge.jsonl schema keys).
|
|
66
|
-
During \`
|
|
66
|
+
During \`node .cclaw/hooks/stage-complete.mjs <stage>\`, cclaw validates those
|
|
67
67
|
bullets, appends unique entries to \`.cclaw/knowledge.jsonl\`, and stamps a
|
|
68
68
|
harvest marker in the artifact.
|
|
69
69
|
|
|
@@ -43,7 +43,7 @@ export const DESIGN = {
|
|
|
43
43
|
"If a section has no issues, say 'No issues found' and move on.",
|
|
44
44
|
"Do not skip failure-mode mapping.",
|
|
45
45
|
"For design baseline approval: present the full baseline. **STOP.** Do NOT proceed until user explicitly approves the design.",
|
|
46
|
-
"**STOP BEFORE ADVANCE.** Mandatory delegation `planner` must be marked completed or explicitly waived in `.cclaw/state/delegation-log.json`. Then close the stage via `
|
|
46
|
+
"**STOP BEFORE ADVANCE.** Mandatory delegation `planner` must be marked completed or explicitly waived in `.cclaw/state/delegation-log.json`. Then close the stage via `node .cclaw/hooks/stage-complete.mjs design` (do not hand-edit `.cclaw/state/flow-state.json`).",
|
|
47
47
|
"Take a firm position on every recommendation. Do NOT hedge with 'it depends' or 'you could do either'. State your opinion, then justify it.",
|
|
48
48
|
"Use pushback patterns for weak framing: if the user says 'it's just a small change', respond with 'small changes to shared interfaces have outsized blast radius — let's map it'. If 'we'll refactor later', respond with 'later never comes — show me the refactor ticket or do it now'.",
|
|
49
49
|
"When the user's proposed architecture is suboptimal, say so directly. Offer the alternative with concrete trade-offs, do not bury criticism in praise.",
|
|
@@ -29,7 +29,7 @@ export const PLAN = {
|
|
|
29
29
|
"Map scope Locked Decisions — every D-XX from scope is referenced by at least one plan task (or explicitly marked deferred with reason).",
|
|
30
30
|
"Run anti-placeholder + anti-scope-reduction scans — block `TODO/TBD/...` and phrasing like `v1`, `for now`, `later` for locked boundaries.",
|
|
31
31
|
"Define checkpoints — mark points where progress should be validated before continuing.",
|
|
32
|
-
"WAIT_FOR_CONFIRM — write plan artifact and explicitly pause. **STOP.** Do NOT proceed until user confirms. Then close the stage with `
|
|
32
|
+
"WAIT_FOR_CONFIRM — write plan artifact and explicitly pause. **STOP.** Do NOT proceed until user confirms. Then close the stage with `node .cclaw/hooks/stage-complete.mjs plan` and tell user to run `/cc-next`."
|
|
33
33
|
],
|
|
34
34
|
interactionProtocol: [
|
|
35
35
|
"Plan in read-only mode relative to implementation.",
|
|
@@ -39,7 +39,7 @@ export const PLAN = {
|
|
|
39
39
|
"Preserve locked scope boundaries: no silent scope reduction language in task rows.",
|
|
40
40
|
"Enforce WAIT_FOR_CONFIRM: present the plan summary with options (A) Approve / (B) Revise / (C) Reject.",
|
|
41
41
|
"**STOP.** Do NOT proceed until user explicitly approves.",
|
|
42
|
-
"**STOP BEFORE ADVANCE.** Mandatory delegation `planner` must be marked completed or explicitly waived in `.cclaw/state/delegation-log.json`. Then close the stage via `
|
|
42
|
+
"**STOP BEFORE ADVANCE.** Mandatory delegation `planner` must be marked completed or explicitly waived in `.cclaw/state/delegation-log.json`. Then close the stage via `node .cclaw/hooks/stage-complete.mjs plan` and tell the user to run `/cc-next`."
|
|
43
43
|
],
|
|
44
44
|
process: [
|
|
45
45
|
"Build dependency graph and ordered slices.",
|
|
@@ -42,7 +42,7 @@ export const SCOPE = {
|
|
|
42
42
|
"Once the user accepts or rejects a recommendation, commit fully. Do not re-argue.",
|
|
43
43
|
"Produce a clean scope summary after all issues are resolved.",
|
|
44
44
|
"**STOP.** Wait for explicit user approval of scope contract before advancing to design.",
|
|
45
|
-
"**STOP BEFORE ADVANCE.** Mandatory delegation `planner` must be marked completed or explicitly waived in `.cclaw/state/delegation-log.json`. Then close the stage via `
|
|
45
|
+
"**STOP BEFORE ADVANCE.** Mandatory delegation `planner` must be marked completed or explicitly waived in `.cclaw/state/delegation-log.json`. Then close the stage via `node .cclaw/hooks/stage-complete.mjs scope` (do not hand-edit `.cclaw/state/flow-state.json`)."
|
|
46
46
|
],
|
|
47
47
|
process: [
|
|
48
48
|
"Run premise challenge and existing-solution leverage check.",
|
package/dist/doctor.js
CHANGED
|
@@ -484,11 +484,11 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
484
484
|
: `no deprecated "tddTestGlobs" key detected in ${RUNTIME_ROOT}/config.yaml`
|
|
485
485
|
});
|
|
486
486
|
const expectedMode = parsedConfig.promptGuardMode === "strict" ? "strict" : "advisory";
|
|
487
|
-
const promptGuardPath = path.join(projectRoot, RUNTIME_ROOT, "hooks", "
|
|
487
|
+
const promptGuardPath = path.join(projectRoot, RUNTIME_ROOT, "hooks", "run-hook.mjs");
|
|
488
488
|
let promptGuardModeOk = false;
|
|
489
489
|
if (await exists(promptGuardPath)) {
|
|
490
490
|
const promptGuardContent = await fs.readFile(promptGuardPath, "utf8");
|
|
491
|
-
promptGuardModeOk = promptGuardContent.includes(`
|
|
491
|
+
promptGuardModeOk = promptGuardContent.includes(`const DEFAULT_PROMPT_GUARD_MODE = "${expectedMode}"`);
|
|
492
492
|
}
|
|
493
493
|
checks.push({
|
|
494
494
|
name: "hook:prompt_guard:mode",
|
|
@@ -496,13 +496,13 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
496
496
|
details: `${promptGuardPath} must match promptGuardMode=${expectedMode}`
|
|
497
497
|
});
|
|
498
498
|
if (parsedConfig.gitHookGuards === true) {
|
|
499
|
-
const runtimePreCommit = path.join(projectRoot, RUNTIME_ROOT, "hooks", "git", "pre-commit.
|
|
500
|
-
const runtimePrePush = path.join(projectRoot, RUNTIME_ROOT, "hooks", "git", "pre-push.
|
|
499
|
+
const runtimePreCommit = path.join(projectRoot, RUNTIME_ROOT, "hooks", "git", "pre-commit.mjs");
|
|
500
|
+
const runtimePrePush = path.join(projectRoot, RUNTIME_ROOT, "hooks", "git", "pre-push.mjs");
|
|
501
501
|
const runtimeScriptsOk = (await exists(runtimePreCommit)) && (await exists(runtimePrePush));
|
|
502
502
|
checks.push({
|
|
503
503
|
name: "git_hooks:managed:runtime_scripts",
|
|
504
504
|
ok: runtimeScriptsOk,
|
|
505
|
-
details: `${RUNTIME_ROOT}/hooks/git/pre-commit.
|
|
505
|
+
details: `${RUNTIME_ROOT}/hooks/git/pre-commit.mjs and pre-push.mjs must exist when gitHookGuards=true`
|
|
506
506
|
});
|
|
507
507
|
const gitHooksDir = await resolveGitHooksDir(projectRoot);
|
|
508
508
|
if (!gitHooksDir) {
|
|
@@ -673,15 +673,9 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
673
673
|
}
|
|
674
674
|
// Hook scripts
|
|
675
675
|
for (const script of [
|
|
676
|
-
"
|
|
677
|
-
"
|
|
678
|
-
"
|
|
679
|
-
"run-hook.cmd",
|
|
680
|
-
"stage-complete.sh",
|
|
681
|
-
"pre-compact.sh",
|
|
682
|
-
"prompt-guard.sh",
|
|
683
|
-
"workflow-guard.sh",
|
|
684
|
-
"context-monitor.sh"
|
|
676
|
+
"run-hook.mjs",
|
|
677
|
+
"stage-complete.mjs",
|
|
678
|
+
"opencode-plugin.mjs"
|
|
685
679
|
]) {
|
|
686
680
|
const scriptPath = path.join(projectRoot, RUNTIME_ROOT, "hooks", script);
|
|
687
681
|
const scriptExists = await exists(scriptPath);
|
|
@@ -699,10 +693,13 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
699
693
|
catch {
|
|
700
694
|
executable = false;
|
|
701
695
|
}
|
|
696
|
+
const executableCheckOk = process.platform === "win32" ? true : executable;
|
|
702
697
|
checks.push({
|
|
703
698
|
name: `hook:script:${script}:executable`,
|
|
704
|
-
ok:
|
|
705
|
-
details:
|
|
699
|
+
ok: executableCheckOk,
|
|
700
|
+
details: process.platform === "win32"
|
|
701
|
+
? `${scriptPath} executable-bit check skipped on Windows`
|
|
702
|
+
: `${scriptPath} must be executable`
|
|
706
703
|
});
|
|
707
704
|
}
|
|
708
705
|
}
|
|
@@ -776,11 +773,11 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
776
773
|
const preCommands = collectHookCommands(hooks.PreToolUse);
|
|
777
774
|
const postCommands = collectHookCommands(hooks.PostToolUse);
|
|
778
775
|
const stopCommands = collectHookCommands(hooks.Stop);
|
|
779
|
-
const wiringOk = sessionCommands.some((cmd) => cmd.includes("session-start
|
|
780
|
-
preCommands.some((cmd) => cmd.includes("prompt-guard
|
|
781
|
-
preCommands.some((cmd) => cmd.includes("workflow-guard
|
|
782
|
-
postCommands.some((cmd) => cmd.includes("context-monitor
|
|
783
|
-
stopCommands.some((cmd) => cmd.includes("stop-checkpoint
|
|
776
|
+
const wiringOk = sessionCommands.some((cmd) => cmd.includes("session-start")) &&
|
|
777
|
+
preCommands.some((cmd) => cmd.includes("prompt-guard")) &&
|
|
778
|
+
preCommands.some((cmd) => cmd.includes("workflow-guard")) &&
|
|
779
|
+
postCommands.some((cmd) => cmd.includes("context-monitor")) &&
|
|
780
|
+
stopCommands.some((cmd) => cmd.includes("stop-checkpoint"));
|
|
784
781
|
checks.push({
|
|
785
782
|
name: "hook:wiring:claude",
|
|
786
783
|
ok: wiringOk,
|
|
@@ -809,11 +806,11 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
809
806
|
const preCommands = collectHookCommands(hooks.preToolUse);
|
|
810
807
|
const postCommands = collectHookCommands(hooks.postToolUse);
|
|
811
808
|
const stopCommands = collectHookCommands(hooks.stop);
|
|
812
|
-
const wiringOk = sessionCommands.some((cmd) => cmd.includes("session-start
|
|
813
|
-
preCommands.some((cmd) => cmd.includes("prompt-guard
|
|
814
|
-
preCommands.some((cmd) => cmd.includes("workflow-guard
|
|
815
|
-
postCommands.some((cmd) => cmd.includes("context-monitor
|
|
816
|
-
stopCommands.some((cmd) => cmd.includes("stop-checkpoint
|
|
809
|
+
const wiringOk = sessionCommands.some((cmd) => cmd.includes("session-start")) &&
|
|
810
|
+
preCommands.some((cmd) => cmd.includes("prompt-guard")) &&
|
|
811
|
+
preCommands.some((cmd) => cmd.includes("workflow-guard")) &&
|
|
812
|
+
postCommands.some((cmd) => cmd.includes("context-monitor")) &&
|
|
813
|
+
stopCommands.some((cmd) => cmd.includes("stop-checkpoint"));
|
|
817
814
|
checks.push({
|
|
818
815
|
name: "hook:wiring:cursor",
|
|
819
816
|
ok: wiringOk,
|
|
@@ -876,14 +873,14 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
876
873
|
const codexPreCmds = collectHookCommands(codexHooks.PreToolUse);
|
|
877
874
|
const codexPostCmds = collectHookCommands(codexHooks.PostToolUse);
|
|
878
875
|
const codexStopCmds = collectHookCommands(codexHooks.Stop);
|
|
879
|
-
const codexWiringOk = codexSessionCmds.some((cmd) => cmd.includes("session-start
|
|
880
|
-
codexUserPromptCmds.some((cmd) => cmd.includes("prompt-guard
|
|
881
|
-
codexUserPromptCmds.some((cmd) => cmd.includes("workflow-guard
|
|
876
|
+
const codexWiringOk = codexSessionCmds.some((cmd) => cmd.includes("session-start")) &&
|
|
877
|
+
codexUserPromptCmds.some((cmd) => cmd.includes("prompt-guard")) &&
|
|
878
|
+
codexUserPromptCmds.some((cmd) => cmd.includes("workflow-guard")) &&
|
|
882
879
|
codexUserPromptCmds.some((cmd) => cmd.includes("verify-current-state")) &&
|
|
883
|
-
codexPreCmds.some((cmd) => cmd.includes("prompt-guard
|
|
884
|
-
codexPreCmds.some((cmd) => cmd.includes("workflow-guard
|
|
885
|
-
codexPostCmds.some((cmd) => cmd.includes("context-monitor
|
|
886
|
-
codexStopCmds.some((cmd) => cmd.includes("stop-checkpoint
|
|
880
|
+
codexPreCmds.some((cmd) => cmd.includes("prompt-guard")) &&
|
|
881
|
+
codexPreCmds.some((cmd) => cmd.includes("workflow-guard")) &&
|
|
882
|
+
codexPostCmds.some((cmd) => cmd.includes("context-monitor")) &&
|
|
883
|
+
codexStopCmds.some((cmd) => cmd.includes("stop-checkpoint"));
|
|
887
884
|
checks.push({
|
|
888
885
|
name: "hook:wiring:codex",
|
|
889
886
|
ok: codexWiringOk,
|
|
@@ -963,10 +960,10 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
963
960
|
content.includes("event: async") &&
|
|
964
961
|
content.includes('"tool.execute.before"') &&
|
|
965
962
|
content.includes('"tool.execute.after"') &&
|
|
966
|
-
content.includes("prompt-guard
|
|
967
|
-
content.includes("workflow-guard
|
|
968
|
-
content.includes("context-monitor
|
|
969
|
-
content.includes("pre-compact
|
|
963
|
+
content.includes("prompt-guard") &&
|
|
964
|
+
content.includes("workflow-guard") &&
|
|
965
|
+
content.includes("context-monitor") &&
|
|
966
|
+
content.includes("pre-compact") &&
|
|
970
967
|
content.includes('"session.created"') &&
|
|
971
968
|
content.includes('"session.idle"') &&
|
|
972
969
|
content.includes('"session.resumed"') &&
|
|
@@ -981,7 +978,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
981
978
|
content.includes('"tool.execute.after": async');
|
|
982
979
|
precompactHookOk =
|
|
983
980
|
content.includes('eventType === "session.compacted"') &&
|
|
984
|
-
content.includes('runHookScript("pre-compact
|
|
981
|
+
content.includes('runHookScript("pre-compact"');
|
|
985
982
|
}
|
|
986
983
|
checks.push({
|
|
987
984
|
name: "lifecycle:opencode:rehydration_events",
|
|
@@ -996,7 +993,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
996
993
|
checks.push({
|
|
997
994
|
name: "hook:opencode:precompact_digest",
|
|
998
995
|
ok: precompactHookOk,
|
|
999
|
-
details: `${file} must run pre-compact
|
|
996
|
+
details: `${file} must run pre-compact on session.compacted before bootstrap refresh.`
|
|
1000
997
|
});
|
|
1001
998
|
const runtimeShape = await opencodePluginRuntimeShapeCheck(projectRoot);
|
|
1002
999
|
checks.push({
|
|
@@ -1011,34 +1008,48 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1011
1008
|
details: registration.details
|
|
1012
1009
|
});
|
|
1013
1010
|
}
|
|
1014
|
-
const hasBash = await commandAvailable("bash");
|
|
1015
1011
|
const hasNode = await commandAvailable("node");
|
|
1016
1012
|
const hasPython = await commandAvailable("python3");
|
|
1017
1013
|
const hasJq = await commandAvailable("jq");
|
|
1018
|
-
checks.push({
|
|
1019
|
-
name: "capability:required:bash",
|
|
1020
|
-
ok: hasBash,
|
|
1021
|
-
details: "bash is required to execute cclaw hook scripts"
|
|
1022
|
-
});
|
|
1023
1014
|
checks.push({
|
|
1024
1015
|
name: "capability:required:node",
|
|
1025
1016
|
ok: hasNode,
|
|
1026
1017
|
details: "node is required for cclaw runtime scripts and CLI wiring"
|
|
1027
1018
|
});
|
|
1028
|
-
checks.push({
|
|
1029
|
-
name: "capability:runtime:json_parser",
|
|
1030
|
-
ok: hasPython || hasJq,
|
|
1031
|
-
details: "at least one of python3 or jq must be available for hook JSON parsing fallbacks"
|
|
1032
|
-
});
|
|
1033
1019
|
checks.push({
|
|
1034
1020
|
name: "warning:capability:jq",
|
|
1035
1021
|
ok: true,
|
|
1036
|
-
details: hasJq
|
|
1022
|
+
details: hasJq
|
|
1023
|
+
? "jq available (optional)"
|
|
1024
|
+
: "warning: jq not found; Node hook runtime no longer depends on jq"
|
|
1037
1025
|
});
|
|
1038
1026
|
checks.push({
|
|
1039
1027
|
name: "warning:capability:python3",
|
|
1040
1028
|
ok: true,
|
|
1041
|
-
details: hasPython
|
|
1029
|
+
details: hasPython
|
|
1030
|
+
? "python3 available (optional)"
|
|
1031
|
+
: "warning: python3 not found; Node hook runtime no longer depends on python3"
|
|
1032
|
+
});
|
|
1033
|
+
const windowsHookConfigCandidates = [
|
|
1034
|
+
path.join(projectRoot, ".claude/hooks/hooks.json"),
|
|
1035
|
+
path.join(projectRoot, ".cursor/hooks.json"),
|
|
1036
|
+
path.join(projectRoot, ".codex/hooks.json")
|
|
1037
|
+
];
|
|
1038
|
+
const legacyDispatchFiles = [];
|
|
1039
|
+
for (const candidate of windowsHookConfigCandidates) {
|
|
1040
|
+
if (!(await exists(candidate)))
|
|
1041
|
+
continue;
|
|
1042
|
+
const content = await fs.readFile(candidate, "utf8");
|
|
1043
|
+
if (/run-hook\.cmd|bash\s+\.cclaw\/hooks\//u.test(content)) {
|
|
1044
|
+
legacyDispatchFiles.push(path.relative(projectRoot, candidate));
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
checks.push({
|
|
1048
|
+
name: "warning:windows:hook_dispatch_node_only",
|
|
1049
|
+
ok: legacyDispatchFiles.length === 0,
|
|
1050
|
+
details: legacyDispatchFiles.length === 0
|
|
1051
|
+
? "hook configs use node-dispatched .cclaw/hooks/run-hook.mjs commands"
|
|
1052
|
+
: `warning: legacy shell hook dispatch remains in ${legacyDispatchFiles.join(", ")}`
|
|
1042
1053
|
});
|
|
1043
1054
|
// Knowledge store exists (canonical JSONL, no markdown mirror)
|
|
1044
1055
|
checks.push({
|
package/dist/install.js
CHANGED
|
@@ -24,8 +24,7 @@ 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 {
|
|
28
|
-
import { contextMonitorScript, promptGuardScript, workflowGuardScript } from "./content/observe.js";
|
|
27
|
+
import { stageCompleteScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
|
|
29
28
|
import { nodeHookRuntimeScript } from "./content/node-hooks.js";
|
|
30
29
|
import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
|
|
31
30
|
import { decisionProtocolMarkdown, completionProtocolMarkdown, ethosProtocolMarkdown } from "./content/protocols.js";
|
|
@@ -73,32 +72,121 @@ async function resolveGitHooksDir(projectRoot) {
|
|
|
73
72
|
}
|
|
74
73
|
}
|
|
75
74
|
function managedGitRuntimeScript(hookName) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
return `#!/usr/bin/env node
|
|
76
|
+
// ${GIT_HOOK_MANAGED_MARKER}: runtime ${hookName}
|
|
77
|
+
import fs from "node:fs";
|
|
78
|
+
import path from "node:path";
|
|
79
|
+
import process from "node:process";
|
|
80
|
+
import { spawnSync } from "node:child_process";
|
|
81
|
+
|
|
82
|
+
const HOOK_NAME = ${JSON.stringify(hookName)};
|
|
83
|
+
const RUNTIME_ROOT = ${JSON.stringify(RUNTIME_ROOT)};
|
|
84
|
+
|
|
85
|
+
function runGit(args, cwd) {
|
|
86
|
+
const result = spawnSync("git", args, {
|
|
87
|
+
cwd,
|
|
88
|
+
encoding: "utf8",
|
|
89
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
status: typeof result.status === "number" ? result.status : 1,
|
|
93
|
+
stdout: typeof result.stdout === "string" ? result.stdout : ""
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resolveRepoRoot() {
|
|
98
|
+
const result = runGit(["rev-parse", "--show-toplevel"], process.cwd());
|
|
99
|
+
if (result.status === 0) {
|
|
100
|
+
const root = result.stdout.trim();
|
|
101
|
+
if (root.length > 0) return root;
|
|
102
|
+
}
|
|
103
|
+
return process.cwd();
|
|
104
|
+
}
|
|
82
105
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
[
|
|
106
|
+
function resolveChangedFiles(root) {
|
|
107
|
+
if (HOOK_NAME === "pre-commit") {
|
|
108
|
+
const result = runGit(["diff", "--cached", "--name-only"], root);
|
|
109
|
+
return result.status === 0 ? result.stdout : "";
|
|
110
|
+
}
|
|
111
|
+
const upstreamResult = runGit(["diff", "--name-only", "@{upstream}...HEAD"], root);
|
|
112
|
+
if (upstreamResult.status === 0) {
|
|
113
|
+
return upstreamResult.stdout;
|
|
114
|
+
}
|
|
115
|
+
const fallback = runGit(["diff", "--name-only", "HEAD~1...HEAD"], root);
|
|
116
|
+
return fallback.status === 0 ? fallback.stdout : "";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const root = resolveRepoRoot();
|
|
120
|
+
const runtimeHook = path.join(root, RUNTIME_ROOT, "hooks", "run-hook.mjs");
|
|
121
|
+
if (!fs.existsSync(runtimeHook)) {
|
|
122
|
+
process.exit(0);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const changedFiles = resolveChangedFiles(root)
|
|
126
|
+
.split(/\\r?\\n/gu)
|
|
127
|
+
.map((line) => line.trim())
|
|
128
|
+
.filter((line) => line.length > 0);
|
|
129
|
+
if (changedFiles.length === 0) {
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
86
132
|
|
|
87
|
-
|
|
88
|
-
|
|
133
|
+
const payload = JSON.stringify({
|
|
134
|
+
tool_name: "Write",
|
|
135
|
+
tool_input: {
|
|
136
|
+
path: changedFiles.join("\\n"),
|
|
137
|
+
paths: changedFiles
|
|
138
|
+
}
|
|
139
|
+
});
|
|
89
140
|
|
|
90
|
-
|
|
141
|
+
const result = spawnSync(process.execPath, [runtimeHook, "prompt-guard"], {
|
|
142
|
+
cwd: root,
|
|
143
|
+
env: process.env,
|
|
144
|
+
input: payload,
|
|
145
|
+
encoding: "utf8",
|
|
146
|
+
stdio: ["pipe", "ignore", "inherit"]
|
|
147
|
+
});
|
|
148
|
+
process.exit(typeof result.status === "number" ? result.status : 1);
|
|
91
149
|
`;
|
|
92
150
|
}
|
|
93
151
|
function managedGitRelayHook(hookName) {
|
|
94
|
-
return `#!/usr/bin/env
|
|
95
|
-
|
|
96
|
-
|
|
152
|
+
return `#!/usr/bin/env node
|
|
153
|
+
// ${GIT_HOOK_MANAGED_MARKER}: relay ${hookName}
|
|
154
|
+
import fs from "node:fs";
|
|
155
|
+
import path from "node:path";
|
|
156
|
+
import process from "node:process";
|
|
157
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
158
|
+
|
|
159
|
+
const RUNTIME_REL_DIR = ${JSON.stringify(GIT_HOOK_RUNTIME_REL_DIR)};
|
|
160
|
+
const HOOK_NAME = ${JSON.stringify(hookName)};
|
|
161
|
+
|
|
162
|
+
function resolveRepoRoot() {
|
|
163
|
+
const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
|
|
164
|
+
cwd: process.cwd(),
|
|
165
|
+
encoding: "utf8",
|
|
166
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
167
|
+
});
|
|
168
|
+
if (typeof result.status === "number" && result.status === 0) {
|
|
169
|
+
const root = (result.stdout || "").trim();
|
|
170
|
+
if (root.length > 0) return root;
|
|
171
|
+
}
|
|
172
|
+
return process.cwd();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const root = resolveRepoRoot();
|
|
176
|
+
const runtimeHook = path.join(root, RUNTIME_REL_DIR, HOOK_NAME + ".mjs");
|
|
177
|
+
if (!fs.existsSync(runtimeHook)) {
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
97
180
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
181
|
+
const child = spawn(process.execPath, [runtimeHook, ...process.argv.slice(2)], {
|
|
182
|
+
cwd: root,
|
|
183
|
+
env: process.env,
|
|
184
|
+
stdio: "inherit"
|
|
185
|
+
});
|
|
186
|
+
child.on("error", () => process.exit(1));
|
|
187
|
+
child.on("close", (code, signal) => {
|
|
188
|
+
process.exit(signal ? 1 : typeof code === "number" ? code : 1);
|
|
189
|
+
});
|
|
102
190
|
`;
|
|
103
191
|
}
|
|
104
192
|
async function removeManagedGitHookRelays(projectRoot) {
|
|
@@ -141,7 +229,7 @@ async function syncManagedGitHooks(projectRoot, config) {
|
|
|
141
229
|
const runtimeGitHooksDir = path.join(projectRoot, GIT_HOOK_RUNTIME_REL_DIR);
|
|
142
230
|
await ensureDir(runtimeGitHooksDir);
|
|
143
231
|
for (const hookName of ["pre-commit", "pre-push"]) {
|
|
144
|
-
const runtimePathForHook = path.join(runtimeGitHooksDir, `${hookName}.
|
|
232
|
+
const runtimePathForHook = path.join(runtimeGitHooksDir, `${hookName}.mjs`);
|
|
145
233
|
await writeFileSafe(runtimePathForHook, managedGitRuntimeScript(hookName));
|
|
146
234
|
try {
|
|
147
235
|
await fs.chmod(runtimePathForHook, 0o755);
|
|
@@ -631,22 +719,7 @@ async function writeHooks(projectRoot, config) {
|
|
|
631
719
|
mode: config.ironLaws?.mode,
|
|
632
720
|
strictLaws: config.ironLaws?.strictLaws
|
|
633
721
|
}), null, 2)}\n`);
|
|
634
|
-
await writeFileSafe(path.join(hooksDir, "
|
|
635
|
-
await writeFileSafe(path.join(hooksDir, "session-start.sh"), sessionStartScript());
|
|
636
|
-
await writeFileSafe(path.join(hooksDir, "stop-checkpoint.sh"), stopCheckpointScript());
|
|
637
|
-
await writeFileSafe(path.join(hooksDir, "run-hook.cmd"), runHookDispatcherScript());
|
|
638
|
-
await writeFileSafe(path.join(hooksDir, "stage-complete.sh"), stageCompleteScript());
|
|
639
|
-
await writeFileSafe(path.join(hooksDir, "pre-compact.sh"), preCompactScript());
|
|
640
|
-
await writeFileSafe(path.join(hooksDir, "prompt-guard.sh"), promptGuardScript({
|
|
641
|
-
strictMode: config.promptGuardMode === "strict"
|
|
642
|
-
}));
|
|
643
|
-
await writeFileSafe(path.join(hooksDir, "workflow-guard.sh"), workflowGuardScript({
|
|
644
|
-
workflowGuardMode: config.strictness ?? "advisory",
|
|
645
|
-
tddEnforcementMode: config.tddEnforcement ?? "advisory",
|
|
646
|
-
tddTestPathPatterns: config.tdd?.testPathPatterns ?? config.tddTestGlobs,
|
|
647
|
-
tddProductionPathPatterns: config.tdd?.productionPathPatterns
|
|
648
|
-
}));
|
|
649
|
-
await writeFileSafe(path.join(hooksDir, "context-monitor.sh"), contextMonitorScript());
|
|
722
|
+
await writeFileSafe(path.join(hooksDir, "stage-complete.mjs"), stageCompleteScript());
|
|
650
723
|
await writeFileSafe(path.join(hooksDir, "run-hook.mjs"), nodeHookRuntimeScript({
|
|
651
724
|
promptGuardMode: config.promptGuardMode ?? config.strictness ?? "advisory",
|
|
652
725
|
workflowGuardMode: config.strictness ?? "advisory",
|
|
@@ -658,15 +731,7 @@ async function writeHooks(projectRoot, config) {
|
|
|
658
731
|
await writeFileSafe(path.join(hooksDir, "opencode-plugin.mjs"), opencodePluginSource);
|
|
659
732
|
try {
|
|
660
733
|
for (const script of [
|
|
661
|
-
"
|
|
662
|
-
"session-start.sh",
|
|
663
|
-
"stop-checkpoint.sh",
|
|
664
|
-
"run-hook.cmd",
|
|
665
|
-
"stage-complete.sh",
|
|
666
|
-
"pre-compact.sh",
|
|
667
|
-
"prompt-guard.sh",
|
|
668
|
-
"workflow-guard.sh",
|
|
669
|
-
"context-monitor.sh",
|
|
734
|
+
"stage-complete.mjs",
|
|
670
735
|
"run-hook.mjs",
|
|
671
736
|
"opencode-plugin.mjs"
|
|
672
737
|
]) {
|
|
@@ -1151,7 +1216,16 @@ async function cleanLegacyArtifacts(projectRoot) {
|
|
|
1151
1216
|
runtimePath(projectRoot, "observations.jsonl"),
|
|
1152
1217
|
runtimePath(projectRoot, "hooks", "observe.sh"),
|
|
1153
1218
|
runtimePath(projectRoot, "hooks", "summarize-observations.sh"),
|
|
1154
|
-
runtimePath(projectRoot, "hooks", "summarize-observations.mjs")
|
|
1219
|
+
runtimePath(projectRoot, "hooks", "summarize-observations.mjs"),
|
|
1220
|
+
runtimePath(projectRoot, "hooks", "_lib.sh"),
|
|
1221
|
+
runtimePath(projectRoot, "hooks", "session-start.sh"),
|
|
1222
|
+
runtimePath(projectRoot, "hooks", "stop-checkpoint.sh"),
|
|
1223
|
+
runtimePath(projectRoot, "hooks", "run-hook.cmd"),
|
|
1224
|
+
runtimePath(projectRoot, "hooks", "stage-complete.sh"),
|
|
1225
|
+
runtimePath(projectRoot, "hooks", "pre-compact.sh"),
|
|
1226
|
+
runtimePath(projectRoot, "hooks", "prompt-guard.sh"),
|
|
1227
|
+
runtimePath(projectRoot, "hooks", "workflow-guard.sh"),
|
|
1228
|
+
runtimePath(projectRoot, "hooks", "context-monitor.sh")
|
|
1155
1229
|
]) {
|
|
1156
1230
|
try {
|
|
1157
1231
|
await fs.rm(legacyRuntimeFile, { force: true });
|
|
@@ -1355,13 +1429,10 @@ function stripManagedHookCommands(value) {
|
|
|
1355
1429
|
}
|
|
1356
1430
|
function isManagedRuntimeHookCommand(command) {
|
|
1357
1431
|
const normalized = command.trim().replace(/\s+/gu, " ");
|
|
1358
|
-
if (/(^|\s)(?:
|
|
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)) {
|
|
1432
|
+
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)) {
|
|
1361
1433
|
return true;
|
|
1362
1434
|
}
|
|
1363
|
-
// Codex UserPromptSubmit non-blocking state nudge
|
|
1364
|
-
// legacy shell and newer Node wrappers call this internal command.
|
|
1435
|
+
// Codex UserPromptSubmit non-blocking state nudge.
|
|
1365
1436
|
return /internal verify-current-state(?:\s|$)/u.test(normalized);
|
|
1366
1437
|
}
|
|
1367
1438
|
async function removeManagedHookEntries(hookFilePath) {
|
package/dist/policy.js
CHANGED
|
@@ -180,19 +180,19 @@ export async function policyChecks(projectRoot, options = {}) {
|
|
|
180
180
|
{ file: runtimeFile("contexts/execution.md"), needle: "Context Mode: execution", name: "context_mode:execution" },
|
|
181
181
|
{ file: runtimeFile("contexts/review.md"), needle: "Context Mode: review", name: "context_mode:review" },
|
|
182
182
|
{ file: runtimeFile("contexts/incident.md"), needle: "Context Mode: incident", name: "context_mode:incident" },
|
|
183
|
-
{ file: runtimeFile("hooks/
|
|
184
|
-
{ file: runtimeFile("hooks/
|
|
185
|
-
{ file: runtimeFile("hooks/
|
|
186
|
-
{ file: runtimeFile("hooks/
|
|
187
|
-
{ file: runtimeFile("hooks/
|
|
188
|
-
{ file: runtimeFile("hooks/
|
|
189
|
-
{ file: runtimeFile("hooks/
|
|
190
|
-
{ file: runtimeFile("hooks/
|
|
191
|
-
{ file: runtimeFile("hooks/
|
|
192
|
-
{ file: runtimeFile("hooks/
|
|
193
|
-
{ file: runtimeFile("hooks/
|
|
183
|
+
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "activeRunId", name: "hooks:session_start:active_run" },
|
|
184
|
+
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "checkpoint.json", name: "hooks:session_start:checkpoint_ref" },
|
|
185
|
+
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "stage-activity.jsonl", name: "hooks:session_start:activity_ref" },
|
|
186
|
+
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "suggestion-memory.json", name: "hooks:session_start:suggestion_memory" },
|
|
187
|
+
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "context-warnings.jsonl", name: "hooks:session_start:context_warning_ref" },
|
|
188
|
+
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "checkpoint.json", name: "hooks:stop:checkpoint_write" },
|
|
189
|
+
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "write_to_cclaw_runtime", name: "hooks:guard:risky_write_advisory" },
|
|
190
|
+
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "stage_invocation_without_recent_flow_read", name: "hooks:workflow_guard:flow_read_reason" },
|
|
191
|
+
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "stage_jump_", name: "hooks:workflow_guard:stage_jump_reason" },
|
|
192
|
+
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "tdd_write_without_open_red", name: "hooks:workflow_guard:tdd_red_first" },
|
|
193
|
+
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "context remaining is", name: "hooks:context:threshold_warning" },
|
|
194
194
|
{ file: runtimeFile("hooks/opencode-plugin.mjs"), needle: "activeRunId", name: "hooks:opencode:active_run" },
|
|
195
|
-
{ file: runtimeFile("hooks/
|
|
195
|
+
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "Knowledge digest", name: "hooks:session_start:knowledge_digest" },
|
|
196
196
|
{ file: runtimeFile("hooks/opencode-plugin.mjs"), needle: "Knowledge digest", name: "hooks:opencode:knowledge_digest" }
|
|
197
197
|
];
|
|
198
198
|
if (activeHarnesses.has("opencode")) {
|
|
@@ -203,7 +203,7 @@ export async function policyChecks(projectRoot, options = {}) {
|
|
|
203
203
|
});
|
|
204
204
|
utilitySkillChecks.push({
|
|
205
205
|
file: ".opencode/plugins/cclaw-plugin.mjs",
|
|
206
|
-
needle: "workflow-guard
|
|
206
|
+
needle: "workflow-guard",
|
|
207
207
|
name: "hooks:opencode:deployed_workflow_guard"
|
|
208
208
|
});
|
|
209
209
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cclaw-cli",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.10",
|
|
4
4
|
"description": "Installer-first flow toolkit for coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,10 +23,9 @@
|
|
|
23
23
|
"test:coverage": "vitest run --coverage",
|
|
24
24
|
"test:mutation": "stryker run",
|
|
25
25
|
"smoke:runtime": "npm run build && node scripts/smoke-init.mjs",
|
|
26
|
-
"lint:hooks": "npm run build && node scripts/lint-generated-hooks.mjs",
|
|
27
26
|
"build:harness-docs": "npm run build && node scripts/build-harness-docs.mjs",
|
|
28
27
|
"build:plugin-manifests": "npm run build && node scripts/build-plugin-manifests.mjs",
|
|
29
|
-
"release:check": "npm run build && node scripts/verify-bin-executable.mjs && npm run test && node scripts/
|
|
28
|
+
"release:check": "npm run build && node scripts/verify-bin-executable.mjs && npm run test && node scripts/build-plugin-manifests.mjs && npm pack --dry-run && node scripts/smoke-init.mjs",
|
|
30
29
|
"release:bundle": "npm run release:check && npm pack"
|
|
31
30
|
},
|
|
32
31
|
"keywords": [
|