@ngockhoale/ukit 2.4.1 → 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.
- package/CHANGELOG.md +65 -0
- package/manifests/platform.full.yaml +19 -111
- package/package.json +2 -1
- package/scripts/index/refresh-index.mjs +48 -18
- package/src/cli/commands/doctor.js +59 -2
- package/src/core/compact/threshold.js +36 -6
- package/src/core/gatewayProbe.js +143 -15
- package/src/core/gatewayResilienceEnv.js +136 -7
- package/src/diagnostics/classifyHang.js +246 -0
- package/src/index/buildIndex.js +1096 -75
- package/templates/.claude/hooks/auto-allow-bash.sh +99 -87
- package/templates/.claude/hooks/auto-prune-bash.sh +4 -0
- package/templates/.claude/hooks/block-dangerous.sh +46 -1
- package/templates/.claude/hooks/completion-gate.sh +65 -7
- package/templates/.claude/hooks/compress-output.sh +49 -2
- package/templates/.claude/hooks/context-hardcap-gate.sh +52 -4
- package/templates/.claude/hooks/context-window-guard.sh +204 -71
- package/templates/.claude/hooks/handoff-model-guard.sh +50 -3
- package/templates/.claude/hooks/handoff-resume.sh +47 -3
- package/templates/.claude/hooks/post-edit-verify.sh +45 -2
- package/templates/.claude/hooks/pre-edit-backup.sh +45 -2
- package/templates/.claude/hooks/protect-files.sh +46 -1
- package/templates/.claude/hooks/record-execution.sh +46 -2
- package/templates/.claude/hooks/reinject-context.sh +1 -1
- package/templates/.claude/hooks/reset-compact-pressure.sh +4 -0
- package/templates/.claude/hooks/sensitive-data-guard.sh +101 -18
- package/templates/.claude/hooks/skill-router.sh +59 -5
- package/templates/.claude/hooks/stale-spec-guard.sh +47 -2
- package/templates/.claude/hooks/task-watchdog.sh +129 -126
- package/templates/.claude/hooks/verification-guard.sh +136 -106
- package/templates/.claude/hooks/vision-router.sh +138 -18
- package/templates/.claude/settings.json +0 -5
- package/templates/.claude/ukit/index/lib/index-core.mjs +1027 -68
- package/templates/.claude/ukit/index/post-edit-verify.mjs +8 -0
- package/templates/.claude/ukit/index/pre-edit-backup.mjs +8 -0
- package/templates/.claude/ukit/index/refresh-index.mjs +48 -18
- package/templates/.claude/ukit/index/route-task.mjs +610 -4
- package/templates/.claude/ukit/index/stale-spec-check.mjs +8 -0
- 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 +672 -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.mjs +120 -0
- package/templates/.claude/ukit/runtime/hook-input.sh +140 -0
- 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/output-compression.mjs +8 -0
- package/templates/.claude/ukit/runtime/reinject-context.mjs +8 -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 +107 -0
- package/templates/.omp/hooks/pre/ukit-bridge.js +171 -57
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,71 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to UKit are documented here.
|
|
4
4
|
|
|
5
|
+
## 2.4.2 - 2026-09-16
|
|
6
|
+
|
|
7
|
+
Fix: the gateway resilience posture disabled the only recovery path a buffering gateway
|
|
8
|
+
leaves open — the direct client-side cause of the recurring "session stands still mid-turn
|
|
9
|
+
until the user interrupts and re-prompts" freeze. Live probes of the stall-class gateway on
|
|
10
|
+
2026-09-16 proved the full chain: the gateway still buffers entire streams (a 16-token
|
|
11
|
+
probe returned 23 SSE events in ONE body read), so a long generation is silent until it
|
|
12
|
+
finishes; the non-streaming route is healthy (HTTP 200 + valid Anthropic Message); and the
|
|
13
|
+
managed env shipped since 2.3.16 kept the non-streaming fallback DISABLED with a 10-minute
|
|
14
|
+
stream-idle watchdog — meaning the client sat in dead air and, on abort, had no recovery
|
|
15
|
+
path at all. The managed posture now flips to fail-fast-and-recover.
|
|
16
|
+
|
|
17
|
+
- **Posture flip** (`src/core/gatewayResilienceEnv.js`): `CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK`
|
|
18
|
+
managed default `'1'` → `'0'` (fallback stays enabled — on a buffering gateway a
|
|
19
|
+
non-streaming POST is the one request shape without an idle watchdog), and
|
|
20
|
+
`CLAUDE_STREAM_IDLE_TIMEOUT_MS` `'600000'` → `'120000'` (a silent stream aborts after
|
|
21
|
+
2 minutes and recovers via the healthy non-streaming route instead of hanging for 10).
|
|
22
|
+
Values equal to the previously shipped defaults are UKit's own earlier writes and are
|
|
23
|
+
migrated on the next `ukit install` (new `GATEWAY_RESILIENCE_ENV_LEGACY_DEFAULTS` map +
|
|
24
|
+
`migrated` report field, backup written as usual); genuinely user-tuned values are still
|
|
25
|
+
never touched.
|
|
26
|
+
- **Probe misdiagnosis fixed** (`src/core/gatewayProbe.js`): the probe 404'd healthy
|
|
27
|
+
alias-based gateways because it POSTed the built-in default `claude-sonnet-4-5`, which is
|
|
28
|
+
not provisioned there ("No active credentials for provider: claude", `model_not_found`,
|
|
29
|
+
reproduced live) while real sessions run on the `ANTHROPIC_DEFAULT_*_MODEL` lane aliases.
|
|
30
|
+
The probe now resolves `ANTHROPIC_MODEL` → `ANTHROPIC_DEFAULT_OPUS/SONNET/HAIKU_MODEL`
|
|
31
|
+
before the built-in default and reports the probed model. `anthropic-version` is now sent
|
|
32
|
+
for the Bearer scheme too (live-verified accepted), and the API key is resolved through
|
|
33
|
+
the same 3-tier order as the base URL (env → project settings → home settings) so plain
|
|
34
|
+
terminals no longer probe keyless (`resolveGatewayApiKey`).
|
|
35
|
+
- **request-id re-classified** (same file, `docs/GATEWAY.md` §3): a missing `request-id`
|
|
36
|
+
on an HTTP 200 + valid Anthropic Message is an advisory hint, not a FAIL — the real
|
|
37
|
+
gateway ships no request-id even on healthy responses and sessions complete fine. The
|
|
38
|
+
Message-shape hard gate still catches the 888-byte error-envelope class from 2.3.16.
|
|
39
|
+
- **Docs**: `docs/GATEWAY.md` intro/§1/§2/§3/§4/§5 updated for the fail-fast posture,
|
|
40
|
+
the 2026-09-16 buffering reconfirmation, the legacy-value migration, and the probe's
|
|
41
|
+
alias-model + key resolution.
|
|
42
|
+
- **Verification**: focused suites green (gatewayProbe 14, gatewayResilienceEnv 23,
|
|
43
|
+
packageVersion 3); full `yarn test` green; live re-probe after the fix reports the alias
|
|
44
|
+
model, a healthy non-streaming route, and the still-present buffering signature.
|
|
45
|
+
- **Orphan-leak class closed for the remaining hooks (2.4.1 follow-up)**: the 2.4.1 watchdog
|
|
46
|
+
shipped in exactly one hook (`skill-router.sh`); the other 18 node-spawning hooks could still
|
|
47
|
+
orphan a node grandchild to launchd when their work hung (wedged import, stalled mount — this
|
|
48
|
+
repo lives on an external volume). Every remaining hook now arms the same wall-clock
|
|
49
|
+
self-deadline: inline heredoc/`node -e` blocks carry
|
|
50
|
+
`setTimeout(() => process.exit(0), HOOK_DEADLINE_MS).unref()` directly, wrapper hooks pass
|
|
51
|
+
`UKIT_HOOK_DEADLINE_MS` (default 3000 ms) and their runtime scripts (`execution-ledger`,
|
|
52
|
+
`output-compression`, `reinject-context`, `post-edit-verify`, `pre-edit-backup`,
|
|
53
|
+
`stale-spec-check`) self-exit only when that env var is present, so CLI usage is never
|
|
54
|
+
self-killed. Lock-mutation blocks with a 5000 ms bounded wait (auto-allow-bash,
|
|
55
|
+
auto-prune-bash, reset-compact-pressure) use an 8000 ms deadline so the watchdog can never
|
|
56
|
+
fire while the mutation is legally waiting for the lock.
|
|
57
|
+
`tests/consistency/hookWatchdogCoverage.test.js` enforces the coverage for every future hook.
|
|
58
|
+
- **Bounded stdin transport for the hook chain (C19 wave 1, H01)**: hook payloads no longer
|
|
59
|
+
materialize unbounded in a shell variable or the exec environment — the exec-env path that
|
|
60
|
+
made large PreToolUse payloads fail `exec` outright (E2BIG) on macOS. New
|
|
61
|
+
`templates/.claude/ukit/runtime/hook-input.sh` (`ukit_stage_hook_input` via `mktemp` +
|
|
62
|
+
byte-capped `head`, drained producer, overflow policies `truncate`/`temp-file`/`refuse`)
|
|
63
|
+
and `hook-input.mjs` (streaming reader + CLI) stage every payload to a bounded temp file;
|
|
64
|
+
all 16 node-spawning wrapper hooks now pass `INPUT_FILE`, never the payload, with a
|
|
65
|
+
bounded inline fallback when the helper is absent. Sensitive-data-guard overflow is
|
|
66
|
+
fail-closed (`refuse`, silent exit 2); 32 MiB receipts verified < 10 s.
|
|
67
|
+
|
|
68
|
+
- Machine-envelope lane in the sensitive-data gate: harness-injected notifications (task-notification / system-reminder / cross-session-message tags) now deliver with a redacted labels/counts advisory instead of being blocked when they quote secret-shaped test fixtures — ends the silent "Waiting for N background agents" stall. User-typed prompts, file/grep/bash channels, allowlist and overflow-refuse posture unchanged (fail-closed, default-ON, never echoes values).
|
|
69
|
+
|
|
5
70
|
## 2.4.1 - 2026-09-14
|
|
6
71
|
|
|
7
72
|
Fix: the skill-router hook could hang the session and leak an orphaned process. `skill-router.sh`
|
|
@@ -1059,7 +1059,7 @@ items:
|
|
|
1059
1059
|
sourceTemplate: .claude/hooks/record-execution.sh
|
|
1060
1060
|
targetPath: .claude/hooks/record-execution.sh
|
|
1061
1061
|
requires:
|
|
1062
|
-
- ukit-runtime-
|
|
1062
|
+
- ukit-runtime-scripts
|
|
1063
1063
|
mergeStrategy: overwrite_with_backup
|
|
1064
1064
|
variables: []
|
|
1065
1065
|
enabledByDefault: true
|
|
@@ -1071,7 +1071,7 @@ items:
|
|
|
1071
1071
|
sourceTemplate: .claude/hooks/completion-gate.sh
|
|
1072
1072
|
targetPath: .claude/hooks/completion-gate.sh
|
|
1073
1073
|
requires:
|
|
1074
|
-
- ukit-runtime-
|
|
1074
|
+
- ukit-runtime-scripts
|
|
1075
1075
|
mergeStrategy: overwrite_with_backup
|
|
1076
1076
|
variables: []
|
|
1077
1077
|
enabledByDefault: true
|
|
@@ -1139,7 +1139,7 @@ items:
|
|
|
1139
1139
|
targetPath: .claude/hooks/skill-router.sh
|
|
1140
1140
|
requires:
|
|
1141
1141
|
- ukit-index-route-catalog-script
|
|
1142
|
-
- ukit-runtime-
|
|
1142
|
+
- ukit-runtime-scripts
|
|
1143
1143
|
mergeStrategy: overwrite_with_backup
|
|
1144
1144
|
variables: []
|
|
1145
1145
|
enabledByDefault: true
|
|
@@ -1164,7 +1164,7 @@ items:
|
|
|
1164
1164
|
sourceTemplate: .claude/hooks/context-hardcap-gate.sh
|
|
1165
1165
|
targetPath: .claude/hooks/context-hardcap-gate.sh
|
|
1166
1166
|
requires:
|
|
1167
|
-
- ukit-runtime-
|
|
1167
|
+
- ukit-runtime-scripts
|
|
1168
1168
|
mergeStrategy: overwrite_with_backup
|
|
1169
1169
|
variables: []
|
|
1170
1170
|
enabledByDefault: true
|
|
@@ -1204,10 +1204,16 @@ items:
|
|
|
1204
1204
|
packs:
|
|
1205
1205
|
- core
|
|
1206
1206
|
|
|
1207
|
-
|
|
1207
|
+
# One directory item ships the whole runtime dir. autoDiscoverTemplates:false silently
|
|
1208
|
+
# skips any template file without an explicit item, which is exactly how later-wave
|
|
1209
|
+
# runtime modules (async-lock.mjs, hook-process.mjs, transcript-tail.mjs, hook-input.*)
|
|
1210
|
+
# never reached installed projects and broke execution-ledger.mjs imports at hook time.
|
|
1211
|
+
# A directory scan keeps every new runtime module shipping without a manifest edit; the
|
|
1212
|
+
# walker preserves per-file modes (hook-input.sh keeps its exec bit).
|
|
1213
|
+
- id: ukit-runtime-scripts
|
|
1208
1214
|
type: config
|
|
1209
|
-
sourceTemplate: .claude/ukit/runtime
|
|
1210
|
-
targetPath: .claude/ukit/runtime
|
|
1215
|
+
sourceTemplate: .claude/ukit/runtime
|
|
1216
|
+
targetPath: .claude/ukit/runtime
|
|
1211
1217
|
requires: []
|
|
1212
1218
|
mergeStrategy: overwrite_with_backup
|
|
1213
1219
|
variables: []
|
|
@@ -1231,7 +1237,7 @@ items:
|
|
|
1231
1237
|
sourceTemplate: .claude/hooks/compress-output.sh
|
|
1232
1238
|
targetPath: .claude/hooks/compress-output.sh
|
|
1233
1239
|
requires:
|
|
1234
|
-
- ukit-runtime-
|
|
1240
|
+
- ukit-runtime-scripts
|
|
1235
1241
|
mergeStrategy: overwrite_with_backup
|
|
1236
1242
|
variables: []
|
|
1237
1243
|
enabledByDefault: true
|
|
@@ -1273,58 +1279,12 @@ items:
|
|
|
1273
1279
|
packs:
|
|
1274
1280
|
- core
|
|
1275
1281
|
|
|
1276
|
-
- id: ukit-runtime-text-profile-script
|
|
1277
|
-
type: config
|
|
1278
|
-
sourceTemplate: .claude/ukit/runtime/text-profile.mjs
|
|
1279
|
-
targetPath: .claude/ukit/runtime/text-profile.mjs
|
|
1280
|
-
requires: []
|
|
1281
|
-
mergeStrategy: overwrite_with_backup
|
|
1282
|
-
variables: []
|
|
1283
|
-
enabledByDefault: true
|
|
1284
|
-
packs:
|
|
1285
|
-
- core
|
|
1286
|
-
|
|
1287
|
-
- id: ukit-runtime-execution-ledger-script
|
|
1288
|
-
type: config
|
|
1289
|
-
sourceTemplate: .claude/ukit/runtime/execution-ledger.mjs
|
|
1290
|
-
targetPath: .claude/ukit/runtime/execution-ledger.mjs
|
|
1291
|
-
requires: []
|
|
1292
|
-
mergeStrategy: overwrite_with_backup
|
|
1293
|
-
variables: []
|
|
1294
|
-
enabledByDefault: true
|
|
1295
|
-
packs:
|
|
1296
|
-
- core
|
|
1297
|
-
|
|
1298
|
-
- id: ukit-runtime-hook-chain-runner-script
|
|
1299
|
-
type: config
|
|
1300
|
-
sourceTemplate: .claude/ukit/runtime/hook-chain-runner.mjs
|
|
1301
|
-
targetPath: .claude/ukit/runtime/hook-chain-runner.mjs
|
|
1302
|
-
requires: []
|
|
1303
|
-
mergeStrategy: overwrite_with_backup
|
|
1304
|
-
variables: []
|
|
1305
|
-
enabledByDefault: true
|
|
1306
|
-
packs:
|
|
1307
|
-
- core
|
|
1308
|
-
|
|
1309
|
-
- id: ukit-runtime-safe-patch-core-script
|
|
1310
|
-
type: config
|
|
1311
|
-
sourceTemplate: .claude/ukit/runtime/safe-patch-core.mjs
|
|
1312
|
-
targetPath: .claude/ukit/runtime/safe-patch-core.mjs
|
|
1313
|
-
requires:
|
|
1314
|
-
- ukit-runtime-text-profile-script
|
|
1315
|
-
mergeStrategy: overwrite_with_backup
|
|
1316
|
-
variables: []
|
|
1317
|
-
enabledByDefault: true
|
|
1318
|
-
packs:
|
|
1319
|
-
- core
|
|
1320
|
-
|
|
1321
1282
|
- id: ukit-index-anchor-search-script
|
|
1322
1283
|
type: config
|
|
1323
1284
|
sourceTemplate: .claude/ukit/index/anchor-search.mjs
|
|
1324
1285
|
targetPath: .claude/ukit/index/anchor-search.mjs
|
|
1325
1286
|
requires:
|
|
1326
|
-
- ukit-runtime-
|
|
1327
|
-
- ukit-runtime-safe-patch-core-script
|
|
1287
|
+
- ukit-runtime-scripts
|
|
1328
1288
|
mergeStrategy: overwrite_with_backup
|
|
1329
1289
|
variables: []
|
|
1330
1290
|
enabledByDefault: true
|
|
@@ -1348,8 +1308,7 @@ items:
|
|
|
1348
1308
|
sourceTemplate: .claude/ukit/index/provision-worktree.mjs
|
|
1349
1309
|
targetPath: .claude/ukit/index/provision-worktree.mjs
|
|
1350
1310
|
requires:
|
|
1351
|
-
- ukit-runtime-
|
|
1352
|
-
- ukit-runtime-safe-patch-core-script
|
|
1311
|
+
- ukit-runtime-scripts
|
|
1353
1312
|
mergeStrategy: overwrite_with_backup
|
|
1354
1313
|
variables: []
|
|
1355
1314
|
enabledByDefault: true
|
|
@@ -1361,8 +1320,7 @@ items:
|
|
|
1361
1320
|
sourceTemplate: .claude/ukit/index/stale-spec-check.mjs
|
|
1362
1321
|
targetPath: .claude/ukit/index/stale-spec-check.mjs
|
|
1363
1322
|
requires:
|
|
1364
|
-
- ukit-runtime-
|
|
1365
|
-
- ukit-runtime-safe-patch-core-script
|
|
1323
|
+
- ukit-runtime-scripts
|
|
1366
1324
|
mergeStrategy: overwrite_with_backup
|
|
1367
1325
|
variables: []
|
|
1368
1326
|
enabledByDefault: true
|
|
@@ -1374,8 +1332,7 @@ items:
|
|
|
1374
1332
|
sourceTemplate: .claude/ukit/index/pre-edit-backup.mjs
|
|
1375
1333
|
targetPath: .claude/ukit/index/pre-edit-backup.mjs
|
|
1376
1334
|
requires:
|
|
1377
|
-
- ukit-runtime-
|
|
1378
|
-
- ukit-runtime-safe-patch-core-script
|
|
1335
|
+
- ukit-runtime-scripts
|
|
1379
1336
|
mergeStrategy: overwrite_with_backup
|
|
1380
1337
|
variables: []
|
|
1381
1338
|
enabledByDefault: true
|
|
@@ -1387,8 +1344,7 @@ items:
|
|
|
1387
1344
|
sourceTemplate: .claude/ukit/index/post-edit-verify.mjs
|
|
1388
1345
|
targetPath: .claude/ukit/index/post-edit-verify.mjs
|
|
1389
1346
|
requires:
|
|
1390
|
-
- ukit-runtime-
|
|
1391
|
-
- ukit-runtime-safe-patch-core-script
|
|
1347
|
+
- ukit-runtime-scripts
|
|
1392
1348
|
mergeStrategy: overwrite_with_backup
|
|
1393
1349
|
variables: []
|
|
1394
1350
|
enabledByDefault: true
|
|
@@ -1549,54 +1505,6 @@ items:
|
|
|
1549
1505
|
packs:
|
|
1550
1506
|
- core
|
|
1551
1507
|
|
|
1552
|
-
- id: ukit-runtime-token-utils-script
|
|
1553
|
-
type: config
|
|
1554
|
-
sourceTemplate: .claude/ukit/runtime/token-utils.mjs
|
|
1555
|
-
targetPath: .claude/ukit/runtime/token-utils.mjs
|
|
1556
|
-
mergeStrategy: overwrite_with_backup
|
|
1557
|
-
variables: []
|
|
1558
|
-
enabledByDefault: true
|
|
1559
|
-
packs:
|
|
1560
|
-
- core
|
|
1561
|
-
|
|
1562
|
-
- id: ukit-runtime-compact-threshold-script
|
|
1563
|
-
type: config
|
|
1564
|
-
sourceTemplate: .claude/ukit/runtime/compact-threshold.mjs
|
|
1565
|
-
targetPath: .claude/ukit/runtime/compact-threshold.mjs
|
|
1566
|
-
requires:
|
|
1567
|
-
- ukit-runtime-token-utils-script
|
|
1568
|
-
mergeStrategy: overwrite_with_backup
|
|
1569
|
-
variables: []
|
|
1570
|
-
enabledByDefault: true
|
|
1571
|
-
packs:
|
|
1572
|
-
- core
|
|
1573
|
-
|
|
1574
|
-
- id: ukit-runtime-reinject-context-script
|
|
1575
|
-
type: config
|
|
1576
|
-
sourceTemplate: .claude/ukit/runtime/reinject-context.mjs
|
|
1577
|
-
targetPath: .claude/ukit/runtime/reinject-context.mjs
|
|
1578
|
-
requires:
|
|
1579
|
-
- ukit-runtime-token-utils-script
|
|
1580
|
-
- ukit-runtime-compact-threshold-script
|
|
1581
|
-
mergeStrategy: overwrite_with_backup
|
|
1582
|
-
variables: []
|
|
1583
|
-
enabledByDefault: true
|
|
1584
|
-
packs:
|
|
1585
|
-
- core
|
|
1586
|
-
|
|
1587
|
-
- id: ukit-runtime-output-compression-script
|
|
1588
|
-
type: config
|
|
1589
|
-
sourceTemplate: .claude/ukit/runtime/output-compression.mjs
|
|
1590
|
-
targetPath: .claude/ukit/runtime/output-compression.mjs
|
|
1591
|
-
requires:
|
|
1592
|
-
- ukit-runtime-token-utils-script
|
|
1593
|
-
- ukit-runtime-compact-threshold-script
|
|
1594
|
-
mergeStrategy: overwrite_with_backup
|
|
1595
|
-
variables: []
|
|
1596
|
-
enabledByDefault: true
|
|
1597
|
-
packs:
|
|
1598
|
-
- core
|
|
1599
|
-
|
|
1600
1508
|
- id: ukit-index-reset-auto-permissions-script
|
|
1601
1509
|
type: config
|
|
1602
1510
|
sourceTemplate: .claude/ukit/index/reset-auto-permissions.mjs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ngockhoale/ukit",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.3",
|
|
4
4
|
"description": "Install/update an index-first AI workspace for Claude Code, OpenAI Codex, OpenCode, and omp (Oh My Pi).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"test:release-core": "vitest run --exclude tests/integration/packageArtifact.test.js",
|
|
60
60
|
"release:verify": "node ./scripts/release/verify-release.mjs",
|
|
61
61
|
"test": "vitest run",
|
|
62
|
+
"test:liveness": "vitest run tests/liveness/",
|
|
62
63
|
"test:watch": "vitest"
|
|
63
64
|
},
|
|
64
65
|
"engines": {
|
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
buildCodeIndex,
|
|
7
|
-
|
|
7
|
+
inspectIndexStaleness,
|
|
8
8
|
DEFAULT_INDEX_CACHE_MAX_AGE_MS,
|
|
9
9
|
} from '../../src/index/buildIndex.js';
|
|
10
10
|
|
|
@@ -19,34 +19,64 @@ const changedFiles = changedArg
|
|
|
19
19
|
|
|
20
20
|
const force = readBooleanFlag(args, '--force');
|
|
21
21
|
const lastRefreshMs = await getLastRefreshTime(rootDir);
|
|
22
|
-
const stale = force
|
|
23
|
-
? true
|
|
24
|
-
: await isIndexStale({ rootDir, maxAgeMs: DEFAULT_INDEX_CACHE_MAX_AGE_MS });
|
|
25
22
|
|
|
26
|
-
if (!
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
console.log(`changed-files-hint: ${changedFiles.join(', ')}`);
|
|
33
|
-
}
|
|
34
|
-
console.log(`root: ${rootDir}`);
|
|
35
|
-
} else {
|
|
36
|
-
const summary = await buildCodeIndex({ rootDir });
|
|
23
|
+
if (changedFiles.length > 0 && !force) {
|
|
24
|
+
// Hints carry the change set, so no staleness enumeration is needed:
|
|
25
|
+
// buildCodeIndex merges the hinted paths into the existing artifacts and
|
|
26
|
+
// falls back to the bounded full discovery path on its own when the hints
|
|
27
|
+
// are unsafe or the previous artifacts cannot be merged (TASK-023).
|
|
28
|
+
const summary = await buildCodeIndex({ rootDir, changedFiles });
|
|
37
29
|
|
|
38
30
|
console.log('[index:refresh] completed');
|
|
31
|
+
console.log(`mode: ${summary.mode}`);
|
|
39
32
|
if (lastRefreshMs !== null) {
|
|
40
33
|
console.log(`lastRefreshBefore: ${new Date(lastRefreshMs).toISOString()}`);
|
|
41
34
|
}
|
|
42
|
-
|
|
43
|
-
console.log(`changed-files-hint: ${changedFiles.join(', ')}`);
|
|
44
|
-
}
|
|
35
|
+
console.log(`changed-files-hint: ${changedFiles.join(', ')}`);
|
|
45
36
|
console.log(`root: ${rootDir}`);
|
|
46
37
|
console.log(`files: ${summary.fileCount}`);
|
|
47
38
|
console.log(`parsed: ${summary.parsedCodeFileCount}`);
|
|
48
39
|
console.log(`reused: ${summary.reusedCodeFileCount}`);
|
|
40
|
+
if (summary.removedFiles.length > 0) {
|
|
41
|
+
console.log(`removed: ${summary.removedFiles.length}`);
|
|
42
|
+
}
|
|
49
43
|
console.log(`indexDir: ${summary.indexDir}`);
|
|
44
|
+
} else {
|
|
45
|
+
const staleness = force
|
|
46
|
+
? null
|
|
47
|
+
: await inspectIndexStaleness({ rootDir, maxAgeMs: DEFAULT_INDEX_CACHE_MAX_AGE_MS });
|
|
48
|
+
const stale = force || staleness.stale;
|
|
49
|
+
|
|
50
|
+
if (!stale) {
|
|
51
|
+
console.log('[index:refresh] skipped (cache fresh)');
|
|
52
|
+
if (lastRefreshMs !== null) {
|
|
53
|
+
console.log(`lastRefresh: ${new Date(lastRefreshMs).toISOString()}`);
|
|
54
|
+
}
|
|
55
|
+
if (changedFiles.length > 0) {
|
|
56
|
+
console.log(`changed-files-hint: ${changedFiles.join(', ')}`);
|
|
57
|
+
}
|
|
58
|
+
console.log(`root: ${rootDir}`);
|
|
59
|
+
} else {
|
|
60
|
+
// Reuse the staleness check's discovery snapshot: one enumeration per refresh.
|
|
61
|
+
const summary = await buildCodeIndex({
|
|
62
|
+
rootDir,
|
|
63
|
+
discoverySnapshot: staleness ? staleness.snapshot : null,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
console.log('[index:refresh] completed');
|
|
67
|
+
console.log(`mode: ${summary.mode}`);
|
|
68
|
+
if (lastRefreshMs !== null) {
|
|
69
|
+
console.log(`lastRefreshBefore: ${new Date(lastRefreshMs).toISOString()}`);
|
|
70
|
+
}
|
|
71
|
+
if (changedFiles.length > 0) {
|
|
72
|
+
console.log(`changed-files-hint: ${changedFiles.join(', ')}`);
|
|
73
|
+
}
|
|
74
|
+
console.log(`root: ${rootDir}`);
|
|
75
|
+
console.log(`files: ${summary.fileCount}`);
|
|
76
|
+
console.log(`parsed: ${summary.parsedCodeFileCount}`);
|
|
77
|
+
console.log(`reused: ${summary.reusedCodeFileCount}`);
|
|
78
|
+
console.log(`indexDir: ${summary.indexDir}`);
|
|
79
|
+
}
|
|
50
80
|
}
|
|
51
81
|
|
|
52
82
|
function readFlagValue(argv, flag) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
3
4
|
import { pathExists, readJsonIfExists } from '../../core/fileOps.js';
|
|
4
5
|
import { buildPathConfig } from '../../core/paths.js';
|
|
5
6
|
import { buildRuntimePaths } from '../../core/runtimePaths.js';
|
|
@@ -10,8 +11,13 @@ import { detectProviders } from '../../context/detectProviders.js';
|
|
|
10
11
|
import { profileSkills } from '../../core/skillProfile.js';
|
|
11
12
|
import {
|
|
12
13
|
resolveGatewayBaseUrl,
|
|
14
|
+
resolveGatewayApiKey,
|
|
13
15
|
probeGateway,
|
|
14
16
|
} from '../../core/gatewayProbe.js';
|
|
17
|
+
import {
|
|
18
|
+
GATEWAY_RESILIENCE_ENV_DEFAULTS,
|
|
19
|
+
scanProfileResilienceEnv,
|
|
20
|
+
} from '../../core/gatewayResilienceEnv.js';
|
|
15
21
|
|
|
16
22
|
export const DOCTOR_HELP_FLAGS = new Set(['--help', '-h']);
|
|
17
23
|
const KNOWN_FLAGS = new Set([...DOCTOR_HELP_FLAGS, '--skills', '--gateway']);
|
|
@@ -28,7 +34,7 @@ export function printDoctorHelp() {
|
|
|
28
34
|
console.log(' --gateway Live gateway probe (streaming + non-streaming); advisory only');
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
export async function runDoctor({ packageRoot, projectRoot, argv = [] }) {
|
|
37
|
+
export async function runDoctor({ packageRoot, projectRoot, argv = [], homeDir = os.homedir() }) {
|
|
32
38
|
const unknownFlags = argv.filter((flag) => !KNOWN_FLAGS.has(flag));
|
|
33
39
|
if (unknownFlags.length > 0) {
|
|
34
40
|
throw new Error(`Unknown option: ${unknownFlags[0]}. Supported: ${SUPPORTED_FLAGS_LIST}`);
|
|
@@ -158,6 +164,40 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [] }) {
|
|
|
158
164
|
if (argv.includes('--gateway')) {
|
|
159
165
|
console.log('');
|
|
160
166
|
console.log('[UKit] Gateway probe (advisory — does not affect exit code):');
|
|
167
|
+
|
|
168
|
+
// Per-profile resilience scan. A session launched with
|
|
169
|
+
// `--settings ~/.claude/profile/<name>.json` bypasses ~/.claude/settings.json, so the
|
|
170
|
+
// managed keys can be missing exactly where they matter while the main settings file
|
|
171
|
+
// looks healthy. Advisory only: this never enters `checks` and never touches exitCode.
|
|
172
|
+
console.log(
|
|
173
|
+
`[UKit] Profile resilience scan (advisory) — managed keys: ${Object.keys(GATEWAY_RESILIENCE_ENV_DEFAULTS).join(', ')}`,
|
|
174
|
+
);
|
|
175
|
+
let profileScan = null;
|
|
176
|
+
try {
|
|
177
|
+
profileScan = await scanProfileResilienceEnv({ homeDir });
|
|
178
|
+
} catch {
|
|
179
|
+
profileScan = null;
|
|
180
|
+
}
|
|
181
|
+
if (!profileScan || profileScan.scanned.length === 0) {
|
|
182
|
+
console.log('[UKit] - No profile files found (nothing to check).');
|
|
183
|
+
} else {
|
|
184
|
+
for (const entry of profileScan.scanned) {
|
|
185
|
+
if (entry.missing.length === 0) {
|
|
186
|
+
console.log(`[UKit] ✓ ${entry.file}`);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
console.log(
|
|
190
|
+
`[UKit] ✗ ${entry.file} — ${entry.validJson ? `missing ${entry.missing.join(', ')}` : 'not valid JSON (assuming every managed key missing)'}`,
|
|
191
|
+
);
|
|
192
|
+
console.log(
|
|
193
|
+
`[UKit] remedy: add ${entry.missing.join(', ')} to the profile's "env" block; see docs/GATEWAY.md`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
if (profileScan.compliant) {
|
|
197
|
+
console.log('[UKit] All scanned profile files carry the managed resilience keys.');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
161
201
|
let resolved;
|
|
162
202
|
try {
|
|
163
203
|
resolved = await resolveGatewayBaseUrl({ projectRoot });
|
|
@@ -168,9 +208,25 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [] }) {
|
|
|
168
208
|
console.log('[UKit] - Gateway probe skipped — no custom gateway configured (ANTHROPIC_BASE_URL not set in env, project .claude/settings.json, or ~/.claude/settings.json).');
|
|
169
209
|
} else {
|
|
170
210
|
console.log(`[UKit] baseUrl: ${resolved.baseUrl} (source: ${resolved.source})`);
|
|
211
|
+
// Plain terminals do not export ANTHROPIC_* (the key lives in settings.json env and is
|
|
212
|
+
// only injected into Claude Code sessions), so resolve the key through the same 3-tier
|
|
213
|
+
// order as the baseUrl — otherwise the probe authenticates keyless and misreports.
|
|
214
|
+
let keyInfo = null;
|
|
215
|
+
try {
|
|
216
|
+
keyInfo = await resolveGatewayApiKey({ projectRoot });
|
|
217
|
+
} catch {
|
|
218
|
+
keyInfo = null;
|
|
219
|
+
}
|
|
220
|
+
if (!keyInfo?.value) {
|
|
221
|
+
console.log('[UKit] - No API key found (env, project settings, home settings) — probe runs unauthenticated and may 401/404.');
|
|
222
|
+
}
|
|
171
223
|
let result;
|
|
172
224
|
try {
|
|
173
|
-
result = await probeGateway({
|
|
225
|
+
result = await probeGateway({
|
|
226
|
+
baseUrl: resolved.baseUrl,
|
|
227
|
+
apiKey: keyInfo?.value ?? null,
|
|
228
|
+
apiKeySource: keyInfo?.scheme ?? null,
|
|
229
|
+
});
|
|
174
230
|
} catch (error) {
|
|
175
231
|
result = null;
|
|
176
232
|
console.log(`[UKit] ✗ Gateway probe threw: ${error?.message ?? String(error)}`);
|
|
@@ -178,6 +234,7 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [] }) {
|
|
|
178
234
|
if (result) {
|
|
179
235
|
const s = result.streaming;
|
|
180
236
|
const n = result.nonStreaming;
|
|
237
|
+
console.log(`[UKit] probed model: ${result.model}`);
|
|
181
238
|
const streamingLabel = s.ok
|
|
182
239
|
? `streaming OK (events=${s.eventsReceived}, chunks=${s.chunks})`
|
|
183
240
|
: `streaming FAIL (events=${s.eventsReceived}, chunks=${s.chunks}, buffered=${s.buffered}${s.error ? `, error=${s.error}` : ''})`;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { readJsonIfExists, writeJson, withFileLock } from '../fileOps.js';
|
|
2
|
+
// The installed runtime is the single implementation of capacity negotiation; this source
|
|
3
|
+
// twin consumes it rather than re-deriving the precedence rules. Both files ship together.
|
|
4
|
+
import {
|
|
5
|
+
readContextCapacityRecord,
|
|
6
|
+
SOFT_TO_CAP_RATIO,
|
|
7
|
+
} from '../../../templates/.claude/ukit/runtime/compact-threshold.mjs';
|
|
8
|
+
import { resolveContextCapTokens } from '../../../templates/.claude/ukit/runtime/context-capacity.mjs';
|
|
2
9
|
import { buildRuntimePaths } from '../runtimePaths.js';
|
|
3
10
|
import { buildCompactMachineKey, compressLine, estimateTokenCount } from '../token/index.js';
|
|
4
11
|
import { compactContextBlock } from './index.js';
|
|
@@ -436,22 +443,45 @@ function computeEstimatedTotalTokens({
|
|
|
436
443
|
return baselineTokens + estimatedContextTokens + windowTokens + sessionExcess;
|
|
437
444
|
}
|
|
438
445
|
|
|
446
|
+
// The record the advisory guard published for this project, or null when nothing has been
|
|
447
|
+
// negotiated yet (fresh install, non-project caller, tests).
|
|
448
|
+
function readNegotiatedCapacity(config = {}) {
|
|
449
|
+
const record = readContextCapacityRecord(config?.projectRoot);
|
|
450
|
+
if (!record) return null;
|
|
451
|
+
return resolveContextCapTokens({
|
|
452
|
+
env: process.env,
|
|
453
|
+
config,
|
|
454
|
+
modelMetadata: { model: record.model },
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
439
458
|
export function buildCompactThresholds(config = {}) {
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
459
|
+
const shippedHardCap = positiveInteger(
|
|
460
|
+
config?.compact?.hardCapTokens,
|
|
461
|
+
loadShippedCompactBudget().hardCapTokens,
|
|
443
462
|
);
|
|
463
|
+
const explicitSoftThreshold = finiteNumber(config?.compact?.tokenThreshold, 0);
|
|
464
|
+
// Negotiated capacity (H22): the guard publishes what it derived from the live route and
|
|
465
|
+
// this shared path consumes it; without a record the shipped tuning applies unchanged.
|
|
466
|
+
const negotiated = readNegotiatedCapacity(config);
|
|
467
|
+
const hardCapTokens = negotiated
|
|
468
|
+
? Math.max(1, Math.min(shippedHardCap, negotiated.capTokens))
|
|
469
|
+
: shippedHardCap;
|
|
470
|
+
// An explicit operator tokenThreshold is honored as-is; otherwise the advisory phase is
|
|
471
|
+
// derived from the negotiating cap so it can never sit above the cap it precedes.
|
|
472
|
+
const softThreshold = explicitSoftThreshold > 0
|
|
473
|
+
? Math.max(1, explicitSoftThreshold)
|
|
474
|
+
: Math.max(1, Math.round(hardCapTokens * SOFT_TO_CAP_RATIO));
|
|
444
475
|
const hardThreshold = Math.max(softThreshold + 1, Math.round(softThreshold * 1.6));
|
|
445
476
|
const baselineTokens = Math.max(120, Math.min(18_000, Math.round(softThreshold * 0.18)));
|
|
446
|
-
// Keep the source mirror's config contract aligned with the installed runtime: malformed
|
|
447
|
-
// caps fall back to a safe ceiling rather than turning every mutation into an over-cap call.
|
|
448
|
-
const hardCapTokens = positiveInteger(config?.compact?.hardCapTokens, loadShippedCompactBudget().hardCapTokens);
|
|
449
477
|
|
|
450
478
|
return {
|
|
451
479
|
softThreshold,
|
|
452
480
|
hardThreshold,
|
|
453
481
|
baselineTokens,
|
|
454
482
|
hardCapTokens,
|
|
483
|
+
capacitySource: negotiated?.capacity?.source ?? 'shipped',
|
|
484
|
+
capacityTokens: negotiated?.capacity?.tokens ?? null,
|
|
455
485
|
};
|
|
456
486
|
}
|
|
457
487
|
|