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,231 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
3
+ import { Database } from 'bun:sqlite';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { promisify } from 'node:util';
6
+ import { TRACK_AGENT_FORBIDDEN_ACTION_REGEX, universeLaunch } from '../autopilot/track-agent.js';
7
+ import { debug } from '../debug/log.js';
8
+ import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js';
9
+ import { getUserConfig, type SeatLoopConfig } from '../user-config.js';
10
+ import { listChecklist } from '../release-loop/checklist.js';
11
+ import { readSchedules } from '../release-loop/feature-store.js';
12
+
13
+ // `kind`/`createdAt` are the V3 shadow-compare keys (내부 문서 `METHOD-v3-shadow-compare-2026-10-02` · MK 10-02 18:54).
14
+ export type SeatItem = { source: 'request' | 'checklist'; kind?: 'request' | 'cell'; id: string; title: string; text: string; version?: string; queuedAt?: string; createdAt?: string };
15
+ export type SeatInputs = { requests: SeatItem[]; checklist: SeatItem[]; role: string };
16
+ // V3 ledger row: `ts` · `item.id` · `item.kind` · `item.createdAt` · `action` (door or skipped-*) · `reason` (forbidden word for a decision).
17
+ export type SeatAction = 'decision' | 'harness' | 'skipped-budget' | 'skipped-empty';
18
+ export type SeatEntry = { seat: string; ts?: string; at: string; status: 'shadow' | 'attempting' | 'outcome-unknown' | 'launched' | 'hitl' | 'skipped-budget' | 'skipped-empty'; item?: SeatItem; action?: SeatAction; reason?: string; runId?: string };
19
+ export type SeatLoopResult = SeatEntry | { seat: string; status: 'skipped-off' };
20
+ export type SeatDeps = {
21
+ root?: string;
22
+ repo?: string;
23
+ config?: SeatLoopConfig;
24
+ now?: () => Date;
25
+ read?: (path: string) => string;
26
+ run?: (args: string[]) => Promise<string>;
27
+ append?: (path: string, entry: SeatEntry) => void;
28
+ versions?: () => string[];
29
+ /** Checklist items of one version — default reads the release ledger DB (checklist.json is only a legacy import source since REL5b). */
30
+ checklistItems?: (version: string) => Array<{ id: string; title: string; status: string; owner?: string | null }>;
31
+ ledgerFiles?: (directory: string) => string[];
32
+ lockContended?: () => void;
33
+ };
34
+
35
+ const repoRoot = resolve(import.meta.dir, '../..');
36
+ const exec = promisify(execFile);
37
+ const defaultRead = (path: string): string => {
38
+ try { return readFileSync(path, 'utf8'); }
39
+ catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return ''; throw error; }
40
+ };
41
+ const defaultRun = async (args: string[]): Promise<string> => {
42
+ const { argv, env } = universeLaunch(args);
43
+ const { stdout } = await exec('bun', argv, {
44
+ cwd: repoRoot, env, encoding: 'utf8',
45
+ timeout: args[0] === 'harness' && args[1] === 'say' ? 3_600_000 : 120_000, maxBuffer: 4 * 1024 * 1024,
46
+ });
47
+ return stdout;
48
+ };
49
+ const defaultAppend = (path: string, entry: SeatEntry): void => {
50
+ mkdirSync(dirname(path), { recursive: true });
51
+ appendFileSync(path, `${JSON.stringify(entry)}\n`, { mode: 0o600 });
52
+ };
53
+ const observe = (event: string, data: Record<string, unknown>): void => {
54
+ try { debug.log('seat.loop', event, data); } catch { /* logging cannot change execution */ }
55
+ };
56
+
57
+ // SQLite's OS lock survives the awaited external call and is released if the process exits.
58
+ async function withSeatLock<T>(directory: string, action: () => Promise<T>, contended?: () => void): Promise<T> {
59
+ mkdirSync(directory, { recursive: true });
60
+ const path = join(directory, '.once.mutex.sqlite');
61
+ const db = new Database(path, { create: true, strict: true });
62
+ try {
63
+ chmodSync(path, 0o600);
64
+ const deadline = Date.now() + 120_000;
65
+ let signalled = false;
66
+ while (true) {
67
+ try { db.exec('BEGIN IMMEDIATE'); break; }
68
+ catch (error) {
69
+ if ((error as { code?: string }).code !== 'SQLITE_BUSY' || Date.now() >= deadline) throw error;
70
+ if (!signalled) { contended?.(); signalled = true; }
71
+ await new Promise((done) => setTimeout(done, 25));
72
+ }
73
+ }
74
+ try { return await action(); }
75
+ finally { db.exec('ROLLBACK'); }
76
+ } finally { db.close(); }
77
+ }
78
+
79
+ export function seatDay(now: Date): string {
80
+ return new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit' }).format(now);
81
+ }
82
+
83
+ export function seatLedgerPath(seat: string, root: string, now: Date): string {
84
+ return join(root, 'seat-loop', seat, `${seatDay(now)}.jsonl`);
85
+ }
86
+
87
+ function rows(text: string): Record<string, unknown>[] {
88
+ return text.split('\n').filter(Boolean).map((line) => JSON.parse(line) as Record<string, unknown>);
89
+ }
90
+
91
+ function versionOrder(a: string, b: string): number {
92
+ const aa = a.split('.').map(Number), bb = b.split('.').map(Number);
93
+ return (aa[0]! - bb[0]!) || (aa[1]! - bb[1]!) || (aa[2]! - bb[2]!);
94
+ }
95
+
96
+ export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}): Promise<SeatInputs> {
97
+ const read = deps.read ?? defaultRead;
98
+ const root = deps.root ?? effectiveInstanceRoot();
99
+ const requests = new Map<string, Record<string, unknown>>();
100
+ for (const row of rows(read(join(root, 'seat-requests', 'requests.jsonl')))) {
101
+ if (typeof row.key === 'string') requests.set(row.key, row);
102
+ }
103
+ const pending = [...requests.values()]
104
+ .filter((row) => row.seat === seat && (row.status === 'pending' || row.status === 'queued') && typeof row.text === 'string')
105
+ .map((row) => ({ source: 'request' as const, kind: 'request' as const, id: String(row.key), title: String(row.text), text: String(row.text), queuedAt: String(row.queuedAt ?? ''), createdAt: String(row.queuedAt ?? '') }))
106
+ .sort((a, b) => a.queuedAt.localeCompare(b.queuedAt) || a.id.localeCompare(b.id));
107
+ const releaseRoot = deps.root ?? releaseLedgerRoot();
108
+ const versions = deps.versions ?? (() => {
109
+ const dir = join(releaseRoot, 'release');
110
+ const fromDirs = existsSync(dir) ? readdirSync(dir).filter((v) => /^\d+\.\d+\.\d+$/.test(v)) : [];
111
+ let fromSchedule: string[] = [];
112
+ try { fromSchedule = readSchedules().map((row) => row.version); } catch { /* ledger unreadable — directories only */ }
113
+ return [...new Set([...fromDirs, ...fromSchedule])].filter((v) => /^\d+\.\d+\.\d+$/.test(v));
114
+ });
115
+ const itemsOf = deps.checklistItems ?? ((version: string) => listChecklist(version).items);
116
+ const checklist: SeatItem[] = [];
117
+ for (const version of versions().sort(versionOrder)) {
118
+ let items: ReturnType<typeof itemsOf>;
119
+ try { items = itemsOf(version); } catch (error) {
120
+ debug.log('seat.loop', 'checklist-unreadable', { seat, version, error: String(error).slice(0, 200) });
121
+ continue;
122
+ }
123
+ for (const item of items) {
124
+ if (item.owner === seat && (item.status === 'yellow' || item.status === 'red')) {
125
+ checklist.push({ source: 'checklist', kind: 'cell', version, id: item.id, title: item.title, text: item.title });
126
+ }
127
+ }
128
+ }
129
+ checklist.sort((a, b) => versionOrder(a.version!, b.version!) || a.id.localeCompare(b.id));
130
+ return { requests: pending, checklist, role: read(join(deps.repo ?? repoRoot, 'docs', 'roles', `${seat}.md`)).slice(0, 4_000) };
131
+ }
132
+
133
+ function itemKey(item: SeatItem): string { return `${item.source}:${item.version ?? ''}:${item.id}`; }
134
+
135
+ // In shadow mode a shadowed item counts as handled, so a rehearsal day walks the queue like the seat would.
136
+ export function pickNext(inputs: SeatInputs, ledger: readonly SeatEntry[], opts: { shadow?: boolean } = {}): SeatItem | null {
137
+ const handled = new Set(ledger.filter((entry) => entry.status === 'launched' || entry.status === 'hitl' || entry.status === 'attempting' || entry.status === 'outcome-unknown' || (opts.shadow === true && entry.status === 'shadow'))
138
+ .filter((entry): entry is SeatEntry & { item: SeatItem } => !!entry.item && !!entry.item.source && typeof entry.item.id === 'string').map((entry) => itemKey(entry.item)));
139
+ return [...inputs.requests, ...inputs.checklist].find((item) => !handled.has(itemKey(item))) ?? null;
140
+ }
141
+
142
+ export function planAction(item: SeatItem, seat?: string): { kind: 'decision' | 'harness'; text: string; reason?: string } {
143
+ const hit = `${item.id}\n${item.title}\n${item.text}`.match(TRACK_AGENT_FORBIDDEN_ACTION_REGEX);
144
+ const kind = hit ? 'decision' : 'harness';
145
+ return { kind, text: seat && kind === 'harness' ? seatTaskText(seat, item) : `${item.id} ${item.title}`, ...(hit ? { reason: hit[0] } : {}) };
146
+ }
147
+
148
+ // The launched sentence names the seat, the source and the release so same-named items stay distinct.
149
+ export function seatTaskText(seat: string, item: SeatItem): string {
150
+ const where = item.source === 'checklist' ? `${item.version ?? '?'} 체크리스트 칸 ${item.id}` : `자리 요청 ${item.id}`;
151
+ return `[${seat} 자리 · ${where} · 역할 docs/roles/${seat}.md] ${item.title.replace(/[\r\n]+/g, ' ')}`;
152
+ }
153
+
154
+ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promise<SeatLoopResult> {
155
+ if (!/^(?:MK|OP|TC|UX)$/.test(seat)) throw new Error(`unknown seat: ${seat}`);
156
+ const config = deps.config ?? getUserConfig().loops?.seat ?? { mode: 'off' };
157
+ if (config.mode === 'off' || !(config.seats ?? ['MK']).includes(seat)) {
158
+ observe('skipped-off', { seat });
159
+ return { seat, status: 'skipped-off' };
160
+ }
161
+ const now = (deps.now ?? (() => new Date()))();
162
+ const path = seatLedgerPath(seat, deps.root ?? effectiveInstanceRoot(), now);
163
+ const directory = dirname(path);
164
+ return withSeatLock(directory, async () => {
165
+ const files = deps.ledgerFiles?.(directory) ?? (existsSync(directory) ? readdirSync(directory) : []);
166
+ const ledger = files.filter((name) => /^\d{4}-\d{2}-\d{2}\.jsonl$/.test(name) && name <= `${seatDay(now)}.jsonl`)
167
+ .flatMap((name) => rows((deps.read ?? defaultRead)(join(directory, name))) as SeatEntry[]);
168
+ const inputs = await gatherSeatInputs(seat, deps);
169
+ const item = pickNext(inputs, ledger, { shadow: config.mode === 'shadow' });
170
+ if (item) observe('picked', { seat, item, date: seatDay(now) });
171
+ const planned = item ? planAction(item, seat) : undefined;
172
+ const entry: SeatEntry = { seat, ts: now.toISOString(), at: now.toISOString(), status: item ? 'shadow' : 'skipped-empty',
173
+ ...(item ? { item, action: planned!.kind, ...(planned!.reason ? { reason: planned!.reason } : {}) } : { action: 'skipped-empty' as const }) };
174
+ const append = deps.append ?? defaultAppend;
175
+ if (config.mode === 'shadow' || !item) {
176
+ append(path, entry);
177
+ if (config.mode === 'shadow') observe('shadow', { seat, item, status: entry.status });
178
+ return entry;
179
+ }
180
+ const run = deps.run ?? defaultRun;
181
+ let outcome: string | undefined;
182
+ try { outcome = (JSON.parse(await run(['harness', 'budget', '--json'])) as { outcome?: string }).outcome; }
183
+ catch { /* failed budget observation is never permission to launch */ }
184
+ if (outcome !== 'proceed' && outcome !== 'next-provider') {
185
+ entry.status = 'skipped-budget';
186
+ entry.action = 'skipped-budget';
187
+ delete entry.reason;
188
+ append(path, entry);
189
+ observe('skipped-budget', { seat, item, outcome: outcome ?? 'unavailable' });
190
+ return entry;
191
+ }
192
+ const action = planned!;
193
+ // Persist the intent before crossing the process boundary: an unreturned call may already have acted.
194
+ append(path, { ...entry, status: 'attempting' });
195
+ try {
196
+ if (action.kind === 'decision') {
197
+ const title = `${seat}: ${action.text.replace(/[\r\n]+/g, ' ')}`;
198
+ const raised = await run(['decisions', 'raise', '--title', title, '--category', 'other',
199
+ '--s', `${seat} 배정 항목: ${action.text.replace(/[\r\n]+/g, ' ')}\n역할: ${inputs.role}`, '--c', '자동 실행 금지 문면에 해당하여 사람 결정이 필요하다',
200
+ '--option', 'a=사람 승인:승인 후 별도로 집행', '--option', 'b=보류:집행하지 않음',
201
+ '--skip-recommend', '금지 문면은 자동으로 권고하지 않는다', '--agent', 'seat-loop', '--json']);
202
+ const decision: unknown = JSON.parse(raised.trim());
203
+ if (!decision || typeof decision !== 'object' || typeof (decision as { id?: unknown }).id !== 'string') {
204
+ throw new Error('decisions raise returned no decision id');
205
+ }
206
+ entry.status = 'hitl';
207
+ append(path, entry);
208
+ observe('hitl', { seat, item });
209
+ return entry;
210
+ }
211
+ const output = await run(['harness', 'say', action.text, '--substrate', 'pod', '--pod-pool', config.podPool ?? 'pool-node-b@node-b:8', '--base', 'main', '--json']);
212
+ const results: unknown = JSON.parse(output.trim());
213
+ if (!Array.isArray(results) || results.length === 0 || results.some((result) =>
214
+ !result || typeof result !== 'object' || (result as { status?: unknown }).status !== 'done')) {
215
+ throw new Error('harness say did not complete successfully');
216
+ }
217
+ const runId = (results[0] as { runId?: unknown }).runId;
218
+ if (typeof runId !== 'string' || !/^run-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(runId)) {
219
+ throw new Error('harness say output has no runId');
220
+ }
221
+ entry.status = 'launched';
222
+ entry.runId = runId;
223
+ append(path, entry);
224
+ observe('launched', { seat, item, runId });
225
+ return entry;
226
+ } catch (error) {
227
+ append(path, { ...entry, status: 'outcome-unknown' });
228
+ throw error;
229
+ }
230
+ }, deps.lockContended);
231
+ }
@@ -0,0 +1,64 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join, resolve } from 'node:path';
5
+ import { promisify } from 'node:util';
6
+ import { debug } from '../debug/log.js';
7
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
8
+ import { getUserConfig, type SeatLoopConfig } from '../user-config.js';
9
+ import { seatDay, seatLedgerPath, type SeatEntry } from './seat-loop.js';
10
+
11
+ export type SeatReportDeps = {
12
+ root?: string;
13
+ repo?: string;
14
+ now?: () => Date;
15
+ read?: (path: string) => string;
16
+ config?: SeatLoopConfig;
17
+ post?: boolean;
18
+ send?: (body: string, seat: string, pr: number) => Promise<void> | void;
19
+ };
20
+ export type SeatReportResult = { seat: string; date: string; body: string; posted: boolean };
21
+ const repoRoot = resolve(import.meta.dir, '../..');
22
+ const exec = promisify(execFile);
23
+
24
+ async function defaultSend(body: string, seat: string, pr: number, repo: string): Promise<void> {
25
+ const dir = mkdtempSync(join(tmpdir(), 'seat-report-'));
26
+ try {
27
+ const file = join(dir, 'body.md');
28
+ writeFileSync(file, body);
29
+ await exec('bash', [join(repo, 'scripts', 'coord-post.sh'), file], {
30
+ cwd: repo, env: { ...process.env, COORD_ID: seat, CH_PR: String(pr) }, timeout: 30_000,
31
+ });
32
+ } finally { rmSync(dir, { recursive: true, force: true }); }
33
+ }
34
+
35
+ export async function seatReport(seat: string, deps: SeatReportDeps = {}): Promise<SeatReportResult> {
36
+ if (!/^(?:MK|OP|TC|UX)$/.test(seat)) throw new Error(`unknown seat: ${seat}`);
37
+ const now = (deps.now ?? (() => new Date()))();
38
+ const date = seatDay(now);
39
+ let raw = '';
40
+ try { raw = (deps.read ?? ((path) => readFileSync(path, 'utf8')))(seatLedgerPath(seat, deps.root ?? effectiveInstanceRoot(), now)); }
41
+ catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
42
+ const entries = raw.split('\n').filter(Boolean).map((line) => JSON.parse(line) as SeatEntry);
43
+ // One line per item, its last state wins: an `attempting` row followed by `launched` is not «unconfirmed».
44
+ const latest = new Map<string, SeatEntry>();
45
+ for (const entry of entries) {
46
+ const key = entry.item ? `${entry.item.source}:${entry.item.version ?? ''}:${entry.item.id}` : `none:${entry.status}`;
47
+ latest.delete(key);
48
+ latest.set(key, entry);
49
+ }
50
+ const detail = [...latest.values()].map((entry) => {
51
+ const label = entry.item ? `${entry.item.version ? `${entry.item.version} ` : ''}${entry.item.id} ${entry.item.title}`.replace(/\s+/g, ' ').trim() : '배정 없음';
52
+ if (entry.status === 'launched') return `발사 ${label} (${entry.runId ?? 'runId 없음'})`;
53
+ if (entry.status === 'hitl') return `결정 상정 ${label}`;
54
+ if (entry.status === 'shadow') return `shadow ${label}`;
55
+ if (entry.status === 'attempting' || entry.status === 'outcome-unknown') return `결과 확인 필요 ${entry.status} ${label}`;
56
+ return `건너뜀 ${entry.status} ${label}`;
57
+ });
58
+ const body = `**[${seat}]** {{TS}} → 보고 ${date}: ${detail.join(' · ') || '원장 기록 없음'}`;
59
+ const pr = (deps.config ?? getUserConfig().loops?.seat)?.reportPr;
60
+ const posted = deps.post === true && pr !== undefined;
61
+ if (posted) await (deps.send ?? ((text, id, number) => defaultSend(text, id, number, deps.repo ?? repoRoot)))(body, seat, pr);
62
+ try { debug.log('seat.loop', 'reported', { seat, date, entries: entries.length, posted }); } catch { /* observation is fail-soft */ }
63
+ return { seat, date, body, posted };
64
+ }
@@ -156,6 +156,8 @@ interface SelfDevSubtaskGradeRationale {
156
156
  }
