@miller-tech/uap 2.21.2 → 2.36.1

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 (328) hide show
  1. package/README.md +22 -10
  2. package/config/model-profiles/qwen38-coder390.json +46 -0
  3. package/config/model-profiles/qwen38.json +3 -2
  4. package/config/model-registry.json +110 -0
  5. package/dist/.tsbuildinfo +1 -1
  6. package/dist/benchmarks/paired/types.d.ts +2 -2
  7. package/dist/bin/cli.js +315 -20
  8. package/dist/bin/cli.js.map +1 -1
  9. package/dist/cli/attention.d.ts +6 -0
  10. package/dist/cli/attention.d.ts.map +1 -0
  11. package/dist/cli/attention.js +115 -0
  12. package/dist/cli/attention.js.map +1 -0
  13. package/dist/cli/bench-report.d.ts +82 -0
  14. package/dist/cli/bench-report.d.ts.map +1 -0
  15. package/dist/cli/bench-report.js +237 -0
  16. package/dist/cli/bench-report.js.map +1 -0
  17. package/dist/cli/clients.d.ts +6 -0
  18. package/dist/cli/clients.d.ts.map +1 -0
  19. package/dist/cli/clients.js +142 -0
  20. package/dist/cli/clients.js.map +1 -0
  21. package/dist/cli/dashboard-grants.d.ts +26 -0
  22. package/dist/cli/dashboard-grants.d.ts.map +1 -0
  23. package/dist/cli/dashboard-grants.js +69 -0
  24. package/dist/cli/dashboard-grants.js.map +1 -0
  25. package/dist/cli/dashboard-pace.d.ts +31 -0
  26. package/dist/cli/dashboard-pace.d.ts.map +1 -0
  27. package/dist/cli/dashboard-pace.js +42 -0
  28. package/dist/cli/dashboard-pace.js.map +1 -0
  29. package/dist/cli/dashboard-session.d.ts +25 -0
  30. package/dist/cli/dashboard-session.d.ts.map +1 -0
  31. package/dist/cli/dashboard-session.js +814 -0
  32. package/dist/cli/dashboard-session.js.map +1 -0
  33. package/dist/cli/dashboard-types.d.ts +18 -0
  34. package/dist/cli/dashboard-types.d.ts.map +1 -0
  35. package/dist/cli/dashboard-types.js +9 -0
  36. package/dist/cli/dashboard-types.js.map +1 -0
  37. package/dist/cli/dashboard.d.ts +2 -18
  38. package/dist/cli/dashboard.d.ts.map +1 -1
  39. package/dist/cli/dashboard.js +43 -793
  40. package/dist/cli/dashboard.js.map +1 -1
  41. package/dist/cli/deliver.d.ts +35 -0
  42. package/dist/cli/deliver.d.ts.map +1 -1
  43. package/dist/cli/deliver.js +93 -9
  44. package/dist/cli/deliver.js.map +1 -1
  45. package/dist/cli/diff.d.ts +85 -0
  46. package/dist/cli/diff.d.ts.map +1 -0
  47. package/dist/cli/diff.js +293 -0
  48. package/dist/cli/diff.js.map +1 -0
  49. package/dist/cli/eta.d.ts +35 -0
  50. package/dist/cli/eta.d.ts.map +1 -0
  51. package/dist/cli/eta.js +101 -0
  52. package/dist/cli/eta.js.map +1 -0
  53. package/dist/cli/handsfree.d.ts +7 -0
  54. package/dist/cli/handsfree.d.ts.map +1 -1
  55. package/dist/cli/handsfree.js +27 -0
  56. package/dist/cli/handsfree.js.map +1 -1
  57. package/dist/cli/hooks.d.ts.map +1 -1
  58. package/dist/cli/hooks.js +126 -5
  59. package/dist/cli/hooks.js.map +1 -1
  60. package/dist/cli/loops.d.ts +24 -5
  61. package/dist/cli/loops.d.ts.map +1 -1
  62. package/dist/cli/loops.js +92 -12
  63. package/dist/cli/loops.js.map +1 -1
  64. package/dist/cli/models.d.ts +62 -0
  65. package/dist/cli/models.d.ts.map +1 -0
  66. package/dist/cli/models.js +567 -0
  67. package/dist/cli/models.js.map +1 -0
  68. package/dist/cli/plan.d.ts +9 -0
  69. package/dist/cli/plan.d.ts.map +1 -1
  70. package/dist/cli/plan.js +57 -4
  71. package/dist/cli/plan.js.map +1 -1
  72. package/dist/cli/proxy-relay.d.ts +55 -0
  73. package/dist/cli/proxy-relay.d.ts.map +1 -0
  74. package/dist/cli/proxy-relay.js +116 -0
  75. package/dist/cli/proxy-relay.js.map +1 -0
  76. package/dist/cli/proxy.d.ts +4 -0
  77. package/dist/cli/proxy.d.ts.map +1 -1
  78. package/dist/cli/proxy.js +26 -1
  79. package/dist/cli/proxy.js.map +1 -1
  80. package/dist/cli/sandbox.d.ts +101 -4
  81. package/dist/cli/sandbox.d.ts.map +1 -1
  82. package/dist/cli/sandbox.js +393 -44
  83. package/dist/cli/sandbox.js.map +1 -1
  84. package/dist/cli/self.d.ts +13 -0
  85. package/dist/cli/self.d.ts.map +1 -0
  86. package/dist/cli/self.js +94 -0
  87. package/dist/cli/self.js.map +1 -0
  88. package/dist/cli/systemd-services.d.ts +2 -8
  89. package/dist/cli/systemd-services.d.ts.map +1 -1
  90. package/dist/cli/systemd-services.js +5 -20
  91. package/dist/cli/systemd-services.js.map +1 -1
  92. package/dist/cli/task.js +1 -1
  93. package/dist/cli/task.js.map +1 -1
  94. package/dist/cli/wizard-config.d.ts.map +1 -1
  95. package/dist/cli/wizard-config.js +31 -2
  96. package/dist/cli/wizard-config.js.map +1 -1
  97. package/dist/config/settings-registry.d.ts +1 -1
  98. package/dist/config/settings-registry.d.ts.map +1 -1
  99. package/dist/config/settings-registry.js +108 -0
  100. package/dist/config/settings-registry.js.map +1 -1
  101. package/dist/coordination/capability-router.js +2 -2
  102. package/dist/coordination/capability-router.js.map +1 -1
  103. package/dist/coordination/database.d.ts +13 -0
  104. package/dist/coordination/database.d.ts.map +1 -1
  105. package/dist/coordination/database.js +63 -7
  106. package/dist/coordination/database.js.map +1 -1
  107. package/dist/coordination/eta-inject.d.ts +32 -0
  108. package/dist/coordination/eta-inject.d.ts.map +1 -0
  109. package/dist/coordination/eta-inject.js +64 -0
  110. package/dist/coordination/eta-inject.js.map +1 -0
  111. package/dist/coordination/reactor.d.ts.map +1 -1
  112. package/dist/coordination/reactor.js +14 -0
  113. package/dist/coordination/reactor.js.map +1 -1
  114. package/dist/coordination/service.d.ts +18 -8
  115. package/dist/coordination/service.d.ts.map +1 -1
  116. package/dist/coordination/service.js +38 -28
  117. package/dist/coordination/service.js.map +1 -1
  118. package/dist/dashboard/client-registry.d.ts +163 -0
  119. package/dist/dashboard/client-registry.d.ts.map +1 -0
  120. package/dist/dashboard/client-registry.js +549 -0
  121. package/dist/dashboard/client-registry.js.map +1 -0
  122. package/dist/dashboard/controls.d.ts.map +1 -1
  123. package/dist/dashboard/controls.js +33 -4
  124. package/dist/dashboard/controls.js.map +1 -1
  125. package/dist/dashboard/data-seeder.d.ts +10 -7
  126. package/dist/dashboard/data-seeder.d.ts.map +1 -1
  127. package/dist/dashboard/data-seeder.js +31 -75
  128. package/dist/dashboard/data-seeder.js.map +1 -1
  129. package/dist/dashboard/data-service.d.ts +9 -0
  130. package/dist/dashboard/data-service.d.ts.map +1 -1
  131. package/dist/dashboard/data-service.js +32 -3
  132. package/dist/dashboard/data-service.js.map +1 -1
  133. package/dist/dashboard/placement-routes.d.ts +119 -0
  134. package/dist/dashboard/placement-routes.d.ts.map +1 -0
  135. package/dist/dashboard/placement-routes.js +331 -0
  136. package/dist/dashboard/placement-routes.js.map +1 -0
  137. package/dist/dashboard/server.d.ts.map +1 -1
  138. package/dist/dashboard/server.js +408 -2
  139. package/dist/dashboard/server.js.map +1 -1
  140. package/dist/delivery/attention.d.ts +130 -0
  141. package/dist/delivery/attention.d.ts.map +1 -0
  142. package/dist/delivery/attention.js +341 -0
  143. package/dist/delivery/attention.js.map +1 -0
  144. package/dist/delivery/decompose.d.ts +18 -0
  145. package/dist/delivery/decompose.d.ts.map +1 -1
  146. package/dist/delivery/decompose.js +22 -1
  147. package/dist/delivery/decompose.js.map +1 -1
  148. package/dist/delivery/eta-estimate.d.ts +111 -0
  149. package/dist/delivery/eta-estimate.d.ts.map +1 -0
  150. package/dist/delivery/eta-estimate.js +171 -0
  151. package/dist/delivery/eta-estimate.js.map +1 -0
  152. package/dist/delivery/eta.d.ts +23 -0
  153. package/dist/delivery/eta.d.ts.map +1 -0
  154. package/dist/delivery/eta.js +59 -0
  155. package/dist/delivery/eta.js.map +1 -0
  156. package/dist/delivery/orchestrated-mission.d.ts +10 -0
  157. package/dist/delivery/orchestrated-mission.d.ts.map +1 -1
  158. package/dist/delivery/orchestrated-mission.js +15 -1
  159. package/dist/delivery/orchestrated-mission.js.map +1 -1
  160. package/dist/delivery/plan-check.d.ts +7 -1
  161. package/dist/delivery/plan-check.d.ts.map +1 -1
  162. package/dist/delivery/plan-check.js +23 -2
  163. package/dist/delivery/plan-check.js.map +1 -1
  164. package/dist/delivery/run-state.d.ts +1 -1
  165. package/dist/delivery/run-state.d.ts.map +1 -1
  166. package/dist/delivery/run-state.js +8 -1
  167. package/dist/delivery/run-state.js.map +1 -1
  168. package/dist/delivery/task-orchestrator.d.ts +8 -3
  169. package/dist/delivery/task-orchestrator.d.ts.map +1 -1
  170. package/dist/delivery/task-orchestrator.js +75 -43
  171. package/dist/delivery/task-orchestrator.js.map +1 -1
  172. package/dist/delivery/task-sync.d.ts +21 -0
  173. package/dist/delivery/task-sync.d.ts.map +1 -1
  174. package/dist/delivery/task-sync.js +44 -8
  175. package/dist/delivery/task-sync.js.map +1 -1
  176. package/dist/delivery/task-workspace.d.ts +3 -3
  177. package/dist/delivery/task-workspace.js +3 -3
  178. package/dist/generators/self-card.d.ts +65 -0
  179. package/dist/generators/self-card.d.ts.map +1 -0
  180. package/dist/generators/self-card.js +256 -0
  181. package/dist/generators/self-card.js.map +1 -0
  182. package/dist/inference/probe.js +1 -1
  183. package/dist/inference/probe.js.map +1 -1
  184. package/dist/memory/prepopulate.d.ts.map +1 -1
  185. package/dist/memory/prepopulate.js +2 -0
  186. package/dist/memory/prepopulate.js.map +1 -1
  187. package/dist/models/analytics.d.ts +32 -0
  188. package/dist/models/analytics.d.ts.map +1 -1
  189. package/dist/models/analytics.js +68 -5
  190. package/dist/models/analytics.js.map +1 -1
  191. package/dist/models/plan-readability.d.ts +55 -0
  192. package/dist/models/plan-readability.d.ts.map +1 -0
  193. package/dist/models/plan-readability.js +339 -0
  194. package/dist/models/plan-readability.js.map +1 -0
  195. package/dist/placement/admission.d.ts +59 -0
  196. package/dist/placement/admission.d.ts.map +1 -0
  197. package/dist/placement/admission.js +197 -0
  198. package/dist/placement/admission.js.map +1 -0
  199. package/dist/placement/auto.d.ts +44 -0
  200. package/dist/placement/auto.d.ts.map +1 -0
  201. package/dist/placement/auto.js +212 -0
  202. package/dist/placement/auto.js.map +1 -0
  203. package/dist/placement/enforce.d.ts +128 -0
  204. package/dist/placement/enforce.d.ts.map +1 -0
  205. package/dist/placement/enforce.js +655 -0
  206. package/dist/placement/enforce.js.map +1 -0
  207. package/dist/placement/idle.d.ts +50 -0
  208. package/dist/placement/idle.d.ts.map +1 -0
  209. package/dist/placement/idle.js +213 -0
  210. package/dist/placement/idle.js.map +1 -0
  211. package/dist/placement/ledger.d.ts +323 -0
  212. package/dist/placement/ledger.d.ts.map +1 -0
  213. package/dist/placement/ledger.js +263 -0
  214. package/dist/placement/ledger.js.map +1 -0
  215. package/dist/placement/preview.d.ts +51 -0
  216. package/dist/placement/preview.d.ts.map +1 -0
  217. package/dist/placement/preview.js +61 -0
  218. package/dist/placement/preview.js.map +1 -0
  219. package/dist/placement/probes.d.ts +47 -0
  220. package/dist/placement/probes.d.ts.map +1 -0
  221. package/dist/placement/probes.js +207 -0
  222. package/dist/placement/probes.js.map +1 -0
  223. package/dist/placement/proxy-env.d.ts +21 -0
  224. package/dist/placement/proxy-env.d.ts.map +1 -0
  225. package/dist/placement/proxy-env.js +42 -0
  226. package/dist/placement/proxy-env.js.map +1 -0
  227. package/dist/placement/registry.d.ts +1038 -0
  228. package/dist/placement/registry.d.ts.map +1 -0
  229. package/dist/placement/registry.js +274 -0
  230. package/dist/placement/registry.js.map +1 -0
  231. package/dist/policies/liveness.d.ts +6 -8
  232. package/dist/policies/liveness.d.ts.map +1 -1
  233. package/dist/policies/liveness.js +6 -25
  234. package/dist/policies/liveness.js.map +1 -1
  235. package/dist/policies/sandbox-anchor.d.ts +91 -0
  236. package/dist/policies/sandbox-anchor.d.ts.map +1 -0
  237. package/dist/policies/sandbox-anchor.js +187 -0
  238. package/dist/policies/sandbox-anchor.js.map +1 -0
  239. package/dist/policies/sandbox-grants.d.ts +131 -0
  240. package/dist/policies/sandbox-grants.d.ts.map +1 -0
  241. package/dist/policies/sandbox-grants.js +562 -0
  242. package/dist/policies/sandbox-grants.js.map +1 -0
  243. package/dist/state/manifest.d.ts +4 -2
  244. package/dist/state/manifest.d.ts.map +1 -1
  245. package/dist/state/manifest.js +32 -16
  246. package/dist/state/manifest.js.map +1 -1
  247. package/dist/utils/checkout-root.d.ts +2 -0
  248. package/dist/utils/checkout-root.d.ts.map +1 -0
  249. package/dist/utils/checkout-root.js +43 -0
  250. package/dist/utils/checkout-root.js.map +1 -0
  251. package/dist/utils/resolve-uap-bin.d.ts +5 -0
  252. package/dist/utils/resolve-uap-bin.d.ts.map +1 -0
  253. package/dist/utils/resolve-uap-bin.js +33 -0
  254. package/dist/utils/resolve-uap-bin.js.map +1 -0
  255. package/dist/utils/terminal-safe.d.ts +12 -0
  256. package/dist/utils/terminal-safe.d.ts.map +1 -0
  257. package/dist/utils/terminal-safe.js +53 -0
  258. package/dist/utils/terminal-safe.js.map +1 -0
  259. package/docs/ATTRIBUTION.md +1 -0
  260. package/docs/DASHBOARD_UPLIFT_SPEC.md +8 -8
  261. package/docs/INDEX.md +6 -5
  262. package/docs/architecture/OVERVIEW.md +15 -3
  263. package/docs/architecture/PROTOCOL.md +7 -1
  264. package/docs/architecture/adr/0006-operator-directed-model-placement.md +86 -0
  265. package/docs/architecture/adr/0007-auto-displacement-allowlist-as-standing-consent.md +79 -0
  266. package/docs/architecture/adr/0008-fleet-clients-link-out.md +92 -0
  267. package/docs/architecture/adr/0009-completion-triggered-dispatch-and-per-leaf-model-dials.md +71 -0
  268. package/docs/architecture/adr/0010-attention-queue-and-afk-auto-decider.md +159 -0
  269. package/docs/architecture/adr/0011-sandbox-grants-and-network-egress-boundary.md +219 -0
  270. package/docs/architecture/adr/0012-operator-legibility-surfaces.md +175 -0
  271. package/docs/getting-started/INSTALLATION.md +1 -1
  272. package/docs/guides/AUTOMATIC_FEATURES.md +8 -0
  273. package/docs/guides/LOCAL_MODELS.md +1 -1
  274. package/docs/guides/PROXY.md +2 -1
  275. package/docs/guides/SANDBOX.md +91 -4
  276. package/docs/integrations/MCP_ROUTER.md +1 -1
  277. package/docs/plans/model-performance-proof-options.md +176 -0
  278. package/docs/reference/API.md +1 -1
  279. package/docs/reference/CLI.md +169 -4
  280. package/docs/reference/COMPARISON.md +1 -1
  281. package/docs/reference/CONFIGURATION.md +12 -3
  282. package/docs/reference/CONFIGURATION_REFERENCE.md +269 -14
  283. package/docs/reference/DATABASE_SCHEMA.md +1 -1
  284. package/docs/reference/FEATURES.md +15 -3
  285. package/docs/reference/PATTERNS.md +1 -1
  286. package/docs/reference/PLATFORMS.md +14 -1
  287. package/docs/specs/operator-model-placement.md +1314 -0
  288. package/docs/specs/operator-model-placement.review.md +339 -0
  289. package/package.json +2 -2
  290. package/scripts/maintenance/publish-npm.sh +23 -11
  291. package/scripts/version-bump.sh +6 -51
  292. package/src/policies/enforcers/__pycache__/_common.cpython-311.pyc +0 -0
  293. package/src/policies/enforcers/__pycache__/expert_review_required.cpython-311.pyc +0 -0
  294. package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-311.pyc +0 -0
  295. package/src/policies/schemas/policies/mandatory-testing-deployment.md +2 -5
  296. package/templates/hooks/__pycache__/deliver_autoroute.cpython-311.pyc +0 -0
  297. package/templates/hooks/session-start.sh +44 -0
  298. package/tools/agents/UAP/cli.py +14 -3
  299. package/tools/agents/UAP/paths.py +50 -0
  300. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-311.pyc +0 -0
  301. package/tools/agents/scripts/anthropic_proxy.py +1386 -99
  302. package/tools/agents/scripts/toolcall_path_normalizer.py +46 -1
  303. package/tools/agents/tests/test_anthropic_proxy_streaming.py +29 -6
  304. package/tools/agents/tests/test_doubling_break.py +64 -5
  305. package/tools/agents/tests/test_error_loop_break.py +10 -4
  306. package/tools/agents/tests/test_error_loop_ignores_correctives.py +13 -2
  307. package/tools/agents/tests/test_lockstep_escalation.py +280 -9
  308. package/tools/agents/tests/test_path_containment.py +94 -0
  309. package/tools/agents/tests/test_proxy_unix_socket.py +162 -0
  310. package/tools/agents/tests/test_signature_quality.py +349 -0
  311. package/tools/agents/tests/test_uap_compliance.py +11 -1
  312. package/web/dash/core.js +1 -0
  313. package/web/dash/tab-clients.js +196 -0
  314. package/web/dash/tab-models.js +387 -8
  315. package/web/dash/tab-policies.js +87 -4
  316. package/web/dash/tabs.js +77 -73
  317. package/web/dashboard.html +6 -0
  318. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  319. package/src/policies/enforcers/__pycache__/expert_review_required.cpython-312.pyc +0 -0
  320. package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-312.pyc +0 -0
  321. package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
  322. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
  323. package/web/dash/tab-agents.js +0 -21
  324. package/web/dash/tab-deliver.js +0 -12
  325. package/web/dash/tab-memory.js +0 -12
  326. package/web/dash/tab-orchestration.js +0 -21
  327. package/web/dash/tab-overview.js +0 -21
  328. package/web/dash/tab-tasks.js +0 -21
