elanous 0.2.18 → 0.2.19

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 (404) 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/{P6YTVgzuZz5oTZhY4Din_ → J5H7BvqgSI8DUjtRRcA_S}/_buildManifest.js +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{726.83532d69452ee24b.js → 1470.98a6ab61ccb4131c.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{9247-269e4464cc4b491c.js → 2375-e061bb24cd2b5cde.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{211-9c9090c59e2c5907.js → 2977-e38442e86289f1d3.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → 2ea96958.e8c7e112de50b067.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +2 -0
  16. package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
  19. package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 4323.c5dfb6f503304775.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 4847.56f5bc454fbcc827.js} +2 -2
  28. package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 6062-73e68343a9814f6a.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/{935.4ddaa99fc1d1126c.js → 7647.2fcd30d894d5ee30.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/{f6231284-45726550aba08fca.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +25 -0
  49. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/live/page-b70cd005d9169e25.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +1 -0
  110. package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
  111. package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +1 -0
  112. package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
  113. package/apps/pwa/out/approvals/index.html +1 -1
  114. package/apps/pwa/out/approvals/index.txt +23 -23
  115. package/apps/pwa/out/autopilot/index.html +1 -1
  116. package/apps/pwa/out/autopilot/index.txt +23 -23
  117. package/apps/pwa/out/board/index.html +1 -1
  118. package/apps/pwa/out/board/index.txt +23 -23
  119. package/apps/pwa/out/botlab/index.html +1 -1
  120. package/apps/pwa/out/botlab/index.txt +22 -22
  121. package/apps/pwa/out/bots/index.html +1 -1
  122. package/apps/pwa/out/bots/index.txt +22 -22
  123. package/apps/pwa/out/ceo/index.html +1 -1
  124. package/apps/pwa/out/ceo/index.txt +23 -23
  125. package/apps/pwa/out/chat/index.html +1 -1
  126. package/apps/pwa/out/chat/index.txt +22 -22
  127. package/apps/pwa/out/consult/index.html +1 -1
  128. package/apps/pwa/out/consult/index.txt +23 -23
  129. package/apps/pwa/out/control/index.html +1 -1
  130. package/apps/pwa/out/control/index.txt +23 -23
  131. package/apps/pwa/out/decisions/index.html +1 -1
  132. package/apps/pwa/out/decisions/index.txt +22 -22
  133. package/apps/pwa/out/design-check/index.html +1 -1
  134. package/apps/pwa/out/design-check/index.txt +23 -23
  135. package/apps/pwa/out/editor/index.html +1 -1
  136. package/apps/pwa/out/editor/index.txt +25 -24
  137. package/apps/pwa/out/exec/index.html +1 -1
  138. package/apps/pwa/out/exec/index.txt +23 -23
  139. package/apps/pwa/out/field/index.html +1 -1
  140. package/apps/pwa/out/field/index.txt +23 -23
  141. package/apps/pwa/out/healthz +1 -1
  142. package/apps/pwa/out/index.html +1 -1
  143. package/apps/pwa/out/index.txt +22 -22
  144. package/apps/pwa/out/inside/index.html +1 -1
  145. package/apps/pwa/out/inside/index.txt +24 -23
  146. package/apps/pwa/out/intake/index.html +1 -1
  147. package/apps/pwa/out/intake/index.txt +23 -23
  148. package/apps/pwa/out/live/index.html +1 -1
  149. package/apps/pwa/out/live/index.txt +23 -23
  150. package/apps/pwa/out/loops/index.html +1 -1
  151. package/apps/pwa/out/loops/index.txt +28 -27
  152. package/apps/pwa/out/market/index.html +1 -1
  153. package/apps/pwa/out/market/index.txt +22 -22
  154. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  155. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  156. package/apps/pwa/out/morning/index.html +1 -1
  157. package/apps/pwa/out/morning/index.txt +23 -23
  158. package/apps/pwa/out/observatory/index.html +1 -1
  159. package/apps/pwa/out/observatory/index.txt +23 -23
  160. package/apps/pwa/out/ops/checklist/index.html +1 -1
  161. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  162. package/apps/pwa/out/ops/release/index.html +1 -1
  163. package/apps/pwa/out/ops/release/index.txt +22 -22
  164. package/apps/pwa/out/ops/seats/index.html +1 -1
  165. package/apps/pwa/out/ops/seats/index.txt +22 -22
  166. package/apps/pwa/out/outputs/index.html +1 -1
  167. package/apps/pwa/out/outputs/index.txt +22 -22
  168. package/apps/pwa/out/reflection/index.html +1 -1
  169. package/apps/pwa/out/reflection/index.txt +23 -23
  170. package/apps/pwa/out/scheduler/index.html +1 -1
  171. package/apps/pwa/out/scheduler/index.txt +23 -23
  172. package/apps/pwa/out/sessions/index.html +1 -1
  173. package/apps/pwa/out/sessions/index.txt +22 -22
  174. package/apps/pwa/out/settings/devices/index.html +1 -1
  175. package/apps/pwa/out/settings/devices/index.txt +23 -23
  176. package/apps/pwa/out/settings/index.html +1 -1
  177. package/apps/pwa/out/settings/index.txt +23 -23
  178. package/apps/pwa/out/setup/done/index.html +1 -1
  179. package/apps/pwa/out/setup/done/index.txt +23 -23
  180. package/apps/pwa/out/setup/index.html +1 -1
  181. package/apps/pwa/out/setup/index.txt +23 -23
  182. package/apps/pwa/out/share/index.html +1 -1
  183. package/apps/pwa/out/share/index.txt +23 -23
  184. package/apps/pwa/out/showroom/index.html +1 -1
  185. package/apps/pwa/out/showroom/index.txt +22 -22
  186. package/apps/pwa/out/tasks/index.html +1 -1
  187. package/apps/pwa/out/tasks/index.txt +23 -23
  188. package/apps/pwa/out/term/index.html +1 -1
  189. package/apps/pwa/out/term/index.txt +23 -23
  190. package/apps/pwa/out/today/index.html +1 -1
  191. package/apps/pwa/out/today/index.txt +23 -23
  192. package/apps/pwa/out/trace/index.html +1 -1
  193. package/apps/pwa/out/trace/index.txt +23 -23
  194. package/apps/pwa/out/vault/index.html +1 -1
  195. package/apps/pwa/out/vault/index.txt +23 -23
  196. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  197. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  198. package/apps/pwa/out/workflows/index.html +1 -1
  199. package/apps/pwa/out/workflows/index.txt +23 -23
  200. package/apps/pwa/out/workspace/index.html +1 -1
  201. package/apps/pwa/out/workspace/index.txt +23 -23
  202. package/apps/pwa/out/worktrees/index.html +1 -1
  203. package/apps/pwa/out/worktrees/index.txt +23 -23
  204. package/graphs/ops/guardian.yaml +26 -0
  205. package/graphs/ops/recipes.yaml +3 -0
  206. package/package.json +1 -1
  207. package/scripts/coord-channel-watch.sh +2 -2
  208. package/scripts/discord-seat-probe.ts +3 -1
  209. package/scripts/lib/pch-regress-map.ts +23 -0
  210. package/scripts/morning-report.ts +18 -4
  211. package/scripts/pch-regress.ts +88 -0
  212. package/scripts/release-loop/gate-diff.ts +18 -0
  213. package/scripts/release-loop/gate-node.ts +338 -46
  214. package/scripts/release-loop/gate-partial.ts +106 -0
  215. package/scripts/release-loop/gate-timing.ts +120 -0
  216. package/scripts/release-loop/resume-release.ts +64 -5
  217. package/scripts/release-loop/shard-plan.ts +65 -0
  218. package/scripts/release-loop/unattended-release.ts +2 -0
  219. package/scripts/video/scene-table.ts +159 -0
  220. package/src/acp/core-turn-bridge.ts +39 -2
  221. package/src/away/presence.ts +40 -0
  222. package/src/away/release-watch.ts +255 -0
  223. package/src/chat/index.ts +2 -1
  224. package/src/chat/short-question.ts +3 -40
  225. package/src/cli/away-cli.ts +68 -0
  226. package/src/cli/guardian-cli.ts +49 -0
  227. package/src/cli/intake-cli.ts +13 -7
  228. package/src/cli/log-event-names.ts +1 -0
  229. package/src/cli/pod-cli.ts +5 -1
  230. package/src/cli/pty-drive-cli.ts +14 -3
  231. package/src/cli/release-cli.ts +32 -7
  232. package/src/cli/setup-cli.ts +31 -1
  233. package/src/cli/tasks-cli.ts +104 -11
  234. package/src/context-bus/context-now-public.ts +1 -1
  235. package/src/context-bus/context-now-surfaces.ts +16 -6
  236. package/src/context-bus/context-now.ts +97 -3
  237. package/src/core-turn/short-question.ts +100 -0
  238. package/src/dashboard/dashboard-status-lines.ts +24 -4
  239. package/src/dashboard/index.ts +50 -5
  240. package/src/dashboard/input/slash-executor.ts +6 -2
  241. package/src/dashboard/slash-runtime/dashboard-handlers.ts +63 -36
  242. package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
  243. package/src/dashboard/slash-runtime/help-from-registry.ts +21 -8
  244. package/src/dashboard/slash-runtime/help-groups.ts +44 -0
  245. package/src/dashboard/slash-runtime/model-choices.ts +125 -0
  246. package/src/domains/guardian-tick.ts +138 -0
  247. package/src/domains/outbound-alert.ts +44 -13
  248. package/src/domains/schedule-registry.ts +23 -0
  249. package/src/domains/telegram-kind-route.ts +39 -0
  250. package/src/domains/watch-default-scheduler.ts +95 -0
  251. package/src/esc-abort-gate.ts +44 -0
  252. package/src/graph-runner/runner.ts +25 -5
  253. package/src/harness/harness-cli-command.ts +2 -0
  254. package/src/harness/harness-salvage-cli.ts +78 -0
  255. package/src/harness/harness-worktree-add.ts +68 -0
  256. package/src/harness/ledger-write-guard.ts +6 -16
  257. package/src/hq/hq.ts +6 -1
  258. package/src/index.ts +52 -3
  259. package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
  260. package/src/instance/test-write-guard.ts +200 -0
  261. package/src/intake-plane/digest.ts +39 -15
  262. package/src/intake-plane/harness-target.ts +4 -3
  263. package/src/intake-plane/intake-sources.ts +1 -0
  264. package/src/intake-plane/items.ts +1 -0
  265. package/src/loops/orchestrator/degradation.ts +57 -0
  266. package/src/loops/orchestrator/finish-rate.ts +33 -3
  267. package/src/loops/orchestrator/tick.ts +166 -45
  268. package/src/maturity/feature-maturity.ts +6 -4
  269. package/src/maturity/tui-slash-maturity.ts +1 -0
  270. package/src/mss/logging/log-store.ts +3 -0
  271. package/src/nexus/api/http-server.ts +16 -5
  272. package/src/nexus/api/loop-edges.ts +290 -17
  273. package/src/nexus/api/ops-api.ts +96 -28
  274. package/src/nexus/index.ts +7 -0
  275. package/src/release-loop/checklist.ts +2 -0
  276. package/src/release-loop/feature-store.ts +54 -7
  277. package/src/release-loop/light-rc.ts +173 -0
  278. package/src/release-loop/placement.ts +51 -17
  279. package/src/release-loop/rubric.ts +57 -1
  280. package/src/self-dev/dev-cli.ts +36 -0
  281. package/src/self-dev/orchestrate.ts +1 -0
  282. package/src/self-dev/pr-preview.ts +179 -0
  283. package/src/self-dev/shard-run.ts +23 -7
  284. package/src/self-implement/gate-cli.ts +29 -8
  285. package/src/self-implement/goal-author-clarification.ts +88 -13
  286. package/src/self-implement/goal-clarification-escalation.ts +2 -0
  287. package/src/self-implement/orchestrator.ts +2 -1
  288. package/src/self-implement/salvage-branches.ts +254 -0
  289. package/src/task-agent/actions.ts +63 -2
  290. package/src/task-agent/board.ts +132 -0
  291. package/src/task-agent/card-evidence.ts +203 -0
  292. package/src/task-agent/completion-evidence.ts +126 -0
  293. package/src/task-agent/judge.ts +18 -0
  294. package/src/task-agent/live-moves.ts +176 -0
  295. package/src/task-agent/mission.ts +32 -6
  296. package/src/task-agent/shadow.ts +183 -15
  297. package/src/task-agent/task-hand.ts +117 -15
  298. package/src/task-orchestrator/surfaces/pod-command-job.ts +64 -4
  299. package/src/task-orchestrator/surfaces/pod-lease.ts +142 -13
  300. package/src/task-orchestrator/surfaces/pod-pool.ts +13 -5
  301. package/src/task-orchestrator/surfaces/self-implement-pod.ts +2 -2
  302. package/src/telegram-away-command.ts +38 -0
  303. package/src/telegram-commands.ts +11 -0
  304. package/src/telegram-report-log-probe.ts +21 -0
  305. package/src/telegram-report.ts +27 -3
  306. package/src/tui-client/dashboard-session.ts +1 -0
  307. package/src/user-config.ts +134 -2
  308. package/src/version/packed-revision.json +1 -1
  309. package/apps/pwa/out/_next/static/chunks/1307-30dff22c9e1f3073.js +0 -1
  310. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
  311. package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
  314. package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/6158.af2e93b7adff709b.js +0 -2
  335. package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
  344. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d92f7ee3994e84c7.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-4733c967b56a4277.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/board/page-d22fd849cc22f104.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/botlab/page-bb742f995c6769e7.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/app/bots/page-02b073a2ac793b83.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/app/control/page-308111abadf35670.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/app/design-check/page-5d97b2901e0d1ea6.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/app/editor/page-c39554c321576982.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/app/exec/page-8c3d8e63ae2e1c98.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/app/field/page-9b72a07d424701c6.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/app/intake/page-a572f58cd3ca5864.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/app/market/page-fa244bd146a00ae8.js +0 -1
  369. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-44d56d6391965a6a.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/app/morning/page-83a700d4c754d802.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/app/not-found-7b3170f374f77b9d.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a3c58f5c72a5f3f9.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/app/reflection/page-1467047104141297.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-44d5f767491692bf.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/app/share/page-3cc47dca0922ab9a.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/app/tasks/page-b17cd46cf0e9b8cf.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/app/trace/page-7bf54c9bb9274af8.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/app/workspace/page-134da468538baf48.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/webpack-c2782b330447ffb6.js +0 -1
  403. package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
  404. /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → J5H7BvqgSI8DUjtRRcA_S}/_ssgManifest.js +0 -0
@@ -47,6 +47,7 @@ import { runReleaseIfReady } from './release-run-if-ready.js';
47
47
  import { formatPreflight, releasePreflight } from '../../scripts/release-loop/preflight.js';
48
48
  import { autoStartScheduledRelease, type AutoStartDeferral } from '../release-loop/auto-start.js';
49
49
  import { landingFreezeMessage, LandingFrozenError, readLandingFreeze } from '../release-loop/landing-freeze.js';
50
+ import { formatLightRc, runLightRc, type LightRcDeps } from '../release-loop/light-rc.js';
50
51
 
51
52
  export const DEFAULT_PUBLIC_REPO = 'ElanvitalAI/elanous';
52
53
  const SEMVER = /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/;
@@ -594,7 +595,7 @@ function compareReleaseVersions(a: string, b: string): number {
594
595
  return 0;
595
596
  }
596
597
 
597
- export function registerReleaseCommands(program: Command, releaseRunDeps: UnattendedReleaseDeps = {}, landedDeps: LandedButYellowDeps = {}, hqDeps: HqDeps = {}): void {
598
+ export function registerReleaseCommands(program: Command, releaseRunDeps: UnattendedReleaseDeps = {}, landedDeps: LandedButYellowDeps = {}, hqDeps: HqDeps = {}, lightRcDeps: LightRcDeps = {}): void {
598
599
  const release = program.command('release').description('공개 배포 한 판 — prepare(로컬) → publish(--yes) → verify (docs/manual/MANUAL-versioning-and-release-2026-09-25.md)');
599
600
  const schedule = release.command('schedule').description('판별 컷·착지 마감 조회·갱신');
600
601
  const scheduleLedgerRoot = () => releaseRunDeps.ledgerRoot ?? releaseLedgerRoot();
@@ -793,17 +794,17 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
793
794
  withContext(checklist.command('set <id>').description('칸 상태·근거·담당·처분 갱신'))
794
795
  .option('--status <status>', 'green|yellow|red|done').option('--evidence <evidence>', '근거').option('--owner <owner>', '담당')
795
796
  .option('--disposition <disposition>', 'move|known-issue|block').option('--kind <kind>', 'screen = 다섯 화면 짝 칸 · 근거에 짝: PWA … · 데스크톱 … · 폴드 … · 아이폰 … · 아이패드 … 한 줄')
796
- .option('--priority <priority>', 'P0|P1|P2').option('--deadline-version <v>', '마감 판').option('--predecessor <id...>', '선행 칸 id')
797
+ .option('--priority <priority>', 'P0|P1|P2').option('--deadline-version <v>', '마감 판').option('--predecessor <id...>', '선행 칸 id').option('--no-predecessor', '선행 칸 비우기(진짜 의존이 아니면 · CHECKLIST-PRED-CLEAR)')
797
798
  .option('--ceo-minutes <minutes>', '대표 손 분량(분)').option('--ceo-date <date>', '대표 손 날짜 YYYY-MM-DD')
798
799
  .option('--accelerator', '가속 등급 부여').option('--no-accelerator', '가속 등급 해제(기본은 없음)')
799
800
  .option('--ref <path...>', '근거 문서 더하기 — 저장소 상대 경로[#절] (반복 가능 · 중복 제거)')
800
- .action((id: string, opts: { ref?: string[]; status?: string; evidence?: string; owner?: string; disposition?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[]; ceoMinutes?: string; ceoDate?: string; accelerator?: boolean }, cmd: Command) => {
801
+ .action((id: string, opts: { ref?: string[]; status?: string; evidence?: string; owner?: string; disposition?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[] | false; ceoMinutes?: string; ceoDate?: string; accelerator?: boolean }, cmd: Command) => {
801
802
  const { version, json } = context(cmd);
802
803
  if (opts.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(opts.status)) throw new CliUserError(`잘못된 상태: ${opts.status}`, 'green|yellow|red|done');
803
804
  if (opts.disposition !== undefined && !['move', 'known-issue', 'block'].includes(opts.disposition)) throw new CliUserError(`잘못된 처분: ${opts.disposition}`, 'move|known-issue|block');
804
805
  if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined && opts.disposition === undefined && opts.kind === undefined && opts.priority === undefined && opts.deadlineVersion === undefined && opts.predecessor === undefined && opts.ceoMinutes === undefined && opts.ceoDate === undefined && opts.accelerator === undefined && opts.ref === undefined) throw new CliUserError('갱신할 칸을 지정하라', '--ref · --status · --evidence · --owner · --disposition · --kind · --priority · --deadline-version · --predecessor · --ceo-minutes · --ceo-date · --accelerator · --no-accelerator 중 하나');
805
806
  if (!mayWrite(cmd)) return;
806
- const data = setItem(version, id, { ...(opts.ref !== undefined ? { refs: opts.ref } : {}), ...(opts.status !== undefined ? { status: opts.status as ChecklistStatus } : {}), ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.disposition !== undefined ? { disposition: opts.disposition as ChecklistDisposition } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}), ...(opts.priority !== undefined ? { priority: opts.priority as PlacementPriority } : {}), ...(opts.deadlineVersion !== undefined ? { deadlineVersion: opts.deadlineVersion } : {}), ...(opts.predecessor !== undefined ? { predecessors: opts.predecessor } : {}), ...(opts.ceoMinutes !== undefined ? { ceoMinutes: ceoMinutes(opts.ceoMinutes) } : {}), ...(opts.ceoDate !== undefined ? { ceoDate: opts.ceoDate } : {}), ...(opts.accelerator !== undefined ? { accelerator: opts.accelerator ? true : null } : {}) }, process.env.ELANOUS_TRACK || 'cli');
807
+ const data = setItem(version, id, { ...(opts.ref !== undefined ? { refs: opts.ref } : {}), ...(opts.status !== undefined ? { status: opts.status as ChecklistStatus } : {}), ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.disposition !== undefined ? { disposition: opts.disposition as ChecklistDisposition } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}), ...(opts.priority !== undefined ? { priority: opts.priority as PlacementPriority } : {}), ...(opts.deadlineVersion !== undefined ? { deadlineVersion: opts.deadlineVersion } : {}), ...(opts.predecessor !== undefined ? { predecessors: opts.predecessor === false ? [] : opts.predecessor } : {}), ...(opts.ceoMinutes !== undefined ? { ceoMinutes: ceoMinutes(opts.ceoMinutes) } : {}), ...(opts.ceoDate !== undefined ? { ceoDate: opts.ceoDate } : {}), ...(opts.accelerator !== undefined ? { accelerator: opts.accelerator ? true : null } : {}) }, process.env.ELANOUS_TRACK || 'cli');
807
808
  const item = data.items.find((entry) => entry.id === id);
808
809
  const why = item?.kind === 'screen' && item.status === 'green' ? parityGap(item.evidence) : null;
809
810
  if (why) {
@@ -1054,16 +1055,40 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
1054
1055
  process.exitCode = 1;
1055
1056
  }
1056
1057
  });
1058
+ // LIGHT-RC (0.2.19) — read-only for the ledger · never touches the landing freeze or launches Pods.
1059
+ release.command('light-rc')
1060
+ .description('가벼운 RC 점검 — 직전 판 컷(또는 --base) 이후 변경 범위 self gate ⊕ 상시 목록(release/light-rc-always.txt)을 원격 게이트 호스트에서 · 새 회귀(introduced>0)면 exit 1')
1061
+ .requiredOption('--version <v>', '점검할 판(x.y.z)')
1062
+ .option('--base <ref>', '비교 기준(기본: git merge-base origin/main origin/release/<원장 직전 판>)')
1063
+ .option('--remote <host>', '게이트 호스트(기본 config gateRemote.host)')
1064
+ .option('--local', '원격 대신 이 기계에서 돌린다(원격 자식이 이것으로 다시 불린다)')
1065
+ .option('--shards <n>', '시험 분할 수', '4')
1066
+ .option('--json', '결과 한 줄 JSON(stdout) · 사람 줄은 stderr')
1067
+ .action(async (o: { version: string; base?: string; remote?: string; local?: boolean; shards: string; json?: boolean }) => {
1068
+ // The root strips `--local` from argv (remote-bookmark attach) before Commander sees it — same read as `self gate`.
1069
+ const local = !!o.local || process.argv.slice(2).includes('--local');
1070
+ await jsonAction(o.json, async (log) => {
1071
+ if (o.remote !== undefined && local) throw new Error('release light-rc: --remote and --local cannot be combined');
1072
+ const shards = Number(o.shards);
1073
+ const result = await runLightRc(o.version, { base: o.base, remote: o.remote, local, shards, ledgerRoot: releaseRunDeps.ledgerRoot, log: (l) => console.error(l) }, lightRcDeps);
1074
+ for (const line of formatLightRc(result)) log(line);
1075
+ return result;
1076
+ }, (r) => r.ok);
1077
+ });
1057
1078
  release.command('resume')
1058
1079
  .description('실패한 릴리스 런을 이어 달린다 — cut-branch --append 로 고친 release/<v> 새 끝을 version-release 기록에 반영(빨리감기만·상태 백업) 뒤 --from 노드부터')
1059
1080
  .requiredOption('--run <runId>', '실패한 release-loop 런 ID')
1060
1081
  .requiredOption('--from <node>', '다시 시작할 노드(예: gate)')
1082
+ .option('--partial', 'GATE-PARTIAL — --from gate 에서 앞 게이트 뒤 바뀐 시험 파일 ⊕ 앞 실패 파일만 다시 돈다(시험·문서만 바뀐 경우 · 아니면 전체 게이트로)')
1061
1083
  .option('--json', '결과 한 줄 JSON(stdout)')
1062
- .action(async (o: { run: string; from: string; json?: boolean }) => {
1084
+ .action(async (o: { run: string; from: string; partial?: boolean; json?: boolean }) => {
1063
1085
  await jsonAction(o.json, async (log) => {
1064
- const result = await resumeReleaseRun({ runId: o.run, from: o.from });
1086
+ const result = await resumeReleaseRun({ runId: o.run, from: o.from, partial: o.partial });
1087
+ if (result.partial.requested) log(result.partial.applied
1088
+ ? `◐ 부분 재검: ${result.partial.files}파일 · 나머지는 앞 게이트(${result.partial.priorCommit.slice(0, 12)}) 결과를 잇는다`
1089
+ : `● 전체 게이트로: ${result.partial.reason}`);
1065
1090
  log(`${result.tip.changed ? `↻ ${result.tip.branch} ${result.tip.from.slice(0, 12)} → ${result.tip.to.slice(0, 12)} (백업 ${result.tip.backup})` : `· ${result.tip.branch} 끝 그대로 ${result.tip.to.slice(0, 12)}`} · ${o.from} 부터 → ${result.state.status}`);
1066
- return { tip: result.tip, status: result.state.status, runId: result.state.runId };
1091
+ return { tip: result.tip, partial: result.partial, status: result.state.status, runId: result.state.runId };
1067
1092
  }, (r) => r.status === 'done' || r.status === 'awaiting-approval');
1068
1093
  });
1069
1094
  release.command('auto-start')
@@ -1,13 +1,16 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
+ import { createHash } from 'node:crypto';
5
+ import { addSource, listSources } from '../intake-plane/intake-sources.js';
6
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
4
7
  import { Command } from 'commander';
5
8
  import { formatDoctorReport, registerDoctorCommand, runDoctor, type DoctorOptions, type DoctorReport } from './doctor-cli.js';
6
9
  import { applyClaudePluginSetup, planClaudePluginSetup } from './claude-plugin-setup.js';
7
10
  import { recommendedSetup } from './setup-recommend.js';
8
11
  import { detectSkillCliAuth, type CliAuthStatus } from '../onboarding/cli-auth-status.js';
9
12
  import { debug } from '../debug/log.js';
10
- import { getUserConfig, saveUserConfig, type UserConfig } from '../user-config.js';
13
+ import { getUserConfig, saveUserConfig, userConfigPath, type UserConfig } from '../user-config.js';
11
14
 
12
15
  const ELANOUS_LOGIN_COMMAND = 'elanous login openai-codex';
13
16
  const PROVIDER_COMMAND = 'elanous config set llm.provider openai-codex';
@@ -29,6 +32,9 @@ export interface SetupCliDeps extends DoctorOptions {
29
32
  homeDir?: () => string;
30
33
  getUserConfig?: () => UserConfig;
31
34
  saveUserConfig?: (config: UserConfig) => void;
35
+ watchRoot?: string;
36
+ /** Test seam: injected configs are treated as already provisioned unless explicitly first-run. */
37
+ firstRunTopics?: boolean;
32
38
  planClaudePluginSetup?: typeof planClaudePluginSetup;
33
39
  applyClaudePluginSetup?: typeof applyClaudePluginSetup;
34
40
  prompt?: (message: string) => Promise<string | undefined>;
@@ -209,6 +215,7 @@ export function registerSetupCommand(program: Command, deps: SetupCliDeps = {}):
209
215
  setExitCode(1);
210
216
  return;
211
217
  }
218
+ const isFirstRun = deps.firstRunTopics ?? (!deps.getUserConfig && !existsSync(userConfigPath()));
212
219
  let steps = setupSteps(deps);
213
220
  const provider = steps[1]!;
214
221
  if (!provider.complete) {
@@ -224,6 +231,29 @@ export function registerSetupCommand(program: Command, deps: SetupCliDeps = {}):
224
231
  out.log(formatSetupReport(doctor, steps, false));
225
232
  showCliSkills(false);
226
233
  for (const line of recommendedSetup((deps.getUserConfig ?? getUserConfig)())) out.log(line);
234
+ let configured = (deps.getUserConfig ?? getUserConfig)();
235
+ if (!configured.starter?.topics && configured.watch?.enabled !== false
236
+ && isFirstRun
237
+ && isStdinTty() && !opts.yes) {
238
+ const answer = (await prompt('관심 주제는 무엇인가요? (쉼표로 구분 · 건너뛰려면 Enter) '))?.trim();
239
+ if (answer) {
240
+ configured = { ...configured, starter: { topics: answer.split(',').map((topic) => topic.trim()).filter(Boolean) } };
241
+ (deps.saveUserConfig ?? saveUserConfig)(configured);
242
+ }
243
+ }
244
+ const starter = configured.starter;
245
+ if (Array.isArray(starter?.topics) && configured.watch?.enabled !== false) {
246
+ const root = deps.watchRoot ?? effectiveInstanceRoot();
247
+ const existing = listSources({ seat: 'user' }, root);
248
+ for (const rawTopic of starter.topics) {
249
+ if (typeof rawTopic !== 'string' || !rawTopic.trim()) continue;
250
+ const topic = rawTopic.trim();
251
+ if (existing.some((source) => source.kind === 'github-query' && source.spec === topic)) continue;
252
+ const id = `watch-${createHash('sha256').update(topic).digest('hex').slice(0, 12)}`;
253
+ if (listSources({}, root).some((source) => source.id === id)) continue;
254
+ existing.push(addSource({ id, seat: 'user', kind: 'github-query', spec: topic, every: '1d', why: 'starter.topics' }, root));
255
+ }
256
+ }
227
257
  const accepted = opts.yes || (isStdinTty() && /^(?:y|yes)?$/i.test((await prompt('추천대로 켤까요? [Y/n] '))?.trim() ?? 'n'));
228
258
  if (accepted) {
229
259
  const runDoctorFix = deps.runDoctorFix ?? (async (args: readonly string[]) => {
@@ -1,11 +1,14 @@
1
1
  import { readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
2
+ import { join, resolve } from 'node:path';
3
3
  import { InvalidArgumentError, Option, type Command } from 'commander';
4
4
  import { debug } from '../debug/log.js';
5
5
  import { getElanousConfigDir } from '../elanous-config-dir.js';
6
6
  import { readNexusRuntime } from '../nexus/runtime.js';
7
+ import { checklistDevVersion } from '../release-loop/checklist.js';
8
+ import { cardsBoard, countBoardTasks, INTERNAL_PACK_PROJECT, mergeBoards, releasePackBoard, type BoardNode } from '../task-agent/board.js';
7
9
  import type { Task } from '../task-orchestrator/types.js';
8
- import { handTask, nextMoveFor, readTaskAgentState, readTaskCard, taskAgentStatePath as taskStatePathDefault, shellQuote, TASK_CARD_PREFIX, TASK_SEATS, type TaskCard, type TaskLauncher, type TaskSeat } from '../task-agent/task-hand.js';
10
+ import { cardPrNumber, COMPLETION_KINDS, handTask, nextMoveFor, readTaskAgentState, readTaskCard, taskAgentStatePath as taskStatePathDefault, shellQuote, TASK_CARD_PREFIX, TASK_SEATS, type CompletionKind, type TaskCard, type TaskLaunchContext, type TaskLaunchReceipt, type TaskLauncher, type TaskSeat } from '../task-agent/task-hand.js';
11
+ import { refreshCardRunBinding, type CardRunEvidenceDeps } from '../task-agent/card-evidence.js';
9
12
  import { advanceMission, handMission, MissionAdvanceError, openMissionIds, recordPieceRef, type AdvanceMissionResult, type MissionDecompose, type PieceEvidenceReader } from '../task-agent/mission.js';
10
13
 
11
14
  type TaskRow = Pick<Task, 'id' | 'title' | 'priority' | 'status' | 'createdAt' | 'approval' | 'generatedBy'>;
@@ -25,6 +28,10 @@ export interface TasksCliDeps {
25
28
  missionDecompose?: MissionDecompose;
26
29
  /** `task advance` 의 착지 근거 읽기(시험 주입). 기본 = `predecessorState`(gh · 런 원장). */
27
30
  pieceEvidence?: PieceEvidenceReader;
31
+ /** `task board` 의 내부 팩 어댑터(시험 주입). 기본 = 현재 판(`checklistDevVersion()`)의 `releasePackBoard` — 읽기만. */
32
+ releaseBoard?: () => BoardNode[];
33
+ /** `tasks show`·`advance` 의 런 근거 읽기(시험 주입) — 기본 = 런 원장(이 우주 ⊕ 연합) ⊕ 호스트 logs 의 Pod job-finished. */
34
+ cardRunEvidence?: CardRunEvidenceDeps;
28
35
  }
29
36
 
30
37
  /** `<조각id>=<값>` 반복 인자. */
@@ -65,16 +72,28 @@ export async function registerTaskAgentSink(register?: (surface: string) => Prom
65
72
  * config-dir 은 명시(`--config-dir`/`--test`)가 있으면 그것, 없으면 운영 루트(`prodInstanceRoot()`)다.
66
73
  * (작업 트리에서 그냥 띄우면 test 우주로 가서 동결·몫·풀 상한 밖에서 돈다.)
67
74
  */
68
- export async function defaultTaskLauncher(args: string[]): Promise<void> {
69
- const { spawn } = await import('node:child_process');
75
+ /** This package's CLI entry — never `process.argv[1]`, which is whatever script started this process
76
+ * (ORCH-LIVE-1008: the orchestrator tick runs `bun …/tick.ts delegate`, so the hand launched `tick.ts --config-dir …
77
+ * harness say …`, which exited 2 with stdio ignored — the card said `launched` and no run ever started). */
78
+ export const ELANOUS_CLI_ENTRY = resolve(import.meta.dir, '../../bin/elanous.mjs');
79
+
80
+ /** argv for a detached `elanous` child that runs `args` in `configDir`. */
81
+ export function taskLauncherArgv(configDir: string, args: readonly string[]): string[] {
82
+ return [ELANOUS_CLI_ENTRY, '--config-dir', configDir, ...args];
83
+ }
84
+
85
+ export async function defaultTaskLauncher(args: string[], cwd?: string, context?: TaskLaunchContext, deps: { spawn?: DetachedSpawn } = {}): Promise<void | TaskLaunchReceipt> {
86
+ const spawn = deps.spawn ?? ((await import('node:child_process')).spawn as unknown as DetachedSpawn);
70
87
  const { getElanousConfigDirOverride } = await import('../elanous-config-dir.js');
71
88
  const { prodInstanceRoot } = await import('../instance/resolve.js');
72
89
  const configDir = getElanousConfigDirOverride() ?? prodInstanceRoot();
73
- await spawnDetachedConfirmed(spawn as unknown as DetachedSpawn, process.execPath, [process.argv[1]!, '--config-dir', configDir, ...args]);
90
+ // TA-CARD-RUN-LINK: 런 id 를 자식 환경에 얹는다 — `harness say` 의 런(호스트 orchestrate)이 그 id 를 상속한다.
91
+ await spawnDetachedConfirmed(spawn, process.execPath, taskLauncherArgv(configDir, args), SPAWN_CONFIRM_TIMEOUT_MS, cwd, context?.env);
92
+ return context ? { runId: context.runId } : undefined;
74
93
  }
75
94
 
76
95
  /** `spawn` 의 필요한 면만(시험 주입). */
77
- export type DetachedSpawn = (command: string, args: string[], options: { detached: true; stdio: 'ignore' }) => {
96
+ export type DetachedSpawn = (command: string, args: string[], options: { detached: true; stdio: 'ignore'; cwd?: string; env?: NodeJS.ProcessEnv }) => {
78
97
  pid?: number;
79
98
  once(event: 'spawn', listener: () => void): unknown;
80
99
  once(event: 'error', listener: (error: Error) => void): unknown;
@@ -89,10 +108,10 @@ export const SPAWN_CONFIRM_TIMEOUT_MS = 2_000;
89
108
  * 떼어 띄우고 «떴는지» 확인한다 — 'spawn' 이면 성공, 'error'(ENOENT·EACCES 등)면 던진다.
90
109
  * 확인 없이 돌려주면 비동기 error 가 처리되지 않고 카드가 `launched` 로 남는다(사후 리뷰 must-fix).
91
110
  */
92
- export function spawnDetachedConfirmed(spawnFn: DetachedSpawn, command: string, args: string[], timeoutMs = SPAWN_CONFIRM_TIMEOUT_MS): Promise<void> {
111
+ export function spawnDetachedConfirmed(spawnFn: DetachedSpawn, command: string, args: string[], timeoutMs = SPAWN_CONFIRM_TIMEOUT_MS, cwd?: string, env?: Record<string, string>): Promise<void> {
93
112
  return new Promise<void>((resolve, reject) => {
94
113
  let child: ReturnType<DetachedSpawn>;
95
- try { child = spawnFn(command, args, { detached: true, stdio: 'ignore' }); } catch (error) { reject(error); return; }
114
+ try { child = spawnFn(command, args, { detached: true, stdio: 'ignore', ...(cwd ? { cwd } : {}), ...(env ? { env: { ...process.env, ...env } } : {}) }); } catch (error) { reject(error); return; }
96
115
  let settled = false;
97
116
  const finish = (error?: Error) => {
98
117
  if (settled) return;
@@ -122,10 +141,20 @@ function headLine(text: string): string {
122
141
  return first.length > 100 ? `${first.slice(0, 100)}…` : first;
123
142
  }
124
143
 
144
+ /** 런 연결 한 줄 — 런 id 가 없으면 없다(옛 카드 · shadow). */
145
+ function runLinkLine(card: TaskCard): string[] {
146
+ if (!card.runId) return [];
147
+ const number = cardPrNumber(card);
148
+ const url = typeof card.pr === 'object' ? card.pr.url : undefined;
149
+ const pr = number !== undefined ? `#${number}${url ? ` ${url}` : ''}` : '-';
150
+ return [`런: ${card.runId} · 골: ${card.goalId ?? '-'} · PR: ${pr}${card.runChildId ? ` · 자식 런: ${card.runChildId}` : ''}`];
151
+ }
152
+
125
153
  function taskCardLines(card: TaskCard): string[] {
126
154
  return [
127
155
  [card.id, card.status, card.seat ?? '-', card.checklistId ?? '-', card.createdAt, card.text].join('\t'),
128
156
  ...(card.mission ? [`미션: ${card.mission} · after: ${card.after?.length ? card.after.join(', ') : '-'}`] : []),
157
+ ...runLinkLine(card),
129
158
  `다음 수: ${moveLine(card)}`,
130
159
  ];
131
160
  }
@@ -135,7 +164,8 @@ function missionLines(mission: TaskCard, pieces: TaskCard[]): string[] {
135
164
  const edges = pieces.flatMap((piece) => (piece.after ?? []).map((before) => `간선: ${before} -> ${piece.id}`));
136
165
  return [
137
166
  [mission.id, 'mission', mission.seat ?? '-', mission.checklistId ?? '-', mission.createdAt, `조각 ${pieces.length} · 간선 ${edges.length} · 출처 ${mission.splitSource ?? '-'}`, headLine(mission.text)].join('\t'),
138
- ...pieces.map((piece) => [`조각: ${piece.id}`, piece.status, nextMoveFor(piece).kind, `after: ${piece.after?.length ? piece.after.join(',') : '-'}`, headLine(piece.text)].join('\t')),
167
+ ...pieces.map((piece) => [`조각: ${piece.id}`, piece.status, nextMoveFor(piece).kind, `after: ${piece.after?.length ? piece.after.join(',') : '-'}`, headLine(piece.text),
168
+ ...(piece.runId ? [`런: ${piece.runId}${cardPrNumber(piece) !== undefined ? ` · PR #${cardPrNumber(piece)}` : ''}`] : [])].join('\t')),
139
169
  ...edges,
140
170
  ...(mission.greenProposal ? [`green 제안: ${mission.greenProposal.at} · 칸 ${mission.greenProposal.checklistId ?? '-'} · 근거 ${Object.entries(mission.greenProposal.evidence).map(([id, ref]) => `${id}=${ref}`).join(', ')}`] : []),
141
171
  ];
@@ -255,16 +285,31 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
255
285
  .description('TASK-AGENT 에 과제 한 줄을 넘긴다 — 카드를 적고 첫 수(harness say --substrate pod --merge-by-host)를 고른다 · 기본 shadow(발사 0) · `--mission` 이면 조각 카드 ⊕ 선후로 쪼개 선행 없는 조각만 넘긴다')
256
286
  .addOption(new Option('--seat <seat>', `과제 자리 (${TASK_SEATS.join('|')})`).choices([...TASK_SEATS]))
257
287
  .option('--checklist <cellId>', '릴리스 체크리스트 칸 id')
288
+ .addOption(new Option('--completion <kind>', `종결 종류 (${COMPLETION_KINDS.join('|')} · 기본 code-pr = PR 병합)`).choices([...COMPLETION_KINDS]))
258
289
  .option('--mission <text>', '미션 문면 — 조각(`- …`·`① …`·`1. …` ⊕ `after: 1`)으로 나열돼 있으면 결정론, 아니면 분해기로 쪼갠다')
290
+ .option('--project <id>', '프로젝트 id — --target 과 함께')
291
+ .option('--goal <id>', '보드 소속 목표 id (task board)')
292
+ .option('--milestone <id>', '보드 소속 이정표 id (task board)')
293
+ .option('--target <dir>', '프로젝트 대상 디렉터리(있어야 한다 · 상대 경로는 절대로 · git 이 아니면 --completion 이 code-pr 밖이어야 한다) — 발사기를 이 디렉터리에서 띄운다')
259
294
  .option('--live', '실제로 발사한다(기본은 shadow — 명령만 출력)')
260
295
  .option('--json', 'JSON 출력')
261
- .action(async (text: string | undefined, opts: { seat?: TaskSeat; checklist?: string; mission?: string; live?: boolean; json?: boolean }) => {
296
+ .action(async (text: string | undefined, opts: { seat?: TaskSeat; checklist?: string; completion?: CompletionKind; mission?: string; project?: string; target?: string; goal?: string; milestone?: string; live?: boolean; json?: boolean }) => {
262
297
  await registerTaskAgentSink(deps.registerSink);
263
298
  if ((text === undefined) === (opts.mission === undefined)) {
264
299
  out('과제 한 줄 «또는» --mission <문면> 중 하나만 준다');
265
300
  process.exitCode = 1;
266
301
  return;
267
302
  }
303
+ if ((opts.project === undefined) !== (opts.target === undefined)) {
304
+ out('--project <id> 와 --target <dir> 를 함께 줘야 한다');
305
+ process.exitCode = 1;
306
+ return;
307
+ }
308
+ if (opts.mission !== undefined && (opts.completion !== undefined || opts.project !== undefined || opts.goal !== undefined || opts.milestone !== undefined)) {
309
+ out('--completion/--project/--target/--goal/--milestone 은 과제 한 줄에만 준다 — --mission 조각은 종전처럼 code-pr 이다');
310
+ process.exitCode = 1;
311
+ return;
312
+ }
268
313
  if (opts.mission !== undefined) {
269
314
  try {
270
315
  const result = await handMission({
@@ -297,8 +342,12 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
297
342
  try {
298
343
  const result = await handTask({
299
344
  text: text!,
345
+ ...(opts.project !== undefined && opts.target !== undefined ? { project: { id: opts.project, target: opts.target } } : {}),
346
+ ...(opts.goal !== undefined ? { goal: opts.goal } : {}),
347
+ ...(opts.milestone !== undefined ? { milestone: opts.milestone } : {}),
300
348
  ...(opts.seat ? { seat: opts.seat } : {}),
301
349
  ...(opts.checklist ? { checklistId: opts.checklist } : {}),
350
+ ...(opts.completion ? { completion: opts.completion } : {}),
302
351
  live: opts.live === true,
303
352
  ...(deps.taskStatePath ? { statePath: deps.taskStatePath } : {}),
304
353
  launcher: deps.taskLauncher ?? defaultTaskLauncher,
@@ -307,6 +356,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
307
356
  else {
308
357
  out(`${result.card.id}\t${result.mode}\t${result.launched ? 'launched' : 'not launched'}`);
309
358
  out(`elanous ${result.move.command!.map(shellQuote).join(' ')}`);
359
+ if (result.cwd) out(`(cwd: ${result.cwd})`);
310
360
  if (!result.launched) out('(shadow — 띄우지 않았다 · 띄우려면 --live)');
311
361
  }
312
362
  } catch (error) {
@@ -315,6 +365,37 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
315
365
  }
316
366
  });
317
367
 
368
+ tasks.command('board')
369
+ .description('보드 — 프로젝트 › 목표 › 이정표 › 과제. 과제 카드(project·goal·milestone)와 내부 팩(릴리스 체크리스트 = elanous › 판 › 칸)을 합쳐 읽는다(쓰지 않는다)')
370
+ .option('--project <id>', '이 프로젝트만')
371
+ .option('--json', 'JSON 출력')
372
+ .action(async (opts: { project?: string; json?: boolean }) => {
373
+ await registerTaskAgentSink(deps.registerSink);
374
+ try {
375
+ // 내부 팩은 elanous 프로젝트만 낸다 — 다른 프로젝트로 거르면 릴리스 원장을 열지 않는다(그 원장의 오류가 카드 보드를 막지 않게).
376
+ const wantsPack = opts.project === undefined || opts.project === INTERNAL_PACK_PROJECT;
377
+ const board = mergeBoards(
378
+ cardsBoard(deps.taskStatePath ?? taskStatePathDefault()),
379
+ wantsPack ? (deps.releaseBoard ?? (() => releasePackBoard(checklistDevVersion())))() : [],
380
+ );
381
+ // 무소속(unassigned: true)은 어떤 --project 로도 고르지 않는다 — 실제 프로젝트 id 와 겹치지 않게.
382
+ const selected = opts.project !== undefined ? board.filter((project) => project.unassigned !== true && project.id === opts.project) : board;
383
+ try { debug.log('task-agent', 'board', { projects: selected.length, tasks: countBoardTasks(selected), project: opts.project ?? null }); } catch { /* fail-soft */ }
384
+ if (opts.json) out(JSON.stringify(selected));
385
+ else {
386
+ const show = (node: BoardNode, depth: number): void => {
387
+ out(`${' '.repeat(depth)}${node.level}\t${node.id}\t${node.status}\t${headLine(node.title)}`);
388
+ for (const next of node.children ?? []) show(next, depth + 1);
389
+ };
390
+ if (selected.length === 0) out(opts.project !== undefined ? `프로젝트 없음: ${opts.project}` : '보드가 비어 있다');
391
+ for (const project of selected) show(project, 0);
392
+ }
393
+ } catch (error) {
394
+ out(error instanceof Error ? error.message : '보드 읽기 실패');
395
+ process.exitCode = 1;
396
+ }
397
+ });
398
+
318
399
  tasks.command('advance [missionId]')
319
400
  .description('TASK-AGENT 미션 ② — 조각의 PR 병합(근거: `--pr`/`--goal` 로 카드에 적은 PR 번호·골 id)으로 착지를 가르고 새로 준비된 조각만 한 번 넘긴다 · 모두 착지면 green «제안»만 적는다 · 기본 shadow(발사 0)')
320
401
  .option('--all', 'green 제안이 아직 없는 미션 전부')
@@ -366,6 +447,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
366
447
  ...(statePath ? { statePath } : {}),
367
448
  launcher: deps.taskLauncher ?? defaultTaskLauncher,
368
449
  ...(deps.pieceEvidence ? { readEvidence: deps.pieceEvidence } : {}),
450
+ ...(deps.cardRunEvidence ? { runEvidence: deps.cardRunEvidence } : {}),
369
451
  }));
370
452
  } catch (error) {
371
453
  // 미션 id 하나면 그대로 실패 · --all 이면 그 미션만 실패로 적고 다음 미션으로 간다(크론이 한 미션에 막히지 않게).
@@ -405,7 +487,18 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
405
487
  if (id.startsWith(TASK_CARD_PREFIX)) {
406
488
  await registerTaskAgentSink(deps.registerSink);
407
489
  let card: TaskCard | undefined;
408
- try { card = readTaskCard(id, deps.taskStatePath); } catch (error) {
490
+ try {
491
+ card = readTaskCard(id, deps.taskStatePath);
492
+ // 런 id 가 있으면 원장에서 골 id · PR 을 묶는다(찾았을 때만 적는다 · 실패는 그대로 보인다).
493
+ const bindable = card?.pieces ? card.pieces : card ? [card.id] : [];
494
+ for (const cardId of bindable) {
495
+ try { await refreshCardRunBinding(cardId, deps.taskStatePath ?? taskStatePathDefault(), deps.cardRunEvidence ?? {}); } catch (error) {
496
+ // 근거 없음으로 보인다 — 원장 부재와 갈리게 실패는 남긴다.
497
+ try { debug.log('task-agent', 'card-bind-failed', { card: cardId, via: 'show', error: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
498
+ }
499
+ }
500
+ if (card) card = readTaskCard(id, deps.taskStatePath);
501
+ } catch (error) {
409
502
  out(error instanceof Error ? error.message : '과제 카드 읽기 실패');
410
503
  process.exitCode = 1;
411
504
  return;
@@ -72,7 +72,7 @@ export function filterPublicDemoContext(answer: ContextNowAnswer, brandCheck: ty
72
72
  id: fact.id ? (rulesMissing || privateIds.has(fact.id) ? '내부 항목' : safe(fact.id, '공개 항목')) : null,
73
73
  title: fact.title === null ? null : rulesMissing ? internalItem(1) : privateIds.has(fact.id ?? '') ? '내부 항목' : safe(fact.title, '공개 항목'),
74
74
  source: source(fact.source) });
75
- } else facts.push({ ...fact, version: safe(fact.version, '공개 판'), source: source(fact.source) });
75
+ } else if (fact.kind === 'version') facts.push({ ...fact, version: safe(fact.version, '공개 판'), source: source(fact.source) });
76
76
  }
77
77
  for (const kind of ['cell', 'decision'] as const) {
78
78
  if (!collapsed[kind]) continue;
@@ -34,6 +34,9 @@ function factText(fact: ContextFact): string {
34
34
  case 'cell': return `${fact.id} ${fact.title} (${fact.status})`;
35
35
  case 'decision': return `${fact.id} ${fact.title} (${fact.status})`;
36
36
  case 'seat': return `${fact.seat} ${fact.id ?? ''} ${fact.title ?? ''} (${fact.status})`.trim();
37
+ case 'run': return fact.unreadable ? `못 읽음 · ${fact.unreadable}` : `${fact.goal} · ${fact.phase} · ${fact.elapsed}`;
38
+ case 'release': return fact.unreadable ? `못 읽음 · ${fact.unreadable}` : `${fact.version} · ${fact.node} · ${fact.status}`;
39
+ case 'schedule-late': return fact.unreadable ? `못 읽음 · ${fact.unreadable}` : `${fact.count}개 · ${fact.names.join(', ') || '없음'}`;
37
40
  }
38
41
  }
39
42
 
@@ -61,20 +64,27 @@ export function renderTuiNow(input: ContextNowAnswer, audience: ContextNowAudien
61
64
  if (source.startsWith('elanous://release/')) return '원장';
62
65
  return Array.from(source).slice(0, 40).join('');
63
66
  };
64
- const factLabels = { version: '판', cell: '칸', decision: '결정', seat: '자리' } as const;
67
+ const factLabels = { run: '도는 런', release: '발행 런', 'schedule-late': '지연 스케줄', version: '판', cell: '칸', decision: '결정', seat: '자리' } as const;
65
68
  const eventLabels: Record<string, string> = { report: '보고', dispatch: '발사', decision: '결정' };
66
69
  const events = answer.events.slice().sort((a, b) => Date.parse(b.at) - Date.parse(a.at));
67
70
  const rows = [
68
- ...answer.facts.map(f => [factLabels[f.kind], shortText(factText(f)), shortSource(f.source)]),
71
+ ...answer.facts.slice().sort((a, b) => {
72
+ const priority = (kind: ContextFact['kind']) => ({ run: 0, release: 1, 'schedule-late': 2 } as Partial<Record<ContextFact['kind'], number>>)[kind] ?? 3;
73
+ return priority(a.kind) - priority(b.kind);
74
+ }).map(f => [factLabels[f.kind], shortText(factText(f)), shortSource(f.source)]),
69
75
  ...events.slice(0, 8).map(e => [eventLabels[e.kind] ?? '소식', shortText(e.summary), shortSource(e.source)]),
70
76
  ...answer.guide.map(guide => ['안내', guide, '']),
71
77
  ...(events.length > 8 ? [['안내', `… 사건 ${events.length - 8}개 더(/now <주제> 로 좁히기)`, '']] : []),
72
78
  ];
79
+ const clean = (text: string) => text.replaceAll('|', ' ').replaceAll(/\r?\n/g, ' ');
80
+ const width = (text: string) => Array.from(text).reduce((total, char) => total + (/[\u1100-\u115f\u2e80-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe10-\ufe6f\uff01-\uff60\uffe0-\uffe6]/u.test(char) ? 2 : 1), 0);
81
+ const data = [['종류', '사실', '출처'], ...rows].map(row => row.map(clean));
82
+ const widths = [0, 1].map(i => Math.max(...data.map(row => width(row[i] ?? ''))));
83
+ const format = (row: string[]) => `${row[0]}${' '.repeat(widths[0]! - width(row[0]!) + 2)}${row[1]}${' '.repeat(widths[1]! - width(row[1]!) + 2)}${row[2]}`;
84
+ const time = new Intl.DateTimeFormat('en-GB', { timeZone: 'Asia/Seoul', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }).format(new Date(answer.at));
73
85
  return [
74
- `지금${answer.topic ? ` · ${answer.topic}` : ''} (${answer.at})`,
75
- '| 종류 | 사실 | 출처 |',
76
- '| --- | --- | --- |',
77
- ...rows.map(row => `| ${row.map(cell => cell.replaceAll('|', '\\|').replaceAll(/\r?\n/g, ' ')).join(' | ')} |`),
86
+ `지금${answer.topic ? ` · ${answer.topic}` : ''} (${time} KST)`,
87
+ ...data.map(format),
78
88
  ];
79
89
  }
80
90
 
@@ -1,4 +1,8 @@
1
- import { existsSync, readFileSync } from 'node:fs';
1
+ import { existsSync, lstatSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { queryRunningRuns, type QueriedRunningRunsResult } from '../self-implement/running-runs.js';
4
+ import { loadRunLedger } from '../self-implement/run-ledger.js';
5
+ import { localLoopRows } from '../dashboard/slash-runtime/loops-table.js';
2
6
  import { checkBrand } from '../../scripts/brand/check.js';
3
7
  import { debug, redactSecretText } from '../debug/log.js';
4
8
  import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
@@ -12,7 +16,10 @@ export type ContextFact =
12
16
  | { kind: 'version'; version: string; source: string }
13
17
  | { kind: 'cell'; version: string; id: string; title: string; status: string; owner: string | null; source: string }
14
18
  | { kind: 'decision'; id: string; title: string; status: string; dueAt: string | null; source: string }
15
- | { kind: 'seat'; seat: string; at: string; status: string; id: string | null; title: string | null; source: string };
19
+ | { kind: 'seat'; seat: string; at: string; status: string; id: string | null; title: string | null; source: string }
20
+ | { kind: 'run'; goal: string; phase: string; elapsed: string; source: string; unreadable?: string }
21
+ | { kind: 'release'; version: string; node: string; status: string; source: string; unreadable?: string }
22
+ | { kind: 'schedule-late'; count: number | null; names: string[]; source: string; unreadable?: string };
16
23
  export type ContextEvent = { at: string; kind: string; summary: string; source: string };
17
24
  export type ContextNowAnswer = { at: string; topic: string | null; facts: ContextFact[]; events: ContextEvent[]; guide: string[]; hiddenCount?: number };
18
25
 
@@ -23,6 +30,10 @@ export interface ContextNowDeps {
23
30
  decisions?: () => DecisionEntry[];
24
31
  seatEntries?: (now: Date) => Array<{ entry: SeatEntry; source: string }>;
25
32
  events?: (since: string) => CoordEvent[];
33
+ runningRuns?: (now: Date) => Extract<ContextFact, { kind: 'run' }>[];
34
+ queryRuns?: () => QueriedRunningRunsResult;
35
+ releaseRun?: () => Extract<ContextFact, { kind: 'release' }> | null;
36
+ lateSchedules?: (now: Date) => Extract<ContextFact, { kind: 'schedule-late' }> | null;
26
37
  brandCheck?: typeof checkBrand;
27
38
  }
28
39
 
@@ -39,6 +50,75 @@ function todaySeatEntries(now: Date): Array<{ entry: SeatEntry; source: string }
39
50
  });
40
51
  }
41
52
 
53
+ const runSource = 'elanous://self-implement/running-runs';
54
+ const releaseSource = 'elanous://graph-runs/release-loop';
55
+ const scheduleSource = 'elanous://schedules';
56
+ const oneLine = (text: string) => redactSecretText(text.split(/\r?\n/, 1)[0] ?? '').slice(0, 120);
57
+
58
+ function runningRunFacts(now: Date, queryRuns: () => QueriedRunningRunsResult = () => queryRunningRuns({ includeTest: false, caller: 'context-now' })): Extract<ContextFact, { kind: 'run' }>[] {
59
+ const observed = queryRuns();
60
+ const unreadable = observed.completeness === 'partial' || observed.pty.unreadable.length || !!observed.phases?.unreadableTargetCount;
61
+ const facts = observed.entries.filter(entry => observed.countedStatuses.includes(entry.status)).slice(0, 8).flatMap(entry => {
62
+ let ledgerReadFailure = false;
63
+ const starts = entry.ledgerDirectories.flatMap(directory => {
64
+ try { return loadRunLedger(entry.runId, directory)?.filter(record => record.event === 'start') ?? []; }
65
+ catch { ledgerReadFailure = true; return []; }
66
+ });
67
+ const goalRecord = starts.find(record => typeof record.data.feature === 'string' && record.data.feature.trim());
68
+ const goal = typeof goalRecord?.data.feature === 'string' ? goalRecord.data.feature : ledgerReadFailure ? '골 관측 불가' : '골 미기록';
69
+ const started = starts.map(record => record.timestamp ? Date.parse(record.timestamp) : NaN).find(Number.isFinite) ?? NaN;
70
+ const minutes = Number.isFinite(started)
71
+ ? Math.max(0, Math.floor((now.getTime() - started) / 60_000)) : null;
72
+ const fact = { kind: 'run' as const, goal: oneLine(goal),
73
+ phase: entry.lastPhase ?? '단계 미관측', elapsed: minutes === null ? ledgerReadFailure ? '경과 관측 불가' : '경과 미관측' : `${minutes}분`,
74
+ source: `${runSource}/${encodeURIComponent(entry.runId)}` };
75
+ return ledgerReadFailure
76
+ ? [
77
+ ...(goalRecord || minutes !== null ? [fact] : []),
78
+ { kind: 'run' as const, goal: '', phase: '', elapsed: '', source: fact.source, unreadable: '런 원장 일부 관측 불가' },
79
+ ] : [fact];
80
+ });
81
+ return unreadable ? [...facts, { kind: 'run', goal: '', phase: '', elapsed: '', source: runSource, unreadable: '런 원장 또는 관측 대상 일부' }] : facts;
82
+ }
83
+
84
+ function latestReleaseRun(): Extract<ContextFact, { kind: 'release' }> | null {
85
+ const dir = join(effectiveInstanceRoot(), 'graph-runs', 'release-loop');
86
+ let filenames: string[];
87
+ try { filenames = readdirSync(dir).filter(name => /^[A-Za-z0-9-]+\.json$/.test(name)); }
88
+ catch (error) {
89
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null;
90
+ throw error;
91
+ }
92
+ const runs = filenames.flatMap(filename => {
93
+ const path = join(dir, filename);
94
+ let run: { startedAt?: unknown; status?: unknown; input?: { version?: unknown }; path?: unknown; nodes?: unknown };
95
+ try {
96
+ if (!lstatSync(path).isFile()) return [];
97
+ run = JSON.parse(readFileSync(path, 'utf8'));
98
+ } catch (error) {
99
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT' || error instanceof SyntaxError) return [];
100
+ throw error;
101
+ }
102
+ if (typeof run.startedAt !== 'string' || typeof run.status !== 'string' || !Array.isArray(run.path) || !Array.isArray(run.nodes)) return [];
103
+ const steps = run.path.filter((step): step is string => typeof step === 'string');
104
+ const nodes = run.nodes.filter((node): node is { nodeId: string } => !!node && typeof node.nodeId === 'string');
105
+ const version = typeof run.input?.version === 'string' && /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/.test(run.input.version) ? run.input.version : '판 미기록';
106
+ const index = Math.min(steps.length, nodes.length + (run.status === 'running' || run.status === 'awaiting-approval' ? 1 : 0));
107
+ return [{ startedAt: run.startedAt, fact: { kind: 'release' as const, version, node: `${index}/${steps.length}${steps[index - 1] ? ` ${oneLine(steps[index - 1]!)}` : ''}`,
108
+ status: oneLine(run.status), source: `${releaseSource}/${encodeURIComponent(filename.slice(0, -5))}` } }];
109
+ });
110
+ return runs.sort((a, b) => b.startedAt.localeCompare(a.startedAt))[0]?.fact ?? null;
111
+ }
112
+
113
+ function lateScheduleFact(now: Date): Extract<ContextFact, { kind: 'schedule-late' }> {
114
+ const names = localLoopRows(now).filter(row => row.verdict === 'late').map(row => oneLine(row.name));
115
+ return { kind: 'schedule-late', count: names.length, names, source: scheduleSource };
116
+ }
117
+
118
+ function unreadableReason(error: unknown): string {
119
+ return oneLine(error instanceof Error ? error.message : String(error)) || '원천 조회 실패';
120
+ }
121
+
42
122
  /** Read a bounded, source-labelled view of current ledgers, never a transcript or decision body. */
43
123
  export function contextNow(options: { topic?: string; limit?: number; audience?: 'operator' | 'user' | 'public-demo' } = {}, deps: ContextNowDeps = {}): ContextNowAnswer {
44
124
  const now = (deps.now ?? (() => new Date()))();
@@ -71,6 +151,20 @@ export function contextNow(options: { topic?: string; limit?: number; audience?:
71
151
  const rawEvents = (deps.events ?? (since => listCoordEvents({ since })))(new Date(now.getTime() - 7 * 86_400_000).toISOString());
72
152
  const chronologicalEvents = rawEvents.slice().sort((a, b) => a.at.localeCompare(b.at));
73
153
  const matches = (text: string) => !topic || text.toLocaleLowerCase().includes(topic.toLocaleLowerCase());
154
+ const operational: ContextFact[] = [];
155
+ if (options.audience !== 'public-demo') {
156
+ try { operational.push(...(deps.runningRuns ?? (date => runningRunFacts(date, deps.queryRuns)))(now)); }
157
+ catch (error) { operational.push({ kind: 'run', goal: '', phase: '', elapsed: '', source: runSource, unreadable: unreadableReason(error) }); }
158
+ try {
159
+ const release = (deps.releaseRun ?? latestReleaseRun)();
160
+ if (release) operational.push(release);
161
+ } catch (error) { operational.push({ kind: 'release', version: '', node: '', status: '', source: releaseSource, unreadable: unreadableReason(error) }); }
162
+ try { const schedules = (deps.lateSchedules ?? lateScheduleFact)(now); if (schedules) operational.push(schedules); }
163
+ catch (error) { operational.push({ kind: 'schedule-late', count: null, names: [], source: scheduleSource, unreadable: unreadableReason(error) }); }
164
+ }
165
+ const selectedOperational = operational.filter(f => !topic || ('unreadable' in f && !!f.unreadable) || (f.kind === 'run' && matches(`${f.goal} ${f.phase}`))
166
+ || (f.kind === 'release' && matches(`${f.version} ${f.node} ${f.status}`))
167
+ || (f.kind === 'schedule-late' && matches(f.names.join(' '))));
74
168
  const selectedFacts = facts.filter(f => !topic || (f.kind === 'cell' && matches(`${f.id} ${f.title}`))
75
169
  || (f.kind === 'seat' && matches(`${f.id ?? ''} ${f.title ?? ''}`)));
76
170
  const groups = (['version', 'cell', 'decision', 'seat'] as const).map(kind => selectedFacts.filter(f => f.kind === kind));
@@ -92,7 +186,7 @@ export function contextNow(options: { topic?: string; limit?: number; audience?:
92
186
  chosen[index]!.push(...extra);
93
187
  remaining -= extra.length;
94
188
  }
95
- const boundedFacts = chosen.flat();
189
+ const boundedFacts = [...selectedOperational.slice(0, limit), ...chosen.flat()];
96
190
  const summaries = chronologicalEvents.map(event => ({
97
191
  at: event.at, kind: event.kind, summary: redactSecretText(event.summary.split(/\r?\n/, 1)[0] ?? '').slice(0, 120),
98
192
  source: event.refs.source ?? event.refs.url ?? `elanous://context/event/${encodeURIComponent(event.id)}`,