@ngockhoale/ukit 2.7.1 → 2.7.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 (52) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +1 -3
  3. package/manifests/hostCapabilities.yaml +0 -10
  4. package/manifests/instructionRules.yaml +0 -16
  5. package/manifests/platform.full.yaml +2 -16
  6. package/package.json +3 -5
  7. package/scripts/perf/audit-perf.mjs +4 -4
  8. package/scripts/perf/perf-findings.json +420 -0
  9. package/src/cli/adapters.js +10 -13
  10. package/src/cli/commands/doctor.js +60 -10
  11. package/src/cli/index.js +2 -2
  12. package/src/core/ompConfigMerge.js +19 -0
  13. package/src/core/opencodeSteer.js +37 -0
  14. package/src/core/permissionDoctor.js +319 -0
  15. package/src/core/permissionPolicy.js +179 -0
  16. package/src/core/projectImportant.js +14 -8
  17. package/src/core/runInstallPipeline.js +25 -0
  18. package/src/core/runtimeConfig.js +43 -4
  19. package/src/core/status.js +6 -4
  20. package/src/core/unattendedDoctor.js +9 -5
  21. package/src/manifest/selectItems.js +0 -1
  22. package/src/render/buildVariables.js +10 -1
  23. package/templates/.claude/agents/bug-debugger.md +1 -1
  24. package/templates/.claude/agents/feature-implementer.md +1 -1
  25. package/templates/.claude/agents/ukit-small-task-maintainer.md +2 -2
  26. package/templates/.claude/commands/ukit/handoff-create.md +4 -4
  27. package/templates/.claude/commands/ukit/handoff-fullstack.md +4 -4
  28. package/templates/.claude/commands/ukit/handoff-implement.md +2 -2
  29. package/templates/.claude/commands/ukit/handoff-review.md +2 -2
  30. package/templates/.claude/settings.json +14 -6
  31. package/templates/.claude/skills/update-status/SKILL.md +1 -1
  32. package/templates/.claude/ukit/index/route-task.mjs +7 -2
  33. package/templates/.claude/ukit/skill-router-state.json +1 -0
  34. package/templates/.codex/README.md +1 -1
  35. package/templates/.codex/settings.json +0 -1
  36. package/templates/.omp/agents/bug-debugger.md +1 -1
  37. package/templates/.omp/agents/feature-implementer.md +1 -1
  38. package/templates/.omp/agents/ukit-small-task-maintainer.md +2 -2
  39. package/templates/.omp/config.yml +13 -7
  40. package/templates/.ukit/storage/cache/hook-latency/unknown.jsonl +4 -0
  41. package/templates/AGENTS.md +4 -13
  42. package/templates/CLAUDE.md +1 -3
  43. package/templates/adapter-presets/claude/settings.local.json +0 -1
  44. package/templates/docs/AI_HANDOFF/INDEX.md +1 -1
  45. package/templates/docs/AI_HANDOFF/RULES.md +2 -2
  46. package/templates/docs/PROJECT.md +1 -1
  47. package/templates/docs/UKIT_INTERNALS.md +1 -1
  48. package/templates/instructions/core.md +1 -3
  49. package/templates/instructions/layout.yaml +2 -4
  50. package/templates/instructions/overlays/agents.md +3 -10
  51. package/templates/ukit/storage/config.json +2 -10
  52. package/templates/adapter-presets/opencode/opencode.template.json +0 -1