@@ -124,18 +124,18 @@ export declare const MetricVectorSchema: z.ZodObject<{
124
124
  error: z.ZodNullable<z.ZodString>;
125
125
  }, "strip", z.ZodTypeAny, {
126
126
  error: string | null;
127
+ turns: number | null;
127
128
  tokens: number | null;
128
129
  latencyMs: number;
129
- turns: number | null;
130
130
  toolCalls: number | null;
131
131
  correct: boolean;
132
132
  costUsd: number | null;
133
133
  wellFormed: boolean | null;
134
134
  }, {
135
135
  error: string | null;
136
+ turns: number | null;
136
137
  tokens: number | null;
137
138
  latencyMs: number;
138
- turns: number | null;
139
139
  toolCalls: number | null;
140
140
  correct: boolean;
141
141
  costUsd: number | null;
package/dist/bin/cli.js CHANGED
@@ -3,7 +3,7 @@ import { Command, Option } from 'commander';
3
3
  import chalk from 'chalk';
4
4
  import { registerConfigCommands } from '../cli/config-command.js';
5
5
  import { existsSync, readFileSync } from 'fs';
6
- import { execSync, execFileSync } from 'child_process';
6
+ import { execFileSync } from 'child_process';
7
7
  import { fileURLToPath } from 'url';
8
8
  import { dirname, join } from 'path';
9
9
  // Lazy import helpers - commands are loaded on-demand to reduce startup time (~10x faster --help)
@@ -25,6 +25,7 @@ const lazy = {
25
25
  model: () => import('../cli/model.js').then((m) => m.registerModelCommands),
26
26
  mcpRouter: () => import('../cli/mcp-router.js').then((m) => m.mcpRouterCommand),
27
27
  dashboard: () => import('../cli/dashboard.js').then((m) => m.dashboardCommand),
28
+ clients: () => import('../cli/clients.js').then((m) => m.clientsCommand),
28
29
  hooks: () => import('../cli/hooks.js'),
29
30
  patterns: () => import('../cli/patterns.js').then((m) => m.patternsCommand),
30
31
  skill: () => import('../cli/skill.js').then((m) => m.skillCommand),
@@ -44,8 +45,11 @@ const lazy = {
44
45
  interaction: () => import('../cli/interaction.js').then((m) => m.interactionCommand),
45
46
  orchestratorToggle: () => import('../cli/orchestrator-toggle.js').then((m) => m.orchestratorToggleCommand),
46
47
  handsfree: () => import('../cli/handsfree.js').then((m) => m.handsfreeCommand),
48
+ eta: () => import('../cli/eta.js').then((m) => m.etaCommand),
49
+ diff: () => import('../cli/diff.js').then((m) => m.diffCommand),
47
50
  proxy: () => import('../cli/proxy.js').then((m) => m.proxyCommand),
48
51
  benchPaired: () => import('../cli/bench.js').then((m) => m.benchPairedCommand),
52
+ benchReport: () => import('../cli/bench-report.js').then((m) => m.benchReportCommand),
49
53
  tune: () => import('../cli/self-tuning.js').then((m) => m.tuneCommand),
50
54
  sandbox: () => import('../cli/sandbox.js').then((m) => m.sandboxCommand),
51
55
  design: () => import('../cli/design.js').then((m) => m.designCommand),
@@ -57,11 +61,14 @@ const lazy = {
57
61
  doctor: () => import('../cli/doctor.js').then((m) => m.doctorCommand),
58
62
  loops: () => import('../cli/loops.js').then((m) => m.loopsCommand),
59
63
  inference: () => import('../cli/inference.js').then((m) => m.inferenceHealthCommand),
64
+ models: () => import('../cli/models.js').then((m) => m.modelsCommand),
60
65
  review: () => import('../cli/review.js').then((m) => m.reviewCommand),
61
66
  principles: () => import('../cli/principles.js').then((m) => m.principlesCommand),
62
67
  challenge: () => import('../cli/challenge.js').then((m) => m.challengeCommand),
63
68
  fidelity: () => import('../cli/fidelity.js').then((m) => m.fidelityCommand),
64
69
  plan: () => import('../cli/plan.js').then((m) => m.planCommand),
70
+ self: () => import('../cli/self.js').then((m) => m.selfCommand),
71
+ attention: () => import('../cli/attention.js').then((m) => m.attentionCommand),
65
72
  };
66
73
  // Read version from package.json
67
74
  const __filename = fileURLToPath(import.meta.url);
@@ -109,10 +116,13 @@ program
109
116
  });
110
117
  program
111
118
  .command('proxy [subcommand] [value]')
112
- .description('Reference-counted, session-scoped proxy + dashboard lifecycle: ensure | release | status | start | stop | restart | enable | disable | dashboard [on|off]')
119
+ .description('Reference-counted, session-scoped proxy + dashboard lifecycle: ensure | release | status | start | stop | restart | enable | disable | dashboard [on|off] | connect --socket <path>')
113
120
  .option('--client <id>', 'Client/session id (defaults to session env or parent pid)')
114
121
  .option('--client-pid <n>', 'Long-lived agent pid for liveness (hooks pass $PPID)')
115
122
  .option('--port <n>', 'Proxy port (default 4000 / $PROXY_PORT)')
123
+ .option('--socket <path>', 'connect: proxy AF_UNIX socket (default $UAP_PROXY_SOCKET)')
124
+ .option('--ready-file <path>', 'connect: touch this file once the relay is listening (readiness handshake)')
125
+ .option('--allow-anyone', 'connect: accept that the relay re-serves the 0600 socket on TCP — while it runs, EVERY local user can reach the model path')
116
126
  .option('--quiet', 'Suppress output (used by hooks)')
117
127
  .option('--if-enabled', 'No-op unless .uap.json proxy.autostart is true (hook-safe)')
118
128
  .option('--json', 'Machine-readable status output')
@@ -132,11 +142,20 @@ program
132
142
  .command('sandbox')
133
143
  .description('Run a command with a kernel-enforced workdir boundary (bubblewrap): only the ' +
134
144
  'current dir + scratch are writable, so writes outside fail at the kernel — the ' +
135
- 'boundary --dangerously-skip-permissions cannot bypass. Usage: uap sandbox -- <command> [args...]')
145
+ 'boundary --dangerously-skip-permissions cannot bypass. Optional P4 authorities: ' +
146
+ '`--net off` cuts the network namespace entirely (kernel egress boundary), ' +
147
+ 'and per-session writable holes come from the consent ledger ' +
148
+ '(uap sandbox grant <path> --session <id> --reason "..." | grants | revoke <id>). ' +
149
+ 'Usage: uap sandbox [--session <id>] [--net shared|off] -- <command> [args...]')
136
150
  .argument('[command...]', 'Command to run sandboxed (prefix with -- to pass its flags through)')
