@ngockhoale/ukit 2.4.2 → 2.4.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.
Files changed (43) hide show
  1. package/manifests/platform.full.yaml +19 -111
  2. package/package.json +2 -1
  3. package/scripts/index/refresh-index.mjs +47 -22
  4. package/src/core/compact/threshold.js +36 -6
  5. package/src/diagnostics/classifyHang.js +246 -0
  6. package/src/index/buildIndex.js +1033 -62
  7. package/templates/.claude/hooks/auto-allow-bash.sh +82 -93
  8. package/templates/.claude/hooks/block-dangerous.sh +31 -5
  9. package/templates/.claude/hooks/completion-gate.sh +51 -10
  10. package/templates/.claude/hooks/compress-output.sh +38 -6
  11. package/templates/.claude/hooks/context-hardcap-gate.sh +35 -6
  12. package/templates/.claude/hooks/context-window-guard.sh +128 -18
  13. package/templates/.claude/hooks/handoff-model-guard.sh +31 -5
  14. package/templates/.claude/hooks/handoff-resume.sh +31 -5
  15. package/templates/.claude/hooks/post-edit-verify.sh +31 -5
  16. package/templates/.claude/hooks/pre-edit-backup.sh +31 -5
  17. package/templates/.claude/hooks/protect-files.sh +31 -5
  18. package/templates/.claude/hooks/record-execution.sh +31 -5
  19. package/templates/.claude/hooks/sensitive-data-guard.sh +44 -8
  20. package/templates/.claude/hooks/skill-router.sh +31 -5
  21. package/templates/.claude/hooks/stale-spec-guard.sh +31 -5
  22. package/templates/.claude/hooks/task-watchdog.sh +108 -123
  23. package/templates/.claude/hooks/verification-guard.sh +107 -112
  24. package/templates/.claude/hooks/vision-router.sh +49 -13
  25. package/templates/.claude/settings.json +0 -5
  26. package/templates/.claude/ukit/index/lib/index-core.mjs +960 -63
  27. package/templates/.claude/ukit/index/refresh-index.mjs +47 -22
  28. package/templates/.claude/ukit/index/route-task.mjs +610 -4
  29. package/templates/.claude/ukit/runtime/async-lock.mjs +340 -0
  30. package/templates/.claude/ukit/runtime/compact-threshold.mjs +73 -24
  31. package/templates/.claude/ukit/runtime/context-capacity.mjs +144 -0
  32. package/templates/.claude/ukit/runtime/execution-ledger.mjs +664 -170
  33. package/templates/.claude/ukit/runtime/hook-chain-budget.mjs +92 -0
  34. package/templates/.claude/ukit/runtime/hook-chain-runner.mjs +84 -29
  35. package/templates/.claude/ukit/runtime/hook-input.sh +85 -5
  36. package/templates/.claude/ukit/runtime/hook-payload-store.mjs +160 -0
  37. package/templates/.claude/ukit/runtime/hook-process.mjs +250 -0
  38. package/templates/.claude/ukit/runtime/hook-telemetry.mjs +255 -0
  39. package/templates/.claude/ukit/runtime/hook-telemetry.sh +60 -0
  40. package/templates/.claude/ukit/runtime/stop-coordinator.mjs +509 -0
  41. package/templates/.claude/ukit/runtime/task-watchdog.mjs +180 -6
  42. package/templates/.claude/ukit/runtime/transcript-tail.mjs +1 -1
  43. package/templates/.omp/hooks/pre/ukit-bridge.js +171 -57
@@ -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 inline staging keeps the
30
- # advisory/fail-open behavior below alive with a capped payload file.
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
- head -c 2097153 > "$UKIT_INPUT_FILE" 2>/dev/null
33
- cat >/dev/null 2>&1
34
- if [ "$(wc -c < "$UKIT_INPUT_FILE" | tr -d '[:space:]')" -gt 2097152 ]; then
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
@@ -41,7 +67,7 @@ PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
41
67
  INPUT_FILE="$UKIT_INPUT_FILE" PROJECT_ROOT="$PROJECT_ROOT" node <<'NODE' || true
42
68
  const HOOK_DEADLINE_MS = Number.parseInt(process.env.UKIT_HOOK_DEADLINE_MS || '', 10) || 8000;
43
69
  setTimeout(() => process.exit(0), HOOK_DEADLINE_MS).unref();
44
- const fs = require('fs');
70
+ const fs = require('fs').promises;
45
71
  const path = require('path');
46
72
  const { spawn } = require('child_process');
47
73
  const { pathToFileURL } = require('url');
@@ -59,9 +85,15 @@ process.on('exit', () => {
59
85
  }
60
86
  });
61
87
 