157
157
 
158
158
  export interface SelfDevDecompositionObservation extends SelfDevDecompositionMeta {
159
+ /** Number of registration-only goals folded into their dependency. */
160
+ foldedRegistration: number;
159
161
  /** Number of retained subtasks whose original feature text grades `too_large`. */
160
162
  tooLargeSubtaskCount: number;
161
163
  /** Number of retained subtasks whose original feature text grades `too_small`. */
@@ -250,7 +252,7 @@ function observeDecomposerSelection(selection: SelfDevDecomposerSelection, obser
250
252
  }
251
253
 
252
254
  function applyGoalDefaultsAndHotPaths(goals: SelfDevGoal[], defaults: { base?: string; autoMerge?: boolean }): SelfDevGoal[] {
253
- return pruneDanglingDependencies(goals.map((goal) => {
255
+ return goals.map((goal) => {
254
256
  const hotPaths = [...new Set([...(goal.hotPaths ?? []), ...inferHotPaths(goal.feature ?? '')])];
255
257
  return {
256
258
  ...goal,
@@ -258,7 +260,55 @@ function applyGoalDefaultsAndHotPaths(goals: SelfDevGoal[], defaults: { base?: s
258
260
  ...(defaults.base !== undefined ? { base: defaults.base } : {}),
259
261
  ...(defaults.autoMerge !== undefined ? { autoMerge: defaults.autoMerge } : {}),
260
262
  };
261
- }));
263
+ });
264
+ }
265
+
266
+ const REGISTRATION_FILES = new Set([
267
+ 'src/nexus/api/http-server.ts',
268
+ 'src/index.ts',
269
+ 'src/telegram-commands.ts',
270
+ ]);
271
+
272
+ function isRegistrationFile(path: string): boolean {
273
+ return REGISTRATION_FILES.has(path) || /^graphs\/(?:[^/]+\/)*[^/]+\.yaml$/.test(path);
274
+ }
275
+
276
+ /** Fold only registration-only goals into their one retained dependency. */
277
+ export function foldRegistrationOnlyGoals(goals: SelfDevGoal[]): SelfDevGoal[] {
278
+ let remaining = [...goals];
279
+ let changed = true;
280
+ while (changed) {
281
+ changed = false;
282
+ for (const registration of remaining) {
283
+ const files = registration.hotPaths ?? [];
284
+ if (files.length === 0 || !files.every(isRegistrationFile) || registration.dependsOn?.length !== 1) continue;
285
+ const dependencyId = registration.dependsOn[0];
286
+ const targetIndex = remaining.findIndex((goal) => goal.id === dependencyId && goal !== registration);
287
+ if (targetIndex < 0) continue;
288
+ const target = remaining[targetIndex]!;
289
+ remaining = remaining.filter((goal) => goal !== registration).map((goal) => {
290
+ if (goal === target) {
291
+ const dependsOn = goal.dependsOn?.filter((id) => id !== registration.id && id !== goal.id);
292
+ const { dependsOn: _old, ...rest } = goal;
293
+ return {
294
+ ...rest,
295
+ ...(dependsOn?.length ? { dependsOn } : {}),
296
+ feature: `${goal.feature}\nAlso register: ${registration.feature}`,
297
+ hotPaths: [...new Set([...(goal.hotPaths ?? []), ...files])],
298
+ };
299
+ }
300
+ if (!goal.dependsOn?.includes(registration.id!)) return goal;
301
+ const dependsOn = [...new Set(goal.dependsOn.map((id) => id === registration.id ? target.id! : id))]
302
+ .filter((id) => id !== goal.id);
303
+ const { dependsOn: _old, ...rest } = goal;
304
+ return dependsOn.length ? { ...rest, dependsOn } : rest;
305
+ });
306
+ debug.log('self-dev.decompose', 'fold-registration', { from: registration.id, into: target.id, files });
307
+ changed = true;
308
+ break;
309
+ }
310
+ }
311
+ return remaining;
262
312
  }
263
313
 
264
314
  function observeDecomposition(
@@ -267,12 +317,14 @@ function observeDecomposition(
267
317
  arcClassification?: { arcs: SelfDevArc[]; hintDeviation?: { requested: number; actual: number } },
268
318
  observationContext: SelfDevDecompositionObservationContext = {},
269
319
  pathGroundingOptions?: DecomposePathGroundingOptions,
320
+ foldedRegistration = 0,
270
321
  ): SelfDevDecomposition {
271
322
  const retainedSubtasks = decomposition.outcome === 'decomposed' ? goals : [];
272
323
  const gradedSubtasks = retainedSubtasks.map(gradeRetainedSubtask);
273
324
  const tooLargeSubtaskCount = gradedSubtasks.filter(({ grade }) => grade.verdict === 'too_large').length;
274
325
  const observation: SelfDevDecompositionObservation & { goalId: string | null; runId: string | null } = {
275
326
  ...decomposition,
327
+ foldedRegistration,
276
328
  tooLargeSubtaskCount,
277
329
  tooSmallSubtaskCount: gradedSubtasks.filter(({ grade }) => grade.verdict === 'too_small').length,
278
330
  hasMultipleTooLargeSubtasks: tooLargeSubtaskCount >= 2,
@@ -467,14 +519,15 @@ async function finalizeDecomposition(
467
519
  decomposition: SelfDevDecompositionMeta,
468
520
  opts: SelfDevDecomposeOptions,
469
521
  arcHint: number | undefined,
522
+ foldedRegistration = 0,
470
523
  ): Promise<SelfDevDecomposition> {
471
- if (arcHint === undefined || goals.length === 0) return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding);
524
+ if (arcHint === undefined || goals.length === 0) return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding, foldedRegistration);
472
525
  try {
473
526
  const arcLlm = opts.arcLlm ?? opts.llm ?? (await defaultDecomposeLlm(opts.model));
474
527
  const tasks = goals.map((goal) => ({ id: goal.id ?? '', title: goal.feature ?? '', description: goal.feature, dependsOn: goal.dependsOn }));
475
528
  const grouping = parseSelfDevArcGrouping(await arcLlm(buildSelfDevArcClassifyPrompt(feature, tasks, arcHint)));
476
529
  const classification = grouping ? deriveSelfDevArcsFromGrouping(tasks, grouping, arcHint) : null;
477
- if (!classification) return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding);
530
+ if (!classification) return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding, foldedRegistration);
478
531
  const arcByTask = new Map(classification.arcs.flatMap((arc) => arc.taskIds.map((taskId) => [taskId, arc])));
479
532
  const taskIdsForArc = new Map(classification.arcs.map((arc) => [arc.id, arc.taskIds]));
480
533
  const orderedGoals = goals.map((goal) => {
@@ -482,9 +535,9 @@ async function finalizeDecomposition(
482
535
  const dependsOn = [...new Set([...(goal.dependsOn ?? []), ...arcDependencies])].filter((id) => id !== goal.id);
483
536
  return dependsOn.length ? { ...goal, dependsOn } : goal;
484
537
  });
485
- return observeDecomposition(pruneDanglingDependencies(orderedGoals), decomposition, classification, opts.observation, opts.pathGrounding);
538
+ return observeDecomposition(pruneDanglingDependencies(orderedGoals), decomposition, classification, opts.observation, opts.pathGrounding, foldedRegistration);
486
539
  } catch {
487
- return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding);
540
+ return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding, foldedRegistration);
488
541
  }
489
542
  }
490
543
 
@@ -514,17 +567,18 @@ export async function decomposeSelfDevGoal(
514
567
  }
515
568
  observeDecomposerSelection('fabric', opts.observation);
516
569
  const actualTaskCount = result.goals.length;
517
- const goals = actualTaskCount > 0
570
+ const prepared = actualTaskCount > 0
518
571
  ? applyGoalDefaultsAndHotPaths(result.goals.slice(0, hardMaxTasks), defaults)
519
572
  : [single];
520
- return finalizeDecomposition(feature, goals, {
573
+ const goals = foldRegistrationOnlyGoals(prepared);
574
+ return finalizeDecomposition(feature, pruneDanglingDependencies(goals), {
521
575
  recommendedMaxTasks: maxTasks,
522
576
  actualTaskCount,
523
577
  truncatedAtHardMax: actualTaskCount > hardMaxTasks,
524
578
  exceededRecommendedMax: actualTaskCount > maxTasks,
525
579
  outcome: actualTaskCount === 0 ? 'single-no-subtasks' : 'decomposed',
526
580
  omittedGoalCount: result.omittedGoalCount,
527
- }, opts, actualTaskCount > 0 ? arcHint : undefined);
581
+ }, opts, actualTaskCount > 0 ? arcHint : undefined, prepared.length - goals.length);
528
582
  }