151
+ .option('--session <id>', 'Session id the grant ledger is scoped to (default $UAP_SESSION_ID)')
152
+ .option('--net <mode>', 'Network mode: shared (default) or off (kernel egress boundary — no network at all)')
153
+ .option('--ttl <secs>', 'grant: seconds before the grant expires (default 8h, cap 7d)')
154
+ .option('--reason <why>', 'grant: WHY this authority is consented (required)')
155
+ .option('--json', 'grants: machine-readable list')
137
156
  .allowUnknownOption(true)
138
- .action(async (command) => {
139
- await (await lazy.sandbox())(command || []);
157
+ .action(async (command, options) => {
158
+ await (await lazy.sandbox())(command || [], options);
140
159
  });
141
160
  program
142
161
  .command('generate')
@@ -468,6 +487,7 @@ program
468
487
  .argument('[id]', 'list index or fingerprint prefix — show one incident with its escalation handoff')
469
488
  .option('--limit <n>', 'how many recent incidents to list', '10')
470
489
  .option('--json', 'Emit machine-readable JSON')
490
+ .option('--steer', 'Emit the recurring-hazard digest for session context (silent when no class repeats)')
471
491
  .option('--file <path>', 'Ledger path (default: $UAP_LOOP_INCIDENTS or ~/.config/uap/loop-incidents.jsonl)')
472
492
  .action(async (id, options) => {
473
493
  const cmd = await lazy.loops();
@@ -498,6 +518,142 @@ program
498
518
  const cmd = await lazy.inference();
499
519
  await cmd(options);
500
520
  });
521
+ // Model placement registry (spec: docs/specs/operator-model-placement.md) —
522
+ // `models` (plural) is placement: where a model lives and what it costs.
523
+ // The existing `uap model` (singular) is multi-model routing: which model
524
+ // answers. Both surfaces cross-reference each other in help text.
525
+ program
526
+ .command('models')
527
+ .description('Model placement registry: validate footprints, measure the live backend')
528
+ .addCommand(new Command('validate')
529
+ .description('Cross-check the registry (repo + ~/.uap/model-registry.json) against the capacity policy')
530
+ .option('--repo-path <path>', 'explicit repo registry path (default: <project>/config/model-registry.json)')
531
+ .option('--local-path <path>', 'explicit machine-local registry path (default: ~/.uap/model-registry.json)')
532
+ .action(async (options) => {
533
+ const code = await (await lazy.models())('validate', options);
534
+ if (code)
535
+ process.exitCode = code;
536
+ }))
537
+ .addCommand(new Command('measure')
538
+ .description('Record the live backend\'s measured footprint into the machine-local registry')
539
+ .option('--model <key>', 'registry model key to attribute the measurement to')
540
+ .option('--config <key>', 'registry config key to attribute the measurement to')
541
+ .option('--endpoint <url>', 'inference base URL (default: http://127.0.0.1:8080)')
542
+ .option('--device <key>', 'device key the measurement is valid on (default: first gpu in the registry)')
543
+ .option('--dry-run', 'Print what would be written; write nothing')
544
+ .action(async (options) => {
545
+ const code = await (await lazy.models())('measure', { ...options, dryRun: options.dryRun === true });
546
+ if (code)
547
+ process.exitCode = code;
548
+ }))
549
+ .addCommand(new Command('status')
550
+ .description('Sync and print the placement ledger: devices (probe fact), residents, headroom')
551
+ .option('--repo-path <path>', 'explicit repo registry path')
552
+ .option('--local-path <path>', 'explicit machine-local registry path')
553
+ .option('--ledger-path <path>', 'explicit placement ledger path (default: ~/.uap/placement.json)')
554
+ .action(async (options) => {
555
+ const code = await (await lazy.models())('status', options);
556
+ if (code)
557
+ process.exitCode = code;
558
+ }))
559
+ .addCommand(new Command('pending')
560
+ .description('List parked placement requests awaiting the operator, with ranked options')
561
+ .option('--repo-path <path>', 'explicit repo registry path')
562
+ .option('--local-path <path>', 'explicit machine-local registry path')
563
+ .option('--ledger-path <path>', 'explicit placement ledger path (default: ~/.uap/placement.json)')
564
+ .action(async (options) => {
565
+ const code = await (await lazy.models())('pending', options);
566
+ if (code)
567
+ process.exitCode = code;
568
+ }))
569
+ .addCommand(
570
+ // Args as .argument(), NOT in the name: commander matches the invoked
571
+ // subcommand by NAME, and a name like 'apply <id> <option>' never
572
+ // matches 'apply' (verified live: help listed it, invocation didn't).
573
+ new Command('apply')
574
+ .argument('<placementId>', 'pending placement id (from `uap models pending`)')
575
+ .argument('<option>', '1-based option index (as printed by `uap models pending`)')
576
+ .description('The explicit operator yes: print the impact list, then enforce the placement (rollback on failure)')
577
+ .option('--yes', 'Actually enforce; without it, prints the impact list and the confirm command')
578
+ .option('--repo-path <path>', 'explicit repo registry path')
579
+ .option('--local-path <path>', 'explicit machine-local registry path')
580
+ .option('--ledger-path <path>', 'explicit placement ledger path (default: ~/.uap/placement.json)')
581
+ .action(async (placementId, option, options) => {
582
+ const code = await (await lazy.models())('apply', {
583
+ ...options,
584
+ placementId,
585
+ option: Number(option),
586
+ yes: options.yes === true,
587
+ });
588
+ if (code)
589
+ process.exitCode = code;
590
+ }))
591
+ .addCommand(new Command('dismiss')
592
+ .argument('<placementId>', 'pending placement id')
593
+ .description('Refuse a parked placement request; the client falls back on expiry')
594
+ .option('--ledger-path <path>', 'explicit placement ledger path (default: ~/.uap/placement.json)')
595
+ .action(async (placementId, options) => {
596
+ const code = await (await lazy.models())('dismiss', { ...options, placementId });
597
+ if (code)
598
+ process.exitCode = code;
599
+ }))
600
+ .addCommand(new Command('load')
601
+ .argument('<model>', 'registry model key (e.g. qwen3.8-27b)')
602
+ .description('Operator-initiated load: reuse/load-alongside only (displacement needs a pending request)')
603
+ .option('--ledger-path <path>', 'explicit placement ledger path (default: ~/.uap/placement.json)')
604
+ .action(async (model, options) => {
605
+ const code = await (await lazy.models())('load', { ...options, model });
606
+ if (code)
607
+ process.exitCode = code;
608
+ }))
609
+ .addCommand(new Command('unload')
610
+ .argument('<model>', 'registry model key whose live residents should be drained and stopped')
611
+ .description('Drain, stop, verify-free, and refresh for a model\'s live residents')
612
+ .option('--ledger-path <path>', 'explicit placement ledger path (default: ~/.uap/placement.json)')
613
+ .action(async (model, options) => {
614
+ const code = await (await lazy.models())('unload', { ...options, model });
615
+ if (code)
616
+ process.exitCode = code;
617
+ }))
618
+ .addCommand(new Command('units')
619
+ .description('Write Conflicts= drop-ins for same-device registry units (systemd then refuses two residents per device)')
620
+ .option('--repo-path <path>', 'explicit repo registry path')
621
+ .option('--local-path <path>', 'explicit machine-local registry path')
622
+ .action(async (options) => {
623
+ const code = await (await lazy.models())('units', options);
624
+ if (code)
625
+ process.exitCode = code;
626
+ }))
627
+ .addCommand(new Command('auto')
628
+ .description('Auto-load policy: load-alongside always; displacement only for allowlisted models (request-driven, no manual apply). Off until --enable.')
629
+ .option('--enable', 'enable auto-load')
630
+ .option('--disable', 'disable auto-load; every park needs a manual apply again')
631
+ .option('--allow-displace <models>', 'comma-separated registry model keys whose displacement may auto-enforce')
632
+ .option('--disallow-displace <models>', 'comma-separated registry model keys to remove from the displacement allowlist')
633
+ .option('--allow-unload <models>', 'comma-separated registry model keys that may be auto-unloaded once idle (standing consent; needs --yes)')
634
+ .option('--disallow-unload <models>', 'comma-separated registry model keys to remove from the idle-unload allowlist')
635
+ .option('--unload-idle-after-secs <secs>', 'arm idle-unload: no gated use for this many seconds and a listed resident is swept (min 60)')
636
+ .option('--unload-idle-off', 'disarm the idle-unload timer (allowlist is kept)')
637
+ .option('--yes', 'required with --allow-displace / --allow-unload: the standing consent being recorded')
638
+ .option('--policy-path <path>', 'explicit policy file path (testing only: the controller reads ~/.uap/placement-auto.json or UAP_PLACEMENT_AUTO)')
639
+ .action(async (options) => {
640
+ const code = await (await lazy.models())('auto', {
641
+ enable: options.enable === true,
642
+ disable: options.disable === true,
643
+ allowDisplace: options.allowDisplace,
644
+ disallowDisplace: options.disallowDisplace,
645
+ allowUnload: options.allowUnload,
646
+ disallowUnload: options.disallowUnload,
647
+ unloadIdleAfterSecs: options.unloadIdleAfterSecs === undefined
648
+ ? undefined
649
+ : Number(options.unloadIdleAfterSecs),
650
+ unloadIdleAfterSecsOff: options.unloadIdleOff === true,
651
+ yes: options.yes === true,
652
+ policyPath: options.policyPath,
653
+ });
654
+ if (code)
655
+ process.exitCode = code;
656
+ }));
501
657
  // Quality-metrics gate — complexity/coverage/mutation policing with a ratchet
502
658
  program
503
659
  .command('quality')
@@ -895,6 +1051,39 @@ program
895
1051
  const cmd = await lazy.plan();
896
1052
  await cmd(action, { json: Boolean(options.json), file, review: options.review, force: Boolean(options.force) });
897
1053
  });
