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
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env bun
2
+ // PWA build gate (GATE-PWA · 10-02 MAT1b near-miss: a PWA-bundled src module gained a TUI import and every gate passed).
3
+ // The PWA is a static export outside `bun test`/tsc, so a change that reaches its bundle graph is built here.
4
+ // Reach is decided by the TypeScript program of apps/pwa/tsconfig.json (aliases, extends, type imports included);
5
+ // anything the program cannot decide is treated as reaching the PWA — «unsure» builds, it never skips.
6
+ import { spawnSync } from 'node:child_process';
7
+ import { existsSync, readdirSync } from 'node:fs';
8
+ import { basename, dirname, extname, join, relative, resolve, sep } from 'node:path';
9
+ import ts from 'typescript';
10
+ import { debug } from '../src/debug/log.js';
11
+ import { checkPwaBuildDeps } from '../src/cli/pwa-build.js';
12
+ import { prepareDeterministicChildEnvironment } from './lib/deterministic-env.js';
13
+ import { parseChangedFiles } from './ci-ios-unit-tests.js';
14
+
15
+ const ROOT = join(import.meta.dir, '..');
16
+ const PWA_PREFIX = 'apps/pwa/';
17
+ const PWA_IGNORED = /^apps\/pwa\/(?:node_modules|\.next|out)\//;
18
+ const TS_SOURCE = /\.(?:tsx?|mts|cts)$/;
19
+ const CODE_OR_ASSET = /\.(?:[cm]?[jt]sx?|json|css|scss|svg|png|jpe?g|webp|woff2?)$/;
20
+
21
+ export type BuildResult = { status: number | null; output: string; error?: Error };
22
+ export type PwaGraph = { files: Set<string>; uncertain: string | null };
23
+ export type PwaGateIo = {
24
+ args?: readonly string[];
25
+ cwd?: string;
26
+ log?: (line: string) => void;
27
+ error?: (line: string) => void;
28
+ /** Repo-relative changed files when `--changed-files` is absent (test seam · default git diff vs origin/main). */
29
+ changedFiles?: (root: string) => string[] | null;
30
+ graph?: (root: string) => PwaGraph;
31
+ runBuild?: (root: string) => BuildResult;
32
+ };
33
+
34
+ function defaultBuild(root: string): BuildResult {
35
+ const pwa = join(root, 'apps/pwa');
36
+ let deps = checkPwaBuildDeps(pwa);
37
+ if (!deps.ok) {
38
+ // Pod worktrees and fresh harvest trees often have no apps/pwa/node_modules — install once from the lockfile, then re-check.
39
+ const install = spawnSync('bun', ['install', '--frozen-lockfile'], { cwd: pwa, encoding: 'utf8', timeout: 300_000, maxBuffer: 16 * 1024 * 1024 });
40
+ try { debug.log('gate.pwa', 'deps-install', { status: install.status }); } catch { /* fail-soft */ }
41
+ deps = checkPwaBuildDeps(pwa);
42
+ if (!deps.ok) return { status: null, output: `apps/pwa/node_modules incomplete after install (rc=${install.status}): ${deps.missing.slice(0, 3).join(', ')}` };
43
+ }
44
+ const isolated = prepareDeterministicChildEnvironment('elanous-pwa-build-gate-');
45
+ try {
46
+ // Same build `nexus build` runs (runPwaBuild → bun run build), with HOME/state/config confined to a temp child env.
47
+ const result = spawnSync('bun', ['run', 'build'], { cwd: pwa, env: isolated.env, encoding: 'utf8', timeout: 300_000, maxBuffer: 32 * 1024 * 1024 });
48
+ return { status: result.status, output: `${result.stdout ?? ''}\n${result.stderr ?? ''}`, error: result.error };
49
+ } finally {
50
+ isolated.cleanup();
51
+ }
52
+ }
53
+
54
+ function defaultChangedFiles(root: string): string[] | null {
55
+ const r = spawnSync('git', ['diff', '--name-only', 'origin/main...HEAD'], { cwd: root, encoding: 'utf8' });
56
+ if (r.status !== 0) return null;
57
+ return r.stdout.split('\n').map((l) => l.trim()).filter(Boolean);
58
+ }
59
+
60
+ /** Dynamic import()/require() whose argument is not a string literal — the program cannot follow it. */
61
+ function opaqueLoad(sf: ts.SourceFile): boolean {
62
+ let found = false;
63
+ const visit = (node: ts.Node): void => {
64
+ if (found) return;
65
+ if (ts.isCallExpression(node) && (node.expression.kind === ts.SyntaxKind.ImportKeyword
66
+ || (ts.isIdentifier(node.expression) && node.expression.text === 'require'))) {
67
+ const arg = node.arguments[0];
68
+ if (!arg || !(ts.isStringLiteral(arg) || ts.isNoSubstitutionTemplateLiteral(arg))) { found = true; return; }
69
+ }
70
+ ts.forEachChild(node, visit);
71
+ };
72
+ visit(sf);
73
+ return found;
74
+ }
75
+
76
+ /** Every repo file the PWA program loads, plus same-stem siblings (a `.d.ts` may stand in for the `.js` the bundler reads). */
77
+ export function pwaGraph(root: string): PwaGraph {
78
+ const configPath = join(root, 'apps/pwa/tsconfig.json');
79
+ const read = ts.readConfigFile(configPath, ts.sys.readFile);
80
+ if (read.error) return { files: new Set(), uncertain: `tsconfig unreadable: ${ts.flattenDiagnosticMessageText(read.error.messageText, ' ')}` };
81
+ const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, dirname(configPath));
82
+ if (parsed.errors.some((d) => d.category === ts.DiagnosticCategory.Error)) {
83
+ return { files: new Set(), uncertain: `tsconfig invalid: ${ts.flattenDiagnosticMessageText(parsed.errors[0]!.messageText, ' ')}` };
84
+ }
85
+ const program = ts.createProgram({ rootNames: parsed.fileNames, options: { ...parsed.options, noEmit: true } });
86
+ const files = new Set<string>();
87
+ const absRoot = resolve(root) + sep;
88
+ for (const sf of program.getSourceFiles()) {
89
+ const abs = resolve(sf.fileName);
90
+ if (!abs.startsWith(absRoot) || abs.includes(`${sep}node_modules${sep}`)) continue;
91
+ const rel = relative(root, abs).split(sep).join('/');
92
+ files.add(rel);
93
+ // A non-literal import()/require() is a bundler «context»: anything beside it may be loaded — take the whole directory.
94
+ if (opaqueLoad(sf)) {
95
+ const dir = dirname(abs);
96
+ try {
97
+ for (const name of readdirSync(dir)) if (/\.(?:[cm]?[jt]sx?|json)$/.test(name)) files.add(relative(root, join(dir, name)).split(sep).join('/'));
98
+ } catch { return { files, uncertain: `cannot list ${relative(root, dir)} beside a non-literal import` }; }
99
+ }
100
+ const stem = basename(rel).replace(/\.d\.ts$|\.[^.]+$/, '');
101
+ for (const ext of ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.d.ts', '.json', '.css']) {
102
+ const sibling = join(dirname(rel), stem + ext).split(sep).join('/');
103
+ if (existsSync(join(root, sibling))) files.add(sibling);
104
+ }
105
+ }
106
+ return { files, uncertain: null };
107
+ }
108
+
109
+ export type PwaReach = { reaches: boolean; reason: string; hits: string[] };
110
+
111
+ /** Decide whether a change set reaches the PWA build. Unknowns reach. */
112
+ export function pwaReach(changed: readonly string[], graph: () => PwaGraph): PwaReach {
113
+ const pwaHits = changed.filter((f) => f.startsWith(PWA_PREFIX) && !PWA_IGNORED.test(f));
114
+ if (pwaHits.length) return { reaches: true, reason: `apps/pwa 변경 ${pwaHits.length}개`, hits: pwaHits };
115
+ // Any repo file the PWA program loads counts (src/, scripts/, shared roots); non-code files under src/ build conservatively.
116
+ const candidates = changed.filter((f) => CODE_OR_ASSET.test(f));
117
+ if (!candidates.length) return { reaches: false, reason: `변경 ${changed.length}개 중 코드·자산 파일 0개`, hits: [] };
118
+ let g: PwaGraph;
119
+ try { g = graph(); } catch (err) { return { reaches: true, reason: `PWA 그래프를 못 읽었다(${err instanceof Error ? err.message : String(err)}) — 닿는 것으로 본다`, hits: candidates }; }
120
+ if (g.uncertain) return { reaches: true, reason: `PWA 그래프 판정 불가(${g.uncertain}) — 닿는 것으로 본다`, hits: candidates };
121
+ const hits = candidates.filter((f) => g.files.has(f) || (f.startsWith('src/') && !TS_SOURCE.test(f)));
122
+ if (hits.length) return { reaches: true, reason: `PWA 가 닿는 파일 ${hits.length}개`, hits };
123
+ return { reaches: false, reason: `코드 변경 ${candidates.length}개 — PWA 그래프(${g.files.size}개) 밖`, hits: [] };
124
+ }
125
+
126
+ export function runPwaBuildGate(io: PwaGateIo = {}): number {
127
+ const log = io.log ?? console.log;
128
+ const error = io.error ?? console.error;
129
+ const root = io.cwd ?? ROOT;
130
+ if (!io.graph && !existsSync(join(root, 'apps/pwa/package.json'))) {
131
+ log('[pwa-gate] 해당 없음 — 이 트리에 apps/pwa 가 없다.');
132
+ return 0;
133
+ }
134
+ const changed = parseChangedFiles(io.args ?? process.argv.slice(2)) ?? (io.changedFiles ?? defaultChangedFiles)(root);
135
+ if (changed === null) {
136
+ error('[pwa-gate] 측정 불가 — 변경 파일 목록을 못 얻었다(git diff 실패).');
137
+ return 1;
138
+ }
139
+ const reach = pwaReach(changed, () => (io.graph ?? pwaGraph)(root));
140
+ try { debug.log('gate.pwa', reach.reaches ? 'reach' : 'skip', { changed: changed.length, hits: reach.hits.slice(0, 20), reason: reach.reason }); } catch { /* fail-soft */ }
141
+ if (!reach.reaches) {
142
+ log(`[pwa-gate] 해당 없음 — ${reach.reason}.`);
143
+ return 0;
144
+ }
145
+ log(`[pwa-gate] 대상 — ${reach.reason}${reach.hits.length ? ` (${reach.hits.slice(0, 5).join(', ')}${reach.hits.length > 5 ? ' …' : ''})` : ''} — PWA 빌드를 돌린다.`);
146
+ const started = Date.now();
147
+ const result = (io.runBuild ?? defaultBuild)(root);
148
+ const seconds = Math.round((Date.now() - started) / 1000);
149
+ try { debug.log('gate.pwa', 'build', { status: result.status, seconds }); } catch { /* fail-soft */ }
150
+ if (result.status === 0) {
151
+ log(`[pwa-gate] PASS — PWA 빌드 성공(${seconds}s).`);
152
+ return 0;
153
+ }
154
+ const tail = result.output.trim().split('\n').slice(-15).join('\n');
155
+ if (result.status === null) error(`[pwa-gate] 측정 불가 — 빌드를 못 돌렸다: ${result.error?.message ?? tail.split('\n').pop() ?? 'unknown'}`);
156
+ else error(`[pwa-gate] FAIL — PWA 빌드 실패(rc=${result.status} · ${seconds}s):\n${tail}`);
157
+ return 1;
158
+ }
159
+
160
+ if (import.meta.main) process.exit(runPwaBuildGate());
@@ -48,8 +48,8 @@
48
48
 
