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,14 @@
1
+ /** U1a — how long an empty-input Ctrl+C stays armed (and its hint stays on screen). */
2
+ export const CTRL_C_ARM_MS = 2_000;
3
+
4
+ export function resolveCtrlCInInput({ text, now, armedAt }: {
5
+ text: string;
6
+ now: number;
7
+ armedAt: number | null;
8
+ }): { action: 'clear-input' | 'arm-exit' | 'exit'; armedAt: number | null; hint: string | null } {
9
+ if (text.length > 0) return { action: 'clear-input', armedAt: null, hint: null };
10
+ if (armedAt !== null && now >= armedAt && now - armedAt <= CTRL_C_ARM_MS) {
11
+ return { action: 'exit', armedAt: null, hint: null };
12
+ }
13
+ return { action: 'arm-exit', armedAt: now, hint: '한 번 더 누르면 나갑니다 · /quit' };
14
+ }
package/src/chat/index.ts CHANGED
@@ -19,6 +19,7 @@ import type { ModalSurface } from '../display/modal-stack.js';
19
19
  import type { ContentBlock } from '../llm.js';
20
20
  import { debug } from '../debug/log.js';
21
21
  import { matchTextInputGlobalAction } from './global-actions.js';
22
+ import { CTRL_C_ARM_MS, resolveCtrlCInInput } from './ctrl-c-in-input.js';
22
23
  import { isTextInputQuitChord } from './input-quit-chord.js';
23
24
  // P5 — leader-chord pass-through from input focus. chat.ts swallows
24
25
  // plain letters into its textarea buffer, so the resolver never sees
