elanous 0.2.17 → 0.2.18

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/P6YTVgzuZz5oTZhY4Din_/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{1307-aac7332576c4d6fc.js → 1307-30dff22c9e1f3073.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{6158.9c0449a9a8d0064b.js → 6158.af2e93b7adff709b.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-3143b07a817d1da9.js → page-d92f7ee3994e84c7.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-17ba086ef82d339b.js → page-4733c967b56a4277.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/board/{page-7eb661ff23af7da2.js → page-d22fd849cc22f104.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/botlab/page-bb742f995c6769e7.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/bots/{page-6e5c3a867557298b.js → page-02b073a2ac793b83.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/consult/{page-5e77cd1802567c89.js → page-a0eb02638b1e6f31.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/control/page-308111abadf35670.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-4187666132fc98c4.js → page-5d97b2901e0d1ea6.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/app/editor/{page-09328d499162bae4.js → page-c39554c321576982.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/exec/{page-29ff3a8f84f03b6a.js → page-8c3d8e63ae2e1c98.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/field/{page-80ec7401bc83f511.js → page-9b72a07d424701c6.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/intake/{page-fc7d86467291ae38.js → page-a572f58cd3ca5864.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/loops/{page-0e2d6985fb42cd2f.js → page-c130b63862437814.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/market/{page-13e4108999f58d13.js → page-fa244bd146a00ae8.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-e8296ee284aa1293.js → page-44d56d6391965a6a.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/morning/{page-043a9111e74f6b24.js → page-83a700d4c754d802.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/{not-found-23a4601ceb7f452e.js → not-found-7b3170f374f77b9d.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-10d958605a77b3bd.js → page-a3c58f5c72a5f3f9.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-7a1d85e121065cc8.js → page-e8e1ecaea7cd71d2.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-12916918988ac97e.js → page-1467047104141297.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d8a3347a9d0c3f4a.js → page-2da6f9b8a38c6978.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-1d824510df025d18.js → page-44d5f767491692bf.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/share/{page-7f49fe645d5132f7.js → page-3cc47dca0922ab9a.js} +1 -1
  55. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-27d71f131acd9320.js → page-b17cd46cf0e9b8cf.js} +1 -1
  57. package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/trace/{page-424659bacfb8cdd7.js → page-7bf54c9bb9274af8.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-5d4fef9ccd7d406b.js → page-134da468538baf48.js} +1 -1
  64. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/{webpack-539cde99554be20f.js → webpack-c2782b330447ffb6.js} +1 -1
  66. package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +1 -0
  67. package/apps/pwa/out/approvals/index.html +1 -1
  68. package/apps/pwa/out/approvals/index.txt +14 -14
  69. package/apps/pwa/out/autopilot/index.html +1 -1
  70. package/apps/pwa/out/autopilot/index.txt +14 -14
  71. package/apps/pwa/out/board/index.html +1 -1
  72. package/apps/pwa/out/board/index.txt +14 -14
  73. package/apps/pwa/out/botlab/index.html +1 -1
  74. package/apps/pwa/out/botlab/index.txt +14 -14
  75. package/apps/pwa/out/bots/index.html +1 -1
  76. package/apps/pwa/out/bots/index.txt +14 -14
  77. package/apps/pwa/out/ceo/index.html +1 -1
  78. package/apps/pwa/out/ceo/index.txt +14 -14
  79. package/apps/pwa/out/chat/index.html +1 -1
  80. package/apps/pwa/out/chat/index.txt +14 -14
  81. package/apps/pwa/out/consult/index.html +1 -1
  82. package/apps/pwa/out/consult/index.txt +14 -14
  83. package/apps/pwa/out/control/index.html +1 -1
  84. package/apps/pwa/out/control/index.txt +14 -14
  85. package/apps/pwa/out/decisions/index.html +1 -0
  86. package/apps/pwa/out/decisions/index.txt +30 -0
  87. package/apps/pwa/out/design-check/index.html +1 -1
  88. package/apps/pwa/out/design-check/index.txt +14 -14
  89. package/apps/pwa/out/editor/index.html +1 -1
  90. package/apps/pwa/out/editor/index.txt +14 -14
  91. package/apps/pwa/out/exec/index.html +1 -1
  92. package/apps/pwa/out/exec/index.txt +14 -14
  93. package/apps/pwa/out/field/index.html +1 -1
  94. package/apps/pwa/out/field/index.txt +14 -14
  95. package/apps/pwa/out/healthz +1 -1
  96. package/apps/pwa/out/index.html +1 -1
  97. package/apps/pwa/out/index.txt +14 -14
  98. package/apps/pwa/out/inside/index.html +1 -1
  99. package/apps/pwa/out/inside/index.txt +14 -14
  100. package/apps/pwa/out/intake/index.html +1 -1
  101. package/apps/pwa/out/intake/index.txt +14 -14
  102. package/apps/pwa/out/live/index.html +1 -1
  103. package/apps/pwa/out/live/index.txt +14 -14
  104. package/apps/pwa/out/loops/index.html +1 -1
  105. package/apps/pwa/out/loops/index.txt +14 -14
  106. package/apps/pwa/out/market/index.html +1 -1
  107. package/apps/pwa/out/market/index.txt +14 -14
  108. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  109. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  110. package/apps/pwa/out/morning/index.html +1 -1
  111. package/apps/pwa/out/morning/index.txt +14 -14
  112. package/apps/pwa/out/observatory/index.html +1 -1
  113. package/apps/pwa/out/observatory/index.txt +14 -14
  114. package/apps/pwa/out/ops/checklist/index.html +1 -1
  115. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  116. package/apps/pwa/out/ops/release/index.html +1 -1
  117. package/apps/pwa/out/ops/release/index.txt +14 -14
  118. package/apps/pwa/out/ops/seats/index.html +1 -1
  119. package/apps/pwa/out/ops/seats/index.txt +14 -14
  120. package/apps/pwa/out/outputs/index.html +1 -1
  121. package/apps/pwa/out/outputs/index.txt +14 -14
  122. package/apps/pwa/out/reflection/index.html +1 -1
  123. package/apps/pwa/out/reflection/index.txt +15 -15
  124. package/apps/pwa/out/scheduler/index.html +1 -1
  125. package/apps/pwa/out/scheduler/index.txt +14 -14
  126. package/apps/pwa/out/sessions/index.html +1 -1
  127. package/apps/pwa/out/sessions/index.txt +14 -14
  128. package/apps/pwa/out/settings/devices/index.html +1 -1
  129. package/apps/pwa/out/settings/devices/index.txt +14 -14
  130. package/apps/pwa/out/settings/index.html +1 -1
  131. package/apps/pwa/out/settings/index.txt +14 -14
  132. package/apps/pwa/out/setup/done/index.html +1 -1
  133. package/apps/pwa/out/setup/done/index.txt +14 -14
  134. package/apps/pwa/out/setup/index.html +1 -1
  135. package/apps/pwa/out/setup/index.txt +14 -14
  136. package/apps/pwa/out/share/index.html +1 -1
  137. package/apps/pwa/out/share/index.txt +14 -14
  138. package/apps/pwa/out/showroom/index.html +1 -1
  139. package/apps/pwa/out/showroom/index.txt +14 -14
  140. package/apps/pwa/out/tasks/index.html +1 -1
  141. package/apps/pwa/out/tasks/index.txt +14 -14
  142. package/apps/pwa/out/term/index.html +1 -1
  143. package/apps/pwa/out/term/index.txt +14 -14
  144. package/apps/pwa/out/today/index.html +1 -1
  145. package/apps/pwa/out/today/index.txt +14 -14
  146. package/apps/pwa/out/trace/index.html +1 -1
  147. package/apps/pwa/out/trace/index.txt +14 -14
  148. package/apps/pwa/out/vault/index.html +1 -1
  149. package/apps/pwa/out/vault/index.txt +14 -14
  150. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  151. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  152. package/apps/pwa/out/workflows/index.html +1 -1
  153. package/apps/pwa/out/workflows/index.txt +14 -14
  154. package/apps/pwa/out/workspace/index.html +1 -1
  155. package/apps/pwa/out/workspace/index.txt +14 -14
  156. package/apps/pwa/out/worktrees/index.html +1 -1
  157. package/apps/pwa/out/worktrees/index.txt +14 -14
  158. package/package.json +2 -1
  159. package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
  160. package/samples/workflows/templates/chat-driven-research.yaml +35 -0
  161. package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
  162. package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
  163. package/samples/workflows/templates/manual-quickstart.yaml +21 -0
  164. package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
  165. package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
  166. package/scripts/ci-public-export-import-gate.ts +7 -2
  167. package/scripts/ci-typecheck-changed.ts +21 -6
  168. package/scripts/discord-seat-probe.ts +3 -2
  169. package/scripts/docs/generate-cli-anatomy.ts +162 -0
  170. package/scripts/goals/authoring-metrics.ts +73 -0
  171. package/scripts/landing-heal/must-fix-match.ts +7 -7
  172. package/scripts/landing-heal/run-mission.ts +3 -2
  173. package/scripts/public-export-test-run.ts +37 -2
  174. package/scripts/release-loop/checklist-gate-node.ts +13 -11
  175. package/scripts/release-loop/cut-branch.ts +2 -1
  176. package/scripts/release-loop/cutoff.ts +4 -2
  177. package/scripts/release-loop/docs-land-node.ts +8 -1
  178. package/scripts/release-loop/docs-node.ts +1 -1
  179. package/scripts/release-loop/gate-node.ts +99 -14
  180. package/scripts/release-loop/node-verdict.ts +2 -1
  181. package/scripts/release-loop/npm-publish-node.ts +11 -7
  182. package/scripts/release-loop/ops-upgrade-node.ts +8 -1
  183. package/scripts/release-loop/preflight.ts +246 -0
  184. package/scripts/release-loop/publish-node.ts +8 -1
  185. package/scripts/release-loop/release-readiness.ts +11 -6
  186. package/scripts/release-loop/release-version.ts +48 -0
  187. package/scripts/release-loop/resume-release.ts +115 -0
  188. package/scripts/release-loop/unattended-release.ts +43 -7
  189. package/scripts/release-loop/version-node.ts +145 -40
  190. package/scripts/rhythm/daily.ts +25 -4
  191. package/scripts/skills/public-check.ts +62 -12
  192. package/scripts/test-deterministic.ts +51 -33
  193. package/scripts/test-diet/effectiveness.ts +62 -1
  194. package/scripts/test-diet/lib.ts +1 -1
  195. package/scripts/test-diet/node.ts +8 -1
  196. package/src/agent-mission/review-loop.ts +4 -3
  197. package/src/agent-substrate/pr-comment-meta.ts +20 -0
  198. package/src/autopilot/build/llm-conflict-merge.ts +28 -8
  199. package/src/briefing/brief-items.ts +86 -17
  200. package/src/cli/autopilot-cli.ts +30 -11
  201. package/src/cli/brief-cli.ts +36 -2
  202. package/src/cli/freeze-cli.ts +3 -2
  203. package/src/cli/intake-cli.ts +73 -1
  204. package/src/cli/notify-cli.ts +75 -0
  205. package/src/cli/provider-cli.ts +12 -2
  206. package/src/cli/release-cli.ts +101 -17
  207. package/src/cli/tasks-cli.ts +309 -3
  208. package/src/context-bus/condense.ts +19 -3
  209. package/src/context-bus/context-day.ts +77 -24
  210. package/src/context-bus/context-first.ts +33 -0
  211. package/src/discord-self-message.ts +20 -2
  212. package/src/discord-slash-wire.ts +1 -0
  213. package/src/domains/outbound-alert.ts +200 -27
  214. package/src/domains/outbound-log-probe.ts +16 -1
  215. package/src/domains/release-tool.ts +2 -2
  216. package/src/execution-loop/gates.ts +11 -2
  217. package/src/flow-loop/split.ts +8 -4
  218. package/src/git-fs/worktree.ts +23 -3
  219. package/src/harness/harness-cli-command.ts +10 -2
  220. package/src/harness/harness-queue.ts +388 -33
  221. package/src/harness/helper-scan.ts +10 -3
  222. package/src/hq/seats.ts +369 -0
  223. package/src/index.ts +133 -15
  224. package/src/intake-plane/adopt-gate.ts +49 -0
  225. package/src/intake-plane/check.ts +3 -0
  226. package/src/intake-plane/collect-github.ts +57 -25
  227. package/src/intake-plane/discord-seat-work.ts +8 -2
  228. package/src/intake-plane/requirement-funnel.ts +316 -0
  229. package/src/knowledge/know-find.ts +10 -1
  230. package/src/loops/budget.ts +36 -0
  231. package/src/loops/orchestrator/finish-rate.ts +73 -1
  232. package/src/loops/orchestrator/grid-place.ts +11 -0
  233. package/src/loops/orchestrator/tick.ts +295 -20
  234. package/src/maturity/feature-maturity.ts +3 -2
  235. package/src/nexus/api/decisions-route.ts +11 -1
  236. package/src/nexus/api/loop-edges.ts +69 -13
  237. package/src/nexus/api/ops-seats-sources.ts +2 -2
  238. package/src/nexus/api/workflow-templates.ts +3 -0
  239. package/src/oauth/codex.ts +38 -0
  240. package/src/onboarding/first-chat-gate.ts +29 -0
  241. package/src/release-loop/checklist.ts +104 -6
  242. package/src/release-loop/feature-store.ts +10 -9
  243. package/src/release-loop/landing-freeze.ts +6 -4
  244. package/src/release-loop/placement.ts +13 -3
  245. package/src/release-loop/rubric.ts +80 -0
  246. package/src/seat-dispatch/seat-ask.ts +79 -16
  247. package/src/seat-loop/seat-loop.ts +12 -6
  248. package/src/self-dev/ask-launch-flow.ts +24 -3
  249. package/src/self-dev/ask-path-candidates.ts +68 -0
  250. package/src/self-dev/dev-pipeline.ts +4 -0
  251. package/src/self-dev/draft-triage-rules.ts +1 -1
  252. package/src/self-dev/orchestrate-cli.ts +22 -0
  253. package/src/self-dev/round-pr-supersede.ts +244 -0
  254. package/src/self-dev/run-supervisor.ts +14 -0
  255. package/src/self-implement/follow-up-goals.ts +2 -2
  256. package/src/self-implement/gate-remote.ts +427 -0
  257. package/src/self-implement/goal-author.ts +8 -3
  258. package/src/self-implement/l8-merge-queue.ts +29 -5
  259. package/src/self-implement/orchestrator.ts +59 -12
  260. package/src/self-implement/prior-draft-findings.ts +4 -3
  261. package/src/skills/skill-feedback.ts +76 -0
  262. package/src/task-agent/actions.ts +39 -6
  263. package/src/task-agent/judge.ts +31 -5
  264. package/src/task-agent/mission.ts +423 -0
  265. package/src/task-agent/shadow.ts +90 -0
  266. package/src/task-agent/task-hand.ts +235 -0
  267. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +10 -0
  268. package/src/task-orchestrator/surfaces/self-implement-pod.ts +86 -18
  269. package/src/telegram-loops-command.ts +1 -1
  270. package/src/telegram.ts +5 -20
  271. package/src/user-config.ts +63 -3
  272. package/src/version/packed-revision.json +1 -1
  273. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
  274. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
  275. package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
  276. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
  277. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
  278. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
  279. package/apps/pwa/out/_next/static/chunks/6877-fe94e76a5841ee9b.js +0 -1
  280. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
  281. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
  282. package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +0 -1
  283. package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
  284. package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +0 -1
  285. package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +0 -1
  286. package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +0 -1
  287. package/apps/pwa/out/_next/static/chunks/app/chat/page-9a41d65355fc25b9.js +0 -1
  288. package/apps/pwa/out/_next/static/chunks/app/control/page-41afec0b255ed2bb.js +0 -1
  289. package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +0 -1
  290. package/apps/pwa/out/_next/static/chunks/app/layout-de43a186e5ba5683.js +0 -1
  291. package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +0 -1
  292. package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +0 -1
  293. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +0 -1
  294. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a04260b8ccc968d4.js +0 -1
  295. package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +0 -1
  296. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +0 -1
  297. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-5a64cb5208c6a8fd.js +0 -1
  298. package/apps/pwa/out/_next/static/chunks/app/settings/page-1407ec83ea64c1f9.js +0 -1
  299. package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +0 -1
  300. package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +0 -1
  301. package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +0 -1
  302. package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +0 -1
  303. package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +0 -1
  304. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-fc2df261f8a2c7df.js +0 -1
  305. package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +0 -1
  306. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +0 -1
  307. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
  308. package/apps/pwa/out/_next/static/yRA5ur6DcPl0YHSRheH2O/_buildManifest.js +0 -1
  309. /package/apps/pwa/out/_next/static/{yRA5ur6DcPl0YHSRheH2O → P6YTVgzuZz5oTZhY4Din_}/_ssgManifest.js +0 -0
@@ -5,8 +5,10 @@
5
5
  // elanous release tag --version <x.y.z> --source <commit> [--yes]
6
6
  // elanous release verify [--version <x.y.z>]
7
7
  // elanous release notes --from <ref> [--to <ref>]
8
- // elanous release run --version <x.y.z> [--cut-commit <sha>] [--dry-run] [--json] [--if-ready]
8
+ // elanous release run --version <x.y.z> [--prerelease rc] [--main-cut | --cut-commit <sha>] [--dry-run] [--json] [--if-ready]
9
+ // elanous release resume --run <runId> --from <node> [--json] (release/<v> 수리 뒤 새 끝으로 이어 달리기)
9
10
  // elanous release auto-start [--window-minutes <n>] [--apply] [--json]
11
+ // elanous release preflight --version <x.y.z> [--publish-at <iso>] [--json] (컷 30분 전 사전 점검 · 읽기 전용 · ⛔ 있으면 exit 1)
10
12
  // elanous release cut-branch --version <x.y.z> --base <sha> --pick <sha>... [--dry-run]
11
13
  //
12
14
  // 🩸 계기(2026-09-25 v0.1.0 첫 공개): 손으로 밟은 절차에서 둘을 빠뜨릴 뻔했다 — 공개본 git 커밋(판 커밋이 공개본 커밋이 된다) ·
@@ -25,11 +27,12 @@ import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js
25
27
  import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
26
28
  import { envLiteral } from '../platform/env-literal.js';
27
29
  import { getUserConfig, userConfigPath } from '../user-config.js';
28
- import { addItem, claimItem, decodeClaimHistoryEntry, devVersion, listChecklist, ownerMatches, parseOwner, parityGap, removeItem, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition, type ChecklistKind } from '../release-loop/checklist.js';
30
+ import { addItem, cellsReferencingDoc, claimItem, decodeClaimHistoryEntry, devVersion, listChecklist, ownerMatches, parseOwner, parityGap, removeItem, renderRefsStatus, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition, type ChecklistKind } from '../release-loop/checklist.js';
29
31
  import * as features from '../release-loop/feature-store.js';
30
32
  import { evidencePlan, landedButYellow, type MergedChecklistPr } from '../release-loop/landed-but-yellow.js';
31
33
  import { formatSchedule, getSchedule, listSchedules, setSchedule } from '../release-loop/release-schedule.js';
32
34
  import { placeCell, rebalance, seatMove, type PlacementPriority } from '../release-loop/placement.js';
35
+ import { parseRubric, readRubricItems, rubricGrade, rubricScore } from '../release-loop/rubric.js';
33
36
  import { writeStdoutJson } from './stdout-json.js';
34
37
  import { CliUserError } from './cli-user-error.js';
35
38
  import { hqCliWriteAllowed, type HqDeps } from '../hq/hq.js';
@@ -37,8 +40,11 @@ import { fileLeaseStore } from '../hq/lease.js';
37
40
  import { isIsolatedLedgerWriteRoot } from '../hq/ledger-write-target.js';
38
41
  import { runUnattendedRelease, type UnattendedReleaseDeps } from '../../scripts/release-loop/unattended-release.js';
39
42
  import { cutReleaseBranch } from '../../scripts/release-loop/cut-branch.js';
43
+ import { resumeReleaseRun } from '../../scripts/release-loop/resume-release.js';
44
+ import type { PrereleaseKind } from '../../scripts/release-loop/release-version.js';
40
45
  import { releaseReadiness } from '../../scripts/release-loop/release-readiness.js';
41
46
  import { runReleaseIfReady } from './release-run-if-ready.js';
47
+ import { formatPreflight, releasePreflight } from '../../scripts/release-loop/preflight.js';
42
48
  import { autoStartScheduledRelease, type AutoStartDeferral } from '../release-loop/auto-start.js';
43
49
  import { landingFreezeMessage, LandingFrozenError, readLandingFreeze } from '../release-loop/landing-freeze.js';
44
50
 
@@ -151,7 +157,7 @@ export async function prepareRelease(opts: PrepareOptions, run: Runner = default
151
157
  must(run('git', ['add', '-A'], publicDir), 'git add');
152
158
  must(run('git', ['-c', `user.name=${name}`, '-c', `user.email=${email}`, 'commit', '-q', '--allow-empty', '-m', `elanous ${tag}`], publicDir), 'git commit');
153
159
  const publicCommit = must(run('git', ['rev-parse', 'HEAD'], publicDir), 'git rev-parse');
154
- log(`③ 공개 커밋 ${publicCommit.slice(0, 12)} (${publicRepo} main 위)`);
160
+ log(`③ 공개 커밋 ${publicCommit.slice(0, 12)} (${publicRepo} main 위${isPrerelease(opts.version) ? ` · 미리보기 — 공개 main 이 아니라 release/${opts.version} 가지 ⊕ 태그로 나간다` : ''})`);
155
161
 
156
162
  // ④ PWA 빌드 — ⛔ 빠뜨리면 «웹 화면 없는 판»(빌드 산출은 공개본에 안 실린다).
157
163
  symlinkSync(join(modulesRoot, 'node_modules'), join(publicDir, 'node_modules'), 'dir');
@@ -216,12 +222,17 @@ export function planPublish(m: ReleaseManifest, notesFile: string): PublishStep[
216
222
  // 🩸 2026-09-25 v0.1.1 드라이런: release-build 의 `files` 에는 SHA256SUMS 가 없어(체크섬 대상 목록이라) «자산 4» 로 올릴 뻔했다.
217
223
  const assets = [...new Set([...m.files.map((f) => f.name), 'SHA256SUMS'])];
218
224
  return [
219
- { what: `공개 저장소 ${m.publicRepo} main 푸시(${m.publicCommit.slice(0, 12)})`, command: 'git', args: ['push', 'origin', 'HEAD:main'], cwd: m.publicDir },
225
+ // RELEASE-REHEARSAL-RC: a prerelease never moves public main — its export goes to release/<v> plus its tag (one atomic push).
226
+ m.prerelease
227
+ ? { what: `공개 저장소 ${m.publicRepo} release/${m.version} 가지 ⊕ 태그 ${m.tag} 푸시(${m.publicCommit.slice(0, 12)} · main 무접촉)`, command: 'git',
228
+ args: ['push', '--atomic', 'origin', `${m.publicCommit}:refs/heads/release/${m.version}`, `${m.publicCommit}:refs/tags/${m.tag}`], cwd: m.publicDir }
229
+ : { what: `공개 저장소 ${m.publicRepo} main 푸시(${m.publicCommit.slice(0, 12)})`, command: 'git', args: ['push', 'origin', 'HEAD:main'], cwd: m.publicDir },
220
230
  {
221
231
  what: `릴리스 ${m.tag}${m.prerelease ? ' (prerelease)' : ''} 생성 ⊕ 자산 ${assets.length}(${assets.join(' · ')})`,
222
232
  command: 'gh',
223
233
  args: ['release', 'create', m.tag, ...assets.map((name) => join(m.distDir, name)), '--repo', m.publicRepo, '--target', m.publicCommit,
224
- '--title', `elanous ${m.tag}`, '--notes-file', notesFile, ...(m.prerelease ? ['--prerelease'] : [])],
234
+ // RELEASE-REHEARSAL-RC: a prerelease is never Latest — the one-line installer keeps serving the last stable release.
235
+ '--title', `elanous ${m.tag}${m.prerelease ? ' (pre-release)' : ''}`, '--notes-file', notesFile, ...(m.prerelease ? ['--prerelease', '--latest=false', '--verify-tag'] : [])],
225
236
  cwd: m.publicDir,
226
237
  },
227
238
  ];
@@ -660,6 +671,36 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
660
671
  };
661
672
  const withContext = (cmd: Command) => cmd.option('--version <v>', '판 또는 별칭').option('--json', '결과 JSON').option('--hq-override', '본부 임대 거부를 관측하며 수동 우회');
662
673
  const actor = () => process.env.ELANOUS_TRACK || 'cli';
674
+ withContext(checklist.command('rubric').description('루브릭 채점(읽기 전용)'))
675
+ .action(async (_opts: unknown, cmd: Command) => {
676
+ if (cmd.opts().version === undefined && cmd.parent?.opts().version === undefined) throw new CliUserError('조회할 판을 지정하라', '--version <v>');
677
+ const { version, json } = context(cmd);
678
+ const items = readRubricItems(version, releaseLedgerRoot());
679
+ const rows = items.flatMap((item) => {
680
+ const rubric = parseRubric(`${item.title}\n${item.evidence ?? ''}`);
681
+ if (!rubric) return [];
682
+ const score = rubricScore(rubric);
683
+ return [{ id: item.id, score, grade: rubricGrade(score), priority: item.priority ?? null }];
684
+ }).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
685
+ const missing = items.length - rows.length;
686
+ if (json) { await writeStdoutJson(`${JSON.stringify({ version, rows, missing })}\n`); return; }
687
+ console.log('id · 점수 · 등급 · 현재 우선순위');
688
+ for (const row of rows) console.log(`${row.id} · ${row.score} · ${row.grade} · ${row.priority ?? '-'}`);
689
+ console.log(`루브릭 없는 칸 ${missing}`);
690
+ });
691
+ withContext(checklist.command('refs <doc>').description('문서를 인용하는 칸 — 모든 판의 구현 현황(읽기 전용)'))
692
+ .action(async (doc: string, _opts: unknown, cmd: Command) => {
693
+ if (!doc.trim() || doc.trim().startsWith('#')) throw new CliUserError(`잘못된 문서: ${doc}`, '저장소 상대 경로[#절] — 예: docs/RFC-x.md#§3');
694
+ const { json } = context(cmd);
695
+ const ledgerRoot = releaseLedgerRoot();
696
+ const rows = cellsReferencingDoc(doc, features.assignedVersions(ledgerRoot).map((version) => listChecklist(version, ledgerRoot)));
697
+ const byStatus = { green: 0, yellow: 0, red: 0, done: 0 } as Record<ChecklistStatus, number>;
698
+ for (const row of rows) byStatus[row.status] += 1;
699
+ debug.log('release.checklist', 'refs-read', { doc, rows: rows.length });
700
+ if (json) { await writeStdoutJson(`${JSON.stringify({ doc, rows, byStatus })}\n`); return; }
701
+ console.log(renderRefsStatus(rows));
702
+ console.log(`칸 ${rows.length} · 🟢 ${byStatus.green} · 🟡 ${byStatus.yellow} · 🔴 ${byStatus.red} · ✅ ${byStatus.done}`);
703
+ });
663
704
  withContext(checklist.command('list').description('모든 칸')).option('--owner <owner>', '자리 또는 하위 자리로 거르기').action(async (opts: { owner?: string }, cmd: Command) => {
664
705
  const { version, codenames, json } = context(cmd);
665
706
  await checklistOutput(version, codenames, 'list', json, opts.owner);
@@ -742,10 +783,11 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
742
783
  .option('--ceo-minutes <minutes>', '대표 손 분량(분)').option('--ceo-date <date>', '대표 손 날짜 YYYY-MM-DD (없으면 판 착지일 KST)')
743
784
  .option('--accelerator', '가속 등급 — 다른 칸의 처리량을 올리는 칸(자율성·효율성·동시성). 생략하면 등급 없음')
744
785
  .option('--allow-duplicate-id', '다른 판에 같은 id 가 있어도 경고 후 추가')
745
- .action((id: string, title: string, opts: { owner?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[]; ceoMinutes?: string; ceoDate?: string; accelerator?: boolean; allowDuplicateId?: boolean }, cmd: Command) => {
786
+ .option('--ref <path...>', '근거 문서 — 저장소 상대 경로[#절] (반복 가능)')
787
+ .action((id: string, title: string, opts: { ref?: string[]; owner?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[]; ceoMinutes?: string; ceoDate?: string; accelerator?: boolean; allowDuplicateId?: boolean }, cmd: Command) => {
746
788
  const { version, json } = context(cmd);
747
789
  if (!mayWrite(cmd)) return;
748
- const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(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 ? { accelerator: true } : {}) }, { allowDuplicateId: opts.allowDuplicateId });
790
+ const data = addItem(version, { id, title, ...(opts.ref !== undefined ? { refs: opts.ref } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(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 ? { accelerator: true } : {}) }, { allowDuplicateId: opts.allowDuplicateId });
749
791
  if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 추가`);
750
792
  });
751
793
  withContext(checklist.command('set <id>').description('칸 상태·근거·담당·처분 갱신'))
@@ -754,13 +796,14 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
754
796
  .option('--priority <priority>', 'P0|P1|P2').option('--deadline-version <v>', '마감 판').option('--predecessor <id...>', '선행 칸 id')
755
797
  .option('--ceo-minutes <minutes>', '대표 손 분량(분)').option('--ceo-date <date>', '대표 손 날짜 YYYY-MM-DD')
756
798
  .option('--accelerator', '가속 등급 부여').option('--no-accelerator', '가속 등급 해제(기본은 없음)')
757
- .action((id: string, opts: { status?: string; evidence?: string; owner?: string; disposition?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[]; ceoMinutes?: string; ceoDate?: string; accelerator?: boolean }, cmd: Command) => {
799
+ .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) => {
758
801
  const { version, json } = context(cmd);
759
802
  if (opts.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(opts.status)) throw new CliUserError(`잘못된 상태: ${opts.status}`, 'green|yellow|red|done');
760
803
  if (opts.disposition !== undefined && !['move', 'known-issue', 'block'].includes(opts.disposition)) throw new CliUserError(`잘못된 처분: ${opts.disposition}`, 'move|known-issue|block');
761
- 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) throw new CliUserError('갱신할 칸을 지정하라', '--status · --evidence · --owner · --disposition · --kind · --priority · --deadline-version · --predecessor · --ceo-minutes · --ceo-date · --accelerator · --no-accelerator 중 하나');
804
+ 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 중 하나');
762
805
  if (!mayWrite(cmd)) return;
763
- const data = setItem(version, id, { ...(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');
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');
764
807
  const item = data.items.find((entry) => entry.id === id);
765
808
  const why = item?.kind === 'screen' && item.status === 'green' ? parityGap(item.evidence) : null;
766
809
  if (why) {
@@ -909,7 +952,16 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
909
952
  .action(async (o: { version: string; base: string; pick: string[]; append?: boolean; dryRun?: boolean; json?: boolean }) => {
910
953
  await jsonAction(o.json, async (log) => cutReleaseBranch({ ...o, log }), () => true);
911
954
  });
912
- const runAction = async (o: { version: string; cutCommit?: string; dryRun?: boolean; ifReady?: boolean; forceFreeze?: boolean; json?: boolean }, logRun = console.log): Promise<boolean | AutoStartDeferral | undefined> => {
955
+ const runAction = async (o: { version: string; cutCommit?: string; mainCut?: boolean; prerelease?: string; dryRun?: boolean; ifReady?: boolean; forceFreeze?: boolean; json?: boolean }, logRun = console.log): Promise<boolean | AutoStartDeferral | undefined> => {
956
+ // Only the rc rehearsal channel is operated for now (alpha/beta stay valid version shapes, not run options).
957
+ if (o.prerelease !== undefined && o.prerelease !== 'rc') {
958
+ const message = `--prerelease supports rc only: ${o.prerelease}`;
959
+ if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, error: message })}\n`);
960
+ else console.error(`⛔ ${message}`);
961
+ process.exitCode = 1;
962
+ return false;
963
+ }
964
+ const prerelease = o.prerelease as PrereleaseKind | undefined;
913
965
  if (!o.dryRun) {
914
966
  try {
915
967
  const frozen = readLandingFreeze(releaseRunDeps.freezeRoot);
@@ -935,12 +987,13 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
935
987
  return false;
936
988
  }
937
989
  }
938
- if (o.ifReady && !o.dryRun) {
990
+ // A prerelease rehearsal never reads the stable version's readiness (checklist cells): it runs directly.
991
+ if (o.ifReady && !o.dryRun && !prerelease) {
939
992
  try {
940
993
  const outcome = await runReleaseIfReady(o.version, {
941
994
  ledgerRoot: releaseRunDeps.ledgerRoot,
942
995
  readiness: (version, options) => releaseReadiness(version, { ledgerRoot: releaseRunDeps.ledgerRoot, checklist: releaseRunDeps.checklist, ...options }),
943
- run: () => runUnattendedRelease({ version: o.version, cutCommit: o.cutCommit, forceFreeze: o.forceFreeze }, releaseRunDeps),
996
+ run: () => runUnattendedRelease({ version: o.version, cutCommit: o.cutCommit, mainCut: o.mainCut, prerelease, forceFreeze: o.forceFreeze }, releaseRunDeps),
944
997
  });
945
998
  if (outcome.skipped) {
946
999
  if (o.json) await writeStdoutJson(`${JSON.stringify(outcome)}\n`);
@@ -968,7 +1021,7 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
968
1021
  }
969
1022
  }
970
1023
  await jsonAction(o.json, async (log) => {
971
- const result = await runUnattendedRelease({ version: o.version, dryRun: o.dryRun, cutCommit: o.cutCommit, forceFreeze: o.forceFreeze }, releaseRunDeps);
1024
+ const result = await runUnattendedRelease({ version: o.version, dryRun: o.dryRun, cutCommit: o.cutCommit, mainCut: o.mainCut, prerelease, forceFreeze: o.forceFreeze }, releaseRunDeps);
972
1025
  log(`${result.dryRun ? '· 드라이런' : '▶ 릴리스 루프'} ${result.input.version} · 입력 ${JSON.stringify(result.input)}`);
973
1026
  return result;
974
1027
  }, (r) => r.dryRun || r.state?.status === 'done' || r.state?.status === 'awaiting-approval');
@@ -976,12 +1029,43 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
976
1029
  release.command('run')
977
1030
  .description('릴리스 루프 실행 — 원장 직전 판과 release.loop 설정으로 그래프 입력 구성 · --dry-run 은 입력만 출력')
978
1031
  .requiredOption('--version <v>', '공개할 판(x.y.z)')
979
- .option('--cut-commit <sha>', 'origin/main HEAD 대신 컷으로 쓸 커밋')
1032
+ .option('--cut-commit <sha>', 'origin/main HEAD 대신 컷으로 쓸 커밋(이미 자른 release/<v> 끝 — 옛 경로)')
1033
+ .option('--main-cut', '옛 경로: 판 올림을 main 에 착지(기본은 release/<v> 가지에만 올린다)')
1034
+ .option('--prerelease <kind>', '미리보기 판(rc) — <v>-rc.<다음 빈 번호> · npm next · GitHub pre-release · docs-land/ops-upgrade/dev-bump 건너뜀')
980
1035
  .option('--dry-run', '체크리스트·그래프 실행 없이 전체 입력 보기')
981
1036
  .option('--if-ready', '준비되지 않았으면 사유를 출력하고 성공으로 건너뛴다')
982
- .option('--force-freeze', '동결 중 게이트·발행 강행(관측 기록)')
1037
+ .option('--force-freeze', '동결 중 게이트·발행 강행(관측 기록) — 가지 컷은 동결이 필요 없다 · 동결은 비상 스위치로 남는다')
983
1038
  .option('--json', '결과 한 줄 JSON(stdout) · 사람 줄은 stderr')
984
- .action(async (o: { version: string; cutCommit?: string; dryRun?: boolean; ifReady?: boolean; forceFreeze?: boolean; json?: boolean }) => { await runAction(o); });
1039
+ .action(async (o: { version: string; cutCommit?: string; mainCut?: boolean; prerelease?: string; dryRun?: boolean; ifReady?: boolean; forceFreeze?: boolean; json?: boolean }) => { await runAction(o); });
1040
+ release.command('preflight')
1041
+ .description('컷 30분 전 사전 점검(읽기 전용) — 체크리스트 게이트 모의 · 발행 뒤 칸 · run --dry-run 입력 · 동결 · 일정/게이트 실측. ⛔ 있으면 exit 1')
1042
+ .requiredOption('--version <v>', '점검할 판(x.y.z)')
1043
+ .option('--publish-at <iso>', '계획 발행 시각(기본: 컷 뒤 첫 07·18시 KST)')
1044
+ .option('--json', '결과 한 줄 JSON(stdout)')
1045
+ .action(async (o: { version: string; publishAt?: string; json?: boolean }) => {
1046
+ try {
1047
+ const result = await releasePreflight(o.version, { publishAt: o.publishAt, releaseRunDeps });
1048
+ if (o.json) await writeStdoutJson(`${JSON.stringify(result)}\n`);
1049
+ else for (const line of formatPreflight(result)) console.log(line);
1050
+ if (result.blockers > 0) process.exitCode = 1;
1051
+ } catch (e) {
1052
+ if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, error: (e as Error).message })}\n`);
1053
+ else console.error(`⛔ ${(e as Error).message}`);
1054
+ process.exitCode = 1;
1055
+ }
1056
+ });
1057
+ release.command('resume')
1058
+ .description('실패한 릴리스 런을 이어 달린다 — cut-branch --append 로 고친 release/<v> 새 끝을 version-release 기록에 반영(빨리감기만·상태 백업) 뒤 --from 노드부터')
1059
+ .requiredOption('--run <runId>', '실패한 release-loop 런 ID')
1060
+ .requiredOption('--from <node>', '다시 시작할 노드(예: gate)')
1061
+ .option('--json', '결과 한 줄 JSON(stdout)')
1062
+ .action(async (o: { run: string; from: string; json?: boolean }) => {
1063
+ await jsonAction(o.json, async (log) => {
1064
+ const result = await resumeReleaseRun({ runId: o.run, from: o.from });
1065
+ 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 };
1067
+ }, (r) => r.status === 'done' || r.status === 'awaiting-approval');
1068
+ });
985
1069
  release.command('auto-start')
986
1070
  .description('판 일정의 컷 창에서 릴리스 시작을 미리 본다 · --apply 로 release run --if-ready 실행')
987
1071
  .option('--window-minutes <n>', '컷 뒤 선택 창(분)', '15')
@@ -1,9 +1,12 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import type { Command } from 'commander';
3
+ import { InvalidArgumentError, Option, type Command } from 'commander';
4
+ import { debug } from '../debug/log.js';
4
5
  import { getElanousConfigDir } from '../elanous-config-dir.js';
5
6
  import { readNexusRuntime } from '../nexus/runtime.js';
6
7
  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';
9
+ import { advanceMission, handMission, MissionAdvanceError, openMissionIds, recordPieceRef, type AdvanceMissionResult, type MissionDecompose, type PieceEvidenceReader } from '../task-agent/mission.js';
7
10
 
8
11
  type TaskRow = Pick<Task, 'id' | 'title' | 'priority' | 'status' | 'createdAt' | 'approval' | 'generatedBy'>;
9
12
 
@@ -12,6 +15,130 @@ export interface TasksCliDeps {
12
15
  bearerToken?: string;
13
16
  fetch?: typeof fetch;
14
17
  output?: (line: string) => void;
18
+ /** `task hand --live` 의 발사기(시험 주입). 기본은 운영 config-dir 로 `harness say` 를 떼어 띄운다. */
19
+ taskLauncher?: TaskLauncher;
20
+ /** 과제 카드 상태 파일(시험 주입). 기본 = `effectiveInstanceRoot()/task-agent-actions.json`. */
21
+ taskStatePath?: string;
22
+ /** logs.db 싱크 등록(시험 주입). 기본 = `registerStandaloneLogSink`. `hand`·`show` 가 부른다. */
23
+ registerSink?: (surface: string) => Promise<unknown>;
24
+ /** `task hand --mission` 의 분해기(시험 주입). 기본 = 나열형은 결정론 · 아니면 `decomposeSelfDevGoal`. */
25
+ missionDecompose?: MissionDecompose;
26
+ /** `task advance` 의 착지 근거 읽기(시험 주입). 기본 = `predecessorState`(gh · 런 원장). */
27
+ pieceEvidence?: PieceEvidenceReader;
28
+ }
29
+
30
+ /** `<조각id>=<값>` 반복 인자. */
31
+ function collectPair(value: string, previous: Array<[string, string]> = []): Array<[string, string]> {
32
+ const at = value.indexOf('=');
33
+ if (at <= 0 || at === value.length - 1) throw new InvalidArgumentError(`<조각id>=<값> 모양이 아니다: ${value}`);
34
+ return [...previous, [value.slice(0, at), value.slice(at + 1)]];
35
+ }
36
+
37
+ function advanceLines(result: AdvanceMissionResult): string[] {
38
+ // 제안이 끝난 미션은 다시 조회하지 않는다 — «착지 0»으로 보이지 않게 따로 적는다.
39
+ if (result.green === 'already-proposed' && Object.keys(result.evidence).length === 0) return [`${result.missionId}\tadvance\t${result.mode}\tgreen already-proposed — 다시 조회·넘기지 않는다`];
40
+ return [
41
+ `${result.missionId}\tadvance\t${result.mode}\t착지 ${result.landed.length} · 준비 ${result.ready.length} · 넘김 ${result.handed.length} · green ${result.green}`,
42
+ ...Object.entries(result.evidence).map(([id, state]) => ` 근거: ${id}\t${state}${result.landed.includes(id) ? '\t착지' : ''}`),
43
+ ...result.handed.map((piece) => ` 넘김: ${piece.pieceId}\t${piece.launched ? 'launched' : piece.error ? 'failed' : 'not launched'}${piece.error ? `\t${piece.error}` : ''}`),
44
+ ...(result.green === 'proposed' ? [' green 제안을 미션 카드에 적었다 — 체크리스트는 바꾸지 않는다(OP/주인이 뒤집는다)'] : []),
45
+ ];
46
+ }
47
+
48
+ /** `tasks hand`·`show` 가 남기는 `task-agent` 관측의 logs.db surface 라벨. */
49
+ export const TASK_AGENT_SINK_SURFACE = 'task-agent';
50
+
51
+ /**
52
+ * 독립 CLI 프로세스는 데몬의 logs.db 싱크를 상속하지 않는다 — 등록하지 않으면 `debug.log('task-agent', …)` 가
53
+ * 파일 트레일에만 남고 `elanous logs --category task-agent` 에 0건으로 보인다(10-06 실측: 카드 3장 · 이벤트 0).
54
+ * fail-open — 등록 실패가 과제 넘기기를 막지 않는다.
55
+ */
56
+ export async function registerTaskAgentSink(register?: (surface: string) => Promise<unknown>): Promise<void> {
57
+ try {
58
+ const fn = register ?? (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink;
59
+ await fn(TASK_AGENT_SINK_SURFACE);
60
+ } catch { /* fail-open — 파일 트레일이 진실원 */ }
61
+ }
62
+
63
+ /**
64
+ * 기본 발사기 — 지금 도는 진입점으로 `--config-dir <운영 config>` 를 붙여 떼어 띄운다.
65
+ * config-dir 은 명시(`--config-dir`/`--test`)가 있으면 그것, 없으면 운영 루트(`prodInstanceRoot()`)다.
66
+ * (작업 트리에서 그냥 띄우면 test 우주로 가서 동결·몫·풀 상한 밖에서 돈다.)
67
+ */
68
+ export async function defaultTaskLauncher(args: string[]): Promise<void> {
69
+ const { spawn } = await import('node:child_process');
70
+ const { getElanousConfigDirOverride } = await import('../elanous-config-dir.js');
71
+ const { prodInstanceRoot } = await import('../instance/resolve.js');
72
+ const configDir = getElanousConfigDirOverride() ?? prodInstanceRoot();
73
+ await spawnDetachedConfirmed(spawn as unknown as DetachedSpawn, process.execPath, [process.argv[1]!, '--config-dir', configDir, ...args]);
74
+ }
75
+
76
+ /** `spawn` 의 필요한 면만(시험 주입). */
77
+ export type DetachedSpawn = (command: string, args: string[], options: { detached: true; stdio: 'ignore' }) => {
78
+ pid?: number;
79
+ once(event: 'spawn', listener: () => void): unknown;
80
+ once(event: 'error', listener: (error: Error) => void): unknown;
81
+ removeListener(event: string, listener: (...args: never[]) => void): unknown;
82
+ unref(): void;
83
+ };
84
+
85
+ /** 발사 확인 대기 상한 — 'spawn'·'error' 어느 쪽도 이 안에 안 오면 pid 유무로 판정한다. */
86
+ export const SPAWN_CONFIRM_TIMEOUT_MS = 2_000;
87
+
88
+ /**
89
+ * 떼어 띄우고 «떴는지» 확인한다 — 'spawn' 이면 성공, 'error'(ENOENT·EACCES 등)면 던진다.
90
+ * 확인 없이 돌려주면 비동기 error 가 처리되지 않고 카드가 `launched` 로 남는다(사후 리뷰 must-fix).
91
+ */
92
+ export function spawnDetachedConfirmed(spawnFn: DetachedSpawn, command: string, args: string[], timeoutMs = SPAWN_CONFIRM_TIMEOUT_MS): Promise<void> {
93
+ return new Promise<void>((resolve, reject) => {
94
+ let child: ReturnType<DetachedSpawn>;
95
+ try { child = spawnFn(command, args, { detached: true, stdio: 'ignore' }); } catch (error) { reject(error); return; }
96
+ let settled = false;
97
+ const finish = (error?: Error) => {
98
+ if (settled) return;
99
+ settled = true;
100
+ clearTimeout(timer);
101
+ child.removeListener('spawn', onSpawn);
102
+ // error 리스너는 남겨 둔다 — 확인 뒤 늦게 오는 error 도 처리되지 않은 예외가 되지 않게.
103
+ if (error) { reject(error); return; }
104
+ child.unref();
105
+ resolve();
106
+ };
107
+ const onSpawn = () => finish();
108
+ const onError = (error: Error) => finish(error);
109
+ child.once('spawn', onSpawn);
110
+ child.once('error', onError);
111
+ const timer = setTimeout(() => finish(child.pid ? undefined : new Error(`발사 확인 시간 초과(${timeoutMs}ms) · pid 없음`)), timeoutMs);
112
+ });
113
+ }
114
+
115
+ function moveLine(card: TaskCard): string {
116
+ const move = nextMoveFor(card);
117
+ return `${move.kind}${move.command ? ` — elanous ${move.command.map(shellQuote).join(' ')}` : ''} (${move.reason})`;
118
+ }
119
+
120
+ function headLine(text: string): string {
121
+ const first = text.split('\n')[0]!.trim();
122
+ return first.length > 100 ? `${first.slice(0, 100)}…` : first;
123
+ }
124
+
125
+ function taskCardLines(card: TaskCard): string[] {
126
+ return [
127
+ [card.id, card.status, card.seat ?? '-', card.checklistId ?? '-', card.createdAt, card.text].join('\t'),
128
+ ...(card.mission ? [`미션: ${card.mission} · after: ${card.after?.length ? card.after.join(', ') : '-'}`] : []),
129
+ `다음 수: ${moveLine(card)}`,
130
+ ];
131
+ }
132
+
133
+ /** 미션 카드 — 머리 한 줄 ⊕ 조각 한 줄씩(상태·다음 수·선행) ⊕ 간선 한 줄씩. */
134
+ function missionLines(mission: TaskCard, pieces: TaskCard[]): string[] {
135
+ const edges = pieces.flatMap((piece) => (piece.after ?? []).map((before) => `간선: ${before} -> ${piece.id}`));
136
+ return [
137
+ [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')),
139
+ ...edges,
140
+ ...(mission.greenProposal ? [`green 제안: ${mission.greenProposal.at} · 칸 ${mission.greenProposal.checklistId ?? '-'} · 근거 ${Object.entries(mission.greenProposal.evidence).map(([id, ref]) => `${id}=${ref}`).join(', ')}`] : []),
141
+ ];
15
142
  }
16
143
 
17
144
  const PRIORITY_RANK: Record<Task['priority'], number> = { urgent: 0, high: 1, medium: 2, low: 3 };
@@ -89,7 +216,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
89
216
  if (!auth) throw new NexusUnavailable();
90
217
  return auth;
91
218
  };
92
- const tasks = program.command('tasks').alias('task').description('TOX 태스크 조회 및 승인');
219
+ const tasks = program.command('tasks').alias('task').description('TOX 태스크 조회 및 승인 · TASK-AGENT 과제 넘기기(hand)');
93
220
 
94
221
  tasks.command('list')
95
222
  .description('우선순위와 생성 시각 순으로 태스크 보기')
@@ -124,10 +251,189 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
124
251
  } catch (error) { handleError(error, out); }
125
252
  });
126
253
 
254
+ tasks.command('hand [text]')
255
+ .description('TASK-AGENT 에 과제 한 줄을 넘긴다 — 카드를 적고 첫 수(harness say --substrate pod --merge-by-host)를 고른다 · 기본 shadow(발사 0) · `--mission` 이면 조각 카드 ⊕ 선후로 쪼개 선행 없는 조각만 넘긴다')
256
+ .addOption(new Option('--seat <seat>', `과제 자리 (${TASK_SEATS.join('|')})`).choices([...TASK_SEATS]))
257
+ .option('--checklist <cellId>', '릴리스 체크리스트 칸 id')
258
+ .option('--mission <text>', '미션 문면 — 조각(`- …`·`① …`·`1. …` ⊕ `after: 1`)으로 나열돼 있으면 결정론, 아니면 분해기로 쪼갠다')
259
+ .option('--live', '실제로 발사한다(기본은 shadow — 명령만 출력)')
260
+ .option('--json', 'JSON 출력')
261
+ .action(async (text: string | undefined, opts: { seat?: TaskSeat; checklist?: string; mission?: string; live?: boolean; json?: boolean }) => {
262
+ await registerTaskAgentSink(deps.registerSink);
263
+ if ((text === undefined) === (opts.mission === undefined)) {
264
+ out('과제 한 줄 «또는» --mission <문면> 중 하나만 준다');
265
+ process.exitCode = 1;
266
+ return;
267
+ }
268
+ if (opts.mission !== undefined) {
269
+ try {
270
+ const result = await handMission({
271
+ text: opts.mission,
272
+ ...(opts.seat ? { seat: opts.seat } : {}),
273
+ ...(opts.checklist ? { checklistId: opts.checklist } : {}),
274
+ live: opts.live === true,
275
+ ...(deps.taskStatePath ? { statePath: deps.taskStatePath } : {}),
276
+ launcher: deps.taskLauncher ?? defaultTaskLauncher,
277
+ ...(deps.missionDecompose ? { decompose: deps.missionDecompose } : {}),
278
+ });
279
+ if (opts.json) out(JSON.stringify(result));
280
+ else {
281
+ out(`${result.mission.id}\tmission\t${result.mode}\t조각 ${result.pieces.length} · 간선 ${result.edges.length} · 출처 ${result.source}`);
282
+ for (const piece of result.pieces) {
283
+ out([piece.card.id, piece.launched ? 'launched' : piece.error ? 'failed' : 'not launched', `${piece.move.kind} (${piece.move.reason})`, headLine(piece.card.text)].join('\t'));
284
+ if (piece.move.command) out(` elanous ${piece.move.command.map(shellQuote).join(' ')}`);
285
+ if (piece.error) out(` ${piece.error}`);
286
+ }
287
+ for (const [before, after] of result.edges) out(`간선: ${before} -> ${after}`);
288
+ if (result.mode === 'shadow') out('(shadow — 띄우지 않았다 · 띄우려면 --live)');
289
+ }
290
+ if (result.pieces.some((piece) => piece.error)) process.exitCode = 1;
291
+ } catch (error) {
292
+ out(error instanceof Error ? error.message : '미션 넘기기 실패');
293
+ process.exitCode = 1;
294
+ }
295
+ return;
296
+ }
297
+ try {
298
+ const result = await handTask({
299
+ text: text!,
300
+ ...(opts.seat ? { seat: opts.seat } : {}),
301
+ ...(opts.checklist ? { checklistId: opts.checklist } : {}),
302
+ live: opts.live === true,
303
+ ...(deps.taskStatePath ? { statePath: deps.taskStatePath } : {}),
304
+ launcher: deps.taskLauncher ?? defaultTaskLauncher,
305
+ });
306
+ if (opts.json) out(JSON.stringify(result));
307
+ else {
308
+ out(`${result.card.id}\t${result.mode}\t${result.launched ? 'launched' : 'not launched'}`);
309
+ out(`elanous ${result.move.command!.map(shellQuote).join(' ')}`);
310
+ if (!result.launched) out('(shadow — 띄우지 않았다 · 띄우려면 --live)');
311
+ }
312
+ } catch (error) {
313
+ out(error instanceof Error ? error.message : '과제 넘기기 실패');
314
+ process.exitCode = 1;
315
+ }
316
+ });
317
+
318
+ tasks.command('advance [missionId]')
319
+ .description('TASK-AGENT 미션 ② — 조각의 PR 병합(근거: `--pr`/`--goal` 로 카드에 적은 PR 번호·골 id)으로 착지를 가르고 새로 준비된 조각만 한 번 넘긴다 · 모두 착지면 green «제안»만 적는다 · 기본 shadow(발사 0)')
320
+ .option('--all', 'green 제안이 아직 없는 미션 전부')
321
+ .option('--pr <pieceId=N>', '조각 카드에 그 런의 PR 번호를 적는다(반복 가능)', collectPair)
322
+ .option('--goal <pieceId=goalId>', '조각 카드에 골 id(16자)를 적는다 — 런 원장 pr-opened 로 PR 을 찾는다(반복 가능)', collectPair)
323
+ .option('--live', '준비된 조각을 실제로 발사한다(기본은 shadow)')
324
+ .option('--json', 'JSON 출력')
325
+ .action(async (missionId: string | undefined, opts: { all?: boolean; pr?: Array<[string, string]>; goal?: Array<[string, string]>; live?: boolean; json?: boolean }) => {
326
+ await registerTaskAgentSink(deps.registerSink);
327
+ const started = Date.now();
328
+ if ((missionId === undefined) === (opts.all !== true)) {
329
+ const reason = '미션 id «또는» --all 중 하나만 준다';
330
+ try { debug.log('task-agent', 'mission-advance', { missionId: missionId ?? null, all: opts.all === true, mode: opts.live ? 'live' : 'shadow', stage: 'cli-args', error: reason, ms: Date.now() - started }); } catch { /* fail-soft */ }
331
+ out(reason);
332
+ process.exitCode = 1;
333
+ return;
334
+ }
335
+ const statePath = deps.taskStatePath;
336
+ try {
337
+ // 근거 인자는 운영자 입력이다 — 모양을 «먼저» 전부 검사해 하나라도 틀리면 아무것도 적지 않고 진행도 안 한다.
338
+ const refs: Array<[string, { pr: number } | { goalId: string }]> = [
339
+ ...(opts.pr ?? []).map(([pieceId, value]): [string, { pr: number }] => {
340
+ if (!/^#?[1-9]\d*$/.test(value)) throw new Error(`PR 번호가 아니다: ${value}`);
341
+ return [pieceId, { pr: Number(value.replace(/^#/, '')) }];
342
+ }),
343
+ ...(opts.goal ?? []).map(([pieceId, value]): [string, { goalId: string }] => {
344
+ if (!/^[a-f0-9]{16}$/.test(value)) throw new Error(`골 id 는 16자 hex 다: ${value}`);
345
+ return [pieceId, { goalId: value }];
346
+ }),
347
+ ];
348
+ const twice = refs.map(([pieceId]) => pieceId).filter((pieceId, at, all) => all.indexOf(pieceId) !== at);
349
+ if (twice.length) throw new Error(`한 조각에 근거를 두 번 줬다(--pr·--goal 은 조각마다 하나): ${[...new Set(twice)].join(', ')}`);
350
+ // 기록 실패(없는 조각 · 제안된 미션)는 그 근거만 실패로 적고 계속 — `--all` 의 나머지 미션을 막지 않는다.
351
+ const refFailures: string[] = [];
352
+ for (const [pieceId, ref] of refs) {
353
+ try { recordPieceRef(pieceId, ref, statePath, missionId); } catch (error) {
354
+ const reason = error instanceof Error ? error.message : String(error);
355
+ if (missionId !== undefined) throw error;
356
+ refFailures.push(reason);
357
+ }
358
+ }
359
+ const ids = missionId !== undefined ? [missionId] : openMissionIds(statePath);
360
+ const results: AdvanceMissionResult[] = [];
361
+ const failures: Array<{ missionId: string; error: string }> = [];
362
+ for (const id of ids) {
363
+ try {
364
+ results.push(await advanceMission(id, {
365
+ live: opts.live === true,
366
+ ...(statePath ? { statePath } : {}),
367
+ launcher: deps.taskLauncher ?? defaultTaskLauncher,
368
+ ...(deps.pieceEvidence ? { readEvidence: deps.pieceEvidence } : {}),
369
+ }));
370
+ } catch (error) {
371
+ // 미션 id 하나면 그대로 실패 · --all 이면 그 미션만 실패로 적고 다음 미션으로 간다(크론이 한 미션에 막히지 않게).
372
+ if (missionId !== undefined) throw error;
373
+ failures.push({ missionId: id, error: error instanceof Error ? error.message : String(error) });
374
+ }
375
+ }
376
+ if (missionId === undefined) {
377
+ // --all 은 호출마다 요약 한 줄 — 대상 0·근거 기록 실패뿐인 호출도 관측에 남는다(미션별 줄은 advanceMission 이 남긴다).
378
+ try { debug.log('task-agent', 'mission-advance', { missionId: null, all: true, mode: opts.live ? 'live' : 'shadow', stage: 'all-summary', missions: ids.length, advanced: results.length, failures: failures.map((failure) => failure.missionId), refFailures: refFailures.length, ms: Date.now() - started }); } catch { /* fail-soft */ }
379
+ }
380
+ if (opts.json) out(JSON.stringify(missionId !== undefined ? results[0] : failures.length || refFailures.length ? { results, failures, refFailures } : results));
381
+ else {
382
+ if (results.length === 0 && failures.length === 0) out('넘길 미션이 없다 (green 제안 전 미션 0)');
383
+ for (const failure of failures) out(`${failure.missionId}\tadvance\tfailed\t${failure.error}`);
384
+ for (const reason of refFailures) out(`근거 기록 실패: ${reason}`);
385
+ for (const result of results) for (const line of advanceLines(result)) out(line);
386
+ if (!opts.live && results.some((result) => result.handed.length > 0)) out('(shadow — 띄우지 않았다 · 띄우려면 --live)');
387
+ }
388
+ if (failures.length || refFailures.length || results.some((result) => result.handed.some((piece) => piece.error))) process.exitCode = 1;
389
+ } catch (error) {
390
+ const reason = error instanceof Error ? error.message : '미션 진행 실패';
391
+ // advanceMission 에 닿기 전(근거 인자 검사·기록)에 멈춘 호출도 한 줄 — 관측이 «호출마다» 남는다.
392
+ // advanceMission 안에서 던진 것은 거기서 이미 남겼다(같은 오류를 두 번 적지 않는다).
393
+ if (!(error instanceof MissionAdvanceError)) {
394
+ try { debug.log('task-agent', 'mission-advance', { missionId: missionId ?? null, all: opts.all === true, mode: opts.live ? 'live' : 'shadow', stage: 'cli-args', error: reason, ms: Date.now() - started }); } catch { /* fail-soft */ }
395
+ }
396
+ out(reason);
397
+ process.exitCode = 1;
398
+ }
399
+ });
400
+
127
401
  tasks.command('show <id>')
128
- .description('태스크 상세 보기')
402
+ .description(`태스크 상세 보기 (id 가 \`${TASK_CARD_PREFIX}\` 로 시작하면 TASK-AGENT 과제 카드 ⊕ 다음 수)`)
129
403
  .option('--json', 'JSON 출력')
130
404
  .action(async (id: string, opts: { json?: boolean }) => {
405
+ if (id.startsWith(TASK_CARD_PREFIX)) {
406
+ await registerTaskAgentSink(deps.registerSink);
407
+ let card: TaskCard | undefined;
408
+ try { card = readTaskCard(id, deps.taskStatePath); } catch (error) {
409
+ out(error instanceof Error ? error.message : '과제 카드 읽기 실패');
410
+ process.exitCode = 1;
411
+ return;
412
+ }
413
+ if (!card) { out(`과제 카드 없음: ${id}`); process.exitCode = 1; return; }
414
+ if (card.pieces) {
415
+ let all: Record<string, TaskCard>;
416
+ try { all = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(deps.taskStatePath ?? taskStatePathDefault()).tasks ?? {}; } catch (error) {
417
+ out(error instanceof Error ? error.message : '과제 카드 읽기 실패');
418
+ process.exitCode = 1;
419
+ return;
420
+ }
421
+ const pieces = card.pieces.map((pieceId) => all[pieceId]).filter((piece): piece is TaskCard => piece !== undefined);
422
+ const missingPieces = card.pieces.filter((pieceId) => !all[pieceId]);
423
+ if (opts.json) {
424
+ out(JSON.stringify({ card, nextMove: nextMoveFor(card), pieces: pieces.map((piece) => ({ card: piece, nextMove: nextMoveFor(piece) })), missingPieces }));
425
+ if (missingPieces.length) process.exitCode = 1;
426
+ }
427
+ else {
428
+ for (const line of missionLines(card, pieces)) out(line);
429
+ for (const missing of missingPieces) { out(`조각 카드 없음: ${missing} — 선후 기록이 불완전하다`); process.exitCode = 1; }
430
+ }
431
+ return;
432
+ }
433
+ if (opts.json) out(JSON.stringify({ card, nextMove: nextMoveFor(card) }));
434
+ else for (const line of taskCardLines(card)) out(line);
435
+ return;
436
+ }
131
437
  try {
132
438
  activeToken = undefined;
133
439
  const auth = authenticate();