elanous 0.2.15 → 0.2.17

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 (309) 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-33ffcc49ab45123b.js → 1307-aac7332576c4d6fc.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{6877-7c6208705c284124.js → 6877-fe94e76a5841ee9b.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-0e2c57fe6add4b2a.js → page-3143b07a817d1da9.js} +1 -1
  10. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-e05a77df7e005b04.js → page-17ba086ef82d339b.js} +1 -1
  11. package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/bots/{page-b8c49f6b4fa2df91.js → page-6e5c3a867557298b.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/chat/{page-976813c91d7394ea.js → page-9a41d65355fc25b9.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/control/{page-1d4901eb7c2d8822.js → page-41afec0b255ed2bb.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-72d3e7ff35a8c954.js → page-4187666132fc98c4.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/editor/{page-60c7a8e1a4fe62ce.js → page-09328d499162bae4.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/exec/{page-2a52b316cb143d9c.js → page-29ff3a8f84f03b6a.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/field/{page-c3ce483c8d5db370.js → page-80ec7401bc83f511.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/intake/{page-96f074f6538e8a2c.js → page-fc7d86467291ae38.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/{layout-e97be5334d83a9f5.js → layout-de43a186e5ba5683.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/morning/{page-c1343f9499d86f3f.js → page-043a9111e74f6b24.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/{not-found-8739a2e0ae793130.js → not-found-23a4601ceb7f452e.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-d23b12bb92242558.js → page-10d958605a77b3bd.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-c7fea475488201a8.js → page-a04260b8ccc968d4.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-5efbf81124c474ae.js → page-12916918988ac97e.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-06ee266953d9c879.js → page-5a64cb5208c6a8fd.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/settings/{page-153c2b3bcc89e8de.js → page-1407ec83ea64c1f9.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-1d824510df025d18.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/share/{page-0d6d23b40263a7ea.js → page-7f49fe645d5132f7.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/tasks/page-27d71f131acd9320.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/trace/{page-73735b64bfe1ae67.js → page-424659bacfb8cdd7.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-f7e631e9b4daf66e.js → page-fc2df261f8a2c7df.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-9817c7f9007466cc.js → page-5d4fef9ccd7d406b.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +1 -0
  55. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
  56. package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
  57. package/apps/pwa/out/approvals/index.html +1 -1
  58. package/apps/pwa/out/approvals/index.txt +15 -15
  59. package/apps/pwa/out/autopilot/index.html +1 -1
  60. package/apps/pwa/out/autopilot/index.txt +15 -15
  61. package/apps/pwa/out/board/index.html +1 -1
  62. package/apps/pwa/out/board/index.txt +15 -15
  63. package/apps/pwa/out/botlab/index.html +1 -1
  64. package/apps/pwa/out/botlab/index.txt +15 -15
  65. package/apps/pwa/out/bots/index.html +1 -1
  66. package/apps/pwa/out/bots/index.txt +15 -15
  67. package/apps/pwa/out/ceo/index.html +1 -1
  68. package/apps/pwa/out/ceo/index.txt +15 -15
  69. package/apps/pwa/out/chat/index.html +1 -1
  70. package/apps/pwa/out/chat/index.txt +15 -15
  71. package/apps/pwa/out/consult/index.html +1 -1
  72. package/apps/pwa/out/consult/index.txt +15 -15
  73. package/apps/pwa/out/control/index.html +1 -1
  74. package/apps/pwa/out/control/index.txt +15 -15
  75. package/apps/pwa/out/design-check/index.html +1 -1
  76. package/apps/pwa/out/design-check/index.txt +15 -15
  77. package/apps/pwa/out/editor/index.html +1 -1
  78. package/apps/pwa/out/editor/index.txt +15 -15
  79. package/apps/pwa/out/exec/index.html +1 -1
  80. package/apps/pwa/out/exec/index.txt +15 -15
  81. package/apps/pwa/out/field/index.html +1 -1
  82. package/apps/pwa/out/field/index.txt +15 -15
  83. package/apps/pwa/out/healthz +1 -1
  84. package/apps/pwa/out/index.html +1 -1
  85. package/apps/pwa/out/index.txt +15 -15
  86. package/apps/pwa/out/inside/index.html +1 -1
  87. package/apps/pwa/out/inside/index.txt +15 -15
  88. package/apps/pwa/out/intake/index.html +1 -1
  89. package/apps/pwa/out/intake/index.txt +15 -15
  90. package/apps/pwa/out/live/index.html +1 -1
  91. package/apps/pwa/out/live/index.txt +15 -15
  92. package/apps/pwa/out/loops/index.html +1 -1
  93. package/apps/pwa/out/loops/index.txt +15 -15
  94. package/apps/pwa/out/market/index.html +1 -1
  95. package/apps/pwa/out/market/index.txt +15 -15
  96. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  97. package/apps/pwa/out/missions/placeholder/index.txt +15 -15
  98. package/apps/pwa/out/morning/index.html +1 -1
  99. package/apps/pwa/out/morning/index.txt +15 -15
  100. package/apps/pwa/out/observatory/index.html +1 -1
  101. package/apps/pwa/out/observatory/index.txt +15 -15
  102. package/apps/pwa/out/ops/checklist/index.html +1 -1
  103. package/apps/pwa/out/ops/checklist/index.txt +15 -15
  104. package/apps/pwa/out/ops/release/index.html +1 -1
  105. package/apps/pwa/out/ops/release/index.txt +15 -15
  106. package/apps/pwa/out/ops/seats/index.html +1 -1
  107. package/apps/pwa/out/ops/seats/index.txt +15 -15
  108. package/apps/pwa/out/outputs/index.html +1 -1
  109. package/apps/pwa/out/outputs/index.txt +15 -15
  110. package/apps/pwa/out/reflection/index.html +1 -1
  111. package/apps/pwa/out/reflection/index.txt +16 -16
  112. package/apps/pwa/out/scheduler/index.html +1 -1
  113. package/apps/pwa/out/scheduler/index.txt +15 -15
  114. package/apps/pwa/out/sessions/index.html +1 -1
  115. package/apps/pwa/out/sessions/index.txt +15 -15
  116. package/apps/pwa/out/settings/devices/index.html +1 -1
  117. package/apps/pwa/out/settings/devices/index.txt +15 -15
  118. package/apps/pwa/out/settings/index.html +1 -1
  119. package/apps/pwa/out/settings/index.txt +15 -15
  120. package/apps/pwa/out/setup/done/index.html +1 -1
  121. package/apps/pwa/out/setup/done/index.txt +15 -15
  122. package/apps/pwa/out/setup/index.html +1 -1
  123. package/apps/pwa/out/setup/index.txt +15 -15
  124. package/apps/pwa/out/share/index.html +1 -1
  125. package/apps/pwa/out/share/index.txt +15 -15
  126. package/apps/pwa/out/showroom/index.html +1 -1
  127. package/apps/pwa/out/showroom/index.txt +15 -15
  128. package/apps/pwa/out/tasks/index.html +1 -1
  129. package/apps/pwa/out/tasks/index.txt +15 -15
  130. package/apps/pwa/out/term/index.html +1 -1
  131. package/apps/pwa/out/term/index.txt +15 -15
  132. package/apps/pwa/out/today/index.html +1 -1
  133. package/apps/pwa/out/today/index.txt +15 -15
  134. package/apps/pwa/out/trace/index.html +1 -1
  135. package/apps/pwa/out/trace/index.txt +15 -15
  136. package/apps/pwa/out/vault/index.html +1 -1
  137. package/apps/pwa/out/vault/index.txt +15 -15
  138. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  139. package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
  140. package/apps/pwa/out/workflows/index.html +1 -1
  141. package/apps/pwa/out/workflows/index.txt +15 -15
  142. package/apps/pwa/out/workspace/index.html +1 -1
  143. package/apps/pwa/out/workspace/index.txt +15 -15
  144. package/apps/pwa/out/worktrees/index.html +1 -1
  145. package/apps/pwa/out/worktrees/index.txt +15 -15
  146. package/graphs/doc-gen/doc-gen.yaml +24 -0
  147. package/graphs/doc-gen/recipes.yaml +3 -0
  148. package/graphs/intake/intake-daily.yaml +9 -2
  149. package/graphs/intake/news-sources.yaml +4 -0
  150. package/graphs/intake/recipes.yaml +6 -1
  151. package/graphs/orchestrator/orchestrator.yaml +7 -0
  152. package/graphs/orchestrator/recipes.yaml +3 -0
  153. package/graphs/steward/recipes.yaml +2 -1
  154. package/package.json +1 -1
  155. package/scripts/__fixtures__/intake-news.xml +7 -0
  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 +34 -15
  159. package/scripts/docs/doc-rot.ts +164 -0
  160. package/scripts/docs/generate-graph-anatomy.ts +131 -9
  161. package/scripts/docs-cli-check.ts +4 -4
  162. package/scripts/goals/goal-census.ts +343 -0
  163. package/scripts/intake-news.ts +169 -0
  164. package/scripts/measure/author-section-contribution.ts +84 -0
  165. package/scripts/release-loop/cut-branch.ts +43 -13
  166. package/scripts/release-loop/docs-land-node.ts +69 -10
  167. package/scripts/release-loop/gate-node.ts +3 -1
  168. package/scripts/release-loop/publish-node.ts +2 -2
  169. package/scripts/release-loop/release-docs.ts +134 -0
  170. package/scripts/release-loop/vault-note-node.ts +19 -2
  171. package/scripts/release-loop/version-node.ts +7 -4
  172. package/scripts/release-story/draft.ts +42 -14
  173. package/scripts/rhythm/monthly.ts +97 -0
  174. package/src/acp/client.ts +17 -2
  175. package/src/agent-mission/driver.ts +104 -11
  176. package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
  177. package/src/agent-mission/supervisor-metrics.ts +218 -0
  178. package/src/autopilot/track-agent-shadow.ts +2 -1
  179. package/src/autopilot/track-agent.ts +5 -3
  180. package/src/briefing/brief-items.ts +191 -36
  181. package/src/budget/usage-outcomes.ts +204 -0
  182. package/src/cli/autopilot-cli.ts +66 -0
  183. package/src/cli/brief-cli.ts +73 -0
  184. package/src/cli/decisions-cli.ts +4 -1
  185. package/src/cli/intake-cli.ts +1 -1
  186. package/src/cli/know-cli.ts +20 -0
  187. package/src/cli/log-event-names.ts +1 -0
  188. package/src/cli/logs-cli.ts +43 -1
  189. package/src/cli/pr-cli.ts +92 -13
  190. package/src/cli/provider-cli.ts +14 -0
  191. package/src/cli/release-cli.ts +11 -8
  192. package/src/cli/self-update-canary.ts +46 -0
  193. package/src/cli/self-update.ts +399 -1
  194. package/src/cli/usage-cli.ts +26 -0
  195. package/src/context-card/index.ts +45 -0
  196. package/src/context-card/task.ts +76 -0
  197. package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
  198. package/src/decisions/decision-ledger.ts +10 -1
  199. package/src/discord-self-message.ts +9 -0
  200. package/src/flow-loop/split.ts +24 -0
  201. package/src/harness/harness-cli-command.ts +178 -12
  202. package/src/harness/harness-pod-dispatch.ts +36 -55
  203. package/src/harness/harness-queue.ts +86 -7
  204. package/src/harness/harness-seams.ts +13 -3
  205. package/src/harness/helper-repair.ts +139 -8
  206. package/src/harness/helper-scan.ts +13 -2
  207. package/src/harness/nested-elanous-policy.ts +133 -0
  208. package/src/hq/fence-wrapper.ts +123 -0
  209. package/src/hq/hq.ts +16 -2
  210. package/src/hq/lease.ts +5 -3
  211. package/src/hq/transfer.ts +185 -0
  212. package/src/index.ts +168 -13
  213. package/src/intake-plane/digest.ts +85 -28
  214. package/src/knowledge/know-find.ts +105 -0
  215. package/src/llm.ts +75 -19
  216. package/src/loops/orchestrator/finish-rate.ts +14 -3
  217. package/src/loops/orchestrator/tick.ts +96 -25
  218. package/src/maturity/bot-slash-catalog.ts +11 -2
  219. package/src/maturity/feature-maturity.ts +2 -2
  220. package/src/maturity/tui-slash-availability.ts +9 -0
  221. package/src/nexus/api/grid.ts +61 -0
  222. package/src/nexus/api/http-server.ts +19 -1
  223. package/src/nexus/api/task-cards-api.ts +19 -0
  224. package/src/oauth/codex.ts +82 -2
  225. package/src/oauth/overload-failover-launch.ts +75 -0
  226. package/src/oauth/overload-failover.ts +137 -0
  227. package/src/release-loop/checklist.ts +10 -5
  228. package/src/release-loop/feature-store.ts +10 -9
  229. package/src/release-loop/landing-freeze.ts +18 -10
  230. package/src/release-loop/placement.ts +17 -8
  231. package/src/seat-dispatch/seat-ask.ts +33 -6
  232. package/src/seat-loop/seat-loop.ts +172 -21
  233. package/src/self-dev/ask-launch-flow.ts +48 -8
  234. package/src/self-dev/authored-goal-trace.ts +64 -0
  235. package/src/self-dev/dev-cli.ts +19 -1
  236. package/src/self-dev/dev-pipeline.ts +6 -5
  237. package/src/self-dev/entrance-registry.ts +10 -6
  238. package/src/self-dev/old-door.ts +73 -0
  239. package/src/self-dev/run-store.ts +5 -1
  240. package/src/self-dev/run-supervisor.ts +7 -1
  241. package/src/self-dev/supervisor-verdict-edges.ts +46 -0
  242. package/src/self-implement/app-api-budget.ts +166 -0
  243. package/src/self-implement/budget-gate.ts +7 -5
  244. package/src/self-implement/follow-up-goals.ts +289 -0
  245. package/src/self-implement/frozen-merges.ts +25 -20
  246. package/src/self-implement/goal-author-clarification.ts +81 -0
  247. package/src/self-implement/goal-author-cli.ts +33 -5
  248. package/src/self-implement/goal-author.ts +107 -25
  249. package/src/self-implement/goal-clarification-escalation.ts +37 -1
  250. package/src/self-implement/goal-document-write.ts +28 -0
  251. package/src/self-implement/heal-intake.ts +4 -0
  252. package/src/self-implement/heal-triage.ts +44 -0
  253. package/src/self-implement/host-regate.ts +3 -1
  254. package/src/self-implement/orchestrator.ts +157 -8
  255. package/src/self-implement/run-ledger.ts +4 -0
  256. package/src/self-implement/seams.ts +44 -15
  257. package/src/session-runtime/index.ts +2 -0
  258. package/src/session-runtime/retry-api.ts +87 -0
  259. package/src/session-runtime/retry-policy.ts +107 -6
  260. package/src/steward/working-backwards.ts +16 -1
  261. package/src/task-agent/actions.ts +118 -0
  262. package/src/task-agent/judge.ts +96 -0
  263. package/src/task-cards/card-cli.ts +25 -0
  264. package/src/task-cards/card-store.ts +9 -4
  265. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +33 -4
  266. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  267. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
  268. package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
  269. package/src/task-orchestrator/surfaces/pod-pool.ts +95 -18
  270. package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
  271. package/src/task-orchestrator/surfaces/self-implement-pod.ts +168 -37
  272. package/src/task-orchestrator/surfaces/self-implement.ts +2 -0
  273. package/src/telegram-commands.ts +6 -1
  274. package/src/telegram-loops-command.ts +21 -14
  275. package/src/telegram.ts +47 -0
  276. package/src/user-config.ts +59 -4
  277. package/src/version/packed-revision.json +1 -1
  278. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +0 -1
  279. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
  280. package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
  281. package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +0 -1
  282. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +0 -1
  283. package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +0 -1
  284. package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +0 -1
  285. package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +0 -1
  286. package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +0 -1
  287. package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +0 -1
  288. package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +0 -1
  289. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +0 -1
  290. package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +0 -1
  291. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +0 -1
  292. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +0 -1
  293. package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +0 -1
  294. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +0 -1
  295. package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +0 -1
  296. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +0 -1
  297. package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +0 -1
  298. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +0 -1
  299. package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +0 -1
  300. package/apps/pwa/out/_next/static/chunks/app/term/page-f5597e894c02cd33.js +0 -1
  301. package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +0 -1
  302. package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +0 -1
  303. package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +0 -1
  304. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +0 -1
  305. package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +0 -1
  306. package/src/launch-head/seat-goal-queue-lock.fixture.ts +0 -15
  307. package/src/launch-head/seat-goal-queue.ts +0 -198
  308. /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → yRA5ur6DcPl0YHSRheH2O}/_buildManifest.js +0 -0
  309. /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → yRA5ur6DcPl0YHSRheH2O}/_ssgManifest.js +0 -0
@@ -2,32 +2,45 @@ import { Database } from 'bun:sqlite';
2
2
  import { chmodSync, mkdirSync } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { elanousStateRoot } from '../autopilot/state-paths.js';
5
+ import { debug } from '../debug/log.js';
6
+ import { dateKey } from '../time/format.js';
5
7
 
6
- export type BriefPriority = 'high' | 'medium' | 'low';
8
+ export const BRIEF_DOMAINS = ['운영', '판', '흡수', '시장', '행정', '코나투스'] as const;
9
+ export const BRIEF_PRIORITIES = ['P0', 'P1', 'P2'] as const;
10
+ export const BRIEF_SLOTS = ['08:30', 'after-release', '22:00'] as const;
11
+
12
+ export type BriefDomain = (typeof BRIEF_DOMAINS)[number];
13
+ export type BriefPriority = (typeof BRIEF_PRIORITIES)[number];
14
+ export type BriefSlot = (typeof BRIEF_SLOTS)[number];
15
+
16
+ const KST = 'Asia/Seoul';
7
17
 
8
18
  export interface BriefItem {
9
19
  id: number;
10
20
  text: string;
11
- domain: string;
21
+ domain: BriefDomain;
12
22
  priority: BriefPriority;
13
23
  deadline: string | null;
14
24
  evidence: string | null;
15
25
  source: string;
16
26
  created_at: string;
27
+ sent_at: string | null;
17
28
  }
18
29
 
19
30
  export interface AddBriefItem {
20
31
  text: string;
21
- domain: string;
32
+ domain: BriefDomain;
22
33
  priority: BriefPriority;
23
34
  deadline?: string | null;
24
35
  evidence?: string | null;
25
36
  source: string;
37
+ createdAt?: string;
26
38
  }
27
39
 
28
40
  export interface BriefItemsOptions {
29
41
  stateDir?: string;
30
42
  now?: () => Date;
43
+ log?: (category: string, event: string, data?: unknown) => void;
31
44
  }
32
45
 
33
46
  export class BriefItemsInputError extends Error {
@@ -43,14 +56,121 @@ function optional(value: string | null | undefined, field: string): string | nul
43
56
  return value == null ? null : required(value, field);
44
57
  }
45
58
 
46
- /** Append-only item ledger. All three operations open the same instance-scoped SQLite file. */
59
+ /** Collapse whitespace so the same claim, however spaced, counts once. */
60
+ export function normalizeClaim(text: string): string {
61
+ return text.normalize('NFKC').trim().replace(/\s+/g, ' ').toLowerCase();
62
+ }
63
+
64
+ function deadlineDate(deadline: string): string {
65
+ return /^\d{4}-\d{2}-\d{2}$/.test(deadline) ? deadline : dateKey(deadline, { timeZone: KST });
66
+ }
67
+
68
+ /**
69
+ * Unsent items created strictly before the slot instant (KST).
70
+ * `08:30` and `22:00` are that clock today. `after-release` has no fixed
71
+ * clock — a release can land at any hour — so its instant is `now`.
72
+ * Tomorrow's items stay out of every slot.
73
+ */
74
+ export function beforeSlot(createdAt: string, slot: BriefSlot, now: Date): boolean {
75
+ const createdMs = Date.parse(createdAt);
76
+ if (!Number.isFinite(createdMs)) return false;
77
+ const today = dateKey(now, { timeZone: KST });
78
+ const cutoff = slot === 'after-release' ? now.getTime() : Date.parse(`${today}T${slot}:00+09:00`);
79
+ return createdMs < cutoff;
80
+ }
81
+
82
+ function needsDecision(item: BriefItem, today: string): boolean {
83
+ return item.priority === 'P0' || (item.deadline !== null && deadlineDate(item.deadline) === today);
84
+ }
85
+
86
+ /**
87
+ * One briefing page. Item text is copied verbatim.
88
+ * Order: decisions due (P0, then a deadline of today) → domain groups →
89
+ * duplicate claims dropped (same normalized sentence, most urgent kept).
90
+ */
91
+ export function composeBriefMarkdown(items: readonly BriefItem[], slot: BriefSlot, now: Date): string {
92
+ const today = dateKey(now, { timeZone: KST });
93
+ const eligible = items.filter(item => item.sent_at === null && beforeSlot(item.created_at, slot, now));
94
+ // Duplicate claims keep the copy that needs a decision today (P0 or due today), then the higher priority, then the earliest (ACP must-fix).
95
+ const rank = (item: BriefItem) => [needsDecision(item, today) ? 0 : 1, BRIEF_PRIORITIES.indexOf(item.priority), item.id];
96
+ const better = (a: BriefItem, b: BriefItem) => { const x = rank(a), y = rank(b); return x[0]! - y[0]! || x[1]! - y[1]! || x[2]! - y[2]!; };
97
+ const best = new Map<string, BriefItem>();
98
+ for (const item of eligible) {
99
+ const key = normalizeClaim(item.text);
100
+ const kept = best.get(key);
101
+ if (!kept || better(item, kept) < 0) best.set(key, item);
102
+ }
103
+ const unique = [...best.values()].sort((a, b) => a.id - b.id);
104
+ const decisions = unique.filter(item => needsDecision(item, today))
105
+ .sort((a, b) => Number(b.priority === 'P0') - Number(a.priority === 'P0') || a.id - b.id);
106
+ const decisionIds = new Set(decisions.map(item => item.id));
107
+ const lines = [`# 브리핑 — ${slot}`, ''];
108
+ if (decisions.length > 0) {
109
+ lines.push('## 결정이 필요한 것', '');
110
+ for (const item of decisions) lines.push(itemLine(item));
111
+ lines.push('');
112
+ }
113
+ for (const domain of BRIEF_DOMAINS) {
114
+ const group = unique.filter(item => item.domain === domain && !decisionIds.has(item.id));
115
+ if (group.length === 0) continue;
116
+ lines.push(`## ${domain}`, '');
117
+ for (const item of group.sort((a, b) => a.id - b.id)) lines.push(itemLine(item));
118
+ lines.push('');
119
+ }
120
+ return `${lines.join('\n').trimEnd()}\n`;
121
+ }
122
+
123
+ function itemLine(item: BriefItem): string {
124
+ const extra = [
125
+ item.deadline ? `마감: ${item.deadline}` : '',
126
+ item.evidence ? `근거: ${item.evidence}` : '',
127
+ `출처: ${item.source}`,
128
+ ].filter(Boolean).join(' · ');
129
+ return `- [${item.priority}] ${item.text} (${extra})`;
130
+ }
131
+
132
+ /**
133
+ * The first ledger (#24193) used priorities high/medium/low and had no sent_at column. Keep its rows: move the old
134
+ * table aside and copy them into the current shape (high→P0 · medium→P1 · low→P2) — append-only, nothing is lost.
135
+ */
136
+ function migrateLegacyItems(db: Database, log: (category: string, event: string, data?: unknown) => void): void {
137
+ const columns = db.query("SELECT name FROM pragma_table_info('items')").all() as { name: string }[];
138
+ if (columns.length === 0 || columns.some(column => column.name === 'sent_at')) return;
139
+ db.transaction(() => {
140
+ db.exec('DROP TRIGGER IF EXISTS items_no_update; DROP TRIGGER IF EXISTS items_no_delete; ALTER TABLE items RENAME TO items_v1;');
141
+ db.exec(`CREATE TABLE items (
142
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
143
+ text TEXT NOT NULL,
144
+ domain TEXT NOT NULL CHECK(domain IN ('운영', '판', '흡수', '시장', '행정', '코나투스')),
145
+ priority TEXT NOT NULL CHECK(priority IN ('P0', 'P1', 'P2')),
146
+ deadline TEXT,
147
+ evidence TEXT,
148
+ source TEXT NOT NULL,
149
+ created_at TEXT NOT NULL,
150
+ sent_at TEXT
151
+ )`);
152
+ db.exec(`INSERT INTO items (id, text, domain, priority, deadline, evidence, source, created_at, sent_at)
153
+ SELECT id, text,
154
+ CASE WHEN domain IN ('운영', '판', '흡수', '시장', '행정', '코나투스') THEN domain ELSE '운영' END,
155
+ CASE priority WHEN 'high' THEN 'P0' WHEN 'medium' THEN 'P1' ELSE 'P2' END,
156
+ deadline, evidence,
157
+ -- An unknown legacy domain keeps its meaning in the source label instead of vanishing (ACP must-fix).
158
+ CASE WHEN domain IN ('운영', '판', '흡수', '시장', '행정', '코나투스') THEN source ELSE source || ' · 옛 도메인 ' || domain END,
159
+ created_at, NULL FROM items_v1`);
160
+ })();
161
+ log('briefing.items', 'migrated-legacy', { rows: (db.query('SELECT COUNT(*) AS n FROM items_v1').get() as { n: number }).n });
162
+ }
163
+
164
+ /** Append-only briefing item ledger at `<state>/briefing/items.sqlite`. */
47
165
  export class BriefItemsLedger {
48
166
  readonly path: string;
49
167
  private readonly now: () => Date;
168
+ private readonly log: (category: string, event: string, data?: unknown) => void;
50
169
 
51
170
  constructor(options: BriefItemsOptions = {}) {
52
171
  this.path = join(options.stateDir ?? elanousStateRoot(), 'briefing', 'items.sqlite');
53
172
  this.now = options.now ?? (() => new Date());
173
+ this.log = options.log ?? ((category, event, data) => debug.log(category, event, data));
54
174
  }
55
175
 
56
176
  private using<T>(work: (db: Database) => T): T {
@@ -59,16 +179,27 @@ export class BriefItemsLedger {
59
179
  try {
60
180
  chmodSync(this.path, 0o600);
61
181
  db.exec('PRAGMA busy_timeout = 10000');
182
+ migrateLegacyItems(db, this.log);
62
183
  db.exec(`CREATE TABLE IF NOT EXISTS items (
63
184
  id INTEGER PRIMARY KEY AUTOINCREMENT,
64
- text TEXT NOT NULL, domain TEXT NOT NULL,
65
- priority TEXT NOT NULL CHECK(priority IN ('high', 'medium', 'low')),
66
- deadline TEXT, evidence TEXT, source TEXT NOT NULL, created_at TEXT NOT NULL
185
+ text TEXT NOT NULL,
186
+ domain TEXT NOT NULL CHECK(domain IN ('운영', '판', '흡수', '시장', '행정', '코나투스')),
187
+ priority TEXT NOT NULL CHECK(priority IN ('P0', 'P1', 'P2')),
188
+ deadline TEXT,
189
+ evidence TEXT,
190
+ source TEXT NOT NULL,
191
+ created_at TEXT NOT NULL,
192
+ sent_at TEXT
67
193
  );
68
194
  CREATE TRIGGER IF NOT EXISTS items_no_update BEFORE UPDATE ON items
69
195
  BEGIN SELECT RAISE(ABORT, 'brief items are immutable'); END;
70
196
  CREATE TRIGGER IF NOT EXISTS items_no_delete BEFORE DELETE ON items
71
- BEGIN SELECT RAISE(ABORT, 'brief items are immutable'); END;`);
197
+ BEGIN SELECT RAISE(ABORT, 'brief items are immutable'); END;
198
+ CREATE TABLE IF NOT EXISTS sends (
199
+ item_id INTEGER NOT NULL REFERENCES items(id),
200
+ slot TEXT NOT NULL,
201
+ sent_at TEXT NOT NULL
202
+ );`);
72
203
  return work(db);
73
204
  } finally {
74
205
  db.close();
@@ -77,45 +208,69 @@ export class BriefItemsLedger {
77
208
 
78
209
  add(input: AddBriefItem): BriefItem {
79
210
  const text = required(input.text, 'text');
80
- const domain = required(input.domain, 'domain');
211
+ // A brief item is one claim line; a line break would break the composed Markdown structure (ACP must-fix).
212
+ if (/[\r\n]/.test(text)) throw new BriefItemsInputError('text must be a single line');
213
+ if (!(BRIEF_DOMAINS as readonly string[]).includes(input.domain)) throw new BriefItemsInputError('invalid domain');
214
+ if (!(BRIEF_PRIORITIES as readonly string[]).includes(input.priority)) throw new BriefItemsInputError('invalid priority');
81
215
  const source = required(input.source, 'source');
82
- if (!['high', 'medium', 'low'].includes(input.priority)) throw new BriefItemsInputError('invalid priority');
83
216
  const deadline = optional(input.deadline, 'deadline');
217
+ // Every field that reaches the composed Markdown is one line, so no stored item can forge a heading or entry (ACP must-fix).
218
+ for (const [field, value] of [['source', source], ['evidence', input.evidence ?? null], ['deadline', deadline]] as const) {
219
+ if (value !== null && /[\r\n]/.test(value)) throw new BriefItemsInputError(`${field} must be a single line`);
220
+ }
84
221
  if (deadline !== null) {
85
222
  const format = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2}))?$/;
86
223
  if (!format.test(deadline) || !Number.isFinite(Date.parse(deadline))) throw new BriefItemsInputError('invalid deadline');
87
224
  }
88
225
  const evidence = optional(input.evidence, 'evidence');
89
- return this.using(db => {
90
- const createdAt = this.now().toISOString();
91
- const result = db.query('INSERT INTO items (text, domain, priority, deadline, evidence, source, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)')
92
- .run(text, domain, input.priority, deadline, evidence, source, createdAt);
93
- return db.query('SELECT id, text, domain, priority, deadline, evidence, source, created_at FROM items WHERE id = ?')
94
- .get(result.lastInsertRowid) as BriefItem;
226
+ const createdAt = input.createdAt ?? this.now().toISOString();
227
+ if (!Number.isFinite(Date.parse(createdAt))) throw new BriefItemsInputError('invalid createdAt');
228
+ const item = this.using(db => {
229
+ const result = db.query(
230
+ 'INSERT INTO items (text, domain, priority, deadline, evidence, source, created_at, sent_at) VALUES (?, ?, ?, ?, ?, ?, ?, NULL)',
231
+ ).run(text, input.domain, input.priority, deadline, evidence, source, createdAt);
232
+ return db.query(
233
+ 'SELECT id, text, domain, priority, deadline, evidence, source, created_at, (SELECT MIN(sent_at) FROM sends WHERE sends.item_id = items.id) AS sent_at FROM items WHERE id = ?',
234
+ ).get(result.lastInsertRowid) as BriefItem;
95
235
  });
236
+ this.log('briefing.items', 'added', { id: item.id, domain: item.domain, priority: item.priority, source: item.source });
237
+ return item;
96
238
  }
97
239
 
98
- /** Higher priorities first, then nearest deadline by instant, then insertion order. */
99
- list(filters: { domain?: string } = {}): BriefItem[] {
100
- const items = this.using(db => (filters.domain === undefined
101
- ? db.query('SELECT id, text, domain, priority, deadline, evidence, source, created_at FROM items').all()
102
- : db.query('SELECT id, text, domain, priority, deadline, evidence, source, created_at FROM items WHERE domain = ?').all(filters.domain)) as BriefItem[]);
103
- const rank: Record<BriefPriority, number> = { high: 0, medium: 1, low: 2 };
104
- return items.sort((a, b) => rank[a.priority] - rank[b.priority]
105
- || Number(a.deadline === null) - Number(b.deadline === null)
106
- || (a.deadline !== null && b.deadline !== null ? Date.parse(a.deadline) - Date.parse(b.deadline) : 0)
107
- || a.id - b.id);
240
+ /** Insertion order. Optional domain filter. Item text is the stored original. */
241
+ list(filters: { domain?: BriefDomain } = {}): BriefItem[] {
242
+ return this.using(db => (filters.domain === undefined
243
+ ? db.query('SELECT id, text, domain, priority, deadline, evidence, source, created_at, (SELECT MIN(sent_at) FROM sends WHERE sends.item_id = items.id) AS sent_at FROM items ORDER BY id').all()
244
+ : db.query('SELECT id, text, domain, priority, deadline, evidence, source, created_at, (SELECT MIN(sent_at) FROM sends WHERE sends.item_id = items.id) AS sent_at FROM items WHERE domain = ? ORDER BY id').all(filters.domain)) as BriefItem[]);
108
245
  }
109
246
 
110
- /** Pure Markdown output: composing never marks, alters, or sends stored items. */
111
- compose(slot: string, filters: { domain?: string } = {}): string {
112
- required(slot, 'slot');
113
- const items = this.list(filters);
114
- const lines = [`# Briefing — ${slot}`, ''];
115
- for (const item of items) {
116
- lines.push(`- [${item.priority}] ${item.text} (${item.domain})`);
117
- lines.push(` - Source: ${item.source}${item.deadline ? ` · Deadline: ${item.deadline}` : ''}${item.evidence ? ` · Evidence: ${item.evidence}` : ''}`);
118
- }
119
- return `${lines.join('\n').trimEnd()}\n`;
247
+ /**
248
+ * Markdown for one slot. Reads only — never marks, rewrites, or sends an item.
249
+ * Items created at or after the slot instant are left out.
250
+ */
251
+ compose(slot: string): string {
252
+ if (!(BRIEF_SLOTS as readonly string[]).includes(slot)) throw new BriefItemsInputError('invalid slot');
253
+ const items = this.list();
254
+ const markdown = composeBriefMarkdown(items, slot as BriefSlot, this.now());
255
+ const included = items.filter(item => item.sent_at === null && beforeSlot(item.created_at, slot as BriefSlot, this.now()));
256
+ this.log('briefing.items', 'composed', { slot, candidates: included.length, bytes: Buffer.byteLength(markdown) });
257
+ return markdown;
258
+ }
259
+
260
+ /**
261
+ * Records that these items went out in a slot (append-only); later composes leave them out (ACP must-fix).
262
+ * Only an actual sender calls this — compose itself stays read-only (no flag marks items it did not send).
263
+ */
264
+ markSent(ids: readonly number[], slot: BriefSlot): number {
265
+ if (!(BRIEF_SLOTS as readonly string[]).includes(slot)) throw new BriefItemsInputError('invalid slot');
266
+ const at = this.now().toISOString();
267
+ const count = this.using(db => {
268
+ const insert = db.query('INSERT INTO sends (item_id, slot, sent_at) VALUES (?, ?, ?)');
269
+ let n = 0;
270
+ db.transaction(() => { for (const id of ids) { insert.run(id, slot, at); n++; } })();
271
+ return n;
272
+ });
273
+ this.log('briefing.items', 'marked-sent', { slot, count });
274
+ return count;
120
275
  }
121
276
  }
@@ -0,0 +1,204 @@
1
+ // `elanous usage outcomes` — 런 원장의 childProvider × 결과.
2
+ // provider 가 기록되지 않은 런은 «불명». 이름을 지어 세지 않는다.
3
+
4
+ import { readdirSync } from 'node:fs';
5
+ import {
6
+ loadRunLedger,
7
+ runLedgerDir,
8
+ type RunLedgerEntry,
9
+ type RunLedgerReader,
10
+ } from '../self-implement/run-ledger.js';
11
+
12
+ export const UNKNOWN_PROVIDER_LABEL = '불명';
13
+
14
+ export type OutcomeKind = 'landed' | 'draft' | 'failed' | 'open';
15
+
16
+ export interface ProviderOutcomeRow {
17
+ readonly provider: string;
18
+ readonly runs: number;
19
+ readonly landed: number;
20
+ readonly draft: number;
21
+ readonly failed: number;
22
+ readonly open: number;
23
+ /** landed / runs. 런이 0이면 0. */
24
+ readonly unattendedLandRate: number;
25
+ readonly rounds: number;
26
+ readonly blockReasons: Readonly<Record<string, number>>;
27
+ }
28
+
29
+ export interface UsageOutcomesReport {
30
+ readonly sinceDays: number;
31
+ readonly sinceMs: number;
32
+ readonly rows: readonly ProviderOutcomeRow[];
33
+ readonly unreadableLedgers: number;
34
+ }
35
+
36
+ export interface UsageOutcomesDeps {
37
+ readonly dir?: string;
38
+ readonly nowMs?: number;
39
+ readonly list?: (path: string) => string[];
40
+ readonly read?: RunLedgerReader;
41
+ readonly load?: (runId: string, dir: string, read?: RunLedgerReader) => RunLedgerEntry[] | null;
42
+ }
43
+
44
+ const LEDGER_FILE = /\.jsonl$/;
45
+
46
+ function stringField(data: Record<string, unknown>, key: string): string | undefined {
47
+ const value = data[key];
48
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
49
+ }
50
+
51
+ function entryMs(entry: RunLedgerEntry): number | undefined {
52
+ if (!entry.timestamp) return undefined;
53
+ const ms = Date.parse(entry.timestamp);
54
+ return Number.isFinite(ms) ? ms : undefined;
55
+ }
56
+
57
+ /** 기록된 childProvider 만. 없거나 공백이면 «불명». */
58
+ export function providerLabel(entries: readonly RunLedgerEntry[]): string {
59
+ const origin = entries.find((entry) => entry.event === 'run-origin');
60
+ const recorded = origin ? stringField(origin.data, 'childProvider') : undefined;
61
+ return recorded ?? UNKNOWN_PROVIDER_LABEL;
62
+ }
63
+
64
+ function blockReason(entry: RunLedgerEntry): string | undefined {
65
+ return stringField(entry.data, 'stopReason')
66
+ ?? stringField(entry.data, 'reason')
67
+ ?? stringField(entry.data, 'verdict')
68
+ ?? stringField(entry.data, 'error');
69
+ }
70
+
71
+ interface ClassifiedRun {
72
+ readonly provider: string;
73
+ readonly outcome: OutcomeKind;
74
+ readonly rounds: number;
75
+ readonly blockReason?: string;
76
+ readonly atMs: number;
77
+ }
78
+
79
+ export function classifyLedgerRun(entries: readonly RunLedgerEntry[], nowMs: number): ClassifiedRun | null {
80
+ if (entries.length === 0) return null;
81
+ const times = entries.map(entryMs).filter((ms): ms is number => ms !== undefined);
82
+ const atMs = times.length > 0 ? Math.max(...times) : nowMs;
83
+ const rounds = new Set(
84
+ entries
85
+ .map((entry) => entry.data.round)
86
+ .filter((round): round is number => typeof round === 'number' && Number.isFinite(round)),
87
+ ).size;
88
+ const merged = [...entries].reverse().find((entry) => entry.event === 'merged' && entry.data.merged === true);
89
+ const drafted = [...entries].reverse().find((entry) => entry.event === 'pr-opened' && entry.data.draft !== false);
90
+ const failed = [...entries].reverse().find((entry) => entry.event === 'failed' || entry.event === 'run-status' && (entry.data.runStatus === 'failed' || entry.data.runStatus === 'stopped'));
91
+ const outcome: OutcomeKind = merged ? 'landed' : drafted ? 'draft' : failed ? 'failed' : 'open';
92
+ const reason = outcome === 'failed' || outcome === 'open'
93
+ ? (failed ? blockReason(failed) : undefined)
94
+ ?? entries.map(blockReason).reverse().find((value) => value !== undefined)
95
+ : undefined;
96
+ return {
97
+ provider: providerLabel(entries),
98
+ outcome,
99
+ rounds,
100
+ ...(reason ? { blockReason: reason } : {}),
101
+ atMs,
102
+ };
103
+ }
104
+
105
+ export function aggregateUsageOutcomes(
106
+ runs: readonly ClassifiedRun[],
107
+ sinceDays: number,
108
+ sinceMs: number,
109
+ unreadableLedgers = 0,
110
+ ): UsageOutcomesReport {
111
+ const byProvider = new Map<string, {
112
+ runs: number;
113
+ landed: number;
114
+ draft: number;
115
+ failed: number;
116
+ open: number;
117
+ rounds: number;
118
+ blockReasons: Record<string, number>;
119
+ }>();
120
+ for (const run of runs) {
121
+ const row = byProvider.get(run.provider) ?? {
122
+ runs: 0, landed: 0, draft: 0, failed: 0, open: 0, rounds: 0, blockReasons: {},
123
+ };
124
+ row.runs += 1;
125
+ row[run.outcome] += 1;
126
+ row.rounds += run.rounds;
127
+ if (run.blockReason && run.outcome !== 'landed' && run.outcome !== 'draft') {
128
+ row.blockReasons[run.blockReason] = (row.blockReasons[run.blockReason] ?? 0) + 1;
129
+ }
130
+ byProvider.set(run.provider, row);
131
+ }
132
+ const rows = [...byProvider.entries()]
133
+ .map(([provider, row]): ProviderOutcomeRow => ({
134
+ provider,
135
+ ...row,
136
+ unattendedLandRate: row.runs === 0 ? 0 : row.landed / row.runs,
137
+ }))
138
+ .sort((a, b) => a.provider.localeCompare(b.provider, 'en'));
139
+ return { sinceDays, sinceMs, rows, unreadableLedgers };
140
+ }
141
+
142
+ export function queryUsageOutcomes(sinceDays: number, deps: UsageOutcomesDeps = {}): UsageOutcomesReport {
143
+ const days = Number.isFinite(sinceDays) && sinceDays > 0 ? sinceDays : 2;
144
+ const nowMs = deps.nowMs ?? Date.now();
145
+ const sinceMs = nowMs - days * 24 * 60 * 60 * 1000;
146
+ const dir = deps.dir ?? runLedgerDir();
147
+ const list = deps.list ?? readdirSync;
148
+ const load = deps.load ?? loadRunLedger;
149
+ let names: string[];
150
+ try {
151
+ names = list(dir);
152
+ } catch (error) {
153
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
154
+ return aggregateUsageOutcomes([], days, sinceMs, 0);
155
+ }
156
+ throw error;
157
+ }
158
+ const runs: ClassifiedRun[] = [];
159
+ let unreadableLedgers = 0;
160
+ for (const name of names) {
161
+ if (!LEDGER_FILE.test(name)) continue;
162
+ const runId = name.slice(0, -'.jsonl'.length);
163
+ let ledger: RunLedgerEntry[] | null;
164
+ try {
165
+ ledger = load(runId, dir, deps.read);
166
+ } catch {
167
+ unreadableLedgers += 1;
168
+ continue;
169
+ }
170
+ if (!ledger) {
171
+ unreadableLedgers += 1;
172
+ continue;
173
+ }
174
+ const classified = classifyLedgerRun(ledger, nowMs);
175
+ if (!classified || classified.atMs < sinceMs) continue;
176
+ runs.push(classified);
177
+ }
178
+ return aggregateUsageOutcomes(runs, days, sinceMs, unreadableLedgers);
179
+ }
180
+
181
+ export function formatUsageOutcomes(report: UsageOutcomesReport): string {
182
+ const header = `최근 ${report.sinceDays}일 · 런 원장 childProvider × 결과`;
183
+ const verdict = outcomeTableHasCodexGrokUnknown(report)
184
+ ? '판정: codex·grok·불명'
185
+ : '판정: codex·grok·불명 아님';
186
+ if (report.rows.length === 0) return `${header}\n${verdict}\n(해당 런 없음)`;
187
+ const lines = [header, verdict, 'provider runs landed draft failed open landRate rounds blockReasons'];
188
+ for (const row of report.rows) {
189
+ const reasons = Object.entries(row.blockReasons)
190
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
191
+ .map(([reason, count]) => `${reason}=${count}`)
192
+ .join(',');
193
+ const rate = `${Math.round(row.unattendedLandRate * 1000) / 10}%`;
194
+ lines.push(`${row.provider} ${row.runs} ${row.landed} ${row.draft} ${row.failed} ${row.open} ${rate} ${row.rounds} ${reasons || '-'}`);
195
+ }
196
+ if (report.unreadableLedgers > 0) lines.push(`unreadable ledgers: ${report.unreadableLedgers}`);
197
+ return lines.join('\n');
198
+ }
199
+
200
+ /** 판정이 요구하는 최근 이틀 표에 codex·grok·불명 세 줄이 있는지. */
201
+ export function outcomeTableHasCodexGrokUnknown(report: UsageOutcomesReport): boolean {
202
+ const names = new Set(report.rows.map((row) => row.provider));
203
+ return names.has('codex') && names.has('grok') && names.has(UNKNOWN_PROVIDER_LABEL) && report.rows.length >= 3;
204
+ }
@@ -7,9 +7,75 @@ import { writeStdoutJson } from './stdout-json.js';
7
7
  // AutopilotLoopDriver against the mission, prints agent deltas on
8
8
  // stdout + envelope/termination summary on stderr.
9
9
  export function registerAutopilotCommands(program: Command): void {
10
+ // Explicit runtime entry for a selected action; the task chooser can invoke this entry.
10
11
  const autopilotCmd = program
11
12
  .command('autopilot')
12
13
  .description('Mission-driven autopilot — runs an ACP agent against a single mission with safety + budget guards');
14
+ autopilotCmd.command('task-agent-action <actionJson>')
15
+ .description('Execute a selected task-agent action; defaults to shadow')
16
+ .action(async (actionJson: string) => {
17
+ const { executeNextAction } = await import('../task-agent/actions.js');
18
+ const { getUserConfig } = await import('../user-config.js');
19
+ const { spawnSync } = await import('node:child_process');
20
+ const { readFileSync, writeFileSync, mkdirSync } = await import('node:fs');
21
+ const { join, dirname } = await import('node:path');
22
+ const { debug } = await import('../debug/log.js');
23
+ const { withFileLockSync } = await import('../storage/file-lock.js');
24
+ const config = getUserConfig() as ReturnType<typeof getUserConfig> & { taskAgent?: { mode?: string } };
25
+ const { effectiveInstanceRoot } = await import('../instance/resolve.js');
26
+ const statePath = join(effectiveInstanceRoot(), 'task-agent-actions.json');
27
+ let state: { landingDay?: string; landingsToday?: number; failureCounts?: Record<string, number> } = {};
28
+ try { state = JSON.parse(readFileSync(statePath, 'utf8')); } catch { /* first run */ }
29
+ const action = JSON.parse(actionJson) as import('../task-agent/actions.js').NextAction;
30
+ const deps: import('../task-agent/actions.js').ActionDeps = {
31
+ ...state,
32
+ failureCounts: state.failureCounts ?? {},
33
+ saveState: () => {
34
+ mkdirSync(dirname(statePath), { recursive: true });
35
+ // 실패 횟수는 incrementFailure 가 잠금 안에서 키별로 더한다 — 여기선 덮어쓰지 않는다.
36
+ },
37
+ mode: config.taskAgent?.mode,
38
+ // 하루 착지 칸은 상태 파일 잠금 안에서 확보·반환한다 — 두 CLI 프로세스가 같은 잔여 칸을 쓰지 않게.
39
+ reserveLanding: (day, limit) => withFileLockSync(`${statePath}.lock`, () => {
40
+ let disk: typeof state = {};
41
+ try { disk = JSON.parse(readFileSync(statePath, 'utf8')); } catch { /* first */ }
42
+ const used = disk.landingDay === day ? disk.landingsToday ?? 0 : 0;
43
+ if (used >= limit) return false;
44
+ mkdirSync(dirname(statePath), { recursive: true });
45
+ writeFileSync(statePath, JSON.stringify({ ...disk, landingDay: day, landingsToday: used + 1 }));
46
+ deps.landingDay = day; deps.landingsToday = used + 1;
47
+ return true;
48
+ }),
49
+ incrementFailure: (key) => withFileLockSync(`${statePath}.lock`, () => {
50
+ let disk: typeof state = {};
51
+ try { disk = JSON.parse(readFileSync(statePath, 'utf8')); } catch { /* first */ }
52
+ const counts = { ...(disk.failureCounts ?? {}) };
53
+ counts[key] = (counts[key] ?? 0) + 1;
54
+ mkdirSync(dirname(statePath), { recursive: true });
55
+ writeFileSync(statePath, JSON.stringify({ ...disk, failureCounts: counts }));
56
+ return counts[key]!;
57
+ }),
58
+ releaseLanding: (day) => withFileLockSync(`${statePath}.lock`, () => {
59
+ let disk: typeof state = {};
60
+ try { disk = JSON.parse(readFileSync(statePath, 'utf8')); } catch { return; }
61
+ if (disk.landingDay !== day || !disk.landingsToday) return;
62
+ writeFileSync(statePath, JSON.stringify({ ...disk, landingsToday: disk.landingsToday - 1 }));
63
+ deps.landingsToday = disk.landingsToday - 1;
64
+ }),
65
+ observe: (_event, data) => debug.log('task-agent', 'action', data),
66
+ command: async args => {
67
+ const p = args[0] === 'gh'
68
+ // PR 조회는 착지할 작업 트리에서 — 다른 저장소에서 불러도 엉뚱한 PR 을 읽지 않게.
69
+ ? spawnSync('gh', args.slice(1), { encoding: 'utf8', ...(action.cwd ? { cwd: action.cwd } : {}) })
70
+ // 지금 돌고 있는 진입점으로 부른다 — 저장소 밖(설치본)에서 불러도 같은 elanous 를 쓴다.
71
+ : spawnSync(process.execPath, [process.argv[1]!, ...(process.argv.includes('--test') ? ['--test'] : []), ...args], { encoding: 'utf8' });
72
+ return { status: p.status ?? 1, stdout: p.stdout ?? '', stderr: p.stderr ?? '' };
73
+ },
74
+ };
75
+ const result = await executeNextAction(action, deps);
76
+ if (config.taskAgent?.mode === 'live') deps.saveState?.();
77
+ process.stdout.write(`${result}\n`);
78
+ });
13
79
 
14
80
  autopilotCmd
15
81
  .command('run <mission...>')
@@ -0,0 +1,73 @@
1
+ import type { Command } from 'commander';
2
+ import { BRIEF_DOMAINS, BRIEF_PRIORITIES, BRIEF_SLOTS, BriefItemsInputError, BriefItemsLedger, type BriefDomain, type BriefPriority } from '../briefing/brief-items.js';
3
+
4
+ export interface BriefCliDeps {
5
+ ledger?: BriefItemsLedger;
6
+ stateDir?: string;
7
+ now?: () => Date;
8
+ output?: (line: string) => void;
9
+ error?: (line: string) => void;
10
+ }
11
+
12
+ export function registerBriefCommands(program: Command, deps: BriefCliDeps = {}): void {
13
+ const output = deps.output ?? ((line: string) => console.log(line));
14
+ const error = deps.error ?? ((line: string) => console.error(line));
15
+ const ledger = () => deps.ledger ?? new BriefItemsLedger({
16
+ ...(deps.stateDir ? { stateDir: deps.stateDir } : {}),
17
+ ...(deps.now ? { now: deps.now } : {}),
18
+ });
19
+ const fail = (verb: string, cause: unknown) => {
20
+ error(`brief ${verb}: ${cause instanceof Error ? cause.message : String(cause)}`);
21
+ process.exitCode = cause instanceof BriefItemsInputError ? 2 : 1;
22
+ };
23
+ const brief = program.command('brief').description('대표 브리핑 항목 원장 — 발신자는 항목만 내고, 발송은 하지 않는다');
24
+
25
+ brief.command('add')
26
+ .description('주장 한 줄을 원장에 추가한다 (원문 그대로)')
27
+ .requiredOption('--text <text>', '주장 한 줄')
28
+ .requiredOption('--domain <domain>', `도메인 (${BRIEF_DOMAINS.join('|')})`)
29
+ .requiredOption('--priority <priority>', `중요도 (${BRIEF_PRIORITIES.join('|')})`)
30
+ .requiredOption('--source <source>', '출처 (루프·자리·흡수·코나투스)')
31
+ .option('--deadline <deadline>', '마감 (YYYY-MM-DD 또는 ISO)')
32
+ .option('--evidence <evidence>', '근거 링크')
33
+ .option('--created-at <iso>', '작성 시각 (ISO, 생략 시 지금)')
34
+ .option('--json', 'JSON 출력')
35
+ .action((opts: { text: string; domain: string; priority: string; source: string; deadline?: string; evidence?: string; createdAt?: string; json?: boolean }) => {
36
+ try {
37
+ const item = ledger().add({
38
+ text: opts.text,
39
+ domain: opts.domain as BriefDomain,
40
+ priority: opts.priority as BriefPriority,
41
+ source: opts.source,
42
+ ...(opts.deadline !== undefined ? { deadline: opts.deadline } : {}),
43
+ ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}),
44
+ ...(opts.createdAt !== undefined ? { createdAt: opts.createdAt } : {}),
45
+ });
46
+ output(opts.json ? JSON.stringify(item) : `added ${item.id} [${item.priority}] ${item.domain}`);
47
+ } catch (cause) { fail('add', cause); }
48
+ });
49
+
50
+ brief.command('list')
51
+ .description('원장 항목을 추가 순으로 본다')
52
+ .option('--domain <domain>', `도메인 필터 (${BRIEF_DOMAINS.join('|')})`)
53
+ .option('--json', 'JSON 출력')
54
+ .action((opts: { domain?: string; json?: boolean }) => {
55
+ try {
56
+ if (opts.domain !== undefined && !(BRIEF_DOMAINS as readonly string[]).includes(opts.domain)) {
57
+ throw new BriefItemsInputError('invalid domain');
58
+ }
59
+ const items = ledger().list(opts.domain === undefined ? {} : { domain: opts.domain as BriefDomain });
60
+ if (opts.json) output(JSON.stringify(items));
61
+ else if (items.length === 0) output('(항목 없음)');
62
+ else for (const item of items) output(`${item.id}\t${item.priority}\t${item.domain}\t${item.source}\t${item.text}`);
63
+ } catch (cause) { fail('list', cause); }
64
+ });
65
+
66
+ brief.command('compose')
67
+ .description('슬롯 이전의 미발송 항목을 한 장 마크다운으로 만든다 (표준 출력만, 발송 없음)')
68
+ .requiredOption('--slot <slot>', `슬롯 (${BRIEF_SLOTS.join('|')})`)
69
+ .action((opts: { slot: string }) => {
70
+ try { output(ledger().compose(opts.slot)); }
71
+ catch (cause) { fail('compose', cause); }
72
+ });
73
+ }