cclaw-cli 0.48.5 → 0.48.7
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/artifact-linter.js +32 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +44 -5
- package/dist/content/hooks.d.ts +2 -2
- package/dist/content/hooks.js +293 -89
- package/dist/content/ideate-command.js +11 -0
- package/dist/content/iron-laws.d.ts +142 -0
- package/dist/content/iron-laws.js +191 -0
- package/dist/content/meta-skill.js +1 -0
- package/dist/content/next-command.js +12 -0
- package/dist/content/observe.js +555 -45
- package/dist/content/ops-command.js +11 -0
- package/dist/content/session-hooks.js +3 -1
- package/dist/content/stage-schema.d.ts +16 -0
- package/dist/content/stage-schema.js +82 -5
- package/dist/content/stages/review.js +4 -4
- package/dist/content/stages/tdd.js +7 -7
- package/dist/content/start-command.js +12 -0
- package/dist/content/subagents.js +26 -0
- package/dist/content/templates.js +8 -0
- package/dist/content/view-command.js +11 -0
- package/dist/doctor.js +6 -2
- package/dist/harness-adapters.js +3 -0
- package/dist/install.js +11 -1
- package/dist/internal/advance-stage.js +14 -2
- package/dist/internal/envelope-validate.d.ts +7 -0
- package/dist/internal/envelope-validate.js +66 -0
- package/dist/internal/knowledge-digest.d.ts +7 -0
- package/dist/internal/knowledge-digest.js +93 -0
- package/dist/internal/tdd-red-evidence.d.ts +7 -0
- package/dist/internal/tdd-red-evidence.js +130 -0
- package/dist/knowledge-store.d.ts +8 -0
- package/dist/knowledge-store.js +95 -0
- package/dist/tdd-cycle.d.ts +7 -0
- package/dist/tdd-cycle.js +29 -0
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
package/dist/artifact-linter.js
CHANGED
|
@@ -1092,6 +1092,14 @@ export async function validateReviewArmy(projectRoot) {
|
|
|
1092
1092
|
}
|
|
1093
1093
|
const severitySet = new Set(["Critical", "Important", "Suggestion"]);
|
|
1094
1094
|
const statusSet = new Set(["open", "accepted", "resolved"]);
|
|
1095
|
+
const sourceSet = new Set([
|
|
1096
|
+
"spec",
|
|
1097
|
+
"correctness",
|
|
1098
|
+
"security",
|
|
1099
|
+
"performance",
|
|
1100
|
+
"architecture",
|
|
1101
|
+
"external-safety"
|
|
1102
|
+
]);
|
|
1095
1103
|
const findingIds = new Set();
|
|
1096
1104
|
const openCriticalIds = new Set();
|
|
1097
1105
|
if (!Array.isArray(root.findings)) {
|
|
@@ -1128,6 +1136,17 @@ export async function validateReviewArmy(projectRoot) {
|
|
|
1128
1136
|
if (!isStringArray(o.reportedBy) || o.reportedBy.length === 0) {
|
|
1129
1137
|
errors.push(`findings[${i}].reportedBy must be a non-empty string array.`);
|
|
1130
1138
|
}
|
|
1139
|
+
if (o.sources !== undefined) {
|
|
1140
|
+
if (!isStringArray(o.sources) || o.sources.length === 0) {
|
|
1141
|
+
errors.push(`findings[${i}].sources must be a non-empty string array when present.`);
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
const invalidSources = o.sources.filter((source) => !sourceSet.has(source));
|
|
1145
|
+
if (invalidSources.length > 0) {
|
|
1146
|
+
errors.push(`findings[${i}].sources contains unknown values: ${invalidSources.join(", ")}.`);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1131
1150
|
if (o.location === undefined || o.location === null) {
|
|
1132
1151
|
errors.push(`findings[${i}].location is required and must be an object with file + line.`);
|
|
1133
1152
|
}
|
|
@@ -1231,6 +1250,19 @@ export async function validateReviewArmy(projectRoot) {
|
|
|
1231
1250
|
}
|
|
1232
1251
|
}
|
|
1233
1252
|
}
|
|
1253
|
+
if (rec.layerCoverage !== undefined) {
|
|
1254
|
+
if (rec.layerCoverage === null || typeof rec.layerCoverage !== "object" || Array.isArray(rec.layerCoverage)) {
|
|
1255
|
+
errors.push("reconciliation.layerCoverage must be an object when present.");
|
|
1256
|
+
}
|
|
1257
|
+
else {
|
|
1258
|
+
const coverage = rec.layerCoverage;
|
|
1259
|
+
for (const source of sourceSet) {
|
|
1260
|
+
if (coverage[source] !== undefined && typeof coverage[source] !== "boolean") {
|
|
1261
|
+
errors.push(`reconciliation.layerCoverage.${source} must be boolean when present.`);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1234
1266
|
}
|
|
1235
1267
|
return { valid: errors.length === 0, errors };
|
|
1236
1268
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export declare function readConfig(projectRoot: string): Promise<CclawConfig>;
|
|
|
42
42
|
* the user set them explicitly. Keeps the default template small and honest:
|
|
43
43
|
* only knobs a new user would meaningfully flip show up.
|
|
44
44
|
*/
|
|
45
|
-
type AdvancedConfigKey = "promptGuardMode" | "tddEnforcement" | "tddTestGlobs" | "tdd" | "compound" | "defaultTrack" | "languageRulePacks" | "trackHeuristics" | "sliceReview";
|
|
45
|
+
type AdvancedConfigKey = "promptGuardMode" | "tddEnforcement" | "tddTestGlobs" | "tdd" | "compound" | "defaultTrack" | "languageRulePacks" | "trackHeuristics" | "sliceReview" | "ironLaws";
|
|
46
46
|
/**
|
|
47
47
|
* Options controlling the serialisation shape of `config.yaml`.
|
|
48
48
|
*
|
package/dist/config.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parse, stringify } from "yaml";
|
|
4
4
|
import { CCLAW_VERSION, DEFAULT_HARNESSES, FLOW_VERSION, RUNTIME_ROOT } from "./constants.js";
|
|
5
|
+
import { isIronLawId, normalizeStrictLawIds } from "./content/iron-laws.js";
|
|
5
6
|
import { exists, writeFileSafe } from "./fs-utils.js";
|
|
6
7
|
import { FLOW_TRACKS, HARNESS_IDS, LANGUAGE_RULE_PACKS } from "./types.js";
|
|
7
8
|
const CONFIG_PATH = `${RUNTIME_ROOT}/config.yaml`;
|
|
@@ -25,7 +26,8 @@ const ALLOWED_CONFIG_KEYS = new Set([
|
|
|
25
26
|
"defaultTrack",
|
|
26
27
|
"languageRulePacks",
|
|
27
28
|
"trackHeuristics",
|
|
28
|
-
"sliceReview"
|
|
29
|
+
"sliceReview",
|
|
30
|
+
"ironLaws"
|
|
29
31
|
]);
|
|
30
32
|
/**
|
|
31
33
|
* Config keys always present in the minimal init template. Everything else
|
|
@@ -141,7 +143,11 @@ export function createDefaultConfig(harnesses = DEFAULT_HARNESSES, defaultTrack
|
|
|
141
143
|
},
|
|
142
144
|
gitHookGuards: false,
|
|
143
145
|
defaultTrack,
|
|
144
|
-
languageRulePacks: []
|
|
146
|
+
languageRulePacks: [],
|
|
147
|
+
ironLaws: {
|
|
148
|
+
mode: "advisory",
|
|
149
|
+
strictLaws: []
|
|
150
|
+
}
|
|
145
151
|
};
|
|
146
152
|
}
|
|
147
153
|
/**
|
|
@@ -409,6 +415,36 @@ export async function readConfig(projectRoot) {
|
|
|
409
415
|
enforceOnTracks: enforceOnTracks ?? DEFAULT_SLICE_REVIEW_TRACKS
|
|
410
416
|
};
|
|
411
417
|
}
|
|
418
|
+
const ironLawsRaw = parsed.ironLaws;
|
|
419
|
+
let ironLaws = undefined;
|
|
420
|
+
if (Object.prototype.hasOwnProperty.call(parsed, "ironLaws")) {
|
|
421
|
+
if (!isRecord(ironLawsRaw)) {
|
|
422
|
+
throw configValidationError(fullPath, `"ironLaws" must be an object`);
|
|
423
|
+
}
|
|
424
|
+
const unknownIronLawKeys = Object.keys(ironLawsRaw).filter((key) => key !== "mode" && key !== "strictLaws");
|
|
425
|
+
if (unknownIronLawKeys.length > 0) {
|
|
426
|
+
throw configValidationError(fullPath, `"ironLaws" has unknown key(s): ${unknownIronLawKeys.join(", ")}`);
|
|
427
|
+
}
|
|
428
|
+
const modeRaw = ironLawsRaw.mode;
|
|
429
|
+
if (modeRaw !== undefined && modeRaw !== "advisory" && modeRaw !== "strict") {
|
|
430
|
+
throw configValidationError(fullPath, `"ironLaws.mode" must be "advisory" or "strict"`);
|
|
431
|
+
}
|
|
432
|
+
const strictLawIdsRaw = validateStringArray(ironLawsRaw.strictLaws, "ironLaws.strictLaws", fullPath) ?? [];
|
|
433
|
+
const unknownStrictLawIds = strictLawIdsRaw.filter((id) => !isIronLawId(id));
|
|
434
|
+
if (unknownStrictLawIds.length > 0) {
|
|
435
|
+
throw configValidationError(fullPath, `"ironLaws.strictLaws" contains unknown law id(s): ${unknownStrictLawIds.join(", ")}`);
|
|
436
|
+
}
|
|
437
|
+
ironLaws = {
|
|
438
|
+
mode: modeRaw === "strict" ? "strict" : "advisory",
|
|
439
|
+
strictLaws: normalizeStrictLawIds(strictLawIdsRaw)
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
ironLaws = {
|
|
444
|
+
mode: strictness,
|
|
445
|
+
strictLaws: []
|
|
446
|
+
};
|
|
447
|
+
}
|
|
412
448
|
return {
|
|
413
449
|
version: parsed.version ?? CCLAW_VERSION,
|
|
414
450
|
flowVersion: parsed.flowVersion ?? FLOW_VERSION,
|
|
@@ -428,7 +464,8 @@ export async function readConfig(projectRoot) {
|
|
|
428
464
|
defaultTrack,
|
|
429
465
|
languageRulePacks,
|
|
430
466
|
trackHeuristics,
|
|
431
|
-
sliceReview
|
|
467
|
+
sliceReview,
|
|
468
|
+
ironLaws
|
|
432
469
|
};
|
|
433
470
|
}
|
|
434
471
|
function isMinimalKey(key) {
|
|
@@ -452,7 +489,8 @@ function buildSerializableConfig(config, options = {}) {
|
|
|
452
489
|
"defaultTrack",
|
|
453
490
|
"languageRulePacks",
|
|
454
491
|
"trackHeuristics",
|
|
455
|
-
"sliceReview"
|
|
492
|
+
"sliceReview",
|
|
493
|
+
"ironLaws"
|
|
456
494
|
];
|
|
457
495
|
for (const key of ordered) {
|
|
458
496
|
const value = config[key];
|
|
@@ -506,7 +544,8 @@ export async function detectAdvancedKeys(projectRoot) {
|
|
|
506
544
|
"defaultTrack",
|
|
507
545
|
"languageRulePacks",
|
|
508
546
|
"trackHeuristics",
|
|
509
|
-
"sliceReview"
|
|
547
|
+
"sliceReview",
|
|
548
|
+
"ironLaws"
|
|
510
549
|
];
|
|
511
550
|
const present = new Set();
|
|
512
551
|
for (const key of advancedCandidates) {
|
package/dist/content/hooks.d.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
export interface HookRuntimeOptions {
|
|
9
9
|
}
|
|
10
10
|
/** Shared bash preamble for generated hook scripts. */
|
|
11
|
-
export declare const RUNTIME_SHELL_DETECT_ROOT = "HARNESS=\"codex\"\
|
|
11
|
+
export declare const RUNTIME_SHELL_DETECT_ROOT = "CCLAW_HOOK_LIB_PATH=\"\"\nfor candidate in \"${CCLAW_PROJECT_ROOT:-}\" \"${CLAUDE_PROJECT_DIR:-}\" \"${CURSOR_PROJECT_DIR:-}\" \"${CURSOR_PROJECT_ROOT:-}\" \"${OPENCODE_PROJECT_DIR:-}\" \"${OPENCODE_PROJECT_ROOT:-}\" \"${PWD:-}\"; do\n if [ -n \"$candidate\" ] && [ -f \"$candidate/.cclaw/hooks/_lib.sh\" ]; then\n CCLAW_HOOK_LIB_PATH=\"$candidate/.cclaw/hooks/_lib.sh\"\n break\n fi\ndone\nif [ -n \"$CCLAW_HOOK_LIB_PATH\" ] && [ -f \"$CCLAW_HOOK_LIB_PATH\" ]; then\n # shellcheck disable=SC1090\n . \"$CCLAW_HOOK_LIB_PATH\"\nfi\n\nif command -v cclaw_hook_detect_root >/dev/null 2>&1; then\n cclaw_hook_detect_root\nelse\n HARNESS=\"codex\"\n if [ -n \"${CLAUDE_PROJECT_DIR:-}\" ]; then\n HARNESS=\"claude\"\n elif [ -n \"${CURSOR_PROJECT_DIR:-}\" ] || [ -n \"${CURSOR_PROJECT_ROOT:-}\" ]; then\n HARNESS=\"cursor\"\n elif [ -n \"${OPENCODE_PROJECT_DIR:-}\" ] || [ -n \"${OPENCODE_PROJECT_ROOT:-}\" ]; then\n HARNESS=\"opencode\"\n fi\n\n ROOT=\"\"\n for candidate in \"${CCLAW_PROJECT_ROOT:-}\" \"${CLAUDE_PROJECT_DIR:-}\" \"${CURSOR_PROJECT_DIR:-}\" \"${CURSOR_PROJECT_ROOT:-}\" \"${OPENCODE_PROJECT_DIR:-}\" \"${OPENCODE_PROJECT_ROOT:-}\" \"${PWD:-}\"; do\n if [ -n \"$candidate\" ] && [ -d \"$candidate/.cclaw\" ]; then\n ROOT=\"$candidate\"\n break\n fi\n done\n if [ -z \"$ROOT\" ]; then\n ROOT=\"${CCLAW_PROJECT_ROOT:-${CLAUDE_PROJECT_DIR:-${CURSOR_PROJECT_DIR:-${CURSOR_PROJECT_ROOT:-${OPENCODE_PROJECT_DIR:-${OPENCODE_PROJECT_ROOT:-${PWD}}}}}}}\"\n fi\nfi";
|
|
12
|
+
export declare function hookLibScript(): string;
|
|
12
13
|
export declare function sessionStartScript(_options?: HookRuntimeOptions): string;
|
|
13
14
|
export declare function stopCheckpointScript(): string;
|
|
14
15
|
export declare function runHookDispatcherScript(): string;
|
|
@@ -18,4 +19,3 @@ export { claudeHooksJsonWithObservation as claudeHooksJson } from "./observe.js"
|
|
|
18
19
|
export { cursorHooksJsonWithObservation as cursorHooksJson } from "./observe.js";
|
|
19
20
|
export { codexHooksJsonWithObservation as codexHooksJson } from "./observe.js";
|
|
20
21
|
export { opencodePluginJs } from "./opencode-plugin.js";
|
|
21
|
-
export declare function hooksAgentsMdBlock(): string;
|
package/dist/content/hooks.js
CHANGED
|
@@ -15,39 +15,201 @@ const ESCAPE_FN = `escape_json() {
|
|
|
15
15
|
str=\${str//$'\\n'/\\\\n}
|
|
16
16
|
printf '%s' "$str"
|
|
17
17
|
}`;
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
elif [ -n "\${CURSOR_PROJECT_DIR:-}" ] || [ -n "\${CURSOR_PROJECT_ROOT:-}" ]; then
|
|
22
|
-
HARNESS="cursor"
|
|
23
|
-
elif [ -n "\${OPENCODE_PROJECT_DIR:-}" ] || [ -n "\${OPENCODE_PROJECT_ROOT:-}" ]; then
|
|
24
|
-
HARNESS="opencode"
|
|
25
|
-
fi
|
|
26
|
-
|
|
27
|
-
ROOT=""
|
|
18
|
+
const HOOK_LIB_FILE = "_lib.sh";
|
|
19
|
+
/** Shared bash preamble for generated hook scripts. */
|
|
20
|
+
export const RUNTIME_SHELL_DETECT_ROOT = `CCLAW_HOOK_LIB_PATH=""
|
|
28
21
|
for candidate in "\${CCLAW_PROJECT_ROOT:-}" "\${CLAUDE_PROJECT_DIR:-}" "\${CURSOR_PROJECT_DIR:-}" "\${CURSOR_PROJECT_ROOT:-}" "\${OPENCODE_PROJECT_DIR:-}" "\${OPENCODE_PROJECT_ROOT:-}" "\${PWD:-}"; do
|
|
29
|
-
if [ -n "$candidate" ] && [ -
|
|
30
|
-
|
|
22
|
+
if [ -n "$candidate" ] && [ -f "$candidate/${RUNTIME_ROOT}/hooks/${HOOK_LIB_FILE}" ]; then
|
|
23
|
+
CCLAW_HOOK_LIB_PATH="$candidate/${RUNTIME_ROOT}/hooks/${HOOK_LIB_FILE}"
|
|
31
24
|
break
|
|
32
25
|
fi
|
|
33
26
|
done
|
|
34
|
-
if [ -
|
|
35
|
-
|
|
27
|
+
if [ -n "$CCLAW_HOOK_LIB_PATH" ] && [ -f "$CCLAW_HOOK_LIB_PATH" ]; then
|
|
28
|
+
# shellcheck disable=SC1090
|
|
29
|
+
. "$CCLAW_HOOK_LIB_PATH"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
if command -v cclaw_hook_detect_root >/dev/null 2>&1; then
|
|
33
|
+
cclaw_hook_detect_root
|
|
34
|
+
else
|
|
35
|
+
HARNESS="codex"
|
|
36
|
+
if [ -n "\${CLAUDE_PROJECT_DIR:-}" ]; then
|
|
37
|
+
HARNESS="claude"
|
|
38
|
+
elif [ -n "\${CURSOR_PROJECT_DIR:-}" ] || [ -n "\${CURSOR_PROJECT_ROOT:-}" ]; then
|
|
39
|
+
HARNESS="cursor"
|
|
40
|
+
elif [ -n "\${OPENCODE_PROJECT_DIR:-}" ] || [ -n "\${OPENCODE_PROJECT_ROOT:-}" ]; then
|
|
41
|
+
HARNESS="opencode"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
ROOT=""
|
|
45
|
+
for candidate in "\${CCLAW_PROJECT_ROOT:-}" "\${CLAUDE_PROJECT_DIR:-}" "\${CURSOR_PROJECT_DIR:-}" "\${CURSOR_PROJECT_ROOT:-}" "\${OPENCODE_PROJECT_DIR:-}" "\${OPENCODE_PROJECT_ROOT:-}" "\${PWD:-}"; do
|
|
46
|
+
if [ -n "$candidate" ] && [ -d "$candidate/${RUNTIME_ROOT}" ]; then
|
|
47
|
+
ROOT="$candidate"
|
|
48
|
+
break
|
|
49
|
+
fi
|
|
50
|
+
done
|
|
51
|
+
if [ -z "$ROOT" ]; then
|
|
52
|
+
ROOT="\${CCLAW_PROJECT_ROOT:-\${CLAUDE_PROJECT_DIR:-\${CURSOR_PROJECT_DIR:-\${CURSOR_PROJECT_ROOT:-\${OPENCODE_PROJECT_DIR:-\${OPENCODE_PROJECT_ROOT:-\${PWD}}}}}}}"
|
|
53
|
+
fi
|
|
36
54
|
fi`;
|
|
37
|
-
|
|
38
|
-
|
|
55
|
+
export function hookLibScript() {
|
|
56
|
+
return `#!/usr/bin/env bash
|
|
57
|
+
# cclaw shared hook library — generated by cclaw sync
|
|
58
|
+
# Shared helper functions for root detection and lightweight JSON parsing.
|
|
59
|
+
|
|
60
|
+
cclaw_hook_detect_root() {
|
|
61
|
+
HARNESS="codex"
|
|
62
|
+
if [ -n "\${CLAUDE_PROJECT_DIR:-}" ]; then
|
|
63
|
+
HARNESS="claude"
|
|
64
|
+
elif [ -n "\${CURSOR_PROJECT_DIR:-}" ] || [ -n "\${CURSOR_PROJECT_ROOT:-}" ]; then
|
|
65
|
+
HARNESS="cursor"
|
|
66
|
+
elif [ -n "\${OPENCODE_PROJECT_DIR:-}" ] || [ -n "\${OPENCODE_PROJECT_ROOT:-}" ]; then
|
|
67
|
+
HARNESS="opencode"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
ROOT=""
|
|
71
|
+
for candidate in "\${CCLAW_PROJECT_ROOT:-}" "\${CLAUDE_PROJECT_DIR:-}" "\${CURSOR_PROJECT_DIR:-}" "\${CURSOR_PROJECT_ROOT:-}" "\${OPENCODE_PROJECT_DIR:-}" "\${OPENCODE_PROJECT_ROOT:-}" "\${PWD:-}"; do
|
|
72
|
+
if [ -n "$candidate" ] && [ -d "$candidate/${RUNTIME_ROOT}" ]; then
|
|
73
|
+
ROOT="$candidate"
|
|
74
|
+
break
|
|
75
|
+
fi
|
|
76
|
+
done
|
|
77
|
+
if [ -z "$ROOT" ]; then
|
|
78
|
+
ROOT="\${CCLAW_PROJECT_ROOT:-\${CLAUDE_PROJECT_DIR:-\${CURSOR_PROJECT_DIR:-\${CURSOR_PROJECT_ROOT:-\${OPENCODE_PROJECT_DIR:-\${OPENCODE_PROJECT_ROOT:-\${PWD}}}}}}}"
|
|
79
|
+
fi
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
cclaw_hook_lower() {
|
|
83
|
+
printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
cclaw_hook_extract_tool_and_payload() {
|
|
87
|
+
local input_json="$1"
|
|
88
|
+
CCLAW_HOOK_TOOL="unknown"
|
|
89
|
+
CCLAW_HOOK_PAYLOAD=""
|
|
90
|
+
if command -v jq >/dev/null 2>&1; then
|
|
91
|
+
CCLAW_HOOK_TOOL=$(printf '%s' "$input_json" | jq -r '.tool_name // .tool // .toolName // .name // .id // .command // .tool.name // .tool.id // .input.tool_name // .input.tool // .input.toolName // .input.name // .input.id // .input.command // .input.tool.name // .input.tool.id // "unknown"' 2>/dev/null || echo "unknown")
|
|
92
|
+
CCLAW_HOOK_PAYLOAD=$(printf '%s' "$input_json" | jq -r '.tool_input // .input // .arguments // .params // .payload // {} | tostring' 2>/dev/null || echo "")
|
|
93
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
94
|
+
CCLAW_HOOK_TOOL=$(INPUT_JSON="$input_json" python3 - <<'PY'
|
|
95
|
+
import json
|
|
96
|
+
import os
|
|
97
|
+
try:
|
|
98
|
+
value = json.loads(os.environ.get("INPUT_JSON", "{}"))
|
|
99
|
+
except Exception:
|
|
100
|
+
value = {}
|
|
101
|
+
|
|
102
|
+
def pick_tool(payload):
|
|
103
|
+
if not isinstance(payload, dict):
|
|
104
|
+
return "unknown"
|
|
105
|
+
candidates = [
|
|
106
|
+
payload.get("tool_name"),
|
|
107
|
+
payload.get("tool"),
|
|
108
|
+
payload.get("toolName"),
|
|
109
|
+
payload.get("name"),
|
|
110
|
+
payload.get("id"),
|
|
111
|
+
payload.get("command")
|
|
112
|
+
]
|
|
113
|
+
top_tool = payload.get("tool")
|
|
114
|
+
if isinstance(top_tool, dict):
|
|
115
|
+
candidates.extend([top_tool.get("name"), top_tool.get("id")])
|
|
116
|
+
nested = payload.get("input")
|
|
117
|
+
if isinstance(nested, dict):
|
|
118
|
+
candidates.extend([
|
|
119
|
+
nested.get("tool_name"),
|
|
120
|
+
nested.get("tool"),
|
|
121
|
+
nested.get("toolName"),
|
|
122
|
+
nested.get("name"),
|
|
123
|
+
nested.get("id"),
|
|
124
|
+
nested.get("command")
|
|
125
|
+
])
|
|
126
|
+
nested_tool = nested.get("tool")
|
|
127
|
+
if isinstance(nested_tool, dict):
|
|
128
|
+
candidates.extend([nested_tool.get("name"), nested_tool.get("id")])
|
|
129
|
+
for candidate in candidates:
|
|
130
|
+
if isinstance(candidate, str) and candidate.strip():
|
|
131
|
+
return candidate.strip()
|
|
132
|
+
return "unknown"
|
|
133
|
+
|
|
134
|
+
print(pick_tool(value))
|
|
135
|
+
PY
|
|
136
|
+
)
|
|
137
|
+
CCLAW_HOOK_PAYLOAD=$(printf '%s' "$input_json")
|
|
138
|
+
else
|
|
139
|
+
CCLAW_HOOK_PAYLOAD=$(printf '%s' "$input_json")
|
|
140
|
+
fi
|
|
141
|
+
[ -n "$CCLAW_HOOK_PAYLOAD" ] || CCLAW_HOOK_PAYLOAD=$(printf '%s' "$input_json")
|
|
142
|
+
[ -n "$CCLAW_HOOK_TOOL" ] || CCLAW_HOOK_TOOL="unknown"
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
cclaw_hook_read_flow_state_minimal() {
|
|
146
|
+
local flow_state_file="$1"
|
|
147
|
+
CCLAW_HOOK_FLOW_STAGE="none"
|
|
148
|
+
CCLAW_HOOK_FLOW_RUN_ID="active"
|
|
149
|
+
CCLAW_HOOK_FLOW_COMPLETED="0"
|
|
150
|
+
[ -f "$flow_state_file" ] || return 0
|
|
151
|
+
|
|
152
|
+
if command -v jq >/dev/null 2>&1; then
|
|
153
|
+
CCLAW_HOOK_FLOW_STAGE=$(jq -r '.currentStage // "none"' "$flow_state_file" 2>/dev/null || echo "none")
|
|
154
|
+
CCLAW_HOOK_FLOW_RUN_ID=$(jq -r '.activeRunId // "active"' "$flow_state_file" 2>/dev/null || echo "active")
|
|
155
|
+
CCLAW_HOOK_FLOW_COMPLETED=$(jq -r '(.completedStages // []) | length' "$flow_state_file" 2>/dev/null || echo "0")
|
|
156
|
+
return 0
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
160
|
+
local flow_meta
|
|
161
|
+
flow_meta=$(python3 - "$flow_state_file" <<'PY'
|
|
162
|
+
import json
|
|
163
|
+
import sys
|
|
164
|
+
stage = "none"
|
|
165
|
+
run_id = "active"
|
|
166
|
+
completed = 0
|
|
167
|
+
try:
|
|
168
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
169
|
+
payload = json.load(fh)
|
|
170
|
+
stage_value = payload.get("currentStage")
|
|
171
|
+
run_value = payload.get("activeRunId")
|
|
172
|
+
completed_value = payload.get("completedStages")
|
|
173
|
+
if isinstance(stage_value, str) and stage_value:
|
|
174
|
+
stage = stage_value
|
|
175
|
+
if isinstance(run_value, str) and run_value:
|
|
176
|
+
run_id = run_value
|
|
177
|
+
if isinstance(completed_value, list):
|
|
178
|
+
completed = len(completed_value)
|
|
179
|
+
except Exception:
|
|
180
|
+
pass
|
|
181
|
+
print(stage)
|
|
182
|
+
print(run_id)
|
|
183
|
+
print(completed)
|
|
184
|
+
PY
|
|
185
|
+
)
|
|
186
|
+
{
|
|
187
|
+
IFS= read -r CCLAW_HOOK_FLOW_STAGE
|
|
188
|
+
IFS= read -r CCLAW_HOOK_FLOW_RUN_ID
|
|
189
|
+
IFS= read -r CCLAW_HOOK_FLOW_COMPLETED
|
|
190
|
+
} <<EOF
|
|
191
|
+
$flow_meta
|
|
192
|
+
EOF
|
|
193
|
+
[ -n "$CCLAW_HOOK_FLOW_STAGE" ] || CCLAW_HOOK_FLOW_STAGE="none"
|
|
194
|
+
[ -n "$CCLAW_HOOK_FLOW_RUN_ID" ] || CCLAW_HOOK_FLOW_RUN_ID="active"
|
|
195
|
+
[ -n "$CCLAW_HOOK_FLOW_COMPLETED" ] || CCLAW_HOOK_FLOW_COMPLETED="0"
|
|
196
|
+
fi
|
|
197
|
+
}
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
39
200
|
export function sessionStartScript(_options = {}) {
|
|
40
201
|
return `#!/usr/bin/env bash
|
|
41
202
|
# cclaw session-start hook — generated by cclaw sync
|
|
42
203
|
# Injects using-cclaw + flow status + active artifacts + compact knowledge digest + checkpoint/activity summary.
|
|
43
204
|
set -euo pipefail
|
|
44
205
|
|
|
45
|
-
${
|
|
206
|
+
${RUNTIME_SHELL_DETECT_ROOT}
|
|
46
207
|
|
|
47
208
|
STATE_FILE="$ROOT/${RUNTIME_ROOT}/state/flow-state.json"
|
|
48
209
|
ACTIVE_FEATURE_FILE="$ROOT/${RUNTIME_ROOT}/state/active-feature.json"
|
|
49
210
|
CHECKPOINT_FILE="$ROOT/${RUNTIME_ROOT}/state/checkpoint.json"
|
|
50
211
|
ACTIVITY_FILE="$ROOT/${RUNTIME_ROOT}/state/stage-activity.jsonl"
|
|
212
|
+
IRON_LAWS_FILE="$ROOT/${RUNTIME_ROOT}/state/iron-laws.json"
|
|
51
213
|
SUGGESTION_MEMORY_FILE="$ROOT/${RUNTIME_ROOT}/state/suggestion-memory.json"
|
|
52
214
|
CONTEXT_WARNINGS_FILE="$ROOT/${RUNTIME_ROOT}/state/context-warnings.jsonl"
|
|
53
215
|
CONTEXT_MODE_FILE="$ROOT/${RUNTIME_ROOT}/state/context-mode.json"
|
|
@@ -352,74 +514,99 @@ if [ -f "$META_SKILL" ]; then
|
|
|
352
514
|
META_CONTENT=$(cat "$META_SKILL" 2>/dev/null || echo "")
|
|
353
515
|
fi
|
|
354
516
|
|
|
355
|
-
# --- Build compact knowledge digest (stage
|
|
517
|
+
# --- Build compact knowledge digest (stage + branch + diff aware) ---
|
|
356
518
|
KNOWLEDGE_DIGEST=""
|
|
357
519
|
LEARNINGS_COUNT=0
|
|
358
520
|
if [ -f "$KNOWLEDGE_FILE" ] && [ -s "$KNOWLEDGE_FILE" ]; then
|
|
359
521
|
LEARNINGS_COUNT=$(grep -c '^{' "$KNOWLEDGE_FILE" 2>/dev/null || echo "0")
|
|
522
|
+
fi
|
|
523
|
+
|
|
524
|
+
if command -v cclaw >/dev/null 2>&1 && [ -f "$KNOWLEDGE_FILE" ] && [ -s "$KNOWLEDGE_FILE" ]; then
|
|
525
|
+
BRANCH_NAME=""
|
|
526
|
+
if command -v git >/dev/null 2>&1 && git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
527
|
+
BRANCH_NAME=$(git -C "$ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
528
|
+
fi
|
|
529
|
+
DIFF_FILES_CSV=""
|
|
530
|
+
if command -v git >/dev/null 2>&1 && git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
531
|
+
DIFF_FILES_CSV=$(git -C "$ROOT" diff --name-only HEAD~5..HEAD 2>/dev/null | head -n 20 | tr '\n' ',' | sed 's/,$//' || echo "")
|
|
532
|
+
fi
|
|
533
|
+
OPEN_GATES_CSV=""
|
|
534
|
+
if [ -f "$STATE_FILE" ] && command -v jq >/dev/null 2>&1; then
|
|
535
|
+
OPEN_GATES_CSV=$(jq -r --arg stage "$STAGE" '
|
|
536
|
+
(.stageGateCatalog[$stage].required // [])
|
|
537
|
+
- (.stageGateCatalog[$stage].passed // [])
|
|
538
|
+
| join(",")
|
|
539
|
+
' "$STATE_FILE" 2>/dev/null || echo "")
|
|
540
|
+
fi
|
|
541
|
+
DIGEST_CMD=(cclaw internal knowledge-digest --stage="$STAGE" --limit=8)
|
|
542
|
+
if [ -n "$BRANCH_NAME" ]; then
|
|
543
|
+
DIGEST_CMD+=("--branch=$BRANCH_NAME")
|
|
544
|
+
fi
|
|
545
|
+
if [ -n "$DIFF_FILES_CSV" ]; then
|
|
546
|
+
DIGEST_CMD+=("--diff-files=$DIFF_FILES_CSV")
|
|
547
|
+
fi
|
|
548
|
+
if [ -n "$OPEN_GATES_CSV" ]; then
|
|
549
|
+
DIGEST_CMD+=("--open-gates=$OPEN_GATES_CSV")
|
|
550
|
+
fi
|
|
551
|
+
KNOWLEDGE_DIGEST=$("\${DIGEST_CMD[@]}" 2>/dev/null || echo "")
|
|
552
|
+
fi
|
|
553
|
+
|
|
554
|
+
if [ -z "$KNOWLEDGE_DIGEST" ] && [ -f "$KNOWLEDGE_FILE" ] && [ -s "$KNOWLEDGE_FILE" ]; then
|
|
360
555
|
if command -v jq >/dev/null 2>&1; then
|
|
361
|
-
KNOWLEDGE_DIGEST=$(tail -n
|
|
556
|
+
KNOWLEDGE_DIGEST=$(tail -n 120 "$KNOWLEDGE_FILE" 2>/dev/null | jq -Rsc --arg stage "$STAGE" '
|
|
362
557
|
split("\\n")
|
|
363
558
|
| map(select(length > 0))
|
|
364
559
|
| map(try fromjson catch null)
|
|
365
560
|
| map(select(type == "object"))
|
|
366
561
|
| map(select((.stage // null) == $stage or (.stage // null) == null))
|
|
367
562
|
| reverse
|
|
368
|
-
| .[0:
|
|
563
|
+
| .[0:6]
|
|
369
564
|
| map("- [" + ((.confidence // "unknown")|tostring) + " • " + ((.stage // "global")|tostring) + " • " + ((.domain // "general")|tostring) + "] " + ((.trigger // "trigger")|tostring) + " -> " + ((.action // "action")|tostring))
|
|
370
565
|
| join("\\n")
|
|
371
566
|
' 2>/dev/null || echo "")
|
|
567
|
+
else
|
|
568
|
+
KNOWLEDGE_DIGEST=$(tail -n 6 "$KNOWLEDGE_FILE" 2>/dev/null || echo "")
|
|
569
|
+
fi
|
|
570
|
+
fi
|
|
571
|
+
|
|
572
|
+
if [ -n "$KNOWLEDGE_DIGEST" ]; then
|
|
573
|
+
printf '# Knowledge digest (auto-generated)\\n\\n%s\\n' "$KNOWLEDGE_DIGEST" > "$KNOWLEDGE_DIGEST_FILE" 2>/dev/null || true
|
|
574
|
+
elif [ -f "$KNOWLEDGE_DIGEST_FILE" ]; then
|
|
575
|
+
printf '# Knowledge digest (auto-generated)\\n\\n(no matching entries for current stage)\\n' > "$KNOWLEDGE_DIGEST_FILE" 2>/dev/null || true
|
|
576
|
+
fi
|
|
577
|
+
|
|
578
|
+
IRON_LAWS_SUMMARY=""
|
|
579
|
+
if [ -f "$IRON_LAWS_FILE" ]; then
|
|
580
|
+
if command -v jq >/dev/null 2>&1; then
|
|
581
|
+
IRON_LAWS_SUMMARY=$(jq -r '
|
|
582
|
+
(.laws // [])
|
|
583
|
+
| map("- [" + (if (.strict // false) then "strict" else "advisory" end) + "] " + ((.id // "law")|tostring) + " -> " + ((.rule // "")|tostring))
|
|
584
|
+
| .[0:6]
|
|
585
|
+
| join("\\n")
|
|
586
|
+
' "$IRON_LAWS_FILE" 2>/dev/null || echo "")
|
|
372
587
|
elif command -v python3 >/dev/null 2>&1; then
|
|
373
|
-
|
|
588
|
+
IRON_LAWS_SUMMARY=$(python3 - "$IRON_LAWS_FILE" <<'PY'
|
|
374
589
|
import json
|
|
375
590
|
import sys
|
|
376
|
-
|
|
377
|
-
path = sys.argv[1]
|
|
378
|
-
stage = sys.argv[2]
|
|
379
|
-
entries = []
|
|
591
|
+
out = []
|
|
380
592
|
try:
|
|
381
|
-
with open(
|
|
382
|
-
|
|
383
|
-
for
|
|
384
|
-
|
|
385
|
-
if not raw:
|
|
386
|
-
continue
|
|
387
|
-
try:
|
|
388
|
-
obj = json.loads(raw)
|
|
389
|
-
except Exception:
|
|
390
|
-
continue
|
|
391
|
-
if not isinstance(obj, dict):
|
|
392
|
-
continue
|
|
393
|
-
row_stage = obj.get("stage")
|
|
394
|
-
if row_stage not in (stage, None):
|
|
593
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
594
|
+
parsed = json.load(fh)
|
|
595
|
+
for row in (parsed.get("laws") or [])[:6]:
|
|
596
|
+
if not isinstance(row, dict):
|
|
395
597
|
continue
|
|
396
|
-
|
|
598
|
+
strict = "strict" if row.get("strict") else "advisory"
|
|
599
|
+
law_id = str(row.get("id") or "law")
|
|
600
|
+
rule = str(row.get("rule") or "")
|
|
601
|
+
out.append(f"- [{strict}] {law_id} -> {rule}")
|
|
397
602
|
except Exception:
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
entries = list(reversed(entries))[:8]
|
|
401
|
-
out = []
|
|
402
|
-
for obj in entries:
|
|
403
|
-
conf = str(obj.get("confidence", "unknown"))
|
|
404
|
-
row_stage = str(obj.get("stage", "global"))
|
|
405
|
-
domain = str(obj.get("domain", "general"))
|
|
406
|
-
trigger = str(obj.get("trigger", "trigger"))
|
|
407
|
-
action = str(obj.get("action", "action"))
|
|
408
|
-
out.append(f"- [{conf} • {row_stage} • {domain}] {trigger} -> {action}")
|
|
603
|
+
out = []
|
|
409
604
|
print("\\n".join(out))
|
|
410
605
|
PY
|
|
411
606
|
)
|
|
412
|
-
else
|
|
413
|
-
KNOWLEDGE_DIGEST=$(tail -n 8 "$KNOWLEDGE_FILE" 2>/dev/null || echo "")
|
|
414
607
|
fi
|
|
415
608
|
fi
|
|
416
609
|
|
|
417
|
-
if [ -n "$KNOWLEDGE_DIGEST" ]; then
|
|
418
|
-
printf '# Knowledge digest (auto-generated)\\n\\n%s\\n' "$KNOWLEDGE_DIGEST" > "$KNOWLEDGE_DIGEST_FILE" 2>/dev/null || true
|
|
419
|
-
elif [ -f "$KNOWLEDGE_DIGEST_FILE" ]; then
|
|
420
|
-
printf '# Knowledge digest (auto-generated)\\n\\n(no matching entries for current stage)\\n' > "$KNOWLEDGE_DIGEST_FILE" 2>/dev/null || true
|
|
421
|
-
fi
|
|
422
|
-
|
|
423
610
|
# --- Installed cclaw-cli version vs. project's recorded version (one-block
|
|
424
611
|
# upgrade-check, gstack-style). Purely informational — we never block. ---
|
|
425
612
|
VERSION_NOTE=""
|
|
@@ -503,6 +690,11 @@ if [ -n "$KNOWLEDGE_DIGEST" ]; then
|
|
|
503
690
|
Knowledge digest (top relevant entries):
|
|
504
691
|
$KNOWLEDGE_DIGEST"
|
|
505
692
|
fi
|
|
693
|
+
if [ -n "$IRON_LAWS_SUMMARY" ]; then
|
|
694
|
+
CTX="$CTX
|
|
695
|
+
Iron laws (enforced policy highlights):
|
|
696
|
+
$IRON_LAWS_SUMMARY"
|
|
697
|
+
fi
|
|
506
698
|
if [ -n "$META_CONTENT" ]; then
|
|
507
699
|
CTX="$CTX
|
|
508
700
|
|
|
@@ -536,7 +728,7 @@ export function stopCheckpointScript() {
|
|
|
536
728
|
# Writes checkpoint state and reminds agent about flow/session consistency.
|
|
537
729
|
set -euo pipefail
|
|
538
730
|
|
|
539
|
-
${
|
|
731
|
+
${RUNTIME_SHELL_DETECT_ROOT}
|
|
540
732
|
|
|
541
733
|
INPUT=$(cat 2>/dev/null || echo '{}')
|
|
542
734
|
|
|
@@ -545,6 +737,7 @@ STATE_FILE="$STATE_DIR/flow-state.json"
|
|
|
545
737
|
CHECKPOINT_FILE="$STATE_DIR/checkpoint.json"
|
|
546
738
|
CHECKPOINT_TMP="$STATE_DIR/checkpoint.json.tmp.$$"
|
|
547
739
|
CHECKPOINT_LOCK_DIR="$STATE_DIR/.checkpoint.lock"
|
|
740
|
+
IRON_LAWS_FILE="$STATE_DIR/iron-laws.json"
|
|
548
741
|
STAGE="none"
|
|
549
742
|
ACTIVE_RUN="none"
|
|
550
743
|
LOOP_COUNT=""
|
|
@@ -617,6 +810,38 @@ if command -v git >/dev/null 2>&1; then
|
|
|
617
810
|
fi
|
|
618
811
|
fi
|
|
619
812
|
|
|
813
|
+
STRICT_STOP_DIRTY="false"
|
|
814
|
+
if [ -f "$IRON_LAWS_FILE" ]; then
|
|
815
|
+
if command -v jq >/dev/null 2>&1; then
|
|
816
|
+
STRICT_STOP_DIRTY=$(jq -r '
|
|
817
|
+
if (.mode // "advisory") == "strict" then "true"
|
|
818
|
+
elif ((.laws // []) | any(.id == "stop-clean-or-checkpointed" and .strict == true)) then "true"
|
|
819
|
+
else "false"
|
|
820
|
+
end
|
|
821
|
+
' "$IRON_LAWS_FILE" 2>/dev/null || echo "false")
|
|
822
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
823
|
+
STRICT_STOP_DIRTY=$(python3 - "$IRON_LAWS_FILE" <<'PY'
|
|
824
|
+
import json
|
|
825
|
+
import sys
|
|
826
|
+
value = "false"
|
|
827
|
+
try:
|
|
828
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
829
|
+
parsed = json.load(fh)
|
|
830
|
+
if str(parsed.get("mode", "advisory")) == "strict":
|
|
831
|
+
value = "true"
|
|
832
|
+
else:
|
|
833
|
+
for row in parsed.get("laws", []):
|
|
834
|
+
if isinstance(row, dict) and row.get("id") == "stop-clean-or-checkpointed" and row.get("strict") is True:
|
|
835
|
+
value = "true"
|
|
836
|
+
break
|
|
837
|
+
except Exception:
|
|
838
|
+
value = "false"
|
|
839
|
+
print(value)
|
|
840
|
+
PY
|
|
841
|
+
)
|
|
842
|
+
fi
|
|
843
|
+
fi
|
|
844
|
+
|
|
620
845
|
TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")
|
|
621
846
|
mkdir -p "$STATE_DIR" 2>/dev/null || true
|
|
622
847
|
CHECKPOINT_WRITTEN=0
|
|
@@ -742,6 +967,11 @@ if [ "$CHECKPOINT_WRITTEN" -eq 0 ]; then
|
|
|
742
967
|
CHECKPOINT_NOTE="Checkpoint update failed. Review ${RUNTIME_ROOT}/state/checkpoint.json manually."
|
|
743
968
|
fi
|
|
744
969
|
|
|
970
|
+
if [ "$DIRTY_STATE" = "dirty" ] && [ "$STRICT_STOP_DIRTY" = "true" ]; then
|
|
971
|
+
printf '[cclaw] Stop blocked by iron law "stop-clean-or-checkpointed": working tree is dirty. Commit/revert changes or update checkpoint blockers before ending the session.\\n' >&2
|
|
972
|
+
exit 1
|
|
973
|
+
fi
|
|
974
|
+
|
|
745
975
|
RUN_SYNC_NOTE="Run metadata sync removed; active artifacts stay in ${RUNTIME_ROOT}/artifacts until /cc-ops archive (or cclaw archive runtime)."
|
|
746
976
|
|
|
747
977
|
# --- Escape for JSON ---
|
|
@@ -775,7 +1005,7 @@ export function runHookDispatcherScript() {
|
|
|
775
1005
|
# Single entrypoint used by harness hook JSON wiring.
|
|
776
1006
|
set -euo pipefail
|
|
777
1007
|
|
|
778
|
-
${
|
|
1008
|
+
${RUNTIME_SHELL_DETECT_ROOT}
|
|
779
1009
|
|
|
780
1010
|
if [ "$#" -lt 1 ]; then
|
|
781
1011
|
printf 'Usage: bash ${RUNTIME_ROOT}/hooks/run-hook.cmd <session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor>\\n' >&2
|
|
@@ -826,7 +1056,7 @@ export function stageCompleteScript() {
|
|
|
826
1056
|
# mutation to \`cclaw internal advance-stage\`.
|
|
827
1057
|
set -euo pipefail
|
|
828
1058
|
|
|
829
|
-
${
|
|
1059
|
+
${RUNTIME_SHELL_DETECT_ROOT}
|
|
830
1060
|
|
|
831
1061
|
if [ "$#" -lt 1 ]; then
|
|
832
1062
|
printf 'Usage: bash ${RUNTIME_ROOT}/hooks/stage-complete.sh <stage> [--passed=...] [--evidence-json=...] [--waive-delegation=...] [--waiver-reason=...]\\n' >&2
|
|
@@ -857,9 +1087,7 @@ export function preCompactScript() {
|
|
|
857
1087
|
# having to re-derive it from scratch.
|
|
858
1088
|
set -uo pipefail
|
|
859
1089
|
|
|
860
|
-
${
|
|
861
|
-
|
|
862
|
-
INPUT=$(cat 2>/dev/null || echo '{}')
|
|
1090
|
+
${RUNTIME_SHELL_DETECT_ROOT}
|
|
863
1091
|
|
|
864
1092
|
STATE_DIR="$ROOT/${RUNTIME_ROOT}/state"
|
|
865
1093
|
STATE_FILE="$STATE_DIR/flow-state.json"
|
|
@@ -1004,27 +1232,3 @@ export { codexHooksJsonWithObservation as codexHooksJson } from "./observe.js";
|
|
|
1004
1232
|
// OpenCode plugin — JS module
|
|
1005
1233
|
// ---------------------------------------------------------------------------
|
|
1006
1234
|
export { opencodePluginJs } from "./opencode-plugin.js";
|
|
1007
|
-
// ---------------------------------------------------------------------------
|
|
1008
|
-
// AGENTS.md block for hooks
|
|
1009
|
-
// ---------------------------------------------------------------------------
|
|
1010
|
-
export function hooksAgentsMdBlock() {
|
|
1011
|
-
return `### Hooks (real lifecycle integration)
|
|
1012
|
-
|
|
1013
|
-
Cclaw generates real hook integrations for every harness that exposes a
|
|
1014
|
-
hook primitive:
|
|
1015
|
-
- **Claude/Cursor:** lifecycle rehydration + PreToolUse/PostToolUse + Stop
|
|
1016
|
-
- **OpenCode:** session lifecycle + system transform rehydration + bootstrap parity (digest/warnings/knowledge snapshot)
|
|
1017
|
-
- **Codex:** Codex CLI ≥ v0.114 exposes lifecycle hooks at \`.codex/hooks.json\`, gated behind \`[features] codex_hooks = true\` in \`~/.codex/config.toml\`. \`PreToolUse\`/\`PostToolUse\` intercept **only the \`Bash\` tool** in Codex; \`Write\`/\`Edit\`/\`WebSearch\`/MCP calls are substituted via the \`/cc\` skill bodies under \`.agents/skills/cc*/SKILL.md\` and explicit in-turn agent steps. See \`.cclaw/references/harnesses/codex-playbook.md\` for the coverage matrix.
|
|
1018
|
-
|
|
1019
|
-
| Harness | Hook file | Events |
|
|
1020
|
-
|---------|-----------|--------|
|
|
1021
|
-
| Claude Code | \`.claude/hooks/hooks.json\` | SessionStart(startup/resume/clear/compact), PreToolUse, PostToolUse, Stop |
|
|
1022
|
-
| Cursor | \`.cursor/hooks.json\` | sessionStart/sessionResume/sessionClear/sessionCompact, preToolUse, postToolUse, stop |
|
|
1023
|
-
| OpenCode | \`${RUNTIME_ROOT}/hooks/opencode-plugin.mjs\` | session.created/updated/resumed/cleared/compacted/idle, tool.execute.before/after, system transform |
|
|
1024
|
-
| Codex | \`.codex/hooks.json\` | SessionStart(startup/resume), UserPromptSubmit, PreToolUse(Bash), PostToolUse(Bash), Stop (feature-gated by \`codex_hooks = true\`) |
|
|
1025
|
-
|
|
1026
|
-
Hook state files:
|
|
1027
|
-
- \`${RUNTIME_ROOT}/state/stage-activity.jsonl\`
|
|
1028
|
-
- \`${RUNTIME_ROOT}/state/checkpoint.json\`
|
|
1029
|
-
`;
|
|
1030
|
-
}
|