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,7 +2,7 @@
2
2
  // 공개 문서에 적힌 `elanous …` 명령·하위 명령·플래그가 «실제 CLI» 에 있나 — 문서가 실제와 어긋나는 1순위 원인을 잡는다.
3
3
  // bun scripts/docs-cli-check.ts [--json] [files…] (기본 = release/public/내부 문서 `*` ⊕ README.md)
4
4
  // 🩸 계기(2026-09-25 🅢 RFC 피드백 ③): README·install.md 가 거짓이 된 원인이 전부 «설치기·doctor 가 바뀌었는데 문서가 모름»이었다.
5
- // 자 = `bun bin/elanous.mjs <cmd> [<sub>] [<subsub>] --help` 산출(원천) — 문서의 코드 블록과 인라인 코드에서 `elanous ` 로 시작하는 것만 본다.
5
+ // 자 = `bun bin/elanous.mjs <cmd> [<sub>] [<subsub>] --help` 산출(원천) — 문서의 코드 블록과 인라인 코드에서 `elanous ` 또는 `bun bin/elanous.mjs ` 로 시작하는 것만 본다.
6
6
  // ⛔ 못 본 것은 «없다»로 적지 않는다: `--help` 가 실패하면 그 명령은 `unmeasured` 로 따로 센다.
7
7
  import { readdirSync, readFileSync, statSync } from 'node:fs';
8
8
  import { join, relative, resolve } from 'node:path';
