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
@@ -0,0 +1,115 @@
1
+ // RELEASE-BRANCH (10-06): resume a failed release run after `release cut-branch --append --pick` moved release/<v>.
2
+ // The saved version-release output is re-pointed at the new branch tip (fast-forward only, state backed up first), so
3
+ // every later node reads the repaired commit without anyone hand-editing the run state.
4
+ import { spawnSync } from 'node:child_process';
5
+ import { constants, copyFileSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { debug } from '../../src/debug/log.js';
8
+ import { effectiveInstanceRoot } from '../../src/instance/resolve.js';
9
+ import { lastJsonObject, runGraph, type GraphRunState } from '../../src/graph-runner/runner.js';
10
+ import { isReleaseVersion } from './release-version.js';
11
+
12
+ const GRAPH = join(import.meta.dir, '../../graphs/release/release-loop.yaml');
13
+
14
+ export interface ResumeReleaseDeps {
15
+ root?: string;
16
+ repo?: string;
17
+ /** Runs git and returns stdout (throws on failure). */
18
+ git?: (args: string[]) => string;
19
+ /** `git merge-base --is-ancestor a b`. */
20
+ isAncestor?: (ancestor: string, descendant: string) => boolean;
21
+ graph?: typeof runGraph;
22
+ /** The release graph (tests point it at a probe graph with the same graph id). */
23
+ graphPath?: string;
24
+ /** Node command runner for the resumed graph (tests record each node's context instead of running it). */
25
+ runBash?: NonNullable<NonNullable<Parameters<typeof runGraph>[1]>['deps']>['runBash'];
26
+ now?: () => Date;
27
+ }
28
+
29
+ export interface TipRefresh { version: string; branch: string; from: string; to: string; changed: boolean; statePath: string; backup?: string }
30
+
31
+ function gitRunner(repo: string) {
32
+ return (args: string[]): string => {
33
+ // git-spawn-allow: read-only fetch / ls-remote / rev-parse against release/<v>; nothing is pushed or checked out.
34
+ const result = spawnSync('git', args, { cwd: repo, encoding: 'utf8' });
35
+ if (result.status !== 0 || result.error) throw new Error(`git ${args[0]} failed: ${(result.stderr || result.error || 'no output').toString().trim()}`);
36
+ return result.stdout.trim();
37
+ };
38
+ }
39
+
40
+ function ancestorRunner(repo: string) {
41
+ return (ancestor: string, descendant: string): boolean => {
42
+ const result = spawnSync('git', ['merge-base', '--is-ancestor', ancestor, descendant], { cwd: repo });
43
+ if (result.status !== 0 && result.status !== 1) throw new Error(`git merge-base failed: ${result.error || result.stderr || 'unknown'}`);
44
+ return result.status === 0;
45
+ };
46
+ }
47
+
48
+ function safeRunId(runId: string): string {
49
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(runId) || runId === '.' || runId === '..') throw new Error(`invalid run id: ${runId}`);
50
+ return runId;
51
+ }
52
+
53
+ /** Point the saved version-release output at the current release/<v> tip (fast-forward of the recorded commit only). */
54
+ export function refreshReleaseBranchTip(runId: string, deps: ResumeReleaseDeps = {}, from?: string): TipRefresh {
55
+ const root = deps.root ?? effectiveInstanceRoot();
56
+ const statePath = join(root, 'graph-runs', 'release-loop', `${safeRunId(runId)}.json`);
57
+ const state = JSON.parse(readFileSync(statePath, 'utf8')) as GraphRunState;
58
+ if (state.graphId !== 'release-loop' || state.runId !== runId) throw new Error(`run identity mismatch: ${statePath}`);
59
+ if (state.status !== 'failed') throw new Error(`release run ${runId} is ${state.status} — only a failed run is resumed`);
60
+ const restartAt = from === undefined ? -1 : state.path.indexOf(from);
61
+ if (from !== undefined && (restartAt < 0 || state.path.lastIndexOf(from) !== restartAt)) throw new Error(`--from ${from} is not a node on the saved path of ${runId}`);
62
+ const index = state.nodes.findIndex((node) => node.nodeId === 'version-release');
63
+ const record = index >= 0 ? state.nodes[index]! : undefined;
64
+ const output = record ? lastJsonObject(record.output) : undefined;
65
+ if (!record?.ok || !output) throw new Error(`release run ${runId} has no successful version-release record`);
66
+ const version = typeof output.version === 'string' ? output.version : '';
67
+ const recorded = typeof output.commit === 'string' ? output.commit : '';
68
+ if (!isReleaseVersion(version) || !/^[0-9a-f]{40}$/i.test(recorded)) throw new Error(`release run ${runId} version-release output is not a release commit`);
69
+ const branch = `release/${version}`;
70
+ // Only a run that cut release/<v> itself is re-pointed — a main-cut or --cut-commit run has no branch of its own.
71
+ const input = (state.input ?? {}) as { branchCut?: unknown; version?: unknown };
72
+ if (input.branchCut !== true || input.version !== version || output.branch !== branch) {
73
+ throw new Error(`release run ${runId} was not cut as ${branch} (input.branchCut/version or version-release branch differ) — refusing to resume`);
74
+ }
75
+ const git = deps.git ?? gitRunner(deps.repo ?? process.cwd());
76
+ const advertised = git(['ls-remote', '--heads', 'origin', `refs/heads/${branch}`]).split(/\s+/)[0] ?? '';
77
+ if (!/^[0-9a-f]{40}$/i.test(advertised)) throw new Error(`${branch} is not on origin — this run was not cut as a release branch`);
78
+ git(['fetch', 'origin', `+refs/heads/${branch}:refs/remotes/origin/${branch}`]);
79
+ const to = git(['rev-parse', '--verify', `refs/remotes/origin/${branch}^{commit}`]);
80
+ if (to !== advertised) throw new Error(`${branch} moved while it was read (${advertised} → ${to})`);
81
+ if (to === recorded) {
82
+ debug.log('release-loop.resume', 'tip-unchanged', { runId, version, commit: to });
83
+ return { version, branch, from: recorded, to, changed: false, statePath };
84
+ }
85
+ // A repair only appends (`cut-branch --append`): anything but a fast-forward is a different release.
86
+ if (!(deps.isAncestor ?? ancestorRunner(deps.repo ?? process.cwd()))(recorded, to)) {
87
+ throw new Error(`${branch} tip ${to} does not descend from the recorded release commit ${recorded} — refusing to resume`);
88
+ }
89
+ // A new tip invalidates everything built from the old one (gate verdict, prepared archive, …): restart no later
90
+ // than the first node that consumed the commit, so each of them runs again on the new tip.
91
+ const firstConsumer = state.path.indexOf('gate');
92
+ if (restartAt >= 0 && firstConsumer >= 0 && restartAt > firstConsumer) {
93
+ throw new Error(`${branch} moved (${recorded.slice(0, 12)} → ${to.slice(0, 12)}): resume at gate or earlier — later nodes were built from the old tip`);
94
+ }
95
+ const stamp = (deps.now?.() ?? new Date()).toISOString().replace(/[:.]/g, '-');
96
+ const backup = `${statePath}.before-tip-${stamp}-${process.pid}-${to.slice(0, 12)}.bak`;
97
+ // Exclusive: a retry never overwrites an earlier backup.
98
+ copyFileSync(statePath, backup, constants.COPYFILE_EXCL);
99
+ const refreshed = { ...output, commit: to, refreshedFrom: recorded };
100
+ const text = typeof record.output === 'string' ? record.output.replace(/\s*$/, '') : '';
101
+ state.nodes[index] = { ...record, output: `${text}\n${JSON.stringify(refreshed)}\n` };
102
+ const temporary = `${statePath}.${process.pid}.tip.tmp`;
103
+ writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\n`);
104
+ renameSync(temporary, statePath);
105
+ debug.log('release-loop.resume', 'tip-refreshed', { runId, version, branch, from: recorded, to, backup });
106
+ return { version, branch, from: recorded, to, changed: true, statePath, backup };
107
+ }
108
+
109
+ /** Refresh the branch tip, then restart the failed run at `from` with the saved graph snapshot. */
110
+ export async function resumeReleaseRun(opts: { runId: string; from: string }, deps: ResumeReleaseDeps = {}): Promise<{ tip: TipRefresh; state: GraphRunState }> {
111
+ if (opts.from === 'version-release') throw new Error('--from version-release would cut again — resume at a later node');
112
+ const tip = refreshReleaseBranchTip(opts.runId, deps, opts.from);
113
+ const state = await (deps.graph ?? runGraph)(deps.graphPath ?? GRAPH, { resumeRunId: opts.runId, fromNodeId: opts.from, ...(deps.root || deps.runBash ? { deps: { ...(deps.root ? { root: deps.root } : {}), ...(deps.runBash ? { runBash: deps.runBash } : {}) } } : {}) });
114
+ return { tip, state };
115
+ }
@@ -1,12 +1,15 @@
1
+ import { spawnSync } from 'node:child_process';
1
2
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
3
  import { join } from 'node:path';
3
4
  import { getElanousConfigDirOverride } from '../../src/elanous-config-dir.js';
4
5
  import { effectiveInstanceRoot, releaseLedgerRoot } from '../../src/instance/resolve.js';
5
6
  import { userConfigPath } from '../../src/user-config.js';
6
- import { checklistGate } from '../../src/release-loop/checklist.js';
7
+ import { cutChecklistGate } from '../../src/release-loop/checklist.js';
8
+ import { getSchedule } from '../../src/release-loop/release-schedule.js';
7
9
  import { isLandingFreezeRefusal, readLandingFreeze, LandingFrozenError } from '../../src/release-loop/landing-freeze.js';
8
10
  import { debug } from '../../src/debug/log.js';
9
11
  import { runGraph, type GraphRunState } from '../../src/graph-runner/runner.js';
12
+ import { baseVersion, isStableVersion, nextPrereleaseVersion, type PrereleaseKind } from './release-version.js';
10
13
 
11
14
  const VERSION = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
12
15
  const GRAPH = join(import.meta.dir, '../../graphs/release/release-loop.yaml');
@@ -29,6 +32,8 @@ export interface ReleaseRunInput extends ReleaseLoopConfig {
29
32
  cutCommit?: string;
30
33
  gatePodPool: string;
31
34
  forceFreeze?: boolean;
35
+ /** RELEASE-BRANCH: version-release cuts release/<v> from main HEAD and bumps only the branch. */
36
+ branchCut?: boolean;
32
37
  }
33
38
 
34
39
  export interface UnattendedReleaseDeps {
@@ -36,8 +41,25 @@ export interface UnattendedReleaseDeps {
36
41
  config?: ReleaseLoopConfig;
37
42
  configPath?: string;
38
43
  graph?: (path: string, options: { input: ReleaseRunInput }) => Promise<GraphRunState>;
39
- checklist?: typeof checklistGate;
44
+ checklist?: (version: string) => Pick<ReturnType<typeof cutChecklistGate>, 'ok' | 'red' | 'undecided' | 'blocked'>;
40
45
  freezeRoot?: string;
46
+ /** Names already taken for prerelease numbering (remote release branches and tags). */
47
+ releaseRefs?: (base: string, kind: PrereleaseKind) => string[];
48
+ }
49
+
50
+ /** Remote `release/<base>-<kind>.*` branches and `v<base>-<kind>.*` tags — read-only. */
51
+ export function remoteReleaseRefs(base: string, kind: PrereleaseKind, repo = process.cwd()): string[] {
52
+ // git-spawn-allow: read-only ls-remote to number a prerelease.
53
+ const result = spawnSync('git', ['ls-remote', 'origin', `refs/heads/release/${base}-${kind}.*`, `refs/tags/v${base}-${kind}.*`], { cwd: repo, encoding: 'utf8' });
54
+ if (result.status !== 0 || result.error) throw new Error(`git ls-remote failed: ${(result.stderr || result.error || 'no output').toString().trim()}`);
55
+ return result.stdout.split('\n').map((line) => line.split(/\s+/)[1] ?? '').filter(Boolean);
56
+ }
57
+
58
+ /** `0.2.18` + `rc` → the next free `0.2.18-rc.<n>`; a stable run keeps its version. */
59
+ export function resolveRunVersion(version: string, prerelease: PrereleaseKind | undefined, deps: UnattendedReleaseDeps = {}): string {
60
+ if (!prerelease) return version;
61
+ if (!isStableVersion(version)) throw new Error(`--prerelease takes the base version x.y.z: ${version}`);
62
+ return nextPrereleaseVersion(version, prerelease, (deps.releaseRefs ?? remoteReleaseRefs)(version, prerelease));
41
63
  }
42
64
 
43
65
  function compareVersions(a: string, b: string): number {
@@ -76,7 +98,7 @@ export function buildReleaseRunInput(version: string, deps: UnattendedReleaseDep
76
98
  const config = deps.config ?? releaseLoopConfig(deps.configPath);
77
99
  const pool = config.gatePodPool;
78
100
  if (typeof pool !== 'string' || !pool.trim()) throw new Error('release.loop.gatePodPool is required before graph execution');
79
- const previousVersion = latestPublishedPreviousVersion(version, deps.ledgerRoot);
101
+ const previousVersion = latestPublishedPreviousVersion(baseVersion(version), deps.ledgerRoot);
80
102
  return {
81
103
  ...config,
82
104
  version, previousVersion,
@@ -87,9 +109,14 @@ export function buildReleaseRunInput(version: string, deps: UnattendedReleaseDep
87
109
 
88
110
  /** Fail closed at the entry boundary, before any graph node can change a release. */
89
111
  export async function runUnattendedRelease(
90
- opts: { version: string; dryRun?: boolean; cutCommit?: string; forceFreeze?: boolean },
112
+ opts: { version: string; dryRun?: boolean; cutCommit?: string; forceFreeze?: boolean; mainCut?: boolean; prerelease?: PrereleaseKind },
91
113
  deps: UnattendedReleaseDeps = {},
92
114
  ): Promise<{ input: ReleaseRunInput; dryRun: boolean; state?: GraphRunState }> {
115
+ // RELEASE-BRANCH (10-06): the default cut is a release branch, so main keeps landing and the run no longer needs a
116
+ // landing freeze. A freeze stays the emergency switch: when one is on, it still stops the run (`--force-freeze` overrides).
117
+ // `--cut-commit` (an already-cut branch) and `--main-cut` keep the old main-bump path.
118
+ const branchCut = opts.cutCommit === undefined && !opts.mainCut;
119
+ if (opts.prerelease && !branchCut) throw new Error('--prerelease needs the release-branch cut (no --cut-commit / --main-cut): a rehearsal must never bump main');
93
120
  if (!opts.dryRun) {
94
121
  const frozen = readLandingFreeze(deps.freezeRoot);
95
122
  if (frozen) {
@@ -97,11 +124,20 @@ export async function runUnattendedRelease(
97
124
  if (!opts.forceFreeze) throw new LandingFrozenError(frozen);
98
125
  }
99
126
  }
100
- const input = buildReleaseRunInput(opts.version, deps, opts.cutCommit);
127
+ const version = resolveRunVersion(opts.version, opts.prerelease, deps);
128
+ const input = buildReleaseRunInput(version, deps, opts.cutCommit);
101
129
  if (opts.forceFreeze) input.forceFreeze = true;
130
+ if (branchCut) input.branchCut = true;
131
+ debug.log('release.run', 'input', { version, requested: opts.version, branchCut, prerelease: opts.prerelease ?? null, dryRun: opts.dryRun === true });
102
132
  if (opts.dryRun) return { input, dryRun: true };
103
- const gate = (deps.checklist ?? checklistGate)(opts.version);
104
- if (!gate.ok) throw new Error(`release checklist blocked: ${[...gate.red, ...gate.undecided, ...gate.blocked].join(', ')}`);
133
+ if (opts.prerelease) {
134
+ // A rehearsal neither judges nor carries the stable version's checklist cells.
135
+ debug.log('release.run', 'checklist-skipped', { version, reason: 'prerelease' });
136
+ } else {
137
+ // Same judgement as the checklist-gate node: past the landing deadline non-P0 yellows carry; P0 yellows block.
138
+ const gate = (deps.checklist ?? ((v: string) => cutChecklistGate(v, getSchedule(v)?.landBy)))(version);
139
+ if (!gate.ok) throw new Error(`release checklist blocked: ${[...gate.red, ...gate.undecided, ...gate.blocked].join(', ')}`);
140
+ }
105
141
  const beforeGraph = readLandingFreeze(deps.freezeRoot);
106
142
  if (beforeGraph && !opts.forceFreeze) throw new LandingFrozenError(beforeGraph);
107
143
  const state = await (deps.graph ?? runGraph)(GRAPH, { input });
@@ -5,12 +5,17 @@ import { tmpdir } from 'node:os';
5
5
  import { join, resolve } from 'node:path';
6
6
  import { emitNodeResult } from './node-verdict.js';
7
7
  import { debug } from '../../src/debug/log.js';
8
+ import { effectiveInstanceRoot, prodInstanceRoot } from '../../src/instance/resolve.js';
9
+ import { landingFreezeMessage, readLandingFreeze, type LandingFreeze } from '../../src/release-loop/landing-freeze.js';
10
+ import { baseVersion, isReleaseVersion, isStableVersion, prereleaseKind } from './release-version.js';
8
11
 
9
12
  type Kind = 'release' | 'dev-bump';
10
- type Output = { outcome: 'ok' | 'error'; kind: Kind | null; version: string | null; commit: string | null; pr: number | null; files?: string[]; worktree?: string; error?: string };
13
+ type Output = { outcome: 'ok' | 'error'; kind: Kind | null; version: string | null; commit: string | null; pr: number | null; files?: string[]; worktree?: string; error?: string;
14
+ /** RELEASE-BRANCH: the release branch the cut lives on and the main commit it was cut from. */
15
+ branch?: string; base?: string; existing?: boolean; skipped?: string };
11
16
 
12
17
  export function nextDevVersion(version: string): string {
13
- const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(version);
18
+ const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(isReleaseVersion(version) ? baseVersion(version) : version);
14
19
  if (!match) throw new Error(`invalid release version: ${version}`);
15
20
  return `${match[1]}.${match[2]}.${BigInt(match[3]!) + 1n}-dev.0`;
16
21
  }
@@ -64,9 +69,136 @@ function shippedNextMdLines(repo: string, cut: string | undefined): { lines: Set
64
69
  return { lines: new Set(shown.stdout.split('\n').map((line) => line.trim()).filter((line) => line.startsWith('- '))) };
65
70
  }
66
71
 
67
- function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string, cutCommit?: string, forceFreeze = false): Output {
72
+ /** Rewrite the version in package.json, server.json, the ACP agent card and the bun.lock root; returns the files touched. */
73
+ function writeVersionBump(worktree: string, current: string, target: string): string[] {
74
+ const path = join(worktree, 'package.json');
75
+ const text = readFileSync(path, 'utf8');
76
+ const old = `"version": "${current}"`;
77
+ if (!text.includes(old)) throw new Error(`package.json version field not found: ${current}`);
78
+ writeFileSync(path, text.replace(old, `"version": "${target}"`));
79
+ const files = ['package.json'];
80
+ const serverPath = join(worktree, 'server.json');
81
+ if (existsSync(serverPath)) {
82
+ const server = JSON.parse(readFileSync(serverPath, 'utf8')) as { version: string; packages: Array<{ version: string }> };
83
+ server.version = target;
84
+ server.packages[0]!.version = target;
85
+ writeFileSync(serverPath, `${JSON.stringify(server, null, 2)}\n`);
86
+ files.push('server.json');
87
+ }
88
+ const agentPath = join(worktree, 'integrations', 'acp-registry', 'elanous', 'agent.json');
89
+ if (existsSync(agentPath)) {
90
+ const agent = JSON.parse(readFileSync(agentPath, 'utf8')) as { version: string; distribution: { npx: { package: string } } };
91
+ agent.version = target;
92
+ agent.distribution.npx.package = `elanous@${target}`;
93
+ writeFileSync(agentPath, `${JSON.stringify(agent, null, 2)}\n`);
94
+ files.push('agent.json');
95
+ }
96
+ const lockPath = join(worktree, 'bun.lock');
97
+ const lock = readFileSync(lockPath, 'utf8');
98
+ const root = /"workspaces"\s*:\s*\{\s*""\s*:\s*\{([\s\S]*?)(?=\s*"(?:dependencies|devDependencies|optionalDependencies)"\s*:)/.exec(lock);
99
+ if (!root) throw new Error('bun.lock root workspace not found');
100
+ const lockedVersion = /"version"\s*:\s*"([^"]+)"/.exec(root[1]!);
101
+ if (lockedVersion) {
102
+ if (lockedVersion[1] !== current) throw new Error(`bun.lock root version ${lockedVersion[1]} is not ${current}`);
103
+ writeFileSync(lockPath, lock.replace(root[0], root[0].replace(lockedVersion[0], `"version": "${target}"`)));
104
+ }
105
+ return files;
106
+ }
107
+
108
+ export interface BranchCutDeps {
109
+ /** Runs `bun install --frozen-lockfile` in the bump worktree (the lock must accept the new version). */
110
+ install?: (worktree: string) => void;
111
+ /** The landing freeze in force (production root first, like a merge). */
112
+ freeze?: () => LandingFreeze | null;
113
+ /** `--force-freeze`: cut while a freeze is on (recorded). */
114
+ forceFreeze?: boolean;
115
+ }
116
+
117
+ function currentFreeze(): LandingFreeze | null {
118
+ const prod = prodInstanceRoot();
119
+ const root = effectiveInstanceRoot();
120
+ return readLandingFreeze(prod) ?? (root === prod ? null : readLandingFreeze(root));
121
+ }
122
+
123
+ /**
124
+ * RELEASE-BRANCH (대표 10-06 20:0x «0.2.18부터 브랜치 방식»): the release commit lives on `release/<v>`, never on main.
125
+ * An existing branch is reused as is (a resumed run, or a branch a repair appended to) — its tip is the release commit.
126
+ * Otherwise the branch is cut from origin/main and the version bump is committed on it; main keeps its -dev.N version
127
+ * until the post-publish dev bump.
128
+ */
129
+ export function cutReleaseBranchForRun(repo: string, version: string, deps: BranchCutDeps = {}): Output {
130
+ if (!isReleaseVersion(version)) throw new Error(`invalid release version: ${version}`);
131
+ // The run does not need a freeze, but one that is on is the emergency stop: neither a new nor a reused cut passes it.
132
+ const frozen = (deps.freeze ?? currentFreeze)();
133
+ if (frozen) {
134
+ debug.log('release.run', deps.forceFreeze ? 'freeze-forced' : 'frozen', { version, reason: frozen.reason, until: frozen.until, node: 'version-release' });
135
+ if (!deps.forceFreeze) throw new Error(landingFreezeMessage(frozen));
136
+ }
137
+ fetchMain(repo);
138
+ const branch = `release/${version}`;
139
+ const advertised = git(repo, 'ls-remote', '--heads', 'origin', `refs/heads/${branch}`).split(/\s+/)[0] ?? '';
140
+ if (advertised) {
141
+ // A prerelease number is used once: an existing rc branch is another run's cut, never this run's.
142
+ if (prereleaseKind(version) !== null) throw new Error(`${branch} already exists — ${version} was cut by another run; run again for the next free number`);
143
+ if (!/^[0-9a-f]{40}$/i.test(advertised)) throw new Error(`${branch} remote tip is not a commit SHA`);
144
+ git(repo, 'fetch', 'origin', `+refs/heads/${branch}:refs/remotes/origin/${branch}`);
145
+ const tip = git(repo, 'rev-parse', '--verify', `refs/remotes/origin/${branch}^{commit}`);
146
+ if (tip !== advertised) throw new Error(`${branch} moved while it was read (${advertised} → ${tip})`);
147
+ const current = versionAt(repo, tip);
148
+ if (current !== version) throw new Error(`${branch} tip ${tip} package.json version ${current} is not ${version}`);
149
+ // The branch must be a cut of this release line: its fork point on main still carries <base>-dev.N (or <v>).
150
+ const fork = git(repo, 'merge-base', 'origin/main', tip);
151
+ const forkVersion = versionAt(repo, fork);
152
+ const stableLine = baseVersion(version);
153
+ if (forkVersion !== version && !new RegExp(`^${stableLine.replaceAll('.', '\\.')}-dev\\.(0|[1-9][0-9]*)$`).test(forkVersion)) {
154
+ throw new Error(`${branch} forks from main at ${fork} (version ${forkVersion}) — not a cut of ${stableLine}-dev.N`);
155
+ }
156
+ debug.log('release-loop.cut', 'chosen', { version, source: 'release-branch', commit: tip, branch, existing: true });
157
+ return { outcome: 'ok', kind: 'release', version, commit: tip, pr: null, branch, existing: true };
158
+ }
159
+ const base = git(repo, 'rev-parse', 'origin/main');
160
+ const current = versionAt(repo, base);
161
+ const stable = baseVersion(version);
162
+ const devOfStable = new RegExp(`^${stable.replaceAll('.', '\\.')}-dev\\.(0|[1-9][0-9]*)$`);
163
+ if (current !== version && !devOfStable.test(current)) throw new Error(`origin/main version ${current} is not ${stable}-dev.N (release ${version})`);
164
+ const temp = mkdtempSync(join(tmpdir(), 'release-branch-cut-'));
165
+ const worktree = join(temp, 'tree');
166
+ let added = false;
167
+ try {
168
+ git(repo, 'worktree', 'add', '--detach', worktree, base);
169
+ added = true;
170
+ const files = current === version ? [] : writeVersionBump(worktree, current, version);
171
+ if (files.length) {
172
+ (deps.install ?? ((tree: string) => { run('bun', ['install', '--frozen-lockfile'], tree); }))(worktree);
173
+ git(worktree, 'add', '-A');
174
+ git(worktree, 'commit', '-m', `release: ${version}`, '-m', `Cut from main ${base}.`);
175
+ }
176
+ const commit = git(worktree, 'rev-parse', 'HEAD');
177
+ // Create-only: an empty lease refuses to overwrite a branch someone else made meanwhile.
178
+ const push = git(repo, 'push', '--porcelain', `--force-with-lease=refs/heads/${branch}:`, 'origin', `${commit}:refs/heads/${branch}`);
179
+ const created = push.split('\n').some((line) => {
180
+ const [flag, ref, summary] = line.split('\t');
181
+ return flag === '*' && !!ref?.endsWith(`:refs/heads/${branch}`) && summary === '[new branch]';
182
+ });
183
+ if (!created) throw new Error(`${branch} was not created (push: ${push.replace(/\s+/g, ' ').trim() || 'no output'})`);
184
+ debug.log('release-loop.cut', 'chosen', { version, source: 'release-branch', commit, branch, base, existing: false });
185
+ return { outcome: 'ok', kind: 'release', version, commit, pr: null, branch, base, files };
186
+ } finally {
187
+ try { if (added) git(repo, 'worktree', 'remove', '--force', worktree); }
188
+ finally { rmSync(temp, { recursive: true, force: true }); }
189
+ }
190
+ }
191
+
192
+ function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string, cutCommit?: string, forceFreeze = false, branchCut = false): Output {
193
+ if (kind === 'release' && branchCut && cutCommit === undefined) return cutReleaseBranchForRun(repo, releaseVersion, { forceFreeze });
194
+ if (kind === 'dev-bump' && prereleaseKind(releaseVersion) !== null) {
195
+ // A prerelease (rehearsal) never moves main: the next stable release still owns this -dev line.
196
+ debug.log('release-loop.version-dev-bump', 'skipped', { version: releaseVersion, reason: 'prerelease' });
197
+ return { outcome: 'ok', kind, version: releaseVersion, commit: null, pr: null, skipped: 'prerelease' };
198
+ }
68
199
  const target = kind === 'release' ? releaseVersion : nextDevVersion(releaseVersion);
69
- const source = kind === 'release' ? `${releaseVersion}-dev.N` : releaseVersion;
200
+ // With the release on its branch, main still carries <v>-dev.N when the dev bump runs.
201
+ const source = kind === 'release' ? `${releaseVersion}-dev.N` : branchCut ? `${releaseVersion}-dev.N` : releaseVersion;
70
202
  fetchMain(repo);
71
203
  if (kind === 'release' && cutCommit !== undefined) {
72
204
  if (!/^[0-9a-f]{7,40}$/i.test(cutCommit)) throw new Error(`invalid cut commit SHA: ${cutCommit}`);
@@ -107,7 +239,8 @@ function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string,
107
239
  return { outcome: 'ok', kind, version: current, commit: before, pr: null };
108
240
  }
109
241
  }
110
- if (kind === 'release' ? !new RegExp(`^${releaseVersion.replaceAll('.', '\\.')}\\-dev\\.(0|[1-9][0-9]*)$`).test(current) : current !== source) {
242
+ const devOfRelease = new RegExp(`^${releaseVersion.replaceAll('.', '\\.')}\\-dev\\.(0|[1-9][0-9]*)$`);
243
+ if (kind === 'release' || branchCut ? !devOfRelease.test(current) && !(kind === 'dev-bump' && current === releaseVersion) : current !== source) {
111
244
  throw new Error(`origin/main version ${current} is not ${source} (target ${target})`);
112
245
  }
113
246
 
@@ -115,37 +248,7 @@ function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string,
115
248
  const worktree = join(temp, 'tree');
116
249
  try {
117
250
  git(repo, 'worktree', 'add', '-b', `release-version-${kind}-${Date.now()}-${process.pid}`, worktree, 'origin/main');
118
- const path = join(worktree, 'package.json');
119
- const text = readFileSync(path, 'utf8');
120
- const old = `"version": "${current}"`;
121
- if (!text.includes(old)) throw new Error(`package.json version field not found: ${current}`);
122
- writeFileSync(path, text.replace(old, `"version": "${target}"`));
123
- const files = ['package.json'];
124
- const serverPath = join(worktree, 'server.json');
125
- if (existsSync(serverPath)) {
126
- const server = JSON.parse(readFileSync(serverPath, 'utf8')) as { version: string; packages: Array<{ version: string }> };
127
- server.version = target;
128
- server.packages[0]!.version = target;
129
- writeFileSync(serverPath, `${JSON.stringify(server, null, 2)}\n`);
130
- files.push('server.json');
131
- }
132
- const agentPath = join(worktree, 'integrations', 'acp-registry', 'elanous', 'agent.json');
133
- if (existsSync(agentPath)) {
134
- const agent = JSON.parse(readFileSync(agentPath, 'utf8')) as { version: string; distribution: { npx: { package: string } } };
135
- agent.version = target;
136
- agent.distribution.npx.package = `elanous@${target}`;
137
- writeFileSync(agentPath, `${JSON.stringify(agent, null, 2)}\n`);
138
- files.push('agent.json');
139
- }
140
- const lockPath = join(worktree, 'bun.lock');
141
- const lock = readFileSync(lockPath, 'utf8');
142
- const root = /"workspaces"\s*:\s*\{\s*""\s*:\s*\{([\s\S]*?)(?=\s*"(?:dependencies|devDependencies|optionalDependencies)"\s*:)/.exec(lock);
143
- if (!root) throw new Error('bun.lock root workspace not found');
144
- const lockedVersion = /"version"\s*:\s*"([^"]+)"/.exec(root[1]!);
145
- if (lockedVersion) {
146
- if (lockedVersion[1] !== current) throw new Error(`bun.lock root version ${lockedVersion[1]} is not ${current}`);
147
- writeFileSync(lockPath, lock.replace(root[0], root[0].replace(lockedVersion[0], `"version": "${target}"`)));
148
- }
251
+ const files = writeVersionBump(worktree, current, target);
149
252
  run('bun', ['install', '--frozen-lockfile'], worktree);
150
253
  if (kind === 'dev-bump') {
151
254
  const nextPath = join(worktree, 'release', 'next.md');
@@ -207,12 +310,13 @@ function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string,
207
310
  }
208
311
  }
209
312
 
210
- function main(args: string[] = process.argv.slice(2), repo = process.cwd()): Output {
313
+ export function main(args: string[] = process.argv.slice(2), repo = process.cwd()): Output {
211
314
  let kind: Kind | null = null;
212
315
  let version: string | null = null;
213
316
  let cut: string | undefined;
214
317
  let cutCommit: string | undefined;
215
318
  let forceFreeze = false;
319
+ let branchCut = false;
216
320
  try {
217
321
  if (args[0] !== 'release' && args[0] !== 'dev-bump') throw new Error('usage: version-node.ts <release|dev-bump> [--version <v>] [--cut <sha>] [--cut-commit <sha>] --json');
218
322
  kind = args[0];
@@ -226,9 +330,10 @@ function main(args: string[] = process.argv.slice(2), repo = process.cwd()): Out
226
330
  if (process.env.ELANOUS_GRAPH_CONTEXT) {
227
331
  const location = process.env.ELANOUS_GRAPH_CONTEXT;
228
332
  const context = JSON.parse(location.trimStart().startsWith('{') ? location : readFileSync(location, 'utf8')) as {
229
- input?: { version?: unknown; cutCommit?: unknown; forceFreeze?: unknown }; outputs?: Record<string, { commit?: unknown } | undefined> };
333
+ input?: { version?: unknown; cutCommit?: unknown; forceFreeze?: unknown; branchCut?: unknown }; outputs?: Record<string, { commit?: unknown } | undefined> };
230
334
  if (!version && typeof context.input?.version === 'string') version = context.input.version;
231
335
  forceFreeze = context.input?.forceFreeze === true;
336
+ branchCut = context.input?.branchCut === true;
232
337
  if (kind === 'release' && cutCommit === undefined && context.input?.cutCommit !== undefined) {
233
338
  if (typeof context.input.cutCommit !== 'string') throw new Error('cutCommit must be a commit SHA');
234
339
  cutCommit = context.input.cutCommit;
@@ -240,11 +345,11 @@ function main(args: string[] = process.argv.slice(2), repo = process.cwd()): Out
240
345
  if (!version) throw new Error('version required (--version or ELANOUS_GRAPH_CONTEXT.input.version)');
241
346
  nextDevVersion(version);
242
347
  if (kind === 'dev-bump' && cutCommit !== undefined) throw new Error('--cut-commit is only valid for release');
243
- return landing(resolve(repo), kind, version, cut, cutCommit, forceFreeze);
348
+ return landing(resolve(repo), kind, version, cut, cutCommit, forceFreeze, branchCut);
244
349
  } catch (error) {
245
350
  const message = error instanceof Error ? error.message : String(error);
246
351
  const worktree = /\[worktree: ([^\]]+)\]$/.exec(message)?.[1];
247
- const outputVersion = kind === 'dev-bump' && version && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(version)
352
+ const outputVersion = kind === 'dev-bump' && version && isStableVersion(version)
248
353
  ? nextDevVersion(version) : version;
249
354
  return { outcome: 'error', kind, version: outputVersion,
250
355
  commit: null, pr: null, ...(worktree ? { worktree } : {}), error: message };
@@ -301,8 +301,9 @@ function recipient(deps: DailyDeps): { chatId: number; botToken: string } | null
301
301
  function sendReview(file: string, text: string, target: { chatId: number; botToken: string },
302
302
  send: (request: DailySendRequest) => DailySendConfirmation | null,
303
303
  receipt: (key: string) => DeliveryReceipt,
304
- recordDelivery?: (text: string, kind: 'ops-report') => void): DeliveryResult {
305
- const key = file.slice(file.lastIndexOf('/') + 1).replace(/\.md$/, '');
304
+ recordDelivery?: (text: string, kind: 'ops-report') => void,
305
+ keySuffix = ''): DeliveryResult {
306
+ const key = file.slice(file.lastIndexOf('/') + 1).replace(/\.md$/, '') + keySuffix;
306
307
  const label = `telegram:${target.chatId}`;
307
308
  const db = new Database(join(dirname(file), 'delivery.sqlite'));
308
309
  try {
@@ -346,6 +347,21 @@ function sendReview(file: string, text: string, target: { chatId: number; botTok
346
347
  finally { db.close(); }
347
348
  }
348
349
 
350
+ function newsMessage(markdown: string): string | null {
351
+ const section = markdown.split('## ⑦ 외부 동향\n')[1]?.split('\n\n## ')[0];
352
+ if (!section || section.startsWith('못 읽음 ·')) return null;
353
+ const articles = [...section.matchAll(/^- (.+) — (https?:\/\/\S+)\n 시사점: (.*)$/gm)].slice(0, 3);
354
+ return articles.length ? `## 외부 동향\n${articles.map(article => article[0]).join('\n')}` : null;
355
+ }
356
+
357
+ function sendNewsAfterReview(delivery: DeliveryResult, file: string, markdown: string,
358
+ target: { chatId: number; botToken: string }, deps: DailyDeps): string | null {
359
+ if (!['sent', 'already-sent', 'receipt-sent'].includes(delivery.deliveryState)) return null;
360
+ const text = newsMessage(markdown);
361
+ if (!text) return null;
362
+ return sendReview(file, text, target, deps.send ?? sendTelegram, deps.receipt ?? (() => 'unknown'), deps.recordDelivery, '-news').deliveryState;
363
+ }
364
+
349
365
  export type DailyDeps = { now?: () => Date; root?: string; vaultRoot?: string | null; sendEnabled?: boolean; repoRoot?: string; repoName?: string;
350
366
  landings?: (now: Date) => Promise<Landing[]>; release?: () => Promise<Release>; loops?: () => Promise<Loop[]>;
351
367
  grid?: () => Promise<Seat[]>; decisions?: () => Promise<Pending[]>; news?: () => Promise<News[]>;
@@ -402,6 +418,7 @@ export async function runDaily(options: { dryRun?: boolean; noNews?: boolean; st
402
418
  let target = 'unknown';
403
419
  let sendError: string | null = null;
404
420
  let recordError: string | null = null;
421
+ let newsState: string | null = null;
405
422
  let enabled = deps.sendEnabled === true;
406
423
  if (deps.sendEnabled === undefined && options.stage === 'deliver' && !options.dryRun) {
407
424
  try { enabled = record(record(record(getUserConfig().raw.loops).rhythm).daily).send === true; }
@@ -420,12 +437,13 @@ export async function runDaily(options: { dryRun?: boolean; noNews?: boolean; st
420
437
  sendError = sendError ?? delivery.sendError;
421
438
  recordError = delivery.recordError ?? null;
422
439
  deliveryState = delivery.deliveryState;
440
+ if (resolved) newsState = sendNewsAfterReview(delivery, file, markdown, resolved, deps);
423
441
  }
424
442
  tick.sent = sent;
425
443
  const status = vault.vaultFatal || Object.values(sections).includes('unreadable') ? 'degraded'
426
444
  : deliveryState === 'pending' ? 'pending'
427
445
  : ['sent', 'already-sent', 'receipt-sent'].includes(deliveryState) ? 'ok' : 'degraded';
428
- const deliveryLine = `${options.stage} ${status} · target=${target} · ${deliveryState} · sent=${sent} · chars=${chars} · receipt=${receiptKey}${recordError ? ` · ${recordError}` : ''}`;
446
+ const deliveryLine = `${options.stage} ${status} · target=${target} · ${deliveryState} · sent=${sent} · chars=${chars} · receipt=${receiptKey}${recordError ? ` · ${recordError}` : ''}${newsState ? ` · news=${newsState}` : ''}`;
429
447
  if (options.stage === 'deliver') {
430
448
  Object.assign(tick, { status, deliveryState, target, chars, receiptKey, deliveryLine, recordError });
431
449
  if (!options.dryRun) {
@@ -526,6 +544,7 @@ export async function runDaily(options: { dryRun?: boolean; noNews?: boolean; st
526
544
  }
527
545
  let sent = false;
528
546
  let recordError: string | null = null;
547
+ let newsState: string | null = null;
529
548
  let chars = 0;
530
549
  let target = 'unknown';
531
550
  if (!options.dryRun && options.stage !== 'collect' && enabled) {
@@ -541,11 +560,13 @@ export async function runDaily(options: { dryRun?: boolean; noNews?: boolean; st
541
560
  sendError = sendError ?? delivery.sendError;
542
561
  recordError = delivery.recordError ?? null;
543
562
  deliveryState = delivery.deliveryState;
563
+ if (resolved && parts.news.status === 'ok' && parts.news.value.length > 0)
564
+ newsState = sendNewsAfterReview(delivery, file, review.markdown, resolved, deps);
544
565
  }
545
566
  const status = vaultFatal || Object.values(review.sections).includes('unreadable') ? 'degraded'
546
567
  : deliveryState === 'pending' ? 'pending'
547
568
  : ['sent', 'already-sent', 'receipt-sent'].includes(deliveryState) ? 'ok' : 'degraded';
548
- const deliveryLine = `${options.stage === 'collect' ? 'collect' : 'deliver'} ${status} · target=${target} · ${deliveryState} · sent=${sent} · chars=${chars} · receipt=${receiptKey}${recordError ? ` · ${recordError}` : ''}`;
569
+ const deliveryLine = `${options.stage === 'collect' ? 'collect' : 'deliver'} ${status} · target=${target} · ${deliveryState} · sent=${sent} · chars=${chars} · receipt=${receiptKey}${recordError ? ` · ${recordError}` : ''}${newsState ? ` · news=${newsState}` : ''}`;
549
570
  if (options.stage !== 'collect') {
550
571
  const tick = { sections: review.sections, risks: review.risks.length, sent, status, deliveryState, target, chars, receiptKey, deliveryLine, recordError };
551
572
  if (!options.dryRun) {