@ngockhoale/ukit 2.7.0 → 2.7.2

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 (50) hide show
  1. package/CHANGELOG.md +42 -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 +15 -7
  11. package/src/cli/index.js +2 -2
  12. package/src/core/ompConfigMerge.js +36 -7
  13. package/src/core/opencodeSteer.js +37 -0
  14. package/src/core/projectImportant.js +14 -8
  15. package/src/core/runInstallPipeline.js +10 -0
  16. package/src/core/runtimeConfig.js +41 -7
  17. package/src/core/status.js +6 -4
  18. package/src/manifest/selectItems.js +0 -1
  19. package/templates/.claude/agents/bug-debugger.md +1 -1
  20. package/templates/.claude/agents/feature-implementer.md +1 -1
  21. package/templates/.claude/agents/ukit-small-task-maintainer.md +2 -2
  22. package/templates/.claude/commands/ukit/handoff-create.md +5 -5
  23. package/templates/.claude/commands/ukit/handoff-fullstack.md +6 -6
  24. package/templates/.claude/commands/ukit/handoff-implement.md +3 -3
  25. package/templates/.claude/commands/ukit/handoff-review.md +3 -3
  26. package/templates/.claude/skills/update-status/SKILL.md +1 -1
  27. package/templates/.claude/ukit/index/route-task.mjs +7 -2
  28. package/templates/.codex/README.md +1 -1
  29. package/templates/.codex/settings.json +0 -1
  30. package/templates/.omp/README.md +11 -7
  31. package/templates/.omp/RULES.md +7 -6
  32. package/templates/.omp/agents/bug-debugger.md +2 -2
  33. package/templates/.omp/agents/code-reviewer.md +1 -1
  34. package/templates/.omp/agents/feature-implementer.md +2 -2
  35. package/templates/.omp/agents/handoff-planner.md +1 -1
  36. package/templates/.omp/agents/ukit-small-task-maintainer.md +4 -4
  37. package/templates/.omp/config.yml +10 -4
  38. package/templates/AGENTS.md +5 -14
  39. package/templates/CLAUDE.md +2 -4
  40. package/templates/adapter-presets/claude/settings.local.json +0 -1
  41. package/templates/docs/AI_HANDOFF/INDEX.md +1 -1
  42. package/templates/docs/AI_HANDOFF/RULES.md +2 -2
  43. package/templates/docs/PROJECT.md +1 -1
  44. package/templates/docs/UKIT_INTERNALS.md +2 -2
  45. package/templates/instructions/core.md +2 -4
  46. package/templates/instructions/layout.yaml +2 -4
  47. package/templates/instructions/overlays/agents.md +3 -10
  48. package/templates/instructions/overlays/omp-rules.md +7 -6
  49. package/templates/ukit/storage/config.json +7 -13
  50. package/templates/adapter-presets/opencode/opencode.template.json +0 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,48 @@
2
2
 
3
3
  All notable changes to UKit are documented here.
4
4
 
5
+ ## 2.7.2 - 2026-09-20
6
+
7
+ OpenCode adapter removal — cycle C37 (TASK-001..004).
8
+
9
+ - Removed the OpenCode adapter lane end to end: adapter registry key +
10
+ aliases, `templates/adapter-presets/opencode/`, host-capability matrix
11
+ entry, doc-benchmark `opencode.yaml` fixture + BMK-09, manifest/manifest
12
+ wiring, `--tools` help listing, and root `opencode.json` dogfood file.
13
+ - Steer-not-break detection: a leftover `opencode.json` is a user file —
14
+ install/status/doctor now detect it via `src/core/opencodeSteer.js` and
15
+ print one canonical warning advising manual removal (`rm opencode.json`);
16
+ the file is never written, merged, pruned, or auto-deleted.
17
+ - Legacy tolerance: stale `agent`/`agents`/`compact.agentContext.targets`
18
+ values naming `opencode` are normalized away with a warning instead of
19
+ hard-failing; `--tools=opencode` / `--adapter opencode` warn + ignore /
20
+ fall back with exit 0; uninstall still removes a tracked `opencode.json`
21
+ on explicit user request; `opencode.json` stays in gitignore advice.
22
+ - Packaging: `package.json` description/keywords/`files` and `.npmignore`
23
+ no longer reference the deleted preset; `packageArtifact.test.js` asserts
24
+ the tarball ships zero `adapter-presets/opencode` entries.
25
+
26
+ ## 2.7.1 - 2026-09-20
27
+
28
+ omp native-tier steering — cycle C36 (TASK-001..005).
29
+
30
+ - `templates/.omp/config.yml` modelRoles now pins omp's native cost tiers
31
+ `smol`/`default`/`slow` (→ `unic-lite`/`unic-code`/`unic-smart`) so omp's
32
+ built-in tier steering resolves UKit's gateway aliases directly; `vision`
33
+ stays `unic-vision` (capability lane — unic-code/unic-smart cannot read
34
+ images on this gateway).
35
+ - Back-compat: `lite`/`code`/`smart` kept as independent alias keys (not
36
+ references) resolving to the same unic-* values — a maintainer overriding
37
+ a cost tier must edit the matching alias too.
38
+ - `.omp/agents/*.md` rebound from direct unic-* names to `@smol`/`@default`/
39
+ `@slow`/`@vision` frontmatter so agents follow the modelRoles map.
40
+ - Migration: `mergeOmpConfig` merges `modelRoles` per key (user-pinned keys
41
+ preserved, newly added template keys recorded via `addedModelRoleKeys`)
42
+ instead of clobbering the whole map.
43
+ - `orchestration.modelTiers.*.ompRole` added to the runtime config template
44
+ + `runtimeConfig.js` so each tier declares which modelRoles key it binds
45
+ (lite→smol, code→default, smart→slow, vision→vision).
46
+
5
47
  ## 2.7.0 - 2026-09-20
