@ngockhoale/ukit 2.4.2 → 2.5.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 +57 -0
- package/README.md +20 -0
- package/manifests/platform.full.yaml +51 -112
- package/package.json +2 -1
- package/scripts/index/refresh-index.mjs +47 -22
- package/src/cli/commands/doctor.js +132 -2
- package/src/cli/commands/uninstall.js +18 -0
- package/src/core/applyPlan.js +17 -2
- package/src/core/compact/threshold.js +36 -6
- package/src/core/diffPlan.js +35 -0
- package/src/core/fileOps.js +26 -0
- package/src/core/projectImportant.js +430 -0
- package/src/core/sensitiveValueScanner.js +118 -0
- package/src/core/status.js +55 -1
- package/src/core/uninstall.js +183 -3
- package/src/diagnostics/classifyHang.js +246 -0
- package/src/index/buildIndex.js +1033 -62
- package/templates/.claude/hooks/auto-allow-bash.sh +82 -93
- package/templates/.claude/hooks/block-dangerous.sh +31 -5
- package/templates/.claude/hooks/completion-gate.sh +51 -10
- package/templates/.claude/hooks/compress-output.sh +38 -6
- package/templates/.claude/hooks/context-hardcap-gate.sh +35 -6
- package/templates/.claude/hooks/context-window-guard.sh +128 -18
- package/templates/.claude/hooks/handoff-model-guard.sh +31 -5
- package/templates/.claude/hooks/handoff-resume.sh +31 -5
- package/templates/.claude/hooks/post-edit-verify.sh +31 -5
- package/templates/.claude/hooks/pre-edit-backup.sh +31 -5
- package/templates/.claude/hooks/project-important.sh +67 -0
- package/templates/.claude/hooks/protect-files.sh +31 -5
- package/templates/.claude/hooks/record-execution.sh +31 -5
- package/templates/.claude/hooks/sensitive-data-guard.sh +124 -56
- package/templates/.claude/hooks/skill-router.sh +31 -5
- package/templates/.claude/hooks/stale-spec-guard.sh +31 -5
- package/templates/.claude/hooks/task-watchdog.sh +108 -123
- package/templates/.claude/hooks/verification-guard.sh +107 -112
- package/templates/.claude/hooks/vision-router.sh +49 -13
- package/templates/.claude/settings.json +5 -5
- package/templates/.claude/ukit/index/lib/index-core.mjs +960 -63
- package/templates/.claude/ukit/index/refresh-index.mjs +47 -22
- package/templates/.claude/ukit/index/route-task.mjs +610 -4
- package/templates/.claude/ukit/runtime/async-lock.mjs +340 -0
- package/templates/.claude/ukit/runtime/compact-threshold.mjs +73 -24
- package/templates/.claude/ukit/runtime/context-capacity.mjs +144 -0
- package/templates/.claude/ukit/runtime/execution-ledger.mjs +664 -170
- package/templates/.claude/ukit/runtime/hook-chain-budget.mjs +92 -0
- package/templates/.claude/ukit/runtime/hook-chain-runner.mjs +84 -29
- package/templates/.claude/ukit/runtime/hook-input.sh +85 -5
- package/templates/.claude/ukit/runtime/hook-payload-store.mjs +160 -0
- package/templates/.claude/ukit/runtime/hook-process.mjs +250 -0
- package/templates/.claude/ukit/runtime/hook-telemetry.mjs +255 -0
- package/templates/.claude/ukit/runtime/hook-telemetry.sh +60 -0
- package/templates/.claude/ukit/runtime/project-important.mjs +381 -0
- package/templates/.claude/ukit/runtime/sensitive-value-scanner.mjs +128 -0
- package/templates/.claude/ukit/runtime/stop-coordinator.mjs +509 -0
- package/templates/.claude/ukit/runtime/task-watchdog.mjs +180 -6
- package/templates/.claude/ukit/runtime/transcript-tail.mjs +1 -1
- package/templates/.omp/hooks/pre/ukit-bridge.js +178 -61
- package/templates/AGENTS.md +8 -0
- package/templates/PROJECT_IMPORTANT.md +9 -0
|
@@ -25,16 +25,42 @@
|
|
|
25
25
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
26
|
# shellcheck source=/dev/null
|
|
27
27
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
28
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
29
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
30
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
31
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
28
32
|
trap ukit_cleanup_hook_input EXIT
|
|
29
33
|
# Bounded stdin (H01): stage before anything reads it; Node gets a path, never the payload.
|
|
30
34
|
ukit_stage_hook_input 2097152 truncate || exit 0
|
|
31
35
|
else
|
|
32
|
-
# Runtime helper missing (pre-install tree): bounded
|
|
33
|
-
#
|
|
36
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
37
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
38
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
34
39
|
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
if [ -e /dev/fd/0 ]; then
|
|
41
|
+
exec 8<&0
|
|
42
|
+
head -c 2097153 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
43
|
+
else
|
|
44
|
+
head -c 2097153 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
45
|
+
fi
|
|
46
|
+
__ukit_reader=$!
|
|
47
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
48
|
+
case "$__ukit_stage_ms" in
|
|
49
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
50
|
+
esac
|
|
51
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
52
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
53
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
54
|
+
__ukit_waiter=$!
|
|
55
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
56
|
+
__ukit_stage_rc=$?
|
|
57
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
58
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
59
|
+
exec 8<&-
|
|
60
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
61
|
+
# A bounded cut (rc != 0) leaves a short payload that the JSON consumer
|
|
62
|
+
# already degrades on - the shared helper's truncate posture (R4.5).
|
|
63
|
+
if [ "$__ukit_size" -gt 2097152 ]; then
|
|
38
64
|
truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
|
|
39
65
|
fi
|
|
40
66
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
@@ -80,12 +106,18 @@ const projectRoot = process.env.PROJECT_ROOT;
|
|
|
80
106
|
const transcriptPath = typeof payload.transcript_path === 'string' ? payload.transcript_path.trim() : '';
|
|
81
107
|
if (!transcriptPath) process.exit(0);
|
|
82
108
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
109
|
+
// Operator config is read once; a missing/corrupt file just means the defaults.
|
|
110
|
+
let runtimeConfig = {};
|
|
111
|
+
try {
|
|
112
|
+
runtimeConfig = JSON.parse(fs.readFileSync(path.join(projectRoot, '.ukit', 'storage', 'config.json'), 'utf8'));
|
|
113
|
+
} catch { /* fall through to the defaults */ }
|
|
114
|
+
if (!runtimeConfig || typeof runtimeConfig !== 'object') runtimeConfig = {};
|
|
115
|
+
|
|
116
|
+
// Absolute operator ceiling (compact.hardCapTokens). Negotiated capacity can only ever
|
|
117
|
+
// tighten this, never raise it.
|
|
118
|
+
function operatorHardCap() {
|
|
119
|
+
const value = runtimeConfig?.compact?.hardCapTokens;
|
|
120
|
+
if (typeof value === 'number' && Number.isFinite(value) && Number.isInteger(value) && value > 0) return value;
|
|
89
121
|
return 500_000;
|
|
90
122
|
}
|
|
91
123
|
|
|
@@ -160,9 +192,89 @@ for (let i = start; i < entries.length; i += 1) {
|
|
|
160
192
|
}
|
|
161
193
|
|
|
162
194
|
const estimatedTokens = Math.round(contentChars / CHARS_PER_TOKEN);
|
|
163
|
-
|
|
195
|
+
|
|
196
|
+
// Latest assistant model in the live window — this is the model that must fit the NEXT
|
|
197
|
+
// request, so capacity negotiation follows it. A mid-session gateway swap to a smaller
|
|
198
|
+
// model tightens the cap immediately; the earlier model's window is never sticky.
|
|
199
|
+
// (Hoisted above the ratio so both capacity negotiation and the swap note share it.)
|
|
200
|
+
const RECENT_MODEL_WINDOW = 40;
|
|
201
|
+
const recentModels = modelSequence.slice(-RECENT_MODEL_WINDOW);
|
|
202
|
+
const currentModel = recentModels[recentModels.length - 1] || null;
|
|
203
|
+
|
|
204
|
+
// Negotiated capacity (H22): derive the advisory cap from the best available evidence —
|
|
205
|
+
// env override > config override > verified model table > conservative default — instead
|
|
206
|
+
// of assuming one hard-coded 500k window. The source is surfaced in the warning lines
|
|
207
|
+
// below (capacitySource=...) so a mis-negotiated cap is diagnosable at a glance. A
|
|
208
|
+
// missing runtime helper (pre-install tree) keeps the shipped fallback behavior —
|
|
209
|
+
// advisory and fail-open either way.
|
|
210
|
+
async function loadCapacityResolver() {
|
|
211
|
+
try {
|
|
212
|
+
return await import(pathToFileURL(path.join(
|
|
213
|
+
process.env.SCRIPT_DIR || process.cwd(),
|
|
214
|
+
'..',
|
|
215
|
+
'ukit',
|
|
216
|
+
'runtime',
|
|
217
|
+
'context-capacity.mjs',
|
|
218
|
+
)).href);
|
|
219
|
+
} catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let hardCap = operatorHardCap();
|
|
225
|
+
let capacitySource = 'default';
|
|
226
|
+
let negotiatedCapacity = null;
|
|
227
|
+
try {
|
|
228
|
+
const capacityModule = await loadCapacityResolver();
|
|
229
|
+
if (capacityModule && typeof capacityModule.resolveContextCapTokens === 'function') {
|
|
230
|
+
const resolved = capacityModule.resolveContextCapTokens({
|
|
231
|
+
env: process.env,
|
|
232
|
+
config: runtimeConfig,
|
|
233
|
+
modelMetadata: { model: currentModel },
|
|
234
|
+
});
|
|
235
|
+
hardCap = resolved.capTokens;
|
|
236
|
+
capacitySource = resolved.capacity?.source ?? 'default';
|
|
237
|
+
negotiatedCapacity = resolved.capacity ?? null;
|
|
238
|
+
}
|
|
239
|
+
} catch { /* negotiation is advisory — the operator ceiling already applies */ }
|
|
164
240
|
const ratio = estimatedTokens / hardCap;
|
|
165
241
|
|
|
242
|
+
// Publish what this hook negotiated so the compact-threshold/hard-cap-gate path stops
|
|
243
|
+
// assuming a 500k window: this is the only place with live evidence (env + config + the
|
|
244
|
+
// transcript's assistant models). Without the record, automatic compact waits until 240k
|
|
245
|
+
// and the fail-closed gate until 500k on a verified 200k route — after the provider already
|
|
246
|
+
// rejected the request. Best-effort: an unwritable cache dir must never break the guard.
|
|
247
|
+
const CAPACITY_RECORD_PATH = path.join(projectRoot, '.ukit', 'storage', 'cache', 'context-capacity.json');
|
|
248
|
+
if (negotiatedCapacity) {
|
|
249
|
+
try {
|
|
250
|
+
fs.mkdirSync(path.dirname(CAPACITY_RECORD_PATH), { recursive: true });
|
|
251
|
+
const previous = readCapacityRecord();
|
|
252
|
+
// Only rewrite when the evidence changed, so prompt caching sees a stable file.
|
|
253
|
+
if (!previous
|
|
254
|
+
|| previous.tokens !== negotiatedCapacity.tokens
|
|
255
|
+
|| previous.source !== negotiatedCapacity.source
|
|
256
|
+
|| previous.model !== (currentModel ?? null)) {
|
|
257
|
+
const record = {
|
|
258
|
+
tokens: negotiatedCapacity.tokens,
|
|
259
|
+
source: negotiatedCapacity.source,
|
|
260
|
+
confidence: negotiatedCapacity.confidence,
|
|
261
|
+
model: currentModel ?? null,
|
|
262
|
+
updatedAt: Date.now(),
|
|
263
|
+
};
|
|
264
|
+
fs.writeFileSync(CAPACITY_RECORD_PATH, `${JSON.stringify(record)}\n`, 'utf8');
|
|
265
|
+
}
|
|
266
|
+
} catch { /* fail open: negotiation is advisory */ }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function readCapacityRecord() {
|
|
270
|
+
try {
|
|
271
|
+
const parsed = JSON.parse(fs.readFileSync(CAPACITY_RECORD_PATH, 'utf8'));
|
|
272
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
273
|
+
} catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
166
278
|
// Debounce/state file shared by the context warning and the gateway model-swap note.
|
|
167
279
|
const GUARD_STATE_PATH = path.join(projectRoot, '.ukit', 'storage', 'cache', 'context-guard-state.json');
|
|
168
280
|
|
|
@@ -194,11 +306,9 @@ let persistedState = { ...guardState };
|
|
|
194
306
|
// keep working through swaps, and on a tool error after a swap, re-check the tool and
|
|
195
307
|
// call it again instead of stopping. Runs before the ratio early-exit because a swap
|
|
196
308
|
// can happen at any context level.
|
|
197
|
-
const RECENT_MODEL_WINDOW = 40;
|
|
198
309
|
const SWAP_NOTE_COOLDOWN_MS = 60 * 60 * 1000;
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
const currentModel = recentModels[recentModels.length - 1] || null;
|
|
310
|
+
// recentModels/currentModel were hoisted above for capacity negotiation; modelSequence
|
|
311
|
+
// was collected in the single pass over entries.
|
|
202
312
|
let previousModel = null;
|
|
203
313
|
for (let i = recentModels.length - 2; i >= 0; i -= 1) {
|
|
204
314
|
if (recentModels[i] !== currentModel) {
|
|
@@ -286,10 +396,10 @@ const run = readRunCursor();
|
|
|
286
396
|
|
|
287
397
|
const lines_out = [];
|
|
288
398
|
if (ratio >= 1) {
|
|
289
|
-
lines_out.push(`UKIT CONTEXT ALERT — live context ~${estimatedTokens.toLocaleString()} tokens, at/over the ${hardCap.toLocaleString()} cap (${pct}
|
|
399
|
+
lines_out.push(`UKIT CONTEXT ALERT — live context ~${estimatedTokens.toLocaleString()} tokens, at/over the ${hardCap.toLocaleString()} cap (${pct}%, capacitySource=${capacitySource}).`);
|
|
290
400
|
lines_out.push('The next few turns risk "Your input exceeds the context window of this model".');
|
|
291
401
|
} else {
|
|
292
|
-
lines_out.push(`UKIT CONTEXT WARNING — live context ~${estimatedTokens.toLocaleString()} tokens (${pct}% of the ${hardCap.toLocaleString()} cap).`);
|
|
402
|
+
lines_out.push(`UKIT CONTEXT WARNING — live context ~${estimatedTokens.toLocaleString()} tokens (${pct}% of the ${hardCap.toLocaleString()} cap, capacitySource=${capacitySource}).`);
|
|
293
403
|
}
|
|
294
404
|
|
|
295
405
|
if (estimatePartial) {
|
|
@@ -14,16 +14,42 @@
|
|
|
14
14
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
15
|
# shellcheck source=/dev/null
|
|
16
16
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
17
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
18
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
19
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
20
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
17
21
|
trap ukit_cleanup_hook_input EXIT
|
|
18
22
|
# Bounded stdin (H01): stage before anything reads it; the shell var is capped.
|
|
19
23
|
ukit_stage_hook_input 2097152 truncate || exit 0
|
|
20
24
|
else
|
|
21
|
-
# Runtime helper missing (pre-install tree): bounded
|
|
22
|
-
#
|
|
25
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
26
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
27
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
23
28
|
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
if [ -e /dev/fd/0 ]; then
|
|
30
|
+
exec 8<&0
|
|
31
|
+
head -c 2097153 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
32
|
+
else
|
|
33
|
+
head -c 2097153 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
34
|
+
fi
|
|
35
|
+
__ukit_reader=$!
|
|
36
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
37
|
+
case "$__ukit_stage_ms" in
|
|
38
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
39
|
+
esac
|
|
40
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
41
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
42
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
43
|
+
__ukit_waiter=$!
|
|
44
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
45
|
+
__ukit_stage_rc=$?
|
|
46
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
47
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
48
|
+
exec 8<&-
|
|
49
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
50
|
+
# A bounded cut (rc != 0) leaves a short payload that the JSON consumer
|
|
51
|
+
# already degrades on - the shared helper's truncate posture (R4.5).
|
|
52
|
+
if [ "$__ukit_size" -gt 2097152 ]; then
|
|
27
53
|
truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
|
|
28
54
|
fi
|
|
29
55
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
@@ -22,16 +22,42 @@
|
|
|
22
22
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
23
|
# shellcheck source=/dev/null
|
|
24
24
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
25
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
26
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
27
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
28
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
25
29
|
trap ukit_cleanup_hook_input EXIT
|
|
26
30
|
# Bounded stdin (H01): stage before anything reads it; Node gets a path, never the payload.
|
|
27
31
|
ukit_stage_hook_input 2097152 truncate || exit 0
|
|
28
32
|
else
|
|
29
|
-
# Runtime helper missing (pre-install tree): bounded
|
|
30
|
-
#
|
|
33
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
34
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
35
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
31
36
|
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
if [ -e /dev/fd/0 ]; then
|
|
38
|
+
exec 8<&0
|
|
39
|
+
head -c 2097153 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
40
|
+
else
|
|
41
|
+
head -c 2097153 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
42
|
+
fi
|
|
43
|
+
__ukit_reader=$!
|
|
44
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
45
|
+
case "$__ukit_stage_ms" in
|
|
46
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
47
|
+
esac
|
|
48
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
49
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
50
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
51
|
+
__ukit_waiter=$!
|
|
52
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
53
|
+
__ukit_stage_rc=$?
|
|
54
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
55
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
56
|
+
exec 8<&-
|
|
57
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
58
|
+
# A bounded cut (rc != 0) leaves a short payload that the JSON consumer
|
|
59
|
+
# already degrades on - the shared helper's truncate posture (R4.5).
|
|
60
|
+
if [ "$__ukit_size" -gt 2097152 ]; then
|
|
35
61
|
truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
|
|
36
62
|
fi
|
|
37
63
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
@@ -5,16 +5,42 @@
|
|
|
5
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
6
|
# shellcheck source=/dev/null
|
|
7
7
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
8
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
9
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
10
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
11
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
8
12
|
trap ukit_cleanup_hook_input EXIT
|
|
9
13
|
# Bounded stdin (H01): stage before anything reads it; the shell var is capped.
|
|
10
14
|
ukit_stage_hook_input 2097152 truncate || exit 0
|
|
11
15
|
else
|
|
12
|
-
# Runtime helper missing (pre-install tree): bounded
|
|
13
|
-
#
|
|
16
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
17
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
18
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
14
19
|
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
if [ -e /dev/fd/0 ]; then
|
|
21
|
+
exec 8<&0
|
|
22
|
+
head -c 2097153 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
23
|
+
else
|
|
24
|
+
head -c 2097153 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
25
|
+
fi
|
|
26
|
+
__ukit_reader=$!
|
|
27
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
28
|
+
case "$__ukit_stage_ms" in
|
|
29
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
30
|
+
esac
|
|
31
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
32
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
33
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
34
|
+
__ukit_waiter=$!
|
|
35
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
36
|
+
__ukit_stage_rc=$?
|
|
37
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
38
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
39
|
+
exec 8<&-
|
|
40
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
41
|
+
# A bounded cut (rc != 0) leaves a short payload that the JSON consumer
|
|
42
|
+
# already degrades on - the shared helper's truncate posture (R4.5).
|
|
43
|
+
if [ "$__ukit_size" -gt 2097152 ]; then
|
|
18
44
|
truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
|
|
19
45
|
fi
|
|
20
46
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
@@ -5,16 +5,42 @@
|
|
|
5
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
6
|
# shellcheck source=/dev/null
|
|
7
7
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
8
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
9
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
10
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
11
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
8
12
|
trap ukit_cleanup_hook_input EXIT
|
|
9
13
|
# Bounded stdin (H01): stage before anything reads it; the shell var is capped.
|
|
10
14
|
ukit_stage_hook_input 2097152 truncate || exit 0
|
|
11
15
|
else
|
|
12
|
-
# Runtime helper missing (pre-install tree): bounded
|
|
13
|
-
#
|
|
16
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
17
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
18
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
14
19
|
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
if [ -e /dev/fd/0 ]; then
|
|
21
|
+
exec 8<&0
|
|
22
|
+
head -c 2097153 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
23
|
+
else
|
|
24
|
+
head -c 2097153 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
25
|
+
fi
|
|
26
|
+
__ukit_reader=$!
|
|
27
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
28
|
+
case "$__ukit_stage_ms" in
|
|
29
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
30
|
+
esac
|
|
31
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
32
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
33
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
34
|
+
__ukit_waiter=$!
|
|
35
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
36
|
+
__ukit_stage_rc=$?
|
|
37
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
38
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
39
|
+
exec 8<&-
|
|
40
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
41
|
+
# A bounded cut (rc != 0) leaves a short payload that the JSON consumer
|
|
42
|
+
# already degrades on - the shared helper's truncate posture (R4.5).
|
|
43
|
+
if [ "$__ukit_size" -gt 2097152 ]; then
|
|
18
44
|
truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
|
|
19
45
|
fi
|
|
20
46
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# SessionStart hook: emit the deterministic PROJECT_IMPORTANT.md mandate envelope
|
|
3
|
+
# once per context epoch (docs/PROJECT_IMPORTANT_SPEC.md §6, §7, §9).
|
|
4
|
+
# ADVISORY ONLY — always exit 0. A missing runtime or a renderer failure degrades
|
|
5
|
+
# to the frozen `unreadable` warning line, never a raw exception, never a blocked
|
|
6
|
+
# session. No renderer logic lives here — the shared runtime module renders.
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
# shellcheck source=/dev/null
|
|
10
|
+
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
11
|
+
# Arm advisory latency telemetry when available; a missing runtime (pre-install
|
|
12
|
+
# tree) simply leaves telemetry off.
|
|
13
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
14
|
+
trap ukit_cleanup_hook_input EXIT
|
|
15
|
+
# Bounded stdin (H01): the SessionStart payload is unused, but it still flows
|
|
16
|
+
# through the staging bound so a producer that never closes the pipe cannot
|
|
17
|
+
# hold this hook open (R4.5).
|
|
18
|
+
ukit_stage_hook_input 1048576 truncate || exit 0
|
|
19
|
+
else
|
|
20
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
21
|
+
# inline — read cap+1 in the background, bound the wait, never drain with an
|
|
22
|
+
# unconditional `cat`.
|
|
23
|
+
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
24
|
+
if [ -e /dev/fd/0 ]; then
|
|
25
|
+
exec 8<&0
|
|
26
|
+
head -c 1048577 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
27
|
+
else
|
|
28
|
+
head -c 1048577 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
29
|
+
fi
|
|
30
|
+
__ukit_reader=$!
|
|
31
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
32
|
+
case "$__ukit_stage_ms" in
|
|
33
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
34
|
+
esac
|
|
35
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
36
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
37
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
38
|
+
__ukit_waiter=$!
|
|
39
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
40
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
41
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
42
|
+
exec 8<&-
|
|
43
|
+
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
47
|
+
SCRIPT="$PROJECT_ROOT/.claude/ukit/runtime/project-important.mjs"
|
|
48
|
+
UNREADABLE_WARNING='[UKit] PROJECT_IMPORTANT.md was not injected: the file could not be read. Run `ukit doctor`.'
|
|
49
|
+
|
|
50
|
+
if [ ! -f "$SCRIPT" ]; then
|
|
51
|
+
printf '%s\n' "$UNREADABLE_WARNING"
|
|
52
|
+
exit 0
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# The wrapped runtime self-arms on UKIT_HOOK_DEADLINE_MS (SCRIPT_ARM/EXIT_ARM),
|
|
56
|
+
# so a wedged read exits 0 inside the deadline instead of orphaning a node child.
|
|
57
|
+
INPUT="$(cat "$UKIT_INPUT_FILE" 2>/dev/null)"
|
|
58
|
+
OUTPUT=$(printf '%s' "$INPUT" | UKIT_PROJECT_ROOT="$PROJECT_ROOT" UKIT_HARNESS=claude-code UKIT_HOOK_DEADLINE_MS="${UKIT_HOOK_DEADLINE_MS:-3000}" node "$SCRIPT" 2>/dev/null)
|
|
59
|
+
STATUS=$?
|
|
60
|
+
|
|
61
|
+
if [ "$STATUS" -ne 0 ] || [ -z "$OUTPUT" ]; then
|
|
62
|
+
printf '%s\n' "$UNREADABLE_WARNING"
|
|
63
|
+
exit 0
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
printf '%s\n' "$OUTPUT"
|
|
67
|
+
exit 0
|
|
@@ -5,16 +5,42 @@
|
|
|
5
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
6
|
# shellcheck source=/dev/null
|
|
7
7
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
8
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
9
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
10
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
11
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
8
12
|
trap ukit_cleanup_hook_input EXIT
|
|
9
13
|
# Bounded stdin (H01): stage before anything reads it; the shell var is capped.
|
|
10
14
|
ukit_stage_hook_input 2097152 truncate || exit 0
|
|
11
15
|
else
|
|
12
|
-
# Runtime helper missing (pre-install tree): bounded
|
|
13
|
-
#
|
|
16
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
17
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
18
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
14
19
|
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
if [ -e /dev/fd/0 ]; then
|
|
21
|
+
exec 8<&0
|
|
22
|
+
head -c 2097153 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
23
|
+
else
|
|
24
|
+
head -c 2097153 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
25
|
+
fi
|
|
26
|
+
__ukit_reader=$!
|
|
27
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
28
|
+
case "$__ukit_stage_ms" in
|
|
29
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
30
|
+
esac
|
|
31
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
32
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
33
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
34
|
+
__ukit_waiter=$!
|
|
35
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
36
|
+
__ukit_stage_rc=$?
|
|
37
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
38
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
39
|
+
exec 8<&-
|
|
40
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
41
|
+
# A bounded cut (rc != 0) leaves a short payload that the JSON consumer
|
|
42
|
+
# already degrades on - the shared helper's truncate posture (R4.5).
|
|
43
|
+
if [ "$__ukit_size" -gt 2097152 ]; then
|
|
18
44
|
truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
|
|
19
45
|
fi
|
|
20
46
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
@@ -5,17 +5,43 @@
|
|
|
5
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
6
|
# shellcheck source=/dev/null
|
|
7
7
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
8
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
9
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
10
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
11
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
8
12
|
trap ukit_cleanup_hook_input EXIT
|
|
9
13
|
# Bounded stdin (H01): receipts can legitimately be large, so the staged file
|
|
10
14
|
# is the transport and the shell var only mirrors the capped payload.
|
|
11
15
|
ukit_stage_hook_input 33554432 temp-file || exit 0
|
|
12
16
|
else
|
|
13
|
-
# Runtime helper missing (pre-install tree): bounded
|
|
14
|
-
#
|
|
17
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
18
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
19
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
15
20
|
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
if [ -e /dev/fd/0 ]; then
|
|
22
|
+
exec 8<&0
|
|
23
|
+
head -c 33554433 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
24
|
+
else
|
|
25
|
+
head -c 33554433 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
26
|
+
fi
|
|
27
|
+
__ukit_reader=$!
|
|
28
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
29
|
+
case "$__ukit_stage_ms" in
|
|
30
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
31
|
+
esac
|
|
32
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
33
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
34
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
35
|
+
__ukit_waiter=$!
|
|
36
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
37
|
+
__ukit_stage_rc=$?
|
|
38
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
39
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
40
|
+
exec 8<&-
|
|
41
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
42
|
+
# A bounded cut (rc != 0) leaves a short payload that the JSON consumer
|
|
43
|
+
# already degrades on - the shared helper's truncate posture (R4.5).
|
|
44
|
+
if [ "$__ukit_size" -gt 33554432 ]; then
|
|
19
45
|
truncate -s 33554432 "$UKIT_INPUT_FILE" 2>/dev/null
|
|
20
46
|
fi
|
|
21
47
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|