@miller-tech/uap 2.21.3 → 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
@@ -0,0 +1,85 @@
1
+ /**
2
+ * `uap diff` — the local diff view
3
+ *
4
+ * The operator's one-command answer to "what has the agent changed around
5
+ * here?": uncommitted AND unpushed work, summarized legibly — per-file line
6
+ * deltas with status codes (modified / added / deleted / renamed / untracked),
7
+ * against both HEAD and the base branch, with the exact commands to inspect
8
+ * deeper. A worktree-scoped agent's output lives in `.worktrees/NNN-<slug>/`
9
+ * where a plain `git status` scrolls past; this is the at-a-glance view.
10
+ *
11
+ * Safe-git contract (inherited from supervise/observe): git runs via
12
+ * execFileSync ARG ARRAYS (no shell, no interpolation), 5s timeout, and every
13
+ * failure degrades to `unknown` — the view renders with what it could read,
14
+ * it never throws. Output is capped per section (`--max`, default 40 files)
15
+ * because a monorepo can list thousands.
16
+ */
17
+ export interface DiffOptions {
18
+ /** Base ref for the unpushed view (default: master). */
19
+ base?: string;
20
+ /** Per-section file cap (default 40). */
21
+ max?: string;
22
+ /** Test/odd-layout seam: pin the working dir (default: process.cwd()). */
23
+ cwd?: string;
24
+ json?: boolean;
25
+ }
26
+ /** What happened to one file. `!` = a delta row whose status could not be read. */
27
+ export type FileStatus = 'M' | 'A' | 'D' | 'R' | '?' | '!';
28
+ /** One file's line delta as `git diff --numstat` reports it. */
29
+ export interface FileDelta {
30
+ path: string;
31
+ /** Lines added; null for binary files. */
32
+ added: number | null;
33
+ /** Lines removed; null for binary files. */
34
+ removed: number | null;
35
+ status: FileStatus;
36
+ }
37
+ export interface DiffFacts {
38
+ /** Current branch name, or null when detached. */
39
+ branch: string | null;
40
+ /** Worktree dir name (NNN-slug) when running inside a UAP worktree. */
41
+ worktree: string | null;
42
+ /** Uncommitted files (vs HEAD): staged + unstaged, one row per path. */
43
+ uncommitted: FileDelta[];
44
+ /** Untracked paths (never added to the index). */
45
+ untracked: string[];
46
+ /** Unpushed commits vs base: oneline subjects, newest first, capped. */
47
+ unpushedCommits: string[];
48
+ /** Ahead/behind counts vs the merge-base, when computable. */
49
+ ahead: number | null;
50
+ behind: number | null;
51
+ /** Aggregate unpushed file deltas (base...HEAD). */
52
+ unpushedFiles: FileDelta[];
53
+ /** The base ref this view compares against. */
54
+ base: string;
55
+ /** The numstat read (uncommitted deltas) succeeded. False ⇒ the empty
56
+ * uncommitted list is UNKNOWN, not clean — the view must not claim
57
+ * cleanliness from absent data. */
58
+ numstatOk: boolean;
59
+ /** The porcelain status read (untracked + status codes) succeeded. */
60
+ statusOk: boolean;
61
+ /** Populated when git itself was unusable — the view says so. */
62
+ gitError: string | null;
63
+ }
64
+ /** Parse `git diff --numstat` output into delta rows (status unknown). */
65
+ export declare function parseNumstat(out: string): FileDelta[];
66
+ /** Fold `git status --porcelain` codes onto delta rows by path. */
67
+ export declare function applyPorcelain(deltas: FileDelta[], porcelain: string): {
68
+ untracked: string[];
69
+ };
70
+ /** Gather the raw facts from git. Degrades to empty on any failure — and
71
+ * records WHICH reads failed, so the view can say "unknown" instead of
72
+ * claiming "clean" from absent data. */
73
+ export declare function gatherDiffFacts(cwd: string, options?: DiffOptions): DiffFacts;
74
+ /**
75
+ * Pure renderer: facts → the operator-facing view. Exported so the tests pin
76
+ * the wording without running git.
77
+ */
78
+ export declare function renderDiffView(facts: DiffFacts, maxFiles?: number): string;
79
+ /** Parse a positive-integer CLI flag. Returns undefined when the flag is
80
+ * absent (caller applies its default) and null when it is garbage — a LOUD
81
+ * refusal, never NaN math: a truncated view wearing the full totals is a
82
+ * lie. */
83
+ export declare function parsePositiveInt(raw: string | undefined): number | undefined | null;
84
+ export declare function diffCommand(options?: DiffOptions): Promise<void>;
85
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/cli/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAMD,mFAAmF;AACnF,MAAM,MAAM,UAAU,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAE3D,gEAAgE;AAChE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,kDAAkD;IAClD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,wEAAwE;IACxE,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,kDAAkD;IAClD,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,wEAAwE;IACxE,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,8DAA8D;IAC9D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,oDAAoD;IACpD,aAAa,EAAE,SAAS,EAAE,CAAC;IAC3B,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb;;uCAEmC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,QAAQ,EAAE,OAAO,CAAC;IAClB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAoBD,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CAiBrD;AAED,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CA6B9F;AAED;;wCAEwC;AACxC,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,SAAS,CA0EjF;AAuBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,SAAoB,GAAG,MAAM,CAyErF;AAED;;;UAGU;AACV,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,CAInF;AAED,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB1E"}
@@ -0,0 +1,293 @@
1
+ /**
2
+ * `uap diff` — the local diff view
3
+ *
4
+ * The operator's one-command answer to "what has the agent changed around
5
+ * here?": uncommitted AND unpushed work, summarized legibly — per-file line
6
+ * deltas with status codes (modified / added / deleted / renamed / untracked),
7
+ * against both HEAD and the base branch, with the exact commands to inspect
8
+ * deeper. A worktree-scoped agent's output lives in `.worktrees/NNN-<slug>/`
9
+ * where a plain `git status` scrolls past; this is the at-a-glance view.
10
+ *
11
+ * Safe-git contract (inherited from supervise/observe): git runs via
12
+ * execFileSync ARG ARRAYS (no shell, no interpolation), 5s timeout, and every
13
+ * failure degrades to `unknown` — the view renders with what it could read,
14
+ * it never throws. Output is capped per section (`--max`, default 40 files)
15
+ * because a monorepo can list thousands.
16
+ */
17
+ import { execFileSync } from 'child_process';
18
+ import chalk from 'chalk';
19
+ import { termSafe } from '../utils/terminal-safe.js';
20
+ const GIT_TIMEOUT_MS = 5000;
21
+ /** Cap a monorepo's file list — the view is a summary, not a log dump. */
22
+ const DEFAULT_MAX_FILES = 40;
23
+ /** Run git, return stdout or null (never throw). `core.quotepath=false`
24
+ * everywhere: git's default quotes non-ASCII paths as octal escapes
25
+ * (`"\303\251clat"`), which would mangle every unicode filename in the
26
+ * view AND break the porcelain↔numstat exact-match fold (numstat's path
27
+ * would carry escapes porcelain stripped). Raw bytes on both sides. */
28
+ function git(args, cwd) {
29
+ try {
30
+ return execFileSync('git', ['-c', 'core.fsmonitor=false', '-c', 'core.untrackedCache=false', '-c', 'core.quotepath=false', ...args], {
31
+ cwd,
32
+ encoding: 'utf-8',
33
+ timeout: GIT_TIMEOUT_MS,
34
+ stdio: ['ignore', 'pipe', 'pipe'],
35
+ });
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ /** Parse `git diff --numstat` output into delta rows (status unknown). */
42
+ export function parseNumstat(out) {
43
+ const files = [];
44
+ for (const line of out.split('\n')) {
45
+ if (!line)
46
+ continue;
47
+ const m = line.match(/^(\d+|-)\t(\d+|-)\t(.+)$/);
48
+ if (!m)
49
+ continue;
50
+ files.push({
51
+ added: m[1] === '-' ? null : parseInt(m[1], 10),
52
+ removed: m[2] === '-' ? null : parseInt(m[2], 10),
53
+ // Renames arrive as `{old => new}` path shapes; keep the tail verbatim.
54
+ // Outer quotes (git quotes paths with spaces/specials) are stripped so
55
+ // the porcelain fold's exact match sees the same spelling on both sides.
56
+ path: m[3].replace(/^"|"$/g, ''),
57
+ status: '!',
58
+ });
59
+ }
60
+ return files;
61
+ }
62
+ /** Fold `git status --porcelain` codes onto delta rows by path. */
63
+ export function applyPorcelain(deltas, porcelain) {
64
+ const untracked = [];
65
+ for (const line of porcelain.split('\n')) {
66
+ if (!line)
67
+ continue;
68
+ const code = line.slice(0, 2);
69
+ const raw = line.slice(3).trim().replace(/^"|"$/g, '');
70
+ if (code === '??') {
71
+ untracked.push(raw);
72
+ continue;
73
+ }
74
+ // XY: prefer the worktree code (Y) — the file's state on disk is what the
75
+ // operator sees — else the index code (X).
76
+ const y = code[1]?.trim();
77
+ const x = code[0]?.trim();
78
+ const c = y || x || '';
79
+ const status = c === 'D' ? 'D' : c === 'A' ? 'A' : c === 'R' ? 'R' : c === 'M' ? 'M' : '?';
80
+ // Renames: porcelain prints `old -> new` (and sometimes just `new`);
81
+ // numstat prints `{old => new}`. Normalize both spellings before matching
82
+ // so the status lands on the right row. Exact matches win over fuzzy
83
+ // suffix matches (an `endsWith` hit on an earlier row must not steal the
84
+ // status from an exact row). Rows porcelain does not cover keep their
85
+ // `!` — status codes are cosmetic, the deltas are the substance.
86
+ const renameArrow = raw.indexOf(' -> ');
87
+ const effective = (renameArrow >= 0 ? raw.slice(renameArrow + 4) : raw).replace(/^"|"$/g, '');
88
+ const exact = deltas.find((d) => d.path === raw || d.path.replace(/\{[^}]* => ([^}]*)\}/g, '$1') === effective);
89
+ const match = exact ?? deltas.find((d) => d.path.endsWith(raw));
90
+ if (match)
91
+ match.status = status;
92
+ }
93
+ return { untracked };
94
+ }
95
+ /** Gather the raw facts from git. Degrades to empty on any failure — and
96
+ * records WHICH reads failed, so the view can say "unknown" instead of
97
+ * claiming "clean" from absent data. */
98
+ export function gatherDiffFacts(cwd, options = {}) {
99
+ const base = options.base || 'master';
100
+ const facts = {
101
+ branch: null,
102
+ worktree: null,
103
+ uncommitted: [],
104
+ untracked: [],
105
+ unpushedCommits: [],
106
+ ahead: null,
107
+ behind: null,
108
+ unpushedFiles: [],
109
+ base,
110
+ numstatOk: false,
111
+ statusOk: false,
112
+ gitError: null,
113
+ };
114
+ // "Is this a repo" must not be probed with rev-parse HEAD — an unborn
115
+ // branch (fresh init, no commits) has no HEAD and would be misreported as
116
+ // "not a repository". is-inside-work-tree answers the question directly.
117
+ const inside = git(['rev-parse', '--is-inside-work-tree'], cwd);
118
+ if (inside === null || inside.trim() !== 'true') {
119
+ facts.gitError = 'git unavailable or not a repository';
120
+ return facts;
121
+ }
122
+ // symbolic-ref reads the branch even when HEAD is unborn; it fails only
123
+ // when HEAD is detached (no symbolic ref) → null → "(detached)".
124
+ const branch = git(['symbolic-ref', '--short', 'HEAD'], cwd);
125
+ facts.branch = branch === null ? null : branch.trim() || null;
126
+ facts.worktree = /(^|\/)\.worktrees\/([0-9]+-[^/]+)\/?$/.exec(cwd)?.[2] ?? null;
127
+ // Uncommitted: staged + unstaged in ONE numstat pass (vs HEAD), then fold
128
+ // porcelain status codes and untracked paths onto it. --no-ext-diff keeps
129
+ // a repo-configured external diff driver out of the read (numstat bypasses
130
+ // drivers today; the flag is defense-in-depth for future flag changes).
131
+ const numstat = git(['diff', '--no-ext-diff', '--numstat', 'HEAD', '--'], cwd);
132
+ facts.numstatOk = numstat !== null;
133
+ if (numstat !== null) {
134
+ const seen = new Set();
135
+ for (const f of parseNumstat(numstat)) {
136
+ if (seen.has(f.path))
137
+ continue; // same path staged+unstaged: one row
138
+ seen.add(f.path);
139
+ facts.uncommitted.push(f);
140
+ }
141
+ }
142
+ const status = git(['status', '--porcelain', '--untracked-files=all'], cwd);
143
+ facts.statusOk = status !== null;
144
+ if (status !== null) {
145
+ facts.untracked = applyPorcelain(facts.uncommitted, status).untracked;
146
+ }
147
+ // Unpushed: ahead/behind and aggregate deltas vs the merge-base with base.
148
+ const counts = git(['rev-list', '--left-right', '--count', `${base}...HEAD`], cwd);
149
+ if (counts !== null) {
150
+ const m = counts.trim().match(/^(\d+)\s+(\d+)$/);
151
+ if (m) {
152
+ facts.behind = parseInt(m[1], 10);
153
+ facts.ahead = parseInt(m[2], 10);
154
+ }
155
+ }
156
+ if (facts.ahead === null || facts.ahead > 0) {
157
+ const log = git(['log', '--no-ext-diff', '--oneline', '--no-decorate', `${base}..HEAD`, '--'], cwd);
158
+ if (log !== null) {
159
+ facts.unpushedCommits = log
160
+ .split('\n')
161
+ .map((l) => l.trim())
162
+ .filter(Boolean)
163
+ .slice(0, DEFAULT_MAX_FILES);
164
+ }
165
+ const diffBase = git(['diff', '--no-ext-diff', '--numstat', `${base}...HEAD`, '--'], cwd);
166
+ if (diffBase !== null)
167
+ facts.unpushedFiles = parseNumstat(diffBase);
168
+ }
169
+ return facts;
170
+ }
171
+ const fmtDelta = (n) => (n === null ? '±' : String(n));
172
+ function totalDeltas(files) {
173
+ let added = 0;
174
+ let removed = 0;
175
+ for (const f of files) {
176
+ added += f.added ?? 0;
177
+ removed += f.removed ?? 0;
178
+ }
179
+ return { added, removed };
180
+ }
181
+ const STATUS_GLYPH = {
182
+ M: 'M',
183
+ A: 'A',
184
+ D: 'D',
185
+ R: 'R',
186
+ '?': '~',
187
+ '!': '~',
188
+ };
189
+ /**
190
+ * Pure renderer: facts → the operator-facing view. Exported so the tests pin
191
+ * the wording without running git.
192
+ */
193
+ export function renderDiffView(facts, maxFiles = DEFAULT_MAX_FILES) {
194
+ const lines = [];
195
+ const where = facts.worktree
196
+ ? `${facts.branch ?? '(detached)'} in worktree ${facts.worktree}`
197
+ : facts.branch ?? '(detached)';
198
+ lines.push(chalk.bold(`Local changes — ${where}`));
199
+ if (facts.gitError) {
200
+ lines.push(chalk.red(` git: ${facts.gitError}`));
201
+ return lines.join('\n');
202
+ }
203
+ // --- uncommitted ---
204
+ if (!facts.numstatOk || !facts.statusOk) {
205
+ // A git read failed or timed out: the empty lists are UNKNOWN, not clean.
206
+ // An absence of data must never be rendered as a positive claim.
207
+ lines.push(chalk.yellow(' working tree: UNKNOWN — a git read failed or timed out; run `git status` before trusting this view'));
208
+ }
209
+ else if (facts.uncommitted.length === 0 && facts.untracked.length === 0) {
210
+ lines.push(chalk.green(' working tree: clean (nothing uncommitted)'));
211
+ }
212
+ else {
213
+ lines.push(' uncommitted:');
214
+ const sorted = [...facts.uncommitted].sort((a, b) => (b.added ?? 0) - (a.added ?? 0));
215
+ for (const f of sorted.slice(0, maxFiles)) {
216
+ const glyph = STATUS_GLYPH[f.status];
217
+ const tag = f.status === 'D' ? chalk.red(glyph) : f.status === 'A' ? chalk.cyan(glyph) : chalk.yellow(glyph);
218
+ // Column integrity (P5 polish): a path LONGER than the column used to
219
+ // push the +N/-N columns right per row. Cap to the column width (keep
220
+ // the TAIL — the informative half of a path) then pad, so every row
221
+ // lines up exactly.
222
+ const shown = f.path.length > 64 ? `…${f.path.slice(-63)}` : f.path.padEnd(64);
223
+ lines.push(` ${tag} ${shown} ${chalk.green(`+${fmtDelta(f.added)}`)} ${chalk.red(`-${fmtDelta(f.removed)}`)}`);
224
+ }
225
+ if (sorted.length > maxFiles) {
226
+ lines.push(chalk.dim(` … and ${sorted.length - maxFiles} more modified file(s)`));
227
+ }
228
+ for (const u of facts.untracked.slice(0, maxFiles)) {
229
+ lines.push(` ${chalk.cyan('?')} ${u}`);
230
+ }
231
+ if (facts.untracked.length > maxFiles) {
232
+ lines.push(chalk.dim(` … and ${facts.untracked.length - maxFiles} more untracked path(s)`));
233
+ }
234
+ const t = totalDeltas(facts.uncommitted);
235
+ lines.push(chalk.dim(` tracked totals: +${t.added} -${t.removed} across ${facts.uncommitted.length} tracked, ${facts.untracked.length} untracked`));
236
+ }
237
+ // --- unpushed ---
238
+ if (facts.ahead === null && facts.behind === null) {
239
+ lines.push(chalk.dim(` unpushed: unknown (no merge-base with ${facts.base}?)`));
240
+ }
241
+ else if ((facts.ahead ?? 0) === 0) {
242
+ lines.push(chalk.green(` unpushed: none (in sync with ${facts.base})`));
243
+ }
244
+ else {
245
+ const ab = facts.behind && facts.behind > 0 ? `, ${facts.behind} behind` : '';
246
+ lines.push(` unpushed: ${chalk.bold(String(facts.ahead))} commit(s) vs ${facts.base}${ab}:`);
247
+ for (const c of facts.unpushedCommits.slice(0, maxFiles))
248
+ lines.push(chalk.dim(` ${termSafe(c)}`));
249
+ if (facts.unpushedCommits.length > maxFiles) {
250
+ lines.push(chalk.dim(` … and ${facts.unpushedCommits.length - maxFiles} more commit(s)`));
251
+ }
252
+ const t = totalDeltas(facts.unpushedFiles);
253
+ lines.push(chalk.dim(` diff vs ${facts.base}: +${t.added} -${t.removed} across ${facts.unpushedFiles.length} file(s)`));
254
+ }
255
+ lines.push('');
256
+ lines.push(chalk.dim(' inspect: git diff -- <file> · git log -p <commit> · git status'));
257
+ return lines.join('\n');
258
+ }
259
+ /** Parse a positive-integer CLI flag. Returns undefined when the flag is
260
+ * absent (caller applies its default) and null when it is garbage — a LOUD
261
+ * refusal, never NaN math: a truncated view wearing the full totals is a
262
+ * lie. */
263
+ export function parsePositiveInt(raw) {
264
+ if (raw === undefined)
265
+ return undefined;
266
+ const n = parseInt(raw, 10);
267
+ return Number.isFinite(n) && n >= 1 ? n : null;
268
+ }
269
+ export async function diffCommand(options = {}) {
270
+ const cwd = options.cwd || process.cwd();
271
+ if (options.base?.startsWith('-')) {
272
+ // Git parses argv: a ref starting with '-' would be consumed as a git
273
+ // OPTION (e.g. --output=<file>), not a rev. Refnames cannot legitimately
274
+ // start with '-', so refuse loudly instead of letting git act on it.
275
+ console.error(chalk.red(`--base must be a ref, not an option (got '${options.base}')`));
276
+ process.exitCode = 1;
277
+ return;
278
+ }
279
+ const maxRaw = parsePositiveInt(options.max);
280
+ if (options.max !== undefined && maxRaw === null) {
281
+ console.error(chalk.red(`--max must be a positive integer (got '${options.max}')`));
282
+ process.exitCode = 1;
283
+ return;
284
+ }
285
+ const maxFiles = maxRaw ?? DEFAULT_MAX_FILES;
286
+ const facts = gatherDiffFacts(cwd, options);
287
+ if (options.json) {
288
+ console.log(JSON.stringify(facts, null, 2));
289
+ return;
290
+ }
291
+ console.log(renderDiffView(facts, maxFiles));
292
+ }
293
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/cli/diff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `uap eta` — the operator's window into the agent's own measured pace.
3
+ *
4
+ * Prints the EWMA estimate for one (model, taskType, complexity) key — or,
5
+ * with `--all`, every key that has history. The estimate is built ONLY from
6
+ * this project's own recorded attempts (S1a), and it never hides the
7
+ * sample count, the spread, or the failure rate: a number without its
8
+ * provenance is exactly the kind of confident guess this feature exists
9
+ * to replace.
10
+ */
11
+ import { type EtaEstimate } from '../delivery/eta-estimate.js';
12
+ export interface EtaOptions {
13
+ /** Model id (default: the active/executor model). */
14
+ model?: string;
15
+ /** Sample key: task type (default 'orchestrated'). */
16
+ taskType?: string;
17
+ /** Sample key: complexity tier (default 'medium'). */
18
+ complexity?: string;
19
+ /** List every key with history instead of one key. */
20
+ all?: boolean;
21
+ /** Sample window / half-life overrides. */
22
+ limit?: string;
23
+ halfLife?: string;
24
+ /** Test/odd-layout seam: pin the analytics DB path. */
25
+ db?: string;
26
+ json?: boolean;
27
+ }
28
+ /** Render one estimate as a single operator-scannable line. */
29
+ export declare function renderEtaLine(key: {
30
+ modelId: string;
31
+ taskType: string;
32
+ complexity: string;
33
+ }, est: EtaEstimate): string;
34
+ export declare function etaCommand(options?: EtaOptions): Promise<void>;
35
+ //# sourceMappingURL=eta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eta.d.ts","sourceRoot":"","sources":["../../src/cli/eta.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,6BAA6B,CAAC;AAIrC,MAAM,WAAW,UAAU;IACzB,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sDAAsD;IACtD,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,+DAA+D;AAC/D,wBAAgB,aAAa,CAC3B,GAAG,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,EAC9D,GAAG,EAAE,WAAW,GACf,MAAM,CAeR;AAED,wBAAsB,UAAU,CAAC,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFxE"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * `uap eta` — the operator's window into the agent's own measured pace.
3
+ *
4
+ * Prints the EWMA estimate for one (model, taskType, complexity) key — or,
5
+ * with `--all`, every key that has history. The estimate is built ONLY from
6
+ * this project's own recorded attempts (S1a), and it never hides the
7
+ * sample count, the spread, or the failure rate: a number without its
8
+ * provenance is exactly the kind of confident guess this feature exists
9
+ * to replace.
10
+ */
11
+ import chalk from 'chalk';
12
+ import { resolveActiveModel } from '../delivery/handsfree-config.js';
13
+ import { estimatePace, formatDurationMs, listPaceKeys, } from '../delivery/eta-estimate.js';
14
+ import { parsePositiveInt } from './diff.js';
15
+ import { termSafe } from '../utils/terminal-safe.js';
16
+ /** Render one estimate as a single operator-scannable line. */
17
+ export function renderEtaLine(key, est) {
18
+ const thin = est.confidence === 'thin' ? chalk.yellow(' [thin data]') : '';
19
+ const spread = chalk.dim(` (spread ${formatDurationMs(est.minMs)}–${formatDurationMs(est.maxMs)})`);
20
+ const failures = est.successRate < 1
21
+ ? chalk.yellow(` · ${(100 * (1 - est.successRate)).toFixed(0)}% of attempts fail → expect ~${est.attemptsPerSuccess.toFixed(1)}x wall time`)
22
+ : '';
23
+ return (`${chalk.cyan(termSafe(key.modelId))} ${chalk.dim(`${termSafe(key.taskType)}/${termSafe(key.complexity)}`)}: ` +
24
+ chalk.bold(`~${formatDurationMs(est.etaMs)}`) +
25
+ ` per successful task, ~${est.etaTurns.toFixed(1)} turns` +
26
+ ` ${chalk.dim(`(EWMA n=${est.samples}/${est.observed} attempts, half-life ${est.halfLifeSamples})`)}${spread}${failures}${thin}`);
27
+ }
28
+ export async function etaCommand(options = {}) {
29
+ const cwd = process.cwd();
30
+ // NaN must never reach the fold: `Math.max(1, parseInt('abc'))` is NaN and
31
+ // a NaN half-life would print a confident-looking garbage line. Loud refusal.
32
+ const halfLifeRaw = parsePositiveInt(options.halfLife);
33
+ if (options.halfLife !== undefined && halfLifeRaw === null) {
34
+ console.error(chalk.red(`--half-life must be a positive integer (got '${options.halfLife}')`));
35
+ process.exitCode = 1;
36
+ return;
37
+ }
38
+ const halfLife = halfLifeRaw ?? undefined;
39
+ const limitRaw = parsePositiveInt(options.limit);
40
+ if (options.limit !== undefined && limitRaw === null) {
41
+ console.error(chalk.red(`--limit must be a positive integer (got '${options.limit}')`));
42
+ process.exitCode = 1;
43
+ return;
44
+ }
45
+ const limit = limitRaw ?? undefined;
46
+ if (options.all) {
47
+ // Enumerate every key with history in ONE store open; show the estimates
48
+ // and the keys whose history is still too thin to estimate (so the
49
+ // operator knows WHY a key they expect is missing, and what would fill
50
+ // it). --json stays PURE JSON (guidance text is gated to the human path).
51
+ const { keys, estimates, truncated } = listPaceKeys({
52
+ dbPath: options.db,
53
+ limit,
54
+ halfLifeSamples: halfLife,
55
+ });
56
+ if (keys.length === 0) {
57
+ if (options.json) {
58
+ console.log(JSON.stringify({ keys: [], estimates: [], truncated: false }, null, 2));
59
+ return;
60
+ }
61
+ console.log(chalk.dim('No recorded attempts yet — run an orchestrated mission; the estimator needs'));
62
+ console.log(chalk.dim('at least 3 successful attempts per (model, taskType, complexity) key.'));
63
+ return;
64
+ }
65
+ if (options.json) {
66
+ console.log(JSON.stringify({ keys, estimates: estimates.filter((e) => e.estimate).map((e) => ({ key: e.key, ...e.estimate })), truncated }, null, 2));
67
+ return;
68
+ }
69
+ console.log(chalk.bold('Measured pace — this project\'s own recorded attempts (EWMA):'));
70
+ for (const e of estimates) {
71
+ if (e.estimate) {
72
+ console.log(' ' + renderEtaLine(e.key, e.estimate));
73
+ }
74
+ else {
75
+ console.log(` ${chalk.cyan(termSafe(e.key.modelId))} ${chalk.dim(`${termSafe(e.key.taskType)}/${termSafe(e.key.complexity)}`)}: ` +
76
+ chalk.dim('— too few successful attempts to estimate (needs ≥3)'));
77
+ }
78
+ }
79
+ if (truncated)
80
+ console.log(chalk.dim(' … and more keys beyond the first 50 (oldest-activity keys elided)'));
81
+ return;
82
+ }
83
+ const modelId = options.model || resolveActiveModel(cwd);
84
+ const taskType = options.taskType || 'orchestrated';
85
+ const complexity = options.complexity || 'medium';
86
+ const key = { modelId, taskType, complexity };
87
+ const est = estimatePace({ modelId, taskType, complexity, limit, halfLifeSamples: halfLife, dbPath: options.db });
88
+ if (options.json) {
89
+ console.log(JSON.stringify({ key, estimate: est }, null, 2));
90
+ return;
91
+ }
92
+ if (!est) {
93
+ console.log(chalk.dim(`No estimate for ${termSafe(modelId)} ${termSafe(taskType)}/${termSafe(complexity)}: fewer than 3 successful attempts recorded.`));
94
+ console.log(chalk.dim(' `uap eta --all` lists every key with history; the estimator will not guess from thin data.'));
95
+ return;
96
+ }
97
+ console.log(chalk.bold('Measured pace — this project\'s own recorded attempts (EWMA):'));
98
+ console.log(' ' + renderEtaLine(key, est));
99
+ console.log(chalk.dim(` newest sample: ${termSafe(est.lastSampleAt)}`));
100
+ }
101
+ //# sourceMappingURL=eta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eta.js","sourceRoot":"","sources":["../../src/cli/eta.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,13 @@
15
15
  * per-build block/stagnation counters so it can never wedge the agent (the
16
16
  * balance between "never stop early" and "never spin forever").