529
583
 
530
584
  observeDecomposerSelection('default', opts.observation);
@@ -539,8 +593,9 @@ export async function decomposeSelfDevGoal(
539
593
  const actualTaskCount = rawSubtasks.length;
540
594
  const decomposition: SelfDevDecompositionMeta = { recommendedMaxTasks: maxTasks, actualTaskCount, truncatedAtHardMax: actualTaskCount > hardMaxTasks, exceededRecommendedMax: actualTaskCount > maxTasks, outcome: actualTaskCount === 0 ? 'single-no-subtasks' : 'decomposed' };
541
595
  const parsedGoals = parseSelfDevDecomposition(JSON.stringify({ subtasks: rawSubtasks.slice(0, hardMaxTasks) }), defaults);
542
- const goals = parsedGoals.length > 0 ? pruneDanglingDependencies(parsedGoals) : [single];
543
- return finalizeDecomposition(feature, goals, decomposition, opts, parsedGoals.length > 0 ? arcHint : undefined);
596
+ const foldedGoals = foldRegistrationOnlyGoals(parsedGoals);
597
+ const goals = parsedGoals.length > 0 ? pruneDanglingDependencies(foldedGoals) : [single];
598
+ return finalizeDecomposition(feature, goals, decomposition, opts, parsedGoals.length > 0 ? arcHint : undefined, parsedGoals.length - foldedGoals.length);
544
599
  }