1054
+ // S2 self-knowledge card — UAP's own manual, built offline from shipped docs
1055
+ program
1056
+ .command('self')
1057
+ .description('Self-knowledge card: the version-pinned digest of UAP\'s own commands/features, and offline search over it')
1058
+ .argument('[action]', 'card | query <question> (default: card)')
1059
+ .argument('[question]', 'query: the question to answer from the card')
1060
+ .option('--json', 'Emit JSON')
1061
+ .option('--refresh', 'card: rebuild even when the version pin still matches')
1062
+ .option('--limit <n>', 'query: max results (default 5)')
1063
+ .action(async (action, question, options) => {
1064
+ const cmd = await lazy.self();
1065
+ await cmd(action, question, {
1066
+ json: Boolean(options.json),
1067
+ refresh: Boolean(options.refresh),
1068
+ limit: options.limit !== undefined ? Number(options.limit) : undefined,
1069
+ });
1070
+ });
1071
+ // Attention queue — the ask-instead-of-stop rail
1072
+ program
1073
+ .command('attention')
1074
+ .description('Operator attention queue: items needing your decision, with AFK safe-default adoption')
1075
+ .argument('[action]', 'list | ack <id> | decide <id> <decision> | sweep (default: list)')
1076
+ .argument('[id]', 'ack/decide: the item id')
1077
+ .argument('[decision]', 'decide: your decision text')
1078
+ .option('--status <s>', 'list: pending | acknowledged | decided | auto-decided | escalated (default: pending)')
1079
+ .option('--json', 'Emit JSON')
1080
+ .action(async (action, id, decision, options) => {
1081
+ const cmd = await lazy.attention();
1082
+ await cmd(action, id, decision, {
1083
+ json: Boolean(options.json),
1084
+ status: typeof options.status === 'string' ? options.status : undefined,
1085
+ });
1086
+ });
898
1087
  // Paired UAP benchmark — controlled UAP-on vs UAP-off A/B