17
17
  */
18
+ /**
19
+ * The status ETA line for `remaining` open ledger items, from the model's own
20
+ * measured pace — or null when there is no estimate (thin data must not guess).
21
+ * Exported for the tests; the optional dbPath is their seam (the status surface
22
+ * always reads the project store).
23
+ */
24
+ export declare function handsfreeEtaLine(modelId: string, remaining: number, dbPath?: string): string | null;
18
25
  /**
19
26
  * Stop-hook decision. Pure so it is unit-testable without process/exit:
20
27
  * returns { block, message } given the current ledger state + profile + input.
@@ -1 +1 @@
1
- {"version":3,"file":"handsfree.d.ts","sourceRoot":"","sources":["../../src/cli/handsfree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAiEH;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,OAAO,GACtB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CA2FvD;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CA8Jf"}
1
+ {"version":3,"file":"handsfree.d.ts","sourceRoot":"","sources":["../../src/cli/handsfree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAsBH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAanG;AA8CD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,OAAO,GACtB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CA2FvD;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAmKf"}
@@ -22,6 +22,27 @@ import { findUapConfigPath, modifyUapConfig } from '../utils/config-loader.js';
22
22
  import { loadLedger, initLedger, markItem, remainingItems, isComplete, formatRemaining, progress, syncLedgerFromTodos, } from '../delivery/completion-ledger.js';
23
23
  import { loadPersistenceConfig, resolveActiveModel } from '../delivery/handsfree-config.js';
24
24
  import { resolvePersistenceProfile } from '../delivery/persistence-profile.js';
25
+ import { estimatePace, remainingWallMs, formatDurationMs } from '../delivery/eta-estimate.js';
26
+ /**
27
+ * The status ETA line for `remaining` open ledger items, from the model's own
28
+ * measured pace — or null when there is no estimate (thin data must not guess).
29
+ * Exported for the tests; the optional dbPath is their seam (the status surface
30
+ * always reads the project store).
31
+ */
32
+ export function handsfreeEtaLine(modelId, remaining, dbPath) {
33
+ if (!Number.isFinite(remaining) || remaining <= 0)
34
+ return null;
35
+ // requireExisting: a status READ must not materialize an empty analytics
36
+ // store on a checkout that has no history yet.
37
+ const est = estimatePace({ modelId, dbPath, requireExisting: true });
38
+ const wall = remainingWallMs(est, remaining);
39
+ if (wall === null || !est)
40
+ return null;
41
+ const thin = est.confidence === 'thin' ? chalk.yellow(' (thin data)') : '';
42
+ return (` eta: ${chalk.bold(`~${formatDurationMs(wall)}`)} for the remaining ${remaining} item(s) ` +
43
+ chalk.dim(`(EWMA ~${formatDurationMs(est.etaMs)}/task, n=${est.samples}/${est.observed})`) +
44
+ thin);
45
+ }
25
46
  /** Fix C: hard cap on pre-ledger nudges so a planning stall can never wedge. */
26
47
  const PRE_LEDGER_MAX = Math.max(0, Number(process.env.UAP_HANDSFREE_PRELEDGER_MAX ?? 1));
27
48
  function statePath(cwd) {
@@ -184,6 +205,12 @@ export async function handsfreeCommand(sub, arg, options = {}) {
184
205
  const p = progress(ledger);
185
206
  console.log(` ledger: ${chalk.bold(`${p.done}/${p.total}`)} done (${p.pct}%), ` +
186
207
  `${p.failed} failed, ${p.pending} pending`);
208
+ // Measured-pace ETA for the remaining work: the
209
+ // operator's own history, scaled by this key's attempts-per-success.
210
+ // Shown ONLY when the estimate exists — thin data would guess.
211
+ const etaLine = handsfreeEtaLine(model, p.pending + p.failed);
212
+ if (etaLine)
213
+ console.log(etaLine);
187
214
  if (!isComplete(ledger))
188
215
  console.log(chalk.dim(' ' + formatRemaining(ledger, 6).replace(/\n/g, '\n ')));
189
216
  }