elanous 0.2.17 → 0.2.18

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 (309) 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/P6YTVgzuZz5oTZhY4Din_/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{1307-aac7332576c4d6fc.js → 1307-30dff22c9e1f3073.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{6158.9c0449a9a8d0064b.js → 6158.af2e93b7adff709b.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-3143b07a817d1da9.js → page-d92f7ee3994e84c7.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-17ba086ef82d339b.js → page-4733c967b56a4277.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/board/{page-7eb661ff23af7da2.js → page-d22fd849cc22f104.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/botlab/page-bb742f995c6769e7.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/bots/{page-6e5c3a867557298b.js → page-02b073a2ac793b83.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/consult/{page-5e77cd1802567c89.js → page-a0eb02638b1e6f31.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/control/page-308111abadf35670.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-4187666132fc98c4.js → page-5d97b2901e0d1ea6.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/app/editor/{page-09328d499162bae4.js → page-c39554c321576982.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/exec/{page-29ff3a8f84f03b6a.js → page-8c3d8e63ae2e1c98.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/field/{page-80ec7401bc83f511.js → page-9b72a07d424701c6.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/intake/{page-fc7d86467291ae38.js → page-a572f58cd3ca5864.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/loops/{page-0e2d6985fb42cd2f.js → page-c130b63862437814.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/market/{page-13e4108999f58d13.js → page-fa244bd146a00ae8.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-e8296ee284aa1293.js → page-44d56d6391965a6a.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/morning/{page-043a9111e74f6b24.js → page-83a700d4c754d802.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/{not-found-23a4601ceb7f452e.js → not-found-7b3170f374f77b9d.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-10d958605a77b3bd.js → page-a3c58f5c72a5f3f9.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-7a1d85e121065cc8.js → page-e8e1ecaea7cd71d2.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-12916918988ac97e.js → page-1467047104141297.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d8a3347a9d0c3f4a.js → page-2da6f9b8a38c6978.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-1d824510df025d18.js → page-44d5f767491692bf.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/share/{page-7f49fe645d5132f7.js → page-3cc47dca0922ab9a.js} +1 -1
  55. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-27d71f131acd9320.js → page-b17cd46cf0e9b8cf.js} +1 -1
  57. package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/trace/{page-424659bacfb8cdd7.js → page-7bf54c9bb9274af8.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-5d4fef9ccd7d406b.js → page-134da468538baf48.js} +1 -1
  64. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/{webpack-539cde99554be20f.js → webpack-c2782b330447ffb6.js} +1 -1
  66. package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +1 -0
  67. package/apps/pwa/out/approvals/index.html +1 -1
  68. package/apps/pwa/out/approvals/index.txt +14 -14
  69. package/apps/pwa/out/autopilot/index.html +1 -1
  70. package/apps/pwa/out/autopilot/index.txt +14 -14
  71. package/apps/pwa/out/board/index.html +1 -1
  72. package/apps/pwa/out/board/index.txt +14 -14
  73. package/apps/pwa/out/botlab/index.html +1 -1
  74. package/apps/pwa/out/botlab/index.txt +14 -14
  75. package/apps/pwa/out/bots/index.html +1 -1
  76. package/apps/pwa/out/bots/index.txt +14 -14
  77. package/apps/pwa/out/ceo/index.html +1 -1
  78. package/apps/pwa/out/ceo/index.txt +14 -14
  79. package/apps/pwa/out/chat/index.html +1 -1
  80. package/apps/pwa/out/chat/index.txt +14 -14
  81. package/apps/pwa/out/consult/index.html +1 -1
  82. package/apps/pwa/out/consult/index.txt +14 -14
  83. package/apps/pwa/out/control/index.html +1 -1
  84. package/apps/pwa/out/control/index.txt +14 -14
  85. package/apps/pwa/out/decisions/index.html +1 -0
  86. package/apps/pwa/out/decisions/index.txt +30 -0
  87. package/apps/pwa/out/design-check/index.html +1 -1
  88. package/apps/pwa/out/design-check/index.txt +14 -14
  89. package/apps/pwa/out/editor/index.html +1 -1
  90. package/apps/pwa/out/editor/index.txt +14 -14
  91. package/apps/pwa/out/exec/index.html +1 -1
  92. package/apps/pwa/out/exec/index.txt +14 -14
  93. package/apps/pwa/out/field/index.html +1 -1
  94. package/apps/pwa/out/field/index.txt +14 -14
  95. package/apps/pwa/out/healthz +1 -1
  96. package/apps/pwa/out/index.html +1 -1
  97. package/apps/pwa/out/index.txt +14 -14
  98. package/apps/pwa/out/inside/index.html +1 -1
  99. package/apps/pwa/out/inside/index.txt +14 -14
  100. package/apps/pwa/out/intake/index.html +1 -1
  101. package/apps/pwa/out/intake/index.txt +14 -14
  102. package/apps/pwa/out/live/index.html +1 -1
  103. package/apps/pwa/out/live/index.txt +14 -14
  104. package/apps/pwa/out/loops/index.html +1 -1
  105. package/apps/pwa/out/loops/index.txt +14 -14
  106. package/apps/pwa/out/market/index.html +1 -1
  107. package/apps/pwa/out/market/index.txt +14 -14
  108. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  109. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  110. package/apps/pwa/out/morning/index.html +1 -1
  111. package/apps/pwa/out/morning/index.txt +14 -14
  112. package/apps/pwa/out/observatory/index.html +1 -1
  113. package/apps/pwa/out/observatory/index.txt +14 -14
  114. package/apps/pwa/out/ops/checklist/index.html +1 -1
  115. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  116. package/apps/pwa/out/ops/release/index.html +1 -1
  117. package/apps/pwa/out/ops/release/index.txt +14 -14
  118. package/apps/pwa/out/ops/seats/index.html +1 -1
  119. package/apps/pwa/out/ops/seats/index.txt +14 -14
  120. package/apps/pwa/out/outputs/index.html +1 -1
  121. package/apps/pwa/out/outputs/index.txt +14 -14
  122. package/apps/pwa/out/reflection/index.html +1 -1
  123. package/apps/pwa/out/reflection/index.txt +15 -15
  124. package/apps/pwa/out/scheduler/index.html +1 -1
  125. package/apps/pwa/out/scheduler/index.txt +14 -14
  126. package/apps/pwa/out/sessions/index.html +1 -1
  127. package/apps/pwa/out/sessions/index.txt +14 -14
  128. package/apps/pwa/out/settings/devices/index.html +1 -1
  129. package/apps/pwa/out/settings/devices/index.txt +14 -14
  130. package/apps/pwa/out/settings/index.html +1 -1
  131. package/apps/pwa/out/settings/index.txt +14 -14
  132. package/apps/pwa/out/setup/done/index.html +1 -1
  133. package/apps/pwa/out/setup/done/index.txt +14 -14
  134. package/apps/pwa/out/setup/index.html +1 -1
  135. package/apps/pwa/out/setup/index.txt +14 -14
  136. package/apps/pwa/out/share/index.html +1 -1
  137. package/apps/pwa/out/share/index.txt +14 -14
  138. package/apps/pwa/out/showroom/index.html +1 -1
  139. package/apps/pwa/out/showroom/index.txt +14 -14
  140. package/apps/pwa/out/tasks/index.html +1 -1
  141. package/apps/pwa/out/tasks/index.txt +14 -14
  142. package/apps/pwa/out/term/index.html +1 -1
  143. package/apps/pwa/out/term/index.txt +14 -14
  144. package/apps/pwa/out/today/index.html +1 -1
  145. package/apps/pwa/out/today/index.txt +14 -14
  146. package/apps/pwa/out/trace/index.html +1 -1
  147. package/apps/pwa/out/trace/index.txt +14 -14
  148. package/apps/pwa/out/vault/index.html +1 -1
  149. package/apps/pwa/out/vault/index.txt +14 -14
  150. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  151. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  152. package/apps/pwa/out/workflows/index.html +1 -1
  153. package/apps/pwa/out/workflows/index.txt +14 -14
  154. package/apps/pwa/out/workspace/index.html +1 -1
  155. package/apps/pwa/out/workspace/index.txt +14 -14
  156. package/apps/pwa/out/worktrees/index.html +1 -1
  157. package/apps/pwa/out/worktrees/index.txt +14 -14
  158. package/package.json +2 -1
  159. package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
  160. package/samples/workflows/templates/chat-driven-research.yaml +35 -0
  161. package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
  162. package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
  163. package/samples/workflows/templates/manual-quickstart.yaml +21 -0
  164. package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
  165. package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
  166. package/scripts/ci-public-export-import-gate.ts +7 -2
  167. package/scripts/ci-typecheck-changed.ts +21 -6
  168. package/scripts/discord-seat-probe.ts +3 -2
  169. package/scripts/docs/generate-cli-anatomy.ts +162 -0
  170. package/scripts/goals/authoring-metrics.ts +73 -0
  171. package/scripts/landing-heal/must-fix-match.ts +7 -7
  172. package/scripts/landing-heal/run-mission.ts +3 -2
  173. package/scripts/public-export-test-run.ts +37 -2
  174. package/scripts/release-loop/checklist-gate-node.ts +13 -11
  175. package/scripts/release-loop/cut-branch.ts +2 -1
  176. package/scripts/release-loop/cutoff.ts +4 -2
  177. package/scripts/release-loop/docs-land-node.ts +8 -1
  178. package/scripts/release-loop/docs-node.ts +1 -1
  179. package/scripts/release-loop/gate-node.ts +99 -14
  180. package/scripts/release-loop/node-verdict.ts +2 -1
  181. package/scripts/release-loop/npm-publish-node.ts +11 -7
  182. package/scripts/release-loop/ops-upgrade-node.ts +8 -1
  183. package/scripts/release-loop/preflight.ts +246 -0
  184. package/scripts/release-loop/publish-node.ts +8 -1
  185. package/scripts/release-loop/release-readiness.ts +11 -6
  186. package/scripts/release-loop/release-version.ts +48 -0
  187. package/scripts/release-loop/resume-release.ts +115 -0
  188. package/scripts/release-loop/unattended-release.ts +43 -7
  189. package/scripts/release-loop/version-node.ts +145 -40
  190. package/scripts/rhythm/daily.ts +25 -4
  191. package/scripts/skills/public-check.ts +62 -12
  192. package/scripts/test-deterministic.ts +51 -33
  193. package/scripts/test-diet/effectiveness.ts +62 -1
  194. package/scripts/test-diet/lib.ts +1 -1
  195. package/scripts/test-diet/node.ts +8 -1
  196. package/src/agent-mission/review-loop.ts +4 -3
  197. package/src/agent-substrate/pr-comment-meta.ts +20 -0
  198. package/src/autopilot/build/llm-conflict-merge.ts +28 -8
  199. package/src/briefing/brief-items.ts +86 -17
  200. package/src/cli/autopilot-cli.ts +30 -11
  201. package/src/cli/brief-cli.ts +36 -2
  202. package/src/cli/freeze-cli.ts +3 -2
  203. package/src/cli/intake-cli.ts +73 -1
  204. package/src/cli/notify-cli.ts +75 -0
  205. package/src/cli/provider-cli.ts +12 -2
  206. package/src/cli/release-cli.ts +101 -17
  207. package/src/cli/tasks-cli.ts +309 -3
  208. package/src/context-bus/condense.ts +19 -3
  209. package/src/context-bus/context-day.ts +77 -24
  210. package/src/context-bus/context-first.ts +33 -0
  211. package/src/discord-self-message.ts +20 -2
  212. package/src/discord-slash-wire.ts +1 -0
  213. package/src/domains/outbound-alert.ts +200 -27
  214. package/src/domains/outbound-log-probe.ts +16 -1
  215. package/src/domains/release-tool.ts +2 -2
  216. package/src/execution-loop/gates.ts +11 -2
  217. package/src/flow-loop/split.ts +8 -4
  218. package/src/git-fs/worktree.ts +23 -3
  219. package/src/harness/harness-cli-command.ts +10 -2
  220. package/src/harness/harness-queue.ts +388 -33
  221. package/src/harness/helper-scan.ts +10 -3
  222. package/src/hq/seats.ts +369 -0
  223. package/src/index.ts +133 -15
  224. package/src/intake-plane/adopt-gate.ts +49 -0
  225. package/src/intake-plane/check.ts +3 -0
  226. package/src/intake-plane/collect-github.ts +57 -25
  227. package/src/intake-plane/discord-seat-work.ts +8 -2
  228. package/src/intake-plane/requirement-funnel.ts +316 -0
  229. package/src/knowledge/know-find.ts +10 -1
  230. package/src/loops/budget.ts +36 -0
  231. package/src/loops/orchestrator/finish-rate.ts +73 -1
  232. package/src/loops/orchestrator/grid-place.ts +11 -0
  233. package/src/loops/orchestrator/tick.ts +295 -20
  234. package/src/maturity/feature-maturity.ts +3 -2
  235. package/src/nexus/api/decisions-route.ts +11 -1
  236. package/src/nexus/api/loop-edges.ts +69 -13
  237. package/src/nexus/api/ops-seats-sources.ts +2 -2
  238. package/src/nexus/api/workflow-templates.ts +3 -0
  239. package/src/oauth/codex.ts +38 -0
  240. package/src/onboarding/first-chat-gate.ts +29 -0
  241. package/src/release-loop/checklist.ts +104 -6
  242. package/src/release-loop/feature-store.ts +10 -9
  243. package/src/release-loop/landing-freeze.ts +6 -4
  244. package/src/release-loop/placement.ts +13 -3
  245. package/src/release-loop/rubric.ts +80 -0
  246. package/src/seat-dispatch/seat-ask.ts +79 -16
  247. package/src/seat-loop/seat-loop.ts +12 -6
  248. package/src/self-dev/ask-launch-flow.ts +24 -3
  249. package/src/self-dev/ask-path-candidates.ts +68 -0
  250. package/src/self-dev/dev-pipeline.ts +4 -0
  251. package/src/self-dev/draft-triage-rules.ts +1 -1
  252. package/src/self-dev/orchestrate-cli.ts +22 -0
  253. package/src/self-dev/round-pr-supersede.ts +244 -0
  254. package/src/self-dev/run-supervisor.ts +14 -0
  255. package/src/self-implement/follow-up-goals.ts +2 -2
  256. package/src/self-implement/gate-remote.ts +427 -0
  257. package/src/self-implement/goal-author.ts +8 -3
  258. package/src/self-implement/l8-merge-queue.ts +29 -5
  259. package/src/self-implement/orchestrator.ts +59 -12
  260. package/src/self-implement/prior-draft-findings.ts +4 -3
  261. package/src/skills/skill-feedback.ts +76 -0
  262. package/src/task-agent/actions.ts +39 -6
  263. package/src/task-agent/judge.ts +31 -5
  264. package/src/task-agent/mission.ts +423 -0
  265. package/src/task-agent/shadow.ts +90 -0
  266. package/src/task-agent/task-hand.ts +235 -0
  267. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +10 -0
  268. package/src/task-orchestrator/surfaces/self-implement-pod.ts +86 -18
  269. package/src/telegram-loops-command.ts +1 -1
  270. package/src/telegram.ts +5 -20
  271. package/src/user-config.ts +63 -3
  272. package/src/version/packed-revision.json +1 -1
  273. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
  274. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
  275. package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
  276. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
  277. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
  278. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
  279. package/apps/pwa/out/_next/static/chunks/6877-fe94e76a5841ee9b.js +0 -1
  280. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
  281. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
  282. package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +0 -1
  283. package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
  284. package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +0 -1
  285. package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +0 -1
  286. package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +0 -1
  287. package/apps/pwa/out/_next/static/chunks/app/chat/page-9a41d65355fc25b9.js +0 -1
  288. package/apps/pwa/out/_next/static/chunks/app/control/page-41afec0b255ed2bb.js +0 -1
  289. package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +0 -1
  290. package/apps/pwa/out/_next/static/chunks/app/layout-de43a186e5ba5683.js +0 -1
  291. package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +0 -1
  292. package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +0 -1
  293. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +0 -1
  294. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a04260b8ccc968d4.js +0 -1
  295. package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +0 -1
  296. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +0 -1
  297. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-5a64cb5208c6a8fd.js +0 -1
  298. package/apps/pwa/out/_next/static/chunks/app/settings/page-1407ec83ea64c1f9.js +0 -1
  299. package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +0 -1
  300. package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +0 -1
  301. package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +0 -1
  302. package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +0 -1
  303. package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +0 -1
  304. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-fc2df261f8a2c7df.js +0 -1
  305. package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +0 -1
  306. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +0 -1
  307. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
  308. package/apps/pwa/out/_next/static/yRA5ur6DcPl0YHSRheH2O/_buildManifest.js +0 -1
  309. /package/apps/pwa/out/_next/static/{yRA5ur6DcPl0YHSRheH2O → P6YTVgzuZz5oTZhY4Din_}/_ssgManifest.js +0 -0
@@ -0,0 +1,80 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import type { ChecklistItem } from './checklist.js';
5
+
6
+ export interface Rubric {
7
+ A: number;
8
+ E: number;
9
+ R: number;
10
+ D: number;
11
+ M: number;
12
+ B: number;
13
+ S: number;
14
+ X: number;
15
+ }
16
+
17
+ const AXES = ['A', 'E', 'R', 'D', 'M', 'B', 'S', 'X'] as const;
18
+
19
+ /** Read one explicit rubric line; an old score in parentheses is not an axis. */
20
+ export function parseRubric(text: string): Rubric | null {
21
+ for (const line of text.split(/\r?\n/)) {
22
+ const match = /(?:^|\s)루브릭:\s*([^\r\n]*)/.exec(line);
23
+ if (!match) continue;
24
+ const values: Partial<Rubric> = {};
25
+ const body = match[1]!.replace(/\([^)]*\)/g, ' ');
26
+ for (const token of body.trim().split(/\s+/)) {
27
+ const axis = /^([AERDMBSX])([0-3])$/.exec(token);
28
+ if (axis) values[axis[1] as keyof Rubric] = Number(axis[2]);
29
+ }
30
+ if (AXES.every((axis) => values[axis] !== undefined)) return values as Rubric;
31
+ }
32
+ return null;
33
+ }
34
+
35
+ export function rubricScore(r: Rubric): number {
36
+ return 2 * r.A + 2 * r.E + 2 * r.R + 1.5 * r.D + r.M + r.B - 0.5 * r.S - 0.5 * r.X;
37
+ }
38
+
39
+ export function rubricGrade(score: number): 'P1' | 'P2' | 'P3' | 'P4' {
40
+ if (score >= 11) return 'P1';
41
+ if (score >= 8) return 'P2';
42
+ if (score >= 5) return 'P3';
43
+ return 'P4';
44
+ }
45
+
46
+ /** Inspect an existing checklist without initializing, importing, or migrating its ledger. */
47
+ export function readRubricItems(version: string, ledgerRoot: string): Pick<ChecklistItem, 'id' | 'title' | 'evidence' | 'priority'>[] {
48
+ const dbPath = join(ledgerRoot, 'release', 'features.sqlite');
49
+ if (existsSync(dbPath)) {
50
+ const db = new Database(dbPath, { readonly: true, strict: true });
51
+ try {
52
+ const columns = new Set((db.query('PRAGMA table_info(assignments)').all() as Array<{ name: string }>).map((row) => row.name));
53
+ const priority = columns.has('priority') ? 'a.priority' : 'NULL';
54
+ const rows = db.query(`SELECT f.id, COALESCE(a.title_override, f.title) AS title, a.evidence, ${priority} AS priority
55
+ FROM assignments a JOIN features f ON f.id = a.feature_id WHERE a.version = ? ORDER BY a.rowid`)
56
+ .all(version) as Array<{ id: string; title: string; evidence: string | null; priority: ChecklistItem['priority'] | null }>;
57
+ const imported = db.query('SELECT 1 FROM imported_versions WHERE version = ?').get(version);
58
+ if (rows.length || imported) {
59
+ const refs = db.query('SELECT feature_id, ref FROM evidence WHERE version = ? ORDER BY rowid')
60
+ .all(version) as Array<{ feature_id: string; ref: string }>;
61
+ const refsById = new Map<string, string[]>();
62
+ for (const { feature_id, ref } of refs) {
63
+ const list = refsById.get(feature_id) ?? [];
64
+ list.push(ref);
65
+ refsById.set(feature_id, list);
66
+ }
67
+ return rows.map((row) => {
68
+ const evidence = [row.evidence, ...(refsById.get(row.id) ?? [])]
69
+ .filter((value): value is string => value !== null).join('\n');
70
+ return { id: row.id, title: row.title, ...(evidence ? { evidence } : {}), ...(row.priority ? { priority: row.priority } : {}) };
71
+ });
72
+ }
73
+ } finally { db.close(); }
74
+ }
75
+ const path = join(ledgerRoot, 'release', version, 'checklist.json');
76
+ if (!existsSync(path)) return [];
77
+ const legacy = JSON.parse(readFileSync(path, 'utf8')) as { version: string; items: ChecklistItem[] };
78
+ if (legacy.version !== version || !Array.isArray(legacy.items)) throw new Error(`잘못된 체크리스트: ${path}`);
79
+ return legacy.items;
80
+ }
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from 'node:crypto';
2
2
  import { join } from 'node:path';
3
3
  import { debug } from '../debug/log.js';
4
4
  import { openMsgStore, type MsgStore } from '../msg/msg-store.js';
5
- import { dispatchCeoTask, type CeoCommandDeps } from './ceo-commands.js';
5
+ import { CEO_SEAT_COMMANDS, dispatchCeoTask, type CeoCommandDeps } from './ceo-commands.js';
6
6
  import type { SeatId } from './seat-questions.js';
7
7
 
8
8
  export type AskOrigin =
@@ -23,7 +23,7 @@ function createTable(store: MsgStore): void {
23
23
  answer TEXT, answered_at INTEGER, asked_at INTEGER
24
24
  ); CREATE TABLE IF NOT EXISTS seat_ask_outbox (
25
25
  id TEXT PRIMARY KEY, origin TEXT NOT NULL, text TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending',
26
- claim_until INTEGER NOT NULL DEFAULT 0, claim_token TEXT
26
+ claim_until INTEGER NOT NULL DEFAULT 0, claim_token TEXT, sent_at INTEGER
27
27
  )`);
28
28
  const askColumns = store.db.query('PRAGMA table_info(seat_asks)').all() as Array<{ name: string }>;
29
29
  if (!askColumns.some(({ name }) => name === 'answer')) store.db.exec('ALTER TABLE seat_asks ADD COLUMN answer TEXT');
@@ -32,6 +32,7 @@ function createTable(store: MsgStore): void {
32
32
  const columns = store.db.query('PRAGMA table_info(seat_ask_outbox)').all() as Array<{ name: string }>;
33
33
  if (!columns.some(({ name }) => name === 'claim_until')) store.db.exec('ALTER TABLE seat_ask_outbox ADD COLUMN claim_until INTEGER NOT NULL DEFAULT 0');
34
34
  if (!columns.some(({ name }) => name === 'claim_token')) store.db.exec('ALTER TABLE seat_ask_outbox ADD COLUMN claim_token TEXT');
35
+ if (!columns.some(({ name }) => name === 'sent_at')) store.db.exec('ALTER TABLE seat_ask_outbox ADD COLUMN sent_at INTEGER');
35
36
  }
36
37
 
37
38
  export interface SeatAskShadow {
@@ -77,10 +78,24 @@ export interface SeatAskDeps {
77
78
  botId?: string;
78
79
  }
79
80
 
80
- /** Persist the return address before delivering through the /cto channel and seat inbox. */
81
- export async function askSeat(text: string, origin: AskOrigin, command: CeoCommandDeps, deps: SeatAskDeps): Promise<string> {
81
+ const seatTitle = (seat: SeatId): string => Object.entries(CEO_SEAT_COMMANDS)
82
+ .find(([, id]) => id === seat)![0]!.toUpperCase();
83
+
84
+ /** Persist the return address before delivering through the coordination channel and seat inbox. */
85
+ export function askSeatAs(seat: SeatId, body: string, origin: AskOrigin, command: CeoCommandDeps, deps: SeatAskDeps,
86
+ kind: 'task' | 'question' = 'task'): Promise<string> {
87
+ return recordAndDispatchSeatAsk(seat, body, origin, command, deps, kind);
88
+ }
89
+
90
+ /** Preserve the CTO question form and its original receipt. */
91
+ export function askSeat(text: string, origin: AskOrigin, command: CeoCommandDeps, deps: SeatAskDeps): Promise<string> {
82
92
  const body = parseSeatAsk(text);
83
93
  if (!body) throw new Error('not a CTO ask');
94
+ return askSeatAs('TC', body, origin, command, deps, 'question');
95
+ }
96
+
97
+ async function recordAndDispatchSeatAsk(seat: SeatId, body: string, origin: AskOrigin, command: CeoCommandDeps, deps: SeatAskDeps,
98
+ kind: 'task' | 'question'): Promise<string> {
84
99
  const id = randomUUID();
85
100
  const timeout = deps.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS;
86
101
  if (!Number.isSafeInteger(timeout) || timeout < 1) throw new Error('invalid seat ask timeout');
@@ -90,13 +105,14 @@ export async function askSeat(text: string, origin: AskOrigin, command: CeoComma
90
105
  createTable(store);
91
106
  const askedAt = (deps.now ?? Date.now)();
92
107
  store.db.query('INSERT INTO seat_asks (id, seat, origin, deadline, timeout_minutes, status, asked_at) VALUES (?, ?, ?, ?, ?, ?, ?)')
93
- .run(id, 'TC', JSON.stringify(origin), askedAt + timeout, minutes, 'pending', askedAt);
108
+ .run(id, seat, JSON.stringify(origin), askedAt + timeout, minutes, 'pending', askedAt);
94
109
  } finally { store.close(); }
95
110
  try {
96
- const result = await (deps.dispatch ?? dispatchCeoTask)('TC', `질문: ${body}\n답장 요청: ${id}\n회신: elanous seat answer ${id} "<답>"`, command,
111
+ const result = await (deps.dispatch ?? dispatchCeoTask)(seat, `${kind === 'question' ? '질문' : '일'}: ${body}\n답장 요청: ${id}\n회신: elanous seat answer ${id} "<답>"`, command,
97
112
  { via: origin.channel, ref: `ask:${id}` });
98
- debug.log('seat.ask', 'sent', { seat: 'TC', id, via: origin.channel, channel: result.channel });
99
- return `CTO에게 물었습니다. 답을 기다립니다 (최대 ${minutes}분). 요청: ${id}${result.channel === 'failed' ? ` · 조율 채널 전송 실패: ${result.channelError}` : ''}`;
113
+ debug.log('seat.ask', 'sent', { seat, id, via: origin.channel, channel: result.channel });
114
+ const title = seatTitle(seat);
115
+ return `${kind === 'question' ? `${title}에게 물었습니다. 답을 기다립니다` : `${title}에게 맡겼습니다. 결과를 이 대화로 돌려드립니다`} (최대 ${minutes}분). 요청: ${id}${result.channel === 'failed' ? ` · 조율 채널 전송 실패: ${result.channelError}` : ''}`;
100
116
  } catch (error) {
101
117
  const failed = (deps.open ?? openMsgStore)();
102
118
  try { createTable(failed); failed.db.query('DELETE FROM seat_asks WHERE id = ?').run(id); }
@@ -114,10 +130,11 @@ export function answerSeatAsk(id: string, text: string, deps: Pick<SeatAskDeps,
114
130
  createTable(store);
115
131
  const now = (deps.now ?? Date.now)();
116
132
  const updated = store.db.query(`UPDATE seat_asks SET answer = ?, answered_at = ?
117
- WHERE id = ? AND seat = 'TC' AND status = 'pending' AND answer IS NULL AND deadline > ?`)
133
+ WHERE id = ? AND status = 'pending' AND answer IS NULL AND deadline > ?`)
118
134
  .run(answer, now, id, now);
119
135
  if (updated.changes !== 1) throw new Error(`seat ask not pending or deadline passed: ${id}`);
120
- debug.log('seat.ask', 'reply-recorded', { seat: 'TC', id });
136
+ const row = store.db.query('SELECT seat FROM seat_asks WHERE id = ?').get(id) as { seat: string };
137
+ debug.log('seat.ask', 'reply-recorded', { seat: row.seat, id });
121
138
  } finally { store.close(); }
122
139
  }
123
140
 
@@ -133,6 +150,49 @@ export function seatAskReplyInfo(id: string, open: () => MsgStore = openMsgStore
133
150
  } finally { store.close(); }
134
151
  }
135
152
 
153
+ export interface SeatAskRoundTrip {
154
+ id: string;
155
+ surface: AskOrigin['channel'];
156
+ askedAt: number | null;
157
+ answeredAt: number | null;
158
+ deliveredAt: number | null;
159
+ roundTripMs: number | null;
160
+ verdict: '10분 안' | '10분 넘음' | '미답' | '답 대기' | '전달 대기' | '시각 미상';
161
+ }
162
+
163
+ /** Read one row per CTO question, including undelivered and legacy rows. since is an epoch-millisecond lower bound. */
164
+ export function listSeatAskRoundTrips({ since, now = Date.now(), open = openMsgStore }: {
165
+ since: number; now?: number; open?: () => MsgStore;
166
+ }): SeatAskRoundTrip[] {
167
+ if (!Number.isFinite(since) || !Number.isFinite(now)) throw new Error('invalid seat ask time window');
168
+ const store = open();
169
+ try {
170
+ createTable(store);
171
+ const rows = store.db.query(`SELECT a.id, a.origin, a.asked_at, a.answered_at, a.deadline, a.status,
172
+ a.answer IS NOT NULL AS has_answer, o.status AS outbox_status, o.sent_at
173
+ FROM seat_asks a LEFT JOIN seat_ask_outbox o ON o.id = a.id
174
+ WHERE a.seat = 'TC' AND a.status != 'shadow'
175
+ AND COALESCE(a.asked_at, a.deadline - a.timeout_minutes * 60000) >= ?
176
+ ORDER BY COALESCE(a.asked_at, a.deadline - a.timeout_minutes * 60000) DESC, a.id DESC`)
177
+ .all(since) as Array<{ id: string; origin: string; asked_at: number | null; answered_at: number | null;
178
+ deadline: number; status: string; has_answer: number; outbox_status: string | null; sent_at: number | null }>;
179
+ return rows.map(row => {
180
+ const surface = (JSON.parse(row.origin) as AskOrigin).channel;
181
+ const deliveredAt = row.outbox_status === 'sent' ? row.sent_at : null;
182
+ const roundTripMs = row.answered_at === null || row.asked_at === null || deliveredAt === null ? null : deliveredAt - row.asked_at;
183
+ const verdict: SeatAskRoundTrip['verdict'] = row.asked_at === null ? '시각 미상'
184
+ : row.status === 'expired' || (!row.has_answer && row.status === 'pending' && now >= row.deadline) ? '미답'
185
+ : row.status === 'answered' || row.has_answer ? row.outbox_status === 'sent' && deliveredAt === null ? '시각 미상'
186
+ : deliveredAt === null ? '전달 대기'
187
+ : row.answered_at === null || roundTripMs === null ? '시각 미상'
188
+ : roundTripMs <= 600_000 ? '10분 안' : '10분 넘음'
189
+ : '답 대기';
190
+ return { id: row.id, surface, askedAt: row.asked_at, answeredAt: row.answered_at,
191
+ deliveredAt, roundTripMs, verdict };
192
+ });
193
+ } finally { store.close(); }
194
+ }
195
+
136
196
  /** Deliver recorded seat answers or expiration notices via the original surface. */
137
197
  export async function deliverSeatAnswers(deps: SeatAskDeps & { askId?: string }): Promise<void> {
138
198
  const store = (deps.open ?? openMsgStore)();
@@ -150,9 +210,11 @@ export async function deliverSeatAnswers(deps: SeatAskDeps & { askId?: string })
150
210
  : reply && Date.parse(reply.created_at) < ask.deadline ? reply.body.slice(id.length + 2).trim() : '';
151
211
  const status = answer ? 'answered' : now >= ask.deadline ? 'expired' : null;
152
212
  if (!status) return null;
153
- const text = status === 'answered' ? `CTO 답변 (${id}): ${answer}`
154
- : `CTO 미답 (${id}): 아직 답 없음 (${ask.timeout_minutes}분 경과).`;
155
- const updated = store.db.query("UPDATE seat_asks SET status = ? WHERE id = ? AND status = 'pending'").run(status, id);
213
+ const title = seatTitle(ask.seat as SeatId);
214
+ const text = status === 'answered' ? `${title} 답변 (${id}): ${answer}`
215
+ : `${title} 미답 (${id}): 아직 답 없음 (${ask.timeout_minutes}분 경과).`;
216
+ const updated = store.db.query("UPDATE seat_asks SET status = ?, answered_at = COALESCE(answered_at, ?) WHERE id = ? AND status = 'pending'")
217
+ .run(status, status === 'answered' && reply ? Date.parse(reply.created_at) : null, id);
156
218
  if (updated.changes !== 1) return null;
157
219
  store.db.query('INSERT INTO seat_ask_outbox (id, origin, text) VALUES (?, ?, ?)').run(id, ask.origin, text);
158
220
  return status;
@@ -184,7 +246,8 @@ export async function deliverSeatAnswers(deps: SeatAskDeps & { askId?: string })
184
246
  if (claimed.changes !== 1) continue;
185
247
  try {
186
248
  await deps.send(origin, item.text);
187
- store.db.query("UPDATE seat_ask_outbox SET status = 'sent', claim_token = NULL WHERE id = ? AND claim_token = ?").run(item.id, token);
249
+ store.db.query("UPDATE seat_ask_outbox SET status = 'sent', sent_at = ?, claim_token = NULL WHERE id = ? AND claim_token = ?")
250
+ .run((deps.now ?? Date.now)(), item.id, token);
188
251
  } catch (error) {
189
252
  store.db.query('UPDATE seat_ask_outbox SET claim_until = 0, claim_token = NULL WHERE id = ? AND claim_token = ?').run(item.id, token);
190
253
  throw error;
@@ -209,7 +272,7 @@ export function acknowledgeSeatAnswers(clientId: string, ids: string[], open: ()
209
272
  const store = open();
210
273
  try {
211
274
  createTable(store);
212
- const ack = store.db.query("UPDATE seat_ask_outbox SET status = 'sent' WHERE id = ? AND origin = ? AND status = 'pending'");
213
- for (const id of ids) ack.run(id, JSON.stringify({ channel: 'pwa', clientId }));
275
+ const ack = store.db.query("UPDATE seat_ask_outbox SET status = 'sent', sent_at = ? WHERE id = ? AND origin = ? AND status = 'pending'");
276
+ for (const id of ids) ack.run(Date.now(), id, JSON.stringify({ channel: 'pwa', clientId }));
214
277
  } finally { store.close(); }
215
278
  }
@@ -6,7 +6,7 @@ import { dirname, join, resolve } from 'node:path';
6
6
  import { promisify } from 'node:util';
7
7
  import { TRACK_AGENT_FORBIDDEN_ACTION_REGEX, universeLaunch } from '../autopilot/track-agent.js';
8
8
  import { debug } from '../debug/log.js';
9
- import { surfaceEventsDbPath } from '../domains/surface-events.js';
9
+ import { openSurfaceEventsDb, recordEvent, surfaceEventsDbPath } from '../domains/surface-events.js';
10
10
  import { judgeNeighbor } from '../loops/neighbors.js';
11
11
  import { addHarnessQueue, harnessQueueIdForKey, harnessQueueOutcome, listHarnessQueue } from '../harness/harness-queue.js';
12
12
  import { isRescueAllowedAction } from '../steward/rescue.js';
@@ -68,7 +68,7 @@ export type SeatDeps = {
68
68
  now?: () => Date;
69
69
  read?: (path: string) => string;
70
70
  run?: (args: string[]) => Promise<string>;
71
- enqueue?: (seat: string, text: string, root: string, idempotencyKey: string) => Promise<{ id: string }>;
71
+ enqueue?: (seat: string, text: string, root: string, idempotencyKey: string, item: SeatItem) => Promise<{ id: string }>;
72
72
  queueOutcome?: (id: string, root: string) => ReturnType<typeof harnessQueueOutcome>;
73
73
  queueItems?: (root: string) => ReturnType<typeof listHarnessQueue>;
74
74
  queueIdForKey?: (key: string, root: string) => string | undefined;
@@ -910,11 +910,11 @@ function neighborLastSeen(root: string, isolated: boolean, now: Date, wanted: Re
910
910
  if (!existsSync(path)) return last;
911
911
  const db = new Database(path, { readonly: true });
912
912
  try {
913
- // Same outbound seat events and refs.seat as context day; unlike its 24h display window,
914
- // look back across the whole journal so a genuinely old ACK can be judged absent.
913
+ // Read outbound seat context and loop heartbeats across the whole journal,
914
+ // unlike context day's 24h display window, so a genuinely old ACK can be judged absent.
915
915
  // Newest first and stop once every wanted seat has an ACK, so a long journal is not read and sorted in full each turn.
916
916
  const statement = db.prepare(`SELECT ts, refs FROM events
917
- WHERE surface IN ('coord:channel', 'context:session') AND direction='outbound' AND ts<=? ORDER BY ts DESC`);
917
+ WHERE surface IN ('coord:channel', 'context:session', 'loop:heartbeat') AND direction='outbound' AND ts<=? ORDER BY ts DESC`);
918
918
  try {
919
919
  const events = statement.iterate(now.toISOString()) as Iterable<{ ts: string; refs: string | null }>;
920
920
  for (const event of events) {
@@ -996,6 +996,12 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
996
996
  const directory = dirname(path);
997
997
  return withSeatLock(directory, async () => {
998
998
  const stateRoot = deps.root ?? effectiveInstanceRoot();
999
+ try {
1000
+ const db = openSurfaceEventsDb(deps.root ? join(stateRoot, 'surface_events.db') : surfaceEventsDbPath());
1001
+ try { recordEvent(db, { surface: 'loop:heartbeat', direction: 'outbound', kind: 'heartbeat',
1002
+ text: `seat loop ${seat} heartbeat`, refs: JSON.stringify({ seat }), ts: now.toISOString() }); }
1003
+ finally { db.close(); }
1004
+ } catch (error) { observe('heartbeat-write-failed', { seat, error: String(error).slice(0, 200) }); }
999
1005
  const recorded = readSeatLedger(seat, deps, now);
1000
1006
  const ledger = config.mode === 'live-safe' ? reconciledQueueLedger(recorded, stateRoot, deps) : recorded;
1001
1007
  let turnError: unknown;
@@ -1556,7 +1562,7 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
1556
1562
  }
1557
1563
  let queued: { id: string };
1558
1564
  try {
1559
- queued = await (deps.enqueue ?? ((assigned, text, root, idempotencyKey) => addHarnessQueue({ seat: assigned, say: text, idempotencyKey }, { root })))(seat, action.text, stateRoot, queueKey(seat, item));
1565
+ queued = await (deps.enqueue ?? ((assigned, text, root, idempotencyKey) => addHarnessQueue({ seat: assigned, say: text, idempotencyKey }, { root })))(seat, action.text, stateRoot, queueKey(seat, item), item);
1560
1566
  } catch (error) {
1561
1567
  // Same key, different task body: never adopt the other work. Refuse this turn; the key frees once that item finishes.
1562
1568
  if (!/idempotency key collision/.test(String(error))) throw error;
@@ -12,6 +12,7 @@
12
12
  // 그래서 결과를 «세 값»으로 낸다: 발사 / 저작 전 중단 / 전제 검사 중단.
13
13
  import { linesOutsideFencedCode } from '../self-implement/goal-author.js';
14
14
  import { recordAuthoredGoal } from './authored-goal-trace.js';
15
+ import { findAskPathCandidates, type AskPathCandidateOptions } from './ask-path-candidates.js';
15
16
  import { retiredEntranceNotice, type EntranceDeclaration } from './entrance-registry.js';
16
17
  import {
17
18
  classifyConcurrentAuthoring,
@@ -164,6 +165,8 @@ export interface AskLaunchFlowDeps {
164
165
  /** ⛔ 못 얻으면 `null` — 같은 이유. */
165
166
  recentAuthoringSamples(): RecentAuthoringSample[] | null;
166
167
  authorGoal(args: readonly string[], options: Record<string, unknown>): Promise<AskAuthoredGoal>;
168
+ /** Code-search seam for the one automatic no-target-paths retry. */
169
+ findPathCandidates?: (askText: string, options: AskPathCandidateOptions) => ReturnType<typeof findAskPathCandidates>;
167
170
  /** AUTHOR-TRACE seam (tests). */
168
171
  recordAuthoredGoal?: typeof recordAuthoredGoal;
169
172
  /** Pod launches reserve a host slot before goal authoring begins. */
@@ -849,7 +852,7 @@ export async function runAskLaunchFlow(
849
852
  if (launchPreflight === false) return { kind: 'stopped-before-authoring' };
850
853
 
851
854
  await deps.beforeAuthoring?.();
852
- const authorAndPreflight = async (): Promise<{
855
+ const authorAndPreflight = async (authorAskText: string = input.askText): Promise<{
853
856
  readonly goalFile: string;
854
857
  readonly decision: ReturnType<typeof decideAskPreflight>;
855
858
  }> => {
@@ -868,7 +871,7 @@ export async function runAskLaunchFlow(
868
871
  };
869
872
  // ⭐ RFC §5 5단계 — 확인된 implement 저작만 활성 구현 그래프에 이름을 잇는다.
870
873
  observeFrontNodeEntry('author', { ...frontObservation, provenance: 'authoring-start' });
871
- const authored = await deps.authorGoal([input.askText], {
874
+ const authored = await deps.authorGoal([authorAskText], {
872
875
  cwd: deps.cwd(),
873
876
  ...(input.groundingCwd === undefined ? {} : { groundingCwd: input.groundingCwd }),
874
877
  ...(input.authorGrade === undefined ? {} : { authorGrade: input.authorGrade }),
@@ -1011,8 +1014,26 @@ export async function runAskLaunchFlow(
1011
1014
  const first = authored;
1012
1015
  reauthored = true;
1013
1016
  deps.print('[ask] ⑴ ⚠️ 대상 경로를 못 찾아 자동 재저작 한 번을 시도한다');
1014
- authored = await authorAndPreflight();
1017
+ let candidates: ReturnType<typeof findAskPathCandidates> = { tokens: [], paths: [] };
1018
+ if (parseAskTargetPathHintsResult(input.askText).labelMissing) {
1019
+ try {
1020
+ candidates = (deps.findPathCandidates ?? findAskPathCandidates)(input.askText, { cwd: deps.cwd() });
1021
+ } catch (error) {
1022
+ candidates = { tokens: [], paths: [], error: error instanceof Error ? error.message : String(error) };
1023
+ }
1024
+ deps.log('ask-auto-target-candidates', {
1025
+ tokens: candidates.tokens, paths: candidates.paths,
1026
+ ...(candidates.error === undefined ? {} : { error: candidates.error }),
1027
+ }, candidates.error === undefined ? 'info' : 'warn');
1028
+ }
1029
+ if (candidates.paths.length > 0) {
1030
+ deps.print(`[ask] ⑴ 코드에서 후보 경로 ${candidates.paths.length}개를 찾아 넣고 다시 저작한다: ${candidates.paths.join(' · ')}`);
1031
+ }
1032
+ authored = await authorAndPreflight(candidates.paths.length > 0
1033
+ ? `대상 경로: ${candidates.paths.join(' · ')}\n${input.askText}`
1034
+ : input.askText);
1015
1035
  deps.log('ask-auto-reauthor', {
1036
+ candidatePathCount: candidates.paths.length,
1016
1037
  firstGoalFile: first.goalFile,
1017
1038
  secondGoalFile: authored.goalFile,
1018
1039
  firstPathCount: first.decision.result.paths.length,
@@ -0,0 +1,68 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { statSync } from 'node:fs';
3
+ import { isAbsolute, relative, resolve } from 'node:path';
4
+
5
+ export interface AskPathCandidateOptions {
6
+ readonly cwd: string;
7
+ readonly run?: (command: string, args: readonly string[], options: { cwd: string; encoding: 'utf8' }) => string;
8
+ }
9
+
10
+ export type AskPathCandidates =
11
+ | { readonly tokens: string[]; readonly paths: string[]; readonly error?: undefined }
12
+ | { readonly tokens: string[]; readonly paths: string[]; readonly error: string };
13
+
14
+ const STOP_WORDS = new Set(['the', 'and', 'test', 'src', 'scripts', 'file', 'path', 'paths', 'code', 'with', 'from', 'this', 'that', 'into', 'for', 'then', 'when', 'have', 'does', 'should', 'will', 'please', 'target', 'change']);
15
+ const WORD = /[-/]?[A-Za-z][A-Za-z0-9_.-]*(?:\/[A-Za-z0-9_.-]+)*/g;
16
+
17
+ function existingFile(fragment: string, cwd: string): string | null {
18
+ const path = fragment.replace(/^\.\//, '');
19
+ if (!path.includes('/') && !/\.[A-Za-z][A-Za-z0-9]*$/.test(path)) return null;
20
+ const absolute = resolve(cwd, path);
21
+ const rel = relative(cwd, absolute);
22
+ if (!rel || rel.startsWith('..') || isAbsolute(rel)) return null;
23
+ try { return statSync(absolute).isFile() ? rel.split('\\').join('/') : null; }
24
+ catch { return null; }
25
+ }
26
+
27
+ /** Find existing code paths by lexical evidence from an unmodified ask. */
28
+ export function findAskPathCandidates(askText: string, { cwd, run = execFileSync }: AskPathCandidateOptions): AskPathCandidates {
29
+ const scores = new Map<string, Set<string>>();
30
+ const tokens: string[] = [];
31
+ const seen = new Set<string>();
32
+ const chunks = [...askText.matchAll(/`([^`]+)`/g)].map((match) => match[1]!).concat([askText]);
33
+ for (const chunk of chunks) {
34
+ for (const match of chunk.matchAll(WORD)) {
35
+ const raw = match[0];
36
+ const token = raw.replace(/^\//, '');
37
+ const file = existingFile(token, cwd);
38
+ if (file) {
39
+ const hits = scores.get(file) ?? new Set<string>();
40
+ hits.add(token);
41
+ scores.set(file, hits);
42
+ }
43
+ if (STOP_WORDS.has(token.toLowerCase()) || seen.has(token)) continue;
44
+ if (!file && !token.includes('/') && !token.includes('.') && !token.includes('-') && !token.includes('_')
45
+ && !/[a-z][A-Z]/.test(token) && token.length < 4) continue;
46
+ seen.add(token);
47
+ tokens.push(token);
48
+ }
49
+ }
50
+ for (const token of tokens) {
51
+ let matches: string;
52
+ try {
53
+ matches = run('rg', ['-l', '-F', '--glob', '!*.test.ts', '--', token, 'src', 'scripts'], { cwd, encoding: 'utf8' });
54
+ } catch (error) {
55
+ if ((error as { status?: number }).status === 1) continue; // rg: no matches
56
+ return { tokens, paths: [], error: error instanceof Error ? error.message : String(error) };
57
+ }
58
+ for (const path of matches.split(/\r?\n/).filter(Boolean)) {
59
+ const hits = scores.get(path) ?? new Set<string>();
60
+ hits.add(token);
61
+ scores.set(path, hits);
62
+ }
63
+ }
64
+ return {
65
+ tokens,
66
+ paths: [...scores].sort(([a, aHits], [b, bHits]) => bHits.size - aHits.size || a.localeCompare(b, 'en')).slice(0, 5).map(([path]) => path),
67
+ };
68
+ }
@@ -721,6 +721,8 @@ export function buildSelfImplementDevSpec(o: {
721
721
  ground?: boolean;
722
722
  draft: boolean;
723
723
  autoMerge?: boolean;
724
+ /** Pod child: stop at merge-ready so the launching host re-gates (and checks the landing freeze) before merging. */
725
+ mergeByHost?: boolean;
724
726
  openPr?: boolean;
725
727
  autoReview?: boolean;
726
728
  autoReviewSource?: DevSelectionSource;
@@ -755,6 +757,8 @@ export function buildSelfImplementDevSpec(o: {
755
757
  ...(o.runId ? { runId: o.runId } : {}),
756
758
  self: {
757
759
  draft: o.draft,
760
+ // FREEZE-POD: this flag used to be dropped here, so `self implement --merge-by-host` in a Pod merged by itself (#24478).
761
+ ...(o.mergeByHost === true ? { mergeByHost: true } : {}),
758
762
  ...(o.childLlm ? { childLlm: o.childLlm } : {}),
759
763
  ...(o.maxWaitSec !== undefined ? { maxWaitSec: o.maxWaitSec } : {}),
760
764
  ...(o.ground ? { ground: true } : {}),
@@ -31,7 +31,7 @@ export type DraftDecision = { action: 'close' | 'keep'; reason: string };
31
31
 
32
32
  // Labels the sweeper must never close: an approval-waiting state and the human «keep» / release holds (R-PRK3).
33
33
  // RELGUARD's release-path hold waits for OP approval — a long wait is not «stale» (TC review #23217).
34
- const PROTECTED_LABELS: ReadonlySet<string> = new Set<string>([...PR_LABELS
34
+ export const PROTECTED_LABELS: ReadonlySet<string> = new Set<string>([...PR_LABELS
35
35
  .filter((label) => label.sweep.action === 'exclude' || (label.sweep.action === 'none' && (label.axis === 'state' || label.axis === 'addon')))
36
36
  .map((label) => label.name), RELEASE_PATH_LABEL]);
37
37
 
@@ -16,6 +16,7 @@ import { resolveOrchestrateConcurrency } from './orchestrate.js';
16
16
  import type { FailureClassification, SelfDevGoal, SelfDevJobResult } from './orchestrate.js';
17
17
  import { superviseRun, type SupervisorDecision } from './run-supervisor.js';
18
18
  import { readDecomposeProposals, applyDecomposeProposals } from './decompose-proposal.js';
19
+ import { createRoundPrTracker, defaultRoundPrAdapters, type RoundPrAdapters } from './round-pr-supersede.js';
19
20
  import { createHash } from 'node:crypto';
20
21
  import { debug } from '../debug/log.js';
21
22
 
@@ -211,6 +212,8 @@ export interface OrchestrateCliDeps {
211
212
  pipelineDeps?: DevPipelineDeps;
212
213
  /** 분해 제안과 골 개정 시도 되채움 조회(테스트 주입). 기본 readDecomposeProposals(원장). */
213
214
  readProposals?: typeof readDecomposeProposals;
215
+ /** DRAFT-TRIAGE — gh seams for superseding the previous supervisor round's draft PR (tests). */
216
+ roundPrAdapters?: RoundPrAdapters;
214
217
  }
215
218
 
216
219
  /**
@@ -399,6 +402,23 @@ export async function runSelfOrchestrateCliCommand(
399
402
  };
400
403
  // ⭐ 루프는 «공용»이다(run-supervisor.superviseRun) — 단일 실행도 같은 자를 쓴다.
401
404
  // ⛔ 여기가 아는 것은 「어떻게 다시 거나」뿐이고, 「걸까 말까」는 그 자가 안다.
405
+ // DRAFT-TRIAGE — a rework round of the same goal opens a new PR; the previous round's draft is superseded.
406
+ let roundPrTracker: ReturnType<typeof createRoundPrTracker> | undefined;
407
+ let supervisedRound = 0;
408
+ const recordRoundPrs = async (rs: readonly SelfDevJobResult[]): Promise<void> => {
409
+ try {
410
+ // Once a PR is tracked, every round runs — an unfinished supersede is retried even without a new PR.
411
+ if (!roundPrTracker && !rs.some((r) => r.prUrl)) return;
412
+ roundPrTracker ??= createRoundPrTracker({
413
+ runId: process.env.ELANOUS_RUN_ID ?? rs.find((r) => r.runId?.trim())?.runId ?? null,
414
+ adapters: deps.roundPrAdapters ?? await defaultRoundPrAdapters(),
415
+ });
416
+ roundPrTracker.record(rs, supervisedRound);
417
+ } catch (error) {
418
+ try { debug.log('self-dev.supervisor', 'round-pr-record-failed', { round: supervisedRound, error: String(error) }); } catch { /* fail-open */ }
419
+ }
420
+ };
421
+ await recordRoundPrs(results);
402
422
  results = await superviseRun({
403
423
  initial: results,
404
424
  limits,
@@ -467,6 +487,8 @@ export async function runSelfOrchestrateCliCommand(
467
487
  const resumeHold = currentDecision?.needsHuman ?? [];
468
488
  const next = await exec(spec, { ...baseRuntime, resumeFrom: [...previous], ...(resumeHold.length > 0 ? { resumeHold } : {}) }, pipelineDeps);
469
489
  exitCode = next.exitCode;
490
+ supervisedRound += 1;
491
+ await recordRoundPrs(next.results);
470
492
  return next.results;
471
493
  },
472
494
  });