elanous 0.2.9 → 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 (361) 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-f972fc1c00a62e88.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-f8116559c7424da7.js +1 -0
  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-54e1f966ab60a4dc.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/editor/{page-973996d584f008c1.js → page-7ed95f296f5ee8e9.js} +1 -1
  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-8b0402a46a749a3d.js → page-b31610deda76337b.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-037f83e2a1edc8e2.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-0df30e0c7e50148a.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-4fddc90b23feb215.js +1 -0
  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-90670589b87abea8.js → page-26102fd7350f171e.js} +1 -1
  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-35bedde6d885f08f.js → page-139ba5c367bbf60a.js} +1 -1
  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 +14 -14
  64. package/apps/pwa/out/autopilot/index.html +1 -1
  65. package/apps/pwa/out/autopilot/index.txt +14 -14
  66. package/apps/pwa/out/board/index.html +1 -1
  67. package/apps/pwa/out/board/index.txt +14 -14
  68. package/apps/pwa/out/botlab/index.html +1 -1
  69. package/apps/pwa/out/botlab/index.txt +14 -14
  70. package/apps/pwa/out/bots/index.html +1 -1
  71. package/apps/pwa/out/bots/index.txt +14 -14
  72. package/apps/pwa/out/chat/index.html +1 -1
  73. package/apps/pwa/out/chat/index.txt +14 -14
  74. package/apps/pwa/out/consult/index.html +1 -1
  75. package/apps/pwa/out/consult/index.txt +14 -14
  76. package/apps/pwa/out/control/index.html +1 -1
  77. package/apps/pwa/out/control/index.txt +14 -14
  78. package/apps/pwa/out/design-check/index.html +1 -1
  79. package/apps/pwa/out/design-check/index.txt +14 -14
  80. package/apps/pwa/out/editor/index.html +1 -1
  81. package/apps/pwa/out/editor/index.txt +14 -14
  82. package/apps/pwa/out/exec/index.html +1 -1
  83. package/apps/pwa/out/exec/index.txt +14 -14
  84. package/apps/pwa/out/field/index.html +1 -1
  85. package/apps/pwa/out/field/index.txt +14 -14
  86. package/apps/pwa/out/healthz +1 -1
  87. package/apps/pwa/out/index.html +1 -1
  88. package/apps/pwa/out/index.txt +14 -14
  89. package/apps/pwa/out/intake/index.html +1 -1
  90. package/apps/pwa/out/intake/index.txt +14 -14
  91. package/apps/pwa/out/live/index.html +1 -1
  92. package/apps/pwa/out/live/index.txt +14 -14
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +14 -14
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +14 -14
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +14 -14
  101. package/apps/pwa/out/ops/checklist/index.html +1 -1
  102. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  103. package/apps/pwa/out/ops/release/index.html +1 -1
  104. package/apps/pwa/out/ops/release/index.txt +14 -14
  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 +15 -15
  109. package/apps/pwa/out/scheduler/index.html +1 -1
  110. package/apps/pwa/out/scheduler/index.txt +14 -14
  111. package/apps/pwa/out/sessions/index.html +1 -1
  112. package/apps/pwa/out/sessions/index.txt +14 -14
  113. package/apps/pwa/out/settings/devices/index.html +1 -1
  114. package/apps/pwa/out/settings/devices/index.txt +14 -14
  115. package/apps/pwa/out/settings/index.html +1 -1
  116. package/apps/pwa/out/settings/index.txt +14 -14
  117. package/apps/pwa/out/setup/done/index.html +1 -1
  118. package/apps/pwa/out/setup/done/index.txt +14 -14
  119. package/apps/pwa/out/setup/index.html +1 -1
  120. package/apps/pwa/out/setup/index.txt +14 -14
  121. package/apps/pwa/out/share/index.html +1 -1
  122. package/apps/pwa/out/share/index.txt +14 -14
  123. package/apps/pwa/out/showroom/index.html +1 -1
  124. package/apps/pwa/out/showroom/index.txt +14 -14
  125. package/apps/pwa/out/tasks/index.html +1 -1
  126. package/apps/pwa/out/tasks/index.txt +14 -14
  127. package/apps/pwa/out/term/index.html +1 -1
  128. package/apps/pwa/out/term/index.txt +14 -14
  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 +14 -14
  133. package/apps/pwa/out/vault/index.html +1 -1
  134. package/apps/pwa/out/vault/index.txt +14 -14
  135. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  136. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  137. package/apps/pwa/out/workflows/index.html +1 -1
  138. package/apps/pwa/out/workflows/index.txt +14 -14
  139. package/apps/pwa/out/workspace/index.html +1 -1
  140. package/apps/pwa/out/workspace/index.txt +14 -14
  141. package/apps/pwa/out/worktrees/index.html +1 -1
  142. package/apps/pwa/out/worktrees/index.txt +14 -14
  143. package/graphs/field/field-feed.yaml +2 -0
  144. package/graphs/implement-loop.yaml +1 -1
  145. package/package.json +1 -1
  146. package/plugins/card-followup/graphs/run-step.ts +48 -13
  147. package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
  148. package/plugins/doc-draft/graphs/run-step.ts +13 -6
  149. package/scripts/ci-pwa-build-gate.ts +160 -0
  150. package/scripts/coord-channel-watch.sh +42 -8
  151. package/scripts/coord-digest.ts +199 -0
  152. package/scripts/coord-post.sh +60 -3
  153. package/scripts/install.ps1 +68 -9
  154. package/scripts/install.sh +39 -12
  155. package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
  156. package/scripts/lib/tui-regress-checks.ts +146 -0
  157. package/scripts/memory-lifecycle-cycle.ts +2 -2
  158. package/scripts/ops-now-probe.ts +327 -0
  159. package/scripts/publish-internal-dist.ts +11 -1
  160. package/scripts/release-loop/cutoff.ts +19 -8
  161. package/scripts/release-loop/ops-upgrade-node.ts +72 -16
  162. package/scripts/release-loop/release-readiness.ts +9 -0
  163. package/scripts/release-loop/version-node.ts +58 -10
  164. package/scripts/repo-watch-cycle.ts +1 -1
  165. package/scripts/retro-cycle.ts +2 -2
  166. package/scripts/schedule-health-report.ts +1 -1
  167. package/scripts/storyboard/storyboard.ts +5 -161
  168. package/scripts/test-diet/lib.ts +30 -1
  169. package/scripts/test-diet/node.ts +82 -7
  170. package/scripts/test-timeout-audit.ts +114 -0
  171. package/scripts/tree-sync-alert.ts +1 -1
  172. package/scripts/tui-regress.ts +71 -0
  173. package/scripts/waitlist-daily-digest.ts +1 -1
  174. package/skills/explainer-video/SKILL.md +4 -0
  175. package/skills/explainer-video/engine/reel.mjs +18 -13
  176. package/skills/explainer-video/engine/reel.sh +24 -5
  177. package/skills/explainer-video/engine/stitch.mjs +145 -0
  178. package/skills/pitch-deck/SKILL.md +36 -0
  179. package/skills/pitch-deck/template.html +80 -0
  180. package/src/acp/codex-approval-adapter.ts +9 -3
  181. package/src/acp/core-turn-bridge.ts +117 -18
  182. package/src/acp/fact-question.ts +11 -0
  183. package/src/acp/server.ts +48 -7
  184. package/src/acp/tool-approval.ts +126 -0
  185. package/src/acp/turn-runner.ts +36 -0
  186. package/src/agent/monad-agent-turn.ts +8 -0
  187. package/src/ask-user-question/pending-questions.ts +25 -10
  188. package/src/ask-user-question/types.ts +9 -0
  189. package/src/auth/setup-link-tokens.ts +16 -5
  190. package/src/autopilot/build/integrity-gate.ts +12 -0
  191. package/src/boot/daemon-prompt-turn.ts +3 -0
  192. package/src/boot/daemon-tools/index.ts +8 -0
  193. package/src/boot/daemon-tools/types.ts +2 -0
  194. package/src/boot/daemon-tools/web-search.ts +1 -0
  195. package/src/chat/ctrl-c-in-input.ts +14 -0
  196. package/src/chat/index.ts +52 -0
  197. package/src/chat/pickers/state.ts +6 -2
  198. package/src/cli/agent-cli.ts +7 -0
  199. package/src/cli/decisions-cli.ts +25 -3
  200. package/src/cli/doctor-bundle-upload.ts +68 -0
  201. package/src/cli/doctor-cli.ts +38 -4
  202. package/src/cli/ops-cli.ts +20 -0
  203. package/src/cli/pod-cli.ts +49 -0
  204. package/src/cli/pr-cli.ts +31 -2
  205. package/src/cli/pwa-stop.ts +163 -13
  206. package/src/cli/pwa-test.ts +3 -0
  207. package/src/cli/release-cli.ts +63 -20
  208. package/src/cli/self-update.ts +14 -0
  209. package/src/cli/storyboard-cli.ts +37 -0
  210. package/src/cli/test-state-dir-flag.ts +11 -4
  211. package/src/connectors/linear.ts +146 -0
  212. package/src/context-bus/coord-events.ts +115 -0
  213. package/src/dashboard/dashboard-boot-fixture.ts +23 -0
  214. package/src/dashboard/index.ts +58 -21
  215. package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -0
  216. package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
  217. package/src/decisions/decision-cards.ts +49 -1
  218. package/src/decisions/decision-ledger.ts +75 -4
  219. package/src/decisions/decision-linear-projection.ts +157 -0
  220. package/src/decisions/web-push-decision-cards.ts +50 -0
  221. package/src/directive/interpretation-card.ts +36 -0
  222. package/src/discord.ts +2 -1
  223. package/src/domains/coo-admin-tool.ts +62 -0
  224. package/src/domains/core-tools.ts +11 -3
  225. package/src/domains/elanous-card.ts +29 -0
  226. package/src/domains/ops-facts-tool.ts +76 -0
  227. package/src/domains/outbound-alert.ts +41 -13
  228. package/src/domains/release-tool.ts +168 -0
  229. package/src/domains/telegram-kind-route.ts +19 -1
  230. package/src/exec-requests/planner.ts +98 -28
  231. package/src/exec-requests/runner.ts +107 -10
  232. package/src/exec-requests/store.ts +8 -2
  233. package/src/field/field-media.ts +10 -7
  234. package/src/field/field-reel.ts +14 -4
  235. package/src/git-fs/worktree.ts +45 -6
  236. package/src/graph-runner/graph-approval-card.ts +113 -0
  237. package/src/graph-runner/graph-cli.ts +129 -4
  238. package/src/graph-runner/graph-notify.ts +25 -3
  239. package/src/graph-runner/runner.ts +142 -16
  240. package/src/harness/browser-verify.ts +25 -2
  241. package/src/harness/harness-cli-command.ts +231 -35
  242. package/src/harness/harness-incidents-writer.fixture.ts +24 -0
  243. package/src/harness/harness-incidents.ts +129 -0
  244. package/src/harness/harness-stop.ts +53 -3
  245. package/src/hitl/clarification-policy.ts +3 -1
  246. package/src/index.ts +156 -37
  247. package/src/intake-plane/author-goals.ts +37 -4
  248. package/src/intake-plane/check.ts +3 -0
  249. package/src/intake-plane/discord-seat-work.ts +47 -2
  250. package/src/intake-plane/seat-doc-route.ts +170 -6
  251. package/src/intake-plane/telegram-seat-work.ts +44 -2
  252. package/src/intake-plane/telegram-work.ts +1 -0
  253. package/src/maturity/cli-maturity.ts +34 -0
  254. package/src/maturity/feature-maturity.ts +98 -1
  255. package/src/maturity/tui-slash-maturity.ts +31 -0
  256. package/src/memory.ts +32 -4
  257. package/src/nexus/api/exec-requests.ts +33 -3
  258. package/src/nexus/api/http-server.ts +53 -1
  259. package/src/nexus/api/meta-api.ts +11 -0
  260. package/src/nexus/api/ops-api.ts +10 -1
  261. package/src/nexus/api/ops-seats-sources.ts +37 -7
  262. package/src/nexus/api/ops-seats.ts +16 -6
  263. package/src/nexus/api/seat-requests.ts +88 -8
  264. package/src/nexus/api/sessions-store.ts +63 -0
  265. package/src/nexus/api/setup-codex-login.ts +160 -0
  266. package/src/nexus/api/usage-api.ts +102 -0
  267. package/src/nexus/index.ts +45 -4
  268. package/src/onboarding/first-run.ts +89 -0
  269. package/src/onboarding/lan-address.ts +45 -0
  270. package/src/onboarding/web-first.ts +119 -24
  271. package/src/onboarding.ts +4 -0
  272. package/src/ops-now/ops-now-refresh.ts +100 -0
  273. package/src/project/project-store.ts +115 -0
  274. package/src/release-loop/checklist.ts +78 -4
  275. package/src/release-loop/feature-store.ts +154 -21
  276. package/src/release-loop/release-schedule.ts +53 -0
  277. package/src/repl/index.ts +7 -0
  278. package/src/seat-dispatch/ceo-commands.ts +41 -8
  279. package/src/seat-dispatch/ceo-intent.ts +38 -0
  280. package/src/seat-loop/seat-loop.ts +231 -0
  281. package/src/seat-loop/seat-report.ts +64 -0
  282. package/src/self-dev/decompose.ts +66 -11
  283. package/src/self-dev/run-store.ts +10 -1
  284. package/src/self-implement/execution-clarification.ts +115 -0
  285. package/src/self-implement/gate-cli.ts +6 -3
  286. package/src/self-implement/orchestrator.ts +37 -3
  287. package/src/self-implement/pipeline-shape.ts +1 -1
  288. package/src/self-implement/run-status-mapping.ts +5 -3
  289. package/src/session/chat.ts +2 -0
  290. package/src/session/index.ts +9 -0
  291. package/src/shell-env-bootstrap.ts +17 -0
  292. package/src/steward/directive.ts +51 -2
  293. package/src/steward/launch.ts +250 -0
  294. package/src/steward/outcome-notify.ts +115 -0
  295. package/src/steward/steward-cards.ts +8 -2
  296. package/src/steward/triage-plan.ts +32 -4
  297. package/src/steward/triage.ts +93 -9
  298. package/src/steward/working-backwards.ts +64 -0
  299. package/src/storyboard/storyboard.ts +141 -0
  300. package/src/task-cards/card-store.ts +2 -2
  301. package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
  302. package/src/telegram-card-followup.ts +233 -0
  303. package/src/telegram-report.ts +15 -9
  304. package/src/telegram-run.ts +7 -0
  305. package/src/telegram.ts +29 -8
  306. package/src/tool-runtime/types.ts +4 -0
  307. package/src/tui-client/dashboard-session.ts +2 -0
  308. package/src/user-config.ts +118 -11
  309. package/src/version/packed-revision.json +1 -1
  310. package/src/web-push/notify-exec-request.ts +32 -0
  311. package/apps/pwa/out/_next/static/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/8630-cdfc61e82c653ffc.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/approvals/page-fb7c2d458f32fcad.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/bots/page-5f30e00693527ebd.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/chat/page-7fbae173b3c103a7.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/consult/page-3b4edcce364062ea.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/exec/page-016e2bda04eecb1f.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/field/page-b4d6d9a3b56f2940.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/morning/page-adfbd2ba9598e59a.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/page-1a8e4807292f6b17.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/sessions/page-c71c33a573230672.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/app/tasks/page-369846d2921af584.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/app/trace/page-77b28bfb0996d5ea.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/app/workspace/page-4498c3853f4f688d.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
  359. package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +0 -1
  360. package/src/dashboard/turn-typeahead-echo.ts +0 -23
  361. /package/apps/pwa/out/_next/static/{bFI5EEwg7T6aG88ibX4Zq → 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 }))