49
49
  set -u
50
50
 
51
- PR_NUM="${CH_PR:-20798}" # ⛔ 2026-09-27: #16815 가 2,500 상한에 닿아 채널 «5» 로 옮김(🅞 · 벽에 «닿은 뒤» — 미리 못 옮겼다)
52
- # 📏 이력: #5730(07-28~08-12) → #8328(08-11~08-25) → #12577(08-25~09-10) → #16815(09-10~09-27) → #20798.
51
+ PR_NUM="${CH_PR:-23032}" # ⛔ 2026-10-03: #20798 가 2,500 상한 → 채널 «6»(OP) # ⛔ 2026-09-27: #16815 가 2,500 상한에 닿아 채널 «5» 로 옮김(🅞 · 벽에 «닿은 뒤» — 미리 못 옮겼다)
52
+ # 📏 이력: #5730(07-28~08-12) → #8328(08-11~08-25) → #12577(08-25~09-10) → #16815(09-10~09-27) → #20798(09-27~10-03) → #23032.
53
53
  # ***수명이 «2주 안팎»이다 — 세 판이 전부 그랬다.*** 다음 벽은 09-24 언저리.
54
54
  # 🩹 발신이 rc=1 이면 먼저 세라: gh api repos/ElanvitalAI/elanous/issues/<n> --jq .comments
