cclaw-cli 0.48.24 → 0.48.25
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/content/doctor-references.js +2 -1
- package/dist/content/hooks.d.ts +1 -0
- package/dist/content/hooks.js +30 -0
- package/dist/content/meta-skill.js +15 -0
- package/dist/content/node-hooks.js +110 -15
- package/dist/content/observe.js +3 -5
- package/dist/content/skills.js +36 -2
- package/dist/doctor.js +3 -2
- package/dist/install.js +4 -2
- package/package.json +1 -1
|
@@ -124,7 +124,8 @@ Reference docs for \`cclaw doctor\` checks.
|
|
|
124
124
|
|
|
125
125
|
Earlier releases relied on \`bash\` to execute generated shell hooks and on
|
|
126
126
|
\`python3\`/\`jq\` as JSON fallback parsers. Node-only mode removes both: hooks
|
|
127
|
-
dispatch through
|
|
127
|
+
dispatch through \`.cclaw/hooks/run-hook.cmd <hook-name>\` (which forwards to
|
|
128
|
+
Node), so these tools
|
|
128
129
|
are no longer part of the supported runtime contract.
|
|
129
130
|
|
|
130
131
|
## Typical fixes
|
package/dist/content/hooks.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare function stageCompleteScript(): string;
|
|
2
|
+
export declare function runHookCmdScript(): string;
|
|
2
3
|
export { claudeHooksJsonWithObservation as claudeHooksJson } from "./observe.js";
|
|
3
4
|
export { cursorHooksJsonWithObservation as cursorHooksJson } from "./observe.js";
|
|
4
5
|
export { codexHooksJsonWithObservation as codexHooksJson } from "./observe.js";
|
package/dist/content/hooks.js
CHANGED
|
@@ -120,6 +120,36 @@ async function main() {
|
|
|
120
120
|
void main();
|
|
121
121
|
`;
|
|
122
122
|
}
|
|
123
|
+
export function runHookCmdScript() {
|
|
124
|
+
return `: << 'CMDBLOCK'
|
|
125
|
+
@echo off
|
|
126
|
+
REM Cross-platform wrapper for cclaw Node hook runtime.
|
|
127
|
+
REM Windows executes this batch block; Unix shells treat it as a heredoc comment.
|
|
128
|
+
if "%~1"=="" (
|
|
129
|
+
echo [cclaw] run-hook.cmd: missing hook name >&2
|
|
130
|
+
exit /b 1
|
|
131
|
+
)
|
|
132
|
+
set "HOOK_DIR=%~dp0"
|
|
133
|
+
set "RUNTIME=%HOOK_DIR%run-hook.mjs"
|
|
134
|
+
where node >nul 2>nul
|
|
135
|
+
if %ERRORLEVEL% neq 0 (
|
|
136
|
+
REM Best-effort: missing node should not block harness execution loops.
|
|
137
|
+
exit /b 0
|
|
138
|
+
)
|
|
139
|
+
node "%RUNTIME%" %*
|
|
140
|
+
exit /b %ERRORLEVEL%
|
|
141
|
+
CMDBLOCK
|
|
142
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
143
|
+
if [ "$#" -lt 1 ]; then
|
|
144
|
+
echo "[cclaw] run-hook.cmd: missing hook name" >&2
|
|
145
|
+
exit 1
|
|
146
|
+
fi
|
|
147
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
148
|
+
exit 0
|
|
149
|
+
fi
|
|
150
|
+
exec node "\${SCRIPT_DIR}/run-hook.mjs" "$@"
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
123
153
|
export { claudeHooksJsonWithObservation as claudeHooksJson } from "./observe.js";
|
|
124
154
|
export { cursorHooksJsonWithObservation as cursorHooksJson } from "./observe.js";
|
|
125
155
|
export { codexHooksJsonWithObservation as codexHooksJson } from "./observe.js";
|
|
@@ -40,6 +40,15 @@ If the current stage is ambiguous because \`flow-state.json\` is missing
|
|
|
40
40
|
or corrupt, stop and route through \`/cc\` before any substantive
|
|
41
41
|
response.
|
|
42
42
|
|
|
43
|
+
## Red Flags (stop and re-route)
|
|
44
|
+
|
|
45
|
+
If you think any of these, stop and follow the routing flow:
|
|
46
|
+
|
|
47
|
+
- "This looks simple, I can skip the stage." -> No. Route through \`/cc\`.
|
|
48
|
+
- "I can answer from memory without loading the active stage skill." -> No. Load the skill first.
|
|
49
|
+
- "Hook guard warned, but I can ignore it." -> No. Resolve the warning before continuing.
|
|
50
|
+
- "I'll edit \`.cclaw/state\` directly to move faster." -> No. Use managed commands only.
|
|
51
|
+
|
|
43
52
|
## Routing flow
|
|
44
53
|
|
|
45
54
|
\`\`\`
|
|
@@ -72,6 +81,12 @@ Before stage work:
|
|
|
72
81
|
2. If active stage exists, continue with \`/cc\` or \`/cc-next\`.
|
|
73
82
|
3. Do not jump directly to stage-specific commands.
|
|
74
83
|
|
|
84
|
+
## Platform reliability notes
|
|
85
|
+
|
|
86
|
+
- Managed hook dispatch uses \`.cclaw/hooks/run-hook.cmd\` (cross-platform wrapper).
|
|
87
|
+
- If hooks fail due missing runtime deps (for example \`node\` not on \`PATH\`), run \`cclaw doctor\` before continuing.
|
|
88
|
+
- Prefer cross-platform commands in artifacts/examples (\`npm test\`, \`pnpm test\`, \`python -m pytest\`, etc.) over shell-specific aliases whenever possible.
|
|
89
|
+
|
|
75
90
|
## Stage quick map
|
|
76
91
|
|
|
77
92
|
brainstorm -> scope -> design -> spec -> plan -> tdd -> review -> ship
|
|
@@ -307,10 +307,12 @@ async function readStdin() {
|
|
|
307
307
|
});
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
-
async function runCclawInternal(root, args) {
|
|
310
|
+
async function runCclawInternal(root, args, options = {}) {
|
|
311
311
|
return await new Promise((resolve) => {
|
|
312
312
|
const isWindows = process.platform === "win32";
|
|
313
|
+
const captureStdout = options && options.captureStdout === true;
|
|
313
314
|
let settled = false;
|
|
315
|
+
let stdout = "";
|
|
314
316
|
let stderr = "";
|
|
315
317
|
const finalize = (value) => {
|
|
316
318
|
if (settled) return;
|
|
@@ -325,18 +327,27 @@ async function runCclawInternal(root, args) {
|
|
|
325
327
|
{
|
|
326
328
|
cwd: root,
|
|
327
329
|
env: process.env,
|
|
328
|
-
stdio: ["ignore", "ignore", "pipe"]
|
|
330
|
+
stdio: ["ignore", captureStdout ? "pipe" : "ignore", "pipe"]
|
|
329
331
|
}
|
|
330
332
|
);
|
|
331
333
|
} catch (error) {
|
|
332
334
|
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
333
335
|
finalize({
|
|
334
336
|
code: 1,
|
|
337
|
+
stdout,
|
|
335
338
|
stderr,
|
|
336
339
|
missingBinary: code === "ENOENT" || (isWindows && code === "EINVAL")
|
|
337
340
|
});
|
|
338
341
|
return;
|
|
339
342
|
}
|
|
343
|
+
if (captureStdout) {
|
|
344
|
+
child.stdout?.on("data", (chunk) => {
|
|
345
|
+
stdout += String(chunk ?? "");
|
|
346
|
+
if (stdout.length > 16000) {
|
|
347
|
+
stdout = stdout.slice(-16000);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
340
351
|
child.stderr?.on("data", (chunk) => {
|
|
341
352
|
stderr += String(chunk ?? "");
|
|
342
353
|
if (stderr.length > 8000) {
|
|
@@ -347,6 +358,7 @@ async function runCclawInternal(root, args) {
|
|
|
347
358
|
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
348
359
|
finalize({
|
|
349
360
|
code: 1,
|
|
361
|
+
stdout,
|
|
350
362
|
stderr,
|
|
351
363
|
missingBinary: code === "ENOENT" || (isWindows && code === "EINVAL")
|
|
352
364
|
});
|
|
@@ -355,6 +367,7 @@ async function runCclawInternal(root, args) {
|
|
|
355
367
|
if (signal) {
|
|
356
368
|
finalize({
|
|
357
369
|
code: 1,
|
|
370
|
+
stdout,
|
|
358
371
|
stderr,
|
|
359
372
|
missingBinary: false
|
|
360
373
|
});
|
|
@@ -366,6 +379,7 @@ async function runCclawInternal(root, args) {
|
|
|
366
379
|
: false;
|
|
367
380
|
finalize({
|
|
368
381
|
code: typeof code === "number" ? code : 1,
|
|
382
|
+
stdout,
|
|
369
383
|
stderr,
|
|
370
384
|
missingBinary
|
|
371
385
|
});
|
|
@@ -380,6 +394,32 @@ function detectHarness(env) {
|
|
|
380
394
|
return "codex";
|
|
381
395
|
}
|
|
382
396
|
|
|
397
|
+
function hookEventNameForOutput(hookName) {
|
|
398
|
+
if (hookName === "session-start") return "SessionStart";
|
|
399
|
+
if (hookName === "prompt-guard") return "PreToolUse";
|
|
400
|
+
if (hookName === "workflow-guard") return "PreToolUse";
|
|
401
|
+
if (hookName === "context-monitor") return "PostToolUse";
|
|
402
|
+
if (hookName === "stop-checkpoint") return "Stop";
|
|
403
|
+
if (hookName === "pre-compact") return "PreCompact";
|
|
404
|
+
if (hookName === "verify-current-state") return "UserPromptSubmit";
|
|
405
|
+
return "SessionStart";
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function emitAdvisoryContext(runtime, hookName, note) {
|
|
409
|
+
const normalized = normalizeText(note);
|
|
410
|
+
if (normalized.length === 0) return;
|
|
411
|
+
if (runtime.harness === "claude" || runtime.harness === "codex") {
|
|
412
|
+
runtime.writeJson({
|
|
413
|
+
hookSpecificOutput: {
|
|
414
|
+
hookEventName: hookEventNameForOutput(hookName),
|
|
415
|
+
additionalContext: normalized
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
runtime.writeJson({ additional_context: normalized });
|
|
421
|
+
}
|
|
422
|
+
|
|
383
423
|
async function detectRoot(env) {
|
|
384
424
|
const candidates = [
|
|
385
425
|
env.CCLAW_PROJECT_ROOT,
|
|
@@ -976,21 +1016,55 @@ async function handleSessionStart(runtime) {
|
|
|
976
1016
|
// where lifting becomes relevant; earlier stages update the file silently.
|
|
977
1017
|
let compoundReadinessLine = "";
|
|
978
1018
|
try {
|
|
979
|
-
|
|
980
|
-
const
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1019
|
+
let readiness = null;
|
|
1020
|
+
const internalReadiness = await runCclawInternal(
|
|
1021
|
+
runtime.root,
|
|
1022
|
+
["compound-readiness", "--json"],
|
|
1023
|
+
{ captureStdout: true }
|
|
1024
|
+
);
|
|
1025
|
+
if (internalReadiness.code === 0 && internalReadiness.stdout.trim().length > 0) {
|
|
1026
|
+
try {
|
|
1027
|
+
const parsed = JSON.parse(internalReadiness.stdout);
|
|
1028
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1029
|
+
readiness = parsed;
|
|
1030
|
+
}
|
|
1031
|
+
} catch {
|
|
1032
|
+
readiness = null;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
if (!readiness) {
|
|
1036
|
+
const archivedRunsCount = await countArchivedRunsInline(runtime.root);
|
|
1037
|
+
readiness = await computeCompoundReadinessInline(runtime.root, {
|
|
1038
|
+
prereadRaw: knowledgeRaw,
|
|
1039
|
+
...(typeof archivedRunsCount === "number" ? { archivedRunsCount } : {})
|
|
1040
|
+
});
|
|
1041
|
+
await writeJsonFile(path.join(stateDir, "compound-readiness.json"), readiness);
|
|
1042
|
+
}
|
|
1043
|
+
const readinessObj = toObject(readiness) || {};
|
|
1044
|
+
const ready = Array.isArray(readinessObj.ready) ? readinessObj.ready : [];
|
|
1045
|
+
const readyCount =
|
|
1046
|
+
typeof readinessObj.readyCount === "number" && Number.isFinite(readinessObj.readyCount)
|
|
1047
|
+
? Math.trunc(readinessObj.readyCount)
|
|
1048
|
+
: ready.length;
|
|
1049
|
+
const clusterCount =
|
|
1050
|
+
typeof readinessObj.clusterCount === "number" && Number.isFinite(readinessObj.clusterCount)
|
|
1051
|
+
? Math.trunc(readinessObj.clusterCount)
|
|
1052
|
+
: 0;
|
|
1053
|
+
const threshold =
|
|
1054
|
+
typeof readinessObj.threshold === "number" && Number.isFinite(readinessObj.threshold)
|
|
1055
|
+
? Math.trunc(readinessObj.threshold)
|
|
1056
|
+
: COMPOUND_RECURRENCE_THRESHOLD;
|
|
985
1057
|
if (state.currentStage === "review" || state.currentStage === "ship") {
|
|
986
|
-
if (
|
|
1058
|
+
if (readyCount === 0) {
|
|
987
1059
|
compoundReadinessLine = "Compound readiness: no candidates (clusters=" +
|
|
988
|
-
String(
|
|
1060
|
+
String(clusterCount) + ", threshold=" + String(threshold) + ")";
|
|
989
1061
|
} else {
|
|
990
|
-
const critical =
|
|
1062
|
+
const critical = ready.filter(
|
|
1063
|
+
(entry) => entry && typeof entry === "object" && entry.severity === "critical"
|
|
1064
|
+
).length;
|
|
991
1065
|
const criticalSuffix = critical > 0 ? " (critical=" + String(critical) + ")" : "";
|
|
992
|
-
compoundReadinessLine = "Compound readiness: clusters=" + String(
|
|
993
|
-
", ready=" + String(
|
|
1066
|
+
compoundReadinessLine = "Compound readiness: clusters=" + String(clusterCount) +
|
|
1067
|
+
", ready=" + String(readyCount) + criticalSuffix;
|
|
994
1068
|
}
|
|
995
1069
|
}
|
|
996
1070
|
} catch (err) {
|
|
@@ -1342,7 +1416,9 @@ async function handlePromptGuard(runtime) {
|
|
|
1342
1416
|
const reasons = [];
|
|
1343
1417
|
|
|
1344
1418
|
if (/^(write|edit|multiedit|multi_edit|delete|applypatch|runcommand|shell|terminal|execcommand)$/u.test(toolLower)) {
|
|
1345
|
-
|
|
1419
|
+
// Artifacts, runs, and knowledge writes are part of normal stage flow.
|
|
1420
|
+
// Guard only managed internals that should be mutated via installer/CLI.
|
|
1421
|
+
if (/\\.cclaw\\/(state|hooks|skills|commands|agents)/u.test(payloadLower)) {
|
|
1346
1422
|
reasons.push("write_to_cclaw_runtime");
|
|
1347
1423
|
}
|
|
1348
1424
|
}
|
|
@@ -1356,7 +1432,7 @@ async function handlePromptGuard(runtime) {
|
|
|
1356
1432
|
RUNTIME_ROOT +
|
|
1357
1433
|
" runtime (" +
|
|
1358
1434
|
reasons.join(",") +
|
|
1359
|
-
"). Prefer installer commands
|
|
1435
|
+
"). Prefer installer commands before mutating managed runtime internals (.cclaw/state|hooks|skills|commands|agents).";
|
|
1360
1436
|
await appendJsonLine(guardLog, {
|
|
1361
1437
|
ts: new Date().toISOString(),
|
|
1362
1438
|
harness: runtime.harness,
|
|
@@ -1364,6 +1440,8 @@ async function handlePromptGuard(runtime) {
|
|
|
1364
1440
|
reasons,
|
|
1365
1441
|
note
|
|
1366
1442
|
});
|
|
1443
|
+
const advisoryNote = mode === "strict" ? note + " Blocked by strict mode." : note;
|
|
1444
|
+
emitAdvisoryContext(runtime, "prompt-guard", advisoryNote);
|
|
1367
1445
|
if (mode === "strict") {
|
|
1368
1446
|
process.stderr.write("[cclaw] " + note + " (blocked by strict mode)\\n");
|
|
1369
1447
|
return 1;
|
|
@@ -1877,9 +1955,11 @@ async function handleWorkflowGuard(runtime) {
|
|
|
1877
1955
|
}
|
|
1878
1956
|
|
|
1879
1957
|
if (shouldBlock) {
|
|
1958
|
+
emitAdvisoryContext(runtime, "workflow-guard", note + " Blocked by workflow guard.");
|
|
1880
1959
|
process.stderr.write("[cclaw] " + note + " (blocked by workflow guard)\\n");
|
|
1881
1960
|
return 1;
|
|
1882
1961
|
}
|
|
1962
|
+
emitAdvisoryContext(runtime, "workflow-guard", note);
|
|
1883
1963
|
process.stderr.write("[cclaw] " + note + "\\n");
|
|
1884
1964
|
}
|
|
1885
1965
|
|
|
@@ -1978,6 +2058,7 @@ async function handleContextMonitor(runtime) {
|
|
|
1978
2058
|
remainingPercent,
|
|
1979
2059
|
note
|
|
1980
2060
|
});
|
|
2061
|
+
emitAdvisoryContext(runtime, "context-monitor", note);
|
|
1981
2062
|
process.stderr.write("[cclaw] " + note + "\\n");
|
|
1982
2063
|
nextAdvisoryBand = band;
|
|
1983
2064
|
nextAdvisoryAt = now.toISOString();
|
|
@@ -1998,10 +2079,24 @@ async function handleVerifyCurrentState(runtime) {
|
|
|
1998
2079
|
const mode = resolveStrictness();
|
|
1999
2080
|
const result = await runCclawInternal(runtime.root, ["verify-current-state", "--quiet"]);
|
|
2000
2081
|
if (result.missingBinary) {
|
|
2082
|
+
emitAdvisoryContext(
|
|
2083
|
+
runtime,
|
|
2084
|
+
"verify-current-state",
|
|
2085
|
+
"Cclaw verify-current-state requires cclaw binary on PATH."
|
|
2086
|
+
);
|
|
2001
2087
|
process.stderr.write("[cclaw] hook: cclaw binary is required for verify-current-state\\n");
|
|
2002
2088
|
return 1;
|
|
2003
2089
|
}
|
|
2004
2090
|
if (mode === "strict") {
|
|
2091
|
+
if (result.code !== 0) {
|
|
2092
|
+
emitAdvisoryContext(
|
|
2093
|
+
runtime,
|
|
2094
|
+
"verify-current-state",
|
|
2095
|
+
result.stderr.trim().length > 0
|
|
2096
|
+
? result.stderr.trim()
|
|
2097
|
+
: "Cclaw verify-current-state failed in strict mode."
|
|
2098
|
+
);
|
|
2099
|
+
}
|
|
2005
2100
|
if (result.code !== 0 && result.stderr.trim().length > 0) {
|
|
2006
2101
|
process.stderr.write(result.stderr);
|
|
2007
2102
|
}
|
package/dist/content/observe.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { RUNTIME_ROOT } from "../constants.js";
|
|
2
2
|
import { HOOK_MANIFEST, groupBindingsByEvent } from "./hook-manifest.js";
|
|
3
3
|
function hookDispatcherCommand(hookName) {
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
// JSON.stringify to survive spaces.
|
|
8
|
-
return `node ${RUNTIME_ROOT}/hooks/run-hook.mjs ${hookName}`;
|
|
4
|
+
// Dispatch through the polyglot .cmd wrapper so Windows harnesses can run
|
|
5
|
+
// hooks even when command execution happens under CMD-style shells.
|
|
6
|
+
return `${RUNTIME_ROOT}/hooks/run-hook.cmd ${hookName}`;
|
|
9
7
|
}
|
|
10
8
|
/**
|
|
11
9
|
* Claude / Codex share the same outer envelope: each event is an
|
package/dist/content/skills.js
CHANGED
|
@@ -297,6 +297,34 @@ After T-3 REFACTOR, before declaring Batch 1 done:
|
|
|
297
297
|
export function stageSkillFolder(stage) {
|
|
298
298
|
return STAGE_TO_SKILL_FOLDER[stage];
|
|
299
299
|
}
|
|
300
|
+
function normalizedGuidanceKey(value) {
|
|
301
|
+
return value
|
|
302
|
+
.replace(/`[^`]+`/gu, " ")
|
|
303
|
+
.replace(/[*_]/gu, " ")
|
|
304
|
+
.replace(/[^a-z0-9]+/giu, " ")
|
|
305
|
+
.replace(/\s+/gu, " ")
|
|
306
|
+
.trim()
|
|
307
|
+
.toLowerCase();
|
|
308
|
+
}
|
|
309
|
+
function dedupeGuidance(items, blockedBy) {
|
|
310
|
+
const blocked = new Set(blockedBy
|
|
311
|
+
.map((item) => normalizedGuidanceKey(item))
|
|
312
|
+
.filter((item) => item.length > 0));
|
|
313
|
+
const seen = new Set();
|
|
314
|
+
const result = [];
|
|
315
|
+
for (const item of items) {
|
|
316
|
+
const key = normalizedGuidanceKey(item);
|
|
317
|
+
if (key.length === 0)
|
|
318
|
+
continue;
|
|
319
|
+
if (blocked.has(key))
|
|
320
|
+
continue;
|
|
321
|
+
if (seen.has(key))
|
|
322
|
+
continue;
|
|
323
|
+
seen.add(key);
|
|
324
|
+
result.push(item);
|
|
325
|
+
}
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
300
328
|
export function stageSkillMarkdown(stage, track = "standard") {
|
|
301
329
|
const schema = stageSchema(stage, track);
|
|
302
330
|
const gateList = schema.requiredGates
|
|
@@ -308,6 +336,11 @@ export function stageSkillMarkdown(stage, track = "standard") {
|
|
|
308
336
|
const checklistItems = schema.checklist
|
|
309
337
|
.map((item, i) => `${i + 1}. ${item}`)
|
|
310
338
|
.join("\n");
|
|
339
|
+
const interactionFocus = dedupeGuidance(schema.interactionProtocol, [...schema.checklist, ...schema.process]).slice(0, 5);
|
|
340
|
+
const processSummary = dedupeGuidance(schema.process, schema.checklist).slice(0, 5);
|
|
341
|
+
const processNote = schema.process.length > processSummary.length
|
|
342
|
+
? `- Follow the Checklist above for remaining execution detail (+${schema.process.length - processSummary.length} condensed step${schema.process.length - processSummary.length === 1 ? "" : "s"}).`
|
|
343
|
+
: "";
|
|
311
344
|
const stageRefs = stageSpecificSeeAlso(stage);
|
|
312
345
|
return `---
|
|
313
346
|
name: ${schema.skillName}
|
|
@@ -360,7 +393,7 @@ ${stageDomainExamples(stage)}
|
|
|
360
393
|
${stageExamples(stage)}
|
|
361
394
|
|
|
362
395
|
## Interaction Protocol
|
|
363
|
-
${
|
|
396
|
+
${interactionFocus.length > 0 ? interactionFocus.map((item, i) => `${i + 1}. ${item}`).join("\n") : "- Keep communication concise and decision-focused; rely on the Checklist for execution order."}
|
|
364
397
|
|
|
365
398
|
Shared decision/ask-user protocol:
|
|
366
399
|
\`${DECISION_PROTOCOL_PATH}\`
|
|
@@ -373,7 +406,8 @@ ${gateList}
|
|
|
373
406
|
${evidenceList}
|
|
374
407
|
|
|
375
408
|
## Process
|
|
376
|
-
${
|
|
409
|
+
${processSummary.length > 0 ? processSummary.map((item, i) => `${i + 1}. ${item}`).join("\n") : "1. Execute the Checklist in order.\n2. Satisfy every required gate.\n3. Complete verification before stage closeout."}
|
|
410
|
+
${processNote.length > 0 ? `\n${processNote}` : ""}
|
|
377
411
|
|
|
378
412
|
${reviewSectionsBlock(stage, track)}
|
|
379
413
|
${verificationBlock(stage)}
|
package/dist/doctor.js
CHANGED
|
@@ -674,6 +674,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
674
674
|
// Hook scripts
|
|
675
675
|
for (const script of [
|
|
676
676
|
"run-hook.mjs",
|
|
677
|
+
"run-hook.cmd",
|
|
677
678
|
"stage-complete.mjs",
|
|
678
679
|
"opencode-plugin.mjs"
|
|
679
680
|
]) {
|
|
@@ -1021,7 +1022,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1021
1022
|
if (!(await exists(candidate)))
|
|
1022
1023
|
continue;
|
|
1023
1024
|
const content = await fs.readFile(candidate, "utf8");
|
|
1024
|
-
if (/
|
|
1025
|
+
if (/bash\s+\.cclaw\/hooks\/|\.cclaw\/hooks\/(?:session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor)\.sh/u.test(content)) {
|
|
1025
1026
|
legacyDispatchFiles.push(path.relative(projectRoot, candidate));
|
|
1026
1027
|
}
|
|
1027
1028
|
}
|
|
@@ -1029,7 +1030,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1029
1030
|
name: "warning:windows:hook_dispatch_node_only",
|
|
1030
1031
|
ok: legacyDispatchFiles.length === 0,
|
|
1031
1032
|
details: legacyDispatchFiles.length === 0
|
|
1032
|
-
? "hook configs use
|
|
1033
|
+
? "hook configs use managed .cclaw/hooks/run-hook.cmd dispatch commands"
|
|
1033
1034
|
: `warning: legacy shell hook dispatch remains in ${legacyDispatchFiles.join(", ")}`
|
|
1034
1035
|
});
|
|
1035
1036
|
// Knowledge store exists (canonical JSONL, no markdown mirror)
|
package/dist/install.js
CHANGED
|
@@ -24,7 +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 { stageCompleteScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
|
|
27
|
+
import { stageCompleteScript, runHookCmdScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
|
|
28
28
|
import { nodeHookRuntimeScript } from "./content/node-hooks.js";
|
|
29
29
|
import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
|
|
30
30
|
import { decisionProtocolMarkdown, completionProtocolMarkdown, ethosProtocolMarkdown } from "./content/protocols.js";
|
|
@@ -734,12 +734,14 @@ async function writeHooks(projectRoot, config) {
|
|
|
734
734
|
tddProductionPathPatterns: config.tdd?.productionPathPatterns,
|
|
735
735
|
compoundRecurrenceThreshold: config.compound?.recurrenceThreshold
|
|
736
736
|
}));
|
|
737
|
+
await writeFileSafe(path.join(hooksDir, "run-hook.cmd"), runHookCmdScript());
|
|
737
738
|
const opencodePluginSource = opencodePluginJs();
|
|
738
739
|
await writeFileSafe(path.join(hooksDir, "opencode-plugin.mjs"), opencodePluginSource);
|
|
739
740
|
try {
|
|
740
741
|
for (const script of [
|
|
741
742
|
"stage-complete.mjs",
|
|
742
743
|
"run-hook.mjs",
|
|
744
|
+
"run-hook.cmd",
|
|
743
745
|
"opencode-plugin.mjs"
|
|
744
746
|
]) {
|
|
745
747
|
await fs.chmod(path.join(hooksDir, script), 0o755);
|
|
@@ -1440,7 +1442,7 @@ function isManagedRuntimeHookCommand(command) {
|
|
|
1440
1442
|
// (e.g. `node .cclaw\hooks\run-hook.mjs ...`) still round-trip through
|
|
1441
1443
|
// sync without being duplicated alongside freshly generated entries.
|
|
1442
1444
|
const normalized = command.trim().replace(/\s+/gu, " ").replace(/\\/gu, "/");
|
|
1443
|
-
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)) {
|
|
1445
|
+
if (/(^|\s)(?:node\s+)?(?:"|')?(?:\.\/)?\.cclaw\/hooks\/run-hook\.(?:mjs|cmd)(?:"|')?\s+(?:session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor|verify-current-state)(?:\s|$)/u.test(normalized)) {
|
|
1444
1446
|
return true;
|
|
1445
1447
|
}
|
|
1446
1448
|
// Codex UserPromptSubmit non-blocking state nudge.
|