62
- let rawInput = '';
63
- try { rawInput = fs.readFileSync(process.env.INPUT_FILE || '', 'utf8'); } catch {}
88
+ // TASK-015 fix round 1: every hot-path fs call is awaited. A synchronous read
89
+ // parks the event loop for the whole syscall, so the unref'd self-deadline above
90
+ // could never fire while a project filesystem stalled.
91
+ async function readTextSafe(filePath) {
92
+ try { return await fs.readFile(filePath, 'utf8'); } catch { return ''; }
93
+ }
94
+
64
95
  (async () => {
96
+ const rawInput = await readTextSafe(process.env.INPUT_FILE || '');
65
97
  const payload = (() => {
66
98
  try {
67
99
  const parsed = JSON.parse(rawInput);
@@ -86,7 +118,7 @@ try { rawInput = fs.readFileSync(process.env.INPUT_FILE || '', 'utf8'); } catch
86
118
  async function detectUnicModeSafe() {
87
119
  try {
88
120
  const gatewayPath = path.join(projectRoot, '.claude', 'ukit', 'index', 'unic-gateway.mjs');
89
- if (!fs.existsSync(gatewayPath)) return null;
121
+ try { await fs.access(gatewayPath); } catch { return null; }
90
122
  const mod = await import(pathToFileURL(gatewayPath).href);
91
123
  if (typeof mod.detectUnicGateway !== 'function') return null;
92
124
  return !!mod.detectUnicGateway({ rootDir: projectRoot })?.unicMode;
@@ -135,7 +167,7 @@ try { rawInput = fs.readFileSync(process.env.INPUT_FILE || '', 'utf8'); } catch
135
167
  // pair) and silently skips it. Kept here as a small inline copy rather than
136
168
  // a cross-file import — the contract is a few lines, the cost of drift is
137
169
  // small, and the hook must stay self-contained (no module resolution).
138
- function stripPathNoise(raw) {
170
+ async function stripPathNoise(raw) {
139
171
  const value = String(raw ?? '').trim();
140
172
  if (!value) return value;
141
173
  const pairs = { '(': ')', '[': ']', '{': '}', '"': '"', "'": "'" };
@@ -168,7 +200,8 @@ try { rawInput = fs.readFileSync(process.env.INPUT_FILE || '', 'utf8'); } catch
168
200
  for (const c of candidates) {
169
201
  if (!c) continue;
170
202
  try {
171
- if (fs.existsSync(path.resolve(c))) return c;
203
+ await fs.access(path.resolve(c));
204
+ return c;
172
205
  } catch {
173
206
  // ignore
174
207
  }
@@ -236,7 +269,8 @@ try { rawInput = fs.readFileSync(process.env.INPUT_FILE || '', 'utf8'); } catch
236
269
  // string inside the extractor.
237
270
  let extractorJson = null;
238
271
  const extractorPath = path.join(projectRoot, '.claude', 'ukit', 'index', 'extract-image.mjs');
239
- if (fs.existsSync(extractorPath)) {
272
+ try {
273
+ await fs.access(extractorPath);
240
274
  const args = [extractorPath, '--detect', '--mark-pending', '--json'];
241
275
  if (typeof payload?.transcript_path === 'string' && payload.transcript_path.trim()) {
242
276
  args.push('--session', payload.transcript_path.trim());
@@ -248,7 +282,7 @@ try { rawInput = fs.readFileSync(process.env.INPUT_FILE || '', 'utf8'); } catch
248
282
  // Mirror extractor's resolveRefs input contract: the LLM sometimes leaves
249
283
  // trailing punctuation/quotes on a path named in a prompt. Clean before
250
284
  // handing off so the hook and the extractor agree on the ref string.
251
- args.push('--ref', stripPathNoise(ref));
285
+ args.push('--ref', await stripPathNoise(ref));
252
286
  }
253
287
  let out = null;
254
288
  try {
@@ -260,6 +294,8 @@ try { rawInput = fs.readFileSync(process.env.INPUT_FILE || '', 'utf8'); } catch
260
294
  const head = typeof out === 'string' && out ? `; output head: ${out.slice(0, 200)}` : '';
261
295
  process.stderr.write(`vision-router: extractor call skipped (${err?.message ?? err})${head}\n`);
262
296
  }
297
+ } catch {
298
+ // Missing extractor is a normal fail-open condition.
263
299
  }
264
300
 
265
301
  // TASK-006 contract: in --mark-pending --json mode `images[]` is the
@@ -208,11 +208,6 @@
208
208
  "type": "command",
209
209
  "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/completion-gate.sh\"",
210
210
  "timeout": 4
211
- },
212
- {
213
- "type": "command",
214
- "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/task-watchdog.sh\"",
215
- "timeout": 4
216
211
  }
217
212
  ]
218
213
  }