55
55
  REPO="${CH_REPO:-ElanvitalAI/elanous}"
@@ -418,6 +418,23 @@ FALLBACK_USES=0
418
418
  # 「파이프를 통과하면 성패가 사라진다」와 같은 뿌리다.
419
419
  FALLBACK_FLAG="${TMPDIR:-/tmp}/ch-watch-fallback-$PR_NUM-$TRACK.$$"
420
420
 
421
+ emit_comments() { # $1=JSONL (line, archive, comment)
422
+ local out="$1" deferred
423
+ [ -n "$out" ] || return 0
424
+ if [ "${COORD_WATCH_SKIP_OTHERS_REPORTS:-}" = "1" ]; then
425
+ deferred=$(printf '%s\n' "$out" | jq -c 'select(.archive) | .comment') || return 1
426
+ if [ -n "$deferred" ]; then
427
+ mkdir -p "$HOME/.elanous/coord" || return 1
428
+ printf '%s\n' "$deferred" >> "$HOME/.elanous/coord/$SELF_ID-reports.jsonl" || return 1
429
+ fi
430
+ fi
431
+ if [ "${COORD_WATCH_SKIP_OTHERS_REPORTS:-}" = "1" ]; then
432
+ printf '%s\n' "$out" | jq -r 'select(.archive | not) | .line' || return 1
433
+ else
434
+ printf '%s\n' "$out" | jq -r '.line' || return 1
435
+ fi
436
+ }
437
+
421
438
  fetch_comments() { # $1=since(ISO8601) → stdout=포맷된 신규 행 · rc=0 성공 / rc=1 두 경로 다 실패
422
439
  local since="$1" out fmt
423
440
  # 두 경로가 «같은 형태»를 내도록 정규화된 객체 하나만 본다: {id, created_at, login, body}
@@ -426,13 +443,30 @@ fetch_comments() { # $1=since(ISO8601) → stdout=포맷된 신규 행 · rc=0
426
443
  # 그 글은 필터를 «통과»했고 창에도 떴다 — 그런데 다른 글 사이에 섞여 «요청»으로 안 보였다.
427
444
  # 🔑 그러므로 결손은 「고르기」가 아니라 ***「고른 뒤 «구분»하지 않은 것」***이다.
428
445
  # ⛔ 표지를 «본문 앞»에 둔다 — 뒤에 두면 BODY_CHARS 절단에 잘려 사라진다.
429
- fmt="select(((.body | sub(\"^[[:space:]#]+\"; \"\")) | (startswith(\"**[$SELF_ID]\") or startswith(\"[$SELF_ID]\") or startswith(\"**[$SELF_ALIAS]\") or startswith(\"[$SELF_ALIAS]\"))) | not)
430
- | \"[#$PR_NUM 신규 id=\" + (.id|tostring) + \" \" + .created_at + \" @\" + .login + \"]\"
431
- + (if ((.body | test(\"$REQ_ADDR\")) or ((.body | test(\"$REQ_MARK\")) and (.body | test(\"부탁드립니다|부탁합니다\")))) then \" 🙋‼️ 나에게 온 «요청»\" else \"\" end)
432
- + \" \" + (.body[:$BODY_CHARS] | gsub(\"\n\"; \" ⏎ \"))"
446
+ fmt=$(cat <<JQ
447
+ select(((.body | sub("^[[:space:]#]+"; "")) | (startswith("**[$SELF_ID]") or startswith("[$SELF_ID]") or startswith("**[$SELF_ALIAS]") or startswith("[$SELF_ALIAS]"))) | not)
448
+ | . as \$comment
449
+ | ((.body | split("\n")[0] | capture("^[[:space:]#]*[*][*][[][^]]+[]][*][*][^\n]*→[[:space:]]*(?<to>[^\n]+?)[[:space:]]*·[[:space:]]*(?<kind>요청|결정|사고|보고|정정)[[:space:]]*·")) // null) as \$envelope
450
+ | (if \$envelope == null then false else
451
+ (\$envelope.to | split("·") | map(gsub("^[[:space:]]+|[[:space:]]+$"; "")))
452
+ | any(.[]; . == "$SELF_ID" or . == "$SELF_ALIAS" or . == "전원")
453
+ end) as \$mine
454
+ | ((.body | test("$REQ_ADDR")) or ((.body | test("$REQ_MARK")) and (.body | test("부탁드립니다|부탁합니다")))) as \$legacy_request
455
+ | (\$envelope != null and \$envelope.kind == "보고" and (\$mine | not)) as \$archive
456
+ | {comment: \$comment, archive: \$archive,
457
+ line: ("[#$PR_NUM 신규 id=" + (.id|tostring) + " " + .created_at + " @" + .login + "]"
458
+ + (if \$mine and \$envelope.kind == "결정" then " 🙋 나에게 «결정»"
459
+ elif \$mine and \$envelope.kind == "요청" then " 🙋 나에게 «요청»"
460
+ else "" end)
461
+ + (if \$legacy_request then " 🙋‼️ 나에게 온 «요청»" else "" end)
462
+ + (if \$envelope != null and (\$envelope.kind == "사고" or \$envelope.kind == "정정") then " 🚨"
463
+ elif \$archive then " 📄 보고" else "" end)
464
+ + " " + (.body[:$BODY_CHARS] | gsub("\n"; " ⏎ ")))}
465
+ JQ
466
+ )
433
467
  if out=$(gh api --paginate "repos/$REPO/issues/$PR_NUM/comments?since=$since" \
434
468
  --jq ".[] | {id, created_at, login: .user.login, body} | $fmt" 2>/dev/null); then
435
- printf '%s' "$out"; return 0
469
+ emit_comments "$out"; return $?
436
470
  fi
437
471
  local owner="${REPO%%/*}" name="${REPO##*/}"
438
472
  if out=$(gh api graphql -f query="query{repository(owner:\"$owner\",name:\"$name\"){pullRequest(number:$PR_NUM){comments(last:$COMMENT_FALLBACK_LAST){nodes{databaseId createdAt author{login} body}}}}}" \
@@ -443,7 +477,7 @@ fetch_comments() { # $1=since(ISO8601) → stdout=포맷된 신규 행 · rc=0
443
477
  # ⭐ 폴백이 «쓰였다»는 사실만 남긴다 — 세는 것도 알릴지 정하는 것도 «부모 셸»이 한다.
444
478
  # ⛔ 여기서 세면 서브셸과 함께 사라진다(위 머리말).
445
479
  : > "$FALLBACK_FLAG" 2>/dev/null || true
446
- printf '%s' "$out"; return 0
480
+ emit_comments "$out"; return $?
447
481
  fi
448
482
  return 1
449
483
  }
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env bun
2
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { homedir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { debug } from '../src/debug/log.js';
7
+ import { devVersion, listChecklist, statusChangesSince } from '../src/release-loop/checklist.js';
8
+
9
+ interface Report { id: number | string; created_at: string; login: string; body: string }
10
+ interface Change { version: string; at: string; from: unknown; to: unknown }
11
+ interface Cell { id: string; reports: number; bySender: Record<string, number>; last: string | null; statusChanges: Change[] }
12
+ interface Digest { track: string; since: string; versions: string[]; cells: Cell[]; totals: { reports: number; cells: number; statusChanges: number } }
13
+
14
+ const NO_CELL = '칸 없음';
15
+ const registry = JSON.parse(readFileSync(join(import.meta.dir, 'coord-tracks.json'), 'utf8')) as { tracks: Array<{ id: string; alias?: string }> };
16
+ const recipients = new Set(['전원', ...registry.tracks.flatMap(({ id, alias }) => alias ? [id, alias] : [id])]);
17
+ const STATUS_ICON: Record<string, string> = { yellow: '🟡', green: '🟢', red: '🔴', done: '✅' };
18
+
19
+ function options(args: string[]): { track: string; hours: number; versions: string[]; json: boolean; explicitSince: boolean } {
20
+ let track = '';
21
+ let hours = 2;
22
+ let json = false;
23
+ let explicitSince = false;
24
+ const versions: string[] = [];
25
+ for (let i = 0; i < args.length; i++) {
26
+ const arg = args[i];
27
+ if (arg === '--track' && args[i + 1]) track = args[++i]!;
28
+ else if (arg === '--since' && args[i + 1]) {
29
+ const match = /^(\d+(?:\.\d+)?)h$/.exec(args[++i]!);
30
+ if (!match || Number(match[1]) <= 0) throw new Error('--since 는 양수 시간(예: 2h)이어야 한다');
31
+ hours = Number(match[1]);
32
+ explicitSince = true;
33
+ } else if (arg === '--version' && args[i + 1]) versions.push(args[++i]!);
34
+ else if (arg === '--json') json = true;
35
+ else throw new Error(`모르는 인자: ${arg}`);
36
+ }
37
+ if (!/^[A-Za-z][A-Za-z0-9]*$/.test(track)) throw new Error('--track <ID> 가 필요하다');
38
+ return { track, hours, versions: versions.length ? [...new Set(versions)] : [devVersion().replace(/-dev\.\d+$/, '')], json, explicitSince };
39
+ }
40
+
41
+ function reportCell(body: string): { id: string; sender: string } | null {
42
+ const line = body.split(/\r?\n/, 1)[0] ?? '';
43
+ const envelope = /^\s*(?:#+\s*)?\*\*\[([^\]]+)\]\*\*[^\n]*?→\s*(.+)$/.exec(line);
44
+ if (!envelope) return null;
45
+ const fields = envelope[2]!.split('·').map((part) => part.trim());
46
+ if (!recipients.has(fields[0] ?? '') || fields[1] !== '보고') return null;
47
+ const cell = fields[2];
48
+ return { id: !cell || cell === '-' ? NO_CELL : cell, sender: envelope[1]! };
49
+ }
50
+
51
+ function readCursor(path: string): Set<string> {
52
+ let raw: string;
53
+ try {
54
+ raw = readFileSync(path, 'utf8');
55
+ } catch (error) {
56
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return new Set();
57
+ throw error;
58
+ }
59
+ try {
60
+ const parsed: unknown = JSON.parse(raw);
61
+ if (!Array.isArray(parsed) || !parsed.every((id) => typeof id === 'string' || typeof id === 'number')) {
62
+ throw new Error('invalid cursor ids');
63
+ }
64
+ return new Set(parsed.map(String));
65
+ } catch (error) {
66
+ const backup = `${path}.corrupt-${randomUUID()}`;
67
+ writeFileSync(backup, raw, { mode: 0o600, flag: 'wx' });
68
+ console.error(`${path}: 손상된 커서를 ${backup} 에 보존하고 빈 커서로 복구: ${error instanceof Error ? error.message : String(error)}`);
69
+ return new Set();
70
+ }
71
+ }
72
+
73
+ function saveCursor(path: string, seen: Set<string>): void {
74
+ const temporary = `${path}.tmp-${randomUUID()}`;
75
+ try {
76
+ writeFileSync(temporary, `${JSON.stringify([...seen])}\n`, { mode: 0o600, flag: 'wx' });
77
+ renameSync(temporary, path);
78
+ } finally {
79
+ if (existsSync(temporary)) unlinkSync(temporary);
80
+ }
81
+ }
82
+
83
+ /** Serialize digest runs per track: cursor read → count → cursor save happens under one lock (review round 2).
84
+ * A lock whose owner pid is gone, or older than 60 s, is taken over; otherwise wait up to 10 s, then fail. */
85
+ function withDigestLock<T>(root: string, track: string, fn: () => T): T {
86
+ mkdirSync(root, { recursive: true });
87
+ const path = join(root, `${track}-digest.lock`);
88
+ const deadline = Date.now() + 10_000;
89
+ for (;;) {
90
+ try {
91
+ const fd = openSync(path, 'wx', 0o600);
92
+ writeFileSync(fd, String(process.pid));
93
+ closeSync(fd);
94
+ break;
95
+ } catch (error) {
96
+ if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
97
+ let stale = false;
98
+ try {
99
+ const owner = Number(readFileSync(path, 'utf8').trim());
100
+ const age = Date.now() - statSync(path).mtimeMs;
101
+ if (age > 60_000) stale = true;
102
+ else if (Number.isInteger(owner) && owner > 0) {
103
+ try { process.kill(owner, 0); } catch (e) { if ((e as NodeJS.ErrnoException).code === 'ESRCH') stale = true; }
104
+ }
105
+ } catch { stale = false; }
106
+ if (stale) { try { unlinkSync(path); } catch { /* another run took it */ } continue; }
107
+ if (Date.now() > deadline) throw new Error('다른 요약이 같은 자리에서 도는 중이다 — 잠시 뒤 다시');
108
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
109
+ }
110
+ }
111
+ try { return fn(); } finally { try { unlinkSync(path); } catch { /* already gone */ } }
112
+ }
113
+
114
+ function build(track: string, since: string, versions: string[], root: string, explicitSince: boolean): Digest {
115
+ const cells = new Map<string, Cell>();
116
+ const latest = new Map<string, number>();
117
+ const cellFor = (id: string): Cell => {
118
+ let cell = cells.get(id);
119
+ if (!cell) {
120
+ cell = { id, reports: 0, bySender: Object.create(null) as Record<string, number>, last: null, statusChanges: [] };
121
+ cells.set(id, cell);
122
+ }
123
+ return cell;
124
+ };
125
+ const reportsPath = join(root, `${track}-reports.jsonl`);
126
+ const cursorPath = join(root, `${track}-digest-cursor`);
127
+ const seen = readCursor(cursorPath);
128
+ const cutoff = Date.parse(since);
129
+ const now = Date.now();
130
+ let count = 0;
131
+ const counted = new Set<string>();
132
+ if (existsSync(reportsPath)) for (const [index, line] of readFileSync(reportsPath, 'utf8').split('\n').entries()) {
133
+ if (!line.trim()) continue;
134
+ let report: Report;
135
+ try {
136
+ const parsed: unknown = JSON.parse(line);
137
+ if (typeof parsed !== 'object' || parsed === null ||
138
+ !('id' in parsed) || (typeof parsed.id !== 'string' && typeof parsed.id !== 'number') ||
139
+ !('created_at' in parsed) || typeof parsed.created_at !== 'string' ||
140
+ !('login' in parsed) || typeof parsed.login !== 'string' ||
141
+ !('body' in parsed) || typeof parsed.body !== 'string') throw new Error('invalid report fields');
142
+ report = parsed as Report;
143
+ } catch (error) {
144
+ console.error(`${reportsPath}:${index + 1}: 잘못된 보고 줄 건너뜀: ${error instanceof Error ? error.message : String(error)}`);
145
+ continue;
146
+ }
147
+ const id = String(report.id);
148
+ const at = Date.parse(report.created_at);
149
+ if (!id || !Number.isFinite(at) || at < cutoff || at > now || counted.has(id) || (!explicitSince && seen.has(id))) continue;
150
+ const envelope = reportCell(report.body);
151
+ if (envelope === null) continue;
152
+ const { id: cellId, sender } = envelope;
153
+ seen.add(id);
154
+ counted.add(id);
155
+ const cell = cellFor(cellId);
156
+ cell.reports++;
157
+ cell.bySender[sender] = (cell.bySender[sender] ?? 0) + 1;
158
+ const first = report.body.split(/\r?\n/, 1)[0] ?? '';
159
+ if (at >= (latest.get(cellId) ?? -Infinity)) {
160
+ cell.last = Array.from(first).slice(0, 80).join('');
161
+ latest.set(cellId, at);
162
+ }
163
+ count++;
164
+ }
165
+ for (const version of versions) for (const entry of statusChangesSince(listChecklist(version).history, since)) {
166
+ if (Date.parse(entry.at) > now) continue;
167
+ cellFor(entry.id).statusChanges.push({ version, at: entry.at, from: entry.from, to: entry.to });
168
+ }
169
+ const output = [...cells.values()].map(({ id, reports, bySender, last, statusChanges }) => ({
170
+ id, reports, bySender, last, statusChanges: statusChanges.sort((a, b) => a.at.localeCompare(b.at)),
171
+ })).sort((a, b) => a.id.localeCompare(b.id, 'ko'));
172
+ const totals = { reports: count, cells: output.length, statusChanges: output.filter((cell) => cell.statusChanges.length > 0).length };
173
+ debug.log('coord.digest', 'built', { track, reports: count, cells: totals.cells, statusChanges: totals.statusChanges });
174
+ mkdirSync(root, { recursive: true });
175
+ saveCursor(cursorPath, seen);
176
+ return { track, since, versions, cells: output, totals };
177
+ }
178
+
179
+ function text(digest: Digest): string {
180
+ const lines = digest.cells.map((cell) => {
181
+ const status = cell.statusChanges.map(({ from, to }) => `${STATUS_ICON[String(from)] ?? String(from)}→${STATUS_ICON[String(to)] ?? String(to)}`).join(', ') || '-';
182
+ const senders = Object.entries(cell.bySender).sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => `${name} ${count}`).join(' · ');
183
+ return `${cell.id} · ${status} · 보고 ${cell.reports}${senders ? `(${senders})` : ''} · 마지막: ${cell.last ?? '-'}`;
184
+ });
185
+ lines.push(`합계 · 글 ${digest.totals.reports} · 칸 ${digest.totals.cells} · 상태 바뀐 칸 ${digest.totals.statusChanges}`);
186
+ return lines.join('\n');
187
+ }
188
+
189
+ if (import.meta.main) {
190
+ try {
191
+ const { track, hours, versions, json, explicitSince } = options(process.argv.slice(2));
192
+ const root = join(process.env.HOME ?? homedir(), '.elanous', 'coord');
193
+ const digest = withDigestLock(root, track, () => build(track, new Date(Date.now() - hours * 3_600_000).toISOString(), versions, root, explicitSince));
194
+ console.log(json ? JSON.stringify(digest) : text(digest));
195
+ } catch (error) {
196
+ console.error(error instanceof Error ? error.message : String(error));
197
+ process.exitCode = 1;
198
+ }
199
+ }
@@ -73,11 +73,62 @@ case "$FIRST" in
73
73
  exit 5 ;;
74
74
  esac
75
75
 
76
- # 봉투 v2(RFC-coordination-cost §2 · C1 · 09-30): 첫 줄에 종류 다섯 중 하나 — 막지 않고 «알린다».
77
- case "$FIRST" in
78
- *요청*|*결정*|*사고*|*보고*|*정정*) ;;
76
+ # 봉투 v2(RFC-coordination-cost §2 · C1): 신원 · (📌안내) · 시각 → 받는 이 · 종류 · 칸 · (기한).
77
+ # 필드를 «순서대로» 읽는다 — 받는 이(정본 id 또는 전원)가 끝난 «바로 다음» 필드만 종류다(칸의 «결정 D1» 같은 글로 종류를 고르지 않는다).
78
+ # 전부 경고만 — 발신은 막지 않는다(옛 형식 하위 호환).
79
+ trim() { local v="$1"; v="${v#"${v%%[![:space:]]*}"}"; printf '%s' "${v%"${v##*[![:space:]]}"}"; }
80
+ HEADER=${FIRST#"**[$ID]**"}
81
+ ENVELOPE=""
82
+ case "$HEADER" in *'→'*) case "${HEADER%%→*}" in *' · '*) ;; *) ENVELOPE=$(trim "${HEADER#*→}") ;; esac ;; esac # → 는 시각 바로 뒤일 때만 봉투
83
+ SEP=$'\x1f'
84
+ FIELDS=()
85
+ [ -n "$ENVELOPE" ] && IFS="$SEP" read -r -a FIELDS <<< "${ENVELOPE// · /$SEP}"
86
+ N=${#FIELDS[@]}
87
+ i=0; RECIPIENT_COUNT=0; RECIPIENT_ALL=0
88
+ while [ "$i" -lt "$N" ]; do
89
+ F=$(trim "${FIELDS[$i]}")
90
+ F=${F%% — *} # 옛 머리 «→ OP — 무엇» 의 받는 이 부분만
91
+ TOKS=${F//,/ }
92
+ OK=1; C=0
93
+ for T in $TOKS; do
94
+ if [ "$T" = "전원" ]; then C=$((C + 1)); RECIPIENT_ALL=1
95
+ else case " $IDS " in *" $T "*) C=$((C + 1)) ;; *) OK=0; break ;; esac
96
+ fi
97
+ done
98
+ [ "$OK" -eq 1 ] && [ "$C" -gt 0 ] || break
99
+ RECIPIENT_COUNT=$((RECIPIENT_COUNT + C)); i=$((i + 1))
100
+ case "${FIELDS[$((i - 1))]}" in *' — '*) break ;; esac
101
+ done
102
+ KIND_FIELD=""; SLOT=""; DEADLINE_FIELD=""
103
+ if [ "$i" -lt "$N" ]; then KIND_FIELD=$(trim "${FIELDS[$i]}"); fi
104
+ if [ $((i + 1)) -lt "$N" ]; then SLOT=$(trim "${FIELDS[$((i + 1))]}"); fi
105
+ j=$((i + 1))
106
+ while [ "$j" -lt "$N" ]; do
107
+ F=$(trim "${FIELDS[$j]}")
108
+ case "$F" in '기한 '[![:space:]]*) DEADLINE_FIELD=$F ;; esac
109
+ j=$((j + 1))
110
+ done
111
+ case "$KIND_FIELD" in
112
+ 요청|결정|사고|보고|정정) ;;
79
113
  *) echo "⚠️ 첫 줄에 글 종류(요청·결정·사고·보고·정정)가 없다 — 받는 쪽 감시자가 즉시/모아 읽기를 못 가른다(발신은 한다)" >&2 ;;
80
114
  esac
115
+ if [ "$RECIPIENT_COUNT" -eq 0 ]; then
116
+ echo "⚠️ 받는 이가 없다(→ TC · UX 꼴)" >&2
117
+ fi
118
+ case "$KIND_FIELD" in
119
+ 요청|결정|사고|보고|정정)
120
+ case "$SLOT" in ''|기한|'기한 '*) echo "⚠️ 첫 줄에 칸(확인표·결정 id 또는 -)이 없다" >&2 ;; esac ;;
121
+ esac
122
+ case "$KIND_FIELD" in
123
+ 요청|결정) [ -n "$DEADLINE_FIELD" ] || echo "⚠️ 요청·결정에는 기한을 적는다" >&2 ;;
124
+ esac
125
+ if [ "$RECIPIENT_ALL" -eq 1 ]; then
126
+ case "$KIND_FIELD" in
127
+ 사고|정정) ;;
128
+ *) CEO_MARK=$(printf '\360\237\221\221') # the CEO mark, built from bytes so the public export carries no literal
129
+ case "$FIRST" in *"$CEO_MARK"*) ;; *) echo "⚠️ «전원»은 사고·정정·대표 지시에만" >&2 ;; esac ;;
130
+ esac
131
+ fi
81
132
 
