elanous 0.2.8 → 0.2.10

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 (385) 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/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
  14. package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-d4b6df905f185055.js → page-54b32bd27726588d.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/chat/{page-186fc6952f027acc.js → page-f8116559c7424da7.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-cbbe03c0da7b081a.js → page-54e1f966ab60a4dc.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/market/{page-c0c0d19d51342d3e.js → page-b31610deda76337b.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-2e002ad8faa43291.js → page-6cb0e1bbefe5fb3d.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-7464c1987dd76e6d.js → page-e168fa1030af0548.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-d8ab4d1489223459.js → page-4fddc90b23feb215.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-26102fd7350f171e.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-139ba5c367bbf60a.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
  61. package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
  62. package/apps/pwa/out/approvals/index.html +1 -1
  63. package/apps/pwa/out/approvals/index.txt +30 -29
  64. package/apps/pwa/out/autopilot/index.html +1 -1
  65. package/apps/pwa/out/autopilot/index.txt +30 -29
  66. package/apps/pwa/out/board/index.html +1 -1
  67. package/apps/pwa/out/board/index.txt +30 -29
  68. package/apps/pwa/out/botlab/index.html +1 -1
  69. package/apps/pwa/out/botlab/index.txt +27 -26
  70. package/apps/pwa/out/bots/index.html +1 -1
  71. package/apps/pwa/out/bots/index.txt +27 -26
  72. package/apps/pwa/out/chat/index.html +1 -1
  73. package/apps/pwa/out/chat/index.txt +27 -26
  74. package/apps/pwa/out/consult/index.html +1 -1
  75. package/apps/pwa/out/consult/index.txt +30 -29
  76. package/apps/pwa/out/control/index.html +1 -1
  77. package/apps/pwa/out/control/index.txt +30 -29
  78. package/apps/pwa/out/design-check/index.html +1 -1
  79. package/apps/pwa/out/design-check/index.txt +30 -29
  80. package/apps/pwa/out/editor/index.html +1 -1
  81. package/apps/pwa/out/editor/index.txt +30 -29
  82. package/apps/pwa/out/exec/index.html +1 -1
  83. package/apps/pwa/out/exec/index.txt +30 -29
  84. package/apps/pwa/out/field/index.html +1 -1
  85. package/apps/pwa/out/field/index.txt +30 -29
  86. package/apps/pwa/out/healthz +1 -1
  87. package/apps/pwa/out/index.html +1 -1
  88. package/apps/pwa/out/index.txt +27 -26
  89. package/apps/pwa/out/intake/index.html +1 -1
  90. package/apps/pwa/out/intake/index.txt +30 -29
  91. package/apps/pwa/out/live/index.html +1 -1
  92. package/apps/pwa/out/live/index.txt +30 -29
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +27 -26
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +27 -26
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +30 -29
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +30 -29
  101. package/apps/pwa/out/ops/checklist/index.html +1 -0
  102. package/apps/pwa/out/ops/checklist/index.txt +30 -0
  103. package/apps/pwa/out/ops/release/index.html +1 -0
  104. package/apps/pwa/out/ops/release/index.txt +30 -0
  105. package/apps/pwa/out/ops/seats/index.html +1 -0
  106. package/apps/pwa/out/ops/seats/index.txt +30 -0
  107. package/apps/pwa/out/reflection/index.html +1 -1
  108. package/apps/pwa/out/reflection/index.txt +28 -27
  109. package/apps/pwa/out/scheduler/index.html +1 -1
  110. package/apps/pwa/out/scheduler/index.txt +30 -29
  111. package/apps/pwa/out/sessions/index.html +1 -1
  112. package/apps/pwa/out/sessions/index.txt +27 -26
  113. package/apps/pwa/out/settings/devices/index.html +1 -1
  114. package/apps/pwa/out/settings/devices/index.txt +30 -29
  115. package/apps/pwa/out/settings/index.html +1 -1
  116. package/apps/pwa/out/settings/index.txt +30 -29
  117. package/apps/pwa/out/setup/done/index.html +1 -1
  118. package/apps/pwa/out/setup/done/index.txt +30 -29
  119. package/apps/pwa/out/setup/index.html +1 -1
  120. package/apps/pwa/out/setup/index.txt +30 -29
  121. package/apps/pwa/out/share/index.html +1 -1
  122. package/apps/pwa/out/share/index.txt +30 -29
  123. package/apps/pwa/out/showroom/index.html +1 -1
  124. package/apps/pwa/out/showroom/index.txt +27 -26
  125. package/apps/pwa/out/tasks/index.html +1 -1
  126. package/apps/pwa/out/tasks/index.txt +30 -29
  127. package/apps/pwa/out/term/index.html +1 -1
  128. package/apps/pwa/out/term/index.txt +30 -29
  129. package/apps/pwa/out/today/index.html +1 -0
  130. package/apps/pwa/out/today/index.txt +33 -0
  131. package/apps/pwa/out/trace/index.html +1 -1
  132. package/apps/pwa/out/trace/index.txt +30 -29
  133. package/apps/pwa/out/vault/index.html +1 -1
  134. package/apps/pwa/out/vault/index.txt +30 -29
  135. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  136. package/apps/pwa/out/workflows/chat-ui/index.txt +30 -29
  137. package/apps/pwa/out/workflows/index.html +1 -1
  138. package/apps/pwa/out/workflows/index.txt +30 -29
  139. package/apps/pwa/out/workspace/index.html +1 -1
  140. package/apps/pwa/out/workspace/index.txt +30 -29
  141. package/apps/pwa/out/worktrees/index.html +1 -1
  142. package/apps/pwa/out/worktrees/index.txt +30 -29
  143. package/graphs/field/field-feed.yaml +2 -0
  144. package/graphs/implement-loop.yaml +1 -1
  145. package/graphs/release/recipes.yaml +3 -0
  146. package/graphs/release/release-loop.yaml +3 -1
  147. package/graphs/test-diet/nightly-audit.yaml +18 -0
  148. package/graphs/test-diet/recipes.yaml +3 -0
  149. package/package.json +1 -1
  150. package/plugins/card-followup/graphs/run-step.ts +48 -13
  151. package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
  152. package/plugins/doc-draft/graphs/run-step.ts +14 -7
  153. package/scripts/ci-pwa-build-gate.ts +160 -0
  154. package/scripts/coord-channel-watch.sh +42 -8
  155. package/scripts/coord-digest.ts +199 -0
  156. package/scripts/coord-post.sh +60 -3
  157. package/scripts/install.ps1 +68 -9
  158. package/scripts/install.sh +39 -12
  159. package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
  160. package/scripts/lib/tui-regress-checks.ts +146 -0
  161. package/scripts/memory-lifecycle-cycle.ts +2 -2
  162. package/scripts/ops-now-probe.ts +327 -0
  163. package/scripts/publish-internal-dist.ts +11 -1
  164. package/scripts/release-loop/cutoff.ts +31 -2
  165. package/scripts/release-loop/export-check-node.ts +85 -0
  166. package/scripts/release-loop/gate-node.ts +18 -4
  167. package/scripts/release-loop/node-verdict.ts +8 -1
  168. package/scripts/release-loop/ops-upgrade-node.ts +72 -16
  169. package/scripts/release-loop/release-readiness.ts +9 -0
  170. package/scripts/release-loop/version-node.ts +58 -10
  171. package/scripts/repo-watch-cycle.ts +1 -1
  172. package/scripts/retro-cycle.ts +2 -2
  173. package/scripts/schedule-health-report.ts +1 -1
  174. package/scripts/storyboard/storyboard.ts +5 -161
  175. package/scripts/test-diet/lib.ts +31 -1
  176. package/scripts/test-diet/node.ts +134 -17
  177. package/scripts/test-timeout-audit.ts +114 -0
  178. package/scripts/tree-sync-alert.ts +1 -1
  179. package/scripts/tui-regress.ts +71 -0
  180. package/scripts/waitlist-daily-digest.ts +1 -1
  181. package/skills/explainer-video/SKILL.md +4 -0
  182. package/skills/explainer-video/engine/reel.mjs +18 -13
  183. package/skills/explainer-video/engine/reel.sh +24 -5
  184. package/skills/explainer-video/engine/stitch.mjs +145 -0
  185. package/skills/pitch-deck/SKILL.md +36 -0
  186. package/skills/pitch-deck/template.html +80 -0
  187. package/src/acp/codex-approval-adapter.ts +9 -3
  188. package/src/acp/core-turn-bridge.ts +117 -18
  189. package/src/acp/fact-question.ts +11 -0
  190. package/src/acp/server.ts +48 -7
  191. package/src/acp/tool-approval.ts +126 -0
  192. package/src/acp/turn-runner.ts +36 -0
  193. package/src/agent/monad-agent-turn.ts +8 -0
  194. package/src/ask-user-question/pending-questions.ts +25 -10
  195. package/src/ask-user-question/types.ts +9 -0
  196. package/src/auth/setup-link-tokens.fixture.ts +19 -0
  197. package/src/auth/setup-link-tokens.ts +164 -0
  198. package/src/autopilot/build/integrity-gate.ts +12 -0
  199. package/src/boot/daemon-prompt-turn.ts +3 -0
  200. package/src/boot/daemon-tools/index.ts +8 -0
  201. package/src/boot/daemon-tools/types.ts +2 -0
  202. package/src/boot/daemon-tools/web-search.ts +1 -0
  203. package/src/budget/unified-usage.ts +75 -0
  204. package/src/channel-bot-token.ts +37 -10
  205. package/src/chat/ctrl-c-in-input.ts +14 -0
  206. package/src/chat/index.ts +61 -9
  207. package/src/chat/pickers/state.ts +6 -2
  208. package/src/cli/agent-cli.ts +7 -0
  209. package/src/cli/decisions-cli.ts +25 -3
  210. package/src/cli/doctor-bundle-upload.ts +68 -0
  211. package/src/cli/doctor-cli.ts +38 -4
  212. package/src/cli/op-proxy-sync.ts +83 -0
  213. package/src/cli/ops-cli.ts +20 -0
  214. package/src/cli/pod-cli.ts +49 -0
  215. package/src/cli/pr-cli.ts +37 -4
  216. package/src/cli/pwa-stop.ts +163 -13
  217. package/src/cli/pwa-test.ts +3 -0
  218. package/src/cli/release-cli.ts +98 -15
  219. package/src/cli/self-update.ts +14 -0
  220. package/src/cli/storyboard-cli.ts +37 -0
  221. package/src/cli/test-state-dir-flag.ts +11 -4
  222. package/src/connectors/linear.ts +146 -0
  223. package/src/context-bus/coord-events.ts +115 -0
  224. package/src/dashboard/dashboard-boot-fixture.ts +23 -0
  225. package/src/dashboard/dashboard-status-lines.ts +45 -0
  226. package/src/dashboard/first-screen-band.ts +35 -0
  227. package/src/dashboard/index.ts +128 -34
  228. package/src/dashboard/provider-slash-runtime.ts +28 -11
  229. package/src/dashboard/slash-runtime/dashboard-handlers.ts +17 -0
  230. package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
  231. package/src/dashboard/turn-stream-formatter.ts +1 -1
  232. package/src/decisions/decision-cards.ts +50 -2
  233. package/src/decisions/decision-ledger.ts +75 -4
  234. package/src/decisions/decision-linear-projection.ts +157 -0
  235. package/src/decisions/web-push-decision-cards.ts +50 -0
  236. package/src/directive/interpretation-card.ts +36 -0
  237. package/src/discord.ts +2 -1
  238. package/src/domains/coo-admin-tool.ts +62 -0
  239. package/src/domains/core-tools.ts +11 -3
  240. package/src/domains/elanous-card.ts +29 -0
  241. package/src/domains/ops-facts-tool.ts +76 -0
  242. package/src/domains/outbound-alert.ts +41 -13
  243. package/src/domains/release-tool.ts +168 -0
  244. package/src/domains/telegram-kind-route.ts +19 -1
  245. package/src/exec-requests/planner.ts +98 -28
  246. package/src/exec-requests/runner.ts +107 -10
  247. package/src/exec-requests/store.ts +8 -2
  248. package/src/field/field-media.ts +10 -7
  249. package/src/field/field-reel.ts +14 -4
  250. package/src/git-fs/worktree.ts +45 -6
  251. package/src/graph-runner/graph-approval-card.ts +113 -0
  252. package/src/graph-runner/graph-cli.ts +129 -4
  253. package/src/graph-runner/graph-notify.ts +25 -3
  254. package/src/graph-runner/runner.ts +142 -16
  255. package/src/harness/browser-verify.ts +25 -2
  256. package/src/harness/harness-cli-command.ts +231 -35
  257. package/src/harness/harness-incidents-writer.fixture.ts +24 -0
  258. package/src/harness/harness-incidents.ts +129 -0
  259. package/src/harness/harness-stop.ts +53 -3
  260. package/src/hitl/clarification-policy.ts +3 -1
  261. package/src/index.ts +172 -41
  262. package/src/intake-plane/author-goals.ts +37 -4
  263. package/src/intake-plane/check.ts +3 -0
  264. package/src/intake-plane/discord-seat-work.ts +47 -2
  265. package/src/intake-plane/seat-doc-route.ts +170 -6
  266. package/src/intake-plane/telegram-seat-work.ts +44 -2
  267. package/src/intake-plane/telegram-work.ts +1 -0
  268. package/src/maturity/cli-maturity.ts +34 -0
  269. package/src/maturity/feature-maturity.ts +100 -1
  270. package/src/maturity/tui-slash-maturity.ts +31 -0
  271. package/src/memory.ts +32 -4
  272. package/src/nexus/api/exec-requests.ts +33 -3
  273. package/src/nexus/api/http-server.ts +73 -1
  274. package/src/nexus/api/meta-api.ts +38 -0
  275. package/src/nexus/api/operator.ts +61 -0
  276. package/src/nexus/api/ops-api.ts +148 -0
  277. package/src/nexus/api/ops-seats-sources.ts +95 -0
  278. package/src/nexus/api/ops-seats.ts +124 -0
  279. package/src/nexus/api/public-routes.ts +14 -1
  280. package/src/nexus/api/seat-requests.ts +88 -8
  281. package/src/nexus/api/sessions-store.ts +63 -0
  282. package/src/nexus/api/setup-claim.ts +21 -0
  283. package/src/nexus/api/setup-codex-login.ts +160 -0
  284. package/src/nexus/api/usage-api.ts +102 -0
  285. package/src/nexus/config/secrets/file-backend.ts +56 -18
  286. package/src/nexus/index.ts +45 -4
  287. package/src/onboarding/first-run-doctor.ts +54 -0
  288. package/src/onboarding/first-run.ts +89 -0
  289. package/src/onboarding/lan-address.ts +45 -0
  290. package/src/onboarding/web-first.ts +119 -24
  291. package/src/onboarding.ts +4 -0
  292. package/src/ops-now/ops-now-refresh.ts +100 -0
  293. package/src/project/project-store.ts +115 -0
  294. package/src/release-loop/checklist.ts +76 -61
  295. package/src/release-loop/feature-store.ts +460 -0
  296. package/src/release-loop/manifest.ts +59 -5
  297. package/src/release-loop/release-schedule.ts +53 -0
  298. package/src/repl/index.ts +7 -0
  299. package/src/seat-dispatch/ceo-commands.ts +98 -0
  300. package/src/seat-dispatch/ceo-intent.ts +38 -0
  301. package/src/seat-loop/seat-loop.ts +231 -0
  302. package/src/seat-loop/seat-report.ts +64 -0
  303. package/src/self-dev/decompose.ts +66 -11
  304. package/src/self-dev/run-store.ts +10 -1
  305. package/src/self-implement/execution-clarification.ts +115 -0
  306. package/src/self-implement/gate-cli.ts +6 -3
  307. package/src/self-implement/orchestrator.ts +37 -3
  308. package/src/self-implement/pipeline-shape.ts +1 -1
  309. package/src/self-implement/run-status-mapping.ts +5 -3
  310. package/src/session/chat.ts +2 -0
  311. package/src/session/index.ts +9 -0
  312. package/src/shell-env-bootstrap.ts +17 -0
  313. package/src/skills/tools/usage-slash.ts +8 -7
  314. package/src/steward/directive.ts +51 -2
  315. package/src/steward/launch.ts +250 -0
  316. package/src/steward/outcome-notify.ts +115 -0
  317. package/src/steward/steward-cards.ts +8 -2
  318. package/src/steward/triage-plan.ts +32 -4
  319. package/src/steward/triage.ts +93 -9
  320. package/src/steward/working-backwards.ts +64 -0
  321. package/src/storyboard/storyboard.ts +141 -0
  322. package/src/task-cards/card-store.ts +2 -2
  323. package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
  324. package/src/telegram-card-followup.ts +233 -0
  325. package/src/telegram-commands.ts +16 -0
  326. package/src/telegram-report.ts +15 -9
  327. package/src/telegram-run.ts +90 -13
  328. package/src/telegram.ts +29 -8
  329. package/src/thinking-line.ts +28 -20
  330. package/src/tool-runtime/types.ts +4 -0
  331. package/src/tui/chat/execution-badge.ts +1 -1
  332. package/src/tui-client/dashboard-session.ts +2 -0
  333. package/src/user-config.ts +118 -11
  334. package/src/ux-sim/turn-states.ts +1 -1
  335. package/src/version/packed-revision.json +1 -1
  336. package/src/web-push/notify-exec-request.ts +32 -0
  337. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/4836-3084c58c1725a40b.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/8894-7317998f6cb97d8c.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/approvals/page-9aa47b0bc6d570ff.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +0 -1
  369. package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
  382. package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +0 -1
  383. package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +0 -1
  384. package/src/dashboard/turn-typeahead-echo.ts +0 -23
  385. /package/apps/pwa/out/_next/static/{lywc8m_HDzH9MKSDQhyEj → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  import { createHash } from 'node:crypto';
3
3
  import { spawnSync } from 'node:child_process';
4
- import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
4
+ import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
5
5
  import { join, resolve } from 'node:path';
6
6
  import { spawnSyncText } from '../src/util/spawn-sync-output.js';
7
7
 
@@ -74,11 +74,21 @@ export function publishInternalDist(checkout: string, out: string, keep = 3): {
74
74
  } else {
75
75
  renameSync(staging, target);
76
76
  }
77
+ const alias = join(download, `v${pkg.version}`);
78
+ try {
79
+ if (!lstatSync(alias).isSymbolicLink()) throw new Error(`version alias is not a symlink: ${alias}`);
80
+ } catch (error) {
81
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
82
+ }
77
83
  mkdirSync(join(out, 'latest'), { recursive: true });
78
84
  link = join(out, 'latest', `.download-${process.pid}-${Date.now()}`);
79
85
  symlinkSync(`../download/${name}`, link, 'dir');
80
86
  renameSync(link, join(out, 'latest', 'download'));
81
87
  link = undefined;
88
+ link = join(download, `.alias-${process.pid}-${Date.now()}`);
89
+ symlinkSync(name, link, 'dir');
90
+ renameSync(link, alias);
91
+ link = undefined;
82
92
  const versions = readdirSync(download, { withFileTypes: true })
83
93
  .filter((entry) => entry.isDirectory() && /^v[0-9A-Za-z][0-9A-Za-z._-]*-[0-9a-f]{12}$/.test(entry.name))
84
94
  .map((entry) => ({ name: entry.name, mtime: statSync(join(download, entry.name)).mtimeMs }))
@@ -4,13 +4,14 @@ import { mkdirSync, realpathSync, writeFileSync } from 'node:fs';
4
4
  import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
5
5
  import { debug } from '../../src/debug/log.js';
6
6
  import { effectiveInstanceRoot } from '../../src/instance/resolve.js';
7
- import { buildReleaseManifest, type ReleaseLanding } from '../../src/release-loop/manifest.js';
7
+ import { buildReleaseManifest, parseNextMdNotes, type NextMdNote, type ReleaseLanding } from '../../src/release-loop/manifest.js';
8
8
  import { readReleaseNotes, releaseNotesDir } from '../../src/release-loop/release-note.js';
9
9
  import { queryRunningRuns } from '../../src/self-implement/running-runs.js';
10
10
  import { loadFederatedRunLedger } from '../../src/self-implement/run-ledger.js';
11
11
 
12
12
  const SEED = 'c1cc37478';
13
13
  const CLI_DOC = 'release/public/docs/cli.md';
14
+ const NEXT_MD = 'release/next.md';
14
15
 
15
16
  function git(args: string[], optional = false): string | null {
16
17
  const result = spawnSync('git', args, { cwd: process.cwd(), encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
@@ -61,6 +62,32 @@ function landingAt(sha: string, title: string): ReleaseLanding {
61
62
  };
62
63
  }
63
64
 
65
+ /**
66
+ * next.md lines added between the baseline and the cutoff, each tied to the last landing in range whose diff added that
67
+ * exact line. Lines already in the file at the baseline shipped with an earlier release and are left out (REL7b).
68
+ */
69
+ function nextMdNotes(baseline: string, cutoff: string, landings: ReleaseLanding[]): { notes: NextMdNote[]; carried: number } {
70
+ const text = git(['show', `${cutoff}:${NEXT_MD}`], true);
71
+ if (!text) return { notes: [], carried: 0 };
72
+ const inRange = new Set(addedLines(requiredGit(['diff', '-U0', baseline, cutoff, '--', NEXT_MD])));
73
+ const addedBy = new Map<string, string>();
74
+ for (const landing of landings) {
75
+ if (!landing.changedFiles.includes(NEXT_MD)) continue;
76
+ const diff = requiredGit(['show', '-m', '--first-parent', '--format=', '-U0', landing.sha, '--', NEXT_MD]);
77
+ for (const line of addedLines(diff)) addedBy.set(line, landing.sha);
78
+ }
79
+ const all = parseNextMdNotes(text);
80
+ const notes = all.filter((note) => note.raw !== undefined && inRange.has(note.raw)).map((note) => {
81
+ const sha = addedBy.get(note.raw!);
82
+ return { kind: note.kind, line: note.line, ...(sha ? { sha } : {}) };
83
+ });
84
+ return { notes, carried: all.length - notes.length };
85
+ }
86
+
87
+ function addedLines(diff: string): string[] {
88
+ return diff.split('\n').filter((line) => line.startsWith('+') && !line.startsWith('+++')).map((line) => line.slice(1).trim());
89
+ }
90
+
64
91
  function runningGoalPaths(): string[] {
65
92
  const repo = process.cwd();
66
93
  const paths: string[] = [];
@@ -118,11 +145,13 @@ export function main(args: string[] = process.argv.slice(2)): void {
118
145
  const instanceRoot = effectiveInstanceRoot();
119
146
  const notes = readReleaseNotes(releaseNotesDir(instanceRoot));
120
147
  debug.log('release.notes', 'read', { fragments: notes.size, problems: notes.problems });
148
+ const nextMd = nextMdNotes(baselineSha, cutoff, landings);
121
149
  const manifest = buildReleaseManifest({
122
- version, baseline: { ref: baseline.ref, sha: baselineSha }, cutoff: { sha: cutoff }, landings, notes,
150
+ version, baseline: { ref: baseline.ref, sha: baselineSha }, cutoff: { sha: cutoff }, landings, notes, nextMd: nextMd.notes,
123
151
  runningGoalPaths: runningGoalPaths(), publicCommandsBefore: commandsAt(baselineSha), publicCommandsAfter: commandsAt(cutoff),
124
152
  });
125
153
  const output = { ...manifest, baselineSource: baseline.source };
154
+ debug.log('release-loop.notes', 'fragments', { version, ...manifest.fragments, carriedFromEarlier: nextMd.carried });
126
155
  const directory = join(instanceRoot, 'release', version);
127
156
  mkdirSync(directory, { recursive: true });
128
157
  writeFileSync(join(directory, 'manifest.json'), JSON.stringify(output, null, 2) + '\n');
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env bun
2
+ import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { finishNode, nodeOutput, readGraphContext, runCommand, type CommandResult, type CommandRunner } from './node-verdict.js';
6
+
7
+ export interface ExportCheckResult extends Record<string, unknown> {
8
+ outcome: 'ok' | 'fail';
9
+ verdict: 'pass' | 'fail';
10
+ summary: string;
11
+ step: 'export' | 'build';
12
+ tail: string;
13
+ }
14
+
15
+ function failed(step: ExportCheckResult['step'], message: string, result?: CommandResult): ExportCheckResult {
16
+ const tail = (result ? result.stderr || result.stdout : message).trim().slice(-300);
17
+ return { outcome: 'fail', verdict: 'fail', summary: `${step} failed: ${message}${result ? ` (rc=${result.status})` : ''}`, step, tail };
18
+ }
19
+
20
+ /** Check the same public export and PWA build as release prepare, without cloning or writing to a public remote. */
21
+ export function runExportCheck(run: CommandRunner = runCommand, repoRoot = process.cwd()): ExportCheckResult {
22
+ const context = readGraphContext();
23
+ const commit = nodeOutput(context, 'version-release', 'commit');
24
+ if (context.outputs.gate?.outcome !== 'ok') return failed('export', 'gate did not pass');
25
+ const root = resolve(repoRoot);
26
+ const out = mkdtempSync(join(tmpdir(), 'elanous-export-check-'));
27
+ const sourceDir = join(out, 'source');
28
+ let added = false;
29
+ let step: ExportCheckResult['step'] = 'export';
30
+ let result: ExportCheckResult = failed(step, 'check incomplete');
31
+ try {
32
+ const modulesRoot = dirname(realpathSync(join(root, 'node_modules')));
33
+ const add = run('git', ['worktree', 'add', '-q', '--detach', sourceDir, commit], root);
34
+ if (add.status !== 0) result = failed('export', 'git worktree add', add);
35
+ else {
36
+ added = true;
37
+ const head = run('git', ['rev-parse', 'HEAD'], sourceDir);
38
+ if (head.status !== 0 || head.stdout.trim() !== commit) result = failed('export', 'cut commit mismatch', head);
39
+ else {
40
+ const version = (JSON.parse(readFileSync(join(sourceDir, 'package.json'), 'utf8')) as { version?: string }).version;
41
+ if (version !== context.input.version) result = failed('export', `package.json version ${version} != ${context.input.version}`);
42
+ else {
43
+ symlinkSync(join(modulesRoot, 'node_modules'), join(sourceDir, 'node_modules'), 'dir');
44
+ const exported = run('bun', ['scripts/public-export.ts', '--out', join(out, 'export')], sourceDir);
45
+ if (exported.status !== 0) result = failed('export', 'public export', exported);
46
+ else {
47
+ step = 'build';
48
+ const publicDir = join(out, 'export');
49
+ symlinkSync(join(modulesRoot, 'node_modules'), join(publicDir, 'node_modules'), 'dir');
50
+ const pwaModules = join(modulesRoot, 'apps', 'pwa', 'node_modules');
51
+ if (existsSync(pwaModules) && existsSync(join(publicDir, 'apps', 'pwa'))) symlinkSync(pwaModules, join(publicDir, 'apps', 'pwa', 'node_modules'), 'dir');
52
+ const built = run('bun', ['bin/elanous.mjs', 'nexus', 'build'], publicDir);
53
+ result = built.status === 0 && existsSync(join(publicDir, 'apps', 'pwa', 'out', 'index.html'))
54
+ ? { outcome: 'ok', verdict: 'pass', summary: `public export and PWA build ${commit.slice(0, 12)}`, step: 'build', tail: '' }
55
+ : failed('build', 'PWA build or out/index.html missing', built);
56
+ }
57
+ }
58
+ }
59
+ }
60
+ } catch (error) {
61
+ result = failed(step, error instanceof Error ? error.message : String(error));
62
+ } finally {
63
+ try {
64
+ if (added) {
65
+ const removed = run('git', ['worktree', 'remove', '--force', sourceDir], root);
66
+ if (removed.status !== 0) result = failed(step, 'git worktree remove', removed);
67
+ }
68
+ } catch (error) {
69
+ result = failed(step, `git worktree remove: ${error instanceof Error ? error.message : String(error)}`);
70
+ } finally {
71
+ rmSync(out, { recursive: true, force: true });
72
+ }
73
+ }
74
+ return result;
75
+ }
76
+
77
+ if (import.meta.main) {
78
+ let version = '';
79
+ try {
80
+ version = readGraphContext().input.version;
81
+ process.exitCode = finishNode('export-check', version, runExportCheck());
82
+ } catch (error) {
83
+ process.exitCode = finishNode('export-check', version, failed('export', error instanceof Error ? error.message : String(error)));
84
+ }
85
+ }
@@ -125,6 +125,14 @@ export const POD_MEMORY_SOURCE = 'docs/measurements/td1-whole-gate-mechanical-20
125
125
  export const POD_SHARD_FILE_CAP = 27;
126
126
 
127
127
  export const POD_SWEEP_INTEGRATION_ONLY: readonly string[] = ['scripts/install.test.ts'];
128
+ /** Whole-repository audits run alone in the nightly test-diet graph, not in release-gate shards. */
129
+ export const GATE_NIGHTLY_AUDITS: readonly string[] = [
130
+ 'test/f12-sweep.test.ts', // Sweeps the whole repository; 7.8 GB even after TD1a.
131
+ 'scripts/unwired-exports.test.ts', // Compiles and scans all exports; 9.9 GB.
132
+ 'test/guardian/dispatch-surface-contract.test.ts', // Compiles repository-wide dispatch contracts; 8.3 GB.
133
+ 'test/pwa-build-typecheck.test.ts', // Builds and typechecks the whole PWA; 4.2 GB.
134
+ 'test/user-config-mcp.test.ts', // 2.1 GB peak; keep the gate below the 2 GB audit budget.
135
+ ];
128
136
  const tail40 = (output: string) => output.trimEnd().split(/\r?\n/).slice(-40).join('\n');
129
137
  const lastStartedTestFile = (output: string, paths: string[]): string | undefined => {
130
138
  let last: string | undefined;
@@ -211,9 +219,11 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
211
219
  // CDP 시험은 `test:deterministic` 이 스스로 뺀다 — 조각에 그것만 남으면 bun «시험 없음» exit 1 을 «불완전»으로 읽었다(09-30 G1d 실측 둘).
212
220
  // bun 의 탐색은 숨은 디렉터리(`.x/`)를 안 연다 — 사람의 `bun test` 가 한 번도 안 돌리는 빈 픽스처가 조각에 혼자 남아 «Ran 0 tests» 를 «불완전»으로 읽었다(09-30 G1e 5번 조각).
213
221
  const assignable = files.filter((file) => !cdpPatterns.includes(file) && !POD_SWEEP_INTEGRATION_ONLY.includes(file)
214
- && !file.split('/').some((part) => part.startsWith('.')));
222
+ && !GATE_NIGHTLY_AUDITS.includes(file) && !file.split('/').some((part) => part.startsWith('.')));
215
223
  const integrationOnly = files.filter((file) => POD_SWEEP_INTEGRATION_ONLY.includes(file));
216
224
  if (integrationOnly.length) debug.log('release-loop.gate', 'pod-sweep-integration-only', { files: integrationOnly });
225
+ const nightlyAudits = files.filter((file) => GATE_NIGHTLY_AUDITS.includes(file));
226
+ if (nightlyAudits.length) debug.log('release-loop.gate', 'pod-sweep-nightly-audit', { files: nightlyAudits });
217
227
  if (!assignable.length) throw new Error('sweep incomplete: no tests ran');
218
228
  const durations = pod.durationSource ? readFileDurations(pod.durationSource) : new Map<string, number>();
219
229
  const memory = readFileMemory(join(tree, POD_MEMORY_SOURCE));
@@ -427,9 +437,10 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
427
437
  const outputs: string[] = [];
428
438
  let total = { pass: 0, fail: 0, errors: 0, ran: 0, files: 0 };
429
439
  for (const group of groups) {
430
- if (!group.paths.length || !files.some((f) => group.paths.some((p) => f === p || f.startsWith(`${p}/`)))) continue;
440
+ if (!group.paths.length || !files.some((f) => !GATE_NIGHTLY_AUDITS.includes(f) && group.paths.some((p) => f === p || f.startsWith(`${p}/`)))) continue;
431
441
  const start = Date.now();
432
- const groupIgnores = cdpPatterns.filter((pattern) => group.paths.some((p) => pattern === p || pattern.startsWith(`${p}/`)))
442
+ const groupIgnores = [...new Set([...cdpPatterns, ...GATE_NIGHTLY_AUDITS])]
443
+ .filter((pattern) => files.includes(pattern) && group.paths.some((p) => pattern === p || pattern.startsWith(`${p}/`)))
433
444
  .flatMap((pattern) => ['--path-ignore-patterns', pattern]);
434
445
  const run = await command('bun', ['run', 'test:deterministic', ...groupIgnores, ...group.paths.map(asPath)], tree);
435
446
  if (logDir) {
@@ -608,7 +619,10 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
608
619
  };
609
620
  let baseline: SweepFailures | undefined = trusted ? { failures: trusted.failures, errors: trusted.errors ?? [] } : undefined;
610
621
  if (!baseline) {
611
- const baseRun = await runner.sweep(await getBaseTree(), join(root, 'release', opts.version, 'gate-logs', 'baseline'));
622
+ // K9b — with no reusable ledger the baseline is swept on the same Pod pool as the cut, not on this host (0.2.6: an
623
+ // hour of local baseline after a 23-minute Pod cut).
624
+ const baseRun = await runner.sweep(await getBaseTree(), join(root, 'release', opts.version, 'gate-logs', 'baseline'),
625
+ opts.pod ? { ...opts.pod, durationSource: join(ledger, 'release', opts.baselineVersion, 'gate-logs', 'cut') } : undefined);
612
626
  baseline = failuresOf(baseRun, 'baseline sweep');
613
627
  }
614
628
  for (const id of [...baseline.failures, ...baseline.errors, ...cut.failures, ...cut.errors]) fileOf(id);
@@ -57,6 +57,11 @@ export function errorResult(error: unknown): { outcome: 'error'; verdict: 'fail'
57
57
  return { outcome: 'error', verdict: 'fail', summary: error instanceof Error ? error.message : String(error) };
58
58
  }
59
59
 
60
+ /** A cut whose notes would show users nothing fails the cutoff instead of shipping a «Plus N internal changes.» page. */
61
+ export function cutoffNotesEmpty(data: Record<string, unknown> | undefined): boolean {
62
+ return Array.isArray(data?.escalate) && (data.escalate as Array<{ kind?: string }>).some((item) => item?.kind === 'notes-empty');
63
+ }
64
+
60
65
  // Existing cutoff/PWA/TUI programs have different output conventions; normalize them at the graph boundary.
61
66
  if (import.meta.main) {
62
67
  const node = process.argv[2];
@@ -78,7 +83,9 @@ if (import.meta.main) {
78
83
  const baseline = data?.baseline as { sha?: string } | undefined;
79
84
  const outcome: NodeOutcome = node === 'cutoff' ? run.status === 0 && data?.version === version && (data?.cutoff as { sha?: string } | undefined)?.sha === commit && typeof baseline?.sha === 'string' ? 'ok' : 'error'
80
85
  : run.status === 0 && (verdict === 'pass' || verdict === 'flaky') ? 'ok' : run.status === 1 ? 'fail' : 'error';
81
- result = { ...data, outcome, verdict: outcome === 'ok' ? verdict === 'flaky' ? 'flaky' : 'pass' : 'fail',
86
+ if (node === 'cutoff' && outcome === 'ok' && cutoffNotesEmpty(data)) {
87
+ result = { ...data, outcome: 'fail', verdict: 'fail', summary: 'cutoff notes-empty — 사용자에게 보일 변경 줄이 0이다(release/next.md 와 PR 노트 조각을 확인하라)' };
88
+ } else result = { ...data, outcome, verdict: outcome === 'ok' ? verdict === 'flaky' ? 'flaky' : 'pass' : 'fail',
82
89
  summary: outcome === 'ok' ? `${node} ${verdict === 'flaky' ? 'flaky' : 'pass'}` : `${node} ${data?.error || run.stderr.trim() || data?.verdict || 'failed'} (rc=${run.status})` };
83
90
  } catch (error) { result = errorResult(error); }
84
91
  process.exitCode = finishNode(node ?? 'unknown', version, result);
@@ -1,8 +1,29 @@
1
1
  #!/usr/bin/env bun
2
+ import { existsSync, readFileSync } from 'node:fs';
2
3
  import { homedir } from 'node:os';
4
+ import { join, isAbsolute } from 'node:path';
5
+ import { debug } from '../../src/debug/log.js';
3
6
  import { errorResult, finishNode, lastResult, nodeOutput, readGraphContext, runCommand, type CommandResult, type CommandRunner } from './node-verdict.js';
4
7
 
5
- interface HostResult { host: string; ok: boolean; before: string; after: string; error?: string; skipped?: string; hooks?: string }
8
+ interface HostResult { host: string; ok: boolean; before: string; after: string; error?: string; skipped?: string; hooks?: string; restart?: 'no-service' }
9
+ interface OpsUpgradeDeps { exists?: (path: string) => boolean; installSource?: () => string | null }
10
+
11
+ function localInstallSource(): string | null {
12
+ try {
13
+ const data: unknown = JSON.parse(readFileSync(join(homedir(), '.local/share/elanous/install.json'), 'utf8'));
14
+ return data && typeof data === 'object' && 'source' in data && typeof data.source === 'string' ? data.source : null;
15
+ } catch { return null; }
16
+ }
17
+
18
+ function installedMatches(installed: unknown, version: string, checkout: boolean): boolean {
19
+ return installed === version || checkout && typeof installed === 'string'
20
+ && installed.startsWith(`${version}-`) && /^[0-9a-f]{12}$/.test(installed.slice(version.length + 1));
21
+ }
22
+
23
+ function missingService(data: Record<string, unknown> | undefined): boolean {
24
+ return typeof data?.reason === 'string'
25
+ && /^재시작 실패:\s*(?:Could not find service\s+"?com\.elanous\.nexus\b|(?:Failed to restart elanous-nexus\.service:\s*)?Unit elanous-nexus(?:\.service)? not found\b)/i.test(data.reason);
26
+ }
6
27
 
7
28
  function firstLine(text: string): string { return text.trim().split(/\r?\n/)[0]?.trim() ?? ''; }
8
29
 
@@ -15,7 +36,7 @@ function reason(result: CommandResult): string {
15
36
 
16
37
  function quote(value: string): string { return `'${value.replaceAll("'", "'\\''")}'`; }
17
38
 
18
- export function runOpsUpgrade(run: CommandRunner = runCommand) {
39
+ export function runOpsUpgrade(run: CommandRunner = runCommand, deps: OpsUpgradeDeps = {}) {
19
40
  const context = readGraphContext();
20
41
  const hosts = context.input.opsHosts;
21
42
  if (hosts === undefined || (Array.isArray(hosts) && hosts.length === 0)) {
@@ -27,9 +48,12 @@ export function runOpsUpgrade(run: CommandRunner = runCommand) {
27
48
  if (nodeOutput(context, 'publish', 'tag') !== `v${version}`) throw new Error('published tag/version mismatch');
28
49
  const internalDist = context.input.internalDist;
29
50
  if (internalDist !== undefined && (typeof internalDist !== 'string' || !internalDist.trim())) throw new Error('input.internalDist must be a path string');
30
- const feedPath = typeof internalDist === 'string' && internalDist.startsWith('~/') ? `${homedir()}/${internalDist.slice(2)}` : internalDist;
51
+ const defaultFeed = join(homedir(), '.local/share/elanous-ops/internal-dist');
52
+ const feedPath = typeof internalDist === 'string'
53
+ ? internalDist.startsWith('~/') ? `${homedir()}/${internalDist.slice(2)}` : internalDist
54
+ : (deps.exists ?? existsSync)(defaultFeed) ? defaultFeed : undefined;
31
55
  let feedError = '';
32
- if (typeof feedPath === 'string') {
56
+ if (feedPath !== undefined) {
33
57
  try {
34
58
  const feed = run('bun', ['scripts/publish-internal-dist.ts', '--checkout', process.cwd(), '--out', feedPath]);
35
59
  const data = lastResult(feed);
@@ -38,8 +62,15 @@ export function runOpsUpgrade(run: CommandRunner = runCommand) {
38
62
  }
39
63
  const results: HostResult[] = [];
40
64
  for (const host of hosts as string[]) {
65
+ const source = host === 'local' ? (deps.installSource ?? localInstallSource)() : null;
66
+ const checkout = host === 'local' && source !== null && isAbsolute(source);
67
+ const path = checkout ? 'checkout' as const : 'release' as const;
68
+ const record = (result: HostResult) => {
69
+ results.push(result);
70
+ debug.log('release-loop.ops-upgrade', 'host', { host, path, restart: result.restart ?? (context.input.opsRestart === true ? result.ok ? 'ok' : 'failed' : 'skipped'), ok: result.ok });
71
+ };
41
72
  if (host !== 'local' && feedError) {
42
- results.push({ host, ok: false, before: '', after: '', skipped: '내부 피드 발행 실패', error: feedError });
73
+ record({ host, ok: false, before: '', after: '', skipped: '내부 피드 발행 실패', error: feedError });
43
74
  continue;
44
75
  }
45
76
  const command = (args: string[]): CommandResult => host === 'local'
@@ -51,22 +82,46 @@ export function runOpsUpgrade(run: CommandRunner = runCommand) {
51
82
  try {
52
83
  const previous = command(['--version']);
53
84
  if (previous.status === 0) before = firstLine(previous.stdout);
54
- const updated = command(['update', '--version', version, '--json', ...(context.input.opsRestart === true ? ['--restart'] : [])]);
85
+ if (checkout) {
86
+ const git = (args: string[]) => run('git', args, source!);
87
+ const dirty = git(['status', '--porcelain', '--untracked-files=all']);
88
+ if (dirty.status !== 0 || dirty.stdout.trim()) {
89
+ record({ host, ok: false, before, after, error: dirty.status === 0 ? '체크아웃 변경 있음' : `체크아웃 상태 확인 실패: ${reason(dirty)}` });
90
+ continue;
91
+ }
92
+ const fetched = git(['fetch', '--tags']);
93
+ if (fetched.status !== 0) {
94
+ record({ host, ok: false, before, after, error: `태그 가져오기 실패: ${reason(fetched)}` });
95
+ continue;
96
+ }
97
+ const tag = git(['rev-parse', '--verify', `refs/tags/v${version}^{commit}`]);
98
+ if (tag.status !== 0 || !tag.stdout.trim()) {
99
+ record({ host, ok: false, before, after, error: `태그 없음: v${version}` });
100
+ continue;
101
+ }
102
+ const moved = git(['checkout', '--detach', `v${version}`]);
103
+ if (moved.status !== 0) {
104
+ record({ host, ok: false, before, after, error: `체크아웃 전환 실패: ${reason(moved)}` });
105
+ continue;
106
+ }
107
+ }
108
+ const updated = command(['update', ...(checkout || host !== 'local' && feedPath !== undefined ? [] : ['--version', version]), '--json', ...(context.input.opsRestart === true ? ['--restart'] : [])]);
55
109
  const updateResult = lastResult(updated);
56
- if (updated.status !== 0 || updateResult?.exitCode !== 0 || updateResult.installedVersion !== version) {
57
- const error = updated.status === 0 && updateResult?.exitCode === 0
58
- ? `설치판 불일치 (기대 ${version}, 실제 ${String(updateResult.installedVersion)})` : reason(updated);
59
- results.push({ host, ok: false, before, after, error });
110
+ const noService = host !== 'local' && context.input.opsRestart === true && updateResult?.exitCode === 1 && missingService(updateResult);
111
+ if ((!noService && (updated.status !== 0 || updateResult?.exitCode !== 0)) || !installedMatches(updateResult?.installedVersion, version, checkout)) {
112
+ const error = (updated.status === 0 && updateResult?.exitCode === 0 || noService)
113
+ ? `설치판 불일치 (기대 ${version}, 실제 ${String(updateResult?.installedVersion)})` : reason(updated);
114
+ record({ host, ok: false, before, after, error });
60
115
  continue;
61
116
  }
62
117
  const current = command(['--version']);
63
118
  if (current.status !== 0) {
64
- results.push({ host, ok: false, before, after, error: reason(current) });
119
+ record({ host, ok: false, before, after, error: reason(current) });
65
120
  continue;
66
121
  }
67
122
  after = firstLine(current.stdout);
68
- if (after.split(/\s+/)[0] !== version) {
69
- results.push({ host, ok: false, before, after, error: `올렸는데 판이 그대로 (기대 ${version}, 실제 ${after || '없음'})` });
123
+ if (!installedMatches(after.split(/\s+/)[0], version, checkout)) {
124
+ record({ host, ok: false, before, after, error: `올렸는데 판이 그대로 (기대 ${version}, 실제 ${after || '없음'})` });
70
125
  continue;
71
126
  }
72
127
  // The webhook receiver (`elanous hooks serve`, a user systemd unit on the bot VM) keeps the old code until it
@@ -76,16 +131,17 @@ export function runOpsUpgrade(run: CommandRunner = runCommand) {
76
131
  const restarted = run('ssh', [host, 'systemctl --user try-restart elanous-hooks.service 2>/dev/null && systemctl --user is-active elanous-hooks.service 2>/dev/null || echo absent']);
77
132
  hooks = firstLine(restarted.stdout) || 'absent';
78
133
  }
79
- results.push({ host, ok: true, before, after, ...(hooks ? { hooks } : {}) });
134
+ record({ host, ok: true, before, after, ...(hooks ? { hooks } : {}), ...(noService ? { restart: 'no-service' as const } : {}) });
80
135
  } catch (error) {
81
- results.push({ host, ok: false, before, after, error: firstLine(String(error)) });
136
+ record({ host, ok: false, before, after, error: firstLine(String(error)) });
82
137
  }
83
138
  }
84
139
  const failures = results.filter((result) => !result.ok);
85
140
  return { outcome: failures.length || feedError ? 'fail' as const : 'ok' as const,
86
141
  verdict: failures.length || feedError ? 'fail' as const : 'pass' as const,
87
142
  summary: failures.length ? `운영 판올림 실패: ${failures.map(({ host, error }) => `${host}: ${error}`).join('; ')}${feedError && !failures.some((item) => item.skipped) ? `; ${feedError}` : ''}`
88
- : feedError || `운영 판올림 ${version} · ${results.length}대 확인`, hosts: results };
143
+ : feedError || `운영 판올림 ${version} · ${results.length}대 확인`,
144
+ feed: feedError ? 'failed' as const : feedPath === undefined ? 'skipped-no-path' as const : 'published' as const, hosts: results };
89
145
  }
90
146
 
91
147
  if (import.meta.main) {
@@ -2,10 +2,13 @@ import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { releaseLedgerRoot } from '../../src/instance/resolve.js';
4
4
  import { checklistGate, type ChecklistGate } from '../../src/release-loop/checklist.js';
5
+ import { getSchedule, formatKst } from '../../src/release-loop/release-schedule.js';
6
+ import { debug } from '../../src/debug/log.js';
5
7
 
6
8
  export type ReleaseReadiness =
7
9
  | { ready: false; reason: 'already-published'; details: string }
8
10
  | { ready: false; reason: 'already-running'; details: string }
11
+ | { ready: false; reason: 'before-cut'; details: string }
9
12
  | { ready: false; reason: 'checklist-blocked'; details: ChecklistGate }
10
13
  | { ready: true; reason: 'ready'; details: ChecklistGate };
11
14
 
@@ -13,6 +16,7 @@ export interface ReleaseReadinessDeps {
13
16
  ledgerRoot?: string;
14
17
  checklist?: typeof checklistGate;
15
18
  isPidAlive?: (pid: number) => boolean;
19
+ now?: () => Date;
16
20
  }
17
21
 
18
22
  function readIfPresent(path: string): string | undefined {
@@ -52,6 +56,11 @@ export function releaseReadiness(version: string, deps: ReleaseReadinessDeps = {
52
56
  return { ready: false, reason: 'already-running', details: String(pid) };
53
57
  }
54
58
  }
59
+ const schedule = getSchedule(version, deps.ledgerRoot);
60
+ if (schedule && (deps.now ?? (() => new Date()))().getTime() < Date.parse(schedule.cutAt)) {
61
+ debug.log('release.schedule', 'before-cut', { version, cutAt: schedule.cutAt, landBy: schedule.landBy });
62
+ return { ready: false, reason: 'before-cut', details: `before-cut (${formatKst(schedule.cutAt)})` };
63
+ }
55
64
  const gate = (deps.checklist ?? checklistGate)(version);
56
65
  if (!gate.ok) return { ready: false, reason: 'checklist-blocked', details: gate };
57
66
  return { ready: true, reason: 'ready', details: gate };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  import { spawnSync } from 'node:child_process';
3
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { join, resolve } from 'node:path';
6
6
  import { emitNodeResult } from './node-verdict.js';
@@ -55,7 +55,16 @@ function versionAt(repo: string): string {
55
55
  return pkg.version;
56
56
  }
57
57
 
58
- function landing(repo: string, kind: Kind, releaseVersion: string): Output {
58
+ /** next.md list lines (trimmed) present at the cut commit, or null when the cut or its next.md cannot be read. */
59
+ function shippedNextMdLines(repo: string, cut: string | undefined): { lines: Set<string> } | { reason: string } {
60
+ if (!cut) return { reason: 'cut-unknown' };
61
+ // git-spawn-allow: reads the cut commit's next.md to learn which lines that release already carried.
62
+ const shown = spawnSync('git', ['show', `${cut}:release/next.md`], { cwd: repo, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
63
+ if (shown.status !== 0 || shown.error) return { reason: 'cut-next-md-unreadable' };
64
+ return { lines: new Set(shown.stdout.split('\n').map((line) => line.trim()).filter((line) => line.startsWith('- '))) };
65
+ }
66
+
67
+ function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string): Output {
59
68
  const target = kind === 'release' ? releaseVersion : nextDevVersion(releaseVersion);
60
69
  const source = kind === 'release' ? `${releaseVersion}-dev.N` : releaseVersion;
61
70
  fetchMain(repo);
@@ -85,6 +94,41 @@ function landing(repo: string, kind: Kind, releaseVersion: string): Output {
85
94
  writeFileSync(lockPath, lock.replace(root[0], root[0].replace(lockedVersion[0], `"version": "${target}"`)));
86
95
  }
87
96
  run('bun', ['install', '--frozen-lockfile'], worktree);
97
+ if (kind === 'dev-bump') {
98
+ const nextPath = join(worktree, 'release', 'next.md');
99
+ if (existsSync(nextPath)) {
100
+ const original = readFileSync(nextPath, 'utf8');
101
+ // OP 10-02: dev-bump runs on the then-current main, not the cut. Lines landed after the cut are not in this
102
+ // release and #22745 carries only lines added by the next cut range — so remove only lines the cut carried.
103
+ const shipped = shippedNextMdLines(repo, cut);
104
+ if ('reason' in shipped) {
105
+ console.error(`⚠ next.md 비우기 건너뜀(${shipped.reason}) — 컷 커밋의 next.md 를 못 읽어 «나간 줄»을 증명할 수 없다`);
106
+ debug.log('release-loop.notes', 'next-md-reset-skipped', { reason: shipped.reason }, { level: 'warn' });
107
+ }
108
+ const shippedLines = 'lines' in shipped ? shipped.lines : new Set<string>();
109
+ let keptAfterCut = 0;
110
+ let removed = 0;
111
+ let keptLater = 0;
112
+ let inSection = false;
113
+ let droppingItem = false;
114
+ // Only shipped list items (and their indented continuation lines) go; headings, prose, `###` subheadings and
115
+ // `Target: later` items stay — the reviewer's «preserve everything but the released lines».
116
+ const reset = original.split(/(?<=\n)/).filter((line) => {
117
+ if (/^## /.test(line)) { inSection = true; droppingItem = false; return true; }
118
+ if (!inSection) return true;
119
+ if (/^- /.test(line)) {
120
+ if (/\bTarget:\s*later(?:\.|\s|$)/i.test(line)) { keptLater++; droppingItem = false; return true; }
121
+ if (!shippedLines.has(line.trim())) { keptAfterCut++; droppingItem = false; return true; }
122
+ removed++; droppingItem = true; return false;
123
+ }
124
+ if (droppingItem && /^[ \t]+\S/.test(line)) return false;
125
+ droppingItem = false;
126
+ return true;
127
+ }).join('');
128
+ if (reset !== original) writeFileSync(nextPath, reset);
129
+ debug.log('release-loop.notes', 'next-md-reset', { removed, keptLater, keptAfterCut });
130
+ }
131
+ }
88
132
  const title = kind === 'release' ? `release: ${target}` : `version: ${target}`;
89
133
  const land = spawnSync('bun', ['bin/elanous.mjs', 'pr', 'land', '--commit-message', title, '--title', title, '--body', `Set package.json version to ${target}.`], {
90
134
  cwd: worktree, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024,
@@ -111,24 +155,28 @@ function landing(repo: string, kind: Kind, releaseVersion: string): Output {
111
155
  function main(args: string[] = process.argv.slice(2), repo = process.cwd()): Output {
112
156
  let kind: Kind | null = null;
113
157
  let version: string | null = null;
158
+ let cut: string | undefined;
114
159
  try {
115
- if (args[0] !== 'release' && args[0] !== 'dev-bump') throw new Error('usage: version-node.ts <release|dev-bump> [--version <v>] --json');
160
+ if (args[0] !== 'release' && args[0] !== 'dev-bump') throw new Error('usage: version-node.ts <release|dev-bump> [--version <v>] [--cut <sha>] --json');
116
161
  kind = args[0];
117
162
  for (let i = 1; i < args.length; i++) {
118
163
  if (args[i] === '--json') continue;
119
164
  if (args[i] === '--version' && args[i + 1]) { version = args[++i]!; continue; }
165
+ if (args[i] === '--cut' && args[i + 1]) { cut = args[++i]!; continue; }
120
166
  throw new Error(`unknown or incomplete argument: ${args[i]}`);
121
167
  }
122
- if (!version) {
123
- if (process.env.ELANOUS_GRAPH_CONTEXT) {
124
- const location = process.env.ELANOUS_GRAPH_CONTEXT;
125
- const context = JSON.parse(location.trimStart().startsWith('{') ? location : readFileSync(location, 'utf8')) as { input?: { version?: unknown } };
126
- if (typeof context.input?.version === 'string') version = context.input.version;
127
- }
168
+ if (process.env.ELANOUS_GRAPH_CONTEXT) {
169
+ const location = process.env.ELANOUS_GRAPH_CONTEXT;
170
+ const context = JSON.parse(location.trimStart().startsWith('{') ? location : readFileSync(location, 'utf8')) as {
171
+ input?: { version?: unknown }; outputs?: Record<string, { commit?: unknown } | undefined> };
172
+ if (!version && typeof context.input?.version === 'string') version = context.input.version;
173
+ // The cut = the commit version-release landed (what cutoff and the gate measured).
174
+ const cutCommit = context.outputs?.['version-release']?.commit;
175
+ if (!cut && typeof cutCommit === 'string' && cutCommit) cut = cutCommit;
128
176
  }
129
177
  if (!version) throw new Error('version required (--version or ELANOUS_GRAPH_CONTEXT.input.version)');
130
178
  nextDevVersion(version);
131
- return landing(resolve(repo), kind, version);
179
+ return landing(resolve(repo), kind, version, cut);
132
180
  } catch (error) {
133
181
  const message = error instanceof Error ? error.message : String(error);
134
182
  const worktree = /\[worktree: ([^\]]+)\]$/.exec(message)?.[1];
@@ -25,7 +25,7 @@ function log(s: string): void {
25
25
 
26
26
  const results = runRepoWatchCycle({
27
27
  // 흡수 제안 리포트 발송(report 채널 · 야간무음 게이트 · fail-soft).
28
- notify: (report) => { try { log(`발송 ${sendOutbound(report, 'report') ? 'OK' : '실패'}`); } catch (e) { log(`발송 오류: ${e instanceof Error ? e.message : String(e)}`); } },
28
+ notify: (report) => { try { log(`발송 ${sendOutbound(report, 'ops-report') ? 'OK' : '실패'}`); } catch (e) { log(`발송 오류: ${e instanceof Error ? e.message : String(e)}`); } },
29
29
  // 자율행동 회상 기록(loop=autopilot).
30
30
  record: (input) => { recordAutonomousActionSafe({ loop: 'autopilot', ...input }); },
31
31
  });
@@ -120,11 +120,11 @@ const report = await runRetroCycle({
120
120
  const p = join(REPORT_DIR, filename); writeFileSync(p, md);
121
121
  // ★ B5: --collect-only 면 발송 억제(회고 리포트 영속·resolution/발굴 입력·발송만 skip).
122
122
  if (process.argv.includes('--collect-only')) log(`collect-only — 회고 리포트 영속·발송 skip`);
123
- else try { log(`회고 리포트 발송 ${sendOutbound(md, 'report') ? 'OK' : '실패'}`); } catch (e) { log(`발송 오류: ${e instanceof Error ? e.message : String(e)}`); }
123
+ else try { log(`회고 리포트 발송 ${sendOutbound(md, 'ops-report') ? 'OK' : '실패'}`); } catch (e) { log(`발송 오류: ${e instanceof Error ? e.message : String(e)}`); }
124
124
  return p;
125
125
  },
126
126
  // HITL 승인 요청 — 텔레그램 alert(collect-only 면 로그만).
127
- notify: (t) => { log(`[HITL] ${t.replace(/\n/g, ' ')}`); if (!process.argv.includes('--collect-only')) try { sendOutbound(t, 'alert'); } catch { /* fail-soft */ } },
127
+ notify: (t) => { log(`[HITL] ${t.replace(/\n/g, ' ')}`); if (!process.argv.includes('--collect-only')) try { sendOutbound(t, 'ops-alert'); } catch { /* fail-soft */ } },
128
128
  }, period);
129
129
 
130
130
  log(`retro ${period}: report=${report.reportPath} · 제안=${report.hasProposal ? report.proposalId : '없음'}`);
@@ -96,7 +96,7 @@ function main(): void {
96
96
  console.log(msg);
97
97
  return;
98
98
  }
99
- const ok = sendOutbound(msg, 'alert');
99
+ const ok = sendOutbound(msg, 'ops-alert');
100
100
  console.log(`[schedule-health] ${mode}: ${ok ? '발송' : '발송 실패'} — 밀림 ${health.stale.length}·실패 ${health.errored.length}`);
101
101
  } finally {
102
102
  db.close();