899
1088
  const bench = program
900
1089
  .command('bench')
@@ -920,6 +1109,44 @@ bench
920
1109
  const cmd = await lazy.benchPaired();
921
1110
  await cmd(options);
922
1111
  });
1112
+ bench
1113
+ .command('report')
1114
+ .description('Read the latest paired report (or --dir); --compare shows run-to-run delta drift. Deltas always carry their discrimination status')
1115
+ .option('--dir <dir>', 'A specific paired-run dir (default: newest under benchmark-results)')
1116
+ .option('--compare', 'Compare the two newest runs: which deltas held, flipped, narrowed')
1117
+ .option('--full', 'Print the stored report.md instead of the compact delta summary')
1118
+ .option('--json', 'Emit the raw report JSON')
1119
+ .action(async (options) => {
1120
+ const cmd = await lazy.benchReport();
1121
+ await cmd(options);
1122
+ });
1123
+ // Measured pace — the agent's own EWMA ETA over its recorded attempts (P5: S1b)
1124
+ program
1125
+ .command('eta')
1126
+ .description('Measured pace: the EWMA ETA for a (model, taskType, complexity) key, from this project\'s own recorded attempts')
1127
+ .option('-m, --model <id>', 'Model id (default: the active/executor model)')
1128
+ .option('--task-type <t>', "Sample key: task type (default 'orchestrated')")
1129
+ .option('--complexity <c>', 'Sample key: complexity tier (default medium)')
1130
+ .option('--all', 'List every key with history (estimates + too-thin keys)')
1131
+ .option('--limit <n>', 'Sample window size (default 50)')
1132
+ .option('--half-life <n>', 'EWMA half-life in samples (default 10)')
1133
+ .option('--db <path>', 'Pin the analytics DB path (tests, odd layouts)')
1134
+ .option('--json', 'Emit JSON')
1135
+ .action(async (options) => {
1136
+ const cmd = await lazy.eta();
1137
+ await cmd(options);
1138
+ });
1139
+ // Local diff view — what the agent changed around here (P5: S3)
1140
+ program
1141
+ .command('diff')
1142
+ .description('Local diff view: uncommitted + unpushed changes at a glance, with per-file deltas and inspection commands')
1143
+ .option('--base <ref>', 'Base ref for the unpushed view (default: master)')
1144
+ .option('--max <n>', 'Per-section file cap (default 40)')
1145
+ .option('--json', 'Emit the raw facts as JSON')
1146
+ .action(async (options) => {
1147
+ const cmd = await lazy.diff();
1148
+ await cmd(options);
1149
+ });
923
1150
  // HALO harness-optimization commands