82
133
  TS=$(TZ=Asia/Seoul date '+%Y-%m-%d %H:%M KST')
83
134
  TMP=$(mktemp "${TMPDIR:-/tmp}/coord-post.XXXXXX") || { echo "⛔ mktemp 실패 — 발신하지 않는다" >&2; exit 7; }
@@ -167,3 +218,9 @@ if [ "$RC" -ne 0 ]; then
167
218
  exit "$RC"
168
219
  fi
169
220
  echo "[coord-post] 발신 성공$VIA · 채널 #$PR · 신원 [$ID] · 시각 $TS" >&2
221
+ # Only a successfully sent post is recorded; never pass the body or later lines to the ledger.
222
+ POST_URL=$(printf '%s\n' "$POST_OUTPUT" | grep -oE 'https://github\.com/[^[:space:]]+/issues/[0-9]+#issuecomment-[0-9]+|https://github\.com/[^[:space:]]+/pull/[0-9]+#issuecomment-[0-9]+' | head -1 || true)
223
+ if ! bun bin/elanous.mjs coord event record --seat "$ID" --header "$(head -1 "$TMP")" --url "$POST_URL" >/dev/null 2>&1; then
224
+ echo '⚠️ 맥락 원장 기록 실패(발신은 됐다)' >&2
225
+ fi
226
+ exit 0
@@ -3,6 +3,7 @@ param(
3
3
  [string] $Prefix,
4
4
  [string] $Source,
5
5
  [switch] $NoModifyPath,
6
+ [switch] $NoBootstrapBun,
6
7
  [switch] $Help,
7
8
  [Parameter(ValueFromRemainingArguments = $true)]
8
9
  [string[]] $RemainingArgs
@@ -20,8 +21,8 @@ $ErrorActionPreference = 'Stop'
20
21
 
21
22
  function Show-Usage {
22
23
  @'
23
- Usage: powershell -File scripts/install.ps1 [-Prefix PATH] [-Source PATH.tgz|URL] [-NoModifyPath] [-Help]
24
- powershell -File scripts/install.ps1 [--prefix PATH] [--source PATH.tgz|URL] [--no-modify-path] [--help]
24
+ Usage: powershell -File scripts/install.ps1 [-Prefix PATH] [-Source PATH.tgz|URL] [-NoModifyPath] [-NoBootstrapBun] [-Help]
25
+ powershell -File scripts/install.ps1 [--prefix PATH] [--source PATH.tgz|URL] [--no-modify-path] [--no-bootstrap-bun] [--help]
25
26
 
26
27
  Install elanous into a versioned layout.
27
28
  Prefix / --prefix installation root (default: $ELANOUS_INSTALL_PREFIX or %LOCALAPPDATA%\elanous)
@@ -29,6 +30,7 @@ Install elanous into a versioned layout.
29
30
  Source / --source install a package tarball (local path or http(s) URL; default: $ELANOUS_INSTALL_SOURCE,
30
31
  else pack the checkout or fetch the verified release when standalone)
31
32
  NoModifyPath / --no-modify-path do not append the elanous PATH block to the PowerShell profile
33
+ NoBootstrapBun / --no-bootstrap-bun fail instead of installing bun when missing
32
34
  Help / --help show this help
33
35
  '@ | Write-Output
34
36
  }
@@ -38,11 +40,15 @@ function Fail([string] $Message, [int] $Code) {
38
40
  exit $Code
39
41
  }
40
42
 
43
+ # PowerShell 5.1 parses BOM-less scripts as ANSI. Keep translated messages ASCII in source and decode at runtime.
44
+ function Say([string] $English, [string] $Korean) {
45
+ if ($installLang -eq 'ko') { return [regex]::Unescape($Korean) }
46
+ return $English
47
+ }
48
+
41
49
  function Get-RequiredCommands([string] $InstallerPath) {
42
- # install.sh is the single source of the required-command list; a non-checkout run (e.g. `irm ... | iex`) has no
43
- # install.sh beside it and falls back to the same two names.
44
- $fallbackCommands = 'git', 'bun'
45
- if (-not $InstallerPath -or -not (Test-Path -LiteralPath $InstallerPath)) { return $fallbackCommands }
50
+ # A checkout follows install.sh; standalone release installs need bun but not git.
51
+ if (-not $InstallerPath -or -not (Test-Path -LiteralPath $InstallerPath)) { return @('bun') }
46
52
  $content = Get-Content -LiteralPath $InstallerPath -Raw
47
53
  $match = [regex]::Match($content, 'REQUIRED_COMMANDS=\(([^)]*)\)')
48
54
  if (-not $match.Success) { Fail "could not read REQUIRED_COMMANDS from $InstallerPath" 1 }
@@ -87,11 +93,14 @@ for ($index = 0; $index -lt $RemainingArgs.Count; $index++) {
87
93
  $Source = $RemainingArgs[++$index]
88
94
  }
89
95
  '--no-modify-path' { $NoModifyPath = $true }
96
+ '--no-bootstrap-bun' { $NoBootstrapBun = $true }
90
97
  default { Fail "unknown argument: $($RemainingArgs[$index])" 2 }
91
98
  }
92
99
  }