@@ -21,10 +21,10 @@ export function extractElanousCommands(file: string, text: string): DocCommand[]
21
21
  if (/^\s*(```|~~~)/.test(raw)) { inFence = !inFence; return; }
22
22
  const candidates: string[] = [];
23
23
  if (inFence) candidates.push(raw.replace(/#.*$/, ''));
24
- for (const m of raw.matchAll(/`(elanous [^`]+)`/g)) candidates.push(m[1]!);
24
+ for (const m of raw.matchAll(/`((?:bun bin\/elanous\.mjs |elanous )[^`]+)`/g)) candidates.push(m[1]!);
25
25
  for (const c of candidates) {
26
26
  for (const seg of c.split(/&&|\|\||;|\|/)) {
27
- const m = /^(?:(?:\$\s+|>\s+|sudo\s+|[A-Za-z_][A-Za-z0-9_]*=\S+\s+))*elanous\s+(.+)$/.exec(seg.trim());
27
+ const m = /^(?:(?:\$\s+|>\s+|sudo\s+|[A-Za-z_][A-Za-z0-9_]*=\S+\s+))*(?:bun bin\/elanous\.mjs |elanous\s+)(.+)$/.exec(seg.trim());
28
28
  if (!m) continue;
29
29
  const tokens = m[1]!.trim().split(/\s+/).filter(Boolean);
30
30
  const cmd = tokens[0];
@@ -86,7 +86,7 @@ export function checkCommands(refs: readonly DocCommand[], help: HelpRunner = de
86
86
  }
87
87
  }
88
88
  for (const f of ref.flags) {
89
- if (!new RegExp(`(^|[\\s,])${f}(\\b|[\\s,=<\\[])`, 'm').test(flagsHelp)) findings.push({ kind: 'unknown-flag', ref, detail: `${f} (${flagsPath})` });
89
+ if (!new RegExp(`(^|[\\s,])${f.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(\\b|[\\s,=<\\[])`, 'm').test(flagsHelp)) findings.push({ kind: 'unknown-flag', ref, detail: `${f} (${flagsPath})` });
90
90
  }
91
91
  }
92
92
  return findings;
@@ -0,0 +1,343 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * GOAL-LIFE① — docs/goals 전수 분류(이관 전 측정).
4
+ *
5
+ * GitHub API·gh 를 부르지 않는다. git 이력과 파일 시스템만 읽고,
6
+ * docs/goals 아래 어떤 파일도 쓰지 않는다.
7
+ *
8
+ * bun scripts/goals/goal-census.ts [--json] [--out <path>]
9
+ */
10
+ import { spawnSync } from 'node:child_process';
11
+ import { readdirSync, statSync, writeFileSync } from 'node:fs';
12
+ import { join, relative, resolve } from 'node:path';
13
+ import { debug } from '../../src/debug/log.js';
14
+
15
+ export type GoalKind = 'GOAL' | 'ASK' | 'other';
16
+ export type GoalClass = 'landed' | 'untracked' | 'tracked-no-pr';
17
+
18
+ export interface GoalCensusEntry {
19
+ readonly path: string;
20
+ readonly classification: GoalClass;
21
+ readonly kind: GoalKind;
22
+ readonly bytes: number;
23
+ /** 추가 날짜 YYYY-MM-DD (KST). 미추적이면 파일 mtime. */
24
+ readonly addedDateKst: string;
25
+ readonly prNumber?: number;
26
+ readonly addCommit?: string;
27
+ }
28
+
29
+ export interface GoalCensusSummary {
30
+ readonly total: number;
31
+ readonly byClass: Record<GoalClass, number>;
32
+ readonly byKind: Record<GoalKind, number>;
33
+ readonly matrix: Record<GoalClass, Record<GoalKind, number>>;
34
+ readonly bytes: number;
35
+ readonly bytesByClass: Record<GoalClass, number>;
36
+ /** 월별 추가 수 — YYYY-MM (KST). */
37
+ readonly addedByMonth: Record<string, number>;
38
+ }
39
+
40
+ export interface GoalCensus {
41
+ readonly entries: readonly GoalCensusEntry[];
42
+ readonly summary: GoalCensusSummary;
43
+ }
44
+
45
+ export interface AddedCommit {
46
+ readonly hash: string;
47
+ readonly subject: string;
48
+ /** git %aI — ISO 8601 with offset. */
49
+ readonly authoredAt: string;
50
+ }
51
+
52
+ export interface CensusGoalsInput {
53
+ readonly repoRoot: string;
54
+ /** 경로(repo 상대, posix) → 그 파일을 처음 추가한 커밋. 없으면 미추적. */
55
+ readonly gitLog: (repoRoot: string) => ReadonlyMap<string, AddedCommit>;
56
+ /** repo 상대 미추적 경로. */
57
+ readonly listUntracked: (repoRoot: string) => readonly string[];
58
+ /** 테스트·대체 루트. 기본 docs/goals. */
59
+ readonly goalsDir?: string;
60
+ }
61
+
62
+ const CLASSES: readonly GoalClass[] = ['landed', 'untracked', 'tracked-no-pr'];
63
+ const KINDS: readonly GoalKind[] = ['GOAL', 'ASK', 'other'];
64
+
65
+ const PR_AT_END = /\(#(\d+)\)\s*$/;
66
+
67
+ export function classifyKind(relPath: string): GoalKind {
68
+ const base = relPath.split('/').pop() ?? relPath;
69
+ if (base.startsWith('GOAL')) return 'GOAL';
70
+ if (base.startsWith('ASK')) return 'ASK';
71
+ return 'other';
72
+ }
73
+
74
+ export function prNumberFromSubject(subject: string): number | undefined {
75
+ const m = subject.match(PR_AT_END);
76
+ return m ? Number(m[1]) : undefined;
77
+ }
78
+
79
+ /** UTC ISO(또는 offset 포함) → KST YYYY-MM-DD. */
80
+ export function kstDate(iso: string): string {
81
+ const t = Date.parse(iso);
82
+ if (Number.isNaN(t)) return iso.slice(0, 10);
83
+ const kst = new Date(t + 9 * 60 * 60 * 1000);
84
+ const y = kst.getUTCFullYear();
85
+ const m = String(kst.getUTCMonth() + 1).padStart(2, '0');
86
+ const d = String(kst.getUTCDate()).padStart(2, '0');
87
+ return `${y}-${m}-${d}`;
88
+ }
89
+
90
+ function emptyMatrix(): Record<GoalClass, Record<GoalKind, number>> {
91
+ const matrix = {} as Record<GoalClass, Record<GoalKind, number>>;
92
+ for (const c of CLASSES) {
93
+ matrix[c] = { GOAL: 0, ASK: 0, other: 0 };
94
+ }
95
+ return matrix;
96
+ }
97
+
98
+ function measureEntry(repoRoot: string, rel: string): { bytes: number; mtimeIso: string; files: string[] } {
99
+ const abs = resolve(repoRoot, rel);
100
+ const root = resolve(repoRoot);
101
+ try {
102
+ const st = statSync(abs);
103
+ if (!st.isDirectory()) return { bytes: st.size, mtimeIso: st.mtime.toISOString(), files: [rel] };
104
+ const files: string[] = [];
105
+ walkFiles(abs, root, files);
106
+ let bytes = 0;
107
+ for (const f of files) {
108
+ try { bytes += statSync(resolve(repoRoot, f)).size; } catch { /* skip missing */ }
109
+ }
110
+ return { bytes, mtimeIso: st.mtime.toISOString(), files };
111
+ } catch {
112
+ return { bytes: 0, mtimeIso: new Date(0).toISOString(), files: [] };
113
+ }
114
+ }
115
+
116
+ function earliestAddition(added: ReadonlyMap<string, AddedCommit>, prefix: string): AddedCommit | undefined {
117
+ let best: AddedCommit | undefined;
118
+ for (const [p, c] of added) {
119
+ if (!p.startsWith(prefix)) continue;
120
+ if (!best || c.authoredAt < best.authoredAt) best = c;
121
+ }
122
+ return best;
123
+ }
124
+
125
+ function walkFiles(dir: string, root: string, out: string[]): void {
126
+ let names: string[];
127
+ try {
128
+ names = readdirSync(dir);
129
+ } catch {
130
+ return;
131
+ }
132
+ for (const name of names) {
133
+ if (name === '.git') continue;
134
+ const abs = join(dir, name);
135
+ let st;
136
+ try {
137
+ st = statSync(abs);
138
+ } catch {
139
+ continue;
140
+ }
141
+ if (st.isDirectory()) walkFiles(abs, root, out);
142
+ else if (st.isFile()) out.push(relative(root, abs).split('\\').join('/'));
143
+ }
144
+ }
145
+
146
+ /**
147
+ * `ls docs/goals` 와 같은 최상위 이름(파일·디렉터리 각 1).
148
+ * 디렉터리의 크기·착지 근거는 그 아래 파일을 모아 정한다.
149
+ */
150
+ export function listGoalFiles(repoRoot: string, goalsDir = 'docs/goals'): string[] {
151
+ const abs = resolve(repoRoot, goalsDir);
152
+ let names: string[];
153
+ try {
154
+ names = readdirSync(abs);
155
+ } catch {
156
+ return [];
157
+ }
158
+ return names.filter((n) => n !== '.' && n !== '..').map((n) => `${goalsDir}/${n}`).sort();
159
+ }
160
+
161
+ export function summarize(entries: readonly GoalCensusEntry[]): GoalCensusSummary {
162
+ const byClass = { landed: 0, untracked: 0, 'tracked-no-pr': 0 } as Record<GoalClass, number>;
163
+ const byKind = { GOAL: 0, ASK: 0, other: 0 } as Record<GoalKind, number>;
164
+ const matrix = emptyMatrix();
165
+ const bytesByClass = { landed: 0, untracked: 0, 'tracked-no-pr': 0 } as Record<GoalClass, number>;
166
+ const addedByMonth: Record<string, number> = {};
167
+ let bytes = 0;
168
+ for (const e of entries) {
169
+ byClass[e.classification] += 1;
170
+ byKind[e.kind] += 1;
171
+ matrix[e.classification][e.kind] += 1;
172
+ bytesByClass[e.classification] += e.bytes;
173
+ bytes += e.bytes;
174
+ const month = e.addedDateKst.slice(0, 7);
175
+ addedByMonth[month] = (addedByMonth[month] ?? 0) + 1;
176
+ }
177
+ return { total: entries.length, byClass, byKind, matrix, bytes, bytesByClass, addedByMonth };
178
+ }
179
+
180
+ export function censusGoals(input: CensusGoalsInput): GoalCensus {
181
+ const goalsDir = input.goalsDir ?? 'docs/goals';
182
+ const prefix = goalsDir.endsWith('/') ? goalsDir : `${goalsDir}/`;
183
+ const files = listGoalFiles(input.repoRoot, goalsDir);
184
+ const added = input.gitLog(input.repoRoot);
185
+ const untracked = new Set(input.listUntracked(input.repoRoot).map((p) => p.split('\\').join('/')));
186
+
187
+ const entries: GoalCensusEntry[] = [];
188
+ for (const path of files) {
189
+ if (!path.startsWith(prefix)) continue;
190
+ const kind = classifyKind(path);
191
+ const measured = measureEntry(input.repoRoot, path);
192
+ const { bytes, mtimeIso } = measured;
193
+ const commit = added.get(path) ?? earliestAddition(added, `${path}/`);
194
+ const nestedUntracked = measured.files.length > 0
195
+ && measured.files.every((f) => untracked.has(f) || !added.has(f));
196
+ if (!commit || untracked.has(path) || nestedUntracked) {
197
+ entries.push({
198
+ path,
199
+ classification: 'untracked',
200
+ kind,
201
+ bytes,
202
+ addedDateKst: kstDate(mtimeIso),
203
+ });
204
+ continue;
205
+ }
206
+ const prNumber = prNumberFromSubject(commit.subject);
207
+ if (prNumber !== undefined) {
208
+ entries.push({
209
+ path,
210
+ classification: 'landed',
211
+ kind,
212
+ bytes,
213
+ addedDateKst: kstDate(commit.authoredAt),
214
+ prNumber,
215
+ addCommit: commit.hash,
216
+ });
217
+ } else {
218
+ entries.push({
219
+ path,
220
+ classification: 'tracked-no-pr',
221
+ kind,
222
+ bytes,
223
+ addedDateKst: kstDate(commit.authoredAt),
224
+ addCommit: commit.hash,
225
+ });
226
+ }
227
+ }
228
+ return { entries, summary: summarize(entries) };
229
+ }
230
+
231
+ /**
232
+ * 파일을 처음 추가한 커밋만. `git log --diff-filter=A` 한 번.
233
+ * 최신→과거 순이므로 같은 경로가 다시 나오면 나중 값(최초 추가)이 이긴다.
234
+ */
235
+ export function gitLogAdditions(repoRoot: string, goalsDir = 'docs/goals'): Map<string, AddedCommit> {
236
+ const r = spawnSync(
237
+ 'git',
238
+ [
239
+ '-C', repoRoot,
240
+ 'log',
241
+ '--diff-filter=A',
242
+ '--name-only',
243
+ '--format=@@%H%x09%s%x09%aI',
244
+ '--',
245
+ goalsDir,
246
+ ],
247
+ { encoding: 'utf8', maxBuffer: 512 * 1024 * 1024 },
248
+ );
249
+ if (r.status !== 0) {
250
+ throw new Error(`git log failed: ${(r.stderr || '').trim()}`);
251
+ }
252
+ const out = new Map<string, AddedCommit>();
253
+ let current: AddedCommit | undefined;
254
+ for (const line of r.stdout.split('\n')) {
255
+ if (!line) continue;
256
+ if (line.startsWith('@@')) {
257
+ const body = line.slice(2);
258
+ const tab = body.indexOf('\t');
259
+ const tab2 = body.lastIndexOf('\t');
260
+ if (tab > 0 && tab2 > tab) {
261
+ current = {
262
+ hash: body.slice(0, tab),
263
+ subject: body.slice(tab + 1, tab2),
264
+ authoredAt: body.slice(tab2 + 1),
265
+ };
266
+ }
267
+ continue;
268
+ }
269
+ if (current) out.set(line, current);
270
+ }
271
+ return out;
272
+ }
273
+
274
+ /** 작업 트리의 미추적 파일. `git ls-files --others --exclude-standard`. */
275
+ export function gitListUntracked(repoRoot: string, goalsDir = 'docs/goals'): string[] {
276
+ const r = spawnSync(
277
+ 'git',
278
+ ['-C', repoRoot, 'ls-files', '--others', '--exclude-standard', '--', goalsDir],
279
+ { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 },
280
+ );
281
+ if (r.status !== 0) {
282
+ throw new Error(`git ls-files failed: ${(r.stderr || '').trim()}`);
283
+ }
284
+ return r.stdout.split('\n').filter((p) => p.length > 0);
285
+ }
286
+
287
+ export function formatSummary(census: GoalCensus): string {
288
+ const { summary } = census;
289
+ const lines: string[] = [];
290
+ lines.push(`total ${summary.total} bytes ${summary.bytes}`);
291
+ lines.push('class × kind');
292
+ const header = ['class', ...KINDS, 'bytes'].join('\t');
293
+ lines.push(header);
294
+ for (const c of CLASSES) {
295
+ const row = [
296
+ c,
297
+ ...KINDS.map((k) => String(summary.matrix[c][k])),
298
+ String(summary.bytesByClass[c]),
299
+ ];
300
+ lines.push(row.join('\t'));
301
+ }
302
+ lines.push('added by month (KST)');
303
+ const months = Object.keys(summary.addedByMonth).sort();
304
+ for (const month of months) lines.push(`${month}\t${summary.addedByMonth[month]}`);
305
+ return lines.join('\n') + '\n';
306
+ }
307
+
308
+ function parseArgs(argv: readonly string[]): { json: boolean; out?: string } {
309
+ let json = false;
310
+ let out: string | undefined;
311
+ for (let i = 0; i < argv.length; i++) {
312
+ const a = argv[i];
313
+ if (a === '--json') json = true;
314
+ else if (a === '--out') {
315
+ out = argv[i + 1];
316
+ i += 1;
317
+ } else if (a.startsWith('--out=')) out = a.slice('--out='.length);
318
+ }
319
+ return { json, out };
320
+ }
321
+
322
+ function main(argv: readonly string[]): void {
323
+ const repoRoot = resolve(import.meta.dir, '../..');
324
+ const args = parseArgs(argv);
325
+ const census = censusGoals({
326
+ repoRoot,
327
+ gitLog: (root) => gitLogAdditions(root),
328
+ listUntracked: (root) => gitListUntracked(root),
329
+ });
330
+ const { summary } = census;
331
+ debug.log('goals.census', 'scanned', {
332
+ total: summary.total,
333
+ landed: summary.byClass.landed,
334
+ untracked: summary.byClass.untracked,
335
+ trackedNoPr: summary.byClass['tracked-no-pr'],
336
+ bytes: summary.bytes,
337
+ });
338
+ const body = args.json ? JSON.stringify(census, null, 2) + '\n' : formatSummary(census);
339
+ if (args.out) writeFileSync(args.out, body);
340
+ process.stdout.write(body);
341
+ }
342
+
343
+ if (import.meta.main) main(process.argv.slice(2));
@@ -0,0 +1,169 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { parse as parseYaml } from 'yaml';
6
+ import { judge } from '../src/llm/judge-layer.js';
7
+ import { effectiveInstanceRoot } from '../src/instance/resolve.js';
8
+ import { canonicalUrl } from '../src/intake-plane/items.js';
9
+
10
+ export interface NewsSource { name: string; rss: string }
11
+ export interface NewsArticle { title: string; description: string; url: string; published: string }
12
+ export interface NewsCheck { items: { fact: string; line: string; verdict: string; current: string }[] }
13
+ export interface NewsDeps {
14
+ fetch?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
15
+ now?: () => Date;
16
+ check?: (facts: readonly string[]) => Promise<NewsCheck>;
17
+ classify?: (article: NewsArticle) => Promise<boolean>;
18
+ log?: (message: string) => void;
19
+ }
20
+
21
+ const AXES = /하니스|harness|에이전트|agent|코딩\s*(?:에이전트|agent)|coding\s*agent|로컬\s*(?:llm|언어모델)|local\s*llm|기업.{0,12}ai\s*도입|enterprise\s*ai|ai\s*투자|인공지능\s*투자|ai\s*investment/i;
22
+ const AMBIGUOUS = /\bAI\b|인공지능|생성형|\bLLM\b/i;
23
+ const entity = (text: string): string => text.replace(/&#(x[\da-f]+|\d+);|&(amp|lt|gt|quot|apos|nbsp|lsquo|rsquo|ldquo|rdquo|middot|hellip|ndash|mdash);/gi, (match, numeric: string | undefined, named: string | undefined) => {
24
+ if (numeric) {
25
+ const code = numeric[0]?.toLowerCase() === 'x' ? parseInt(numeric.slice(1), 16) : parseInt(numeric, 10);
26
+ return code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : match;
27
+ }
28
+ return ({ amp: '&', lt: '<', gt: '>', quot: '"', apos: "'", nbsp: ' ', lsquo: '‘', rsquo: '’', ldquo: '“', rdquo: '”', middot: '·', hellip: '…', ndash: '–', mdash: '—' } as Record<string, string>)[named?.toLowerCase() ?? ''] ?? match;
29
+ });
30
+ const plain = (text: string): string => entity(text.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1').replace(/<[^>]+>/g, ' ')).replace(/\s+/g, ' ').trim();
31
+ const field = (xml: string, tag: string): string => plain(xml.match(new RegExp(`<${tag}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/${tag}>`, 'i'))?.[1] ?? '');
32
+
33
+ /** RSS 2.0 items, including CDATA, escaped HTML and pubDate. */
34
+ export function parseNewsRss(xml: string): NewsArticle[] {
35
+ if (!/<rss\b|<rdf:RDF\b/i.test(xml)) throw new Error('RSS 응답이 아님');
36
+ return [...xml.matchAll(/<item(?:\s[^>]*)?>([\s\S]*?)<\/item>/gi)].flatMap((match) => {
37
+ const item = match[1] ?? '';
38
+ const url = field(item, 'link');
39
+ const published = field(item, 'pubDate');
40
+ return /^https?:\/\//i.test(url) && Number.isFinite(Date.parse(published))
41
+ ? [{ title: field(item, 'title'), description: field(item, 'description'), url, published }]
42
+ : [];
43
+ });
44
+ }
45
+
46
+ export function newsInterest(article: NewsArticle): 'yes' | 'no' | 'ambiguous' {
47
+ const text = `${article.title} ${article.description}`;
48
+ if (AXES.test(text)) return 'yes';
49
+ return AMBIGUOUS.test(text) ? 'ambiguous' : 'no';
50
+ }
51
+
52
+ async function classifyAmbiguous(article: NewsArticle): Promise<boolean> {
53
+ const decision = await judge({
54
+ site: 'intake.news.interest',
55
+ prompt: `제목·요약만 보고 하니스·에이전트·코딩 에이전트·로컬 LLM·기업 AI 도입·AI 투자 관련 기사인지 판정한다. JSON {"relevant":true|false} 만 답하라.\n제목: ${article.title}\n요약: ${article.description}`,
56
+ schema: (value) => value && typeof value === 'object' && typeof (value as { relevant?: unknown }).relevant === 'boolean'
57
+ ? (value as { relevant: boolean }).relevant : null,
58
+ });
59
+ if (!decision.ok) throw new Error('관심 축 판정 실패');
60
+ return decision.value;
61
+ }
62
+
63
+ /** The CLI, not a local imitation of the checker, is the implication authority. No --author or release mutation. */
64
+ async function checkWithCli(facts: readonly string[]): Promise<NewsCheck> {
65
+ const result = spawnSync('bun', ['bin/elanous.mjs', '--test', 'intake', 'check', ...facts.flatMap((fact) => ['--fact', fact]), '--json'], {
66
+ cwd: resolve(import.meta.dir, '..'), encoding: 'utf8', timeout: 120_000, maxBuffer: 4 * 1024 * 1024,
67
+ });
68
+ if (result.error || result.status !== 0) throw new Error(result.error?.message ?? result.stderr?.trim() ?? `intake check exited ${result.status}`);
69
+ // Standalone notifications may precede JSON on stdout.
70
+ const start = result.stdout.indexOf('{');
71
+ if (start < 0) throw new Error('intake check JSON 없음');
72
+ const parsed = JSON.parse(result.stdout.slice(start)) as NewsCheck;
73
+ if (!Array.isArray(parsed.items)) throw new Error('intake check items 없음');
74
+ return parsed;
75
+ }
76
+
77
+ function summarize(html: string): string[] {
78
+ const body = html.replace(/<script\b[^>]*>[\s\S]*?<\/script>|<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ');
79
+ const paragraphs = [...body.matchAll(/<(?:p|h[1-4])\b[^>]*>([\s\S]*?)<\/(?:p|h[1-4])>/gi)]
80
+ .map((match) => plain(match[1] ?? '')).filter((text) => text.length >= 25 && !/무단전재|저작권|기자\s*=/.test(text));
81
+ if (!paragraphs.length) throw new Error('본문을 읽지 못함');
82
+ const text = paragraphs.join(' ');
83
+ const sentences = text.split(/(?<=[.!?。])\s+/).map((s) => s.trim()).filter(Boolean);
84
+ // 앞 문장 요약(최대 다섯 줄) — 짧은 기사는 있는 문장만 남긴다. 낱말을 잘라 줄 수를 채우지 않는다(사실을 지어내지 않는다).
85
+ return sentences.slice(0, 5).map((sentence) => sentence.slice(0, 240));
86
+ }
87
+
88
+ export async function runIntakeNews(options: { root: string; sources: readonly NewsSource[]; deps?: NewsDeps }): Promise<{ written: number; failed: number }> {
89
+ const { root, sources, deps = {} } = options;
90
+ const request = deps.fetch ?? fetch;
91
+ const log = deps.log ?? ((message: string) => console.error(message));
92
+ const now = (deps.now ?? (() => new Date()))();
93
+ const day = new Date(now.getTime() + 9 * 3600_000).toISOString().slice(0, 10);
94
+ const state = join(root, 'intake', 'news-seen.jsonl');
95
+ const lens = join(root, 'intake', 'outbox', 'lens', `${day}.jsonl`);
96
+ const seen = new Set(existsSync(state) ? readFileSync(state, 'utf8').split('\n').filter(Boolean) : []);
97
+ // 렌즈에 이미 쓴 기사도 «본 것» — 렌즈 기록과 상태 기록 사이에서 죽어도 재실행이 같은 기사를 다시 쓰지 않는다(24h 창이라 어제·오늘 파일).
98
+ const yesterday = new Date(now.getTime() + 9 * 3600_000 - 86_400_000).toISOString().slice(0, 10);
99
+ for (const lensDay of [yesterday, day]) {
100
+ const file = join(root, 'intake', 'outbox', 'lens', `${lensDay}.jsonl`);
101
+ if (!existsSync(file)) continue;
102
+ for (const line of readFileSync(file, 'utf8').split('\n')) {
103
+ try { const id = (JSON.parse(line) as { id?: unknown }).id; if (typeof id === 'string') seen.add(id); } catch { /* 깨진 줄은 건너뛴다 */ }
104
+ }
105
+ }
106
+ let written = 0;
107
+ let failed = 0;
108
+ for (const source of sources) {
109
+ try {
110
+ const response = await request(source.rss, { signal: AbortSignal.timeout(30_000) });
111
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
112
+ for (const article of parseNewsRss(await response.text())) {
113
+ const published = Date.parse(article.published);
114
+ if (published > now.getTime() || published < now.getTime() - 86_400_000) continue;
115
+ const id = createHash('sha256').update(canonicalUrl(article.url) ?? article.url).digest('hex').slice(0, 16);
116
+ if (seen.has(id)) continue;
117
+ const interest = newsInterest(article);
118
+ if (interest === 'no') continue;
119
+ try {
120
+ if (interest === 'ambiguous' && !(await (deps.classify ?? classifyAmbiguous)(article))) continue;
121
+ const page = await request(article.url, { signal: AbortSignal.timeout(30_000) });
122
+ if (!page.ok) throw new Error(`본문 HTTP ${page.status}`);
123
+ const summary = summarize(await page.text());
124
+ const name = article.title.match(/\b[A-Z][A-Z0-9]{2,}\b/)?.[0] ?? article.title;
125
+ // 함의는 기사 하나에서 셋까지 — 제목 ⊕ 요약 앞 두 문장을 각각 «엘라누스가 이걸 하나»로 대조한다(판정선: 함의 3).
126
+ const facts = [
127
+ `elanous는 \`${name}\` 관련 ${article.title} 기능을 지원한다`,
128
+ ...summary.slice(0, 2).map((line) => `elanous는 다음 일을 한다: ${line.slice(0, 160)}`),
129
+ ];
130
+ const check = await (deps.check ?? checkWithCli)(facts);
131
+ if (!check.items.length) throw new Error('intake check 항목 없음');
132
+ // «판단 필요» is a review suggestion, not an asserted absence; keep the check verdict attached.
133
+ const suggestions = check.items.filter((item) => item.verdict === '없음' || item.verdict === '판단 필요')
134
+ .map((item) => ({ kind: '칸 제안', fact: item.fact, status: '제안', verdict: item.verdict }));
135
+ const verdict = suggestions.length ? '보강'
136
+ : check.items.every((item) => item.verdict === '있음') ? '참고' : '경쟁 대조';
137
+ const record = {
138
+ id, at: now.toISOString(), source: source.name, url: article.url, title: article.title,
139
+ summary, implication: check.items.map((item) => item.line), suggestions,
140
+ lensVerdict: verdict, why: `엘라누스 대조: ${check.items.map((item) => item.line).join(' · ')}`.slice(0, 500), target: name,
141
+ };
142
+ mkdirSync(dirname(lens), { recursive: true });
143
+ appendFileSync(lens, `${JSON.stringify(record)}\n`);
144
+ mkdirSync(dirname(state), { recursive: true });
145
+ appendFileSync(state, `${id}\n`);
146
+ seen.add(id);
147
+ written++;
148
+ } catch (error) {
149
+ failed++;
150
+ log(`못 모음: ${article.url} — ${error instanceof Error ? error.message : String(error)}`);
151
+ }
152
+ }
153
+ } catch (error) {
154
+ failed++;
155
+ log(`못 모음: ${source.name} — ${error instanceof Error ? error.message : String(error)}`);
156
+ }
157
+ }
158
+ return { written, failed };
159
+ }
160
+
161
+ if (import.meta.main) {
162
+ const config = parseYaml(readFileSync(resolve(import.meta.dir, '../graphs/intake/news-sources.yaml'), 'utf8')) as { sources?: NewsSource[] };
163
+ if (!Array.isArray(config.sources) || !config.sources.every((source) => source && typeof source.name === 'string' && source.name.trim() && typeof source.rss === 'string' && /^https?:\/\//.test(source.rss))) {
164
+ throw new Error('news-sources.yaml: sources 배열 필요');
165
+ }
166
+ const result = await runIntakeNews({ root: effectiveInstanceRoot(), sources: config.sources });
167
+ console.log(`intake-news: ${result.written}건 · 못 모음 ${result.failed}건`);
168
+ if (result.failed) process.exitCode = 1;
169
+ }
@@ -0,0 +1,84 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { readFileSync } from 'node:fs';
3
+
4
+ export interface AuthorSummary {
5
+ authorRunId: string;
6
+ sectionChars: Record<string, number>;
7
+ }
8
+
9
+ export interface AssignedGoalEvent {
10
+ category: string;
11
+ event: string;
12
+ ts: string;
13
+ data: { authorRunId?: string; goalId?: string };
14
+ }
15
+
16
+ export interface ExecutionRun {
17
+ runId: string;
18
+ goalId: string;
19
+ startedAt: string;
20
+ outcome: string;
21
+ }
22
+
23
+ export interface SectionContribution {
24
+ authorRunId: string;
25
+ goalId: string;
26
+ runId: string;
27
+ section: string;
28
+ chars: number;
29
+ outcome: string;
30
+ }
31
+
32
+ /** Join an authored summary to the first execution starting after that author's assignment. */
33
+ export function measureAuthorSectionContribution(
34
+ summaries: readonly AuthorSummary[],
35
+ events: readonly AssignedGoalEvent[],
36
+ runs: readonly ExecutionRun[],
37
+ ): SectionContribution[] {
38
+ const rows: SectionContribution[] = [];
39
+ for (const summary of summaries) {
40
+ const assignment = events.filter((event) => event.category === 'goal-author'
41
+ && event.event === 'goal-id-assigned'
42
+ && event.data?.authorRunId === summary.authorRunId
43
+ && typeof event.data.goalId === 'string'
44
+ && Number.isFinite(Date.parse(event.ts)))
45
+ .sort((left, right) => Date.parse(left.ts) - Date.parse(right.ts)
46
+ || left.data.goalId!.localeCompare(right.data.goalId!))[0];
47
+ if (!assignment) continue;
48
+ const assignedAt = Date.parse(assignment.ts);
49
+ const run = runs.filter((candidate) => candidate.goalId === assignment.data.goalId
50
+ && Number.isFinite(Date.parse(candidate.startedAt))
51
+ && Date.parse(candidate.startedAt) >= assignedAt)
52
+ .sort((left, right) => Date.parse(left.startedAt) - Date.parse(right.startedAt)
53
+ || left.runId.localeCompare(right.runId))[0];
54
+ if (!run) continue;
55
+ for (const [section, chars] of Object.entries(summary.sectionChars)) {
56
+ rows.push({ authorRunId: summary.authorRunId, goalId: run.goalId, runId: run.runId, section, chars, outcome: run.outcome });
57
+ }
58
+ }
59
+ return rows;
60
+ }
61
+
62
+ function readRunLedger(path: string): ExecutionRun[] {
63
+ const db = new Database(path, { readonly: true });
64
+ try {
65
+ const records = db.query('SELECT run_id, goal_id, doc FROM goal_run').all() as Array<{ run_id: string; goal_id: string; doc: string }>;
66
+ return records.map(({ run_id, goal_id, doc }) => {
67
+ const record = JSON.parse(doc) as { startedAt: string; outcome: string };
68
+ return { runId: run_id, goalId: goal_id, startedAt: record.startedAt, outcome: record.outcome };
69
+ });
70
+ } finally {
71
+ db.close();
72
+ }
73
+ }
74
+
75
+ if (import.meta.main) {
76
+ const [summaryFile, eventsFile, ledgerFile] = process.argv.slice(2);
77
+ if (!summaryFile || !eventsFile || !ledgerFile) throw new Error('usage: bun scripts/measure/author-section-contribution.ts <summaries.json> <events.jsonl> <goal-runs.db>');
78
+ const summaries = JSON.parse(readFileSync(summaryFile, 'utf8')) as AuthorSummary[];
79
+ const events = readFileSync(eventsFile, 'utf8').split('\n').filter(Boolean).map((line) => JSON.parse(line) as AssignedGoalEvent);
80
+ const rows = measureAuthorSectionContribution(summaries, events, readRunLedger(ledgerFile));
81
+ console.log('| authorRunId | goalId | runId | section | chars | outcome |');
82
+ console.log('| --- | --- | --- | --- | ---: | --- |');
83
+ for (const row of rows) console.log(`| ${row.authorRunId} | ${row.goalId} | ${row.runId} | ${row.section} | ${row.chars} | ${row.outcome} |`);
84
+ }