@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
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,63 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to UKit are documented here.
|
|
4
4
|
|
|
5
|
+
## 2.5.0 - 2026-09-18
|
|
6
|
+
|
|
7
|
+
C20 feature release (14 handoff tasks + 3-lane review): project-owner instructions via a
|
|
8
|
+
seeded root `PROJECT_IMPORTANT.md`, delivered once per context epoch on every supported
|
|
9
|
+
host.
|
|
10
|
+
|
|
11
|
+
- **`PROJECT_IMPORTANT.md` seed-once ownership**: `ukit install` creates the file only
|
|
12
|
+
when missing (`mergeStrategy: skip`, exclusive `'wx'` create, `lstat`-based existence);
|
|
13
|
+
it is never rewritten, merged, chmodded, tracked in `install.json.files`, or deleted —
|
|
14
|
+
reinstalls preserve it byte-for-byte, and delete + reinstall re-seeds.
|
|
15
|
+
- **Once-per-epoch injection**: Claude Code `SessionStart` (including
|
|
16
|
+
`source:'compact'` post-compact) and omp `session_start`/`session_compact` (steer only
|
|
17
|
+
— exactly one copy per epoch) render the file into advisory context; Codex and OpenCode
|
|
18
|
+
get a static read-the-file pointer in `AGENTS.md` (pointer, not runtime delivery).
|
|
19
|
+
- **Bounded deterministic render** (`src/core/projectImportant.js` + installed-runtime
|
|
20
|
+
mirror): 6,000-Unicode-code-point limit with an explicit oversized warning, strict
|
|
21
|
+
UTF-8 decode (malformed input is never injected), BOM excluded from count and body,
|
|
22
|
+
`O_NOFOLLOW`/`O_NONBLOCK` open so symlinks/FIFOs/devices are never followed or hung on,
|
|
23
|
+
and a byte-identical envelope (no volatile metadata) for identical input.
|
|
24
|
+
- **Fail-closed secret gate** (`src/core/sensitiveValueScanner.js`): the exact body to
|
|
25
|
+
be injected is scanned for high-confidence secret shapes; blocks report labels only —
|
|
26
|
+
never a value, excerpt, or hash.
|
|
27
|
+
- **Uninstall backup**: `ukit uninstall` backs the file up to a sibling
|
|
28
|
+
`PROJECT_IMPORTANT.md.ukit-backup[.n]` at project root (`COPYFILE_EXCL`, bounded suffix
|
|
29
|
+
walk, byte-identical reuse, dry-run plans only) and aborts before any managed deletion
|
|
30
|
+
if the backup cannot be made.
|
|
31
|
+
- **Honest-status surface**: `ukit status`/`ukit doctor` report per-check remediation
|
|
32
|
+
classes (`owner-action`/`install-repairable`/`advisory-host-limit`) and label local
|
|
33
|
+
prompt-cache stats as `Local p-cache` — provider telemetry is reported `unknown`, never
|
|
34
|
+
zero.
|
|
35
|
+
|
|
36
|
+
## 2.4.3 - 2026-09-18
|
|
37
|
+
|
|
38
|
+
C19 liveness-hardening release (23 handoff tasks): a full sweep of the shipped hook and
|
|
39
|
+
runtime surface against the freeze/stall/orphan classes behind the recurring "session
|
|
40
|
+
stands still mid-turn" reports, plus the regression instruments that keep them closed.
|
|
41
|
+
|
|
42
|
+
- **Bounded hook stdin staging (H01)**: `hook-input.sh`/`hook-input.mjs` runtime helpers
|
|
43
|
+
plus bounded-staging fallback blocks in all 16 hook wrapper templates — a producer that
|
|
44
|
+
never closes the pipe can no longer hold a hook open (SIGKILL at the staging bound).
|
|
45
|
+
- **Deadline-aware hot-hook filesystem work**: hook runtime modules self-arm on
|
|
46
|
+
`UKIT_HOOK_DEADLINE_MS` so a wedged read or lock wait exits inside the hook budget
|
|
47
|
+
instead of orphaning a node child.
|
|
48
|
+
- **Process-tree + lock containment**: bounded process-tree kill when a descendant escapes
|
|
49
|
+
its group, atomic quarantine of validated stale lock generations, owner-stamp failure
|
|
50
|
+
no longer runs the callback under an ownerless lock, and deep-freeze + containment
|
|
51
|
+
fail-closed for discovery snapshot reuse.
|
|
52
|
+
- **Liveness suite + classifier**: `src/diagnostics/classifyHang.js`, `tests/liveness/`,
|
|
53
|
+
and the `test:liveness` script — evidence-only hang classification (an external signal
|
|
54
|
+
is not a TERM→KILL leak) with regression scenarios pinned in tests.
|
|
55
|
+
- **Execution-ledger hardening**: bounded route-scoped ledger journal, overflow guard on
|
|
56
|
+
drain commits, journal-local locking, and the Stop coordinator
|
|
57
|
+
(`stop-coordinator.mjs`) with its extracted watchdog path.
|
|
58
|
+
- **Runtime shipping fix**: the install pipeline ships every packaged
|
|
59
|
+
`.claude/ukit/runtime` module via the `ukit-runtime-scripts` directory item (TASK-035),
|
|
60
|
+
closing the class where a new runtime module shipped to npm but never installed.
|
|
61
|
+
|
|
5
62
|
## 2.4.2 - 2026-09-16
|
|
6
63
|
|
|
7
64
|
Fix: the gateway resilience posture disabled the only recovery path a buffering gateway
|
package/README.md
CHANGED
|
@@ -69,6 +69,26 @@ If maintainers roll out a newer CLI build, the in-project workflow still stays t
|
|
|
69
69
|
- `.claude/ukit/.ukit/` — installer manifests, metadata, backups
|
|
70
70
|
- `.ukit/` — hidden shared runtime storage for config, cache, and cross-agent memory
|
|
71
71
|
- `docs/` — PROJECT / MEMORY / AI_HANDOFF / WORKLOG baseline
|
|
72
|
+
- `PROJECT_IMPORTANT.md` — your project-owner instruction file (see below)
|
|
73
|
+
|
|
74
|
+
## `PROJECT_IMPORTANT.md` — project-owner instructions
|
|
75
|
+
|
|
76
|
+
`ukit install` seeds a root `PROJECT_IMPORTANT.md` **once**. After that the file is
|
|
77
|
+
yours: UKit never rewrites, merges, formats, chmods, tracks, or deletes it — edits
|
|
78
|
+
survive every `ukit install` rerun byte-for-byte, and deleting it + reinstalling
|
|
79
|
+
seeds a fresh copy. It is also excluded from `ukit uninstall` deletion; uninstall
|
|
80
|
+
backs it up to a sibling `PROJECT_IMPORTANT.md.ukit-backup[.n]` file at project
|
|
81
|
+
root (never overwritten — a new suffix is chosen, and dry-run creates nothing).
|
|
82
|
+
|
|
83
|
+
Put your non-negotiable project rules there. On each session start your AI tool
|
|
84
|
+
receives its contents as advisory project-owner context — Claude Code and omp via
|
|
85
|
+
their session hooks (and once more after a compaction, which starts a new context
|
|
86
|
+
epoch), Codex and OpenCode via a static pointer in `AGENTS.md` (read-the-file
|
|
87
|
+
instructions, not a runtime hook). Keep the file at or below **6,000 Unicode code
|
|
88
|
+
points** — anything past that is truncated with an explicit warning, and the fix is
|
|
89
|
+
to shorten the file, not rerun `ukit install`. Do not put credentials or secrets in
|
|
90
|
+
it: a fail-closed sensitive-value scan runs on the exact body before injection and
|
|
91
|
+
blocks anything that looks like a key or token.
|
|
72
92
|
|
|
73
93
|
## UKit v1.3.1 Runtime
|
|
74
94
|
|
|
@@ -19,6 +19,20 @@ items:
|
|
|
19
19
|
packs:
|
|
20
20
|
- core
|
|
21
21
|
|
|
22
|
+
# Owner-instruction seed. `mergeStrategy: skip` gives seed-once semantics: UKit writes it
|
|
23
|
+
# only when the path is truly absent (exclusive 'wx' create in applyPlan), never rewrites,
|
|
24
|
+
# merges, formats, chmods or tracks it in install.json afterwards.
|
|
25
|
+
- id: project-important
|
|
26
|
+
type: config
|
|
27
|
+
sourceTemplate: PROJECT_IMPORTANT.md
|
|
28
|
+
targetPath: PROJECT_IMPORTANT.md
|
|
29
|
+
requires: []
|
|
30
|
+
mergeStrategy: skip
|
|
31
|
+
variables: []
|
|
32
|
+
enabledByDefault: true
|
|
33
|
+
packs:
|
|
34
|
+
- core
|
|
35
|
+
|
|
22
36
|
- id: docs-memory
|
|
23
37
|
type: config
|
|
24
38
|
sourceTemplate: docs/MEMORY.md
|
|
@@ -987,6 +1001,7 @@ items:
|
|
|
987
1001
|
- hook-block-dangerous
|
|
988
1002
|
- hook-handoff-model-guard
|
|
989
1003
|
- hook-auto-prune-bash
|
|
1004
|
+
- hook-project-important
|
|
990
1005
|
# Env block is post-merged by applyGatewayResilienceEnv (TASK-011) to add managed
|
|
991
1006
|
# gateway-resilience defaults without clobbering user values, so the diff ignores
|
|
992
1007
|
# the env block — byte-equality on the rest of the file is enough to flag an update.
|
|
@@ -1059,7 +1074,7 @@ items:
|
|
|
1059
1074
|
sourceTemplate: .claude/hooks/record-execution.sh
|
|
1060
1075
|
targetPath: .claude/hooks/record-execution.sh
|
|
1061
1076
|
requires:
|
|
1062
|
-
- ukit-runtime-
|
|
1077
|
+
- ukit-runtime-scripts
|
|
1063
1078
|
mergeStrategy: overwrite_with_backup
|
|
1064
1079
|
variables: []
|
|
1065
1080
|
enabledByDefault: true
|
|
@@ -1071,7 +1086,7 @@ items:
|
|
|
1071
1086
|
sourceTemplate: .claude/hooks/completion-gate.sh
|
|
1072
1087
|
targetPath: .claude/hooks/completion-gate.sh
|
|
1073
1088
|
requires:
|
|
1074
|
-
- ukit-runtime-
|
|
1089
|
+
- ukit-runtime-scripts
|
|
1075
1090
|
mergeStrategy: overwrite_with_backup
|
|
1076
1091
|
variables: []
|
|
1077
1092
|
enabledByDefault: true
|
|
@@ -1111,11 +1126,15 @@ items:
|
|
|
1111
1126
|
packs:
|
|
1112
1127
|
- core
|
|
1113
1128
|
|
|
1129
|
+
# requires: [ukit-runtime-scripts] is forward-declared for the shared sensitive-value
|
|
1130
|
+
# scanner (consumed by the guard once TASK-039 wires it). The guard is fail-closed, so
|
|
1131
|
+
# its runtime dependency must be installed first — keeps the ordering posture explicit.
|
|
1114
1132
|
- id: hook-sensitive-data-guard
|
|
1115
1133
|
type: hook
|
|
1116
1134
|
sourceTemplate: .claude/hooks/sensitive-data-guard.sh
|
|
1117
1135
|
targetPath: .claude/hooks/sensitive-data-guard.sh
|
|
1118
|
-
requires:
|
|
1136
|
+
requires:
|
|
1137
|
+
- ukit-runtime-scripts
|
|
1119
1138
|
mergeStrategy: overwrite_with_backup
|
|
1120
1139
|
variables: []
|
|
1121
1140
|
enabledByDefault: true
|
|
@@ -1139,7 +1158,7 @@ items:
|
|
|
1139
1158
|
targetPath: .claude/hooks/skill-router.sh
|
|
1140
1159
|
requires:
|
|
1141
1160
|
- ukit-index-route-catalog-script
|
|
1142
|
-
- ukit-runtime-
|
|
1161
|
+
- ukit-runtime-scripts
|
|
1143
1162
|
mergeStrategy: overwrite_with_backup
|
|
1144
1163
|
variables: []
|
|
1145
1164
|
enabledByDefault: true
|
|
@@ -1164,7 +1183,7 @@ items:
|
|
|
1164
1183
|
sourceTemplate: .claude/hooks/context-hardcap-gate.sh
|
|
1165
1184
|
targetPath: .claude/hooks/context-hardcap-gate.sh
|
|
1166
1185
|
requires:
|
|
1167
|
-
- ukit-runtime-
|
|
1186
|
+
- ukit-runtime-scripts
|
|
1168
1187
|
mergeStrategy: overwrite_with_backup
|
|
1169
1188
|
variables: []
|
|
1170
1189
|
enabledByDefault: true
|
|
@@ -1204,10 +1223,16 @@ items:
|
|
|
1204
1223
|
packs:
|
|
1205
1224
|
- core
|
|
1206
1225
|
|
|
1207
|
-
|
|
1226
|
+
# One directory item ships the whole runtime dir. autoDiscoverTemplates:false silently
|
|
1227
|
+
# skips any template file without an explicit item, which is exactly how later-wave
|
|
1228
|
+
# runtime modules (async-lock.mjs, hook-process.mjs, transcript-tail.mjs, hook-input.*)
|
|
1229
|
+
# never reached installed projects and broke execution-ledger.mjs imports at hook time.
|
|
1230
|
+
# A directory scan keeps every new runtime module shipping without a manifest edit; the
|
|
1231
|
+
# walker preserves per-file modes (hook-input.sh keeps its exec bit).
|
|
1232
|
+
- id: ukit-runtime-scripts
|
|
1208
1233
|
type: config
|
|
1209
|
-
sourceTemplate: .claude/ukit/runtime
|
|
1210
|
-
targetPath: .claude/ukit/runtime
|
|
1234
|
+
sourceTemplate: .claude/ukit/runtime
|
|
1235
|
+
targetPath: .claude/ukit/runtime
|
|
1211
1236
|
requires: []
|
|
1212
1237
|
mergeStrategy: overwrite_with_backup
|
|
1213
1238
|
variables: []
|
|
@@ -1231,7 +1256,7 @@ items:
|
|
|
1231
1256
|
sourceTemplate: .claude/hooks/compress-output.sh
|
|
1232
1257
|
targetPath: .claude/hooks/compress-output.sh
|
|
1233
1258
|
requires:
|
|
1234
|
-
- ukit-runtime-
|
|
1259
|
+
- ukit-runtime-scripts
|
|
1235
1260
|
mergeStrategy: overwrite_with_backup
|
|
1236
1261
|
variables: []
|
|
1237
1262
|
enabledByDefault: true
|
|
@@ -1249,6 +1274,18 @@ items:
|
|
|
1249
1274
|
packs:
|
|
1250
1275
|
- core
|
|
1251
1276
|
|
|
1277
|
+
- id: hook-project-important
|
|
1278
|
+
type: hook
|
|
1279
|
+
sourceTemplate: .claude/hooks/project-important.sh
|
|
1280
|
+
targetPath: .claude/hooks/project-important.sh
|
|
1281
|
+
requires:
|
|
1282
|
+
- ukit-runtime-scripts
|
|
1283
|
+
mergeStrategy: overwrite_with_backup
|
|
1284
|
+
variables: []
|
|
1285
|
+
enabledByDefault: true
|
|
1286
|
+
packs:
|
|
1287
|
+
- core
|
|
1288
|
+
|
|
1252
1289
|
- id: hook-worklog-session-start
|
|
1253
1290
|
type: hook
|
|
1254
1291
|
sourceTemplate: .claude/hooks/session-start.md
|
|
@@ -1273,58 +1310,12 @@ items:
|
|
|
1273
1310
|
packs:
|
|
1274
1311
|
- core
|
|
1275
1312
|
|
|
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
1313
|
- id: ukit-index-anchor-search-script
|
|
1322
1314
|
type: config
|
|
1323
1315
|
sourceTemplate: .claude/ukit/index/anchor-search.mjs
|
|
1324
1316
|
targetPath: .claude/ukit/index/anchor-search.mjs
|
|
1325
1317
|
requires:
|
|
1326
|
-
- ukit-runtime-
|
|
1327
|
-
- ukit-runtime-safe-patch-core-script
|
|
1318
|
+
- ukit-runtime-scripts
|
|
1328
1319
|
mergeStrategy: overwrite_with_backup
|
|
1329
1320
|
variables: []
|
|
1330
1321
|
enabledByDefault: true
|
|
@@ -1348,8 +1339,7 @@ items:
|
|
|
1348
1339
|
sourceTemplate: .claude/ukit/index/provision-worktree.mjs
|
|
1349
1340
|
targetPath: .claude/ukit/index/provision-worktree.mjs
|
|
1350
1341
|
requires:
|
|
1351
|
-
- ukit-runtime-
|
|
1352
|
-
- ukit-runtime-safe-patch-core-script
|
|
1342
|
+
- ukit-runtime-scripts
|
|
1353
1343
|
mergeStrategy: overwrite_with_backup
|
|
1354
1344
|
variables: []
|
|
1355
1345
|
enabledByDefault: true
|
|
@@ -1361,8 +1351,7 @@ items:
|
|
|
1361
1351
|
sourceTemplate: .claude/ukit/index/stale-spec-check.mjs
|
|
1362
1352
|
targetPath: .claude/ukit/index/stale-spec-check.mjs
|
|
1363
1353
|
requires:
|
|
1364
|
-
- ukit-runtime-
|
|
1365
|
-
- ukit-runtime-safe-patch-core-script
|
|
1354
|
+
- ukit-runtime-scripts
|
|
1366
1355
|
mergeStrategy: overwrite_with_backup
|
|
1367
1356
|
variables: []
|
|
1368
1357
|
enabledByDefault: true
|
|
@@ -1374,8 +1363,7 @@ items:
|
|
|
1374
1363
|
sourceTemplate: .claude/ukit/index/pre-edit-backup.mjs
|
|
1375
1364
|
targetPath: .claude/ukit/index/pre-edit-backup.mjs
|
|
1376
1365
|
requires:
|
|
1377
|
-
- ukit-runtime-
|
|
1378
|
-
- ukit-runtime-safe-patch-core-script
|
|
1366
|
+
- ukit-runtime-scripts
|
|
1379
1367
|
mergeStrategy: overwrite_with_backup
|
|
1380
1368
|
variables: []
|
|
1381
1369
|
enabledByDefault: true
|
|
@@ -1387,8 +1375,7 @@ items:
|
|
|
1387
1375
|
sourceTemplate: .claude/ukit/index/post-edit-verify.mjs
|
|
1388
1376
|
targetPath: .claude/ukit/index/post-edit-verify.mjs
|
|
1389
1377
|
requires:
|
|
1390
|
-
- ukit-runtime-
|
|
1391
|
-
- ukit-runtime-safe-patch-core-script
|
|
1378
|
+
- ukit-runtime-scripts
|
|
1392
1379
|
mergeStrategy: overwrite_with_backup
|
|
1393
1380
|
variables: []
|
|
1394
1381
|
enabledByDefault: true
|
|
@@ -1549,54 +1536,6 @@ items:
|
|
|
1549
1536
|
packs:
|
|
1550
1537
|
- core
|
|
1551
1538
|
|
|
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
1539
|
- id: ukit-index-reset-auto-permissions-script
|
|
1601
1540
|
type: config
|
|
1602
1541
|
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.
|
|
3
|
+
"version": "2.5.0",
|
|
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": {
|
|
@@ -19,39 +19,64 @@ const changedFiles = changedArg
|
|
|
19
19
|
|
|
20
20
|
const force = readBooleanFlag(args, '--force');
|
|
21
21
|
const lastRefreshMs = await getLastRefreshTime(rootDir);
|
|
22
|
-
const staleness = force
|
|
23
|
-
? null
|
|
24
|
-
: await inspectIndexStaleness({ rootDir, maxAgeMs: DEFAULT_INDEX_CACHE_MAX_AGE_MS });
|
|
25
|
-
const stale = force || staleness.stale;
|
|
26
22
|
|
|
27
|
-
if (!
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
console.log(`changed-files-hint: ${changedFiles.join(', ')}`);
|
|
34
|
-
}
|
|
35
|
-
console.log(`root: ${rootDir}`);
|
|
36
|
-
} else {
|
|
37
|
-
// Reuse the staleness check's discovery snapshot: one enumeration per refresh.
|
|
38
|
-
const summary = await buildCodeIndex({
|
|
39
|
-
rootDir,
|
|
40
|
-
discoverySnapshot: staleness ? staleness.snapshot : null,
|
|
41
|
-
});
|
|
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 });
|
|
42
29
|
|
|
43
30
|
console.log('[index:refresh] completed');
|
|
31
|
+
console.log(`mode: ${summary.mode}`);
|
|
44
32
|
if (lastRefreshMs !== null) {
|
|
45
33
|
console.log(`lastRefreshBefore: ${new Date(lastRefreshMs).toISOString()}`);
|
|
46
34
|
}
|
|
47
|
-
|
|
48
|
-
console.log(`changed-files-hint: ${changedFiles.join(', ')}`);
|
|
49
|
-
}
|
|
35
|
+
console.log(`changed-files-hint: ${changedFiles.join(', ')}`);
|
|
50
36
|
console.log(`root: ${rootDir}`);
|
|
51
37
|
console.log(`files: ${summary.fileCount}`);
|
|
52
38
|
console.log(`parsed: ${summary.parsedCodeFileCount}`);
|
|
53
39
|
console.log(`reused: ${summary.reusedCodeFileCount}`);
|
|
40
|
+
if (summary.removedFiles.length > 0) {
|
|
41
|
+
console.log(`removed: ${summary.removedFiles.length}`);
|
|
42
|
+
}
|
|
54
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
|
+
}
|
|
55
80
|
}
|
|
56
81
|
|
|
57
82
|
function readFlagValue(argv, flag) {
|
|
@@ -18,6 +18,10 @@ import {
|
|
|
18
18
|
GATEWAY_RESILIENCE_ENV_DEFAULTS,
|
|
19
19
|
scanProfileResilienceEnv,
|
|
20
20
|
} from '../../core/gatewayResilienceEnv.js';
|
|
21
|
+
import {
|
|
22
|
+
inspectProjectImportant,
|
|
23
|
+
inspectProjectImportantWiring,
|
|
24
|
+
} from '../../core/projectImportant.js';
|
|
21
25
|
|
|
22
26
|
export const DOCTOR_HELP_FLAGS = new Set(['--help', '-h']);
|
|
23
27
|
const KNOWN_FLAGS = new Set([...DOCTOR_HELP_FLAGS, '--skills', '--gateway']);
|
|
@@ -103,6 +107,109 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [], homeDir =
|
|
|
103
107
|
|
|
104
108
|
const ok = (v) => (v ? '✓' : '✗');
|
|
105
109
|
|
|
110
|
+
// TASK-044 — typed PROJECT_IMPORTANT.md checks (spec §14). Each failure
|
|
111
|
+
// carries a class: install-repairable | owner-action | advisory-host-limit.
|
|
112
|
+
const INSTALL_REPAIR = 'Run ukit install';
|
|
113
|
+
const piInspection = await inspectProjectImportant({
|
|
114
|
+
projectRoot,
|
|
115
|
+
config: runtimeConfigInspection.config,
|
|
116
|
+
});
|
|
117
|
+
const piWiring = await inspectProjectImportantWiring(projectRoot, { trackedPaths });
|
|
118
|
+
const piState = piInspection.state;
|
|
119
|
+
|
|
120
|
+
const contentCheck = (failedState, label, remedy) => ({
|
|
121
|
+
label,
|
|
122
|
+
passed: piState !== failedState && piState !== 'missing' && piState !== 'unsafe-type' && piState !== 'unreadable',
|
|
123
|
+
applicable: piState !== 'missing' && piState !== 'unsafe-type' && piState !== 'unreadable',
|
|
124
|
+
failed: piState === failedState,
|
|
125
|
+
remediationClass: 'owner-action',
|
|
126
|
+
remedy,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const projectChecks = [
|
|
130
|
+
{
|
|
131
|
+
label: 'PROJECT_IMPORTANT.md exists',
|
|
132
|
+
passed: piState !== 'missing',
|
|
133
|
+
failed: piState === 'missing',
|
|
134
|
+
remediationClass: 'install-repairable',
|
|
135
|
+
remedy: INSTALL_REPAIR,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
label: 'PROJECT_IMPORTANT.md is a regular non-symlink file',
|
|
139
|
+
passed: piState !== 'unsafe-type' && piState !== 'missing',
|
|
140
|
+
failed: piState === 'unsafe-type',
|
|
141
|
+
applicable: piState !== 'missing',
|
|
142
|
+
remediationClass: 'owner-action',
|
|
143
|
+
remedy: 'Replace PROJECT_IMPORTANT.md with a regular file at the project root.',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
label: 'PROJECT_IMPORTANT.md is readable',
|
|
147
|
+
passed: piState !== 'unreadable' && piState !== 'missing' && piState !== 'unsafe-type',
|
|
148
|
+
failed: piState === 'unreadable',
|
|
149
|
+
applicable: piState !== 'missing' && piState !== 'unsafe-type',
|
|
150
|
+
remediationClass: 'advisory-host-limit',
|
|
151
|
+
remedy: 'Check PROJECT_IMPORTANT.md file permissions; the host cannot read it.',
|
|
152
|
+
},
|
|
153
|
+
contentCheck('invalid-utf8', 'PROJECT_IMPORTANT.md is valid UTF-8',
|
|
154
|
+
'Save PROJECT_IMPORTANT.md as valid UTF-8 without changing its intended content.'),
|
|
155
|
+
contentCheck('unsafe-control', 'PROJECT_IMPORTANT.md free of unsupported control characters',
|
|
156
|
+
'Remove unsupported control characters from PROJECT_IMPORTANT.md.'),
|
|
157
|
+
contentCheck('empty', 'PROJECT_IMPORTANT.md is not empty',
|
|
158
|
+
'Add project-owner instructions to PROJECT_IMPORTANT.md.'),
|
|
159
|
+
contentCheck('oversized', 'PROJECT_IMPORTANT.md within 6,000 code points',
|
|
160
|
+
'Shorten PROJECT_IMPORTANT.md to 6,000 Unicode code points or fewer.'),
|
|
161
|
+
contentCheck('secret-blocked', 'PROJECT_IMPORTANT.md free of unallowlisted secrets',
|
|
162
|
+
'Redact the secret or explicitly allowlist it.'),
|
|
163
|
+
{
|
|
164
|
+
label: 'Runtime module .claude/ukit/runtime/project-important.mjs',
|
|
165
|
+
passed: piWiring.claude.runtimeModule,
|
|
166
|
+
failed: !piWiring.claude.runtimeModule,
|
|
167
|
+
remediationClass: 'install-repairable',
|
|
168
|
+
remedy: INSTALL_REPAIR,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
label: 'Hook wrapper .claude/hooks/project-important.sh installed and executable',
|
|
172
|
+
passed: piWiring.claude.hookInstalled && piWiring.claude.hookExecutable,
|
|
173
|
+
failed: !(piWiring.claude.hookInstalled && piWiring.claude.hookExecutable),
|
|
174
|
+
remediationClass: 'install-repairable',
|
|
175
|
+
remedy: INSTALL_REPAIR,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
label: 'Claude settings SessionStart order (project-important.sh first)',
|
|
179
|
+
passed: piWiring.claude.settingsWired,
|
|
180
|
+
failed: !piWiring.claude.settingsWired,
|
|
181
|
+
remediationClass: 'install-repairable',
|
|
182
|
+
remedy: INSTALL_REPAIR,
|
|
183
|
+
},
|
|
184
|
+
];
|
|
185
|
+
if (piWiring.omp.installed) {
|
|
186
|
+
projectChecks.push({
|
|
187
|
+
label: 'omp bridge session_start order (project-important.sh first)',
|
|
188
|
+
passed: piWiring.omp.wired,
|
|
189
|
+
failed: !piWiring.omp.wired,
|
|
190
|
+
remediationClass: 'install-repairable',
|
|
191
|
+
remedy: INSTALL_REPAIR,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (piWiring.codex.installed) {
|
|
195
|
+
projectChecks.push({
|
|
196
|
+
label: 'Codex fallback present (AGENTS.md owner-instructions section)',
|
|
197
|
+
passed: piWiring.codex.wired,
|
|
198
|
+
failed: !piWiring.codex.wired,
|
|
199
|
+
remediationClass: 'install-repairable',
|
|
200
|
+
remedy: INSTALL_REPAIR,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (piWiring.opencode.installed) {
|
|
204
|
+
projectChecks.push({
|
|
205
|
+
label: 'OpenCode fallback present (AGENTS.md owner-instructions section)',
|
|
206
|
+
passed: piWiring.opencode.wired,
|
|
207
|
+
failed: !piWiring.opencode.wired,
|
|
208
|
+
remediationClass: 'install-repairable',
|
|
209
|
+
remedy: INSTALL_REPAIR,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
106
213
|
const providerNames = Object.keys(providers.providers);
|
|
107
214
|
const providerStatus = providerNames
|
|
108
215
|
.map((name) => `${name}=${ok(providers.providers[name].supported)}`)
|
|
@@ -139,6 +246,12 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [], homeDir =
|
|
|
139
246
|
}
|
|
140
247
|
console.log(`[UKit] ${ok(checks.allProvidersConfigured)} All providers configured`);
|
|
141
248
|
|
|
249
|
+
console.log('[UKit] Project rules checks:');
|
|
250
|
+
for (const check of projectChecks) {
|
|
251
|
+
if (check.applicable === false) continue;
|
|
252
|
+
console.log(`[UKit] ${ok(check.passed)} ${check.label}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
142
255
|
if (runtimeConfigInspection.errors.length > 0) {
|
|
143
256
|
console.log(`[UKit] Runtime config issues: ${runtimeConfigInspection.errors.join(' | ')}`);
|
|
144
257
|
}
|
|
@@ -254,8 +367,25 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [], homeDir =
|
|
|
254
367
|
}
|
|
255
368
|
|
|
256
369
|
const allPassed = Object.values(checks).every(Boolean);
|
|
257
|
-
|
|
258
|
-
|
|
370
|
+
const failedProjectChecks = projectChecks.filter(
|
|
371
|
+
(check) => check.applicable !== false && !check.passed,
|
|
372
|
+
);
|
|
373
|
+
const blockingFailures = failedProjectChecks.filter(
|
|
374
|
+
(check) => check.remediationClass === 'install-repairable' || check.remediationClass === 'owner-action',
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
if (failedProjectChecks.length > 0) {
|
|
378
|
+
console.log('[UKit] Remedies:');
|
|
379
|
+
for (const check of failedProjectChecks) {
|
|
380
|
+
console.log(`[UKit] - [${check.remediationClass}] ${check.remedy}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (!allPassed || blockingFailures.length > 0) {
|
|
385
|
+
console.log('[UKit] Some checks failed.');
|
|
386
|
+
if (!allPassed && blockingFailures.length === 0) {
|
|
387
|
+
console.log('[UKit] Re-run `ukit install` to repair install-managed files.');
|
|
388
|
+
}
|
|
259
389
|
process.exitCode = 1;
|
|
260
390
|
} else {
|
|
261
391
|
console.log('[UKit] All checks passed.');
|
|
@@ -42,10 +42,28 @@ export async function runUninstall({ projectRoot, argv = [] }) {
|
|
|
42
42
|
for (const p of result.wouldRemove) {
|
|
43
43
|
console.log(` - ${path.relative(projectRoot, p)}`);
|
|
44
44
|
}
|
|
45
|
+
for (const p of result.preservedPaths ?? []) {
|
|
46
|
+
console.log(`[UKit] Preserved ${path.basename(p)}.`);
|
|
47
|
+
}
|
|
48
|
+
if (result.wouldBackup && (result.backupPaths ?? []).length > 0) {
|
|
49
|
+
console.log(`[UKit] Would back up to ${path.basename(result.backupPaths[0])}.`);
|
|
50
|
+
}
|
|
51
|
+
for (const warning of result.backupWarnings ?? []) {
|
|
52
|
+
console.log(warning);
|
|
53
|
+
}
|
|
45
54
|
console.log(`[UKit] Would remove ${result.wouldRemove.length} path(s). Run without --dry-run to actually uninstall.`);
|
|
46
55
|
return;
|
|
47
56
|
}
|
|
48
57
|
|
|
58
|
+
for (const p of result.preservedPaths ?? []) {
|
|
59
|
+
console.log(`[UKit] Preserved ${path.basename(p)}.`);
|
|
60
|
+
}
|
|
61
|
+
for (const p of result.backupPaths ?? []) {
|
|
62
|
+
console.log(`[UKit] Backup: ${path.basename(p)}`);
|
|
63
|
+
}
|
|
64
|
+
for (const warning of result.backupWarnings ?? []) {
|
|
65
|
+
console.log(warning);
|
|
66
|
+
}
|
|
49
67
|
console.log(`[UKit] Uninstall complete. Removed ${result.removed}/${result.attempted} managed paths.`);
|
|
50
68
|
console.log('[UKit] Note: docs/PROJECT.md, docs/MEMORY.md, docs/AI_HANDOFF/, docs/WORKLOG.md contain user content and were preserved. Delete manually if needed.');
|
|
51
69
|
}
|