93
100
 
94
101
  if ($Help) { Show-Usage; exit 0 }
102
+ $installLang = if ($env:ELANOUS_INSTALL_LANG) { $env:ELANOUS_INSTALL_LANG } else { [Globalization.CultureInfo]::CurrentUICulture.Name }
103
+ $installLang = if ($installLang -match '^ko(?:-|$)') { 'ko' } else { 'en' }
95
104
  if (-not $Prefix) {
96
105
  $dataHome = if ($env:LOCALAPPDATA) { $env:LOCALAPPDATA } else { Join-Path $HOME 'AppData\Local' }
97
106
  $Prefix = if ($env:ELANOUS_INSTALL_PREFIX) { $env:ELANOUS_INSTALL_PREFIX } else { Join-Path $dataHome 'elanous' }
@@ -100,15 +109,46 @@ if (-not $Source -and $env:ELANOUS_INSTALL_SOURCE) { $Source = $env:ELANOUS_INST
100
109
 
101
110
  $scriptDir = if ($PSCommandPath) { Split-Path -Parent $PSCommandPath } else { $null }
102
111
  $repoRoot = if ($scriptDir) { [IO.Path]::GetFullPath((Join-Path $scriptDir '..')) } else { $null }
103
- $requiredCommands = Get-RequiredCommands $(if ($scriptDir) { Join-Path $scriptDir 'install.sh' } else { $null })
112
+ $installerPath = if ($scriptDir) { Join-Path $scriptDir 'install.sh' } else { $null }
113
+ $requiredCommands = Get-RequiredCommands $installerPath
114
+ $missingGit = -not (Get-Command git -ErrorAction SilentlyContinue)
115
+ $bootstrappedBun = $false
116
+ $bunBin = Join-Path $HOME '.bun\bin'
117
+ if (-not (Get-Command bun -ErrorAction SilentlyContinue) -and (Test-Path -LiteralPath (Join-Path $bunBin 'bun.exe') -PathType Leaf)) {
118
+ $env:PATH = $bunBin + [IO.Path]::PathSeparator + $env:PATH
119
+ }
120
+ # Preserve checkout required-command enforcement, including git, before changing any installation files.
104
121
  foreach ($command in $requiredCommands) {
122
+ if ($command -eq 'bun') { continue }
105
123
  if (-not (Get-Command $command -ErrorAction SilentlyContinue)) { Fail "required command missing: $command" 127 }
106
124
  }
125
+ if (-not (Get-Command bun -ErrorAction SilentlyContinue)) {
126
+ $bunCommand = 'irm https://bun.sh/install.ps1 | iex'
127
+ if ($NoBootstrapBun) { Fail "$(Say 'bun is missing; run:' 'bun \uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uba3c\uc800 \uc2e4\ud589\ud558\uc138\uc694:') $bunCommand" 127 }
128
+ # Bun's official install.ps1 resets the session PATH to the user PATH only (System32 and tar vanish) - keep ours.
129
+ $pathBeforeBun = $env:PATH
130
+ try {
131
+ $bootstrapScript = if ($env:ELANOUS_INSTALL_BUN_SCRIPT) {
132
+ Read-Text $env:ELANOUS_INSTALL_BUN_SCRIPT
133
+ } else {
134
+ (Invoke-WebRequest -UseBasicParsing -Uri 'https://bun.sh/install.ps1').Content
135
+ }
136
+ if (-not $bootstrapScript) { throw 'empty installer response' }
137
+ & ([scriptblock]::Create([string]$bootstrapScript))
138
+ if (-not $?) { throw 'installer returned failure' }
139
+ if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { throw "installer exited with code $LASTEXITCODE" }
140
+ } catch { Fail "$(Say 'bun bootstrap failed:' 'bun \uc124\uce58 \uc2e4\ud328:') $($_.Exception.Message)" 1 }
141
+ $env:PATH = $bunBin + [IO.Path]::PathSeparator + $pathBeforeBun
142
+ if (-not (Get-Command bun -ErrorAction SilentlyContinue)) {
143
+ Fail (Say 'bun bootstrap failed: bun not found after installation' 'bun \uc124\uce58 \uc2e4\ud328: \uc124\uce58 \ub4a4\uc5d0\ub3c4 bun \uc744 \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4') 1
144
+ }
145
+ $bootstrappedBun = $true
146
+ }
107
147
  $bunPath = (Get-Command bun).Source
108
148
 
109
149
  # A standalone installer fetches a verified release; explicit sources and checkouts keep their existing paths.
110
150
  $isCheckout = $false
111
- if ($repoRoot -and (Test-Path -LiteralPath (Join-Path $repoRoot 'package.json'))) {
151
+ if ($installerPath -and (Test-Path -LiteralPath $installerPath) -and $repoRoot -and (Test-Path -LiteralPath (Join-Path $repoRoot 'package.json'))) {
112
152
  $isCheckout = (Read-Text (Join-Path $repoRoot 'package.json')) -match '"name":\s*"elanous"'
113
153
  }
114
154
  $releaseDirectory = $null
@@ -247,7 +287,26 @@ try {
247
287
  if (-not $NoModifyPath -and -not (Read-Text $profilePath).Contains($markerStart)) {
248
288
  Add-Content -LiteralPath $profilePath -Value ("`r`n$markerStart`r`n$pathLine`r`n$markerEnd")
249
289
  }
250
- Write-Output "Installed elanous $version at $shimPath"
290
+ # The default execution policy (Restricted) skips $PROFILE in a new window - the user PATH is what a fresh shell sees.
291
+ if (-not $NoModifyPath -and -not $env:ELANOUS_INSTALL_NO_USER_PATH) {
292
+ $binDirectory = Join-Path $Prefix 'bin'
293
+ $userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
294
+ $userEntries = @(($userPath -split ';') | Where-Object { $_ })
295
+ if ($userEntries -notcontains $binDirectory) {
296
+ [Environment]::SetEnvironmentVariable('Path', (@($binDirectory) + $userEntries) -join ';', 'User')
297
+ }
298
+ }
299
+ $installedMessage = Say 'Installed elanous {0} at {1}' 'elanous {0} \uc744(\ub97c) \uc124\uce58\ud588\uc2b5\ub2c8\ub2e4: {1}'
300
+ Write-Output ($installedMessage -f $version, $shimPath)
301
+ $bootstrapped = if ($bootstrappedBun) { 'bun' } else { 'none' }
302
+ $missing = if ($missingGit -and -not $isCheckout) { 'git(next step)' } else { 'none' }
303
+ Write-Output "$(Say 'Install summary' '\uc124\uce58 \uc694\uc57d'): bootstrapped: $bootstrapped; missing: $missing"
304
+ Write-Output (Say 'Next:' '\ub2e4\uc74c:')
305
+ if (-not $NoModifyPath) { Write-Output (Say ' Open a new PowerShell window to use elanous on PATH.' ' elanous \uc744 PATH \uc5d0\uc11c \uc4f0\ub824\uba74 \uc0c8 PowerShell \ucc3d\uc744 \uc5ec\uc138\uc694.') }
306
+ else { Write-Output "$(Say ' Run:' ' \uc2e4\ud589:') $shimPath" }
307
+ if ($missing -ne 'none') {
308
+ Write-Output " winget install --id Git.Git -e # $(Say 'the harness uses git' '\ud558\ub2c8\uc2a4\uac00 git \uc744 \uc0ac\uc6a9\ud569\ub2c8\ub2e4')"
309
+ }
251
310
  } finally {
252
311
  Remove-Item -LiteralPath $tempDirectory -Recurse -Force -ErrorAction SilentlyContinue
253
312
  }