@@ -62,20 +62,30 @@ function landingAt(sha: string, title: string): ReleaseLanding {
62
62
  };
63
63
  }
64
64
 
65
- /** next.md lines at the cutoff, each tied to the last landing in range whose diff added that exact line. */
66
- function nextMdNotes(cutoff: string, landings: ReleaseLanding[]): NextMdNote[] {
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 } {
67
70
  const text = git(['show', `${cutoff}:${NEXT_MD}`], true);
68
- if (!text) return [];
71
+ if (!text) return { notes: [], carried: 0 };
72
+ const inRange = new Set(addedLines(requiredGit(['diff', '-U0', baseline, cutoff, '--', NEXT_MD])));
69
73
  const addedBy = new Map<string, string>();
70
74
  for (const landing of landings) {
71
75
  if (!landing.changedFiles.includes(NEXT_MD)) continue;
72
76
  const diff = requiredGit(['show', '-m', '--first-parent', '--format=', '-U0', landing.sha, '--', NEXT_MD]);
73
- for (const line of diff.split('\n')) if (line.startsWith('+') && !line.startsWith('+++')) addedBy.set(line.slice(1).trim(), landing.sha);
77
+ for (const line of addedLines(diff)) addedBy.set(line, landing.sha);
74
78
  }
75
- return parseNextMdNotes(text).map((note) => {
76
- const sha = note.raw ? addedBy.get(note.raw) : undefined;
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!);
77
82
  return { kind: note.kind, line: note.line, ...(sha ? { sha } : {}) };
78
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());
79
89
  }