@@ -0,0 +1,420 @@
1
+ {
2
+ "generatedAt": "2026-09-20T07:42:56.112Z",
3
+ "schema": "ukit-perf-findings/1",
4
+ "sources": {
5
+ "telemetry": {
6
+ "dir": "/Volumes/KHOA_EXTENAL/WORKING_PROJECT/WORKING/UKit/.ukit/storage/cache/hook-latency",
7
+ "rows": 34629,
8
+ "files": 55
9
+ },
10
+ "settings": "/Volumes/KHOA_EXTENAL/WORKING_PROJECT/WORKING/UKit/.worktrees/task-233/templates/.claude/settings.json",
11
+ "hooksDir": "/Volumes/KHOA_EXTENAL/WORKING_PROJECT/WORKING/UKit/.worktrees/task-233/templates/.claude/hooks",
12
+ "benchmarkIterations": 10,
13
+ "gitLogWindowDays": 14
14
+ },
15
+ "findings": [
16
+ {
17
+ "id": "chain-pretooluse-read-grep-glob",
18
+ "area": "hook chain — PreToolUse Read|Grep|Glob — 1 hook",
19
+ "evidence": "telemetry",
20
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; measured per-call hook cost for the group's matching tools (n=974, p50 57ms, 2 hook rows/call; paths: 465 direct / 509 chain-runner). Hooks: sensitive-data-guard.sh (8s, 55ms)",
21
+ "p50ms": 57,
22
+ "p95ms": 76,
23
+ "totalMs": 53616,
24
+ "invocations": 974,
25
+ "rootCause": "3 process spawns per call (1 settings rows × 3: wrapper bash + node runtime + advisory telemetry node child), against a measured floor of bash 3.8ms + node 16.9ms + telemetry finish 19.5ms. Measured p50 exceeds the 50ms hotspot bar, so the spawn multiplier is the dominant term.",
26
+ "fixTask": "TASK-234",
27
+ "status": "confirmed"
28
+ },
29
+ {
30
+ "id": "chain-pretooluse-edit-write",
31
+ "area": "hook chain — PreToolUse Edit|Write — 6 hooks",
32
+ "evidence": "telemetry",
33
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; measured per-call hook cost for the group's matching tools (n=533, p50 316ms, 6 hook rows/call; paths: 458 direct / 75 chain-runner). Hooks: protect-files.sh (8s, 38ms), stale-spec-guard.sh (11s, 36ms), pre-edit-backup.sh (8s, 36ms), skill-router.sh (20s, 90ms), handoff-model-guard.sh (8s, 37ms), context-hardcap-gate.sh (8s, 54ms)",
34
+ "p50ms": 316,
35
+ "p95ms": 433,
36
+ "totalMs": 179919,
37
+ "invocations": 533,
38
+ "rootCause": "18 process spawns per call (6 settings rows × 3: wrapper bash + node runtime + advisory telemetry node child), against a measured floor of bash 3.8ms + node 16.9ms + telemetry finish 19.5ms. Measured p50 exceeds the 50ms hotspot bar, so the spawn multiplier is the dominant term.",
39
+ "fixTask": "TASK-234",
40
+ "status": "confirmed"
41
+ },
42
+ {
43
+ "id": "chain-pretooluse-bash",
44
+ "area": "hook chain — PreToolUse Bash — 6 hooks",
45
+ "evidence": "telemetry",
46
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; measured per-call hook cost for the group's matching tools (n=2973, p50 349ms, 6 hook rows/call; paths: 2711 direct / 262 chain-runner). Hooks: auto-allow-bash.sh (15s, 40ms), block-dangerous.sh (8s, 44ms), sensitive-data-guard.sh (8s, 55ms), handoff-model-guard.sh (8s, 37ms), context-hardcap-gate.sh (8s, 54ms), verification-guard.sh (8s, 56ms)",
47
+ "p50ms": 349,
48
+ "p95ms": 505,
49
+ "totalMs": 1070247,
50
+ "invocations": 2973,
51
+ "rootCause": "18 process spawns per call (6 settings rows × 3: wrapper bash + node runtime + advisory telemetry node child), against a measured floor of bash 3.8ms + node 16.9ms + telemetry finish 19.5ms. Measured p50 exceeds the 50ms hotspot bar, so the spawn multiplier is the dominant term.",
52
+ "fixTask": "TASK-234",
53
+ "status": "confirmed"
54
+ },
55
+ {
56
+ "id": "chain-posttooluse-read-grep-glob",
57
+ "area": "hook chain — PostToolUse Read|Grep|Glob — 1 hook",
58
+ "evidence": "telemetry",
59
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; measured per-call hook cost for the group's matching tools (n=963, p50 64ms, 2 hook rows/call; paths: 454 direct / 509 chain-runner). Hooks: record-execution.sh (8s, 38ms)",
60
+ "p50ms": 64,
61
+ "p95ms": 149,
62
+ "totalMs": 69147,
63
+ "invocations": 963,
64
+ "rootCause": "3 process spawns per call (1 settings rows × 3: wrapper bash + node runtime + advisory telemetry node child), against a measured floor of bash 3.8ms + node 16.9ms + telemetry finish 19.5ms. Measured p50 exceeds the 50ms hotspot bar, so the spawn multiplier is the dominant term.",
65
+ "fixTask": "TASK-234",
66
+ "status": "confirmed"
67
+ },
68
+ {
69
+ "id": "chain-posttooluse-edit-write",
70
+ "area": "hook chain — PostToolUse Edit|Write — 3 hooks",
71
+ "evidence": "telemetry",
72
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; measured per-call hook cost for the group's matching tools (n=523, p50 125ms, 3 hook rows/call; paths: 451 direct / 72 chain-runner). Hooks: post-edit-verify.sh (8s, 36ms), record-execution.sh (8s, 38ms), task-watchdog.sh (8s, 54ms)",
73
+ "p50ms": 125,
74
+ "p95ms": 221,
75
+ "totalMs": 72670,
76
+ "invocations": 523,
77
+ "rootCause": "9 process spawns per call (3 settings rows × 3: wrapper bash + node runtime + advisory telemetry node child), against a measured floor of bash 3.8ms + node 16.9ms + telemetry finish 19.5ms. Measured p50 exceeds the 50ms hotspot bar, so the spawn multiplier is the dominant term.",
78
+ "fixTask": "TASK-234",
79
+ "status": "confirmed"
80
+ },
81
+ {
82
+ "id": "chain-posttooluse-bash",
83
+ "area": "hook chain — PostToolUse Bash — 2 hooks",
84
+ "evidence": "telemetry",
85
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; measured per-call hook cost for the group's matching tools (n=2852, p50 90ms, 2 hook rows/call; paths: 2591 direct / 261 chain-runner). Hooks: compress-output.sh (12s, 61ms), record-execution.sh (8s, 38ms)",
86
+ "p50ms": 90,
87
+ "p95ms": 145,
88
+ "totalMs": 279069,
89
+ "invocations": 2852,
90
+ "rootCause": "6 process spawns per call (2 settings rows × 3: wrapper bash + node runtime + advisory telemetry node child), against a measured floor of bash 3.8ms + node 16.9ms + telemetry finish 19.5ms. Measured p50 exceeds the 50ms hotspot bar, so the spawn multiplier is the dominant term.",
91
+ "fixTask": "TASK-234",
92
+ "status": "confirmed"
93
+ },
94
+ {
95
+ "id": "chain-userpromptsubmit-all",
96
+ "area": "hook chain — UserPromptSubmit (all tools) — 4 hooks",
97
+ "evidence": "telemetry",
98
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; measured per-call hook cost for the group's matching tools (n=193, p50 319ms, 4 hook rows/call; paths: 157 direct / 36 chain-runner). Hooks: sensitive-data-guard.sh (8s, 55ms), skill-router.sh (20s, 90ms), vision-router.sh (12s, 54ms), context-window-guard.sh (10s, 54ms)",
99
+ "p50ms": 319,
100
+ "p95ms": 585,
101
+ "totalMs": 65511,
102
+ "invocations": 193,
103
+ "rootCause": "12 process spawns per call (4 settings rows × 3: wrapper bash + node runtime + advisory telemetry node child), against a measured floor of bash 3.8ms + node 16.9ms + telemetry finish 19.5ms. Measured p50 exceeds the 50ms hotspot bar, so the spawn multiplier is the dominant term.",
104
+ "fixTask": "TASK-234",
105
+ "status": "confirmed"
106
+ },
107
+ {
108
+ "id": "chain-stop-all",
109
+ "area": "hook chain — Stop (all tools) — 1 hook",
110
+ "evidence": "telemetry",
111
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; measured per-call hook cost for the group's matching tools (n=113, p50 81ms, 1 hook rows/call; paths: 113 direct / 0 chain-runner). Hooks: completion-gate.sh (8s, 36ms)",
112
+ "p50ms": 81,
113
+ "p95ms": 108,
114
+ "totalMs": 9542,
115
+ "invocations": 113,
116
+ "rootCause": "3 process spawns per call (1 settings rows × 3: wrapper bash + node runtime + advisory telemetry node child), against a measured floor of bash 3.8ms + node 16.9ms + telemetry finish 19.5ms. Measured p50 exceeds the 50ms hotspot bar, so the spawn multiplier is the dominant term.",
117
+ "fixTask": "TASK-234",
118
+ "status": "confirmed"
119
+ },
120
+ {
121
+ "id": "chain-precompact-all",
122
+ "area": "hook chain — PreCompact (all tools) — 1 hook",
123
+ "evidence": "telemetry",
124
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; measured per-call hook cost for the group's matching tools (n=2, p50 49ms, 1 hook rows/call; paths: 0 direct / 2 chain-runner). Hooks: reinject-context.sh (8s, 33ms)",
125
+ "p50ms": 49,
126
+ "p95ms": 49,
127
+ "totalMs": 84,
128
+ "invocations": 2,
129
+ "rootCause": "3 process spawns per call (1 settings rows × 3: wrapper bash + node runtime + advisory telemetry node child), against a measured floor of bash 3.8ms + node 16.9ms + telemetry finish 19.5ms. Measured p50 is under the 50ms hotspot bar; consolidation is still the mechanism that removes the spawns.",
130
+ "fixTask": "TASK-234",
131
+ "status": "confirmed"
132
+ },
133
+ {
134
+ "id": "chain-sessionstart-all",
135
+ "area": "hook chain — SessionStart (all tools) — 4 hooks",
136
+ "evidence": "telemetry",
137
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; measured per-call hook cost for the group's matching tools (n=58, p50 96ms, 2 hook rows/call; paths: 37 direct / 21 chain-runner). Hooks: project-important.sh (8s, 35ms), auto-prune-bash.sh (12s, 1ms), reset-compact-pressure.sh (12s, 2ms), handoff-resume.sh (8s, 54ms)",
138
+ "p50ms": 96,
139
+ "p95ms": 191,
140
+ "totalMs": 7170,
141
+ "invocations": 58,
142
+ "rootCause": "12 process spawns per call (4 settings rows × 3: wrapper bash + node runtime + advisory telemetry node child), against a measured floor of bash 3.8ms + node 16.9ms + telemetry finish 19.5ms. Measured p50 exceeds the 50ms hotspot bar, so the spawn multiplier is the dominant term.",
143
+ "fixTask": "TASK-234",
144
+ "status": "confirmed"
145
+ },
146
+ {
147
+ "id": "chain-sessionend-all",
148
+ "area": "hook chain — SessionEnd (all tools) — 1 hook",
149
+ "evidence": "settings.json",
150
+ "evidenceDetail": "settings.json group only — no telemetry rows are attributable to this group, so no per-call cost is reported. Hooks: session-episode.sh (8s, 23ms)",
151
+ "p50ms": null,
152
+ "p95ms": null,
153
+ "totalMs": null,
154
+ "invocations": 0,
155
+ "rootCause": "3 process spawns per call (1 settings rows × 3: wrapper bash + node runtime + advisory telemetry node child), against a measured floor of bash 3.8ms + node 16.9ms + telemetry finish 19.5ms. Not measured on this tree.",
156
+ "fixTask": null,
157
+ "status": "deferred"
158
+ },
159
+ {
160
+ "id": "hook-hook-chain-runner",
161
+ "area": "single hook — hook-chain-runner",
162
+ "evidence": "telemetry",
163
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; standalone re-run p50 not benchmarked (script not resolvable from this root)",
164
+ "p50ms": 106,
165
+ "p95ms": 409,
166
+ "totalMs": 283464,
167
+ "invocations": 1753,
168
+ "rootCause": "per-invocation cost is dominated by the 3-spawn hook row (wrapper bash 3.8ms + node 16.9ms + telemetry finish 19.5ms), plus this script's own work",
169
+ "fixTask": "TASK-234",
170
+ "status": "confirmed"
171
+ },
172
+ {
173
+ "id": "hook-skill-router-sh",
174
+ "area": "single hook — skill-router.sh",
175
+ "evidence": "telemetry",
176
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; standalone re-run p50 89.6ms",
177
+ "p50ms": 55,
178
+ "p95ms": 130,
179
+ "totalMs": 50744,
180
+ "invocations": 770,
181
+ "rootCause": "per-invocation cost is dominated by the 3-spawn hook row (wrapper bash 3.8ms + node 16.9ms + telemetry finish 19.5ms), plus this script's own work",
182
+ "fixTask": "TASK-234",
183
+ "status": "confirmed"
184
+ },
185
+ {
186
+ "id": "hook-vision-router-sh",
187
+ "area": "single hook — vision-router.sh",
188
+ "evidence": "telemetry",
189
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; standalone re-run p50 54.4ms",
190
+ "p50ms": 82,
191
+ "p95ms": 232,
192
+ "totalMs": 22275,
193
+ "invocations": 215,
194
+ "rootCause": "per-invocation cost is dominated by the 3-spawn hook row (wrapper bash 3.8ms + node 16.9ms + telemetry finish 19.5ms), plus this script's own work",
195
+ "fixTask": "TASK-234",
196
+ "status": "confirmed"
197
+ },
198
+ {
199
+ "id": "hook-context-window-guard-sh",
200
+ "area": "single hook — context-window-guard.sh",
201
+ "evidence": "telemetry",
202
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; standalone re-run p50 54.1ms",
203
+ "p50ms": 55,
204
+ "p95ms": 90,
205
+ "totalMs": 12101,
206
+ "invocations": 215,
207
+ "rootCause": "per-invocation cost is dominated by the 3-spawn hook row (wrapper bash 3.8ms + node 16.9ms + telemetry finish 19.5ms), plus this script's own work",
208
+ "fixTask": "TASK-234",
209
+ "status": "confirmed"
210
+ },
211
+ {
212
+ "id": "hook-auto-allow-bash-sh",
213
+ "area": "single hook — auto-allow-bash.sh",
214
+ "evidence": "telemetry",
215
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; standalone re-run p50 39.7ms",
216
+ "p50ms": 64,
217
+ "p95ms": 96,
218
+ "totalMs": 196059,
219
+ "invocations": 2994,
220
+ "rootCause": "per-invocation cost is dominated by the 3-spawn hook row (wrapper bash 3.8ms + node 16.9ms + telemetry finish 19.5ms), plus this script's own work",
221
+ "fixTask": "TASK-234",
222
+ "status": "confirmed"
223
+ },
224
+ {
225
+ "id": "hook-block-dangerous-sh",
226
+ "area": "single hook — block-dangerous.sh",
227
+ "evidence": "telemetry",
228
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; standalone re-run p50 44.3ms",
229
+ "p50ms": 89,
230
+ "p95ms": 190,
231
+ "totalMs": 392109,
232
+ "invocations": 4263,
233
+ "rootCause": "per-invocation cost is dominated by the 3-spawn hook row (wrapper bash 3.8ms + node 16.9ms + telemetry finish 19.5ms), plus this script's own work",
234
+ "fixTask": "TASK-234",
235
+ "status": "confirmed"
236
+ },
237
+ {
238
+ "id": "hook-context-hardcap-gate-sh",
239
+ "area": "single hook — context-hardcap-gate.sh",
240
+ "evidence": "telemetry",
241
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; standalone re-run p50 53.9ms",
242
+ "p50ms": 67,
243
+ "p95ms": 93,
244
+ "totalMs": 241749,
245
+ "invocations": 3571,
246
+ "rootCause": "per-invocation cost is dominated by the 3-spawn hook row (wrapper bash 3.8ms + node 16.9ms + telemetry finish 19.5ms), plus this script's own work",
247
+ "fixTask": "TASK-234",
248
+ "status": "confirmed"
249
+ },
250
+ {
251
+ "id": "hook-stale-spec-guard-sh",
252
+ "area": "single hook — stale-spec-guard.sh",
253
+ "evidence": "telemetry",
254
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; standalone re-run p50 36.0ms",
255
+ "p50ms": 53,
256
+ "p95ms": 74,
257
+ "totalMs": 28844,
258
+ "invocations": 544,
259
+ "rootCause": "per-invocation cost is dominated by the 3-spawn hook row (wrapper bash 3.8ms + node 16.9ms + telemetry finish 19.5ms), plus this script's own work",
260
+ "fixTask": "TASK-234",
261
+ "status": "confirmed"
262
+ },
263
+ {
264
+ "id": "hook-pre-edit-backup-sh",
265
+ "area": "single hook — pre-edit-backup.sh",
266
+ "evidence": "telemetry",
267
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; standalone re-run p50 36.0ms",
268
+ "p50ms": 55,
269
+ "p95ms": 74,
270
+ "totalMs": 29992,
271
+ "invocations": 535,
272
+ "rootCause": "per-invocation cost is dominated by the 3-spawn hook row (wrapper bash 3.8ms + node 16.9ms + telemetry finish 19.5ms), plus this script's own work",
273
+ "fixTask": "TASK-234",
274
+ "status": "confirmed"
275
+ },
276
+ {
277
+ "id": "hook-completion-gate-sh",
278
+ "area": "single hook — completion-gate.sh",
279
+ "evidence": "telemetry",
280
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; standalone re-run p50 35.8ms",
281
+ "p50ms": 81,
282
+ "p95ms": 108,
283
+ "totalMs": 9659,
284
+ "invocations": 116,
285
+ "rootCause": "per-invocation cost is dominated by the 3-spawn hook row (wrapper bash 3.8ms + node 16.9ms + telemetry finish 19.5ms), plus this script's own work",
286
+ "fixTask": "TASK-234",
287
+ "status": "confirmed"
288
+ },
289
+ {
290
+ "id": "hook-project-important-tail",
291
+ "area": "project-important.sh — PROJECT_IMPORTANT.md injection tail",
292
+ "evidence": "telemetry",
293
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; bimodal across 249 invocations: p50 43ms vs p95 1551ms vs max 2068ms (paths seen: PreToolUse, SessionStart, (unattributed)) — a cheap path and an expensive path with no cache between them",
294
+ "p50ms": null,
295
+ "p95ms": 1551,
296
+ "totalMs": 115792,
297
+ "invocations": 249,
298
+ "rootCause": "the renderer re-reads PROJECT_IMPORTANT.md and re-runs its full work (strict UTF-8 decode, control-character validation, sensitive-value scan, envelope render) on every invocation; there is no mtime/content-keyed cache, so repeat runs pay the whole cost instead of replaying the previous result",
299
+ "fixTask": "TASK-235",
300
+ "status": "confirmed"
301
+ },
302
+ {
303
+ "id": "record-execution-multiplicity",
304
+ "area": "record-execution.sh — PostToolUse receipt writer registered in 3 matcher groups",
305
+ "evidence": "telemetry",
306
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; measured fire count per tool call = 12 (distribution {\"1\":4341,\"3\":1,\"4\":1,\"12\":1} over 4344 calls). The \"once per matching matcher group\" repeat hypothesis is REFUTED: settings.json names the hook in 3 groups, but those matchers are mutually exclusive per tool, so it runs exactly once per call. Its real cost is breadth — highest-invocation hook measured (n=4360, p50 39ms, total 186663ms).",
307
+ "p50ms": 39,
308
+ "p95ms": 64,
309
+ "totalMs": 186663,
310
+ "invocations": 4360,
311
+ "rootCause": "not a duplication bug: the matchers (Read|Grep|Glob, Edit|Write, Bash) are mutually exclusive, so at most one group fires per tool. The cost is that one full 3-spawn hook row is paid on every tool call of every kind — breadth, not repeats.",
312
+ "fixTask": null,
313
+ "status": "not-a-bug"
314
+ },
315
+ {
316
+ "id": "index-router-cold-start",
317
+ "area": "index/router helpers invoked per prompt (skill-router → resolve-context / route-task / query-index)",
318
+ "evidence": "telemetry",
319
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; UserPromptSubmit groups: n=193, p50 319ms, p95 585ms, 4 hook rows/group. Standalone cold starts: skill-router.sh 89.6ms, query-index.mjs 24.1ms, route-task.mjs not benchmarked, resolve-context.mjs not benchmarked",
320
+ "p50ms": 319,
321
+ "p95ms": 585,
322
+ "totalMs": 65511,
323
+ "invocations": 193,
324
+ "rootCause": "every router helper pays a fresh node cold start (no warm process is reused), and skill-router.sh additionally imports the index core in-process per invocation; the cost is process boot plus module load, not the routing decision itself",
325
+ "fixTask": "TASK-234",
326
+ "status": "confirmed"
327
+ },
328
+ {
329
+ "id": "c29-c32-hot-path-additions",
330
+ "area": "recent hot-path additions — 20 files across 16 commits",
331
+ "evidence": "git-log",
332
+ "evidenceDetail": "git log --since=14 days ago --diff-filter=A -- templates/.claude/hooks templates/.claude/ukit/runtime templates/.claude/ukit/index: templates/.claude/hooks/session-episode.sh, templates/.claude/hooks/project-important.sh, templates/.claude/hooks/project-important.sh, templates/.claude/ukit/runtime/project-important.mjs, templates/.claude/ukit/runtime/sensitive-value-scanner.mjs, templates/.claude/ukit/runtime/hook-chain-budget.mjs, templates/.claude/ukit/runtime/stop-coordinator.mjs, templates/.claude/ukit/runtime/hook-payload-store.mjs, templates/.claude/ukit/runtime/hook-telemetry.mjs, templates/.claude/ukit/runtime/hook-telemetry.sh, templates/.claude/ukit/runtime/context-capacity.mjs, templates/.claude/ukit/runtime/async-lock.mjs, +8 more",
333
+ "p50ms": null,
334
+ "p95ms": null,
335
+ "totalMs": null,
336
+ "invocations": 20,
337
+ "rootCause": "each added hook file becomes another settings.json row on the event it joins, i.e. another 3-spawn hook row per call (SPEC §1.3 consolidation is what removes them); each added runtime module is an extra cold-start import paid per invocation. The regression is additive spawn count, not one slow file.",
338
+ "fixTask": "TASK-234",
339
+ "status": "confirmed"
340
+ },
341
+ {
342
+ "id": "existing-chain-runner-baseline",
343
+ "area": "hook-chain-runner.mjs — already-consolidated omp path (TASK-234 reuse target)",
344
+ "evidence": "telemetry",
345
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; a whole chain of 1 scripts runs in ONE node process at p50 106ms / p95 409ms (n=1753), while a Claude settings group of comparable size pays 3 spawns per script. Nested per-script rows: record-execution.sh p50 71ms, sensitive-data-guard.sh p50 60ms, handoff-model-guard.sh p50 41ms, context-hardcap-gate.sh p50 72ms, auto-allow-bash.sh p50 72ms, block-dangerous.sh p50 82ms",
346
+ "p50ms": 106,
347
+ "p95ms": 409,
348
+ "totalMs": 283464,
349
+ "invocations": 1753,
350
+ "rootCause": "no defect — this is the measured proof that one-process-per-group execution is achievable with per-script telemetry intact, so TASK-234 should re-point settings.json at this runner rather than build a new one",
351
+ "fixTask": "TASK-234",
352
+ "status": "confirmed"
353
+ },
354
+ {
355
+ "id": "process-floor",
356
+ "area": "process spawn floor (bash + node + advisory telemetry child)",
357
+ "evidence": "micro-benchmark",
358
+ "evidenceDetail": "live spawnSync over 10 iterations: bash -c true p50 3.76ms, node -e 0 p50 16.94ms, node hook-telemetry.mjs --finish p50 19.53ms",
359
+ "p50ms": 40.235959,
360
+ "p95ms": 45.548333,
361
+ "totalMs": null,
362
+ "invocations": 0,
363
+ "rootCause": "each settings.json hook row is a bash wrapper that boots node, then arms a second node child for advisory telemetry; this floor is paid per row, so it multiplies by the row count of the event",
364
+ "fixTask": "TASK-234",
365
+ "status": "not-a-bug"
366
+ },
367
+ {
368
+ "id": "hook-reinject-context-sh-unmeasured",
369
+ "area": "single hook — reinject-context.sh (registered in settings.json, no telemetry rows)",
370
+ "evidence": "unavailable",
371
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; no row exists for this script, so no per-invocation number is reported. It still costs 3 spawns on its event and is therefore already counted inside that chain's total.",
372
+ "p50ms": null,
373
+ "p95ms": null,
374
+ "totalMs": null,
375
+ "invocations": 0,
376
+ "rootCause": "no measurement exists for reinject-context.sh on this tree — its cost stays unset (evidenced by absence) rather than estimated; the chain it belongs to carries the spawn floor",
377
+ "fixTask": null,
378
+ "status": "deferred"
379
+ },
380
+ {
381
+ "id": "hook-auto-prune-bash-sh-unmeasured",
382
+ "area": "single hook — auto-prune-bash.sh (registered in settings.json, no telemetry rows)",
383
+ "evidence": "unavailable",
384
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; no row exists for this script, so no per-invocation number is reported. It still costs 3 spawns on its event and is therefore already counted inside that chain's total.",
385
+ "p50ms": null,
386
+ "p95ms": null,
387
+ "totalMs": null,
388
+ "invocations": 0,
389
+ "rootCause": "no measurement exists for auto-prune-bash.sh on this tree — its cost stays unset (evidenced by absence) rather than estimated; the chain it belongs to carries the spawn floor",
390
+ "fixTask": null,
391
+ "status": "deferred"
392
+ },
393
+ {
394
+ "id": "hook-reset-compact-pressure-sh-unmeasured",
395
+ "area": "single hook — reset-compact-pressure.sh (registered in settings.json, no telemetry rows)",
396
+ "evidence": "unavailable",
397
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; no row exists for this script, so no per-invocation number is reported. It still costs 3 spawns on its event and is therefore already counted inside that chain's total.",
398
+ "p50ms": null,
399
+ "p95ms": null,
400
+ "totalMs": null,
401
+ "invocations": 0,
402
+ "rootCause": "no measurement exists for reset-compact-pressure.sh on this tree — its cost stays unset (evidenced by absence) rather than estimated; the chain it belongs to carries the spawn floor",
403
+ "fixTask": null,
404
+ "status": "deferred"
405
+ },
406
+ {
407
+ "id": "hook-session-episode-sh-unmeasured",
408
+ "area": "single hook — session-episode.sh (registered in settings.json, no telemetry rows)",
409
+ "evidence": "unavailable",
410
+ "evidenceDetail": ".ukit/storage/cache/hook-latency — 34629 rows across 55 session files, span 2026-09-17T10:50:18.670Z → 2026-09-20T07:42:45.514Z; no row exists for this script, so no per-invocation number is reported. It still costs 3 spawns on its event and is therefore already counted inside that chain's total.",
411
+ "p50ms": null,
412
+ "p95ms": null,
413
+ "totalMs": null,
414
+ "invocations": 0,
415
+ "rootCause": "no measurement exists for session-episode.sh on this tree — its cost stays unset (evidenced by absence) rather than estimated; the chain it belongs to carries the spawn floor",
416
+ "fixTask": null,
417
+ "status": "deferred"
418
+ }
419
+ ]
420
+ }
@@ -19,16 +19,6 @@ export const OPTIONAL_ADAPTERS = [
19
19
  '.codex/agents',
20
20
  ],
21
21
  },
22
- {
23
- key: 'opencode',
24
- label: 'OpenCode',
25
- itemIds: [
26
- 'multi-opencode-config',
27
- ],
28
- managedPaths: [
29
- 'opencode.json',
30
- ],
31
- },
32
22
  {
33
23
  key: 'omp',
34
24
  label: 'Oh My Pi (omp)',
@@ -75,13 +65,11 @@ export const TOOL_ALIASES = new Map([
75
65
  ['claude-code', 'claude'],
76
66
  ['codex', 'codex'],
77
67
  ['openai-codex', 'codex'],
78
- ['opencode', 'opencode'],
79
- ['open-code', 'opencode'],
80
68
  ['omp', 'omp'],
81
69
  ['oh-my-pi', 'omp'],
82
70
  ]);
83
71
 
84
- export const TOOLS_FLAG_HELP = 'all|none|claude,codex,opencode,omp';
72
+ export const TOOLS_FLAG_HELP = 'all|none|claude,codex,omp';
85
73
 
86
74
  export function parseToolsArg(argv = []) {
87
75
  let toolsArg = null;
@@ -137,6 +125,15 @@ export function resolveOptionalToolKeys(toolsArg) {
137
125
  );
138
126
  }
139
127
 
128
+ // legacy: 'opencode'/'open-code' were adapters until the OpenCode lane was removed.
129
+ // Warn + ignore so `--tools=opencode` still exits 0 instead of hard-failing.
130
+ if (normalizedToken === 'opencode' || normalizedToken === 'open-code') {
131
+ console.warn(
132
+ `[UKit] '${token}' was removed as a UKit adapter — ignoring. Allowed: ${TOOLS_FLAG_HELP}`,
133
+ );
134
+ continue;
135
+ }
136
+
140
137
  const normalizedTool = TOOL_ALIASES.get(normalizedToken) ?? null;
141
138
  if (!normalizedTool) {
142
139
  throw new Error(`Unknown tool '${token}'. Allowed: ${TOOLS_FLAG_HELP}`);
@@ -24,10 +24,12 @@ import {
24
24
  } from '../../core/projectImportant.js';
25
25
  import { runDocContractChecks } from '../../core/docContracts.js';
26
26
  import { inspectUnattendedMode } from '../../core/unattendedDoctor.js';
27
+ import { inspectPermissions } from '../../core/permissionDoctor.js';
28
+ import { findOpencodeArtifacts, opencodeSteerMessage } from '../../core/opencodeSteer.js';
27
29
 
28
30
  export const DOCTOR_HELP_FLAGS = new Set(['--help', '-h']);
29
- const KNOWN_FLAGS = new Set([...DOCTOR_HELP_FLAGS, '--skills', '--gateway', '--docs']);
30
- const SUPPORTED_FLAGS_LIST = '--help, -h, --skills, --gateway, --docs';
31
+ const KNOWN_FLAGS = new Set([...DOCTOR_HELP_FLAGS, '--skills', '--gateway', '--docs', '--permissions', '--json']);
32
+ const SUPPORTED_FLAGS_LIST = '--help, -h, --skills, --gateway, --docs, --permissions, --json';
31
33
 
32
34
  export function printDoctorHelp() {
33
35
  console.log('Usage: ukit doctor [options]');
@@ -39,6 +41,30 @@ export function printDoctorHelp() {
39
41
  console.log(' --skills Also print a skill word-count/budget report');
40
42
  console.log(' --gateway Live gateway probe (streaming + non-streaming); advisory only');
41
43
  console.log(' --docs Run doc-contract checks (manifests/documentation.yaml projects only)');
44
+ console.log(' --permissions Print the full per-host permission report table');
45
+ console.log(' --json Emit the permission report as JSON ({permissions:{...}})');
46
+ }
47
+
48
+ function printPermissionSection(permissionReport, { verbose }) {
49
+ console.log('[UKit] Permission report:');
50
+ console.log(`[UKit] declared: ${permissionReport.declared} → effective: ${permissionReport.effective} (allowUnsafeEval=${permissionReport.allowUnsafeEval})`);
51
+ for (const [host, info] of Object.entries(permissionReport.hosts)) {
52
+ if (!info.present) {
53
+ if (verbose) console.log(`[UKit] ${host}: SKIP (no UKit-managed surface)`);
54
+ continue;
55
+ }
56
+ console.log(`[UKit] ${host}: ${info.enforcement} | prompts=${info.promptCount} | deny=${info.denyCount} (missing ${info.denyMissing.length}) | eval=${info.evalSurface ?? info.evalPosture ?? 'n/a'}`);
57
+ }
58
+ for (const check of permissionReport.checks) {
59
+ if (check.applicable === false) {
60
+ if (verbose) console.log(`[UKit] - ${check.label} (skip: ${check.detail ?? 'not applicable'})`);
61
+ continue;
62
+ }
63
+ const sev = check.severity === 'warning' ? ' (warning)' : check.severity === 'info' ? ' (info)' : '';
64
+ console.log(`[UKit] ${check.passed ? '✓' : '✗'} ${check.label}${sev}`);
65
+ if (check.detail) console.log(`[UKit] detail: ${check.detail}`);
66
+ if (!check.passed && check.remedy) console.log(`[UKit] remedy: ${check.remedy}`);
67
+ }
42
68
  }
43
69
 
44
70
  export async function runDoctor({ packageRoot, projectRoot, argv = [], homeDir = os.homedir(), ompPath = 'omp' }) {
@@ -52,6 +78,15 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [], homeDir =
52
78
  return;
53
79
  }
54
80
 
81
+ // TASK-006 / FR-006 — `--json` is a machine surface: emit ONLY the permission
82
+ // report object, exit 1 when any FAIL class fires.
83
+ if (argv.includes('--json')) {
84
+ const permissionReport = await inspectPermissions({ projectRoot, ompPath });
85
+ console.log(JSON.stringify({ permissions: permissionReport }, null, 2));
86
+ if (permissionReport.failures.length > 0) process.exitCode = 1;
87
+ return;
88
+ }
89
+
55
90
  const pathConfig = buildPathConfig({ packageRoot, projectRoot });
56
91
  const runtimePaths = buildRuntimePaths(projectRoot);
57
92
  const manifest = await loadManifest(pathConfig.manifestPath);
@@ -203,13 +238,16 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [], homeDir =
203
238
  remedy: INSTALL_REPAIR,
204
239
  });
205
240
  }
206
- if (piWiring.opencode.installed) {
241
+ // SPEC FR-007 — a leftover opencode.json is a WARN row: advisory only, never a
242
+ // FAIL, never auto-fixed. Removal is the user's manual action.
243
+ const opencodeArtifacts = findOpencodeArtifacts(projectRoot);
244
+ if (opencodeArtifacts.length > 0) {
207
245
  projectChecks.push({
208
- label: 'OpenCode fallback present (AGENTS.md owner-instructions section)',
209
- passed: piWiring.opencode.wired,
210
- failed: !piWiring.opencode.wired,
211
- remediationClass: 'install-repairable',
212
- remedy: INSTALL_REPAIR,
246
+ label: opencodeSteerMessage(opencodeArtifacts),
247
+ passed: true,
248
+ severity: 'warning',
249
+ remediationClass: 'advisory',
250
+ remedy: 'Remove it manually: rm opencode.json',
213
251
  });
214
252
  }
215
253
 
@@ -252,7 +290,11 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [], homeDir =
252
290
  console.log('[UKit] Project rules checks:');
253
291
  for (const check of projectChecks) {
254
292
  if (check.applicable === false) continue;
255
- console.log(`[UKit] ${ok(check.passed)} ${check.label}`);
293
+ const severity = check.severity === 'warning' ? ' (warning)' : '';
294
+ console.log(`[UKit] ${ok(check.passed)} ${check.label}${severity}`);
295
+ if (check.remedy && check.severity === 'warning') {
296
+ console.log(`[UKit] remedy: ${check.remedy}`);
297
+ }
256
298
  }
257
299
 
258
300
  // TASK-011 / SPEC §8 (FR-007): unattended-mode checks — same remediation-class
@@ -269,6 +311,11 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [], homeDir =
269
311
  }
270
312
  }