@@ -896,6 +897,10 @@ export async function textInput(opts: {
896
897
  let lastReportedLines = -1;
897
898
  let lineIdx = 0; // current line
898
899
  let colIdx = lines[0]!.length; // cursor at end of initial text
900
+ let ctrlCArmedAt: number | null = null;
901
+ let ctrlCHintTimer: ReturnType<typeof setTimeout> | null = null;
902
+ let ctrlCHint: string | null = null;
903
+ let ctrlCHintPainted = false;
899
904
 
900
905
  // KX2 — picker selection / navigation / async fetch caches live in
901
906
  // the shared state machine (chat-picker-state.ts). textInput keeps
@@ -1173,6 +1178,14 @@ export async function textInput(opts: {
1173
1178
  }
1174
1179
  prevDrawnLines = visibleLines;
1175
1180
 
1181
+ if (ctrlCHint) {
1182
+ writeTerminal(ansi.moveTo(row + 2, col) + '\x1b[2K' + C.muted(truncate(ctrlCHint, width)));
1183
+ ctrlCHintPainted = true;
1184
+ } else if (ctrlCHintPainted) {
1185
+ writeTerminal(ansi.moveTo(row + 2, col) + '\x1b[2K');
1186
+ ctrlCHintPainted = false;
1187
+ }
1188
+
1176
1189
  // Cursor — its buffer line is `lineIdx`; its screen row is the
1177
1190
  // bottom-anchored offset within the visible window. Critically,
1178
1191
  // `textBeforeCursor` reads from `lines[lineIdx]` (the buffer), not
@@ -1468,6 +1481,11 @@ export async function textInput(opts: {
1468
1481
  process.exit(0);
1469
1482
  }
1470
1483
 
1484
+ if (!(key.ctrl && (key.name === 'c' || key.name === 'ㅊ') && !key.shift && !key.alt)) {
1485
+ ctrlCArmedAt = null;
1486
+ ctrlCHint = null;
1487
+ }
1488
+
1471
1489
  // A bracketed-paste envelope is one atomic event. Handle it before
1472
1490
  // host routing so embedded newlines cannot reach a submit handler.
1473
1491
  if (key.name === 'paste') {
@@ -1571,6 +1589,40 @@ export async function textInput(opts: {
1571
1589
  continue;
1572
1590
  }
1573
1591
 
1592
+ if (key.ctrl && (key.name === 'c' || key.name === 'ㅊ') && !key.shift && !key.alt) {
1593
+ const text = lines.join('\n');
1594
+ const result = resolveCtrlCInInput({ text, now: Date.now(), armedAt: ctrlCArmedAt });
1595
+ debug.log('chat.input', 'ctrl-c', { action: result.action, length: text.length });
1596
+ ctrlCArmedAt = result.armedAt;
1597
+ ctrlCHint = result.hint;
1598
+ if (ctrlCHintTimer) clearTimeout(ctrlCHintTimer);
1599
+ ctrlCHintTimer = null;
1600
+ if (result.action === 'arm-exit') {
1601
+ // U1a — the hint promises «one more press exits» only while that is true.
1602
+ const armedAt = result.armedAt;
1603
+ ctrlCHintTimer = setTimeout(() => {
1604
+ ctrlCHintTimer = null;
1605
+ if (ctrlCArmedAt !== armedAt) return;
1606
+ ctrlCArmedAt = null;
1607
+ ctrlCHint = null;
1608
+ drawAll();
1609
+ }, CTRL_C_ARM_MS + 50);
1610
+ ctrlCHintTimer.unref?.();
1611
+ }
1612
+ if (result.action === 'exit') return submitBufferResult('/quit');
1613
+ if (result.action === 'clear-input') {
1614
+ lines = [''];
1615
+ lineIdx = 0;
1616
+ colIdx = 0;
1617
+ historyIdx = -1;
1618
+ savedCurrentInput = '';
1619
+ onUserBufferEdit();
1620
+ await picker.refresh(bufView());
1621
+ }
1622
+ drawAll();
1623
+ continue;
1624
+ }
1625
+
1574
1626
  const globalDispatcher = dispatchGlobalAction;
1575
1627
  const globalAction = globalDispatcher
1576
1628
  ? matchTextInputGlobalAction(key, {
@@ -33,6 +33,7 @@ import type {
33
33
  } from '../index.js';
34
34
  import { filterSlashCommands } from '../index.js';
35
35
  import { filterInputMatches } from '../../input/query-match.js';
36
+ import { readTuiSlashAudience, slashMaturity, slashVisibleFor } from '../../maturity/tui-slash-maturity.js';
36
37
 
37
38
  /** Read-only snapshot of the textInput buffer handed to state on
38
39
  * every public call. The state machine never mutates it; it only
@@ -234,8 +235,11 @@ export function createPickerState(deps: PickerDeps): PickerState {
234
235
  let currentAtItems: AtCandidate[] = [];
235
236
  let currentSkillItems: SkillCandidate[] = [];
236
237
 
237
- const filteredSlash = (buf: PickerBufferView): SlashCommand[] =>
238
- filterSlashCommands((buf.lines[0] ?? '').slice(1), deps.commands);
238
+ const filteredSlash = (buf: PickerBufferView): SlashCommand[] => {
239
+ const { role, showBeta } = readTuiSlashAudience();
240
+ return filterSlashCommands((buf.lines[0] ?? '').slice(1), deps.commands)
241
+ .filter((cmd) => (role === 'owner' && slashMaturity(cmd.name) === undefined) || slashVisibleFor(cmd.name, role, { showBeta }));
242
+ };
239
243
 
240
244
  const refreshArgs = async (buf: PickerBufferView): Promise<ArgSuggestion[]> => {
241
245
  const ctx = parseArgContext(buf, deps.commands);
@@ -3,6 +3,7 @@ import { resolve } from 'node:path';
3
3
  import { getUserConfig, reloadUserConfig } from '../user-config.js';
4
4
  import { handleOnboardingRefusal, needsOnboarding, runOnboarding } from '../onboarding.js';
5
5
  import { runTurn, ensureCliSession, sessionBudget } from '../session/chat.js';
6
+ import { suggestProjectForFolder } from '../project/project-store.js';
6
7
  import { NoLlmProviderAvailableError, noProviderAvailableMessage } from '../llm.js';
7
8
  import { resolveSessionId, getActiveSessionId, setActiveSessionId } from '../session/index.js';
8
9
  import { getHarnessSpace } from '../harness/harness-space.js';
@@ -433,6 +434,12 @@ export async function runChatTurnCli(opts: {
433
434
  }
434
435
  const session = ensureCliSession(opts.cfg, resolvedSessionIdHint);
435
436
  setActiveSessionId(session.id);
437
+ if (!opts.json && (!resolvedSessionIdHint || session.id !== resolvedSessionIdHint)) {
438
+ try {
439
+ const project = suggestProjectForFolder(process.cwd());
440
+ if (project) console.log(`Project suggestion: ${project.name} (${project.id})`);
441
+ } catch { /* suggestions must not interrupt a CLI conversation */ }
442
+ }
436
443
  const harnessSpace = getHarnessSpace();
437
444
  // Tag the debug log with this session id so every event in this
438
445
  // process attributes correctly. enrichDebugRecord picks up the
@@ -1,4 +1,5 @@
1
1
  import type { Command } from 'commander';
2
+ import { projectDecisionsToLinear } from '../decisions/decision-linear-projection.js';
2
3
  import { DecisionLedger, importDecisionMarkdown, type DecisionCategory, type DecisionLedgerOptions, type DecisionOption, type DecisionTrack, type DecisionEntry } from '../decisions/decision-ledger.js';
3
4
 
4
5
  const repeat = (value: string, values: string[]) => [...values, value];
@@ -49,22 +50,31 @@ export function registerDecisionsCommands(program: Command, config: DecisionLedg
49
50
  const root = program.command('decisions').description('대표 결정 원장 · 로컬 전용');
50
51
  const emit = (value: unknown, json?: boolean, text?: string) => out.log(json ? JSON.stringify(value) : text ?? JSON.stringify(value));
51
52
  const fail = (action: () => void) => { try { action(); } catch (e) { throw new Error(`decisions: ${e instanceof Error ? e.message : String(e)}`); } };
53
+ root.command('linear-sync').description('열린 결정을 COO Linear 프로젝트에 투영하고 닫힌 결정을 동기화한다')
54
+ .option('--dry-run').option('--json')
55
+ .action(async (o: { dryRun?: boolean; json?: boolean }) => {
56
+ const result = await projectDecisionsToLinear({ stateDir: config.stateDir, dryRun: o.dryRun });
57
+ emit(result, o.json, result.reason ?? `생성 ${result.created} · 닫음 ${result.closed} · 건너뜀 ${result.skipped} · 실패 ${result.failed}${result.plan?.length ? `\n${result.plan.map(p => `${p.action} ${p.decisionId}${p.issue ? ` ${p.issue}` : ''}`).join('\n')}` : ''}`);
58
+ });
52
59
  root.command('raise').description('SCQA·선택지와 권고로 결정 항목을 올린다')
53
60
  .requiredOption('--title <text>').requiredOption('--category <category>')
54
61
  .requiredOption('--s <text>').requiredOption('--c <text>').option('--q <text>').option('--a <text>')
55
62
  .requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
56
63
  .option('--recommend <key>').option('--why <text>').option('--skip-recommend <reason>')
57
64
  .option('--track <track>').option('--agent <agent>').option('--session <id>')
65
+ .option('--resume-question <qid>').option('--run <runId>')
58
66
  .option('--ref <url>', '참조 (반복)', repeat, [] as string[])
59
67
  .option('--due <when>', '기한 — UTC ISO(2026-10-04T09:00:00Z) 또는 +Nh(지금부터 N시간) · 기한 2시간 전 텔레그램·디스코드로 다시 알린다')
60
68
  .option('--json')
61
- .action((o: { due?: string; title: string; category: DecisionCategory; s: string; c: string; q?: string; a?: string; option: string[]; recommend?: string; why?: string; skipRecommend?: string; track?: DecisionTrack; agent?: string; session?: string; ref: string[]; json?: boolean }) => fail(() => {
69
+ .action((o: { due?: string; title: string; category: DecisionCategory; s: string; c: string; q?: string; a?: string; option: string[]; recommend?: string; why?: string; skipRecommend?: string; track?: DecisionTrack; agent?: string; session?: string; resumeQuestion?: string; run?: string; ref: string[]; json?: boolean }) => fail(() => {
70
+ if (o.run !== undefined && o.resumeQuestion === undefined) throw new Error('--run requires --resume-question');
62
71
  if (o.skipRecommend !== undefined && (o.recommend !== undefined || o.why !== undefined)) throw new Error('choose recommendation or skip, not both');
63
72
  if (o.skipRecommend === undefined && (!o.recommend || !o.why)) throw new Error('--recommend and --why required, or --skip-recommend <reason>');
64
73
  const options: DecisionOption[] = o.option.map(parseOption);
65
74
  const entry = new DecisionLedger(config).raise({ title: o.title, category: o.category, scqa: { s: o.s, c: o.c, ...(o.q ? { q: o.q } : {}), ...(o.a ? { a: o.a } : {}) }, options,
66
75
  recommendation: o.skipRecommend !== undefined ? { skipped: true, reason: o.skipRecommend } : { option: o.recommend!, why: o.why! },
67
76
  raisedBy: { agent: agent(o.agent), ...(o.track ? { track: o.track } : {}), ...(o.session ? { session: o.session } : {}) }, ...(o.ref.length ? { refs: o.ref } : {}),
77
+ ...(o.resumeQuestion !== undefined ? { resume: { questionId: o.resumeQuestion, ...(o.run !== undefined ? { runId: o.run } : {}) } } : {}),
68
78
  ...(o.due ? { dueAt: dueAt(o.due) } : {}) });
69
79
  emit(entry, o.json, `올림: ${formatDecisionRow(entry)}`);
70
80
  }));
@@ -83,8 +93,20 @@ export function registerDecisionsCommands(program: Command, config: DecisionLedg
83
93
  if (o.auto && !o.delegation?.trim()) throw new Error('--auto requires --delegation');
84
94
  if (!o.auto && (o.delegation || o.track || o.agent)) throw new Error('delegation/track/agent require --auto');
85
95
  const by = o.auto ? { kind: 'auto' as const, agent: agent(o.agent), delegation: o.delegation!, ...(o.track ? { track: o.track } : {}) } : { kind: 'human' as const };
86
- const entry = new DecisionLedger(config).decide(id, choice, by, o.note);
87
- emit(entry, o.json, `결정: ${formatDecisionDetail(entry)}`);
96
+ const { entry, delivery } = new DecisionLedger(config).decideWithDelivery(id, choice, by, o.note);
97
+ if (delivery && !delivery.ok) {
98
+ // The decision is recorded; only the answer to the waiting run failed — say so and how to retry, never «success».
99
+ const hint = `⚠ 답 전달 실패(${delivery.questionId} · ${delivery.reason}) — 결정은 기록됐다 · 다시: elanous decisions retry-answer ${entry.id}`;
100
+ emit({ ...entry, delivery }, o.json, `결정: ${formatDecisionDetail(entry)}\n${hint}`);
101
+ process.exitCode = 3;
102
+ return;
103
+ }
104
+ emit(delivery ? { ...entry, delivery } : entry, o.json, `결정: ${formatDecisionDetail(entry)}`);
105
+ }));
106
+ root.command('retry-answer <id>').description('기록된 결정의 대기 질문 답 전달만 재시도한다').option('--json')
107
+ .action((id: string, o: { json?: boolean }) => fail(() => {
108
+ const entry = new DecisionLedger(config).retryAnswer(id);
109
+ emit(entry, o.json, `답 전달: ${entry.id} → ${entry.resume?.questionId}`);
88
110
  }));
89
111
  root.command('add-options <id>').description('선택지가 미기재된 과거 항목에 확인된 선택지를 추가')
90
112
  .requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
@@ -0,0 +1,68 @@
1
+ import { readFileSync, statSync } from 'node:fs';
2
+ import { debug } from '../debug/log.js';
3
+
4
+ export interface DoctorBundleUploadOptions {
5
+ path: string;
6
+ files: readonly string[];
7
+ uploadUrl?: string;
8
+ confirm: (message: string) => Promise<boolean>;
9
+ fetch: typeof globalThis.fetch;
10
+ yes?: boolean;
11
+ /** Present the inventory before either consent or transport; JSON callers can use stderr. */
12
+ announce?: (contents: string) => void;
13
+ /** Pass the size returned by buildDoctorBundle, if available. */
14
+ bytes?: number;
15
+ }
16
+
17
+ export interface DoctorBundleUploadResult {
18
+ path: string;
19
+ uploaded: boolean;
20
+ code?: string;
21
+ reason?: string;
22
+ exitCode: number;
23
+ message: string;
24
+ }
25
+
26
+ /** The bundle is local until both an HTTPS destination and affirmative consent exist. */
27
+ export async function uploadDoctorBundle({ path, files, uploadUrl, confirm, fetch, yes = false, bytes, announce = console.log }: DoctorBundleUploadOptions): Promise<DoctorBundleUploadResult> {
28
+ const size = bytes ?? (() => { try { return statSync(path).size; } catch { return undefined; } })();
29
+ const skipped = (reason: string, message: string): DoctorBundleUploadResult => {
30
+ debug.log('doctor.bundle', 'upload-skipped', { bytes: size, reason });
31
+ return { path, uploaded: false, reason, exitCode: 0, message };
32
+ };
33
+ if (!uploadUrl?.trim()) return skipped('no-recipient', `수신처가 아직 정해지지 않았습니다 — 파일을 지원에 직접 보내 주세요: ${path}`);
34
+ let destination: URL;
35
+ try {
36
+ destination = new URL(uploadUrl.trim());
37
+ if (destination.protocol !== 'https:' || !destination.hostname || destination.username || destination.password) throw new Error('invalid destination');
38
+ } catch {
39
+ return skipped('invalid-recipient', `수신처가 유효한 HTTPS 주소가 아닙니다 — 파일을 지원에 직접 보내 주세요: ${path}`);
40
+ }
41
+ const contents = `내용 목록 (${size === undefined ? '크기 확인 불가' : `${size} bytes`}):\n${files.map((file) => ` ${file}`).join('\n')}`;
42
+ announce(contents);
43
+ if (!yes) {
44
+ let agreed = false;
45
+ try { agreed = await confirm('올릴까요? (y/N) '); } catch { /* An unavailable prompt is a refusal. */ }
46
+ if (!agreed) {
47
+ debug.log('doctor.bundle', 'declined', { bytes: size, reason: 'consent-declined' });
48
+ return { path, uploaded: false, reason: 'consent-declined', exitCode: 0, message: `업로드하지 않았습니다: ${path}` };
49
+ }
50
+ }
51
+ const failed = (reason: string): DoctorBundleUploadResult => {
52
+ debug.log('doctor.bundle', 'upload-failed', { bytes: size, reason });
53
+ return { path, uploaded: false, reason, exitCode: 1, message: `업로드 실패 (${reason}) — 파일: ${path} — 다시 시도해 주세요.` };
54
+ };
55
+ try {
56
+ const response = await fetch(destination.href, {
57
+ method: 'POST', headers: { 'Content-Type': 'application/gzip' }, body: readFileSync(path), redirect: 'manual',
58
+ });
59
+ if (!response.ok) return failed(`HTTP ${response.status}`);
60
+ const data: unknown = await response.json();
61
+ const code = data && typeof data === 'object' && !Array.isArray(data) ? (data as { code?: unknown }).code : undefined;
62
+ if (typeof code !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(code)) return failed('진단 코드 응답이 올바르지 않습니다');
63
+ debug.log('doctor.bundle', 'uploaded', { bytes: size, reason: 'success' });
64
+ return { path, uploaded: true, code, exitCode: 0, message: `진단 코드: ${code} — 이 코드만 지원에 알려 주세요` };
65
+ } catch {
66
+ return failed('네트워크 또는 파일 읽기 오류');
67
+ }
68
+ }
@@ -38,6 +38,7 @@ import { describeModes, formatModeLine, type DoctorMode } from './doctor-modes.j
38
38
  import { formatDoctorLocalLlm, probeDoctorLocalLlm, type DoctorLocalLlm } from './doctor-local-llm.js';
39
39
  import type { LlmInventory } from '../llm/local-manager/types.js';
40
40
  import { buildDoctorBundle } from './doctor-bundle.js';
41
+ import { uploadDoctorBundle } from './doctor-bundle-upload.js';
41
42
 
42
43
  function defaultCheckPythonEnv(): { status: 'ok' | 'fixable' | 'manual'; evidence: string; remedy?: string } {
43
44
  const c = checkPythonEnv(false);
@@ -193,6 +194,9 @@ export interface DoctorCliDeps extends DoctorOptions, Pick<DoctorFixDeps, 'home'
193
194
  adviceInteractive?: boolean;
194
195
  /** Local-manager inventory seam; default calls getInventory(). */
195
196
  localLlmInventory?: () => Promise<LlmInventory>;
197
+ /** Inject the upload transport and consent for isolated CLI tests. */
198
+ uploadFetch?: typeof globalThis.fetch;
199
+ confirmBundleUpload?: (message: string) => Promise<boolean>;
196
200
  }
197
201
 
198
202
  const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
@@ -1217,15 +1221,21 @@ export function registerDoctorCommand(program: Command, deps: DoctorCliDeps = {}
1217
1221
  .option('--json', 'structured output')
1218
1222
  .option('--credentials', 'show per-credential details in human-readable output')
1219
1223
  .option('--bundle', 'write a redacted diagnostics archive for support')
1224
+ .option('--upload', 'send the bundle to diagnostics.uploadUrl after consent (requires --bundle)')
1220
1225
  .option('--out <dir>', 'output directory for --bundle')
1221
1226
  .option('--fix', 'show repairs; git installation asks on a TTY (other repairs require --yes)')
1222
1227
  .option('--advise', 'ask the configured LLM to rank catalog repairs (read-only advice)')
1223
- .option('--yes', 'apply planned doctor repairs (requires --fix)')
1228
+ .option('--yes', 'apply planned doctor repairs (--fix), or confirm bundle upload (--bundle --upload)')
1224
1229
  .option('--sudo', 'also run the planned sudo install lines — only where `sudo -n true` works (requires --fix --yes)')
1225
1230
  .option('--restart', 'restart the nexus service when it runs a different version than this installed copy, then verify it (requires --fix --yes · interrupts bots, terminals and running turns)')
1226
- .action(async (opts: { json?: boolean; credentials?: boolean; bundle?: boolean; out?: string; fix?: boolean; advise?: boolean; yes?: boolean; sudo?: boolean; restart?: boolean }) => {
1231
+ .action(async (opts: { json?: boolean; credentials?: boolean; bundle?: boolean; upload?: boolean; out?: string; fix?: boolean; advise?: boolean; yes?: boolean; sudo?: boolean; restart?: boolean }) => {
1232
+ if (opts.upload && !opts.bundle) {
1233
+ err.error('--upload requires --bundle');
1234
+ setExitCode(1);
1235
+ return;
1236
+ }
1227
1237
  if (opts.bundle) {
1228
- if (opts.fix || opts.advise || opts.yes || opts.sudo || opts.restart) {
1238
+ if (opts.fix || opts.advise || opts.sudo || opts.restart || (opts.yes && !opts.upload)) {
1229
1239
  err.error('--bundle cannot be combined with repair or advice options');
1230
1240
  setExitCode(1);
1231
1241
  return;
@@ -1235,7 +1245,31 @@ export function registerDoctorCommand(program: Command, deps: DoctorCliDeps = {}
1235
1245
  const doctorReport = runDoctor(deps);
1236
1246
  if (doctorReport.ok) doctorReport.localLlm = await probeDoctorLocalLlm(deps.localLlmInventory);
1237
1247
  const result = buildDoctorBundle({ outDir: opts.out ?? process.cwd(), doctorOptions: deps, doctorReport });
1238
- out.log(opts.json ? JSON.stringify(result) : [`Diagnostics: ${result.path} (${result.bytes} bytes)`, 'Contents:', ...result.files.map((file) => ` ${file}`)].join('\n'));
1248
+ if (opts.upload) {
1249
+ let uploadUrl: string | undefined;
1250
+ try { uploadUrl = (deps.userConfig ?? (deps.getUserConfig ?? getUserConfig)()).diagnostics?.uploadUrl; }
1251
+ catch {
1252
+ const reason = '설정을 읽을 수 없습니다';
1253
+ debug.log('doctor.bundle', 'upload-failed', { bytes: result.bytes, reason: 'config-read-failed' });
1254
+ if (opts.json) out.log(JSON.stringify({ path: result.path, uploaded: false, reason }));
1255
+ else err.error(`업로드 실패 (${reason}) — 파일: ${result.path} — 다시 시도해 주세요.`);
1256
+ setExitCode(1);
1257
+ return;
1258
+ }
1259
+ const uploaded = await uploadDoctorBundle({
1260
+ ...result,
1261
+ uploadUrl,
1262
+ yes: opts.yes,
1263
+ announce: (contents) => { if (opts.json) err.error(contents); else out.log(contents); },
1264
+ confirm: deps.confirmBundleUpload ?? ((message) => confirmDoctorAdvice(message, opts.json === true)),
1265
+ fetch: deps.uploadFetch ?? globalThis.fetch,
1266
+ });
1267
+ if (opts.json) out.log(JSON.stringify({ path: uploaded.path, uploaded: uploaded.uploaded, ...(uploaded.code ? { code: uploaded.code } : {}), ...(uploaded.reason ? { reason: uploaded.reason } : {}) }));
1268
+ else out.log(uploaded.message);
1269
+ if (uploaded.exitCode) setExitCode(uploaded.exitCode);
1270
+ } else {
1271
+ out.log(opts.json ? JSON.stringify(result) : [`Diagnostics: ${result.path} (${result.bytes} bytes)`, 'Contents:', ...result.files.map((file) => ` ${file}`)].join('\n'));
1272
+ }
1239
1273
  } catch {
1240
1274
  err.error('Could not create diagnostics archive.');
1241
1275
  setExitCode(1);
@@ -1,6 +1,11 @@
1
1
  import type { Command } from 'commander';
2
2
  import * as ui from '../ui.js';
3
3
  import { writeStdoutJson } from './stdout-json.js';
4
+ import type { OpsNowResult } from '../ops-now/ops-now-refresh.js';
5
+
6
+ export function opsNowExitCode(outcome: OpsNowResult['outcome']): number {
7
+ return outcome === 'channel-unreadable' || outcome === 'summarize-failed' ? 2 : 0;
8
+ }
4
9
 
5
10
  export function registerOpsCommands(program: Command): void {
6
11
  const opsCmd = program.command('ops')
@@ -173,6 +178,21 @@ async function runOpsBuild(buildId: string | undefined, opts: OpsBuildOpts): Pro
173
178
  process.exit(0);
174
179
  }
175
180
 
181
+ opsCmd.command('now-refresh').description('채널 📌안내로 ops-now 고정 기억 갱신(1회)')
182
+ .option('--once', '한 번 실행(기본)')
183
+ .option('--dry-run', '기억을 쓰지 않고 새 본문 출력')
184
+ .option('--json', '결과를 JSON으로 출력')
185
+ .action(async (o: { dryRun?: boolean; json?: boolean }) => {
186
+ const { refreshOpsNow } = await import('../ops-now/ops-now-refresh.js');
187
+ const result = await refreshOpsNow({ dryRun: o.dryRun === true });
188
+ if (o.json) await writeStdoutJson(JSON.stringify(result, null, 2) + '\n');
189
+ else {
190
+ console.log(`ops-now: ${result.outcome} · 안내 ${result.notices}개`);
191
+ if (o.dryRun && result.body !== undefined) console.log(result.body);
192
+ }
193
+ process.exitCode = opsNowExitCode(result.outcome);
194
+ });
195
+
176
196
  opsCmd.command('status').description('현재 상태 종합(미션·태스크·루프·오케스트레이션·스케줄) · --all-instances 로 fleet 전체 종합(미션/태스크/loops/스케줄/오케스트레이션) · -r/--remote 로 원격 GET 조립')
177
197
  .option('--json').option('--all-instances', '등록 인스턴스 전체 종합(미션/태스크/loops/스케줄/오케스트레이션 · fleet · read-only · §10)')
178
198
  .option('--include-test', '연합에 격리 test 인스턴스도 포함(기본 제외)')
@@ -2,6 +2,11 @@
2
2
  // 산출 경로를 한 줄로 알리고 명령의 종료 코드로 끝난다. `--deadline` 생략 시 기존 Pod Job 상한.
3
3
 
4
4
  import type { Command } from 'commander';
5
+ import { debug } from '../debug/log.js';
6
+ import { getUserConfig } from '../user-config.js';
7
+ import { listCodexAccountsInStore } from '../oauth/codex-account-store.js';
8
+ import { parsePodPool, resolvePodPoolSpec, type PoolKubectl } from '../task-orchestrator/surfaces/pod-pool.js';
9
+ import { leaseKubectl, measurePoolLease, recommendConcurrency, type PoolLeaseMeasure } from '../task-orchestrator/surfaces/pod-lease.js';
5
10
  import { POD_COMMAND_DEADLINE_SECONDS, runPodCommand, type PodCommandResult, type RunPodCommandOptions } from '../task-orchestrator/surfaces/pod-command-job.js';
6
11
 
7
12
  export interface PodCliIo {
@@ -13,6 +18,10 @@ export interface PodCliIo {
13
18
  export interface PodCliDeps {
14
19
  io?: PodCliIo;
15
20
  run?: (options: RunPodCommandOptions) => Promise<PodCommandResult>;
21
+ kubectl?: PoolKubectl;
22
+ accounts?: () => number;
23
+ perAccount?: () => number;
24
+ poolSpec?: (explicit?: string) => string | null;
16
25
  }
17
26
 
18
27
  const HELP = `elanous pod run [--pool <스펙>] [--skill <이름,…>] [--llm grok] [--clone] [--deadline <초>] -- <명령…>
@@ -27,6 +36,46 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
27
36
  };
28
37
  const run = deps.run ?? ((options: RunPodCommandOptions) => runPodCommand(options));
29
38
  const pod = program.command('pod').description('Pod 명령 Job — 하니스 없이 명령 하나를 돌리고 산출을 돌려받는다');
39
+ pod.command('lease').description('Pod 풀의 읽기 전용 동시 실행 권장')
40
+ .command('status').description('지금 추가로 쏠 수 있는 Pod 골과 제한 근거')
41
+ .option('--pool <spec>', '풀 스펙 — 컨텍스트[@ssh호스트][:상한] 쉼표')
42
+ .option('--json', '표 대신 동일 측정값을 JSON 으로')
43
+ .action((opts: { pool?: string; json?: boolean }) => {
44
+ try {
45
+ const kubectl = deps.kubectl ?? leaseKubectl;
46
+ const spec = (deps.poolSpec ?? ((explicit?: string) => resolvePodPoolSpec(explicit)))(opts.pool);
47
+ // No pool configured: mirror the Pod launch fallback to kubectl's current context.
48
+ const current = spec ? null : kubectl(['config', 'current-context']);
49
+ const context = spec ?? (current?.status === 0 ? current.stdout.trim() : '');
50
+ const members = context ? parsePodPool(context) : [];
51
+ const measure: PoolLeaseMeasure = members.length ? measurePoolLease(members, { kubectl }) : { members: [] };
52
+ let accounts: number | null = null;
53
+ let accountReason: string | null = null;
54
+ try { accounts = (deps.accounts ?? (() => listCodexAccountsInStore().length))(); }
55
+ catch { accountReason = '측정 불가: accounts'; }
56
+ const perAccount = (deps.perAccount ?? (() => getUserConfig().pod?.lease?.perAccount ?? 4))();
57
+ const decision = recommendConcurrency(measure, { capacity: members.reduce((sum, m) => sum + m.capacity, 0), accounts: accounts ?? -1, perAccount });
58
+ if (!context) { decision.recommended = null; decision.limitedBy = null; decision.reason = '측정 불가: cluster (current context)'; }
59
+ const result = { pool: spec, ...measure, accounts, perAccount, ...decision };
60
+ debug.log('pod.lease', 'status', { pool: spec ? members.map((m) => ({ capacity: m.capacity })) : null, running: decision.running, pending: decision.pending, recommended: decision.recommended, limitedBy: decision.limitedBy });
61
+ if (opts.json) io.log(JSON.stringify(result));
62
+ else {
63
+ io.log('컨텍스트 | 상한 | Running | Pending | 메모리(상한 합/할당 가능) | CPU(할당 가능)');
64
+ for (const m of measure.members) {
65
+ const mem = (v: number | null) => v === null ? '?' : `${(v / 1024 ** 3).toFixed(1)}Gi`;
66
+ io.log(`${m.context} | ${m.capacity} | ${m.running ?? '?'} | ${m.pending ?? '?'} | ${mem(m.memoryLimitBytes)}/${mem(m.allocatableMemoryBytes)} | ${m.allocatableCpuMillicores === null ? '?' : m.allocatableCpuMillicores / 1000}${m.reason ? ` (${m.reason})` : ''}`);
67
+ }
68
+ io.log(`권장 지금 ${decision.recommended ?? '?'} 개 더 (limitedBy=${decision.limitedBy ?? 'unknown'})${decision.reason ? ` · ${decision.reason}` : ''}`);
69
+ io.log(`capacity: ${decision.capacitySlots ?? '?'} 칸 (상한 ${members.reduce((sum, m) => sum + m.capacity, 0)} − Running ${decision.running ?? '?'} − Pending ${decision.pending ?? '?'})`);
70
+ io.log(`memory: ${decision.memorySlots ?? '?'} 칸 (할당 가능 − Running 하니스 상한 합; 골당 16Gi · 모든 네임스페이스의 노드별 기존 예약을 반영해 배치 가능 ${decision.placeableSlots ?? '?'} 칸)`);
71
+ io.log(`accounts (관측만 · 상한 아님): ${decision.accountSlots ?? '?'} 칸 (${accounts ?? '?'} × ${perAccount} − Running ${decision.running ?? '?'})${accountReason ? ` · ${accountReason}` : ''}`);
72
+ }
73
+ io.exit(0);
74
+ } catch (err) {
75
+ io.error(err instanceof Error ? err.message : String(err));
76
+ io.exit(1);
77
+ }
78
+ });
30
79
  pod.command('run')
31
80
  .description(HELP)
32
81
  .option('--pool <spec>', '풀 스펙 — 컨텍스트[@ssh호스트][:상한] 쉼표')
package/src/cli/pr-cli.ts CHANGED
@@ -13,6 +13,7 @@ import { runGate as runChangedTypecheckGate } from '../../scripts/ci-typecheck-c
13
13
  import { runIsolationHardcodeGate } from '../../scripts/ci-isolation-hardcode-gate.js';
14
14
  import { androidFilesIn, runAndroidUnitTestGate } from '../../scripts/ci-android-unit-tests.js';
15
15
  import { iosFilesIn, runIosUnitTestGate } from '../../scripts/ci-ios-unit-tests.js';
16
+ import { runPwaBuildGate } from '../../scripts/ci-pwa-build-gate.js';
16
17
  import { runMockModuleRestoreGate } from '../../scripts/ci-mock-module-restore-gate.js';
17
18
  import { runModelHardcodeGate } from '../../scripts/ci-model-hardcode-gate.js';
18
19
  import { runDaemonPortGate } from '../../scripts/ci-daemon-port-gate.js';
@@ -51,6 +52,7 @@ import {
51
52
  unionPaths,
52
53
  } from './pr-granularity.js';
53
54
  import { branchLineageSlug, findSiblingPrs } from './pr-lineage.js';
55
+ import { parseReleaseNoteSection } from '../release-loop/release-note.js';
54
56
 
55
57
  export { branchLineageSlug, findSiblingPrs };
56
58
 
@@ -114,6 +116,7 @@ export interface PrLandDeps {
114
116
  runAndroidGate?: (out: { log: (message: string) => void; error: (message: string) => void }, changedFiles: readonly string[]) => boolean;
115
117
  /** iOS 시험 게이트 심(시험 주입용). */
116
118
  runIosGate?: (out: { log: (message: string) => void; error: (message: string) => void }, changedFiles: readonly string[]) => boolean;
119
+ runPwaGate?: (out: { log: (message: string) => void; error: (message: string) => void }, changedFiles: readonly string[]) => boolean;
117
120
  isInteractive?: () => boolean;
118
121
  /** Clock for the recent-landings window (test seam · default Date.now). */
119
122
  now?: () => number;
@@ -406,13 +409,14 @@ function gateVerdict(
406
409
  label: string,
407
410
  gate: (out: { log: (message: string) => void; error: (message: string) => void }) => boolean,
408
411
  out: { log: (message: string) => void; error: (message: string) => void },
412
+ failClosed = false,
409
413
  ): { ok: boolean; measured: boolean } {
410
414
  try {
411
415
  return { ok: gate(out), measured: true };
412
416
  } catch (error) {
413
417
  out.error(`⚠ ${label}: 게이트가 «못 쟀다» — ${error instanceof Error ? error.message : String(error)}`);
414
- out.error(`⚠ ${label}: 「위반 없음」이 아니라 「검사 못 함」이다. 이 착지는 통과시키되 게이트를 고쳐라.`);
415
- return { ok: true, measured: false };
418
+ out.error(`⚠ ${label}: 「위반 없음」이 아니라 「검사 못 함」이다. ${failClosed ? '착지를 막는다.' : '이 착지는 통과시키되 게이트를 고쳐라.'}`);
419
+ return { ok: !failClosed, measured: false };
416
420
  }
417
421
  }
418
422
 
@@ -426,6 +430,21 @@ function record(step: string, ok: boolean, extra: Record<string, unknown> = {}):
426
430
  debug.log('pr.land', 'step', { step, ok, ...extra });
427
431
  }
428
432
 
433
+ export function nextMdWarning(body: string, changedFiles: readonly string[], out: { log: (message: string) => void }): void {
434
+ const section = body.split(/^## 릴리스 노트[ \t]*\r?$/m).at(-1)!;
435
+ const parsed = parseReleaseNoteSection(body);
436
+ const hasNote = parsed.fragment !== undefined || parsed.problems.length > 0;
437
+ const kind = parsed.fragment?.kind ?? (hasNote
438
+ ? /^[ \t]*- 종류:[ \t]*(feat|fix|security|internal)[ \t]*\r?$/m.exec(section.split(/^#{1,6}[ \t]+/m)[0]!)?.[1]
439
+ : undefined);
440
+ const reason = hasNote
441
+ ? (kind === 'feat' || kind === 'fix' || kind === 'security' ? `release-note:${kind}` : undefined)
442
+ : (changedFiles.some((file) => /^(?:apps\/pwa\/src\/|src\/cli\/|src\/dashboard\/)/.test(file)) ? 'user-facing-path' : undefined);
443
+ const warn = !!reason && !changedFiles.includes('release/next.md');
444
+ record('next-md', true, { warnOnly: true, reason: reason ?? (hasNote ? 'non-user-facing-note' : 'no-user-facing-path') });
445
+ if (warn) out.log('⚠ next-md: 사용자에게 보이는 변경인데 release/next.md 에 줄이 없습니다 — 공개 노트에서 빠집니다. 「- <kind> — <영어 한 문장>. Documentation: … Target: next.」 한 줄을 더하십시오.');
446
+ }
447
+
429
448
  type PrRemoteCapability = 'github' | 'local-path' | 'no-remote' | 'other';
430
449
 
431
450
  function isLocalRemote(raw: string): boolean {
@@ -1323,6 +1342,7 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
1323
1342
  }
1324
1343
 
1325
1344
  const changedPaths = currentChangePaths(run, cwd, staged, base, out);
1345
+ nextMdWarning(body ?? '', changedPaths, out);
1326
1346
  publicLeakWarning(changedPaths, deps.runPublicLeakGate ?? runPrLandPublicLeakGate, out);
1327
1347
  if (!exportLeakBlock(changedPaths, () => (deps.runExportLeakCheck ?? runPrLandExportLeakCheck)(changedPaths ?? [], cwd), opts.allowPublicLeak === true, out)) return 1;
1328
1348
  if (publicDocPaths(changedPaths, () => true).length > 0) {
@@ -1371,6 +1391,15 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
1371
1391
  out.log('✓ ios-gate: scripts/ci-ios-unit-tests.ts PASS — iOS 순수-로직 시험이 «실제로 돌았고» 실패 0.');
1372
1392
  }
1373
1393
 
1394
+ // PWA 는 bun test/tsc 밖의 정적 번들이다. 측정 불가도 착지 불가로 판정한다.
1395
+ const pwaGate = gateVerdict('pwa-gate', deps.runPwaGate
1396
+ ? (o) => deps.runPwaGate!(o, changedPaths)
1397
+ : (o) => runPwaBuildGate({ args: ['--changed-files', ...changedPaths], cwd, log: o.log, error: o.error }) === 0, out, true);
1398
+ record('pwa-gate', pwaGate.ok && pwaGate.measured, { measured: pwaGate.measured });
1399
+ if (!pwaGate.ok || !pwaGate.measured) {
1400
+ out.error('✗ pwa-gate: scripts/ci-pwa-build-gate.ts blocked pr land — PWA 빌드 실패 또는 측정 불가.');
1401
+ return 1;
1402
+ }
1374
1403
 
1375
1404
  const overlapFound = emitLandingOverlapAdvisory(run, cwd, base, changedPaths, out);
1376
1405
  const siblingFound = emitSiblingPrAdvisory(