80
90
 
81
91
  function runningGoalPaths(): string[] {
@@ -135,12 +145,13 @@ export function main(args: string[] = process.argv.slice(2)): void {
135
145
  const instanceRoot = effectiveInstanceRoot();
136
146
  const notes = readReleaseNotes(releaseNotesDir(instanceRoot));
137
147
  debug.log('release.notes', 'read', { fragments: notes.size, problems: notes.problems });
148
+ const nextMd = nextMdNotes(baselineSha, cutoff, landings);
138
149
  const manifest = buildReleaseManifest({
139
- version, baseline: { ref: baseline.ref, sha: baselineSha }, cutoff: { sha: cutoff }, landings, notes, nextMd: nextMdNotes(cutoff, landings),
150
+ version, baseline: { ref: baseline.ref, sha: baselineSha }, cutoff: { sha: cutoff }, landings, notes, nextMd: nextMd.notes,
140
151
  runningGoalPaths: runningGoalPaths(), publicCommandsBefore: commandsAt(baselineSha), publicCommandsAfter: commandsAt(cutoff),
141
152
  });
142
153
  const output = { ...manifest, baselineSource: baseline.source };
143
- debug.log('release-loop.notes', 'fragments', { version, ...manifest.fragments });
154
+ debug.log('release-loop.notes', 'fragments', { version, ...manifest.fragments, carriedFromEarlier: nextMd.carried });
144
155
  const directory = join(instanceRoot, 'release', version);
145
156
  mkdirSync(directory, { recursive: true });
146
157
  writeFileSync(join(directory, 'manifest.json'), JSON.stringify(output, null, 2) + '\n');
@@ -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();
@@ -1,167 +1,11 @@
1
1
  #!/usr/bin/env bun
2
- // 스토리보드 정본(storyboards/<kind>/<id>.yaml) — 검사 ⊕ MD 뽑기 (대표 10-01 23:0x · OP 형식 결정 = YAML 정본 ⊕ 스키마 ⊕ 생성 MD).
3
- // bun scripts/storyboard/storyboard.ts lint [파일…] 스키마 필수 칸 · 시간 겹침 · 화면 문구 출처 · 승인본 규칙
4
- // bun scripts/storyboard/storyboard.ts render [파일…] 내부 문서 `STORYBOARD-<id>-v<version>` 를 다시 쓴다(손으로 안 고친다)
5
- // 인자가 없으면 storyboards/ 아래 전부. lint 는 오류가 있으면 rc 1, 경고만이면 rc 0.
6
- import { readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
7
- import { join, relative, resolve } from 'node:path';
8
- import { parse } from 'yaml';
9
-
10
- const ROOT = resolve(import.meta.dir, '../..');
11
- const DIR = join(ROOT, 'storyboards');
12
-
13
- type Text = { text: string; source: string; ref?: string };
14
- type Shot = {
15
- id: string; t: [number, number]; stage: string; layer?: string; persona?: string; purpose: string; subject?: string; action?: string;
16
- camera?: { move?: string; lens_mm?: number; angle?: string; framing?: string }; lighting?: string; color?: string;
17
- transition_in?: { type: string; intent: string }; on_screen_text?: Text[]; audio?: { vo?: string; sfx?: string; bgm?: string };
18
- generation?: { tool: string; model?: string; prompt?: string; asset?: string }; real_or_staged: string; state: string; notes?: string;
19
- };
20
- /** kind: site — a site's page structure, menu and copy draft, planned like a storyboard (SITE2 · 10-02). */
21
- type Section = { id: string; title: string; purpose: string; change: string; copy?: Text[]; notes?: string };
22
- type Site = { domain: string; role: string; preview?: string; branch?: string; nav: Array<{ label: string; href: string }>; pages: Array<{ path: string; sections: Section[] }> };
23
- export type Storyboard = {
24
- id: string; kind: string; title: string; version: number; status: string; approved?: { by: string; at: string; note?: string } | null;
25
- supersedes?: string; owner: string; directive?: string; format: { aspects: string[]; duration_s: number; fps?: number; loop?: boolean; surface?: string };
26
- scqa?: { s?: string; c?: string; q?: string; a?: string }; principles?: string[]; layers?: Record<string, string>; shots: Shot[]; sites?: Site[];
27
- references?: string[]; open_questions?: string[];
28
- };
29
-
30
- const KINDS = ['teaser', 'site-hero', 'event-demo', 'persona-scene', 'explainer', 'ad', 'site'];
31
- const CHANGES = ['keep', 'move', 'new', 'replace', 'remove'];
32
- const STAGES = ['hook', 'buildup', 'climax', 'transition', 'end', 'loop'];
33
- const SOURCES = ['real-ui', 'real-cli', 'real-output', 'copy', 'example'];
34
-
35
- export function lint(sb: Storyboard, file = ''): { errors: string[]; warnings: string[] } {
36
- const errors: string[] = [], warnings: string[] = [];
37
- const need = (cond: unknown, msg: string) => { if (!cond) errors.push(msg); };
38
- need(/^[a-z0-9][a-z0-9-]{2,80}$/.test(sb.id ?? ''), 'id: 소문자·숫자·하이픈 3~80자');
39
- need(KINDS.includes(sb.kind), `kind: ${KINDS.join('|')}`);
40
- need(Number.isInteger(sb.version) && sb.version >= 1, 'version: 1 이상 정수');
41
- need(['draft', 'approved', 'superseded'].includes(sb.status), 'status: draft|approved|superseded');
42
- need(sb.owner, 'owner 필요');
43
- if (sb.status === 'approved') need(sb.approved?.by && sb.approved?.at, '승인본은 approved.by·at 필요');
44
- if (file && sb.kind && !file.includes(`/${sb.kind}/`)) errors.push(`파일 위치가 kind(${sb.kind}) 폴더가 아니다`);
45
- if (sb.kind === 'site') return lintSite(sb, errors, warnings);
46
- need(sb.format?.aspects?.length && sb.format.duration_s > 0, 'format.aspects ⊕ duration_s 필요');
47
- if (!Array.isArray(sb.shots) || !sb.shots.length) { errors.push('shots 가 비었다'); return { errors, warnings }; }
48
- const ids = new Set<string>();
49
- sb.shots.forEach((s, i) => {
50
- const at = `shots[${i}](${s.id ?? '?'})`;
51
- need(s.id && !ids.has(s.id), `${at}: id 없음·중복`); ids.add(s.id);
52
- need(Array.isArray(s.t) && s.t.length === 2 && s.t[0] < s.t[1], `${at}: t = [시작, 끝] 이고 시작 < 끝`);
53
- need(STAGES.includes(s.stage), `${at}: stage ${STAGES.join('|')}`);
54
- need(s.purpose, `${at}: purpose(이 비트의 한 가지) 필요`);
55
- need(['real', 'staged', 'composite'].includes(s.real_or_staged), `${at}: real_or_staged 필요`);
56
- need(['planned', 'shot', 'approved'].includes(s.state), `${at}: state planned|shot|approved`);
57
- if (s.transition_in && !s.transition_in.intent) errors.push(`${at}: 전환에는 의도 한 줄(«그냥 컷»은 설계가 아니다)`);
58
- for (const txt of s.on_screen_text ?? []) {
59
- need(SOURCES.includes(txt.source), `${at}: 화면 문구 «${txt.text}» 의 source 가 ${SOURCES.join('|')} 가 아니다`);
60
- if (txt.source?.startsWith('real-') && !txt.ref) warnings.push(`${at}: 실제 문면 «${txt.text}» 에 ref(출처) 가 없다`);
61
- if (/\d/.test(txt.text) && txt.source === 'copy') warnings.push(`${at}: 마케팅 문구에 숫자 «${txt.text}» — 실측 근거나 «example» 로`);
62
- }
63
- if (s.generation?.tool === 'higgsfield' && !s.generation.prompt) warnings.push(`${at}: 힉스필드 샷에 prompt 가 없다`);
64
- if (s.real_or_staged !== 'real' && !/연출/.test(JSON.stringify(sb))) warnings.push(`${at}: 연출 샷이 있는데 문서 어디에도 «연출 장면» 표기가 없다`);
65
- });
66
- const end = Math.max(...sb.shots.map((s) => s.t?.[1] ?? 0));
67
- if (sb.format?.duration_s && Math.abs(end - sb.format.duration_s) > 0.5) warnings.push(`마지막 샷 끝(${end}s) ≠ format.duration_s(${sb.format.duration_s}s)`);
68
- return { errors, warnings };
69
- }
70
-
71
- /** A site plan has pages instead of shots: every section says why it is there and what changes; copy names its source. */
72
- function lintSite(sb: Storyboard, errors: string[], warnings: string[]): { errors: string[]; warnings: string[] } {
73
- if (!Array.isArray(sb.sites) || !sb.sites.length) { errors.push('site 종류는 sites 가 필요하다'); return { errors, warnings }; }
74
- for (const site of sb.sites) {
75
- const at = `sites(${site.domain ?? '?'})`;
76
- if (!site.domain || !site.role) errors.push(`${at}: domain ⊕ role(이 사이트가 맡는 것) 필요`);
77
- if (!site.nav?.length) errors.push(`${at}: nav(메뉴) 필요`);
78
- if (!site.pages?.length) errors.push(`${at}: pages 필요`);
79
- if (!site.preview) warnings.push(`${at}: preview(미리보기 주소) 없음`);
80
- const ids = new Set<string>();
81
- for (const page of site.pages ?? []) for (const sec of page.sections ?? []) {
82
- const where = `${at}${page.path} #${sec.id ?? '?'}`;
83
- if (!sec.id || ids.has(`${page.path}#${sec.id}`)) errors.push(`${where}: id 없음·중복`);
84
- ids.add(`${page.path}#${sec.id}`);
85
- if (!sec.purpose) errors.push(`${where}: purpose(이 칸이 하는 한 가지) 필요`);
86
- if (!CHANGES.includes(sec.change)) errors.push(`${where}: change ${CHANGES.join('|')}`);
87
- for (const txt of sec.copy ?? []) {
88
- if (!SOURCES.includes(txt.source)) errors.push(`${where}: 문구 «${txt.text}» 의 source 가 ${SOURCES.join('|')} 가 아니다`);
89
- if (/\d/.test(txt.text) && txt.source === 'copy' && !txt.ref) warnings.push(`${where}: 마케팅 문구에 숫자 «${txt.text}» — ref(실측 근거) 를 단다`);
90
- }
91
- }
92
- }
93
- return { errors, warnings };
94
- }
95
-
96
- function renderSites(sb: Storyboard): string[] {
97
- return (sb.sites ?? []).flatMap((site) => [
98
- `## ${site.domain} — ${site.role}`, '',
99
- ...(site.preview ? [`- 미리보기: ${site.preview}${site.branch ? ` (가지 \`${site.branch}\`)` : ''}`] : []),
100
- `- 메뉴: ${site.nav.map((n) => `${n.label}(${n.href})`).join(' · ')}`, '',
101
- ...site.pages.flatMap((page) => [
102
- `### ${page.path}`, '', '| # | 칸 | 제목 | 목적 | 바뀜 | 문구(출처) | 메모 |', '|---|---|---|---|---|---|---|',
103
- ...page.sections.map((sec, i) => `| ${i + 1} | ${md(sec.id)} | ${md(sec.title)} | ${md(sec.purpose)} | ${sec.change} | ${md((sec.copy ?? []).map((x) => `«${x.text}»(${x.source}${x.ref ? ` · ${x.ref}` : ''})`).join(' / '))} | ${md(sec.notes)} |`),
104
- '',
105
- ]),
106
- ]);
107
- }
108
-
109
- const md = (v: unknown) => String(v ?? '').replace(/\|/g, '\\|').replace(/\n+/g, ' ');
110
- export function render(sb: Storyboard, file: string): string {
111
- const head = [
112
- `# STORYBOARD — ${sb.title} (v${sb.version} · ${sb.status}${sb.approved ? ` · 승인 ${sb.approved.by} ${sb.approved.at}` : ''})`,
113
- '',
114
- `> ⛔ 자동 생성 — 손으로 고치지 않는다. 정본 = \`${file}\` · 다시 뽑기 = \`bun scripts/storyboard/storyboard.ts render ${file}\`.`,
115
- `> 종류 ${sb.kind} · 소유 ${sb.owner}${sb.format ? ` · ${sb.format.aspects.join(' · ')} · ${sb.format.duration_s}초${sb.format.loop ? ' · 반복' : ''}${sb.format.surface ? ` · ${sb.format.surface}` : ''}` : ''}${sb.supersedes ? ` · 앞 판 ${sb.supersedes}` : ''}`,
116
- ...(sb.directive ? [`> 지시: ${sb.directive}`] : []),
117
- '',
118
- ];
119
- const scqa = sb.scqa ? ['## SCQA', ...(['s', 'c', 'q', 'a'] as const).filter((k) => sb.scqa?.[k]).map((k) => `- **${k.toUpperCase()}** — ${sb.scqa![k]}`), ''] : [];
120
- const pr = sb.principles?.length ? ['## 원칙', ...sb.principles.map((p, i) => `${i + 1}. ${p}`), ''] : [];
121
- const layers = sb.layers ? ['## 층', ...Object.entries(sb.layers).map(([k, v]) => `- **${k}** — ${v}`), ''] : [];
122
- const table = sb.kind === 'site' ? renderSites(sb) : [
123
- '## 샷 구조표',
124
- '| # | 초 | 단계 | 층·인물 | 목적 | 화면(피사체·행동) | 카메라 | 빛·색 | 전환(의도) | 화면 문구(출처) | 소리 | 만들기 | 실물/연출 · 상태 |',
125
- '|---|---|---|---|---|---|---|---|---|---|---|---|---|',
126
- ...sb.shots.map((s) => `| ${md(s.id)} | ${s.t[0]}–${s.t[1]} | ${s.stage} | ${md([s.layer, s.persona].filter(Boolean).join(' · '))} | ${md(s.purpose)} | ${md([s.subject, s.action].filter(Boolean).join(' — '))} | ${md(s.camera ? [s.camera.move, s.camera.lens_mm ? `${s.camera.lens_mm}mm` : '', s.camera.angle, s.camera.framing].filter(Boolean).join(' · ') : '')} | ${md([s.lighting, s.color].filter(Boolean).join(' · '))} | ${md(s.transition_in ? `${s.transition_in.type} — ${s.transition_in.intent}` : '')} | ${md((s.on_screen_text ?? []).map((x) => `«${x.text}»(${x.source}${x.ref ? ` · ${x.ref}` : ''})`).join(' / '))} | ${md(s.audio ? [s.audio.vo && `VO ${s.audio.vo}`, s.audio.sfx && `SFX ${s.audio.sfx}`, s.audio.bgm && `BGM ${s.audio.bgm}`].filter(Boolean).join(' · ') : '')} | ${md(s.generation ? [s.generation.tool, s.generation.model].filter(Boolean).join(' · ') : '')} | ${s.real_or_staged} · ${s.state} |`),
127
- '',
128
- ];
129
- const shots = sb.shots ?? [];
130
- const prompts = shots.filter((s) => s.generation?.prompt);
131
- const pblock = prompts.length ? ['## 생성 프롬프트', ...prompts.flatMap((s) => [`### ${s.id} · ${s.generation!.tool}${s.generation!.model ? ` · ${s.generation!.model}` : ''}`, '```', s.generation!.prompt!.trim(), '```', ...(s.generation!.asset ? [`결과: \`${s.generation!.asset}\``] : []), ''])] : [];
132
- const notes = shots.filter((s) => s.notes).map((s) => `- ${s.id}: ${s.notes}`);
133
- const tail = [
134
- ...(notes.length ? ['## 샷 메모', ...notes, ''] : []),
135
- ...(sb.open_questions?.length ? ['## 열린 물음', ...sb.open_questions.map((q) => `- ${q}`), ''] : []),
136
- ...(sb.references?.length ? ['## 참고', ...sb.references.map((r) => `- ${r}`), ''] : []),
137
- ];
138
- return [...head, ...scqa, ...pr, ...layers, ...table, ...pblock, ...tail].join('\n');
139
- }
140
-
141
- function files(args: string[]): string[] {
142
- if (args.length) return args.map((a) => resolve(a));
143
- const out: string[] = [];
144
- const walk = (d: string) => { for (const f of readdirSync(d)) { const p = join(d, f); if (statSync(p).isDirectory()) walk(p); else if (/\.ya?ml$/.test(f) && !f.startsWith('_')) out.push(p); } };
145
- walk(DIR);
146
- return out.sort();
147
- }
2
+ // 스토리보드 정본 검사 ⊕ MD 생성. 기존 bun scripts/storyboard/storyboard.ts lint|render 지원.
3
+ import { runStoryboard } from '../../src/cli/storyboard-cli.js';
4
+ export { lint, render } from '../../src/storyboard/storyboard.js';
5
+ export type { Storyboard } from '../../src/storyboard/storyboard.js';
148
6
 
149
7
  if (import.meta.main) {
150
8
  const [cmd, ...rest] = process.argv.slice(2);
151
9
  if (cmd !== 'lint' && cmd !== 'render') { console.error('usage: storyboard.ts lint|render [files…]'); process.exit(2); }
152
- let bad = 0;
153
- for (const f of files(rest)) {
154
- const rel = relative(ROOT, f);
155
- const sb = parse(readFileSync(f, 'utf8')) as Storyboard;
156
- const { errors, warnings } = lint(sb, rel);
157
- for (const e of errors) console.log(`✗ ${rel}: ${e}`);
158
- for (const w of warnings) console.log(`⚠ ${rel}: ${w}`);
159
- if (errors.length) { bad++; continue; }
160
- if (cmd === 'render') {
161
- const out = join(ROOT, 'docs', 'marketing', `STORYBOARD-${sb.id}-v${sb.version}.md`);
162
- writeFileSync(out, render(sb, rel) + '\n');
163
- console.log(`✓ ${rel} → ${relative(ROOT, out)}`);
164
- } else console.log(`✓ ${rel} · v${sb.version} · ${sb.status} · ${sb.kind === 'site' ? `사이트 ${sb.sites?.length ?? 0}` : `샷 ${sb.shots.length}`}`);
165
- }
166
- process.exit(bad ? 1 : 0);
10
+ process.exitCode = runStoryboard(cmd, rest);
167
11
  }