545
600
 
546
601
  /** Default LLM seam — lazy `streamLLM` wire (kept out of the test path). */
@@ -111,6 +111,8 @@ function runPath(runId: string, dir: string): string {
111
111
  return path;
112
112
  }
113
113
 
114
+ const loggedAbsentCheckpoints = new Set<string>();
115
+
114
116
  function loadSelfDevRunFromPath(path: string): SelfDevRunState | null {
115
117
  const runId = path.slice(path.lastIndexOf(sep) + 1, -'.json'.length);
116
118
  try {
@@ -118,7 +120,14 @@ function loadSelfDevRunFromPath(path: string): SelfDevRunState | null {
118
120
  if (o && typeof o.runId === 'string' && Array.isArray(o.results)) return o;
119
121
  try { debug.log('self-dev.run-store', 'read-failed', { runId, reason: 'invalid checkpoint shape' }); } catch { /* preserve fail-soft reads */ }
120
122
  } catch (error) {
121
- try { debug.log('self-dev.run-store', 'read-failed', { runId, reason: String(error) }); } catch { /* preserve fail-soft reads */ }
123
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
124
+ if (!loggedAbsentCheckpoints.has(runId)) {
125
+ loggedAbsentCheckpoints.add(runId);
126
+ try { debug.log('self-dev.run-store', 'checkpoint-absent', { runId }); } catch { /* preserve fail-soft reads */ }
127
+ }
128
+ } else {
129
+ try { debug.log('self-dev.run-store', 'read-failed', { runId, reason: String(error) }); } catch { /* preserve fail-soft reads */ }
130
+ }
122
131
  }
123
132
  return null;
124
133
  }