924
1151
  const harness = program
925
1152
  .command('harness')
@@ -1675,6 +1902,40 @@ program
1675
1902
  .action(async (options) => {
1676
1903
  (await lazy.dashboard())('history', options);
1677
1904
  }));
1905
+ // Client folder fleet — registry CLI behind the dashboard's Clients tab
1906
+ program
1907
+ .command('clients')
1908
+ .description('Client folder fleet: list, add, remove, scan (feeds the dashboard Clients tab)')
1909
+ .addCommand(new Command('list')
1910
+ .description('Fleet summary for every discovered client (live reads)')
1911
+ .action(async () => {
1912
+ (await lazy.clients())('list', undefined, {});
1913
+ }))
1914
+ .addCommand(new Command('add')
1915
+ .description('Pin a client folder manually')
1916
+ .argument('<path>', 'Path to the client project root')
1917
+ .option('--name <name>', 'Display name (defaults to the folder name)')
1918
+ .option('--port <port>', 'Dashboard port for spawn-on-demand (default: 3861+)')
1919
+ .action(async (path, options) => {
1920
+ (await lazy.clients())('add', path, options);
1921
+ }))
1922
+ .addCommand(new Command('remove')
1923
+ .description('Unpin a manually added client')
1924
+ .argument('<path>', 'Path that was pinned with `add`')
1925
+ .action(async (path) => {
1926
+ (await lazy.clients())('remove', path, {});
1927
+ }))
1928
+ .addCommand(new Command('scan')
1929
+ .description('Show what auto-scan discovers')
1930
+ .action(async () => {
1931
+ (await lazy.clients())('scan', undefined, {});
1932
+ }))
1933
+ .addCommand(new Command('stop')
1934
+ .description("Stop a client's spawned dashboard (pid ownership-verified)")
1935
+ .argument('<path>', 'Path to the client project root')
1936
+ .action(async (path) => {
1937
+ (await lazy.clients())('stop', path, {});
1938
+ }));
1678
1939
  // Multi-Model Architecture commands - visible in --help, loaded on demand
