@ngockhoale/ukit 2.6.0 → 2.6.3
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 +59 -0
- package/manifests/platform.full.yaml +16 -0
- package/package.json +1 -1
- package/src/cli/commands/install.js +49 -2
- package/src/cli/commands/update.js +5 -0
- package/src/core/output/index.js +77 -0
- package/src/core/update.js +36 -5
- package/templates/.claude/commands/ukit/handoff-clear.md +2 -2
- package/templates/.claude/commands/ukit/handoff-fullstack.md +10 -7
- package/templates/.claude/hooks/auto-allow-bash.sh +17 -5
- package/templates/.claude/hooks/block-dangerous.sh +18 -5
- package/templates/.claude/hooks/completion-gate.sh +17 -5
- package/templates/.claude/hooks/compress-output.sh +15 -6
- package/templates/.claude/hooks/context-hardcap-gate.sh +77 -46
- package/templates/.claude/hooks/context-window-guard.sh +41 -27
- package/templates/.claude/hooks/handoff-model-guard.sh +41 -17
- package/templates/.claude/hooks/handoff-resume.sh +20 -7
- package/templates/.claude/hooks/post-edit-verify.sh +17 -5
- package/templates/.claude/hooks/pre-edit-backup.sh +17 -5
- package/templates/.claude/hooks/project-important.sh +18 -1
- package/templates/.claude/hooks/protect-files.sh +18 -5
- package/templates/.claude/hooks/record-execution.sh +17 -5
- package/templates/.claude/hooks/sensitive-data-guard.sh +48 -9
- package/templates/.claude/hooks/skill-router.sh +124 -86
- package/templates/.claude/hooks/stale-spec-guard.sh +22 -6
- package/templates/.claude/hooks/task-watchdog.sh +22 -7
- package/templates/.claude/hooks/verification-guard.sh +17 -5
- package/templates/.claude/hooks/vision-router.sh +17 -5
- package/templates/.claude/settings.json +15 -10
- package/templates/.claude/ukit/index/provision-worktree.mjs +30 -2
- package/templates/.claude/ukit/runtime/execution-ledger.mjs +115 -1
- package/templates/.claude/ukit/runtime/hook-input.sh +25 -0
- package/templates/.claude/ukit/runtime/hook-telemetry.mjs +86 -2
- package/templates/.claude/ukit/runtime/output-compression.mjs +87 -0
- package/templates/.omp/hooks/pre/ukit-bridge.js +110 -3
- package/templates/ukit/storage/config.json +1 -0
|
@@ -11,7 +11,15 @@ if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
|
11
11
|
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
12
12
|
trap ukit_cleanup_hook_input EXIT
|
|
13
13
|
# Bounded stdin (H01): stage before anything reads it; the shell var is capped.
|
|
14
|
-
ukit_stage_hook_input 2097152 truncate
|
|
14
|
+
ukit_stage_hook_input 2097152 truncate
|
|
15
|
+
__ukit_main_stage_rc=$?
|
|
16
|
+
if [ "$__ukit_main_stage_rc" -ne 0 ]; then
|
|
17
|
+
# Infra failure during staging (mktemp/truncate): payload was never
|
|
18
|
+
# inspected — announce the degrade (SPEC §8), never a silent pass.
|
|
19
|
+
ukit_emit_input_degraded advisory "pre-edit-backup"
|
|
20
|
+
fi
|
|
21
|
+
# BUG-C21-01: a truncated/stalled staged payload must be announced (SPEC §8), never silently passed.
|
|
22
|
+
ukit_input_degraded && ukit_emit_input_degraded advisory "pre-edit-backup"
|
|
15
23
|
else
|
|
16
24
|
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
17
25
|
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
@@ -38,10 +46,14 @@ else
|
|
|
38
46
|
wait "$__ukit_waiter" 2>/dev/null
|
|
39
47
|
exec 8<&-
|
|
40
48
|
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
if [ "$__ukit_stage_rc" -ne 0 ] || [ "$__ukit_size" -gt 2097152 ]; then
|
|
50
|
+
# BUG-C21-01 fallback parity (SPEC §8): a truncated/stalled staged payload
|
|
51
|
+
# must be announced, never silently acted on — same shape as the helper
|
|
52
|
+
# path's ukit_emit_input_degraded.
|
|
53
|
+
rm -f "$UKIT_INPUT_FILE"
|
|
54
|
+
UKIT_INPUT_FILE=""
|
|
55
|
+
printf '%s\n' '{"systemMessage":"UKit pre-edit-backup: tool-call payload was truncated or stalled during stdin staging; skipped this pass rather than act on incomplete input."}'
|
|
56
|
+
exit 0
|
|
45
57
|
fi
|
|
46
58
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
47
59
|
fi
|
|
@@ -15,7 +15,15 @@ source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
|
15
15
|
# Bounded stdin (H01): the SessionStart payload is unused, but it still flows
|
|
16
16
|
# through the staging bound so a producer that never closes the pipe cannot
|
|
17
17
|
# hold this hook open (R4.5).
|
|
18
|
-
ukit_stage_hook_input 1048576 truncate
|
|
18
|
+
ukit_stage_hook_input 1048576 truncate
|
|
19
|
+
__ukit_main_stage_rc=$?
|
|
20
|
+
if [ "$__ukit_main_stage_rc" -ne 0 ]; then
|
|
21
|
+
# Infra failure during staging (mktemp/truncate): payload was never
|
|
22
|
+
# inspected — announce the degrade (SPEC §8), never a silent pass.
|
|
23
|
+
ukit_emit_input_degraded advisory "project-important"
|
|
24
|
+
fi
|
|
25
|
+
# BUG-C21-01: a truncated/stalled staged payload must be announced (SPEC §8), never silently passed.
|
|
26
|
+
ukit_input_degraded && ukit_emit_input_degraded advisory "project-important"
|
|
19
27
|
else
|
|
20
28
|
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
21
29
|
# inline — read cap+1 in the background, bound the wait, never drain with an
|
|
@@ -37,9 +45,18 @@ else
|
|
|
37
45
|
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
38
46
|
__ukit_waiter=$!
|
|
39
47
|
wait "$__ukit_reader" 2>/dev/null
|
|
48
|
+
__ukit_stage_rc=$?
|
|
40
49
|
kill "$__ukit_waiter" 2>/dev/null
|
|
41
50
|
wait "$__ukit_waiter" 2>/dev/null
|
|
42
51
|
exec 8<&-
|
|
52
|
+
if [ "$__ukit_stage_rc" -ne 0 ]; then
|
|
53
|
+
# BUG-C21-01 fallback parity (SPEC §8): a stalled SessionStart payload is
|
|
54
|
+
# announced, never silently swallowed.
|
|
55
|
+
rm -f "$UKIT_INPUT_FILE"
|
|
56
|
+
UKIT_INPUT_FILE=""
|
|
57
|
+
printf '%s\n' '{"systemMessage":"UKit project-important: session payload stalled during stdin staging; skipped this pass rather than act on incomplete input."}'
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
43
60
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
44
61
|
fi
|
|
45
62
|
|
|
@@ -11,7 +11,15 @@ if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
|
11
11
|
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
12
12
|
trap ukit_cleanup_hook_input EXIT
|
|
13
13
|
# Bounded stdin (H01): stage before anything reads it; the shell var is capped.
|
|
14
|
-
ukit_stage_hook_input 2097152 truncate
|
|
14
|
+
ukit_stage_hook_input 2097152 truncate
|
|
15
|
+
__ukit_main_stage_rc=$?
|
|
16
|
+
if [ "$__ukit_main_stage_rc" -ne 0 ]; then
|
|
17
|
+
# Infra failure during staging (mktemp/truncate): payload was never
|
|
18
|
+
# inspected — announce the degrade (SPEC §8), never a silent pass.
|
|
19
|
+
ukit_emit_input_degraded failclosed "protected-file gate"
|
|
20
|
+
fi
|
|
21
|
+
# BUG-C21-01: a truncated/stalled staged payload must be announced (SPEC §8), never silently passed.
|
|
22
|
+
ukit_input_degraded && ukit_emit_input_degraded failclosed "protected-file gate"
|
|
15
23
|
else
|
|
16
24
|
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
17
25
|
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
@@ -38,10 +46,15 @@ else
|
|
|
38
46
|
wait "$__ukit_waiter" 2>/dev/null
|
|
39
47
|
exec 8<&-
|
|
40
48
|
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
if [ "$__ukit_stage_rc" -ne 0 ] || [ "$__ukit_size" -gt 2097152 ]; then
|
|
50
|
+
# BUG-C21-01 fallback parity (SPEC §8): a truncated/stalled staged payload
|
|
51
|
+
# must be announced, never silently acted on — same shape as the helper
|
|
52
|
+
# path's ukit_emit_input_degraded.
|
|
53
|
+
rm -f "$UKIT_INPUT_FILE"
|
|
54
|
+
UKIT_INPUT_FILE=""
|
|
55
|
+
printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","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. UKit defers this to a human decision."}}'
|
|
56
|
+
echo "BLOCKED: protected-file gate could not inspect a truncated/stalled payload; deferred to human." >&2
|
|
57
|
+
exit 2
|
|
45
58
|
fi
|
|
46
59
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
47
60
|
fi
|
|
@@ -12,7 +12,15 @@ source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
|
12
12
|
trap ukit_cleanup_hook_input EXIT
|
|
13
13
|
# Bounded stdin (H01): receipts can legitimately be large, so the staged file
|
|
14
14
|
# is the transport and the shell var only mirrors the capped payload.
|
|
15
|
-
ukit_stage_hook_input 33554432 temp-file
|
|
15
|
+
ukit_stage_hook_input 33554432 temp-file
|
|
16
|
+
__ukit_main_stage_rc=$?
|
|
17
|
+
if [ "$__ukit_main_stage_rc" -ne 0 ]; then
|
|
18
|
+
# Infra failure during staging (mktemp/truncate): payload was never
|
|
19
|
+
# inspected — announce the degrade (SPEC §8), never a silent pass.
|
|
20
|
+
ukit_emit_input_degraded advisory "record-execution"
|
|
21
|
+
fi
|
|
22
|
+
# BUG-C21-01: a truncated/stalled staged payload must be announced (SPEC §8), never silently passed.
|
|
23
|
+
ukit_input_degraded && ukit_emit_input_degraded advisory "record-execution"
|
|
16
24
|
else
|
|
17
25
|
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
18
26
|
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
@@ -39,10 +47,14 @@ else
|
|
|
39
47
|
wait "$__ukit_waiter" 2>/dev/null
|
|
40
48
|
exec 8<&-
|
|
41
49
|
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
if [ "$__ukit_stage_rc" -ne 0 ] || [ "$__ukit_size" -gt 33554432 ]; then
|
|
51
|
+
# BUG-C21-01 fallback parity (SPEC §8): a truncated/stalled staged payload
|
|
52
|
+
# must be announced, never silently acted on — same shape as the helper
|
|
53
|
+
# path's ukit_emit_input_degraded.
|
|
54
|
+
rm -f "$UKIT_INPUT_FILE"
|
|
55
|
+
UKIT_INPUT_FILE=""
|
|
56
|
+
printf '%s\n' '{"systemMessage":"UKit record-execution: tool-call payload was truncated or stalled during stdin staging; skipped this pass rather than act on incomplete input."}'
|
|
57
|
+
exit 0
|
|
46
58
|
fi
|
|
47
59
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
48
60
|
fi
|
|
@@ -43,7 +43,13 @@ source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
|
43
43
|
ukit_stage_hook_input 2097152 refuse
|
|
44
44
|
STAGE_RC=$?
|
|
45
45
|
if [ "$STAGE_RC" -ne 0 ]; then
|
|
46
|
-
|
|
46
|
+
# BUG-C21-02: a fail-closed refusal must never be a SILENT block — emit the
|
|
47
|
+
# §8 reason (labels only, no payload bytes) so the user sees why it stopped.
|
|
48
|
+
# Any non-zero rc is fail-closed: rc=1 is a staging infra failure and a bare
|
|
49
|
+
# exit 1 is a *non-blocking* hook error that would let unscanned input pass.
|
|
50
|
+
printf '%s\n' '{"systemMessage":"UKit sensitive-data gate blocked this call: the payload could not be staged within bounds (oversized or stalled stdin), so it was never scanned. Fail-closed — reduce the payload or check for a stalled producer."}'
|
|
51
|
+
echo 'BLOCKED (sensitive data gate): payload could not be staged within bounds; never scanned (fail-closed).' >&2
|
|
52
|
+
exit 2
|
|
47
53
|
fi
|
|
48
54
|
else
|
|
49
55
|
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
@@ -73,8 +79,12 @@ else
|
|
|
73
79
|
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
74
80
|
if [ "$__ukit_stage_rc" -ne 0 ] || [ "$__ukit_size" -gt 2097152 ]; then
|
|
75
81
|
# Fail-closed: an unstaged/oversized payload was never inspected
|
|
76
|
-
# (exit 2 blocks; 1 would be a non-blocking hook error).
|
|
82
|
+
# (exit 2 blocks; 1 would be a non-blocking hook error). BUG-C21-02: the
|
|
83
|
+
# refusal announces its §8 reason — a silent block is indistinguishable
|
|
84
|
+
# from a stall in the UI.
|
|
77
85
|
rm -f "$UKIT_INPUT_FILE"
|
|
86
|
+
printf '%s\n' '{"systemMessage":"UKit sensitive-data gate blocked this call: the payload could not be staged within bounds (oversized or stalled stdin), so it was never scanned. Fail-closed — reduce the payload or check for a stalled producer."}'
|
|
87
|
+
echo 'BLOCKED (sensitive data gate): payload could not be staged within bounds; never scanned (fail-closed).' >&2
|
|
78
88
|
exit 2
|
|
79
89
|
fi
|
|
80
90
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
@@ -82,13 +92,32 @@ fi
|
|
|
82
92
|
|
|
83
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'
|
|
84
94
|
const HOOK_DEADLINE_MS = Number.parseInt(process.env.UKIT_HOOK_DEADLINE_MS || '', 10) || 3000;
|
|
85
|
-
|
|
95
|
+
// BUG-C21-03 (chosen posture: FAIL-CLOSED, parity with the staging refusal):
|
|
96
|
+
// a wedged scan must never pass an uninspected payload. The deadline emits a
|
|
97
|
+
// redacted §8 degrade reason and exits 2 — never a silent exit-0 pass.
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
try {
|
|
100
|
+
process.stdout.write(`${JSON.stringify({
|
|
101
|
+
systemMessage: 'UKit sensitive-data gate timed out before finishing its scan; blocked fail-closed rather than pass an uninspected payload.',
|
|
102
|
+
})}\n`);
|
|
103
|
+
} catch {}
|
|
104
|
+
try {
|
|
105
|
+
// stderr mirror: piped stdout is async and process.exit() can truncate the
|
|
106
|
+
// pending write — without this the block can arrive with no visible reason.
|
|
107
|
+
process.stderr.write('BLOCKED (sensitive data gate): scan timed out; blocked fail-closed rather than pass an uninspected payload.\n');
|
|
108
|
+
} catch {}
|
|
109
|
+
process.exit(2);
|
|
110
|
+
}, HOOK_DEADLINE_MS).unref();
|
|
86
111
|
const fs = require('fs');
|
|
112
|
+
const fsp = fs.promises;
|
|
87
113
|
const path = require('path');
|
|
88
114
|
const { createHash } = require('crypto');
|
|
89
115
|
|
|
116
|
+
// BUG-C21-04: every fs call below is async — a sync read on a stalled mount
|
|
117
|
+
// parks the event loop and the deadline above could never fire.
|
|
118
|
+
void (async () => {
|
|
90
119
|
let rawInput = '';
|
|
91
|
-
try { rawInput =
|
|
120
|
+
try { rawInput = await fsp.readFile(process.env.INPUT_FILE || '', 'utf8'); } catch {}
|
|
92
121
|
const payload = (() => {
|
|
93
122
|
try {
|
|
94
123
|
const parsed = JSON.parse(rawInput);
|
|
@@ -100,20 +129,20 @@ const payload = (() => {
|
|
|
100
129
|
|
|
101
130
|
const projectRoot = process.env.PROJECT_ROOT || process.cwd();
|
|
102
131
|
|
|
103
|
-
function readJsonSafe(filePath, fallback) {
|
|
132
|
+
async function readJsonSafe(filePath, fallback) {
|
|
104
133
|
try {
|
|
105
|
-
return JSON.parse(
|
|
134
|
+
return JSON.parse(await fsp.readFile(filePath, 'utf8'));
|
|
106
135
|
} catch {
|
|
107
136
|
return fallback;
|
|
108
137
|
}
|
|
109
138
|
}
|
|
110
139
|
|
|
111
|
-
const config = readJsonSafe(path.join(projectRoot, '.ukit', 'storage', 'config.json'), null);
|
|
140
|
+
const config = await readJsonSafe(path.join(projectRoot, '.ukit', 'storage', 'config.json'), null);
|
|
112
141
|
if (config && config.security && config.security.sensitiveDataGate === false) {
|
|
113
142
|
process.exit(0);
|
|
114
143
|
}
|
|
115
144
|
|
|
116
|
-
const allowlist = readJsonSafe(path.join(projectRoot, '.ukit', 'storage', 'security', 'allowlist.json'), null) || {};
|
|
145
|
+
const allowlist = (await readJsonSafe(path.join(projectRoot, '.ukit', 'storage', 'security', 'allowlist.json'), null)) || {};
|
|
117
146
|
const allowedValueHashes = new Set(
|
|
118
147
|
Array.isArray(allowlist.values) ? allowlist.values.filter((v) => typeof v === 'string') : [],
|
|
119
148
|
);
|
|
@@ -395,6 +424,16 @@ process.stdout.write(`${JSON.stringify({
|
|
|
395
424
|
})}\n`);
|
|
396
425
|
process.exit(2);
|
|
397
426
|
})();
|
|
427
|
+
})();
|
|
398
428
|
NODE
|
|
399
429
|
|
|
400
|
-
|
|
430
|
+
NODE_RC=$?
|
|
431
|
+
# BUG-C21-03: a node crash (any non-blocking non-zero exit) means the payload
|
|
432
|
+
# was never fully scanned — fail-closed parity: announce + block, never a
|
|
433
|
+
# silent non-blocking hook error.
|
|
434
|
+
if [ "$NODE_RC" -ne 0 ] && [ "$NODE_RC" -ne 2 ]; then
|
|
435
|
+
printf '%s\n' '{"systemMessage":"UKit sensitive-data gate crashed before finishing its scan; blocked fail-closed rather than pass an uninspected payload. Run `ukit install` if this repeats."}'
|
|
436
|
+
echo "BLOCKED (sensitive data gate): scanner process exited $NODE_RC; payload never inspected (fail-closed)." >&2
|
|
437
|
+
exit 2
|
|
438
|
+
fi
|
|
439
|
+
exit "$NODE_RC"
|