@@ -0,0 +1,115 @@
1
+ import { randomUUID } from 'node:crypto';
2
+
3
+ import { createPendingQuestion, writePendingQuestion } from '../ask-user-question/pending-questions.js';
4
+ import { createFileAskUserQuestionResolver } from '../ask-user-question/tool.js';
5
+ import { debug } from '../debug/log.js';
6
+ import {
7
+ decideAndObserveClarification,
8
+ defaultClarificationBudget,
9
+ toAskUserQuestionRequest,
10
+ type ClarificationCandidate,
11
+ type ClarificationContext,
12
+ } from '../hitl/clarification-policy.js';
13
+ import { getUserConfig } from '../user-config.js';
14
+ import { appendRunLedgerEntry, type RunLedgerWriter } from './run-ledger.js';
15
+
16
+ export interface ExecutionClarificationContext {
17
+ runId: string;
18
+ budget?: ClarificationContext['budget'];
19
+ pendingQuestionCount?: number;
20
+ config?: { hitl?: { executionDeadlineMinutes?: number } };
21
+ }
22
+
23
+ export type ExecutionClarificationResult =
24
+ | { choice: string; by: 'assumed' | 'owner' | 'auto-recommended' }
25
+ | { deferred: true }
26
+ | { parked: true; reason: 'critical-unanswered'; questionId: string };
27
+
28
+ export interface ExecutionClarificationDeps {
29
+ now?: () => number;
30
+ createId?: () => string;
31
+ resolver?: typeof createFileAskUserQuestionResolver;
32
+ resolverDeps?: Parameters<typeof createFileAskUserQuestionResolver>[1];
33
+ writeRunLedger?: RunLedgerWriter;
34
+ }
35
+
36
+ /** A single execution-phase decision; the file resolver owns the pending/answer lifecycle. */
37
+ export async function askAtExecution(
38
+ candidate: ClarificationCandidate,
39
+ ctx: ExecutionClarificationContext,
40
+ deps: ExecutionClarificationDeps = {},
41
+ ): Promise<ExecutionClarificationResult> {
42
+ const recommended = candidate.options.filter(option => option.recommended);
43
+ if (candidate.options.length < 2 || candidate.options.length > 4 || recommended.length !== 1
44
+ || new Set(candidate.options.map(option => option.label)).size !== candidate.options.length) {
45
+ throw new Error('Execution clarification requires 2–4 distinct options and exactly one recommendation.');
46
+ }
47
+ const observe = (event: 'assumed' | 'asked' | 'answered' | 'auto-recommended' | 'parked', questionId?: string): void => {
48
+ try {
49
+ debug.log('hitl.execution', event, { runId: ctx.runId, questionId, impact: candidate.impact });
50
+ } catch { /* Logging must not decide the user's choice. */ }
51
+ };
52
+ const record = (event: 'assumed' | 'auto-recommended' | 'parked', data: Record<string, unknown>): void => {
53
+ (deps.writeRunLedger ?? appendRunLedgerEntry)({
54
+ timestamp: new Date((deps.now ?? Date.now)()).toISOString(),
55
+ runId: ctx.runId,
56
+ event,
57
+ data: { impact: candidate.impact, ...data },
58
+ });
59
+ };
60
+ const decision = decideAndObserveClarification(candidate, {
61
+ phase: 'execution',
62
+ budget: ctx.budget ?? defaultClarificationBudget('execution'),
63
+ pendingQuestionCount: ctx.pendingQuestionCount,
64
+ }, { consumer: 'self-implement-execution' });
65
+ if (decision.action === 'defer') return { deferred: true };
66
+ if (decision.action === 'assume') {
67
+ record('assumed', { choice: recommended[0]!.label, assumption: decision.assumption });
68
+ observe('assumed');
69
+ return { choice: recommended[0]!.label, by: 'assumed' };
70
+ }
71
+
72
+ const request = toAskUserQuestionRequest(decision);
73
+ if (!request) throw new Error('Execution clarification policy approved a question without a valid wire request.');
74
+ const configured = ctx.config === undefined
75
+ ? getUserConfig().hitl?.executionDeadlineMinutes
76
+ : ctx.config.hitl?.executionDeadlineMinutes;
77
+ const minutes = configured === undefined ? 30 : configured;
78
+ if (!Number.isSafeInteger(minutes) || minutes < 1 || minutes > 35_791) {
79
+ throw new Error('hitl.executionDeadlineMinutes must be a positive integer within the resolver timer limit.');
80
+ }
81
+ const questionId = (deps.createId ?? (() => `execution:${ctx.runId}:${randomUUID()}`))();
82
+ let expired = false;
83
+ let written = false;
84
+ const resolver = (deps.resolver ?? createFileAskUserQuestionResolver)(minutes * 60_000, {
85
+ ...deps.resolverDeps,
86
+ observe: (state, data) => {
87
+ if (state === 'answer-missing') expired = true;
88
+ deps.resolverDeps?.observe?.(state, data);
89
+ },
90
+ createId: () => questionId,
91
+ write: (pending) => {
92
+ const question = createPendingQuestion(questionId, { ...request, runId: ctx.runId }, undefined,
93
+ { now: () => new Date((deps.now ?? Date.now)()) },
94
+ { surface: 'file', delivery: 'file', expiresAt: pending.expiresAt });
95
+ (deps.resolverDeps?.write ?? writePendingQuestion)(question);
96
+ written = true;
97
+ observe('asked', questionId);
98
+ },
99
+ });
100
+ const answer = await resolver({ ...request, runId: ctx.runId });
101
+ const chosen = answer.answers[candidate.id];
102
+ if (written && !expired && !answer.cancelled && typeof chosen === 'string' && candidate.options.some(option => option.label === chosen)) {
103
+ observe('answered', questionId);
104
+ return { choice: chosen, by: 'owner' };
105
+ }
106
+ if (!written || !expired) throw new Error('Execution clarification was cancelled or could not read its answer before expiry.');
107
+ if (candidate.impact === 'critical') {
108
+ record('parked', { questionId, reason: 'critical-unanswered' });
109
+ observe('parked', questionId);
110
+ return { parked: true, reason: 'critical-unanswered', questionId };
111
+ }
112
+ record('auto-recommended', { questionId, choice: recommended[0]!.label, by: 'auto-recommended' });
113
+ observe('auto-recommended', questionId);
114
+ return { choice: recommended[0]!.label, by: 'auto-recommended' };
115
+ }
@@ -7,6 +7,7 @@ import { runModelHardcodeGate } from '../../scripts/ci-model-hardcode-gate.js';
7
7
  import { runDaemonPortGate } from '../../scripts/ci-daemon-port-gate.js';
