@ngockhoale/ukit 2.6.10 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +70 -0
- package/manifests/documentation.yaml +24 -2
- package/manifests/instructionRules.yaml +62 -0
- package/manifests/platform.full.yaml +11 -0
- package/package.json +1 -1
- package/scripts/perf/audit-perf.mjs +920 -0
- package/src/cli/commands/doctor.js +23 -4
- package/src/cli/commands/feedback.js +97 -0
- package/src/cli/commands/memory.js +250 -1
- package/src/cli/commands/metrics.js +109 -1
- package/src/cli/index.js +7 -0
- package/src/core/codeintel/retriever.js +65 -0
- package/src/core/diffPlan.js +8 -0
- package/src/core/memory/store.js +7 -2
- package/src/core/ompConfigMerge.js +222 -0
- package/src/core/runInstallPipeline.js +11 -0
- package/src/core/runtimeConfig.js +64 -0
- package/src/core/unattendedDoctor.js +227 -0
- package/src/diagnostics/failurePatterns.js +1 -34
- package/src/diagnostics/feedbackEvents.js +196 -0
- package/src/diagnostics/laneStats.js +111 -0
- package/src/diagnostics/ledgerFiles.js +47 -0
- package/src/diagnostics/skillAccuracy.js +158 -0
- package/src/learning/patternProposals.js +151 -0
- package/src/learning/tuning.js +213 -0
- package/templates/.claude/hooks/block-dangerous.sh +76 -9
- package/templates/.claude/hooks/context-hardcap-gate.sh +26 -8
- package/templates/.claude/hooks/project-important.sh +70 -9
- package/templates/.claude/hooks/protect-files.sh +24 -7
- package/templates/.claude/hooks/sensitive-data-guard.sh +57 -5
- package/templates/.claude/hooks/session-episode.sh +84 -0
- package/templates/.claude/settings.json +29 -113
- package/templates/.claude/ukit/index/route-task.mjs +6 -0
- package/templates/.claude/ukit/runtime/hook-chain-runner.mjs +217 -10
- package/templates/.claude/ukit/runtime/hook-input.sh +119 -0
- package/templates/.omp/config.yml +32 -4
- package/templates/.omp/hooks/pre/ukit-bridge.js +12 -1
- package/templates/AGENTS.md +22 -10
- package/templates/CLAUDE.md +22 -10
- package/templates/adapter-presets/opencode/opencode.template.json +1 -1
- package/templates/docs/UKIT_INTERNALS.md +17 -0
- package/templates/instructions/core.md +22 -10
- package/templates/instructions/layout.yaml +12 -12
- package/templates/ukit/storage/config.json +20 -0
|
@@ -44,10 +44,19 @@ source "$HOOK_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
|
44
44
|
if [ "$__ukit_main_stage_rc" -ne 0 ]; then
|
|
45
45
|
# Infra failure during staging (mktemp/truncate): payload was never
|
|
46
46
|
# inspected — announce the degrade (SPEC §8), never a silent pass.
|
|
47
|
-
|
|
47
|
+
# TASK-003: `deny`, not `ask` — ask auto-approves under bypassPermissions.
|
|
48
|
+
ukit_emit_permission_decision deny "UKit could not fully read this tool-call payload (stdin staging was cut at the deadline/size bound), so context hard-cap gate cannot prove it safe."
|
|
49
|
+
fi
|
|
50
|
+
# BUG-C21-01 / TASK-003: degraded input first salvages tool_name — a COMPLETE
|
|
51
|
+
# recovered name takes the normal verdict path below; an unrecoverable one is
|
|
52
|
+
# refused with `deny` (never `ask`: bypassPermissions auto-approves ask).
|
|
53
|
+
if ukit_input_degraded; then
|
|
54
|
+
if UKIT_SALVAGED_TOOL_NAME="$(ukit_salvage_tool_field "$UKIT_INPUT_FILE" "tool_name")"; then
|
|
55
|
+
UKIT_SALVAGE_ACTIVE=1
|
|
56
|
+
else
|
|
57
|
+
ukit_emit_permission_decision deny "UKit could not fully read this tool-call payload (stdin staging was cut at the deadline/size bound) and the tool name could not be recovered, so context hard-cap gate cannot prove it safe."
|
|
58
|
+
fi
|
|
48
59
|
fi
|
|
49
|
-
# BUG-C21-01: a truncated/stalled staged payload must be announced (SPEC §8), never silently passed.
|
|
50
|
-
ukit_input_degraded && ukit_emit_input_degraded failclosed "context hard-cap gate"
|
|
51
60
|
else
|
|
52
61
|
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
53
62
|
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
@@ -80,17 +89,18 @@ else
|
|
|
80
89
|
# path's ukit_emit_input_degraded.
|
|
81
90
|
rm -f "$UKIT_INPUT_FILE"
|
|
82
91
|
UKIT_INPUT_FILE=""
|
|
83
|
-
# TASK-223: same emit shape as ukit_emit_permission_decision —
|
|
84
|
-
# direct host (non-zero discards stdout there), exit 2 inside
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
# TASK-223 + TASK-003: same emit shape as ukit_emit_permission_decision —
|
|
93
|
+
# exit 0 on the direct host (non-zero discards stdout there), exit 2 inside
|
|
94
|
+
# the omp chain. `deny`, never `ask`: bypassPermissions auto-approves ask.
|
|
95
|
+
printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"UKit could not fully read this tool-call payload (stdin staging was cut at the deadline/size bound), so context hard-cap gate cannot prove it safe."}}'
|
|
96
|
+
echo "BLOCKED: context hard-cap gate could not inspect a truncated/stalled payload; refused fail-closed." >&2
|
|
87
97
|
if [ -n "${UKIT_HOOK_CHAIN_RUNNER:-}" ]; then exit 2; fi
|
|
88
98
|
exit 0
|
|
89
99
|
fi
|
|
90
100
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
91
101
|
fi
|
|
92
102
|
|
|
93
|
-
INPUT_FILE="$UKIT_INPUT_FILE" PROJECT_ROOT="$PROJECT_ROOT" HOOK_DIR="$HOOK_DIR" node <<'NODE'
|
|
103
|
+
INPUT_FILE="$UKIT_INPUT_FILE" PROJECT_ROOT="$PROJECT_ROOT" HOOK_DIR="$HOOK_DIR" UKIT_SALVAGE_ACTIVE="${UKIT_SALVAGE_ACTIVE:-0}" UKIT_SALVAGED_TOOL_NAME="${UKIT_SALVAGED_TOOL_NAME:-}" node <<'NODE'
|
|
94
104
|
const HOOK_DEADLINE_MS = Number.parseInt(process.env.UKIT_HOOK_DEADLINE_MS || '', 10) || 3000;
|
|
95
105
|
// SPEC §8: a timed-out gate silently passes work it never evaluated — announce the
|
|
96
106
|
// degrade (fail-open posture kept: still exit 0).
|
|
@@ -164,6 +174,14 @@ async function readRunCursor() {
|
|
|
164
174
|
return {};
|
|
165
175
|
}
|
|
166
176
|
})();
|
|
177
|
+
// TASK-003: on the degraded path the staged prefix cannot parse, so the shell
|
|
178
|
+
// handed us the salvaged tool_name — reconstruct just enough payload for the
|
|
179
|
+
// gate's normal verdict.
|
|
180
|
+
if (process.env.UKIT_SALVAGE_ACTIVE === '1'
|
|
181
|
+
&& typeof process.env.UKIT_SALVAGED_TOOL_NAME === 'string'
|
|
182
|
+
&& process.env.UKIT_SALVAGED_TOOL_NAME) {
|
|
183
|
+
payload.tool_name = process.env.UKIT_SALVAGED_TOOL_NAME;
|
|
184
|
+
}
|
|
167
185
|
const toolName = payload?.tool_name || '';
|
|
168
186
|
if (!GATED_TOOLS.has(toolName)) {
|
|
169
187
|
process.exit(0);
|
|
@@ -6,6 +6,58 @@
|
|
|
6
6
|
# session. No renderer logic lives here — the shared runtime module renders.
|
|
7
7
|
|
|
8
8
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
10
|
+
SCRIPT="$PROJECT_ROOT/.claude/ukit/runtime/project-important.mjs"
|
|
11
|
+
UNREADABLE_WARNING='[UKit] PROJECT_IMPORTANT.md was not injected: the file could not be read. Run `ukit doctor`.'
|
|
12
|
+
PROJECT_IMPORTANT_FILE="$PROJECT_ROOT/PROJECT_IMPORTANT.md"
|
|
13
|
+
PROJECT_IMPORTANT_CACHE_DIR="$PROJECT_ROOT/.ukit/storage/cache"
|
|
14
|
+
PROJECT_IMPORTANT_CACHE="$PROJECT_IMPORTANT_CACHE_DIR/project-important.cache"
|
|
15
|
+
|
|
16
|
+
# Cache invalidation uses both metadata and a cheap content checksum. Metadata
|
|
17
|
+
# avoids reading a stable file twice in the common case, while the checksum
|
|
18
|
+
# catches in-place edits that preserve size and timestamp.
|
|
19
|
+
ukit_pi_fingerprint() {
|
|
20
|
+
local metadata checksum
|
|
21
|
+
# GNU coreutils first: it rejects '%m:%z:%i' outright, while BSD stat would
|
|
22
|
+
# accept the GNU format string and silently print a filesystem summary.
|
|
23
|
+
metadata="$(/usr/bin/stat -c '%Y:%s:%i' "$PROJECT_IMPORTANT_FILE" 2>/dev/null)"
|
|
24
|
+
if [ -z "$metadata" ]; then
|
|
25
|
+
metadata="$(/usr/bin/stat -f '%m:%z:%i' "$PROJECT_IMPORTANT_FILE" 2>/dev/null)"
|
|
26
|
+
fi
|
|
27
|
+
[ -n "$metadata" ] || return 1
|
|
28
|
+
checksum="$(cksum < "$PROJECT_IMPORTANT_FILE" 2>/dev/null)" || return 1
|
|
29
|
+
[ -n "$checksum" ] || return 1
|
|
30
|
+
printf '%s|%s' "$metadata" "$checksum"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Resolve the runtime before consulting the cache. A stale cache must never
|
|
34
|
+
# hide a missing install or renderer, while a valid hit intentionally skips
|
|
35
|
+
# stdin staging: this advisory hook does not inspect its SessionStart payload,
|
|
36
|
+
# and waiting on a producer that holds the pipe open is the stall this hook is
|
|
37
|
+
# being fixed for (auto-prune-bash.sh, wired beside it, never reads stdin either).
|
|
38
|
+
if [ ! -f "$SCRIPT" ]; then
|
|
39
|
+
printf '%s\n' "$UNREADABLE_WARNING"
|
|
40
|
+
exit 0
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if [ -f "$PROJECT_IMPORTANT_FILE" ] && [ -f "$PROJECT_IMPORTANT_CACHE" ]; then
|
|
44
|
+
__ukit_pi_key="$(ukit_pi_fingerprint 2>/dev/null)"
|
|
45
|
+
if [ -n "$__ukit_pi_key" ]; then
|
|
46
|
+
__ukit_pi_cache="$(cat "$PROJECT_IMPORTANT_CACHE" 2>/dev/null)"
|
|
47
|
+
# Key on the first line; the rest is the renderer's exact stdout bytes.
|
|
48
|
+
# Anything malformed (empty, key-only, wrong key) falls through to the
|
|
49
|
+
# cold path, so a corrupt cache is a re-render, never a wrong answer.
|
|
50
|
+
if [ "${__ukit_pi_cache%%$'\n'*}" = "$__ukit_pi_key" ] \
|
|
51
|
+
&& [ "$__ukit_pi_cache" != "$__ukit_pi_key" ]; then
|
|
52
|
+
__ukit_pi_hit="${__ukit_pi_cache#"$__ukit_pi_key"$'\n'}"
|
|
53
|
+
if [ -n "$__ukit_pi_hit" ]; then
|
|
54
|
+
printf '%s\n' "$__ukit_pi_hit"
|
|
55
|
+
exit 0
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
|
|
9
61
|
# shellcheck source=/dev/null
|
|
10
62
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
11
63
|
# Arm advisory latency telemetry when available; a missing runtime (pre-install
|
|
@@ -60,15 +112,6 @@ else
|
|
|
60
112
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
61
113
|
fi
|
|
62
114
|
|
|
63
|
-
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
64
|
-
SCRIPT="$PROJECT_ROOT/.claude/ukit/runtime/project-important.mjs"
|
|
65
|
-
UNREADABLE_WARNING='[UKit] PROJECT_IMPORTANT.md was not injected: the file could not be read. Run `ukit doctor`.'
|
|
66
|
-
|
|
67
|
-
if [ ! -f "$SCRIPT" ]; then
|
|
68
|
-
printf '%s\n' "$UNREADABLE_WARNING"
|
|
69
|
-
exit 0
|
|
70
|
-
fi
|
|
71
|
-
|
|
72
115
|
# The wrapped runtime self-arms on UKIT_HOOK_DEADLINE_MS (SCRIPT_ARM/EXIT_ARM),
|
|
73
116
|
# so a wedged read exits 0 inside the deadline instead of orphaning a node child.
|
|
74
117
|
INPUT="$(cat "$UKIT_INPUT_FILE" 2>/dev/null)"
|
|
@@ -80,5 +123,23 @@ if [ "$STATUS" -ne 0 ] || [ -z "$OUTPUT" ]; then
|
|
|
80
123
|
exit 0
|
|
81
124
|
fi
|
|
82
125
|
|
|
126
|
+
# Cache writes are advisory and atomic. Recompute the key after rendering so a
|
|
127
|
+
# file edit racing the renderer cannot publish bytes under an old fingerprint.
|
|
128
|
+
__ukit_pi_render_key=""
|
|
129
|
+
if [ -f "$PROJECT_IMPORTANT_FILE" ]; then
|
|
130
|
+
__ukit_pi_render_key="$(ukit_pi_fingerprint 2>/dev/null)"
|
|
131
|
+
fi
|
|
132
|
+
if [ -n "$__ukit_pi_render_key" ]; then
|
|
133
|
+
if mkdir -p "$PROJECT_IMPORTANT_CACHE_DIR" 2>/dev/null; then
|
|
134
|
+
__ukit_pi_tmp="$PROJECT_IMPORTANT_CACHE.tmp.$$"
|
|
135
|
+
if printf '%s\n%s' "$__ukit_pi_render_key" "$OUTPUT" > "$__ukit_pi_tmp" 2>/dev/null \
|
|
136
|
+
&& mv -f "$__ukit_pi_tmp" "$PROJECT_IMPORTANT_CACHE" 2>/dev/null; then
|
|
137
|
+
:
|
|
138
|
+
else
|
|
139
|
+
rm -f "$__ukit_pi_tmp" 2>/dev/null || true
|
|
140
|
+
fi
|
|
141
|
+
fi
|
|
142
|
+
fi
|
|
143
|
+
|
|
83
144
|
printf '%s\n' "$OUTPUT"
|
|
84
145
|
exit 0
|
|
@@ -16,10 +16,20 @@ source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
|
16
16
|
if [ "$__ukit_main_stage_rc" -ne 0 ]; then
|
|
17
17
|
# Infra failure during staging (mktemp/truncate): payload was never
|
|
18
18
|
# inspected — announce the degrade (SPEC §8), never a silent pass.
|
|
19
|
-
|
|
19
|
+
# TASK-003: `deny`, not `ask` — ask auto-approves under bypassPermissions.
|
|
20
|
+
ukit_emit_permission_decision deny "UKit could not fully read this tool-call payload (stdin staging was cut at the deadline/size bound), so protected-file gate cannot prove it safe."
|
|
20
21
|
fi
|
|
21
|
-
# BUG-C21-01
|
|
22
|
-
|
|
22
|
+
# BUG-C21-01 / TASK-003: degraded input first attempts a bounded salvage of
|
|
23
|
+
# tool_input.file_path — a COMPLETE recovered path still gets the normal verdict
|
|
24
|
+
# below; an unrecoverable/incomplete one is refused with `deny` (never `ask`:
|
|
25
|
+
# bypassPermissions auto-approves ask on the direct host — review R1-1).
|
|
26
|
+
if ukit_input_degraded; then
|
|
27
|
+
if UKIT_SALVAGED_FILE_PATH="$(ukit_salvage_tool_field "$UKIT_INPUT_FILE" "tool_input.file_path")"; then
|
|
28
|
+
UKIT_SALVAGE_ACTIVE=1
|
|
29
|
+
else
|
|
30
|
+
ukit_emit_permission_decision deny "UKit could not fully read this tool-call payload (stdin staging was cut at the deadline/size bound) and the file path could not be recovered, so protected-file gate cannot prove it safe."
|
|
31
|
+
fi
|
|
32
|
+
fi
|
|
23
33
|
else
|
|
24
34
|
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
25
35
|
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
@@ -52,15 +62,21 @@ else
|
|
|
52
62
|
# path's ukit_emit_input_degraded.
|
|
53
63
|
rm -f "$UKIT_INPUT_FILE"
|
|
54
64
|
UKIT_INPUT_FILE=""
|
|
55
|
-
# TASK-223: same emit shape as ukit_emit_permission_decision —
|
|
56
|
-
# direct host (non-zero discards stdout there), exit 2 inside
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
# TASK-223 + TASK-003: same emit shape as ukit_emit_permission_decision —
|
|
66
|
+
# exit 0 on the direct host (non-zero discards stdout there), exit 2 inside
|
|
67
|
+
# the omp chain. `deny`, never `ask`: bypassPermissions auto-approves ask.
|
|
68
|
+
printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"UKit could not fully read this tool-call payload (stdin staging was cut at the deadline/size bound), so protected-file gate cannot prove it safe."}}'
|
|
69
|
+
echo "BLOCKED: protected-file gate could not inspect a truncated/stalled payload; refused fail-closed." >&2
|
|
59
70
|
if [ -n "${UKIT_HOOK_CHAIN_RUNNER:-}" ]; then exit 2; fi
|
|
60
71
|
exit 0
|
|
61
72
|
fi
|
|
62
73
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
63
74
|
fi
|
|
75
|
+
if [ "${UKIT_SALVAGE_ACTIVE:-0}" = "1" ]; then
|
|
76
|
+
# TASK-003: degraded path already recovered a COMPLETE file_path — skip the
|
|
77
|
+
# normal extraction (the staged prefix would not parse anyway).
|
|
78
|
+
FILE_PATH="$UKIT_SALVAGED_FILE_PATH"
|
|
79
|
+
else
|
|
64
80
|
INPUT="$(cat "$UKIT_INPUT_FILE")"
|
|
65
81
|
# jq is absent on stock macOS. Keep jq as the low-latency normal path, but fall back to
|
|
66
82
|
# UKit's required Node runtime so a missing optional binary cannot disable this gate.
|
|
@@ -80,6 +96,7 @@ process.stdin.on("end", () => {
|
|
|
80
96
|
});
|
|
81
97
|
' 2>/dev/null)
|
|
82
98
|
fi
|
|
99
|
+
fi
|
|
83
100
|
|
|
84
101
|
if [ -z "$FILE_PATH" ]; then
|
|
85
102
|
exit 0
|
|
@@ -30,9 +30,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
30
30
|
HOOK_DIR="$SCRIPT_DIR"
|
|
31
31
|
SCRIPT_PATH="$HOOK_DIR/../ukit/runtime/sensitive-value-scanner.mjs"
|
|
32
32
|
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
33
|
-
# Bounded stdin (H01)
|
|
34
|
-
#
|
|
35
|
-
#
|
|
33
|
+
# Bounded stdin (H01). TASK-003: staging uses `truncate` so a cut payload can be
|
|
34
|
+
# SALVAGED — when the decision-relevant field is provably complete the guard
|
|
35
|
+
# runs the real scan on it; when it cannot be recovered the gate refuses
|
|
36
|
+
# fail-closed (exit 2 + §8 systemMessage, never `ask`: bypassPermissions
|
|
37
|
+
# auto-approves `ask` on the direct host — review R1-1).
|
|
36
38
|
# shellcheck source=/dev/null
|
|
37
39
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
38
40
|
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
@@ -40,7 +42,7 @@ if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
|
40
42
|
# runtime (pre-install tree) simply leaves telemetry off.
|
|
41
43
|
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
42
44
|
trap ukit_cleanup_hook_input EXIT
|
|
43
|
-
ukit_stage_hook_input 2097152
|
|
45
|
+
ukit_stage_hook_input 2097152 truncate
|
|
44
46
|
STAGE_RC=$?
|
|
45
47
|
if [ "$STAGE_RC" -ne 0 ]; then
|
|
46
48
|
# BUG-C21-02: a fail-closed refusal must never be a SILENT block — emit the
|
|
@@ -51,6 +53,40 @@ source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
|
51
53
|
echo 'BLOCKED (sensitive data gate): payload could not be staged within bounds; never scanned (fail-closed).' >&2
|
|
52
54
|
exit 2
|
|
53
55
|
fi
|
|
56
|
+
# TASK-003 degraded-path salvage: recover hook_event_name + the channel's
|
|
57
|
+
# scanned field (prompt for UserPromptSubmit; tool_name + the decision field
|
|
58
|
+
# for PreToolUse Read/Grep/Bash). Every salvage step must prove its field
|
|
59
|
+
# COMPLETE; any failure refuses fail-closed — a partial field is never
|
|
60
|
+
# scanned (a truncated dangerous shape could parse benign = fail-open).
|
|
61
|
+
if ukit_input_degraded; then
|
|
62
|
+
__ukit_salvage_fail() {
|
|
63
|
+
printf '%s\n' '{"systemMessage":"UKit sensitive-data gate blocked this call: the tool-call payload was truncated or stalled during stdin staging and its scanned field could not be recovered intact, so it was never scanned. Fail-closed — reduce the payload or check for a stalled producer."}'
|
|
64
|
+
echo 'BLOCKED (sensitive data gate): truncated/stalled payload; scanned field unrecoverable — refused fail-closed.' >&2
|
|
65
|
+
exit 2
|
|
66
|
+
}
|
|
67
|
+
UKIT_SALVAGED_EVENT="$(ukit_salvage_tool_field "$UKIT_INPUT_FILE" "hook_event_name")" || __ukit_salvage_fail
|
|
68
|
+
UKIT_SALVAGED_TOOL_NAME=""
|
|
69
|
+
UKIT_SALVAGED_FIELD_NAME=""
|
|
70
|
+
UKIT_SALVAGED_FIELD_VALUE=""
|
|
71
|
+
case "$UKIT_SALVAGED_EVENT" in
|
|
72
|
+
UserPromptSubmit)
|
|
73
|
+
UKIT_SALVAGED_FIELD_NAME="prompt"
|
|
74
|
+
UKIT_SALVAGED_FIELD_VALUE="$(ukit_salvage_tool_field "$UKIT_INPUT_FILE" "prompt")" || __ukit_salvage_fail
|
|
75
|
+
;;
|
|
76
|
+
PreToolUse)
|
|
77
|
+
UKIT_SALVAGED_TOOL_NAME="$(ukit_salvage_tool_field "$UKIT_INPUT_FILE" "tool_name")" || __ukit_salvage_fail
|
|
78
|
+
case "$UKIT_SALVAGED_TOOL_NAME" in
|
|
79
|
+
Read) UKIT_SALVAGED_FIELD_NAME="file_path" ;;
|
|
80
|
+
Grep) UKIT_SALVAGED_FIELD_NAME="path" ;;
|
|
81
|
+
Bash) UKIT_SALVAGED_FIELD_NAME="command" ;;
|
|
82
|
+
*) __ukit_salvage_fail ;;
|
|
83
|
+
esac
|
|
84
|
+
UKIT_SALVAGED_FIELD_VALUE="$(ukit_salvage_tool_field "$UKIT_INPUT_FILE" "tool_input.$UKIT_SALVAGED_FIELD_NAME")" || __ukit_salvage_fail
|
|
85
|
+
;;
|
|
86
|
+
*) __ukit_salvage_fail ;;
|
|
87
|
+
esac
|
|
88
|
+
UKIT_SALVAGE_ACTIVE=1
|
|
89
|
+
fi
|
|
54
90
|
else
|
|
55
91
|
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
56
92
|
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
@@ -90,7 +126,7 @@ else
|
|
|
90
126
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
91
127
|
fi
|
|
92
128
|
|
|
93
|
-
INPUT_FILE="$UKIT_INPUT_FILE" PROJECT_ROOT="$PROJECT_ROOT" SCRIPT_PATH="$SCRIPT_PATH" UKIT_HOOK_DEADLINE_MS="${UKIT_HOOK_DEADLINE_MS:-3000}" node <<'NODE'
|
|
129
|
+
INPUT_FILE="$UKIT_INPUT_FILE" PROJECT_ROOT="$PROJECT_ROOT" SCRIPT_PATH="$SCRIPT_PATH" UKIT_HOOK_DEADLINE_MS="${UKIT_HOOK_DEADLINE_MS:-3000}" UKIT_SALVAGE_ACTIVE="${UKIT_SALVAGE_ACTIVE:-0}" UKIT_SALVAGED_EVENT="${UKIT_SALVAGED_EVENT:-}" UKIT_SALVAGED_TOOL_NAME="${UKIT_SALVAGED_TOOL_NAME:-}" UKIT_SALVAGED_FIELD_NAME="${UKIT_SALVAGED_FIELD_NAME:-}" UKIT_SALVAGED_FIELD_VALUE="${UKIT_SALVAGED_FIELD_VALUE:-}" node <<'NODE'
|
|
94
130
|
const HOOK_DEADLINE_MS = Number.parseInt(process.env.UKIT_HOOK_DEADLINE_MS || '', 10) || 3000;
|
|
95
131
|
// BUG-C21-03 (chosen posture: FAIL-CLOSED, parity with the staging refusal):
|
|
96
132
|
// a wedged scan must never pass an uninspected payload. The deadline emits a
|
|
@@ -127,6 +163,22 @@ const payload = (() => {
|
|
|
127
163
|
}
|
|
128
164
|
})();
|
|
129
165
|
|
|
166
|
+
// TASK-003: on the degraded path the staged prefix cannot parse, so the shell
|
|
167
|
+
// handed us the provably-COMPLETE salvaged fields — reconstruct just enough
|
|
168
|
+
// payload for the normal channel scan below.
|
|
169
|
+
if (process.env.UKIT_SALVAGE_ACTIVE === '1' && process.env.UKIT_SALVAGED_EVENT) {
|
|
170
|
+
payload.hook_event_name = process.env.UKIT_SALVAGED_EVENT;
|
|
171
|
+
if (process.env.UKIT_SALVAGED_EVENT === 'PreToolUse' && process.env.UKIT_SALVAGED_TOOL_NAME) {
|
|
172
|
+
payload.tool_name = process.env.UKIT_SALVAGED_TOOL_NAME;
|
|
173
|
+
payload.tool_input = {};
|
|
174
|
+
if (process.env.UKIT_SALVAGED_FIELD_NAME) {
|
|
175
|
+
payload.tool_input[process.env.UKIT_SALVAGED_FIELD_NAME] = process.env.UKIT_SALVAGED_FIELD_VALUE;
|
|
176
|
+
}
|
|
177
|
+
} else if (process.env.UKIT_SALVAGED_EVENT === 'UserPromptSubmit') {
|
|
178
|
+
payload.prompt = process.env.UKIT_SALVAGED_FIELD_VALUE;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
130
182
|
const projectRoot = process.env.PROJECT_ROOT || process.cwd();
|
|
131
183
|
|
|
132
184
|
async function readJsonSafe(filePath, fallback) {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# session-episode.sh — SessionEnd hook: append a session episode to memory v2.
|
|
3
|
+
#
|
|
4
|
+
# Contract (SPEC §7b, TASK-231):
|
|
5
|
+
# * ALWAYS exits 0 — a session must never be blocked from ending.
|
|
6
|
+
# * No decision JSON — UKIT_HOOK_CHAIN_RUNNER-safe.
|
|
7
|
+
# * Runs `ukit memory episode` ONLY when
|
|
8
|
+
# .ukit/storage/config.json → learning.episodes.autoWrite === true
|
|
9
|
+
# (read defensively — the namespace may not exist yet) AND `ukit` resolves
|
|
10
|
+
# on PATH. Episode writes dedupe on meta.ledgerKey, so double-invocation is
|
|
11
|
+
# harmless.
|
|
12
|
+
# * Exports UKIT_SESSION_ID from the stdin payload's session_id so the CLI
|
|
13
|
+
# resolves the same exec-ledger file the session wrote.
|
|
14
|
+
|
|
15
|
+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
16
|
+
CONFIG_FILE="$PROJECT_ROOT/.ukit/storage/config.json"
|
|
17
|
+
|
|
18
|
+
# Bounded stdin read (existing hook style): cap +1 byte in background so a
|
|
19
|
+
# producer that never closes the pipe cannot park the session teardown.
|
|
20
|
+
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-episode-in.XXXXXX")" || exit 0
|
|
21
|
+
if [ -e /dev/fd/0 ]; then
|
|
22
|
+
exec 8<&0
|
|
23
|
+
head -c 65537 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
24
|
+
UKIT_HEAD_PID=$!
|
|
25
|
+
# UKIT_HOOK_STAGE_MS:-2000 — bounded wait for the staged payload.
|
|
26
|
+
( sleep 2; kill "$UKIT_HEAD_PID" 2>/dev/null ) &
|
|
27
|
+
UKIT_WATCH_PID=$!
|
|
28
|
+
wait "$UKIT_HEAD_PID" 2>/dev/null
|
|
29
|
+
kill "$UKIT_WATCH_PID" 2>/dev/null
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Gate + session extraction in one bounded node step; all failures → exit 0.
|
|
33
|
+
# UKIT_HOOK_DEADLINE_MS:-5000 — keeps the registered 8s timeout comfortably
|
|
34
|
+
# above stage (2s) + deadline (5s) + margin (1s).
|
|
35
|
+
node -e '
|
|
36
|
+
const HOOK_DEADLINE_MS = Number.parseInt(process.env.UKIT_HOOK_DEADLINE_MS || "", 10) || 5000;
|
|
37
|
+
setTimeout(() => process.exit(0), HOOK_DEADLINE_MS).unref();
|
|
38
|
+
|
|
39
|
+
const fs = require("fs");
|
|
40
|
+
const { spawnSync } = require("child_process");
|
|
41
|
+
|
|
42
|
+
const inputFile = process.argv[1];
|
|
43
|
+
const configPath = process.argv[2];
|
|
44
|
+
const projectRoot = process.argv[3];
|
|
45
|
+
|
|
46
|
+
function readJson(filePath) {
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let payload = {};
|
|
55
|
+
try {
|
|
56
|
+
const raw = fs.readFileSync(inputFile, "utf8");
|
|
57
|
+
payload = JSON.parse(raw || "{}") || {};
|
|
58
|
+
} catch {}
|
|
59
|
+
|
|
60
|
+
const config = readJson(configPath) || {};
|
|
61
|
+
if (config?.learning?.episodes?.autoWrite !== true) process.exit(0);
|
|
62
|
+
|
|
63
|
+
const probe = spawnSync("ukit", ["--version"], {
|
|
64
|
+
shell: true, stdio: "ignore", timeout: 2000,
|
|
65
|
+
});
|
|
66
|
+
if (probe.error || probe.status === null || probe.status === undefined) process.exit(0);
|
|
67
|
+
|
|
68
|
+
const sessionId = typeof payload.session_id === "string" && payload.session_id.trim()
|
|
69
|
+
? payload.session_id.trim()
|
|
70
|
+
: null;
|
|
71
|
+
|
|
72
|
+
spawnSync("ukit", ["memory", "episode"], {
|
|
73
|
+
shell: true,
|
|
74
|
+
stdio: "ignore",
|
|
75
|
+
timeout: 4000,
|
|
76
|
+
cwd: projectRoot,
|
|
77
|
+
env: sessionId
|
|
78
|
+
? { ...process.env, UKIT_SESSION_ID: sessionId }
|
|
79
|
+
: process.env,
|
|
80
|
+
});
|
|
81
|
+
' "$UKIT_INPUT_FILE" "$CONFIG_FILE" "$PROJECT_ROOT" 2>/dev/null || true
|
|
82
|
+
|
|
83
|
+
rm -f "$UKIT_INPUT_FILE" 2>/dev/null || true
|
|
84
|
+
exit 0
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
"hooks": [
|
|
58
58
|
{
|
|
59
59
|
"type": "command",
|
|
60
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/sensitive-data-guard.sh\"",
|
|
61
|
-
"timeout":
|
|
60
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/ukit/runtime/hook-chain-runner.mjs\" --emit-verdict - \"$CLAUDE_PROJECT_DIR/.claude/hooks/sensitive-data-guard.sh\":8",
|
|
61
|
+
"timeout": 18
|
|
62
62
|
}
|
|
63
63
|
]
|
|
64
64
|
},
|
|
@@ -67,33 +67,8 @@
|
|
|
67
67
|
"hooks": [
|
|
68
68
|
{
|
|
69
69
|
"type": "command",
|
|
70
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/protect-files.sh\"",
|
|
71
|
-
"timeout":
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
"type": "command",
|
|
75
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/stale-spec-guard.sh\"",
|
|
76
|
-
"timeout": 11
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
"type": "command",
|
|
80
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/pre-edit-backup.sh\"",
|
|
81
|
-
"timeout": 8
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
"type": "command",
|
|
85
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/skill-router.sh\"",
|
|
86
|
-
"timeout": 20
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
"type": "command",
|
|
90
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/handoff-model-guard.sh\"",
|
|
91
|
-
"timeout": 8
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
"type": "command",
|
|
95
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/context-hardcap-gate.sh\"",
|
|
96
|
-
"timeout": 8
|
|
70
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/ukit/runtime/hook-chain-runner.mjs\" --emit-verdict - \"$CLAUDE_PROJECT_DIR/.claude/hooks/protect-files.sh\":8 \"$CLAUDE_PROJECT_DIR/.claude/hooks/stale-spec-guard.sh\":11 \"$CLAUDE_PROJECT_DIR/.claude/hooks/pre-edit-backup.sh\":8 \"$CLAUDE_PROJECT_DIR/.claude/hooks/skill-router.sh\":20 \"$CLAUDE_PROJECT_DIR/.claude/hooks/handoff-model-guard.sh\":8 \"$CLAUDE_PROJECT_DIR/.claude/hooks/context-hardcap-gate.sh\":8",
|
|
71
|
+
"timeout": 73
|
|
97
72
|
}
|
|
98
73
|
]
|
|
99
74
|
},
|
|
@@ -102,33 +77,8 @@
|
|
|
102
77
|
"hooks": [
|
|
103
78
|
{
|
|
104
79
|
"type": "command",
|
|
105
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/auto-allow-bash.sh\"",
|
|
106
|
-
"timeout":
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
"type": "command",
|
|
110
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/block-dangerous.sh\"",
|
|
111
|
-
"timeout": 8
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
"type": "command",
|
|
115
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/sensitive-data-guard.sh\"",
|
|
116
|
-
"timeout": 8
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
"type": "command",
|
|
120
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/handoff-model-guard.sh\"",
|
|
121
|
-
"timeout": 8
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
"type": "command",
|
|
125
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/context-hardcap-gate.sh\"",
|
|
126
|
-
"timeout": 8
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
"type": "command",
|
|
130
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/verification-guard.sh\"",
|
|
131
|
-
"timeout": 8
|
|
80
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/ukit/runtime/hook-chain-runner.mjs\" --emit-verdict - \"$CLAUDE_PROJECT_DIR/.claude/hooks/auto-allow-bash.sh\":15 \"$CLAUDE_PROJECT_DIR/.claude/hooks/block-dangerous.sh\":8 \"$CLAUDE_PROJECT_DIR/.claude/hooks/sensitive-data-guard.sh\":8 \"$CLAUDE_PROJECT_DIR/.claude/hooks/handoff-model-guard.sh\":8 \"$CLAUDE_PROJECT_DIR/.claude/hooks/context-hardcap-gate.sh\":8 \"$CLAUDE_PROJECT_DIR/.claude/hooks/verification-guard.sh\":8",
|
|
81
|
+
"timeout": 65
|
|
132
82
|
}
|
|
133
83
|
]
|
|
134
84
|
}
|
|
@@ -139,8 +89,8 @@
|
|
|
139
89
|
"hooks": [
|
|
140
90
|
{
|
|
141
91
|
"type": "command",
|
|
142
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/record-execution.sh\"",
|
|
143
|
-
"timeout":
|
|
92
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/ukit/runtime/hook-chain-runner.mjs\" --emit-verdict - \"$CLAUDE_PROJECT_DIR/.claude/hooks/record-execution.sh\":8",
|
|
93
|
+
"timeout": 18
|
|
144
94
|
}
|
|
145
95
|
]
|
|
146
96
|
},
|
|
@@ -149,18 +99,8 @@
|
|
|
149
99
|
"hooks": [
|
|
150
100
|
{
|
|
151
101
|
"type": "command",
|
|
152
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/post-edit-verify.sh\"",
|
|
153
|
-
"timeout":
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
"type": "command",
|
|
157
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/record-execution.sh\"",
|
|
158
|
-
"timeout": 8
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
"type": "command",
|
|
162
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/task-watchdog.sh\"",
|
|
163
|
-
"timeout": 8
|
|
102
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/ukit/runtime/hook-chain-runner.mjs\" --emit-verdict - \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-edit-verify.sh\":8 \"$CLAUDE_PROJECT_DIR/.claude/hooks/record-execution.sh\":8 \"$CLAUDE_PROJECT_DIR/.claude/hooks/task-watchdog.sh\":8",
|
|
103
|
+
"timeout": 34
|
|
164
104
|
}
|
|
165
105
|
]
|
|
166
106
|
},
|
|
@@ -169,13 +109,8 @@
|
|
|
169
109
|
"hooks": [
|
|
170
110
|
{
|
|
171
111
|
"type": "command",
|
|
172
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/compress-output.sh\"",
|
|
173
|
-
"timeout":
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
"type": "command",
|
|
177
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/record-execution.sh\"",
|
|
178
|
-
"timeout": 8
|
|
112
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/ukit/runtime/hook-chain-runner.mjs\" --emit-verdict - \"$CLAUDE_PROJECT_DIR/.claude/hooks/compress-output.sh\":12 \"$CLAUDE_PROJECT_DIR/.claude/hooks/record-execution.sh\":8",
|
|
113
|
+
"timeout": 30
|
|
179
114
|
}
|
|
180
115
|
]
|
|
181
116
|
}
|
|
@@ -185,23 +120,8 @@
|
|
|
185
120
|
"hooks": [
|
|
186
121
|
{
|
|
187
122
|
"type": "command",
|
|
188
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/sensitive-data-guard.sh\"",
|
|
189
|
-
"timeout":
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
"type": "command",
|
|
193
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/skill-router.sh\"",
|
|
194
|
-
"timeout": 20
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
"type": "command",
|
|
198
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/vision-router.sh\"",
|
|
199
|
-
"timeout": 12
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
"type": "command",
|
|
203
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/context-window-guard.sh\"",
|
|
204
|
-
"timeout": 10
|
|
123
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/ukit/runtime/hook-chain-runner.mjs\" --emit-verdict - \"$CLAUDE_PROJECT_DIR/.claude/hooks/sensitive-data-guard.sh\":8 \"$CLAUDE_PROJECT_DIR/.claude/hooks/skill-router.sh\":20 \"$CLAUDE_PROJECT_DIR/.claude/hooks/vision-router.sh\":12 \"$CLAUDE_PROJECT_DIR/.claude/hooks/context-window-guard.sh\":10",
|
|
124
|
+
"timeout": 60
|
|
205
125
|
}
|
|
206
126
|
]
|
|
207
127
|
}
|
|
@@ -211,8 +131,8 @@
|
|
|
211
131
|
"hooks": [
|
|
212
132
|
{
|
|
213
133
|
"type": "command",
|
|
214
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/completion-gate.sh\"",
|
|
215
|
-
"timeout":
|
|
134
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/ukit/runtime/hook-chain-runner.mjs\" --emit-verdict - \"$CLAUDE_PROJECT_DIR/.claude/hooks/completion-gate.sh\":8",
|
|
135
|
+
"timeout": 18
|
|
216
136
|
}
|
|
217
137
|
]
|
|
218
138
|
}
|
|
@@ -222,8 +142,8 @@
|
|
|
222
142
|
"hooks": [
|
|
223
143
|
{
|
|
224
144
|
"type": "command",
|
|
225
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/reinject-context.sh\"",
|
|
226
|
-
"timeout":
|
|
145
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/ukit/runtime/hook-chain-runner.mjs\" --emit-verdict - \"$CLAUDE_PROJECT_DIR/.claude/hooks/reinject-context.sh\":8",
|
|
146
|
+
"timeout": 18
|
|
227
147
|
}
|
|
228
148
|
]
|
|
229
149
|
}
|
|
@@ -233,23 +153,19 @@
|
|
|
233
153
|
"hooks": [
|
|
234
154
|
{
|
|
235
155
|
"type": "command",
|
|
236
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/project-important.sh\"",
|
|
237
|
-
"timeout":
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
"type": "command",
|
|
246
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/reset-compact-pressure.sh\"",
|
|
247
|
-
"timeout": 12
|
|
248
|
-
},
|
|
156
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/ukit/runtime/hook-chain-runner.mjs\" --emit-verdict - \"$CLAUDE_PROJECT_DIR/.claude/hooks/project-important.sh\":8 \"$CLAUDE_PROJECT_DIR/.claude/hooks/auto-prune-bash.sh\":12 \"$CLAUDE_PROJECT_DIR/.claude/hooks/reset-compact-pressure.sh\":12 \"$CLAUDE_PROJECT_DIR/.claude/hooks/handoff-resume.sh\":8",
|
|
157
|
+
"timeout": 50
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
],
|
|
162
|
+
"SessionEnd": [
|
|
163
|
+
{
|
|
164
|
+
"hooks": [
|
|
249
165
|
{
|
|
250
166
|
"type": "command",
|
|
251
|
-
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/
|
|
252
|
-
"timeout":
|
|
167
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/ukit/runtime/hook-chain-runner.mjs\" --emit-verdict - \"$CLAUDE_PROJECT_DIR/.claude/hooks/session-episode.sh\":8",
|
|
168
|
+
"timeout": 18
|
|
253
169
|
}
|
|
254
170
|
]
|
|
255
171
|
}
|
|
@@ -3793,6 +3793,12 @@ function buildRouteAuditEntry({ route = null, state = null } = {}) {
|
|
|
3793
3793
|
}),
|
|
3794
3794
|
targetFile: route?.routingContext?.targetFile ?? null,
|
|
3795
3795
|
taskType: routingContext.taskType ?? null,
|
|
3796
|
+
// FR-202a (TASK-228): active-skill ids for phase-2 skill-accuracy roll-ups.
|
|
3797
|
+
// Telemetry only — deliberately excluded from dedupeKey/fingerprint inputs.
|
|
3798
|
+
skillIds: (route?.activeSkills ?? state?.activeSkills ?? [])
|
|
3799
|
+
.map((s) => s?.id)
|
|
3800
|
+
.filter(Boolean)
|
|
3801
|
+
.slice(0, 8),
|
|
3796
3802
|
executionMode: routeSummary.executionMode ?? null,
|
|
3797
3803
|
competingMode: routeSummary?.approachSelector?.competingMode ?? null,
|
|
3798
3804
|
competingScoreGap: routeSummary?.approachSelector?.competingScoreGap ?? null,
|