271
313
 
314
+ // TASK-006 / FR-006 — unified permission report. Compact section by default,
315
+ // full per-host table under --permissions. FAIL classes block the exit code.
316
+ const permissionReport = await inspectPermissions({ projectRoot, ompPath });
317
+ printPermissionSection(permissionReport, { verbose: argv.includes('--permissions') });
318
+
272
319
  if (runtimeConfigInspection.errors.length > 0) {
273
320
  console.log(`[UKit] Runtime config issues: ${runtimeConfigInspection.errors.join(' | ')}`);
274
321
  }
@@ -420,7 +467,10 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [], homeDir =
420
467
  const failedUnattendedChecks = unattended.checks.filter(
421
468
  (check) => check.applicable !== false && !check.passed,
422
469
  );
423
- const allFailedChecks = [...failedProjectChecks, ...failedUnattendedChecks];
470
+ const failedPermissionChecks = permissionReport.checks.filter(
471
+ (check) => check.applicable !== false && !check.passed && check.severity !== 'info' && check.severity !== 'warning',
472
+ );
473
+ const allFailedChecks = [...failedProjectChecks, ...failedUnattendedChecks, ...failedPermissionChecks];
424
474
  const blockingFailures = allFailedChecks.filter(
425
475
  (check) => check.remediationClass === 'install-repairable' || check.remediationClass === 'owner-action',
426
476
  );
package/src/cli/index.js CHANGED
@@ -117,8 +117,8 @@ export async function runCli({ argv, packageRoot, projectRoot, packageVersion })
117
117
  console.log('');
118
118
  console.log('Options (install / diff):');
119
119
  console.log(' --tools=<list> Comma-separated adapters to enable (default: all)');
120
- console.log(' Values: claude, codex, opencode, omp');
121
- console.log(' Example: ukit install --tools=claude,codex,opencode,omp');
120
+ console.log(' Values: claude, codex, omp');
121
+ console.log(' Example: ukit install --tools=claude,codex,omp');
122
122
  console.log(' Use --tools=none to disable all optional adapters');
123
123
  console.log(' --with-codegraph Enable CodeGraph MCP integration in CLAUDE.md');
124
124
  console.log(' Adds routing rules that prefer codegraph_* tools for');