elanous 0.2.14 → 0.2.16

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 (350) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/chunks/{1307-6e7e0377c1db0bdc.js → 1307-b46d45b705e8c9ae.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-7b2b4f378354869a.js → page-3cb52e5ea8fc2fa5.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/botlab/page-c1e20d7d338713f1.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/bots/{page-1512d91c7491ed62.js → page-482c5dbde699947c.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/chat/{page-a627db94ec6904a0.js → page-8c2d9526164f5d09.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/control/{page-e6b84ab07d3567d5.js → page-a0e2a6d1bfb52fc5.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c53598cfce6156f2.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/editor/{page-fed69f9759d248d0.js → page-13a63953f53c6a23.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/exec/{page-15374909d8f1aebd.js → page-5d3c8429d04f3831.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/field/{page-012a742f81218b01.js → page-3193bdc0bbab047a.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/intake/{page-99effd9d893a43dc.js → page-6107ca8ddd5e3bdd.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/{layout-938de57ae0315e46.js → layout-dce686e582c23937.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/loops/{page-160089123a40dd91.js → page-1a61c7ca1ceb3ae7.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/app/market/{page-629e1ede482ec6eb.js → page-51f811fa119308b6.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-27e99b4e54a849d4.js → page-4167348049166852.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/not-found-de52e8fdf9095f7a.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-32cde713be47cddb.js → page-ca3952cd78a83ca2.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-e7e10cd3386222e5.js → page-5b042736aa113756.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-e64caff084f7ce21.js → page-1ebfe049c640e171.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-db07eaffba0df7e3.js → page-57924393e3e4e539.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/settings/{page-86f91034bd5cabe5.js → page-0f5cd57ca928f42b.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-abe8e3b74bea1ffc.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/share/{page-ce4d8a1215ce53a3.js → page-d28573d6792b4275.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-58043db78ba519ae.js → page-59f03fda76c03d22.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-357750dfb91bf960.js → page-d751de5f8062ff5e.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/term/{page-274703bc41b71333.js → page-fe1759f94003b32b.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/today/page-d64c984e855c8c68.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/{page-3b7e821dc492bdfd.js → page-9afdee66b820c76e.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-f8da5dc6f3752276.js → page-1ea64acadd704ace.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-2f810b18d89ecba6.js → page-3544f154c87b43ee.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +1 -0
  53. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
  54. package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
  55. package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +1 -0
  56. package/apps/pwa/out/approvals/index.html +1 -1
  57. package/apps/pwa/out/approvals/index.txt +15 -15
  58. package/apps/pwa/out/autopilot/index.html +1 -1
  59. package/apps/pwa/out/autopilot/index.txt +15 -15
  60. package/apps/pwa/out/board/index.html +1 -1
  61. package/apps/pwa/out/board/index.txt +15 -15
  62. package/apps/pwa/out/botlab/index.html +1 -1
  63. package/apps/pwa/out/botlab/index.txt +15 -15
  64. package/apps/pwa/out/bots/index.html +1 -1
  65. package/apps/pwa/out/bots/index.txt +15 -15
  66. package/apps/pwa/out/ceo/index.html +1 -0
  67. package/apps/pwa/out/ceo/index.txt +33 -0
  68. package/apps/pwa/out/chat/index.html +1 -1
  69. package/apps/pwa/out/chat/index.txt +15 -15
  70. package/apps/pwa/out/consult/index.html +1 -1
  71. package/apps/pwa/out/consult/index.txt +15 -15
  72. package/apps/pwa/out/control/index.html +1 -1
  73. package/apps/pwa/out/control/index.txt +15 -15
  74. package/apps/pwa/out/design-check/index.html +1 -1
  75. package/apps/pwa/out/design-check/index.txt +15 -15
  76. package/apps/pwa/out/editor/index.html +1 -1
  77. package/apps/pwa/out/editor/index.txt +15 -15
  78. package/apps/pwa/out/exec/index.html +1 -1
  79. package/apps/pwa/out/exec/index.txt +15 -15
  80. package/apps/pwa/out/field/index.html +1 -1
  81. package/apps/pwa/out/field/index.txt +15 -15
  82. package/apps/pwa/out/healthz +1 -1
  83. package/apps/pwa/out/index.html +1 -1
  84. package/apps/pwa/out/index.txt +15 -15
  85. package/apps/pwa/out/inside/index.html +1 -1
  86. package/apps/pwa/out/inside/index.txt +15 -15
  87. package/apps/pwa/out/intake/index.html +1 -1
  88. package/apps/pwa/out/intake/index.txt +15 -15
  89. package/apps/pwa/out/live/index.html +1 -1
  90. package/apps/pwa/out/live/index.txt +15 -15
  91. package/apps/pwa/out/loops/index.html +1 -1
  92. package/apps/pwa/out/loops/index.txt +15 -15
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +15 -15
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +15 -15
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +15 -15
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +15 -15
  101. package/apps/pwa/out/ops/checklist/index.html +1 -1
  102. package/apps/pwa/out/ops/checklist/index.txt +15 -15
  103. package/apps/pwa/out/ops/release/index.html +1 -1
  104. package/apps/pwa/out/ops/release/index.txt +15 -15
  105. package/apps/pwa/out/ops/seats/index.html +1 -1
  106. package/apps/pwa/out/ops/seats/index.txt +15 -15
  107. package/apps/pwa/out/outputs/index.html +1 -1
  108. package/apps/pwa/out/outputs/index.txt +15 -15
  109. package/apps/pwa/out/reflection/index.html +1 -1
  110. package/apps/pwa/out/reflection/index.txt +16 -16
  111. package/apps/pwa/out/scheduler/index.html +1 -1
  112. package/apps/pwa/out/scheduler/index.txt +15 -15
  113. package/apps/pwa/out/sessions/index.html +1 -1
  114. package/apps/pwa/out/sessions/index.txt +15 -15
  115. package/apps/pwa/out/settings/devices/index.html +1 -1
  116. package/apps/pwa/out/settings/devices/index.txt +15 -15
  117. package/apps/pwa/out/settings/index.html +1 -1
  118. package/apps/pwa/out/settings/index.txt +15 -15
  119. package/apps/pwa/out/setup/done/index.html +1 -1
  120. package/apps/pwa/out/setup/done/index.txt +15 -15
  121. package/apps/pwa/out/setup/index.html +1 -1
  122. package/apps/pwa/out/setup/index.txt +15 -15
  123. package/apps/pwa/out/share/index.html +1 -1
  124. package/apps/pwa/out/share/index.txt +15 -15
  125. package/apps/pwa/out/showroom/index.html +1 -1
  126. package/apps/pwa/out/showroom/index.txt +15 -15
  127. package/apps/pwa/out/tasks/index.html +1 -1
  128. package/apps/pwa/out/tasks/index.txt +15 -15
  129. package/apps/pwa/out/term/index.html +1 -1
  130. package/apps/pwa/out/term/index.txt +15 -15
  131. package/apps/pwa/out/today/index.html +1 -1
  132. package/apps/pwa/out/today/index.txt +15 -15
  133. package/apps/pwa/out/trace/index.html +1 -1
  134. package/apps/pwa/out/trace/index.txt +15 -15
  135. package/apps/pwa/out/vault/index.html +1 -1
  136. package/apps/pwa/out/vault/index.txt +15 -15
  137. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  138. package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
  139. package/apps/pwa/out/workflows/index.html +1 -1
  140. package/apps/pwa/out/workflows/index.txt +15 -15
  141. package/apps/pwa/out/workspace/index.html +1 -1
  142. package/apps/pwa/out/workspace/index.txt +15 -15
  143. package/apps/pwa/out/worktrees/index.html +1 -1
  144. package/apps/pwa/out/worktrees/index.txt +15 -15
  145. package/graphs/doc-gen/doc-gen.yaml +24 -0
  146. package/graphs/doc-gen/recipes.yaml +3 -0
  147. package/graphs/heal/heal-loop.yaml +1 -1
  148. package/graphs/implement-loop.yaml +1 -1
  149. package/graphs/orchestrator/orchestrator.yaml +7 -0
  150. package/graphs/orchestrator/recipes.yaml +3 -0
  151. package/graphs/release/recipes.yaml +3 -0
  152. package/graphs/release/release-loop.yaml +4 -2
  153. package/graphs/steward/recipes.yaml +2 -1
  154. package/package.json +1 -1
  155. package/scripts/brand/check.ts +7 -5
  156. package/scripts/ci-public-export-import-gate.ts +154 -0
  157. package/scripts/ci-test-interference-gate.ts +5 -1
  158. package/scripts/ci-typecheck-changed.ts +82 -31
  159. package/scripts/docs/doc-rot.ts +164 -0
  160. package/scripts/docs/generate-graph-anatomy.ts +437 -0
  161. package/scripts/docs-cli-check.ts +4 -4
  162. package/scripts/expose-rubric-suggest.ts +18 -12
  163. package/scripts/goals/goal-census.ts +343 -0
  164. package/scripts/lecture-note/node.ts +31 -11
  165. package/scripts/lessons/handbook-page.ts +30 -3
  166. package/scripts/marketing/generate-ax-brief.ts +140 -0
  167. package/scripts/marketing/generate-feature-map.ts +118 -0
  168. package/scripts/measure/author-section-contribution.ts +84 -0
  169. package/scripts/release-loop/checklist-gate-node.ts +53 -14
  170. package/scripts/release-loop/cut-branch.ts +43 -13
  171. package/scripts/release-loop/gate-node.ts +82 -14
  172. package/scripts/release-loop/vault-note-node.ts +257 -8
  173. package/scripts/release-story/draft.ts +57 -9
  174. package/scripts/rhythm/daily.ts +263 -76
  175. package/scripts/rhythm/monthly.ts +97 -0
  176. package/scripts/rhythm/weekly.ts +30 -9
  177. package/scripts/smart-merge/resync-shadow.ts +147 -0
  178. package/src/acp/client.ts +17 -2
  179. package/src/agent-env/profile.ts +161 -0
  180. package/src/agent-mission/driver.ts +51 -1
  181. package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
  182. package/src/agent-mission/supervisor-metrics.ts +218 -0
  183. package/src/agent-substrate/pr-reviewer.ts +2 -0
  184. package/src/ask-user-question/pending-questions.ts +5 -0
  185. package/src/auth/github-app-token.ts +112 -13
  186. package/src/autopilot/build/llm-conflict-merge.ts +32 -6
  187. package/src/autopilot/track-agent-shadow.ts +2 -1
  188. package/src/briefing/brief-items.ts +276 -0
  189. package/src/budget/usage-outcomes.ts +204 -0
  190. package/src/claims/claims-render.ts +14 -5
  191. package/src/cli/agent-env-cli.ts +21 -0
  192. package/src/cli/brief-cli.ts +73 -0
  193. package/src/cli/decisions-cli.ts +9 -0
  194. package/src/cli/doctor-cli.ts +23 -1
  195. package/src/cli/doctor-node-modules.ts +147 -0
  196. package/src/cli/intake-cli.ts +46 -17
  197. package/src/cli/know-cli.ts +20 -0
  198. package/src/cli/logs-cli.ts +43 -1
  199. package/src/cli/pod-cli.ts +2 -0
  200. package/src/cli/pr-cli.ts +80 -12
  201. package/src/cli/release-cli.ts +66 -29
  202. package/src/cli/seat-requests-cli.ts +52 -0
  203. package/src/cli/self-update.ts +200 -1
  204. package/src/cli/usage-cli.ts +26 -0
  205. package/src/context-bus/context-now-public.ts +16 -6
  206. package/src/context-bus/context-now.ts +3 -1
  207. package/src/context-bus/coord-events.ts +6 -4
  208. package/src/decisions/decision-cards.ts +33 -7
  209. package/src/decisions/proact-meter.ts +286 -0
  210. package/src/decisions/proact1-lite.ts +86 -0
  211. package/src/decisions/web-push-decision-cards.ts +1 -1
  212. package/src/discord-self-message.ts +9 -0
  213. package/src/domains/alert-audit.ts +271 -0
  214. package/src/domains/core-tools.ts +4 -3
  215. package/src/domains/ops-facts-tool.ts +31 -1
  216. package/src/flow-loop/split.ts +24 -0
  217. package/src/harness/harness-cli-command.ts +147 -17
  218. package/src/harness/harness-incidents.ts +2 -0
  219. package/src/harness/harness-pod-dispatch.ts +67 -8
  220. package/src/harness/harness-queue.ts +380 -50
  221. package/src/harness/harness-seams.ts +13 -3
  222. package/src/harness/helper-repair.ts +218 -0
  223. package/src/harness/helper-scan.ts +27 -4
  224. package/src/harness/launch-stamp.ts +236 -0
  225. package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
  226. package/src/harness/ledger-write-guard.ts +35 -0
  227. package/src/harness/nested-elanous-policy.ts +114 -0
  228. package/src/harness/pod-memory-policy.ts +15 -0
  229. package/src/harness/repository-name.ts +55 -0
  230. package/src/hq/fence-audit.ts +90 -0
  231. package/src/hq/hq.ts +70 -6
  232. package/src/hq/lease.ts +9 -7
  233. package/src/hq/move-plan.ts +126 -0
  234. package/src/hq/promote.ts +31 -0
  235. package/src/hq/transfer.ts +185 -0
  236. package/src/index.ts +189 -13
  237. package/src/intake-plane/check.ts +63 -0
  238. package/src/intake-plane/digest.ts +65 -21
  239. package/src/intake-plane/front-classifier.ts +6 -0
  240. package/src/intake-plane/items.ts +3 -2
  241. package/src/intake-plane/lens.ts +177 -0
  242. package/src/intake-plane/route.ts +145 -1
  243. package/src/knowledge/know-find.ts +105 -0
  244. package/src/llm.ts +75 -19
  245. package/src/loops/budget.ts +36 -5
  246. package/src/loops/checker.ts +2 -2
  247. package/src/loops/orchestrator/author-depth.ts +65 -9
  248. package/src/loops/orchestrator/author-input-check.ts +8 -3
  249. package/src/loops/orchestrator/author-ledger.ts +3 -1
  250. package/src/loops/orchestrator/finish-rate.ts +14 -3
  251. package/src/loops/orchestrator/seat-share.ts +56 -0
  252. package/src/loops/orchestrator/tick.ts +107 -29
  253. package/src/loops/orchestrator/traffic.ts +117 -11
  254. package/src/loops/registry.ts +142 -61
  255. package/src/maturity/bot-slash-catalog.ts +11 -2
  256. package/src/maturity/feature-maturity.ts +4 -3
  257. package/src/maturity/tui-slash-availability.ts +40 -0
  258. package/src/mss/logging/log-store.ts +35 -4
  259. package/src/nexus/api/http-server.ts +28 -14
  260. package/src/nexus/api/loop-edges.ts +173 -0
  261. package/src/nexus/api/schedules-read.ts +13 -0
  262. package/src/nexus/api/seat-requests.ts +79 -56
  263. package/src/oauth/overload-failover-launch.ts +75 -0
  264. package/src/oauth/overload-failover.ts +137 -0
  265. package/src/prompt-enhance/enhance.ts +6 -1
  266. package/src/release-loop/auto-start.ts +6 -3
  267. package/src/release-loop/checklist.ts +19 -5
  268. package/src/release-loop/feature-store.ts +59 -31
  269. package/src/release-loop/merged-pr-checklist.ts +125 -0
  270. package/src/release-loop/placement.ts +98 -29
  271. package/src/seat-dispatch/seat-ask.ts +33 -6
  272. package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
  273. package/src/seat-dispatch/seat-request-ledger.ts +133 -0
  274. package/src/seat-loop/seat-loop.ts +215 -12
  275. package/src/self-dev/ask-launch-flow.ts +22 -1
  276. package/src/self-dev/authored-goal-trace.ts +64 -0
  277. package/src/self-dev/dev-cli.ts +19 -1
  278. package/src/self-dev/dev-pipeline.ts +14 -9
  279. package/src/self-dev/draft-sweep.ts +90 -10
  280. package/src/self-dev/draft-triage-rules.ts +25 -8
  281. package/src/self-dev/entrance-registry.ts +10 -6
  282. package/src/self-dev/old-door.ts +73 -0
  283. package/src/self-dev/orchestrate.ts +15 -1
  284. package/src/self-dev/review-provider-fallback.ts +6 -1
  285. package/src/self-dev/run-supervisor.ts +49 -2
  286. package/src/self-implement/app-api-budget.ts +166 -0
  287. package/src/self-implement/budget-gate.ts +7 -5
  288. package/src/self-implement/follow-up-goals.ts +289 -0
  289. package/src/self-implement/frozen-merges.ts +3 -2
  290. package/src/self-implement/goal-author.ts +50 -6
  291. package/src/self-implement/goal-clarification-escalation.ts +5 -0
  292. package/src/self-implement/heal-intake.ts +120 -2
  293. package/src/self-implement/heal-triage.ts +44 -0
  294. package/src/self-implement/host-regate.ts +9 -2
  295. package/src/self-implement/l8-merge-queue.ts +132 -0
  296. package/src/self-implement/orchestrator.ts +325 -47
  297. package/src/self-implement/pipeline-shape.ts +1 -1
  298. package/src/self-implement/revert-guard.ts +42 -7
  299. package/src/self-implement/run-ledger.ts +6 -0
  300. package/src/self-implement/run-status-mapping.ts +2 -0
  301. package/src/self-implement/seams.ts +45 -16
  302. package/src/self-implement/self-implement-cli.ts +1 -0
  303. package/src/self-implement/speed-claims.ts +338 -0
  304. package/src/session-runtime/index.ts +2 -0
  305. package/src/session-runtime/retry-api.ts +87 -0
  306. package/src/session-runtime/retry-policy.ts +107 -6
  307. package/src/steward/triage.ts +74 -3
  308. package/src/steward/working-backwards.ts +20 -2
  309. package/src/task-cards/card-cli.ts +25 -0
  310. package/src/task-cards/card-store.ts +5 -3
  311. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
  312. package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
  313. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
  314. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
  315. package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
  316. package/src/task-orchestrator/surfaces/pod-pool.ts +70 -9
  317. package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
  318. package/src/task-orchestrator/surfaces/self-implement-pod.ts +166 -46
  319. package/src/task-orchestrator/surfaces/self-implement.ts +7 -1
  320. package/src/telegram-commands.ts +5 -0
  321. package/src/telegram.ts +72 -4
  322. package/src/user-config.ts +64 -7
  323. package/src/version/packed-revision.json +1 -1
  324. package/src/workflow-runtime/nodes/bash.ts +9 -1
  325. package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2ce5471926761fdd.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +0 -1
  348. package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +0 -1
  349. package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +0 -1
  350. /package/apps/pwa/out/_next/static/{x6mjI6vqOkVVu0jFuDkz1 → rv9cierV0jSOvDBm1g0XB}/_ssgManifest.js +0 -0
@@ -16,7 +16,7 @@ export const TERMINAL_STAGES_BY_NODE = {
16
16
  implement: ['aborted', 'timed-out', 'soft-stopped', 'parked'],
17
17
  gate: [],
18
18
  review: ['review-blocked'],
19
- rework: ['gate-failed', 'review-blocked'],
19
+ rework: ['gate-failed', 'review-blocked', 'no-changes'],
20
20
  'main-sync': ['merge-conflict'],
21
21
  regate: ['gate-failed'],
22
22
  'open-pr': ['pr-declined', 'pr-opened', 'worktree-completed'],
@@ -3,10 +3,17 @@ import { lstatSync, readFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { debug } from '../debug/log.js';
5
5
 
6
- export type RevertGuardResult = { reverted: string[]; protected: string[]; warning?: string };
6
+ export type RevertGuardResult = { reverted: string[]; protected: string[]; staleDeleted?: string[]; outsideDeleted?: string[]; warning?: string };
7
+
8
+ function isTargetPath(file: string, targets: readonly string[]): boolean {
9
+ return targets.some((target) => {
10
+ const path = target.replace(/^\.\//, '').replace(/\/+$/, '');
11
+ return path !== '' && (file === path || file.startsWith(`${path}/`));
12
+ });
13
+ }
7
14
 
8
15
  /** Compare the worktree's exact bytes with blobs from recent HEAD ancestry, before add -A. */
9
- export function dropStaleReverts(cwd: string, targets: readonly string[], runId: string, depth = 50): RevertGuardResult {
16
+ export function dropStaleReverts(cwd: string, targets: readonly string[], runId: string, depth = 50, base?: string): RevertGuardResult {
10
17
  const result: RevertGuardResult = { reverted: [], protected: [] };
11
18
  const git = (...args: string[]): Buffer => execFileSync('git', args, { cwd, timeout: 20_000, maxBuffer: 16 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] });
12
19
  const reason = (error: unknown): string => (error instanceof Error ? error.message : String(error)).replace(/\s+/g, ' ').slice(0, 240);
@@ -15,6 +22,38 @@ export function dropStaleReverts(cwd: string, targets: readonly string[], runId:
15
22
  const commits = git('rev-list', `--max-count=${limit + 1}`, 'HEAD').toString('utf8').trim().split('\n');
16
23
  const changed = git('diff', '--name-only', '--diff-filter=M', '-z', 'HEAD', '--').toString('utf8').split('\0').filter(Boolean);
17
24
  const staged = git('diff', '--cached', '--name-only', '--diff-filter=M', '-z', 'HEAD', '--').toString('utf8').split('\0').filter(Boolean);
25
+ const deleted = new Set([
26
+ ...git('diff', '--name-only', '--diff-filter=D', '-z', 'HEAD', '--').toString('utf8').split('\0').filter(Boolean),
27
+ ...git('diff', '--cached', '--name-only', '--diff-filter=D', '-z', 'HEAD', '--').toString('utf8').split('\0').filter(Boolean),
28
+ ]);
29
+ for (const file of deleted) {
30
+ if (isTargetPath(file, targets)) continue;
31
+ try {
32
+ // add -A stages the worktree: even a dangling symlink replaces the staged deletion.
33
+ try {
34
+ lstatSync(join(cwd, file));
35
+ continue;
36
+ } catch (error) {
37
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
38
+ }
39
+ // ls-tree returning an empty entry means absent; a failed lookup must not look like absence.
40
+ const headEntry = git('ls-tree', 'HEAD', '--', file).toString('utf8').trim();
41
+ if (!/^\d+ blob [0-9a-f]+\t/.test(headEntry)) continue;
42
+ const baseEntry = base ? git('ls-tree', base, '--', file).toString('utf8').trim() : null;
43
+ if (baseEntry === '' && base) {
44
+ git('checkout', 'HEAD', '--', file);
45
+ (result.staleDeleted ??= []).push(file);
46
+ result.reverted.push(file);
47
+ debug.log('self-implement.revert-guard', 'reverted-stale-delete', { runId, files: [file] });
48
+ } else {
49
+ (result.outsideDeleted ??= []).push(file);
50
+ debug.log('self-implement.revert-guard', 'outside-delete', { runId, files: [file] }, { level: 'warn' });
51
+ }
52
+ } catch (error) {
53
+ result.warning = `git inspection/checkout failed for ${file}: ${reason(error)}`;
54
+ debug.log('self-implement.revert-guard', 'fail-open', { runId, reason: result.warning }, { level: 'warn' });
55
+ }
56
+ }
18
57
  for (const file of new Set([...changed, ...staged])) {
19
58
  try {
20
59
  if (!lstatSync(join(cwd, file)).isFile()) continue;
@@ -37,11 +76,7 @@ export function dropStaleReverts(cwd: string, targets: readonly string[], runId:
37
76
  }
38
77
  }
39
78
  if (!matchedCommit) continue;
40
- const scoped = targets.some((target) => {
41
- const path = target.replace(/^\.\//, '').replace(/\/+$/, '');
42
- return path !== '' && (file === path || file.startsWith(`${path}/`));
43
- });
44
- if (scoped) {
79
+ if (isTargetPath(file, targets)) {
45
80
  result.protected.push(file);
46
81
  debug.log('self-implement.revert-guard', 'protected-target', { runId, files: [file], matchedCommit }, { level: 'warn' });
47
82
  } else {
@@ -1,5 +1,6 @@
1
1
  import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync } from 'node:fs';
2
2
  import { dirname, isAbsolute, join, resolve } from 'node:path';
3
+ import { refuseProductionLedgerWriteInTest } from '../harness/ledger-write-guard.js';
3
4
  import { tracedPathReferences, verbatimOriginalAsk } from './goal-author.js';
4
5
  import { parseAskTargetPathHintsResult } from '../self-dev/launch-preflight.js';
5
6
  import { isRunStatus } from './run-status-mapping.js';
@@ -93,6 +94,10 @@ export interface RunOriginData extends Record<string, unknown> {
93
94
  substrate: string;
94
95
  instance: string;
95
96
  elanousVersion: string;
97
+ /** install.json version. 못 읽으면 «unknown». */
98
+ installedVersion: string;
99
+ /** install.json commit. 못 읽으면 «unknown». */
100
+ installedCommit: string;
96
101
  podName?: string;
97
102
  nodeName?: string;
98
103
  podNamespace?: string;
@@ -402,6 +407,7 @@ export function describeFederatedMissingRunLedger(runId: string, options: Federa
402
407
 
403
408
  /** Append one observer event. Callers own fail-soft handling. */
404
409
  export function appendRunLedgerEntry(entry: RunLedgerEntry, dir = runLedgerDir()): void {
410
+ if (refuseProductionLedgerWriteInTest(dir, 'run-ledger')) return;
405
411
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
406
412
  const live = runLedgerPath(entry.runId, dir);
407
413
  withRunLedgerLock(dir, entry.runId, () => {
@@ -16,6 +16,7 @@ export type SelfImplementStage =
16
16
  | 'worktree-completed'
17
17
  | 'gate-failed'
18
18
  | 'review-blocked'
19
+ | 'no-changes'
19
20
  | 'merge-conflict'
20
21
  | 'aborted'
21
22
  | 'timed-out'
@@ -30,6 +31,7 @@ const STAGE_OUTCOMES = {
30
31
  'worktree-completed': { runStatus: 'completed' },
31
32
  'gate-failed': { runStatus: 'failed', failureKind: 'gate' },
32
33
  'review-blocked': { runStatus: 'failed', failureKind: 'review' },
34
+ 'no-changes': { runStatus: 'failed', failureKind: 'review' },
33
35
  'merge-conflict': { runStatus: 'failed', failureKind: 'merge-conflict' },
34
36
  aborted: { runStatus: 'failed', failureKind: 'aborted' },
35
37
  'timed-out': { runStatus: 'failed', failureKind: 'timed-out' },
@@ -24,6 +24,9 @@ import { isAbsolute, join, relative, resolve, sep } from 'node:path';
24
24
  import { promisify } from 'node:util';
25
25
  import { forkSessionById, HARNESS_SESSION_ORIGIN, isHarnessSessionOrigin } from '../session/index.js';
26
26
  import { configuredWorktreeRoot, getUserConfig, resolveRoleLlm } from '../user-config.js';
27
+ import { elanousStateRoot } from '../autopilot/state-paths.js';
28
+ import { addHarnessQueue } from '../harness/harness-queue.js';
29
+ import { readFollowUpMode } from './follow-up-goals.js';
27
30
  import { getProviderForConfig } from '../llm.js';
28
31
  import { createWorktree, DEFAULT_BRANCH_WORKTREE_BASE, linkWorktreeDependencies, removeWorktree, resolveDefaultBranchBase, resolveMainRepoRoot } from '../git-fs/worktree.js';
29
32
  import { isWorktreeInUse, listActiveTerminalDirectories } from '../harness/harness-clean.js';
@@ -77,7 +80,8 @@ import { tscEnv, assessTypecheckExecution, classifyTypecheckErrors, diffTypechec
77
80
  import { nestCapReached, childNestEnv, nestInfo } from '../agent/nest-depth.js';
78
81
  import { harnessBoundaryEnv, harnessBoundaryRequestsEnv, harnessBoundaryResponsesEnv, harnessSpaceEnv, getHarnessSpace, normalizeSpaceId, getHarnessRunId, resolveRunIdentity } from '../harness/harness-space.js';
79
82
  import { debug } from '../debug/log.js';
80
- import { RELEASE_PATH_HOLD_MARKER, RELEASE_PATH_LABEL, releasePathHoldShouldPost, releasePathHoldCommentsArgs, releasePrFilePaths } from '../self-dev/release-path-guard.js';
83
+ import { RELEASE_PATH_HOLD_MARKER, RELEASE_PATH_LABEL, releasePathHoldShouldPost, releasePathHoldCommentsArgs } from '../self-dev/release-path-guard.js';
84
+ import { readPrFilesWithRateLimitFallback, runGhApp } from './app-api-budget.js';
81
85
  import { emitDecision } from '../live/detail-switch.js';
82
86
  import { gateDecision, reviewDecision, mergeDecision } from './decision-events.js';
83
87
  import { LogStore } from '../mss/logging/log-store.js';
@@ -110,6 +114,10 @@ declare module './orchestrator.js' {
110
114
  /** PR close(+브랜치 삭제). 기본 구성은 PrManager.closePr 위임(같은 인자·같은 반환).
111
115
  * 오케스트레이터는 아직 부르지 않는다 — 그 배선은 조각 ②다. */
112
116
  closePr?: (prUrl: string, comment?: string) => boolean;
117
+ /** Find an issue comment on this PR by its body marker; absence is distinct from a failed lookup. */
118
+ findPrComment?: (opts: { number: number; marker: string; cwd: string; comments?: unknown }) => Promise<{ id: number; body: string } | undefined>;
119
+ /** Replace an existing PR issue comment by ID, using the same GitHub App identity as postPrComment. */
120
+ editPrComment?: (opts: { id: number; body: string; cwd: string }) => Promise<void>;
113
121
  }
114
122
  }
115
123
  import { harnessPolicyEnv } from './harness-policy.js';
@@ -1415,6 +1423,11 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
1415
1423
  const { supersedeMergedGoalDrafts } = await import('../harness/harness-cli-command.js');
1416
1424
  await supersedeMergedGoalDrafts(number, cwd);
1417
1425
  },
1426
+ followUp: {
1427
+ mode: readFollowUpMode(),
1428
+ stateRoot: elanousStateRoot(),
1429
+ enqueue: (item) => addHarnessQueue({ seat: item.seat, say: item.say, idempotencyKey: item.idempotencyKey }),
1430
+ },
1418
1431
  lineageSupersede: {
1419
1432
  listOpenDrafts: () => listOpenDraftsForLineage(),
1420
1433
  readRunLedger: (runId) => {
@@ -2394,46 +2407,62 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
2394
2407
  postPrComment: async ({ number, body, cwd }) => {
2395
2408
  // Round comments go out as the GitHub App like the PR itself — a personal `gh` login made the owner a
2396
2409
  // participant and mailed them on merge (#22089).
2397
- if (body.startsWith(RELEASE_PATH_HOLD_MARKER)) {
2410
+ if (body.startsWith(RELEASE_PATH_HOLD_MARKER)) {
2398
2411
  // Several merge surfaces may hold the same PR; post the release-path hold comment once.
2399
2412
  let history: unknown;
2400
2413
  try {
2401
- history = JSON.parse((await execFileAsync('gh', releasePathHoldCommentsArgs(number), {
2414
+ history = JSON.parse((await runGhApp('post-pr-comment-history', releasePathHoldCommentsArgs(number), {
2402
2415
  cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env),
2403
2416
  })).stdout);
2404
2417
  } catch { history = undefined; }
2405
2418
  if (!releasePathHoldShouldPost(() => history)) return;
2406
2419
  }
2407
- await execFileAsync('gh', ['pr', 'comment', String(number), '--body', body], {
2420
+ await runGhApp('post-pr-comment', ['pr', 'comment', String(number), '--body', body], {
2421
+ cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env),
2422
+ });
2423
+ },
2424
+
2425
+ findPrComment: async ({ number, marker, cwd, comments }) => {
2426
+ const pages: unknown = comments ?? JSON.parse((await runGhApp('find-pr-comment', releasePathHoldCommentsArgs(number), {
2427
+ cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env),
2428
+ })).stdout);
2429
+ if (!Array.isArray(pages)) throw new Error('gh api PR comments returned an invalid list');
2430
+ const entries = pages.flatMap((page: unknown) => Array.isArray(page) ? page : [page]);
2431
+ for (const entry of entries) {
2432
+ if (!entry || typeof entry !== 'object' || !('body' in entry) || typeof entry.body !== 'string'
2433
+ || !('id' in entry) || typeof entry.id !== 'number' || !Number.isSafeInteger(entry.id) || entry.id <= 0) {
2434
+ throw new Error('gh api PR comments returned an invalid comment');
2435
+ }
2436
+ }
2437
+ return entries.find((entry: { id: number; body: string }) => entry.body.includes(marker));
2438
+ },
2439
+
2440
+ editPrComment: async ({ id, body, cwd }) => {
2441
+ await runGhApp('edit-pr-comment', ['api', '--method', 'PATCH', `repos/{owner}/{repo}/issues/comments/${id}`, '-f', `body=${body}`], {
2408
2442
  cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env),
2409
2443
  });
2410
2444
  },
2411
2445
 
2412
2446
  addPrLabel: async ({ number, label, cwd }) => {
2413
2447
  if (label === RELEASE_PATH_LABEL) {
2414
- const existing = await execFileAsync('gh', ['label', 'list', '--search', label, '--json', 'name'], {
2448
+ const existing = await runGhApp('add-pr-label-list', ['label', 'list', '--search', label, '--json', 'name'], {
2415
2449
  cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, env: ghAutomationEnv(process.env),
2416
2450
  });
2417
2451
  const names: unknown = JSON.parse(existing.stdout);
2418
2452
  if (!Array.isArray(names)) throw new Error('gh label list returned an invalid list');
2419
2453
  if (!names.some((item: unknown) => !!item && typeof item === 'object' && 'name' in item && item.name === label)) {
2420
- await execFileAsync('gh', ['label', 'create', label, '--color', 'D93F0B', '--description', 'Release path requires OP approval'], {
2454
+ await runGhApp('add-pr-label-create', ['label', 'create', label, '--color', 'D93F0B', '--description', 'Release path requires OP approval'], {
2421
2455
  cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, env: ghAutomationEnv(process.env),
2422
2456
  });
2423
2457
  }
2424
2458
  }
2425
- await execFileAsync('gh', ['pr', 'edit', String(number), '--add-label', label], {
2459
+ await runGhApp('add-pr-label', ['pr', 'edit', String(number), '--add-label', label], {
2426
2460
  cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, env: ghAutomationEnv(process.env),
2427
2461
  });
2428
2462
  },
2429
- readPrFiles: async ({ number, cwd }) => {
2430
- // Paginate the PR file API: a single `gh pr view --json files` can omit files after the first page.
2431
- const { stdout } = await execFileAsync('gh', [
2432
- 'api', '--paginate', '--slurp', '--method', 'GET', '-f', 'per_page=100',
2433
- `repos/{owner}/{repo}/pulls/${number}/files`,
2434
- ], { cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env) });
2435
- return releasePrFilePaths(JSON.parse(stdout));
2436
- },
2463
+ readPrFiles: async ({ number, cwd, base }) => readPrFilesWithRateLimitFallback({
2464
+ number, cwd, env: ghAutomationEnv(process.env), ...(base ? { base } : {}),
2465
+ }),
2437
2466
 
2438
2467
  ...(o.approvePr ? { approvePr: o.approvePr } : {}),
2439
2468
 
@@ -2513,7 +2542,7 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
2513
2542
  verdict: rr.verdict,
2514
2543
  mustFix: rr.mustFix,
2515
2544
  shouldFix: rr.shouldFix,
2516
- summary: renderReview(rr).slice(0, 2000),
2545
+ summary: rr.reviewed ? renderReview(rr).slice(0, 2000) : `리뷰 못 함(사유: ${rr.failureReason ?? '사유 미상'})`,
2517
2546
  reviewed: rr.reviewed ?? false,
2518
2547
  // ⛔⭐ 이 한 줄이 `orchestrator.ts` 의 「찍는 줄」에 값을 «흘린다». 빼면 그 줄은 살아 있는데 값이 안 온다.
2519
2548
  ...(rr.failureReason !== undefined ? { failureReason: rr.failureReason } : {}),
@@ -108,6 +108,7 @@ export function singleRunAsJobResult(feature: string, r: SelfImplementResultWith
108
108
  stage: r.stage,
109
109
  ...(!r.ok && r.detail ? { error: { message: r.detail } } : {}),
110
110
  ...(r.branch ? { branch: r.branch } : {}),
111
+ ...(r.harvestable === true && r.branch ? { harvestable: true } : {}),
111
112
  ...(r.worktreePath ? { worktreePath: r.worktreePath } : {}),
112
113
  ...(r.prUrl ? { prUrl: r.prUrl } : {}),
113
114
  ...(r.merged !== undefined ? { merged: r.merged } : {}),
@@ -0,0 +1,338 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { readFileSync, readdirSync } from 'node:fs';
3
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { prodInstanceRoot } from '../instance/resolve.js';
6
+ import { seatOfTree } from '../loops/orchestrator/traffic.js';
7
+ import { getUserConfig, ORCHESTRATOR_DEFAULTS } from '../user-config.js';
8
+ import { isRunStatus } from './run-status-mapping.js';
9
+ import { loadRunLedger, runLedgerDir, type RunLedgerEntry, type RunLedgerReader } from './run-ledger.js';
10
+
11
+ export type SpeedClaimsOutcome = 'merged' | 'failed' | 'abandoned' | 'completed-without-merge';
12
+ export type SpeedClaimsSeat = 'OP' | 'TC' | 'MK' | 'UX' | 'unknown';
13
+ export interface SpeedClaimsRow {
14
+ seat: SpeedClaimsSeat;
15
+ outcome: SpeedClaimsOutcome;
16
+ count: number;
17
+ underOneMinuteCount: number;
18
+ medianMinutes: number;
19
+ q1Minutes: number;
20
+ q3Minutes: number;
21
+ }
22
+ export interface SpeedClaimsMeasurement {
23
+ scope: 'self-implement-run-ledger';
24
+ ledgerDirectory: string;
25
+ status: 'measured' | 'cannot-measure';
26
+ completedCount: number;
27
+ inProgressCount: number;
28
+ cannotMeasureCount: number;
29
+ unknownSeatCount: number;
30
+ rows: SpeedClaimsRow[];
31
+ note: string;
32
+ parent: ParentSpeedClaimsMeasurement;
33
+ }
34
+ export interface ParentSpeedClaimsEvent {
35
+ ts: string;
36
+ category: string;
37
+ event: string;
38
+ data: unknown;
39
+ }
40
+
41
+ export interface ParentSpeedClaimsRow {
42
+ seat: 'OP' | 'TC' | 'MK' | 'UX' | 'direct' | 'unknown';
43
+ stage: string;
44
+ merged: boolean;
45
+ count: number;
46
+ medianMinutes: number;
47
+ q1Minutes: number;
48
+ q3Minutes: number;
49
+ }
50
+
51
+ export interface ParentSpeedClaimsMeasurement {
52
+ scope: 'self-dev.orchestrate-parent-logs';
53
+ status: 'measured' | 'cannot-measure';
54
+ completedCount: number;
55
+ inProgressCount: number;
56
+ cannotMeasureCount: number;
57
+ rows: ParentSpeedClaimsRow[];
58
+ note: string;
59
+ }
60
+
61
+ export interface SpeedClaimsOptions {
62
+ dir?: string;
63
+ list?: (path: string) => string[];
64
+ read?: RunLedgerReader;
65
+ readSeat?: (runId: string, ledgerDirectory: string, entries: readonly RunLedgerEntry[]) => SpeedClaimsSeat;
66
+ readParentEvents?: () => readonly ParentSpeedClaimsEvent[];
67
+ readParentQueue?: () => unknown;
68
+ }
69
+
70
+ const NOTE = 'Child-ledger elapsed minutes from the first child event (or checkpoint creation when unavailable) to the recorded child end, including retries and resumes; this is not the parent launch-to-done interval. Only finished runs enter quartiles (linear interpolation at ranks (n-1)*p). In-progress runs (including parked) and missing, unreadable or invalid timestamps are excluded, not assigned zero. Seats come from the same state root checkpoint, launch queue or recorded launch tree; unresolved seats are counted separately. A completed run without a recorded merge is not a merge.';
71
+ const CANONICAL_RUN_ID = /^run-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
72
+ const SEATS: readonly SpeedClaimsSeat[] = ['OP', 'TC', 'MK', 'UX', 'unknown'];
73
+
74
+ function checkpointFor(runId: string, ledgerDirectory: string): Record<string, unknown> | null {
75
+ try {
76
+ const value: unknown = JSON.parse(readFileSync(join(dirname(ledgerDirectory), 'self-dev-runs', `${runId}.json`), 'utf8'));
77
+ return value && typeof value === 'object' && !Array.isArray(value) && 'runId' in value && value.runId === runId
78
+ ? value as Record<string, unknown> : null;
79
+ } catch { return null; }
80
+ }
81
+
82
+ function seatFromLaunch(runId: string, ledgerDirectory: string, entries: readonly RunLedgerEntry[]): SpeedClaimsSeat {
83
+ const checkpoint = checkpointFor(runId, ledgerDirectory);
84
+ if (SEATS.includes(checkpoint?.seat as SpeedClaimsSeat) && checkpoint?.seat !== 'unknown') return checkpoint!.seat as SpeedClaimsSeat;
85
+ // Queue ids and launch ids are not run ids. Only an exact, unique ask-file match can attribute a queued launch.
86
+ const goalFiles = new Set(entries.filter((entry) => entry.event === 'start')
87
+ .map((entry) => entry.data.goalFile).filter((file): file is string => typeof file === 'string' && isAbsolute(file)));
88
+ if (goalFiles.size === 1) {
89
+ try {
90
+ const queue: unknown = JSON.parse(readFileSync(join(dirname(ledgerDirectory), 'harness', 'queue.json'), 'utf8'));
91
+ if (Array.isArray(queue)) {
92
+ const matched = queue.filter((row) => row && typeof row === 'object' && row.kind === 'ask'
93
+ && (row.status === 'launching' || row.status === 'launched' || row.status === 'finished')
94
+ && row.input === [...goalFiles][0] && SEATS.includes(row.seat));
95
+ if (matched.length === 1 && matched[0]!.seat !== 'unknown') return matched[0]!.seat as SpeedClaimsSeat;
96
+ }
97
+ } catch { /* An absent or unreadable queue cannot establish attribution. */ }
98
+ }
99
+ try {
100
+ const cfg = getUserConfig().loops?.orchestrator ?? ORCHESTRATOR_DEFAULTS;
101
+ const paths = entries.flatMap((entry) => entry.event === 'start' ? [entry.data.targetRoot] : entry.event === 'worktree' ? [entry.data.path] : []);
102
+ const seats = new Set<SpeedClaimsSeat>();
103
+ for (const path of paths) {
104
+ if (typeof path !== 'string' || !isAbsolute(path)) continue;
105
+ try {
106
+ const seat = seatOfTree(path, cfg);
107
+ if (seat) seats.add(seat);
108
+ } catch { /* A removed or unreadable launch tree does not prove a seat. */ }
109
+ }
110
+ if (seats.size === 1) return [...seats][0]!;
111
+ } catch { /* Unavailable seat configuration does not erase a readable run. */ }
112
+ return 'unknown';
113
+ }
114
+
115
+ function parseTime(timestamp: unknown): number | null {
116
+ if (typeof timestamp !== 'string') return null;
117
+ const match = /^(\d{4})-(\d\d)-(\d\d)T\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.exec(timestamp);
118
+ if (!match) return null;
119
+ const year = Number(match[1]);
120
+ const month = Number(match[2]);
121
+ const day = Number(match[3]);
122
+ const days = [31, year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
123
+ if (month < 1 || month > 12 || day < 1 || day > days[month - 1]!) return null;
124
+ const parsed = Date.parse(timestamp);
125
+ return Number.isFinite(parsed) ? parsed : null;
126
+ }
127
+
128
+ function time(entry: RunLedgerEntry | undefined): number | null {
129
+ return parseTime(entry?.timestamp);
130
+ }
131
+
132
+ function percentile(sorted: readonly number[], p: number): number {
133
+ const rank = (sorted.length - 1) * p;
134
+ const low = Math.floor(rank);
135
+ return sorted[low]! + (sorted[Math.ceil(rank)]! - sorted[low]!) * (rank - low);
136
+ }
137
+
138
+ function endOfRun(entries: readonly RunLedgerEntry[]): { entry: RunLedgerEntry; outcome: SpeedClaimsOutcome } | null {
139
+ let end: { entry: RunLedgerEntry; outcome: SpeedClaimsOutcome } | null = null;
140
+ for (const entry of entries) {
141
+ if (entry.event === 'start') { end = null; continue; }
142
+ if (entry.event === 'merged' && entry.data.merged === true) end = { entry, outcome: 'merged' };
143
+ if (end?.outcome === 'merged') continue;
144
+ if (entry.event === 'human-stop') end = { entry, outcome: 'abandoned' };
145
+ if (entry.event === 'run-status' && isRunStatus(entry.data.runStatus)) {
146
+ const status = entry.data.runStatus;
147
+ if (status === 'completed') end = { entry, outcome: 'completed-without-merge' };
148
+ else if (status === 'failed') end = { entry, outcome: entry.data.stage === 'aborted' ? 'abandoned' : 'failed' };
149
+ else if (status === 'cancelled') end = { entry, outcome: 'abandoned' };
150
+ else end = null;
151
+ }
152
+ }
153
+ return end;
154
+ }
155
+
156
+ const PARENT_NOTE = 'Elapsed minutes from the earliest valid parent self-dev.orchestrate start log to the latest valid done log, grouped by that done.data.outcomes[].stage and merged flag. Only runs with both events and valid timestamps enter quartiles. A start without done is in progress. Seats use the same-runId queue seat when available, or direct when a readable queue has no matching entry; an unreadable queue remains unknown. Parent log queries are read-only and independent of child run ledgers.';
157
+ type ParentSeat = ParentSpeedClaimsRow['seat'];
158
+ const PARENT_SEATS: readonly ParentSeat[] = ['OP', 'TC', 'MK', 'UX', 'direct', 'unknown'];
159
+
160
+ function object(value: unknown): Record<string, unknown> | null {
161
+ return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
162
+ }
163
+
164
+ /** Read the first-class logs CLI rather than opening logs.db in the measurement process. */
165
+ function readParentEventsFromLogs(): ParentSpeedClaimsEvent[] {
166
+ const command = fileURLToPath(new URL('../../bin/elanous.mjs', import.meta.url));
167
+ const events: ParentSpeedClaimsEvent[] = [];
168
+ let before: string | undefined;
169
+ const seenCursors = new Set<string>();
170
+ for (;;) {
171
+ const result = spawnSync(process.execPath, [command, `--test=${fileURLToPath(new URL('../../', import.meta.url))}`, 'logs', '--instance', 'prod', '--category', 'self-dev.orchestrate', '--json', '--json-data', '--limit', '5000', ...(before ? ['--before', before] : [])], {
172
+ encoding: 'utf8', maxBuffer: 64 * 1024 * 1024,
173
+ });
174
+ if (result.error || result.status !== 0) throw new Error(result.stderr || result.error?.message || 'parent logs query failed');
175
+ const lines = result.stdout.trim().split('\n').filter(Boolean).map((line) => JSON.parse(line) as Record<string, unknown>);
176
+ if (lines.some((line) => object(line._meta)?.type === 'log-query-unreadable-instances')) throw new Error('parent logs query incomplete');
177
+ events.push(...lines.filter((line): line is Record<string, unknown> & ParentSpeedClaimsEvent =>
178
+ line.store === 'prod' && line.category === 'self-dev.orchestrate' && typeof line.ts === 'string' && typeof line.event === 'string'));
179
+ const limit = lines.find((line) => object(line._meta)?.type === 'log-query-limit');
180
+ if (!limit) return events;
181
+ const meta = object(limit._meta);
182
+ const cursor = meta?.nextCursors ?? meta?.nextCursor;
183
+ if (typeof cursor !== 'number' && !object(cursor)) throw new Error('parent logs pagination incomplete');
184
+ const next = typeof cursor === 'number' ? String(cursor) : JSON.stringify(cursor);
185
+ if (seenCursors.has(next)) throw new Error('parent logs pagination repeated a cursor');
186
+ seenCursors.add(next);
187
+ before = next;
188
+ }
189
+ }
190
+
191
+ function readParentQueueFromState(): unknown {
192
+ return JSON.parse(readFileSync(join(prodInstanceRoot(), 'harness', 'queue.json'), 'utf8')) as unknown;
193
+ }
194
+
195
+ /** Parent launch-to-done census; no child ledger id is used as a join key. */
196
+ export function measureParentSpeedClaims(
197
+ events: readonly ParentSpeedClaimsEvent[], queue?: unknown,
198
+ ): ParentSpeedClaimsMeasurement {
199
+ const result: ParentSpeedClaimsMeasurement = {
200
+ scope: 'self-dev.orchestrate-parent-logs', status: 'measured', completedCount: 0,
201
+ inProgressCount: 0, cannotMeasureCount: 0, rows: [], note: PARENT_NOTE,
202
+ };
203
+ const queueRows = Array.isArray(queue) ? queue : object(queue)?.items;
204
+ const queueByRun = new Map<string, ParentSeat | null>();
205
+ if (Array.isArray(queueRows)) for (const raw of queueRows) {
206
+ const entry = object(raw);
207
+ if (entry && typeof entry.runId === 'string' && PARENT_SEATS.includes(entry.seat as ParentSeat) && entry.seat !== 'direct') {
208
+ const seat = entry.seat as ParentSeat;
209
+ queueByRun.set(entry.runId, queueByRun.has(entry.runId) && queueByRun.get(entry.runId) !== seat ? null : seat);
210
+ }
211
+ }
212
+ const runs = new Map<string, { start?: number; end?: number; outcomes?: unknown }>();
213
+ for (const event of events) {
214
+ if (event.category !== 'self-dev.orchestrate' || (event.event !== 'start' && event.event !== 'done')) continue;
215
+ const data = object(event.data);
216
+ const runId = data?.runId;
217
+ if (typeof runId !== 'string' || !CANONICAL_RUN_ID.test(runId)) { result.cannotMeasureCount++; continue; }
218
+ const run = runs.get(runId) ?? {};
219
+ const at = parseTime(event.ts);
220
+ if (event.event === 'start') {
221
+ if (at !== null) run.start = run.start === undefined || !Number.isFinite(run.start) ? at : Math.min(run.start, at);
222
+ else if (run.start === undefined) run.start = NaN;
223
+ } else if (at !== null && (run.end === undefined || !Number.isFinite(run.end) || at > run.end)) {
224
+ run.end = at;
225
+ run.outcomes = data?.outcomes;
226
+ } else if (at === null && run.end === undefined) {
227
+ run.end = NaN;
228
+ }
229
+ runs.set(runId, run);
230
+ }
231
+ const groups = new Map<string, { seat: ParentSeat; stage: string; merged: boolean; values: number[] }>();
232
+ for (const [runId, run] of runs) {
233
+ if (run.start === undefined && run.end !== undefined) { result.cannotMeasureCount++; continue; }
234
+ if (run.end === undefined) { result.inProgressCount++; continue; }
235
+ if (run.start === undefined || !Number.isFinite(run.start) || !Number.isFinite(run.end) || run.end < run.start || !Array.isArray(run.outcomes) || run.outcomes.length === 0) {
236
+ result.cannotMeasureCount++;
237
+ continue;
238
+ }
239
+ const seat = queueByRun.has(runId) ? (queueByRun.get(runId) ?? 'unknown') : Array.isArray(queueRows) ? 'direct' : 'unknown';
240
+ const outcomes = run.outcomes.map(object);
241
+ if (outcomes.some((outcome) => !outcome)) {
242
+ result.cannotMeasureCount++;
243
+ continue;
244
+ }
245
+ for (const outcome of outcomes) {
246
+ const stage = typeof outcome!.stage === 'string' ? outcome!.stage : 'unknown';
247
+ const merged = outcome!.merged === true;
248
+ const key = JSON.stringify([seat, stage, merged]);
249
+ const group = groups.get(key) ?? { seat, stage, merged, values: [] };
250
+ group.values.push((run.end - run.start!) / 60_000);
251
+ groups.set(key, group);
252
+ }
253
+ result.completedCount++;
254
+ }
255
+ result.rows = [...groups.values()].map(({ seat, stage, merged, values }) => {
256
+ values.sort((a, b) => a - b);
257
+ return { seat, stage, merged, count: values.length, medianMinutes: percentile(values, 0.5), q1Minutes: percentile(values, 0.25), q3Minutes: percentile(values, 0.75) };
258
+ }).sort((a, b) => PARENT_SEATS.indexOf(a.seat) - PARENT_SEATS.indexOf(b.seat) || a.stage.localeCompare(b.stage));
259
+ if (result.cannotMeasureCount > 0) result.status = 'cannot-measure';
260
+ return result;
261
+ }
262
+
263
+ /** Read-only elapsed-time census of canonical local run ledgers, including unsuccessful runs. */
264
+ export function measureSpeedClaims(options: SpeedClaimsOptions = {}): SpeedClaimsMeasurement {
265
+ const ledgerDirectory = resolve(options.dir ?? runLedgerDir());
266
+ let parent: ParentSpeedClaimsMeasurement;
267
+ try {
268
+ const events = (options.readParentEvents ?? readParentEventsFromLogs)();
269
+ let queue: unknown;
270
+ try { queue = (options.readParentQueue ?? readParentQueueFromState)(); } catch { /* Queue absence does not erase parent observations or establish a direct launch. */ }
271
+ parent = measureParentSpeedClaims(events, queue);
272
+ } catch {
273
+ parent = { ...measureParentSpeedClaims([]), status: 'cannot-measure' };
274
+ }
275
+ const result: SpeedClaimsMeasurement = {
276
+ scope: 'self-implement-run-ledger', ledgerDirectory, status: 'measured',
277
+ completedCount: 0, inProgressCount: 0, cannotMeasureCount: 0, unknownSeatCount: 0, rows: [], note: NOTE, parent,
278
+ };
279
+ let files: string[];
280
+ try { files = (options.list ?? readdirSync)(ledgerDirectory); }
281
+ catch { return { ...result, status: 'cannot-measure' }; }
282
+
283
+ const groups = new Map<string, { seat: SpeedClaimsSeat; outcome: SpeedClaimsOutcome; values: number[] }>();
284
+ for (const file of files.sort()) {
285
+ const runId = file.endsWith('.jsonl') ? file.slice(0, -'.jsonl'.length) : '';
286
+ if (!CANONICAL_RUN_ID.test(runId)) continue;
287
+ try {
288
+ const entries = loadRunLedger(runId, ledgerDirectory, options.read);
289
+ if (!entries || entries.length === 0) { result.cannotMeasureCount++; continue; }
290
+ const end = endOfRun(entries);
291
+ const first = time(entries[0]);
292
+ const createdAt = checkpointFor(runId, ledgerDirectory)?.createdAt;
293
+ const creation = typeof createdAt === 'number' && Number.isFinite(createdAt) && createdAt >= 0 ? createdAt : null;
294
+ const start = first === null ? creation : first;
295
+ const endTime = end ? time(end.entry) : null;
296
+ if (start === null || (end && (endTime === null || endTime < start))) {
297
+ result.cannotMeasureCount++;
298
+ continue;
299
+ }
300
+ if (!end) { result.inProgressCount++; continue; }
301
+ const seat = (options.readSeat ?? seatFromLaunch)(runId, ledgerDirectory, entries);
302
+ const safeSeat = SEATS.includes(seat) ? seat : 'unknown';
303
+ if (safeSeat === 'unknown') result.unknownSeatCount++;
304
+ const key = `${safeSeat}:${end.outcome}`;
305
+ const group = groups.get(key) ?? { seat: safeSeat, outcome: end.outcome, values: [] };
306
+ group.values.push((endTime! - start) / 60_000);
307
+ groups.set(key, group);
308
+ result.completedCount++;
309
+ } catch { result.cannotMeasureCount++; }
310
+ }
311
+ result.rows = [...groups.values()].map(({ seat, outcome, values }) => {
312
+ values.sort((a, b) => a - b);
313
+ return { seat, outcome, count: values.length, underOneMinuteCount: values.filter((minutes) => minutes < 1).length, medianMinutes: percentile(values, 0.5), q1Minutes: percentile(values, 0.25), q3Minutes: percentile(values, 0.75) };
314
+ }).sort((a, b) => SEATS.indexOf(a.seat) - SEATS.indexOf(b.seat) || a.outcome.localeCompare(b.outcome));
315
+ if (result.cannotMeasureCount > 0) result.status = 'cannot-measure';
316
+ return result;
317
+ }
318
+
319
+ export function renderSpeedClaims(result: SpeedClaimsMeasurement): string {
320
+ return [
321
+ 'child run-ledger (child start → child end):',
322
+ `ledger directory: ${result.ledgerDirectory}`,
323
+ `status: ${result.status}`,
324
+ `finished (quartile denominator): ${result.completedCount}`,
325
+ `in progress (excluded): ${result.inProgressCount}`,
326
+ `cannot measure (excluded): ${result.cannotMeasureCount}`,
327
+ `unknown seat (finished): ${result.unknownSeatCount}`,
328
+ ...result.rows.map((row) => `seat=${row.seat} outcome=${row.outcome} count=${row.count} underOneMinute=${row.underOneMinuteCount} median=${row.medianMinutes}m q1=${row.q1Minutes}m q3=${row.q3Minutes}m`),
329
+ `note: ${result.note}`,
330
+ 'parent launch → done (self-dev.orchestrate):',
331
+ `parent status: ${result.parent.status}`,
332
+ `parent finished (quartile denominator): ${result.parent.completedCount}`,
333
+ `parent in progress (excluded): ${result.parent.inProgressCount}`,
334
+ `parent cannot measure (excluded): ${result.parent.cannotMeasureCount}`,
335
+ ...result.parent.rows.map((row) => `seat=${row.seat} stage=${row.stage} merged=${row.merged} count=${row.count} median=${row.medianMinutes}m q1=${row.q1Minutes}m q3=${row.q3Minutes}m`),
336
+ `parent note: ${result.parent.note}`,
337
+ ].join('\n');
338
+ }
@@ -66,6 +66,8 @@ export {
66
66
  export {
67
67
  fetchApiWithRetry,
68
68
  ApiHttpError,
69
+ OverloadFailoverError,
70
+ recordLlmCallOutcome,
69
71
  type FetchApiWithRetryOpts,
70
72
  } from './retry-api.js';
71
73