8
8
  import { runAndroidUnitTestGate } from '../../scripts/ci-android-unit-tests.js';
9
9
  import { runIosUnitTestGate } from '../../scripts/ci-ios-unit-tests.js';
10
+ import { runPwaBuildGate } from '../../scripts/ci-pwa-build-gate.js';
10
11
  import { runMockModuleRestoreGate } from '../../scripts/ci-mock-module-restore-gate.js';
11
12
  import { parseReviewDepthFromFilesJson } from '../agent-mission/review-depth.js';
12
13
  import { buildGateBaselineReport, calculateGateTestCountChange, formatGateBaselineNote, formatGateTestCountNote, isGateTestFile, runGateBaseline, type BaselineProcessResult, type BaselineProcessStatus, type GateTestFailure } from './gate-baseline.js';
@@ -83,6 +84,7 @@ interface SelfGateCliDeps {
83
84
  runAndroidGate?: (out: GateOutput) => number;
84
85
  /** iOS 순수-로직 시험 게이트 심(시험 주입용). */
85
86
  runIosGate?: (out: GateOutput) => number;
87
+ runPwaGate?: (out: GateOutput) => number;
86
88
  listPrWorktrees?: (cwd: string) => PrWorktreeListEntry[] | { lookupFailed: string };
87
89
  inspectWorktreeDirtiness?: (path: string) => { dirty: boolean } | { lookupFailed: string };
88
90
  }
