elanous 0.2.16 → 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 (354) 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-b46d45b705e8c9ae.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/9247-269e4464cc4b491c.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d92f7ee3994e84c7.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-3cb52e5ea8fc2fa5.js → page-4733c967b56a4277.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/board/page-d22fd849cc22f104.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c1e20d7d338713f1.js → page-bb742f995c6769e7.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/bots/{page-482c5dbde699947c.js → page-02b073a2ac793b83.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/control/{page-a0e2a6d1bfb52fc5.js → page-308111abadf35670.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-c53598cfce6156f2.js → page-5d97b2901e0d1ea6.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/editor/{page-13a63953f53c6a23.js → page-c39554c321576982.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/exec/{page-5d3c8429d04f3831.js → page-8c3d8e63ae2e1c98.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/field/{page-3193bdc0bbab047a.js → page-9b72a07d424701c6.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/intake/{page-6107ca8ddd5e3bdd.js → page-a572f58cd3ca5864.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/market/page-fa244bd146a00ae8.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-44d56d6391965a6a.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/morning/{page-4167348049166852.js → page-83a700d4c754d802.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/{not-found-de52e8fdf9095f7a.js → not-found-7b3170f374f77b9d.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-ca3952cd78a83ca2.js → page-a3c58f5c72a5f3f9.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1ebfe049c640e171.js → page-1467047104141297.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-abe8e3b74bea1ffc.js → page-44d5f767491692bf.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/share/{page-d28573d6792b4275.js → page-3cc47dca0922ab9a.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-d751de5f8062ff5e.js → page-b17cd46cf0e9b8cf.js} +1 -1
  58. package/apps/pwa/out/_next/static/chunks/app/term/{page-fe1759f94003b32b.js → page-61553851aa606146.js} +1 -1
  59. package/apps/pwa/out/_next/static/chunks/app/today/{page-d64c984e855c8c68.js → page-a1f585e154e61c59.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/app/trace/page-7bf54c9bb9274af8.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/vault/{page-9afdee66b820c76e.js → page-5150ef17b5383a57.js} +1 -1
  62. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-3544f154c87b43ee.js → page-134da468538baf48.js} +1 -1
  65. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/{webpack-539cde99554be20f.js → webpack-c2782b330447ffb6.js} +1 -1
  67. package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +1 -0
  68. package/apps/pwa/out/approvals/index.html +1 -1
  69. package/apps/pwa/out/approvals/index.txt +14 -14
  70. package/apps/pwa/out/autopilot/index.html +1 -1
  71. package/apps/pwa/out/autopilot/index.txt +14 -14
  72. package/apps/pwa/out/board/index.html +1 -1
  73. package/apps/pwa/out/board/index.txt +14 -14
  74. package/apps/pwa/out/botlab/index.html +1 -1
  75. package/apps/pwa/out/botlab/index.txt +14 -14
  76. package/apps/pwa/out/bots/index.html +1 -1
  77. package/apps/pwa/out/bots/index.txt +14 -14
  78. package/apps/pwa/out/ceo/index.html +1 -1
  79. package/apps/pwa/out/ceo/index.txt +14 -14
  80. package/apps/pwa/out/chat/index.html +1 -1
  81. package/apps/pwa/out/chat/index.txt +14 -14
  82. package/apps/pwa/out/consult/index.html +1 -1
  83. package/apps/pwa/out/consult/index.txt +14 -14
  84. package/apps/pwa/out/control/index.html +1 -1
  85. package/apps/pwa/out/control/index.txt +14 -14
  86. package/apps/pwa/out/decisions/index.html +1 -0
  87. package/apps/pwa/out/decisions/index.txt +30 -0
  88. package/apps/pwa/out/design-check/index.html +1 -1
  89. package/apps/pwa/out/design-check/index.txt +14 -14
  90. package/apps/pwa/out/editor/index.html +1 -1
  91. package/apps/pwa/out/editor/index.txt +14 -14
  92. package/apps/pwa/out/exec/index.html +1 -1
  93. package/apps/pwa/out/exec/index.txt +14 -14
  94. package/apps/pwa/out/field/index.html +1 -1
  95. package/apps/pwa/out/field/index.txt +14 -14
  96. package/apps/pwa/out/healthz +1 -1
  97. package/apps/pwa/out/index.html +1 -1
  98. package/apps/pwa/out/index.txt +14 -14
  99. package/apps/pwa/out/inside/index.html +1 -1
  100. package/apps/pwa/out/inside/index.txt +14 -14
  101. package/apps/pwa/out/intake/index.html +1 -1
  102. package/apps/pwa/out/intake/index.txt +14 -14
  103. package/apps/pwa/out/live/index.html +1 -1
  104. package/apps/pwa/out/live/index.txt +14 -14
  105. package/apps/pwa/out/loops/index.html +1 -1
  106. package/apps/pwa/out/loops/index.txt +14 -14
  107. package/apps/pwa/out/market/index.html +1 -1
  108. package/apps/pwa/out/market/index.txt +14 -14
  109. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  110. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  111. package/apps/pwa/out/morning/index.html +1 -1
  112. package/apps/pwa/out/morning/index.txt +14 -14
  113. package/apps/pwa/out/observatory/index.html +1 -1
  114. package/apps/pwa/out/observatory/index.txt +14 -14
  115. package/apps/pwa/out/ops/checklist/index.html +1 -1
  116. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  117. package/apps/pwa/out/ops/release/index.html +1 -1
  118. package/apps/pwa/out/ops/release/index.txt +14 -14
  119. package/apps/pwa/out/ops/seats/index.html +1 -1
  120. package/apps/pwa/out/ops/seats/index.txt +14 -14
  121. package/apps/pwa/out/outputs/index.html +1 -1
  122. package/apps/pwa/out/outputs/index.txt +14 -14
  123. package/apps/pwa/out/reflection/index.html +1 -1
  124. package/apps/pwa/out/reflection/index.txt +15 -15
  125. package/apps/pwa/out/scheduler/index.html +1 -1
  126. package/apps/pwa/out/scheduler/index.txt +14 -14
  127. package/apps/pwa/out/sessions/index.html +1 -1
  128. package/apps/pwa/out/sessions/index.txt +14 -14
  129. package/apps/pwa/out/settings/devices/index.html +1 -1
  130. package/apps/pwa/out/settings/devices/index.txt +14 -14
  131. package/apps/pwa/out/settings/index.html +1 -1
  132. package/apps/pwa/out/settings/index.txt +14 -14
  133. package/apps/pwa/out/setup/done/index.html +1 -1
  134. package/apps/pwa/out/setup/done/index.txt +14 -14
  135. package/apps/pwa/out/setup/index.html +1 -1
  136. package/apps/pwa/out/setup/index.txt +14 -14
  137. package/apps/pwa/out/share/index.html +1 -1
  138. package/apps/pwa/out/share/index.txt +14 -14
  139. package/apps/pwa/out/showroom/index.html +1 -1
  140. package/apps/pwa/out/showroom/index.txt +14 -14
  141. package/apps/pwa/out/tasks/index.html +1 -1
  142. package/apps/pwa/out/tasks/index.txt +14 -14
  143. package/apps/pwa/out/term/index.html +1 -1
  144. package/apps/pwa/out/term/index.txt +14 -14
  145. package/apps/pwa/out/today/index.html +1 -1
  146. package/apps/pwa/out/today/index.txt +14 -14
  147. package/apps/pwa/out/trace/index.html +1 -1
  148. package/apps/pwa/out/trace/index.txt +14 -14
  149. package/apps/pwa/out/vault/index.html +1 -1
  150. package/apps/pwa/out/vault/index.txt +14 -14
  151. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  152. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  153. package/apps/pwa/out/workflows/index.html +1 -1
  154. package/apps/pwa/out/workflows/index.txt +14 -14
  155. package/apps/pwa/out/workspace/index.html +1 -1
  156. package/apps/pwa/out/workspace/index.txt +14 -14
  157. package/apps/pwa/out/worktrees/index.html +1 -1
  158. package/apps/pwa/out/worktrees/index.txt +14 -14
  159. package/graphs/intake/intake-daily.yaml +9 -2
  160. package/graphs/intake/news-sources.yaml +4 -0
  161. package/graphs/intake/recipes.yaml +6 -1
  162. package/package.json +2 -1
  163. package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
  164. package/samples/workflows/templates/chat-driven-research.yaml +35 -0
  165. package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
  166. package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
  167. package/samples/workflows/templates/manual-quickstart.yaml +21 -0
  168. package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
  169. package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
  170. package/scripts/__fixtures__/intake-news.xml +7 -0
  171. package/scripts/ci-public-export-import-gate.ts +7 -2
  172. package/scripts/ci-typecheck-changed.ts +21 -6
  173. package/scripts/discord-seat-probe.ts +3 -2
  174. package/scripts/docs/generate-cli-anatomy.ts +162 -0
  175. package/scripts/goals/authoring-metrics.ts +73 -0
  176. package/scripts/intake-news.ts +169 -0
  177. package/scripts/landing-heal/must-fix-match.ts +7 -7
  178. package/scripts/landing-heal/run-mission.ts +3 -2
  179. package/scripts/public-export-test-run.ts +37 -2
  180. package/scripts/release-loop/checklist-gate-node.ts +13 -11
  181. package/scripts/release-loop/cut-branch.ts +2 -1
  182. package/scripts/release-loop/cutoff.ts +4 -2
  183. package/scripts/release-loop/docs-land-node.ts +77 -11
  184. package/scripts/release-loop/docs-node.ts +1 -1
  185. package/scripts/release-loop/gate-node.ts +99 -14
  186. package/scripts/release-loop/node-verdict.ts +2 -1
  187. package/scripts/release-loop/npm-publish-node.ts +11 -7
  188. package/scripts/release-loop/ops-upgrade-node.ts +8 -1
  189. package/scripts/release-loop/preflight.ts +246 -0
  190. package/scripts/release-loop/publish-node.ts +10 -3
  191. package/scripts/release-loop/release-docs.ts +134 -0
  192. package/scripts/release-loop/release-readiness.ts +11 -6
  193. package/scripts/release-loop/release-version.ts +48 -0
  194. package/scripts/release-loop/resume-release.ts +115 -0
  195. package/scripts/release-loop/unattended-release.ts +43 -7
  196. package/scripts/release-loop/version-node.ts +149 -41
  197. package/scripts/release-story/draft.ts +42 -14
  198. package/scripts/rhythm/daily.ts +25 -4
  199. package/scripts/skills/public-check.ts +62 -12
  200. package/scripts/test-deterministic.ts +51 -33
  201. package/scripts/test-diet/effectiveness.ts +62 -1
  202. package/scripts/test-diet/lib.ts +1 -1
  203. package/scripts/test-diet/node.ts +8 -1
  204. package/src/agent-mission/driver.ts +53 -10
  205. package/src/agent-mission/review-loop.ts +4 -3
  206. package/src/agent-substrate/pr-comment-meta.ts +20 -0
  207. package/src/autopilot/build/llm-conflict-merge.ts +28 -8
  208. package/src/autopilot/track-agent.ts +5 -3
  209. package/src/briefing/brief-items.ts +86 -17
  210. package/src/cli/autopilot-cli.ts +86 -1
  211. package/src/cli/brief-cli.ts +36 -2
  212. package/src/cli/decisions-cli.ts +4 -1
  213. package/src/cli/freeze-cli.ts +3 -2
  214. package/src/cli/intake-cli.ts +74 -2
  215. package/src/cli/log-event-names.ts +1 -0
  216. package/src/cli/notify-cli.ts +75 -0
  217. package/src/cli/pr-cli.ts +12 -1
  218. package/src/cli/provider-cli.ts +24 -0
  219. package/src/cli/release-cli.ts +101 -17
  220. package/src/cli/self-update-canary.ts +46 -0
  221. package/src/cli/self-update.ts +200 -1
  222. package/src/cli/tasks-cli.ts +309 -3
  223. package/src/context-bus/condense.ts +19 -3
  224. package/src/context-bus/context-day.ts +77 -24
  225. package/src/context-bus/context-first.ts +33 -0
  226. package/src/context-card/index.ts +45 -0
  227. package/src/context-card/task.ts +76 -0
  228. package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
  229. package/src/decisions/decision-ledger.ts +10 -1
  230. package/src/discord-self-message.ts +20 -2
  231. package/src/discord-slash-wire.ts +1 -0
  232. package/src/domains/outbound-alert.ts +200 -27
  233. package/src/domains/outbound-log-probe.ts +16 -1
  234. package/src/domains/release-tool.ts +2 -2
  235. package/src/execution-loop/gates.ts +11 -2
  236. package/src/flow-loop/split.ts +8 -4
  237. package/src/git-fs/worktree.ts +23 -3
  238. package/src/harness/harness-cli-command.ts +151 -11
  239. package/src/harness/harness-pod-dispatch.ts +11 -0
  240. package/src/harness/harness-queue.ts +466 -32
  241. package/src/harness/helper-scan.ts +10 -3
  242. package/src/harness/nested-elanous-policy.ts +23 -4
  243. package/src/hq/fence-wrapper.ts +123 -0
  244. package/src/hq/hq.ts +8 -0
  245. package/src/hq/seats.ts +369 -0
  246. package/src/index.ts +190 -23
  247. package/src/intake-plane/adopt-gate.ts +49 -0
  248. package/src/intake-plane/check.ts +3 -0
  249. package/src/intake-plane/collect-github.ts +57 -25
  250. package/src/intake-plane/digest.ts +39 -4
  251. package/src/intake-plane/discord-seat-work.ts +8 -2
  252. package/src/intake-plane/requirement-funnel.ts +316 -0
  253. package/src/knowledge/know-find.ts +10 -1
  254. package/src/loops/budget.ts +36 -0
  255. package/src/loops/orchestrator/finish-rate.ts +73 -1
  256. package/src/loops/orchestrator/grid-place.ts +11 -0
  257. package/src/loops/orchestrator/tick.ts +295 -20
  258. package/src/maturity/feature-maturity.ts +3 -2
  259. package/src/nexus/api/decisions-route.ts +11 -1
  260. package/src/nexus/api/grid.ts +61 -0
  261. package/src/nexus/api/http-server.ts +19 -1
  262. package/src/nexus/api/loop-edges.ts +69 -13
  263. package/src/nexus/api/ops-seats-sources.ts +2 -2
  264. package/src/nexus/api/task-cards-api.ts +19 -0
  265. package/src/nexus/api/workflow-templates.ts +3 -0
  266. package/src/oauth/codex.ts +120 -2
  267. package/src/onboarding/first-chat-gate.ts +29 -0
  268. package/src/release-loop/checklist.ts +104 -6
  269. package/src/release-loop/feature-store.ts +10 -9
  270. package/src/release-loop/landing-freeze.ts +24 -14
  271. package/src/release-loop/placement.ts +13 -3
  272. package/src/release-loop/rubric.ts +80 -0
  273. package/src/seat-dispatch/seat-ask.ts +79 -16
  274. package/src/seat-loop/seat-loop.ts +107 -37
  275. package/src/self-dev/ask-launch-flow.ts +50 -10
  276. package/src/self-dev/ask-path-candidates.ts +68 -0
  277. package/src/self-dev/dev-pipeline.ts +4 -0
  278. package/src/self-dev/draft-triage-rules.ts +1 -1
  279. package/src/self-dev/orchestrate-cli.ts +22 -0
  280. package/src/self-dev/round-pr-supersede.ts +244 -0
  281. package/src/self-dev/run-store.ts +5 -1
  282. package/src/self-dev/run-supervisor.ts +21 -1
  283. package/src/self-dev/supervisor-verdict-edges.ts +46 -0
  284. package/src/self-implement/follow-up-goals.ts +2 -2
  285. package/src/self-implement/frozen-merges.ts +25 -20
  286. package/src/self-implement/gate-remote.ts +427 -0
  287. package/src/self-implement/goal-author-clarification.ts +81 -0
  288. package/src/self-implement/goal-author-cli.ts +33 -5
  289. package/src/self-implement/goal-author.ts +93 -12
  290. package/src/self-implement/goal-clarification-escalation.ts +37 -1
  291. package/src/self-implement/goal-document-write.ts +28 -0
  292. package/src/self-implement/heal-intake.ts +4 -0
  293. package/src/self-implement/host-regate.ts +3 -1
  294. package/src/self-implement/l8-merge-queue.ts +29 -5
  295. package/src/self-implement/orchestrator.ts +78 -13
  296. package/src/self-implement/prior-draft-findings.ts +4 -3
  297. package/src/skills/skill-feedback.ts +76 -0
  298. package/src/task-agent/actions.ts +151 -0
  299. package/src/task-agent/judge.ts +122 -0
  300. package/src/task-agent/mission.ts +423 -0
  301. package/src/task-agent/shadow.ts +90 -0
  302. package/src/task-agent/task-hand.ts +235 -0
  303. package/src/task-cards/card-store.ts +4 -1
  304. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +43 -4
  305. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  306. package/src/task-orchestrator/surfaces/pod-pool.ts +81 -13
  307. package/src/task-orchestrator/surfaces/self-implement-pod.ts +225 -35
  308. package/src/telegram-commands.ts +1 -1
  309. package/src/telegram-loops-command.ts +22 -15
  310. package/src/telegram.ts +5 -20
  311. package/src/user-config.ts +86 -4
  312. package/src/version/packed-revision.json +1 -1
  313. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/chat/page-8c2d9526164f5d09.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/layout-dce686e582c23937.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/loops/page-1a61c7ca1ceb3ae7.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/market/page-51f811fa119308b6.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-5b042736aa113756.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-57924393e3e4e539.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/settings/page-0f5cd57ca928f42b.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/showroom/page-59f03fda76c03d22.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/workflows/page-1ea64acadd704ace.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +0 -1
  352. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
  353. package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +0 -1
  354. /package/apps/pwa/out/_next/static/{rv9cierV0jSOvDBm1g0XB → P6YTVgzuZz5oTZhY4Din_}/_ssgManifest.js +0 -0
@@ -6,11 +6,11 @@ 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';
13
- import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
13
+ import { DecisionLedger, type DecisionEntry, type DecisionOption, type Recommendation } from '../decisions/decision-ledger.js';
14
14
  import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js';
15
15
  import { loopEvent } from '../loops/observe.js';
16
16
  import { getUserConfig, type PersonaLoopConfig, type SeatLoopConfig, type UserConfig } from '../user-config.js';
@@ -43,7 +43,7 @@ export type TcCandidate =
43
43
  | { kind: 'publication-pr-approval-wait'; pr: number; title: string; readyAt: string; approvalWaitAt: string; paths: string[] }
44
44
  | { kind: 'other-seat-cell-defect'; version: string; id: string; title: string; to: SeatId };
45
45
  export type TcPullRequest = { number: number; title: string; body?: string; state: 'OPEN' | 'MERGED' | 'CLOSED'; isDraft: boolean; createdAt: string; updatedAt?: string; mergedAt?: string | null; readyAt?: string; approvalWaitAt?: string; paths?: string[]; reviewDecision?: string };
46
- export type SeatEntry = { seat: string; ts?: string; at: string; modeDowngradeReason?: string; status: 'shadow' | 'attempting' | 'outcome-unknown' | 'queued' | 'refused' | 'held' | 'launched' | 'hitl' | 'wait' | 'asked' | 'answered' | 'awaiting-answer' | 'awaiting-xcheck' | 'awaiting-resolution' | 'resolved-by-neighbor' | 'rejected-no-evidence' | 'skipped-budget' | 'skipped-empty'; item?: SeatItem; candidate?: OpCandidate | TcCandidate; action?: SeatAction; reason?: string; runId?: string; queueId?: string; inquiry?: SeatInquiry; answer?: string; escalated?: boolean; xcheckRequestedAt?: string; xcheckQuestionId?: number; xcheckNeighbor?: SeatId; xcheckNote?: string; xcheckJudgmentEvidence?: string; xcheckWaitMinutes?: number };
46
+ export type SeatEntry = { seat: string; ts?: string; at: string; modeDowngradeReason?: string; status: 'shadow' | 'attempting' | 'outcome-unknown' | 'queued' | 'refused' | 'held' | 'launched' | 'hitl' | 'wait' | 'asked' | 'answered' | 'awaiting-answer' | 'awaiting-xcheck' | 'awaiting-resolution' | 'resolved-by-neighbor' | 'resolved' | 'rejected-no-evidence' | 'skipped-budget' | 'skipped-empty'; item?: SeatItem; candidate?: OpCandidate | TcCandidate; action?: SeatAction; reason?: string; runId?: string; queueId?: string; inquiry?: SeatInquiry; answer?: string; escalated?: boolean; xcheckRequestedAt?: string; xcheckQuestionId?: number; xcheckNeighbor?: SeatId; xcheckNote?: string; xcheckJudgmentEvidence?: string; xcheckWaitMinutes?: number };
47
47
  export type SeatLoopResult = SeatEntry | { seat: string; status: 'skipped-off'; modeDowngradeReason?: string };
48
48
 
49
49
  export function seatLoopTickLine(result: SeatLoopResult): string {
@@ -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;
@@ -375,6 +375,23 @@ function decisionSummary(text: string): string {
375
375
  return Array.from(sentences).slice(0, 240).join('').trim();
376
376
  }
377
377
 
378
+ function decisionCardMaterial(input: {
379
+ what: string; soFar: string; current: string; question: string; answer: string;
380
+ options: DecisionOption[]; recommendation: Recommendation; ifUntouched: string; evidence: string; confidence: string;
381
+ }): { scqa: { s: string; c: string; q: string; a: string }; pendingQuestion: string } {
382
+ const short = (value: string) => decisionSummary(value) || '관측되지 않음';
383
+ return {
384
+ scqa: { s: short(input.what), c: short(input.current), q: short(input.question), a: short(input.answer) },
385
+ pendingQuestion: [
386
+ `무엇: ${input.what}`, `지금까지: ${input.soFar}`, `지금 상태 재측: ${input.current}`,
387
+ ...input.options.map((option) => `선택지 ${option.key} (${option.label}) 결과: ${option.consequence}`),
388
+ 'skipped' in input.recommendation ? `권고 비움: ${input.recommendation.reason}` : `권고: ${input.recommendation.option} — ${input.recommendation.why}`,
389
+ `확신: ${input.confidence}`, `그냥 두면: ${input.ifUntouched}`, `근거: ${input.evidence}`,
390
+ `Q: ${input.question}`, `A: ${input.answer}`,
391
+ ].join('\n'),
392
+ };
393
+ }
394
+
378
395
  function decisionSourceRef(seat: string, item: SeatItem): string {
379
396
  return item.source === 'request' && item.id.startsWith('coord:') ? item.id : decisionRef(seat, item);
380
397
  }
@@ -388,7 +405,7 @@ function decisionReceipt(root: string, seat: string, item: SeatItem): DecisionEn
388
405
  function handledKeys(ledger: readonly SeatEntry[], shadow: boolean, root?: string, liveSafe = false): Set<string> {
389
406
  const inquiryRow = (entry: SeatEntry) => entry.action === 'seat-question' || entry.action === 'seat-answer' || !!entry.inquiry;
390
407
  return new Set(ledger.filter((entry, index) => entry.status === 'asked' || entry.status === 'answered'
391
- || entry.status === 'resolved-by-neighbor'
408
+ || entry.status === 'resolved-by-neighbor' || entry.status === 'resolved'
392
409
  // RM3a: only TC's other-seat defect ask-shadows are deduplicated here — an ordinary inquiry recorded in shadow must still be delivered once questions turn on.
393
410
  || (entry.status === 'shadow' && entry.candidate?.kind === 'other-seat-cell-defect')
394
411
  || (entry.status === 'hitl' && (!entry.inquiry || (!!root && !!entry.item && !!decisionReceipt(root, entry.seat, entry.item))))
@@ -853,7 +870,7 @@ const SEAT_ACTION_RULES: ReadonlyArray<{ kind: 'wait' | 'harness' | 'decision';
853
870
  { kind: 'wait', signal: /실측\s*대기|(?:사람|실제)\s*기기(?!\s*(?:(?:실측|측정|확인)\s*)?완료)(?:\s*(?:실측|측정|확인|대기))?|실물\s*측정(?!\s*완료)/iu, source: 'content', resolvedBy: /(?:실측|측정|(?:사람|실제)\s*기기|실물\s*측정)\s*완료/iu },
854
871
  { kind: 'wait', signal: /\u{1F451}\s*확인\s*대기/iu, source: 'content', resolvedBy: /\u{1F451}\s*확인\s*완료/iu },
855
872
  { kind: 'decision', signal: TRACK_AGENT_FORBIDDEN_ACTION_REGEX, source: 'task' },
856
- { kind: 'decision', signal: /(?:마켓|레지스트리)(?:에|로)?\s*(?:게시|등록|발행)|사이트\s*운영\s*반영|SNS(?:에|로)?\s*(?:게시|발행|업로드)|공개\s*발행/iu, source: 'content' },
873
+ { kind: 'decision', signal: /(?:마켓|레지스트리)(?:에|로)?\s*(?:게시|등록|발행)|사이트\s*운영\s*반영|SNS(?:에|로)?\s*(?:게시|발행|업로드)|공개\s*발행/iu, source: 'task' },
857
874
  { kind: 'harness', signal: /(?:마켓|레지스트리|사이트|SNS|공개)?(?:에|로)?\s*(?:게시|발행|배포|등록|운영\s*반영)(?:를?\s*위한)?\s*(?:준비|초안|원고|자료|구현|점검|테스트)|(?:마켓|레지스트리|사이트|SNS|공개)?(?:에|로)?\s*(?:게시|발행|배포|등록)(?:용|를?\s*위한)\s*(?:초안|원고|자료|코드|기능)/iu, source: 'task' },
858
875
  ];
859
876
 
@@ -861,9 +878,9 @@ export function planAction(item: SeatItem, seat?: string): { kind: 'decision' |
861
878
  const task = `${item.id}\n${item.title}\n${item.text}`;
862
879
  const content = `${task}\n${item.evidence ?? ''}`;
863
880
  const preparation = SEAT_ACTION_RULES.find(({ kind }) => kind === 'harness')!;
864
- const prepared = [...content.matchAll(new RegExp(preparation.signal.source, `${preparation.signal.flags.replace('g', '')}g`))];
881
+ const prepared = [...item.text.matchAll(new RegExp(preparation.signal.source, `${preparation.signal.flags.replace('g', '')}g`))];
865
882
  const rule = SEAT_ACTION_RULES.map(({ kind, signal, source, resolvedBy }) => {
866
- const text = source === 'content' ? content : task;
883
+ const text = kind === 'decision' ? item.text : source === 'content' ? content : task;
867
884
  if (resolvedBy?.test(item.evidence ?? '')) return null;
868
885
  const hits = [...text.matchAll(new RegExp(signal.source, `${signal.flags.replace('g', '')}g`))];
869
886
  const hit = kind === 'decision' ? hits.find((match) => !prepared.some((prep) =>
@@ -893,11 +910,11 @@ function neighborLastSeen(root: string, isolated: boolean, now: Date, wanted: Re
893
910
  if (!existsSync(path)) return last;
894
911
  const db = new Database(path, { readonly: true });
895
912
  try {
896
- // Same outbound seat events and refs.seat as context day; unlike its 24h display window,
897
- // 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.
898
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.
899
916
  const statement = db.prepare(`SELECT ts, refs FROM events
900
- 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`);
901
918
  try {
902
919
  const events = statement.iterate(now.toISOString()) as Iterable<{ ts: string; refs: string | null }>;
903
920
  for (const event of events) {
@@ -936,13 +953,16 @@ function judgeSeatNeighbors(seat: string, config: SeatLoopConfig, deps: SeatDeps
936
953
  if (judgment.state === 'absent') {
937
954
  if (config.mode === 'live-safe' && judgment.action?.action === 'escalate' && isRescueAllowedAction('decision-card')) {
938
955
  const ref = `seat-loop:neighbor:${seat}:${neighbor.id}:${lastSeenAt}`;
956
+ const options = [{ key: 'a', label: '사람 판단 후 별도 조치', consequence: '자리 루프는 대행하지 않는다' },
957
+ { key: 'b', label: '현상 유지', consequence: '결측 상태를 계속 관측한다' }];
958
+ const recommendation = { option: 'b', why: '새 사건을 확인하기 전에는 이웃 업무를 대행하지 않는다' };
939
959
  cardLedger(root, deps, now).raiseOnce({ title: `${seat}: 이웃 ${neighbor.id} 결측`, category: 'other',
940
- scqa: { s: `${seat} 이웃 ${neighbor.id}의 마지막 맥락 버스 사건: ${lastSeenAt}.`,
941
- c: `설정한 heartbeat ${neighbor.heartbeat.everyMinutes}분 × ${neighbor.heartbeat.missedTicks}회 동안 새 사건이 없다.`,
942
- q: '결측 이웃의 업무를 어떻게 처리할까?' },
943
- options: [{ key: 'a', label: '사람 판단 후 별도 조치', consequence: '자리 루프는 대행하지 않는다' },
944
- { key: 'b', label: '현상 유지', consequence: '결측 상태를 계속 관측한다' }],
945
- recommendation: { skipped: true, reason: '결측만으로 대행이나 보류를 자동 결정하지 않는다' },
960
+ ...decisionCardMaterial({ what: `${seat} 이웃 ${neighbor.id} 결측`, soFar: '마지막 맥락 버스 사건 이후 새 ACK 없음',
961
+ current: `현재 시각 ${now.toISOString()}; 마지막 사건 ${lastSeenAt}; heartbeat ${neighbor.heartbeat.everyMinutes}분 × ${neighbor.heartbeat.missedTicks}회 결측`,
962
+ question: '결측 이웃의 업무를 어떻게 처리할까?', answer: '새 ACK까지 현상 유지하고 이웃 업무는 대행하지 않는다',
963
+ options, recommendation, confidence: '중간 — 사건의 부재만 관측됨',
964
+ ifUntouched: '결측 상태를 계속 관측하며 대행은 하지 않는다', evidence: `맥락 버스 마지막 사건 ${lastSeenAt}; ${ref}` }),
965
+ options, recommendation,
946
966
  raisedBy: { agent: 'seat-loop' }, refs: [ref], raisedAt: now.toISOString(),
947
967
  crossCheckSkipped: '이웃 결측으로 교차 확인 불가' }, ref);
948
968
  action = 'decision-card';
@@ -976,6 +996,12 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
976
996
  const directory = dirname(path);
977
997
  return withSeatLock(directory, async () => {
978
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) }); }
979
1005
  const recorded = readSeatLedger(seat, deps, now);
980
1006
  const ledger = config.mode === 'live-safe' ? reconciledQueueLedger(recorded, stateRoot, deps) : recorded;
981
1007
  let turnError: unknown;
@@ -1012,10 +1038,17 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
1012
1038
  const question = proposal.inquiry?.question ?? proposal.item!.title;
1013
1039
  const note = `${proposal.xcheckNote ?? ''} — 해결 가능하다고 했으나 ${waited}분 안 해소 안 됨`.trim();
1014
1040
  if (!decisionReceipt(stateRoot, seat, proposal.item!)) {
1041
+ const options = [{ key: 'a', label: '승인', consequence: '사람 판단으로 진행' }, { key: 'b', label: '보류', consequence: '진행하지 않음' }];
1042
+ const recommendation = { option: 'b', why: `기한 ${waited}분 뒤에도 ${cell?.status ?? '질문 미해결'}이므로 착지 확인 전에는 진행하지 않는다` };
1043
+ const material = decisionCardMaterial({ what: `${seat} 질문 · ${proposal.item!.title}`, soFar: `이웃 ${neighbor} 해결 제안: ${proposal.xcheckNote ?? '기록 없음'}`,
1044
+ current: `기한 ${waited}분 경과 · 칸 상태 ${cell?.status ?? '질문 미해결'}`, question, answer: '재측상 미해결이므로 보류를 권고한다',
1045
+ options, recommendation, confidence: '중간 — 기한 경과와 미해결을 재측함', ifUntouched: '질문/칸은 미해결로 남는다',
1046
+ evidence: `${proposal.item!.evidence ?? '원천 근거 미기재'} · ${note} · ${decisionRef(seat, proposal.item!)}` });
1015
1047
  await (deps.run ?? defaultRun)(['decisions', 'raise', '--title', `${seat}: ${question}`, '--category', 'other',
1016
- '--s', `${seat} 질문 · ${proposal.item!.title}`, '--c', '이웃 자리 해결 제안이 기한 안에 해소되지 않았다',
1048
+ '--s', material.scqa.s, '--c', material.scqa.c, '--q', material.scqa.q, '--a', material.scqa.a,
1049
+ '--pending-question', material.pendingQuestion,
1017
1050
  '--option', 'a=승인:사람 판단으로 진행', '--option', 'b=보류:진행하지 않음',
1018
- '--skip-recommend', '근거가 부족하다', '--xcheck', `${neighbor}:${note}`,
1051
+ '--recommend', recommendation.option, '--why', recommendation.why, '--xcheck', `${neighbor}:${note}`,
1019
1052
  '--agent', 'seat-loop', '--ref', decisionRef(seat, proposal.item!), '--json']);
1020
1053
  }
1021
1054
  const entry: SeatEntry = { ...proposal, ts: now.toISOString(), at: now.toISOString(), status: 'hitl' };
@@ -1313,10 +1346,18 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
1313
1346
  const check = answer ? ['--xcheck', `${requestedNeighbor}:${answer.note}`,
1314
1347
  ...(answer.agree ? [] : ['--dissent', `${requestedNeighbor}: ${answer.note}`])]
1315
1348
  : ['--no-xcheck', `이웃 ${requestedNeighbor} 무응답 ${entry.xcheckWaitMinutes}분`];
1349
+ const options = [{ key: 'a', label: '승인', consequence: '사람 판단으로 진행' }, { key: 'b', label: '보류', consequence: '진행하지 않음' }];
1350
+ const recommendation = { option: 'b', why: '교차 확인에서 해소 근거가 확보되지 않았다' };
1351
+ const material = decisionCardMaterial({ what: `${seat} 질문 · ${item.title}`, soFar: `이웃 ${requestedNeighbor}에 교차 확인 요청`,
1352
+ current: answer ? `이웃 응답: ${answer.note}` : `이웃 ${requestedNeighbor} 무응답 ${entry.xcheckWaitMinutes}분`,
1353
+ question: ask.question, answer: '해소 근거 확인 전까지 보류를 권고한다', options, recommendation,
1354
+ confidence: answer ? '중간 — 이웃 응답을 확인함' : '낮음 — 기한 내 이웃 응답 없음',
1355
+ ifUntouched: '사람 판단 전 집행하지 않는다', evidence: `${item.evidence ?? '원천 근거 미기재'} · ${decisionRef(seat, item)}` });
1316
1356
  await (deps.run ?? defaultRun)(['decisions', 'raise', '--title', `${seat}: ${ask.question}`, '--category', 'other',
1317
- '--s', `${seat} 질문 · ${item.title}`, '--c', '판단 근거 부족 — 사람의 결정이 필요하다',
1357
+ '--s', material.scqa.s, '--c', material.scqa.c, '--q', material.scqa.q, '--a', material.scqa.a,
1358
+ '--pending-question', material.pendingQuestion,
1318
1359
  '--option', 'a=승인:사람 판단으로 진행', '--option', 'b=보류:진행하지 않음',
1319
- '--skip-recommend', '근거가 부족하다', ...check, '--agent', 'seat-loop', '--ref', decisionRef(seat, item), '--json']);
1360
+ '--recommend', recommendation.option, '--why', recommendation.why, ...check, '--agent', 'seat-loop', '--ref', decisionRef(seat, item), '--json']);
1320
1361
  if (!decisionReceipt(root, seat, item)) throw new Error('decisions raise did not deliver a matching card');
1321
1362
  entry.status = 'hitl';
1322
1363
  xcheckEvent('raised', seat, requestedNeighbor, itemKey(item), answer ? answer.agree ? 'agree' : 'dissent' : 'timed-out');
@@ -1442,11 +1483,27 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
1442
1483
  if (action.kind === 'decision') {
1443
1484
  const title = `${seat}: ${decisionSummary(action.text)}`;
1444
1485
  const source = decisionSourceRef(seat, item);
1486
+ const options = [{ key: 'a', label: '사람 승인', consequence: '승인 후 별도로 집행' }, { key: 'b', label: '보류', consequence: '집행하지 않음' }];
1487
+ const recommendation = { option: 'b', why: `요청에서 걸린 문면 «${action.reason}»은 자동 집행 대상이 아니므로 보류한다` };
1488
+ let currentStatus: string | undefined;
1489
+ try {
1490
+ currentStatus = item.source === 'checklist' && item.version
1491
+ ? (deps.checklistItems ?? ((version: string) => listChecklist(version, deps.root ?? releaseLedgerRoot()).items))(item.version)
1492
+ .find((cell) => cell.id === item.id && cell.owner === seat)?.status
1493
+ : item.source === 'request' ? listSeatRequests(stateRoot).find((row) => row.key === item.id && row.seat === seat)?.status
1494
+ : undefined;
1495
+ } catch (error) { observe('decision-source-unreadable', { seat, item: item.id, error: String(error).slice(0, 200) }); }
1496
+ const material = decisionCardMaterial({ what: `${seat} 배정 항목: ${item.title} 역할: ${inputs.role || '역할 근거 없음'}`, soFar: `요청 내용: ${item.text}`,
1497
+ current: `밑바탕 재측: ${currentStatus ?? '상태 미확인'}; 요청에서 금지 문면 «${action.reason}» 발견 — 실행 전 사람 승인 필요`,
1498
+ question: `금지 문면 «${action.reason}»이 걸린 요청을 승인할까?`, answer: '승인 전에는 집행하지 않고 보류한다',
1499
+ options, recommendation, confidence: '높음 — 요청에 금지 문면 직접 일치',
1500
+ ifUntouched: '자동 집행되지 않고 해당 칸은 보류 상태로 남는다',
1501
+ evidence: `${item.evidence?.trim() || '추가 근거 미기재'} · 요청 ${source} · 걸린 낱말 ${action.reason}` });
1445
1502
  const raised = await run(['decisions', 'raise', '--title', title, '--category', 'other',
1446
- '--s', decisionSummary(`${seat} 배정 항목: ${action.text.replace(/[\r\n]+/g, ' ')} 역할: ${inputs.role}`),
1447
- '--c', '자동 실행 금지 문면에 해당하여 사람 결정이 필요하다',
1503
+ '--s', material.scqa.s, '--c', material.scqa.c, '--q', material.scqa.q, '--a', material.scqa.a,
1504
+ '--pending-question', material.pendingQuestion,
1448
1505
  '--option', 'a=사람 승인:승인 후 별도로 집행', '--option', 'b=보류:집행하지 않음',
1449
- '--skip-recommend', '금지 문면은 자동으로 권고하지 않는다', '--no-xcheck', '자리 루프 · 이웃 교환은 DEC-XCHECK ②',
1506
+ '--recommend', recommendation.option, '--why', recommendation.why, '--no-xcheck', '자리 루프 · 이웃 교환은 DEC-XCHECK ②',
1450
1507
  '--agent', 'seat-loop', '--ref', source, '--json']);
1451
1508
  const decision: unknown = JSON.parse(raised.trim());
1452
1509
  if (!decision || typeof decision !== 'object' || typeof (decision as { id?: unknown }).id !== 'string') {
@@ -1461,25 +1518,38 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
1461
1518
  const limit = repeatStopLimit(config);
1462
1519
  const attempts = unlandedAttemptCount(seat, item, ledger, stateRoot, deps);
1463
1520
  if (attempts >= limit && !personReopened(ledger, seat, item, stateRoot)) {
1521
+ // Re-read the underlying item at the decision boundary, not the picked snapshot.
1522
+ const current = item.source === 'checklist' && item.version
1523
+ ? (deps.checklistItems ?? ((version: string) => listChecklist(version, deps.root ?? releaseLedgerRoot()).items))(item.version)
1524
+ .find((cell) => cell.id === item.id && cell.owner === seat)?.status
1525
+ : item.source === 'request' ? listSeatRequests(stateRoot).find((row) => row.key === item.id && row.seat === seat)?.status
1526
+ : undefined;
1527
+ if (current === 'green' || current === 'done') {
1528
+ entry.status = 'resolved';
1529
+ entry.reason = `밑바탕 재측: ${current} — 반복 정지 카드 불필요`;
1530
+ append(path, entry);
1531
+ observe('repeat-recovered', { seat, item: item.id, status: current });
1532
+ return entry;
1533
+ }
1464
1534
  const reason = repeatStopReason(attempts);
1465
1535
  entry.status = 'held';
1466
1536
  entry.reason = reason;
1467
1537
  const ref = decisionRef(seat, item);
1468
1538
  const prior = new DecisionLedger({ stateDir: stateRoot }).list({ status: 'all' }).find((card) => card.refs?.includes(ref) && card.status === 'open');
1469
1539
  if (!prior) {
1540
+ const options = [
1541
+ { key: 'a', label: '다시 연다', consequence: '결정 뒤에 자리 루프가 한 번 더 시도할 수 있다' },
1542
+ { key: 'b', label: '멈춘다', consequence: '같은 칸은 다시 발사하지 않는다' },
1543
+ ];
1544
+ const recommendation = { option: 'b', why: `착지 없는 ${attempts}회 재시도보다 현재 칸을 멈추는 편이 중복 실행을 피한다` };
1470
1545
  cardLedger(stateRoot, deps, now).raiseOnce({
1471
- title: `${seat}: ${item.id} 반복 착지 0`,
1472
- category: 'other',
1473
- scqa: {
1474
- s: `${seat} 칸 ${item.id} 가 착지 없이 ${attempts}회 끝났다.`,
1475
- c: '같은 칸을 다시 발사하지 않고 사람 판단을 기다린다.',
1476
- q: '이 칸을 계속할까, 멈출까?',
1477
- },
1478
- options: [
1479
- { key: 'a', label: '다시 연다', consequence: '결정 뒤에 자리 루프가 한 번 더 시도할 수 있다' },
1480
- { key: 'b', label: '멈춘다', consequence: '같은 칸은 다시 발사하지 않는다' },
1481
- ],
1482
- recommendation: { skipped: true, reason: '착지 없는 반복만으로 계속할지 자동 결정하지 않는다' },
1546
+ title: `${seat}: ${item.id} 반복 착지 0`, category: 'other',
1547
+ ...decisionCardMaterial({ what: `${seat} 칸 ${item.id} · ${item.title}`, soFar: `${attempts}회 착지 없이 종료`,
1548
+ current: `발행 직전 밑바탕 재측: ${current ?? '상태 미확인'}; 같은 칸 착지 0`,
1549
+ question: '이 칸을 계속할까, 멈출까?', answer: '중복 발사를 멈추고 사람의 재개 결정을 기다린다',
1550
+ options, recommendation, confidence: current ? '높음 — 밑바탕 상태 재측됨' : '낮음 — 밑바탕 상태 미확인',
1551
+ ifUntouched: '같은 칸은 다시 발사되지 않는다', evidence: `${item.evidence ?? '원천 근거 미기재'} · ${ref} · 완료되지 않은 시도 ${attempts}회` }),
1552
+ options, recommendation,
1483
1553
  raisedBy: { agent: 'seat-loop' },
1484
1554
  refs: [ref],
1485
1555
  raisedAt: now.toISOString(),
@@ -1492,7 +1562,7 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
1492
1562
  }
1493
1563
  let queued: { id: string };
1494
1564
  try {
1495
- 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);
1496
1566
  } catch (error) {
1497
1567
  // Same key, different task body: never adopt the other work. Refuse this turn; the key frees once that item finishes.
1498
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,
@@ -35,6 +36,8 @@ import { dirname, isAbsolute as isAbsolutePath, relative as relativePath, resolv
35
36
  import {
36
37
  applyClarificationReply,
37
38
  planClarificationIntake,
39
+ selfAnswerGoalDocumentClarifications,
40
+ type GoalClarificationSelfResolver,
38
41
  } from '../self-implement/goal-author-clarification.js';
39
42
  import { classifyReauthoredAsk, countMissingAuthoredConstraintMarkers, askGoalTypeDeclaration, declaredGoalType, parseGoalId } from '../self-implement/goal-author.js';
40
43
  import { observeFrontNodeEntry } from './graph-front-nodes.js';
@@ -143,6 +146,9 @@ export interface AskLaunchFlowDeps {
143
146
  /** Optional structured clarification adapter. Omitted callers retain the
144
147
  * existing CLI line-input contract exactly. */
145
148
  readClarification?(clarification: GoalDocumentClarification): Promise<string>;
149
+ selfResolveClarification?: GoalClarificationSelfResolver;
150
+ /** Replaces the goal document after a self-answer; production entrances pass an atomic writer. */
151
+ writeGoalDocument?(path: string, document: string): void;
146
152
  readFile(path: string): string;
147
153
  writeFile(path: string, data: string): void;
148
154
  cwd(): string;
@@ -159,6 +165,8 @@ export interface AskLaunchFlowDeps {
159
165
  /** ⛔ 못 얻으면 `null` — 같은 이유. */
160
166
  recentAuthoringSamples(): RecentAuthoringSample[] | null;
161
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>;
162
170
  /** AUTHOR-TRACE seam (tests). */
163
171
  recordAuthoredGoal?: typeof recordAuthoredGoal;
164
172
  /** Pod launches reserve a host slot before goal authoring begins. */
@@ -188,6 +196,7 @@ export interface AskLaunchFlowInput {
188
196
  readonly askFile?: string;
189
197
  /** Optional repository root used only for code grounding during goal authoring. */
190
198
  readonly groundingCwd?: string;
199
+ readonly authorGrade?: import('../self-implement/goal-author.js').GoalAuthorGradeSelection;
191
200
  readonly liveRunWindowMinutes: number;
192
201
  readonly recentChangeWindowDays: number;
193
202
  readonly forceRequested: boolean;
@@ -724,16 +733,27 @@ async function intakeClarifications(
724
733
  return effectiveGoalFile;
725
734
  }
726
735
  if (mode === 'deferred-noninteractive') {
727
- // ⛔⭐ 종전엔 「N건 있다」만 말하고 «무엇을 묻는지»도 «어떻게 답하는지»도 안 줬다.
728
- // ⇒ 비대화형 표면(무인 런 · TUI 슬래시)에서 그 물음은 사실상 «사라졌다».
729
- // ⛔ 여기에 새 인터뷰 UI 를 만들지 «않는다» — 답변 창구는 `elanous self clarify answer` 로 «이미» 있다.
730
- // 이 자리는 그것을 «가리키기»만 한다(2026-08-11 72차: 오늘만 `F12` 를 여섯 번 셌다).
731
- deps.print(`[ask] ⑴b ⚠️ 미답 되묻기 ${pending.length}건 — 저작은 그대로 간다 · 구현 전에 LLM 릴레이가 먼저 답하고, 못 하면 미답으로 진행한다 (사람을 기다리지 않는다)`);
732
- for (const item of pending) {
736
+ // The resolver comes in through deps (I/O seam): production entrances pass the author
737
+ // self-resolver; a caller that injects none keeps the previous deferred behaviour.
738
+ if (selfResolveClarifications.value && deps.selfResolveClarification) try {
739
+ const resolution = await selfAnswerGoalDocumentClarifications(document, {
740
+ path: 'ask', ask: input.askText, resolver: deps.selfResolveClarification,
741
+ });
742
+ if (resolution.newlyAnswered > 0) {
743
+ (deps.writeGoalDocument ?? deps.writeFile)(effectiveGoalFile, resolution.document);
744
+ document = resolution.document;
745
+ }
746
+ } catch (error) {
747
+ deps.log('ask-clarify', { goalFile: effectiveGoalFile, reason: 'self-answer-failed', error: error instanceof Error ? error.message : String(error) }, 'warn');
748
+ }
749
+ const remaining = planClarificationIntake(document, false).pending;
750
+ // 미답으로 남은 문항만 기존 답변 창구를 안내한다.
751
+ deps.print(`[ask] ⑴b ⚠️ 미답 되묻기 ${remaining.length}건 — 저작은 그대로 간다 · 구현 전에 LLM 릴레이가 먼저 답하고, 못 하면 미답으로 진행한다 (사람을 기다리지 않는다)`);
752
+ for (const item of remaining) {
733
753
  deps.print(`[ask] ❓ [${item.questionId}] ${item.question}`);
734
754
  item.options.forEach((option, index) => deps.print(`[ask] ${index}) ${option.label}`));
735
755
  }
736
- if (pending.length > 0) {
756
+ if (remaining.length > 0) {
737
757
  const goalArg = deps.relativeToCwd(effectiveGoalFile);
738
758
  deps.print(`[ask] ▶ 답하려면: elanous self clarify answer ${goalArg} <questionId> <옵션번호> (자유 답은 --other "<문장>")`);
739
759
  deps.print(`[ask] ▶ 답한 뒤 재저작: elanous self author --supersedes ${goalArg}`);
@@ -771,6 +791,7 @@ async function intakeClarifications(
771
791
  const reauthored = await deps.authorGoal([], {
772
792
  cwd: deps.cwd(),
773
793
  ...(input.groundingCwd === undefined ? {} : { groundingCwd: input.groundingCwd }),
794
+ ...(input.authorGrade === undefined ? {} : { authorGrade: input.authorGrade }),
774
795
  fromClarification: `${deps.relativeToCwd(effectiveGoalFile)}#${seedQuestionId}`,
775
796
  reauthorFromAnsweredClarification: true,
776
797
  selfResolveClarifications: selfResolveClarifications.value,
@@ -831,7 +852,7 @@ export async function runAskLaunchFlow(
831
852
  if (launchPreflight === false) return { kind: 'stopped-before-authoring' };
832
853
 
833
854
  await deps.beforeAuthoring?.();
834
- const authorAndPreflight = async (): Promise<{
855
+ const authorAndPreflight = async (authorAskText: string = input.askText): Promise<{
835
856
  readonly goalFile: string;
836
857
  readonly decision: ReturnType<typeof decideAskPreflight>;
837
858
  }> => {
@@ -850,9 +871,10 @@ export async function runAskLaunchFlow(
850
871
  };
851
872
  // ⭐ RFC §5 5단계 — 확인된 implement 저작만 활성 구현 그래프에 이름을 잇는다.
852
873
  observeFrontNodeEntry('author', { ...frontObservation, provenance: 'authoring-start' });
853
- const authored = await deps.authorGoal([input.askText], {
874
+ const authored = await deps.authorGoal([authorAskText], {
854
875
  cwd: deps.cwd(),
855
876
  ...(input.groundingCwd === undefined ? {} : { groundingCwd: input.groundingCwd }),
877
+ ...(input.authorGrade === undefined ? {} : { authorGrade: input.authorGrade }),
856
878
  ...(goalType === null ? {} : { goalType }),
857
879
  // ⛔ 인라인 ask(`harness say`)는 askFile 이 없다 — 그 칸을 넘기면 저작기가 빈 줄을 쓴다.
858
880
  ...(() => {
@@ -992,8 +1014,26 @@ export async function runAskLaunchFlow(
992
1014
  const first = authored;
993
1015
  reauthored = true;
994
1016
  deps.print('[ask] ⑴ ⚠️ 대상 경로를 못 찾아 자동 재저작 한 번을 시도한다');
995
- 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);
996
1035
  deps.log('ask-auto-reauthor', {
1036
+ candidatePathCount: candidates.paths.length,
997
1037
  firstGoalFile: first.goalFile,
998
1038
  secondGoalFile: authored.goalFile,
999
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
  });