6
48
 
7
49
  OMP fully autonomous / unattended mode — cycle C35 (TASK-008..014).
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # UKit
2
2
 
3
- UKit installs a project-local AI workspace (`.claude/`, adapters, docs, and index helpers) so your team can work with Claude Code, OpenAI Codex, OpenCode, and omp (Oh My Pi) from the same repo setup.
3
+ UKit installs a project-local AI workspace (`.claude/`, adapters, docs, and index helpers) so your team can work with Claude Code, OpenAI Codex, and omp (Oh My Pi) from the same repo setup.
4
4
 
5
5
  ## Install
6
6
 
@@ -39,7 +39,6 @@ Fill in the generated docs baseline (`docs/PROJECT.md`, `docs/MEMORY.md`, `docs/
39
39
 
40
40
  **Adapters**
41
41
  - `.codex/settings.json` + `.codex/settings.local.json` — Codex config
42
- - `opencode.json` — OpenCode project config with UKit-safe guardrails
43
42
  - `.omp/AGENTS.md` — thin `@../AGENTS.md` import so omp reads the shared instructions
44
43
  - `.omp/RULES.md` — sticky hard rules for omp sessions
45
44
  - `.omp/config.yml` — omp model roles, tool approval, and bash patterns
@@ -75,7 +74,6 @@ When long sessions approach the compact threshold, UKit compacts only safe-zone
75
74
  |------|-------------|--------|--------|
76
75
  | Claude Code | `CLAUDE.md` | `.claude/skills/` (canonical) | `.claude/settings.json` |
77
76
  | OpenAI Codex | `AGENTS.md` | `.claude/skills/` (referenced) | `.codex/settings.json`, `.codex/settings.local.json`, `.codex/README.md` |
78
- | OpenCode | `AGENTS.md` | `.claude/skills/` (native Claude-compatible support) | `opencode.json` |
79
77
  | omp (Oh My Pi) | `.omp/AGENTS.md` (imports `AGENTS.md`) | `.claude/skills/` (read via omp's claude provider — no symlink) | `.omp/config.yml`, `.omp/RULES.md`, `.omp/README.md` |
80
78
 
81
79
  All supported tools share the same SKILL.md ecosystem so the workspace is authored once and reused everywhere.
@@ -27,16 +27,6 @@ hosts:
27
27
  owner-file-injection: { state: observed, note: 'AGENTS.md rendered for codex; parity asserted only at artifact level' }
28
28
  settings-env: { state: tested, evidence: tests/integration/installPipeline.test.js }
29
29
  context-files: { state: tested, evidence: tests/integration/installPipeline.test.js }
30
- opencode:
31
- skills: { state: observed, note: 'reads .claude/skills via discovery; asserted indirectly by artifact tests' }
32
- subagents: { state: unknown }
33
- hooks: { state: unknown }
34
- session-start-injection: { state: unknown }
35
- stop-gate: { state: unknown }
36
- slash-commands: { state: observed, note: 'ukit-* helper entrypoints declared in opencode.json' }
37
- owner-file-injection: { state: observed, note: 'AGENTS.md loaded at session start; no injection test' }
38
- settings-env: { state: tested, evidence: tests/integration/installPipeline.test.js }
39
- context-files: { state: observed, note: 'AGENTS.md asserted by install pipeline; no opencode-specific parse test' }
40
30
  omp:
41
31
  skills: { state: observed, note: 'omp reads .claude/skills via claude discovery provider' }
42
32
  subagents: { state: tested, evidence: tests/consistency/ompAgentParity.test.js }
@@ -412,22 +412,6 @@ rules:
412
412
  When the same file or symbol fails debugLoopThreshold times in one
413
413
  session, route the next attempt one tier higher, capped at smart.
414
414
  marker_in: [templates/CLAUDE.md, templates/AGENTS.md, CLAUDE.md, AGENTS.md]
415
- - id: HOST-OC-01
416
- title: OpenCode must explicitly read the triggered SKILL.md
417
- kind: critical
418
- statement: >-
419
- OpenCode reads AGENTS.md at session start only and does not auto-load
420
- skills; the model must explicitly read the triggered SKILL.md.
421
- marker_in: [templates/AGENTS.md, AGENTS.md]
422
- anchor: 'read the triggered SKILL.md'
423
- - id: HOST-OWN-01
424
- title: Codex/OpenCode read the root owner instructions
425
- kind: critical
426
- statement: >-
427
- When running in Codex or OpenCode, read and follow the root
428
- PROJECT_IMPORTANT.md before doing project work.
429
- marker_in: [templates/AGENTS.md, AGENTS.md]
430
- anchor: 'read and follow the root'
431
415
  - id: DURA-01
432
416
  title: DuraOne skill activates only when installed
433
417
  kind: supporting
@@ -124,8 +124,8 @@ items:
124
124
  - docs-project
125
125
  - docs-memory
126
126
  mergeStrategy: overwrite_with_backup
127
- # Same set as root-claude-md: AGENTS.md is the same body with a different H1 plus one
128
- # OpenCode-only section, so it interpolates the same variables. Kept in sync by
127
+ # Same set as root-claude-md: AGENTS.md is the same body with a different H1, so it
128
+ # interpolates the same variables. Kept in sync by
129
129
  # tests/consistency/contextDocsParity.test.js.
130
130
  variables:
131
131
  - project.name
@@ -1805,17 +1805,3 @@ items:
1805
1805
  packs:
1806
1806
  - core
1807
1807
 
1808
- # --- OpenCode ---
1809
-
1810
- - id: multi-opencode-config
1811
- type: config
1812
- sourceTemplate: adapter-presets/opencode/opencode.template.json
1813
- targetPath: opencode.json
1814
- requires: []
1815
- mergeStrategy: overwrite_with_backup
1816
- variables:
1817
- - project.name
1818
- - runtime.packageManager
1819
- enabledByDefault: true
1820
- packs:
1821
- - core
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "2.7.0",
4
- "description": "Install/update an index-first AI workspace for Claude Code, OpenAI Codex, OpenCode, and omp (Oh My Pi).",
3
+ "version": "2.7.2",
4
+ "description": "Install/update an index-first AI workspace for Claude Code, OpenAI Codex and omp (Oh My Pi).",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "keywords": [
@@ -9,7 +9,6 @@
9
9
  "claude",
10
10
  "codex",
11
11
  "claude-code",
12
- "opencode",
13
12
  "omp",
14
13
  "oh-my-pi",
15
14
  "skills",
@@ -37,8 +36,7 @@
37
36
  "templates/CLAUDE.md",
38
37
  "templates/AGENTS.md",
39
38
  "templates/.gitignore",
40
- "manifests/",
41
- "templates/adapter-presets/opencode/opencode.template.json"
39
+ "manifests/"
42
40
  ],
43
41
  "publishConfig": {
44
42
  "access": "public"
@@ -3,8 +3,8 @@
3
3
  * audit-perf.mjs — TASK-233 (SPEC §2 FR-001) performance audit generator.
4
4
  *
5
5
  * Measures every SPEC §2 area from four independent sources and writes
6
- * `docs/AI_REPORT/perf-findings.json` (machine-readable, consumed by TASK-234,
7
- * TASK-235 and TASK-236) plus `docs/AI_REPORT/perf-measure.md` (raw appendix).
6
+ * `scripts/perf/perf-findings.json` (machine-readable, consumed by TASK-234,
7
+ * TASK-235 and TASK-236) plus `perf-measure.md` next to it (raw appendix).
8
8
  * Running this again with the same arguments IS the "measure it the same way
9
9
  * both times" requirement from SPEC §5 — TASK-236 re-runs it for before/after.
10
10
  *
@@ -735,7 +735,7 @@ function renderMeasureDoc(context) {
735
735
  lines.push('```bash');
736
736
  lines.push('node scripts/perf/audit-perf.mjs \\');
737
737
  lines.push(` --telemetry-dir "${telemetry.dir || path.join(root, '.ukit', 'storage', 'cache', 'hook-latency')}" \\`);
738
- lines.push(` --out "${root}/docs/AI_REPORT/perf-findings.json"`);
738
+ lines.push(` --out "${root}/scripts/perf/perf-findings.json"`);
739
739
  lines.push('node --test tests/handoff/c33/perfFindings.test.js');
740
740
  lines.push('```');
741
741
  lines.push('');
@@ -848,7 +848,7 @@ function main() {
848
848
  const options = parseArgs(process.argv.slice(2));
849
849
  const root = options.root;
850
850
  const telemetryDir = options.telemetryDir || path.join(root, '.ukit', 'storage', 'cache', 'hook-latency');
851
- const outFile = options.out || path.join(root, 'docs', 'AI_REPORT', 'perf-findings.json');
851
+ const outFile = options.out || path.join(root, 'scripts', 'perf', 'perf-findings.json');
852
852
  const measureFile = path.join(path.dirname(outFile), 'perf-measure.md');
853
853
 
854
854
  const telemetry = loadTelemetry(telemetryDir);
@@ -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
+ }