1679
1940
  {
1680
1941
  const modelCmd = program
@@ -1850,7 +2111,10 @@ uapOmpCmd.addCommand(new Command('dashboard')
1850
2111
  const uapOmpDir = process.env.HOME + '/.uap/omp';
1851
2112
  const dashboardScript = `${uapOmpDir}/commands/uap-dashboard.sh`;
1852
2113
  if (existsSync(dashboardScript)) {
1853
- execSync(`bash "${dashboardScript}"`, { stdio: 'inherit' });
2114
+ // Argv array, never a shell: the path interpolates into a command
2115
+ // string at the old call site (prepass debt) and a hostile HOME
2116
+ // must not become a shell metacharacter.
2117
+ execFileSync('bash', [dashboardScript], { stdio: 'inherit' });
1854
2118
  }
1855
2119
  else {
1856
2120
  console.error('UAP dashboard not installed. Run: uap-omp install');
@@ -1864,14 +2128,26 @@ uapOmpCmd.addCommand(new Command('dashboard')
1864
2128
  // Memory command
1865
2129
  uapOmpCmd.addCommand(new Command('memory')
1866
2130
  .description('Manage UAP memory for oh-my-pi')
1867
- .addCommand(new Command('status').description('Show memory status').action(() => {
2131
+ .addCommand(new Command('status').description('Show memory status').action(async () => {
1868
2132
  try {
1869
2133
  const uapOmpDir = process.env.HOME + '/.uap/omp';
1870
2134
  const dbPath = `${uapOmpDir}/memory/short_term.db`;
1871
2135
  if (existsSync(dbPath)) {
1872
- execSync(`sqlite3 "${dbPath}" "SELECT COUNT(*) as total, COUNT(DISTINCT type) as types FROM memories;"`, {
1873
- stdio: 'inherit',
1874
- });
2136
+ // The omp memory DB is better-sqlite3's own format: read it with
2137
+ // the library (lazy — keeps `uap --help` fast), never the sqlite3
2138
+ // CLI through a shell. The old call interpolated the path into
2139
+ // `execSync` (prepass debt).
2140
+ const { default: Database } = await import('better-sqlite3');
2141
+ const db = new Database(dbPath, { readonly: true, fileMustExist: true });
2142
+ try {
2143
+ const row = db
2144
+ .prepare('SELECT COUNT(*) as total, COUNT(DISTINCT type) as types FROM memories')
2145
+ .get();
2146
+ console.log(`total: ${row.total}\ntypes: ${row.types}`);
2147
+ }
2148
+ finally {
2149
+ db.close();
2150
+ }
1875
2151
  }
1876
2152
  else {
1877
2153
  console.log('No memory database found. Run: uap-omp install');
@@ -1886,17 +2162,32 @@ uapOmpCmd.addCommand(new Command('memory')
1886
2162
  .description('Query memory for relevant context')
1887
2163
  .argument('<search>', 'Search term')
1888
2164
  .option('-n, --limit <number>', 'Max results', '5')
1889
- .action((search, options) => {
2165
+ .action(async (search, options) => {
1890
2166
  try {
1891
2167
  const uapOmpDir = process.env.HOME + '/.uap/omp';
1892
2168
  const dbPath = `${uapOmpDir}/memory/short_term.db`;
1893
2169
  if (existsSync(dbPath)) {
1894
- // Sanitize search term to prevent SQL injection
1895
- const sanitizedSearch = search.replace(/'/g, "''");
1896
- const sanitizedLimit = parseInt(options.limit, 10) || 5;
1897
- execSync(`sqlite3 "${dbPath}" "SELECT content, type, importance FROM memories WHERE content LIKE '%${sanitizedSearch}%' ORDER BY importance DESC LIMIT ${sanitizedLimit};"`, {
1898
- stdio: 'inherit',
1899
- });
2170
+ // Parameterized LIKE + bound LIMIT: the search term is
2171
+ // user/LLM-controlled and must never reach a shell or be
2172
+ // spliced into SQL text (the old `execSync` call was a real
2173
+ // injection surface — `$()` inside its double quotes executed).
2174
+ // LIKE wildcards in the TERM are escaped too: a literal `%`
2175
+ // search is a literal-% search, not "dump the whole table".
2176
+ const { default: Database } = await import('better-sqlite3');
2177
+ const parsedLimit = parseInt(options.limit, 10);
2178
+ const limit = Number.isFinite(parsedLimit) ? parsedLimit : 5;
2179
+ const db = new Database(dbPath, { readonly: true, fileMustExist: true });
2180
+ try {
2181
+ const rows = db
2182
+ .prepare("SELECT content, type, importance FROM memories WHERE content LIKE ? ESCAPE '\\' ORDER BY importance DESC LIMIT ?")
2183
+ .all(`%${String(search).replace(/[\\%_]/g, (m) => `\\${m}`)}%`, Math.max(1, limit));
2184
+ for (const r of rows) {
2185
+ console.log(`[${r.type}] (i${r.importance}) ${r.content}`);
2186
+ }
2187
+ }
2188
+ finally {
2189
+ db.close();
2190
+ }
1900
2191
  }
1901
2192
  else {
1902
2193
  console.log('No memory database found. Run: uap-omp install');
@@ -1915,7 +2206,9 @@ uapOmpCmd.addCommand(new Command('worktree')
1915
2206
  const uapOmpDir = process.env.HOME + '/.uap/omp';
1916
2207
  const worktreesFile = `${uapOmpDir}/worktrees.json`;
1917
2208
  if (existsSync(worktreesFile)) {
1918
- execSync(`cat "${worktreesFile}" | jq '.'`, { stdio: 'inherit' });
2209
+ // Pretty-print it ourselves: no shell, no `cat | jq` dependency
2210
+ // (the old pipe interpolated the path through execSync).
2211
+ console.log(JSON.stringify(JSON.parse(readFileSync(worktreesFile, 'utf-8')), null, 2));
1919
2212
  }
1920
2213
  else {
1921
2214
  console.log('No worktrees tracked. Run: uap-omp install');
@@ -1948,7 +2241,9 @@ uapOmpCmd.addCommand(new Command('hooks')
1948
2241
  try {
1949
2242
  const scriptPath = join(__dirname, '../../scripts/omp/uap-omp.sh');
1950
2243
  if (existsSync(scriptPath)) {
1951
- execSync(`bash "${scriptPath}" install`, { stdio: 'inherit' });
2244
+ // Argv array, never a shell (prepass debt: the path used to
2245
+ // interpolate into an execSync command string).
2246
+ execFileSync('bash', [scriptPath, 'install'], { stdio: 'inherit' });
1952
2247
  }
1953
2248
  else {
1954
2249
  console.error('UAP hooks script not found. Please rebuild with: npm run build');
@@ -1963,7 +2258,7 @@ uapOmpCmd.addCommand(new Command('hooks')
1963
2258
  try {
1964
2259
  const scriptPath = join(__dirname, '../../scripts/omp/uap-omp.sh');
1965
2260
  if (existsSync(scriptPath)) {
1966
- execSync(`bash "${scriptPath}" status`, { stdio: 'inherit' });
2261
+ execFileSync('bash', [scriptPath, 'status'], { stdio: 'inherit' });
1967
2262
  }
1968
2263
  else {
1969
2264
  console.error('UAP hooks script not found. Please rebuild with: npm run build');