@@ -619,11 +621,12 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
619
621
  const androidPassed = runAdditionalGate('android-gate', deps.runAndroidGate ?? runAndroidUnitTestGate, selection.files, cwd, lines);
620
622
  // 🍎 iOS 축 — 안드로이드와 «같은 자리»에 둔다(skipTestStep 앞). Swift 만 바뀐 PR 도 여기서 조기 리턴한다.
621
623
  const iosPassed = runAdditionalGate('ios-gate', deps.runIosGate ?? runIosUnitTestGate, selection.files, cwd, lines);
624
+ const pwaPassed = runAdditionalGate('pwa-gate', deps.runPwaGate ?? runPwaBuildGate, selection.files, cwd, lines);
622
625
 
623
626
  if (scope.skipTestStep) {
624
627
  const policyPassed = runPolicyGates(selection.files, cwd, lines, deps);
625
628
  logGateCliBaseline(cwd, { ...ZERO_GATE_BASELINE, unrunImporterTotal, lookupFailed: importerLookupFailed });
626
- return { exitCode: androidPassed && iosPassed && policyPassed ? 0 : 1, lines, changedFiles: selection.files, testFiles: [], unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
629
+ return { exitCode: androidPassed && iosPassed && pwaPassed && policyPassed ? 0 : 1, lines, changedFiles: selection.files, testFiles: [], unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
627
630
  }
628
631
 
629
632
  const test = (deps.runTests ?? ((dir, files) => (deps.runCommand ?? defaultRunCommand)('bun', ['test', ...files], dir)))(cwd, testFiles);
@@ -632,7 +635,7 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
632
635
  lines.push(formatPartialObservationNote(unrunImporterTotal ?? 0, `tests: pass (${testFiles.length} files)`));
633
636
  const policyPassed = runPolicyGates(selection.files, cwd, lines, deps);
634
637
  logGateCliBaseline(cwd, { ...ZERO_GATE_BASELINE, unrunImporterTotal, lookupFailed: importerLookupFailed });
635
- return { exitCode: policyPassed && androidPassed && iosPassed ? 0 : 1, lines, changedFiles: selection.files, testFiles, unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
638
+ return { exitCode: policyPassed && androidPassed && iosPassed && pwaPassed ? 0 : 1, lines, changedFiles: selection.files, testFiles, unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
636
639
  }
637
640
 
638
641
  if (worktreeLog.includes('deterministic environment setup failed')) lines.push(worktreeLog);
@@ -653,6 +656,6 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
653
656
  });
654
657
  lines.push(formatPartialObservationNote(unrunImporterTotal ?? 0, formatGateBaselineNote(report, unrunImporterTotal ?? 0)));
655
658
  const policyPassed = runPolicyGates(selection.files, cwd, lines, deps);
656
- const exitCode = !androidPassed || !iosPassed || !policyPassed || report.introduced > 0 || report.unknown > 0 || report.preconditionUnmet > 0 ? 1 : 0;
659
+ const exitCode = !androidPassed || !iosPassed || !pwaPassed || !policyPassed || report.introduced > 0 || report.unknown > 0 || report.preconditionUnmet > 0 ? 1 : 0;
657
660
  return { exitCode, lines, changedFiles: selection.files, testFiles, unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
658
661
  }