elanous 0.2.18 → 0.2.20

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 (350) 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/chunks/{1307-30dff22c9e1f3073.js → 1307-77fad80cf54477c6.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +25 -0
  14. package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d92f7ee3994e84c7.js → page-c855c24de7db74fe.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-4733c967b56a4277.js → page-85b316bb4235980b.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/board/{page-d22fd849cc22f104.js → page-9b2b26a33cb2cd42.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-bb742f995c6769e7.js → page-c9955fde67b1bcb5.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/bots/{page-02b073a2ac793b83.js → page-7a852956c74015a3.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/chat/{page-8a9b3594bc23289b.js → page-4ba4abe096b6c2a8.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/consult/page-575d4a8b4f7becb2.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/control/{page-308111abadf35670.js → page-f5d56ff5f45ad164.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/decisions/page-2260e9eef0357421.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-5d97b2901e0d1ea6.js → page-7c058069c5e7fa63.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/exec/{page-8c3d8e63ae2e1c98.js → page-466bf0ee16ad1432.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/field/{page-9b72a07d424701c6.js → page-a23bc71713bcc08f.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/intake/{page-a572f58cd3ca5864.js → page-9cd565005b93aca6.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/live/{page-be5ea965a07fbc41.js → page-474cb5888ea0e8f3.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/market/{page-fa244bd146a00ae8.js → page-9ca21f9ad3208a61.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-44d56d6391965a6a.js → page-f9ccb1b1adbea14d.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/morning/{page-83a700d4c754d802.js → page-ec102c9b8b691e37.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/{not-found-7b3170f374f77b9d.js → not-found-a9c2cfa8dda922a5.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1467047104141297.js → page-db03ae3589a81c9e.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4a6463b97f0ad94f.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-102310930791197a.js → page-75bffecc9aa1004e.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/settings/{page-6ed0b73bae8351d8.js → page-31d5b875760bb62d.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/setup/{page-43336055451f6e37.js → page-68793c6e10331d66.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/app/share/{page-3cc47dca0922ab9a.js → page-46d5201d2207a511.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-a77ee355a2304ff0.js → page-d81c07a7b4f618ae.js} +1 -1
  55. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-b17cd46cf0e9b8cf.js → page-be1c4e8697f22360.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/app/term/page-cf0a4b4abead71ea.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/trace/{page-7bf54c9bb9274af8.js → page-0fd895fab9b62c1b.js} +1 -1
  59. package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/workflows/page-0acc8e198f7b0dfb.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-134da468538baf48.js → page-34882c84f84021c5.js} +1 -1
  63. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/{webpack-c2782b330447ffb6.js → webpack-c260571513bf43c2.js} +1 -1
  65. package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
  66. package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +1 -0
  67. package/apps/pwa/out/_next/static/css/d3427996da11e480.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 -1
  87. package/apps/pwa/out/decisions/index.txt +14 -14
  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 +16 -15
  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 +16 -15
  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 +24 -23
  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/demo/editor-recipes.yaml +22 -0
  160. package/graphs/ops/guardian.yaml +26 -0
  161. package/graphs/ops/recipes.yaml +3 -0
  162. package/graphs/retro/daily.yaml +25 -0
  163. package/graphs/retro/recipes.yaml +24 -0
  164. package/package.json +1 -1
  165. package/scripts/ask-marker-check.ts +3 -1
  166. package/scripts/coord-channel-watch.sh +2 -2
  167. package/scripts/discord-seat-probe.ts +3 -1
  168. package/scripts/land-global-checks.ts +89 -0
  169. package/scripts/lib/pch-regress-map.ts +24 -0
  170. package/scripts/morning-report.ts +18 -4
  171. package/scripts/pch-regress.ts +88 -0
  172. package/scripts/release-loop/gate-diff.ts +18 -0
  173. package/scripts/release-loop/gate-node.ts +628 -65
  174. package/scripts/release-loop/gate-partial.ts +106 -0
  175. package/scripts/release-loop/gate-timing.ts +229 -0
  176. package/scripts/release-loop/preflight.ts +6 -4
  177. package/scripts/release-loop/publish-window.ts +141 -0
  178. package/scripts/release-loop/release-docs.ts +1 -4
  179. package/scripts/release-loop/release-universe.ts +38 -0
  180. package/scripts/release-loop/resume-release.ts +67 -5
  181. package/scripts/release-loop/shard-plan.ts +65 -0
  182. package/scripts/release-loop/unattended-release.ts +12 -2
  183. package/scripts/release-story/draft.ts +5 -1
  184. package/scripts/retro/shadow-compare.ts +71 -0
  185. package/scripts/rhythm/daily.ts +6 -1
  186. package/scripts/video/scene-table.ts +159 -0
  187. package/src/acp/core-turn-bridge.ts +39 -2
  188. package/src/agent-mission/driver.ts +34 -6
  189. package/src/autopilot/build/llm-conflict-merge.ts +132 -7
  190. package/src/autopilot/build/merge-intent.ts +135 -7
  191. package/src/away/presence.ts +40 -0
  192. package/src/away/release-watch.ts +272 -0
  193. package/src/chat/index.ts +2 -1
  194. package/src/chat/short-question.ts +3 -40
  195. package/src/cli/away-cli.ts +68 -0
  196. package/src/cli/config-drift.ts +513 -0
  197. package/src/cli/guardian-cli.ts +49 -0
  198. package/src/cli/intake-cli.ts +13 -7
  199. package/src/cli/log-event-names.ts +5 -0
  200. package/src/cli/pod-cli.ts +5 -1
  201. package/src/cli/pr-cli.ts +20 -0
  202. package/src/cli/pty-drive-cli.ts +14 -3
  203. package/src/cli/release-cli.ts +52 -12
  204. package/src/cli/retro-cli.ts +78 -0
  205. package/src/cli/setup-cli.ts +31 -1
  206. package/src/cli/tasks-cli.ts +193 -14
  207. package/src/context-bus/context-now-public.ts +1 -1
  208. package/src/context-bus/context-now-surfaces.ts +16 -6
  209. package/src/context-bus/context-now.ts +97 -3
  210. package/src/core-turn/short-question.ts +100 -0
  211. package/src/dashboard/dashboard-status-lines.ts +24 -4
  212. package/src/dashboard/index.ts +50 -5
  213. package/src/dashboard/input/slash-executor.ts +6 -2
  214. package/src/dashboard/slash-runtime/dashboard-handlers.ts +68 -40
  215. package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
  216. package/src/dashboard/slash-runtime/help-from-registry.ts +62 -23
  217. package/src/dashboard/slash-runtime/help-groups.ts +44 -0
  218. package/src/dashboard/slash-runtime/model-choices.ts +125 -0
  219. package/src/domains/guardian-tick.ts +138 -0
  220. package/src/domains/outbound-alert.ts +44 -13
  221. package/src/domains/outbound-log-probe.ts +6 -1
  222. package/src/domains/schedule-registry.ts +23 -0
  223. package/src/domains/telegram-kind-route.ts +39 -0
  224. package/src/domains/watch-default-scheduler.ts +95 -0
  225. package/src/esc-abort-gate.ts +44 -0
  226. package/src/graph-runner/graph-run-api.ts +205 -0
  227. package/src/graph-runner/runner.ts +36 -6
  228. package/src/harness/harness-cli-command.ts +420 -51
  229. package/src/harness/harness-queue.ts +315 -25
  230. package/src/harness/harness-salvage-cli.ts +140 -0
  231. package/src/harness/harness-worktree-add.ts +68 -0
  232. package/src/harness/ledger-write-guard.ts +6 -16
  233. package/src/hq/fence-outcomes.ts +63 -0
  234. package/src/hq/fence-wrapper.ts +33 -0
  235. package/src/hq/hq.ts +17 -2
  236. package/src/index.ts +118 -6
  237. package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
  238. package/src/instance/test-write-guard.ts +200 -0
  239. package/src/intake-plane/digest.ts +39 -15
  240. package/src/intake-plane/harness-target.ts +4 -3
  241. package/src/intake-plane/intake-sources.ts +1 -0
  242. package/src/intake-plane/items.ts +1 -0
  243. package/src/loops/orchestrator/degradation.ts +57 -0
  244. package/src/loops/orchestrator/finish-rate.ts +33 -3
  245. package/src/loops/orchestrator/tick.ts +166 -45
  246. package/src/maturity/feature-maturity.ts +6 -4
  247. package/src/maturity/tui-slash-maturity.ts +1 -0
  248. package/src/mss/logging/log-store.ts +3 -0
  249. package/src/nexus/api/graph-kinds.ts +10 -4
  250. package/src/nexus/api/graphs-api.ts +178 -6
  251. package/src/nexus/api/http-server.ts +54 -8
  252. package/src/nexus/api/loop-edges.ts +292 -17
  253. package/src/nexus/api/ops-api.ts +112 -29
  254. package/src/nexus/api/ops-board-route.ts +48 -0
  255. package/src/nexus/index.ts +7 -0
  256. package/src/onboarding/entry-hints.ts +5 -0
  257. package/src/onboarding.ts +24 -6
  258. package/src/release-loop/checklist.ts +2 -0
  259. package/src/release-loop/feature-store.ts +68 -18
  260. package/src/release-loop/gate-shards.ts +158 -0
  261. package/src/release-loop/light-rc.ts +173 -0
  262. package/src/release-loop/placement.ts +51 -17
  263. package/src/release-loop/release-schedule.ts +6 -2
  264. package/src/release-loop/rubric.ts +57 -1
  265. package/src/retro/daily.ts +372 -0
  266. package/src/seat-loop/op-decide.ts +173 -0
  267. package/src/seat-loop/seat-loop.ts +60 -3
  268. package/src/seat-loop/seat-report.ts +58 -3
  269. package/src/self-dev/ask-launch-flow.ts +5 -4
  270. package/src/self-dev/ask-launch-io.ts +49 -1
  271. package/src/self-dev/ask-path-candidates.ts +40 -2
  272. package/src/self-dev/dev-cli.ts +36 -0
  273. package/src/self-dev/draft-sweep.ts +371 -16
  274. package/src/self-dev/draft-triage-rules.ts +19 -4
  275. package/src/self-dev/orchestrate.ts +1 -0
  276. package/src/self-dev/pr-preview.ts +247 -0
  277. package/src/self-dev/shard-run.ts +23 -7
  278. package/src/self-implement/blocked-draft-policy.ts +101 -0
  279. package/src/self-implement/execution-clarification.ts +11 -0
  280. package/src/self-implement/gate-cli.ts +29 -8
  281. package/src/self-implement/gate-env-retry.ts +83 -0
  282. package/src/self-implement/goal-author-clarification.ts +88 -13
  283. package/src/self-implement/goal-clarification-escalation.ts +2 -0
  284. package/src/self-implement/orchestrator.ts +220 -6
  285. package/src/self-implement/run-stop.ts +123 -0
  286. package/src/self-implement/run-ttl.ts +258 -0
  287. package/src/self-implement/salvage-branches.ts +254 -0
  288. package/src/self-implement/salvage-retention.ts +123 -0
  289. package/src/self-implement/seams.ts +4 -0
  290. package/src/self-implement/self-implement-cli.ts +2 -0
  291. package/src/self-implement/sibling-resync.ts +223 -0
  292. package/src/self-implement/stop-autoheal.ts +204 -0
  293. package/src/task-agent/actions.ts +63 -2
  294. package/src/task-agent/board.ts +132 -0
  295. package/src/task-agent/card-evidence.ts +292 -0
  296. package/src/task-agent/completion-evidence.ts +126 -0
  297. package/src/task-agent/judge.ts +25 -0
  298. package/src/task-agent/live-moves.ts +276 -0
  299. package/src/task-agent/mission.ts +33 -7
  300. package/src/task-agent/shadow.ts +192 -15
  301. package/src/task-agent/task-hand.ts +124 -17
  302. package/src/task-orchestrator/surfaces/pod-command-job.ts +82 -8
  303. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
  304. package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
  305. package/src/task-orchestrator/surfaces/pod-lease.ts +233 -21
  306. package/src/task-orchestrator/surfaces/pod-pool.ts +155 -34
  307. package/src/task-orchestrator/surfaces/self-implement-pod.ts +21 -5
  308. package/src/telegram-away-command.ts +38 -0
  309. package/src/telegram-commands.ts +52 -0
  310. package/src/telegram-report-log-probe.ts +21 -0
  311. package/src/telegram-report.ts +27 -3
  312. package/src/testing/real-cli-spawn.ts +106 -0
  313. package/src/tui-client/dashboard-session.ts +1 -0
  314. package/src/user-config.ts +190 -5
  315. package/src/version/packed-revision.json +1 -1
  316. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
  324. package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/editor/page-c39554c321576982.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a3c58f5c72a5f3f9.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-44d5f767491692bf.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
  348. package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
  349. /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_buildManifest.js +0 -0
  350. /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_ssgManifest.js +0 -0
@@ -64,12 +64,15 @@ import { GoalRunStore, insertGoalRunRecord, type GoalPriorRuns } from './goal-ru
64
64
  import { writeGoalRunRecordFragment } from './goal-execution-records.js';
65
65
  import { getUserConfig } from '../user-config.js';
66
66
  import { appendRunLedgerEntry, loadRunLedger, parseRunShardIdentity, queryRunChain, type RunChainShardSibling, type RunLedgerEntry, type RunLedgerWriter, type RunOriginData, type RunShardIdentity } from './run-ledger.js';
67
+ import { classifyMergeHoldStop, forgetRunStop, readRunStopRecord, recordRunStop, runStopRecorded } from './run-stop.js';
68
+ import { autohealFromStop, parseAutohealMode, type AutohealActions, type AutohealResult } from './stop-autoheal.js';
67
69
  import { emitSessionEvent, type SessionEventInput } from '../context-bus/session-events.js';
68
70
  import { decideLineageSupersede, lineageSupersedeCloseComment, type LineageSupersedeOpenDraft } from './lineage-supersede.js';
69
71
  import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
70
72
  import { homedir } from 'node:os';
71
73
  import { enqueueControlMemo, readSoftStopRequestStatus, type SoftStopRequestRead } from '../harness/control-inbox.js';
72
74
  import { debug } from '../debug/log.js';
75
+ import { classifyGateEnvDeficit, DEFAULT_GATE_ENV_RETRY, GATE_ENV_DEFICIT_HARVESTABLE_MARK, type GateEnvDeficitKind } from './gate-env-retry.js';
73
76
  import { recordFailureEvent } from './heal-intake.js';
74
77
  import { classifyReworkCapExhaustion, type ReworkCapHealDecision } from './heal-triage.js';
75
78
  import { releasePathHold, releasePathHoldComment, RELEASE_PATH_LABEL } from '../self-dev/release-path-guard.js';
@@ -210,7 +213,7 @@ import { hasModuleLoadFailure, type GateTestFailure } from './gate-baseline.js';
210
213
  import { commitWorktree, defaultBranchRef, preservationHasChanges } from './seams.js';
211
214
  import { detectUncommittedWork } from './uncommitted-work.js';
212
215
  import { spawnSync } from 'node:child_process';
213
- import { blockedDraftDisposition } from './blocked-draft-policy.js';
216
+ import { blockedDraftDisposition, decideDraftOnStop, nextMoveFor, parseDraftOnStopMode, salvageBranchForRun, type DraftOnStopMode } from './blocked-draft-policy.js';
214
217
  import { formatLlmMergeOutcome } from '../autopilot/build/llm-conflict-merge.js';
215
218
  import { FOUND_CITED_PATH_REFUTATION_QUOTE, MISSING_CITED_PATH_REFUTATION_QUOTE, MUST_FIX_REFUTATION_ACKNOWLEDGEMENT_WITH_REASON, REFUTATION_QUOTE_GRAMMAR, observeMustFixCitedPaths, parseMustFixRefutationAcknowledgement, parseMustFixRefutations, renderMustFixCitedPathFacts, snapshotMustFixFindings, stableMustFixId, type ReflectEvidenceFacts, type ReflectGateFacts, type MustFixCitedPathFact, type MustFixFinding, type MustFixRecurrenceHistory, type MustFixRefutation, type MustFixRefutationKind } from './reflect-mustfix.js';
216
219
  import type { IngestionEntry } from '../agent-substrate/execution/ingestion-policy.js';
@@ -762,7 +765,8 @@ function changedFileIsInsideDeclaredTarget(changedFile: string, declaredPath: st
762
765
  const file = normalizeDeclaredScopePath(changedFile);
763
766
  const target = normalizeDeclaredScopePath(declaredPath);
764
767
  if (!file || !target) return false;
765
- return file === target || file.startsWith(`${target}/`);
768
+ return file === target || file.startsWith(`${target}/`)
769
+ || (target.endsWith('.ts') && !target.endsWith('.test.ts') && file === `${target.slice(0, -3)}.test.ts`);
766
770
  }
767
771
 
768
772
  function parseDeclaredTargetPathsFromAsk(askText: string): { readable: boolean; paths: readonly string[] } {
@@ -930,6 +934,12 @@ export type ImplementTerminalStatus = {
930
934
  export interface SelfImplementSeams {
931
935
  /** Run-observer ledger sink. Tests inject an in-memory sink to avoid touching user state. */
932
936
  writeRunLedger?: RunLedgerWriter;
937
+ /** STOP-AUTOHEAL — heal action seams (live mode only runs actions given here; missing = needs-owner). */
938
+ stopAutohealActions?: AutohealActions;
939
+ /** STOP-AUTOHEAL — one-attempt guard reader. Default: real run ledger, or none when `writeRunLedger` is injected. */
940
+ stopAutohealLoadLedger?: (runId: string) => readonly RunLedgerEntry[] | null;
941
+ /** STOP-AUTOHEAL — observes each dispatcher result (tests). */
942
+ onStopAutoheal?: (result: AutohealResult) => void;
933
943
  /** Optional context-journal sink for isolated runs. */
934
944
  emitContextEvent?: (input: SessionEventInput) => void;
935
945
  /** Registers this run as an ephemeral loop-agent asset; failures are observed but never affect execution. */
@@ -1028,6 +1038,11 @@ export interface SelfImplementSeams {
1028
1038
  reviewBaselineObservationSource?: (cwd: string, base: string) => Promise<string | undefined>;
1029
1039
  /** ④ gate — cwd 에서 bun test/build. postsync=정합 후 재게이트(merge-base 실행 범위). */
1030
1040
  gate: (cwd: string, ctx?: { runId?: string; mode?: 'postsync'; round?: number }) => Promise<SelfImplementGateResult>;
1041
+ /**
1042
+ * GATE-ENV-RETRY — 환경 결손 gate 실패에 «게이트만» 다시 도는 정책. 생략 시 기본(2회 · 20초).
1043
+ * `attempts: 0` 이면 재시도하지 않는다. 시험은 `sleep` 으로 대기를 주입한다.
1044
+ */
1045
+ gateEnvRetry?: { attempts?: number; delayMs?: number; sleep?: (ms: number) => Promise<void> };
1031
1046
  /** Gate가 실행된 worktree HEAD가 local origin/main보다 뒤처진 커밋 수. 실패는 undefined(unknown)로 정직하게 남긴다. */
1032
1047
  gateWorktreeBehindMain?: (cwd: string) => Promise<number | undefined>;
1033
1048
  /** 변경 파일 목록 — 게이트 «라우터»가 「코드가 바뀌었나」를 판정하는 입력.
@@ -1068,6 +1083,8 @@ export interface SelfImplementSeams {
1068
1083
  headCommit?: (cwd: string) => string | undefined;
1069
1084
  /** `commit` pins the pushed state; without it the worktree HEAD is pushed. */
1070
1085
  preserveBlockedBranch?: (ctx: { cwd: string; branch: string; title: string; commit?: string }) => boolean | Promise<boolean>;
1086
+ /** DRAFT-NOT-ARCHIVE — `tools.selfImplement.draftOnStop` 읽기 seam. 생략하면 user-config(실패·부재 = 'always' · 종전). */
1087
+ draftOnStopMode?: () => DraftOnStopMode;
1071
1088
  /** Hard-cap salvage evidence: a clean worktree and commits ahead of origin/main. */
1072
1089
  readReworkSalvageEvidence?: (cwd: string) => Promise<ReworkSalvageEvidence>;
1073
1090
  /** Starts exactly one detached `elanous dev --file <goal> --base <branch>` follow-up. */
@@ -1092,6 +1109,8 @@ export interface SelfImplementSeams {
1092
1109
  mergePr?: (opts: { number: number; cwd: string; matchHeadCommit?: string }) => Promise<{ merged: boolean; baseRefName?: string; mergeCommit?: string; detail?: string }>;
1093
1110
  /** Only successful merges trigger this injected, fail-closed draft inventory and mutation seam. */
1094
1111
  supersedeDraftsOnMerge?: (number: number, cwd: string) => Promise<void>;
1112
+ /** SIBLING-RESYNC-ON-MERGE — after a successful merge, tell open sibling PRs on the same paths to rebase + regate now. Failure is observation-only. */
1113
+ resyncSiblingsOnMerge?: (number: number, cwd: string) => Promise<void>;
1095
1114
  /** Post-merge cleanup is opt-in and injectable so tests never touch the filesystem. */
1096
1115
  postMergeCleanup?: {
1097
1116
  enabled: boolean;
@@ -3479,6 +3498,28 @@ function runOriginData(env: NodeJS.ProcessEnv = process.env): RunOriginData {
3479
3498
  };
3480
3499
  }
3481
3500
 
3501
+ /**
3502
+ * STOP-AUTOHEAL — a recorded stop goes straight to the dispatcher (shadow by default:
3503
+ * `tools.selfImplement.autoheal.mode`). Never throws and never changes the run outcome.
3504
+ */
3505
+ async function autohealRecordedStop(stopEntry: RunLedgerEntry, s: SelfImplementSeams | undefined): Promise<void> {
3506
+ try {
3507
+ const record = readRunStopRecord(stopEntry);
3508
+ if (!record) return;
3509
+ const mode = parseAutohealMode((await import('../user-config.js')).getUserConfig().tools.selfImplement.autoheal?.mode);
3510
+ const loadLedger = s?.stopAutohealLoadLedger ?? (s?.writeRunLedger ? () => null : undefined);
3511
+ const result = await autohealFromStop({ runId: stopEntry.runId, ...(stopEntry.goalId ? { goalId: stopEntry.goalId } : {}), record }, {
3512
+ mode,
3513
+ ...(s?.stopAutohealActions ? { actions: s.stopAutohealActions } : {}),
3514
+ ...(loadLedger ? { loadLedger } : {}),
3515
+ ...(s?.writeRunLedger ? { writeLedger: s.writeRunLedger } : {}),
3516
+ });
3517
+ s?.onStopAutoheal?.(result);
3518
+ } catch (error) {
3519
+ try { debug.log('self-implement.autoheal', 'dispatch-failed', { runId: stopEntry.runId, error: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
3520
+ }
3521
+ }
3522
+
3482
3523
  export async function runSelfImplement(opts: SelfImplementOptions): Promise<SelfImplementResult> {
3483
3524
  if (opts.childLlm && (typeof opts.childLlm.provider !== 'string' || !opts.childLlm.provider.trim() || typeof opts.childLlm.model !== 'string' || !opts.childLlm.model.trim())) {
3484
3525
  throw new Error('childLlm.provider and childLlm.model must both be non-empty');
@@ -3500,6 +3541,7 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
3500
3541
  const attemptOrdinal = incrementRunAttemptOrdinal(runId);
3501
3542
  const shardIdentity = parseRunShardIdentity(opts.feature);
3502
3543
  const observeOuter = makeRunObserver(runId, opts.goalId, debug.log.bind(debug), opts.seams.writeRunLedger, shardIdentity);
3544
+ forgetRunStop(runId); // STOP-RECORD: one stop per run «attempt» — a rerun with the same id may record again
3503
3545
  try {
3504
3546
  // 10-05 PODPROVIDER: record which child provider this run uses — «default» when the launch named none.
3505
3547
  const launch = opts.launch ?? resolveLaunchStamp();
@@ -4108,6 +4150,21 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
4108
4150
  observeOuter('credential-failure-observed', { count: credentialFailureCount, ...lastCredentialFailure });
4109
4151
  }
4110
4152
  terminalResult = withEvidenceCoverage(addressedResult);
4153
+ // runSelfImplement is the terminal caller: retain every existing PR/ledger/log observation and append one stop fact.
4154
+ if ((addressedResult.stage === 'aborted' || (addressedResult.mergeReason && addressedResult.mergeReason !== 'auto')) && !runStopRecorded(runId)) {
4155
+ const reason = addressedResult.mergeReason;
4156
+ const aborted = addressedResult.stage === 'aborted';
4157
+ let stopEntry: RunLedgerEntry | undefined;
4158
+ try {
4159
+ stopEntry = recordRunStop({ runId, ...(opts.goalId ? { goalId: opts.goalId } : {}), shardIdentity,
4160
+ site: aborted ? 'implement-aborted' : 'merge-hold',
4161
+ class: aborted ? 'unclassified' : classifyMergeHoldStop(reason), cause: aborted ? 'implement aborted' : `merge held: ${reason}`,
4162
+ evidenceRef: addressedResult.prUrl ?? `run-ledger:${runId}:${aborted ? 'implemented' : 'merge-decision'}`,
4163
+ nextMove: aborted ? 'Inspect preserved worktree and child summary' : 'Review merge decision and PR before landing',
4164
+ }, opts.seams.writeRunLedger ?? appendRunLedgerEntry);
4165
+ } catch { /* stop observation cannot change the run outcome */ }
4166
+ if (stopEntry) await autohealRecordedStop(stopEntry, opts.seams);
4167
+ }
4111
4168
  observeDeclaredScopeDiff(observeOuter, {
4112
4169
  goalFile: opts.goalFile,
4113
4170
  changedFiles: opts.goalFile && addressedResult.worktreePath
@@ -5134,6 +5191,8 @@ async function runSelfImplementInner(
5134
5191
  limitationCount: number;
5135
5192
  } | undefined;
5136
5193
  let gate!: SelfImplementGateResult;
5194
+ /** GATE-ENV-RETRY: 재시도 뒤에도 남은 환경 결손의 종류. 정의되면 이 라운드의 gate 실패는 «수확 대기»로 끝난다. */
5195
+ let gateEnvDeficitKind: GateEnvDeficitKind | undefined;
5137
5196
  let review: SelfImplementReview | undefined;
5138
5197
  /** Review-budget leftovers and the body that carried them, so a later merge drafts from the same text. */
5139
5198
  let preparedReviewBudgetPrBody: string | undefined;
@@ -5381,6 +5440,7 @@ async function runSelfImplementInner(
5381
5440
  const { listSubscribers } = await import('../session/index.js');
5382
5441
  clarificationEscalation = await escalateGoalDocumentClarifications({
5383
5442
  goalFile: resolvedGoalFile,
5443
+ runId,
5384
5444
  dispatchContext: clarificationDispatchContext,
5385
5445
  resolveOriginDelivery: (sessionId) => originHitlDelivery(listSubscribers(sessionId)),
5386
5446
  // ⛔ seam 이 주입되면 전역 리졸버는 «불리지 않는다» — 그때 'terminal' 이라 적으면
@@ -5555,6 +5615,15 @@ async function runSelfImplementInner(
5555
5615
  });
5556
5616
  };
5557
5617
  const podReviewBlockedDraft = process.env.ELANOUS_SUBSTRATE === 'pod';
5618
+ /** DRAFT-NOT-ARCHIVE: 멈춤 산출을 PR 대신 수확 가지로 보냈으면 그 가지 — hard-cap 재발사의 base 가 된다. */
5619
+ let salvagedStopBranch: string | undefined;
5620
+ const readDraftOnStopMode = (): DraftOnStopMode => {
5621
+ try {
5622
+ return s.draftOnStopMode ? s.draftOnStopMode() : parseDraftOnStopMode(getUserConfig().tools?.selfImplement?.draftOnStop).mode;
5623
+ } catch {
5624
+ return 'always';
5625
+ }
5626
+ };
5558
5627
  let blockedDraftNoChanges = false;
5559
5628
  let blockedDraftPrFailure: string | undefined;
5560
5629
  const reviewBlockedResult = (pr: { url: string; number: number } | undefined) => ({
@@ -5767,6 +5836,72 @@ async function runSelfImplementInner(
5767
5836
  progress(stage, '보존할 변경이 없어 draft PR을 열지 않음');
5768
5837
  return undefined;
5769
5838
  }
5839
+ // DRAFT-NOT-ARCHIVE (RFC R1): harvestable 멈춤은 draft PR 대신 수확 가지 ⊕ 런 원장(사유·다음 수).
5840
+ // ⛔ Pod review-blocked 는 결과 stage 가 PR 유무로 갈리므로 이 조각에서는 건드리지 않는다.
5841
+ const draftOnStopMode = readDraftOnStopMode();
5842
+ let launchSeat: string | undefined;
5843
+ try { launchSeat = (opts.launch ?? resolveLaunchStamp()).actor; } catch { launchSeat = undefined; }
5844
+ const draftOnStop = decideDraftOnStop({ mode: draftOnStopMode, classification: classificationRecord.classification, seat: launchSeat });
5845
+ const stopCard = {
5846
+ stage,
5847
+ mode: draftOnStop.mode,
5848
+ stopClass: draftOnStop.stopClass,
5849
+ classification: classificationRecord.classification,
5850
+ reason: reason.slice(0, 240),
5851
+ nextMove: nextMoveFor(classificationRecord.classification),
5852
+ branch: wt.branch,
5853
+ };
5854
+ if (draftOnStop.action === 'salvage-branch' && !(stage === 'review-blocked' && podReviewBlockedDraft)) {
5855
+ const salvageBranch = salvageBranchForRun(runId, wt.branch);
5856
+ let artifactPath: string | undefined;
5857
+ let artifactError: string | undefined;
5858
+ try {
5859
+ artifactPath = persistFullPrBody(s.persistPrBodyArtifact, {
5860
+ origin: 'self-implement-draft-not-archive',
5861
+ body: assembled.body,
5862
+ originalChars: assembled.originalChars,
5863
+ });
5864
+ } catch (persistenceError) {
5865
+ artifactError = persistenceError instanceof Error ? persistenceError.message : String(persistenceError);
5866
+ }
5867
+ let salvageError: string | undefined;
5868
+ try {
5869
+ if (s.preserveBlockedBranch) {
5870
+ if (!await s.preserveBlockedBranch({ cwd: wt.path, branch: salvageBranch, title: prTitle(opts.feature) })) throw new Error('수확 가지 push가 성공하지 않았음');
5871
+ } else {
5872
+ if (!existsSync(wt.path)) throw new Error(`worktree not found: ${wt.path}`);
5873
+ const commit = commitWorktree(wt.path, prTitle(opts.feature));
5874
+ if (!commit.ok && !/nothing to commit/.test(commit.out)) throw new Error(`로컬 커밋 실패: ${commit.out}`);
5875
+ const push = runGitCommand(wt.path, ['push', 'origin', `HEAD:refs/heads/${salvageBranch}`]);
5876
+ if (push.status !== 0) throw new Error(push.stderr || push.stdout || 'git push failed');
5877
+ }
5878
+ } catch (error) {
5879
+ salvageError = safeErrorDescription(error);
5880
+ }
5881
+ const artifactFields = { ...(artifactPath ? { prBodyArtifactPath: artifactPath } : {}), ...(artifactError ? { prBodyArtifactError: artifactError } : {}) };
5882
+ if (salvageError === undefined) {
5883
+ salvagedStopBranch = salvageBranch;
5884
+ observe('salvaged', { ...stopCard, salvageBranch, ...artifactFields }, { category: 'self-implement.draft-not-archive' });
5885
+ observe('rework-blocked-draft-pr', { ...observation, skipped: 'draft-not-archive', salvageBranch, nextMove: stopCard.nextMove, ...artifactFields });
5886
+ progress(stage, `멈춤 — draft PR 없이 수확 가지로 보존: ${salvageBranch} · 사유: ${stopCard.reason.slice(0, 160)} · 다음 수: ${stopCard.nextMove}${artifactPath ? ` · 판정 본문: ${artifactPath}` : ''}`);
5887
+ return undefined;
5888
+ }
5889
+ // 수확 가지를 못 남기면 산출을 잃지 않도록 종전대로 draft PR 로 간다.
5890
+ observe('salvage-failed', { ...stopCard, salvageBranch, error: salvageError, fallback: 'open-draft', ...artifactFields }, { category: 'self-implement.draft-not-archive', level: 'warn' });
5891
+ } else if (draftOnStop.mode === 'needs-owner-only') {
5892
+ const ownerLabel = draftOnStop.action === 'open-draft' ? draftOnStop.ownerLabel : undefined;
5893
+ // 자리를 모르면 «모름»으로 남긴다(⛔ OP 로 추측하지 않는다).
5894
+ observe(draftOnStop.stopClass === 'landable' ? 'landable-draft' : 'needs-owner-draft', { ...stopCard, ownerSeat: ownerLabel ? ownerLabel.slice('elanous:seat-'.length) : 'unknown', ...(ownerLabel ? { ownerLabel } : {}) }, { category: 'self-implement.draft-not-archive' });
5895
+ }
5896
+ // 'always' 는 종전 라벨 그대로 · needs-owner-only 의 needs-owner 는 `elanous:needs-owner` ⊕ (아는 경우만) 자리 라벨.
5897
+ // landable(승인된 산출)은 자리 라벨만 — 다음 수가 `pr land` 라 needs-owner 가 아니다.
5898
+ const needsOwnerDraft = draftOnStop.mode === 'needs-owner-only' && draftOnStop.stopClass === 'needs-owner';
5899
+ const seatLabelled = draftOnStop.mode === 'needs-owner-only' && draftOnStop.stopClass !== 'harvestable';
5900
+ const draftLabels = [
5901
+ ...(stage === 'review-blocked' ? ['review-blocked'] : []),
5902
+ ...(seatLabelled && draftOnStop.action === 'open-draft' && draftOnStop.ownerLabel ? [draftOnStop.ownerLabel] : []),
5903
+ ...(needsOwnerDraft ? ['elanous:needs-owner'] : []),
5904
+ ];
5770
5905
  // Pod review-blocked: a failed commit or PR still pushes the branch, and the result detail says what survived.
5771
5906
  const preserveReviewBlockedBranch = async (cause: string, commitFirst: boolean, suffix = ''): Promise<undefined> => {
5772
5907
  let pushed = false;
@@ -5816,7 +5951,7 @@ async function runSelfImplementInner(
5816
5951
  head: wt.branch,
5817
5952
  ...(opts.base ? { base: opts.base } : {}),
5818
5953
  draft: true,
5819
- ...(stage === 'review-blocked' ? { labels: ['review-blocked'] } : {}),
5954
+ ...(draftLabels.length > 0 ? { labels: draftLabels } : {}),
5820
5955
  cwd: wt.path,
5821
5956
  }), T.pr, 'pr');
5822
5957
  } catch (error) {
@@ -5838,6 +5973,17 @@ async function runSelfImplementInner(
5838
5973
  ...(artifactPath ? { prBodyArtifactPath: artifactPath } : {}),
5839
5974
  ...(artifactError ? { prBodyArtifactError: artifactError } : {}),
5840
5975
  }, { level: 'warn' });
5976
+ // STOP-RECORD: a failed blocked-draft PR open (e.g. same-branch rerun with a PR already open) left no stop reason.
5977
+ let draftStopEntry: RunLedgerEntry | undefined;
5978
+ try {
5979
+ const prExists = /already exists|pull request for branch/i.test(prError);
5980
+ draftStopEntry = recordRunStop({ runId, ...(opts.goalId ? { goalId: opts.goalId } : {}), site: 'draft-pr-open-failed', class: 'unclassified',
5981
+ cause: prExists ? 'draft PR open failed: a PR already exists for this branch' : `draft PR open failed: ${prError}`,
5982
+ evidenceRef: artifactPath ?? `branch:${wt.branch}`,
5983
+ nextMove: prExists ? '기존 PR 갱신 (update the existing PR for this branch)' : 'Retry draft PR open from the preserved branch',
5984
+ }, s.writeRunLedger ?? appendRunLedgerEntry);
5985
+ } catch { /* stop observation cannot change the run outcome */ }
5986
+ if (draftStopEntry) await autohealRecordedStop(draftStopEntry, s);
5841
5987
  if (stage === 'review-blocked' && podReviewBlockedDraft) {
5842
5988
  return preserveReviewBlockedBranch(`draft PR 생성 실패: ${prError}`, true,
5843
5989
  `; ${artifactPath ? `판정 본문: ${artifactPath}` : `판정 본문 보존 실패: ${artifactError ?? 'unknown error'}`}`);
@@ -5872,6 +6018,13 @@ async function runSelfImplementInner(
5872
6018
  observe('pr.comment.seam-missing', { number: pr.number, salvageAction: action }, { level: 'warn' });
5873
6019
  return;
5874
6020
  }
6021
+ // QUIET-PR-COMMENTS: a parked outcome is history of this run, not a new notification — fold it into the
6022
+ // single edited status comment (an edit mails nobody). Without the status seams it stays a separate comment.
6023
+ if (s.findPrComment && s.editPrComment) {
6024
+ bufferPrComment(round, 'author', `Rework salvage status: ${action}.`, reason ? [`reason: ${reason}`] : []);
6025
+ await flushPrComments(pr);
6026
+ return;
6027
+ }
5875
6028
  const body = [
5876
6029
  formatPrComment({ role: 'author', round, run: runId }),
5877
6030
  `Rework salvage status: ${action}.`,
@@ -6045,7 +6198,7 @@ async function runSelfImplementInner(
6045
6198
  observe('rework-salvage', { action: 'launching', ...launchObservation });
6046
6199
  try {
6047
6200
  if (!s.launchReworkSalvage) throw new Error('launch seam unavailable');
6048
- await s.launchReworkSalvage({ goalFile: opts.goalFile!, base: wt.branch, salvageAttempt: (opts.salvageAttempt ?? 0) + 1 });
6201
+ await s.launchReworkSalvage({ goalFile: opts.goalFile!, base: salvagedStopBranch ?? wt.branch, salvageAttempt: (opts.salvageAttempt ?? 0) + 1 });
6049
6202
  observe('rework-salvage', { action: 'launched', ...launchObservation });
6050
6203
  await postSalvageStatus(pr, 'launched', environmentReason ?? (hardCapBlockedExtend ? 'hard-cap-extend' : undefined));
6051
6204
  return 'launched';
@@ -6231,7 +6384,7 @@ async function runSelfImplementInner(
6231
6384
  const source = readFileSync(opts.goalFile, 'utf8');
6232
6385
  // Always run the shared self-answer so every rework pass leaves one observation, even with nothing pending.
6233
6386
  const resolved = await selfAnswerGoalDocumentClarifications(source, {
6234
- path: 'rework', ask: source, resolver: s.selfResolveClarification,
6387
+ path: 'rework', ask: source, resolver: s.selfResolveClarification, runId,
6235
6388
  });
6236
6389
  if (resolved.newlyAnswered > 0) writeGoalDocumentAtomic(opts.goalFile, resolved.document);
6237
6390
  } catch (error) {
@@ -6842,7 +6995,7 @@ async function runSelfImplementInner(
6842
6995
  { label: 'Stay in scope', description: 'Do not accept out-of-scope changes.', recommended: true },
6843
6996
  { label: 'Expand scope', description: 'Accept the changed files outside the target paths.' },
6844
6997
  ],
6845
- }, { runId }, { writeRunLedger: s.writeRunLedger });
6998
+ }, { runId, outsideFiles: scope.outsideNames, outsideCount: scope.outsideCount, declaredPaths: declaredPathsFromGoalDocumentText(goalDocument!).paths }, { writeRunLedger: s.writeRunLedger });
6846
6999
  if ('parked' in result || 'deferred' in result || result.choice !== 'Expand scope') {
6847
7000
  finalizeSupervisorDeliveries('execution-scope-not-approved');
6848
7001
  observe('execution-scope-held', { outsideCount: scope.outsideCount, disposition: 'parked' in result ? 'parked' : 'in-scope-only' });
@@ -6874,6 +7027,31 @@ async function runSelfImplementInner(
6874
7027
  }
6875
7028
  onNodeEntry(node('gate'), round);
6876
7029
  gate = await stepTimeout(s.gate(wt.path, { runId, round }), T.gate, 'gate');
7030
+ // ⭐ GATE-ENV-RETRY — 「잰 쪽」이 넘어진 실패(시험 결과 미수신·index.lock·OOM·측정 불가·네트워크)는
7031
+ // 구현을 버리지 않고 짧게 쉰 뒤 gate «만» 다시 돈다. ⛔ 도입 실패·자식 책임은 판정자가 거짓을 낸다.
7032
+ gateEnvDeficitKind = undefined;
7033
+ let envVerdict = classifyGateEnvDeficit(gate);
7034
+ const envRetryAttempts = Math.max(0, s.gateEnvRetry?.attempts ?? DEFAULT_GATE_ENV_RETRY.attempts);
7035
+ const envRetryDelayMs = Math.max(0, s.gateEnvRetry?.delayMs ?? DEFAULT_GATE_ENV_RETRY.delayMs);
7036
+ const envRetrySleep = s.gateEnvRetry?.sleep ?? ((ms: number) => new Promise<void>((done) => setTimeout(done, ms)));
7037
+ for (let attempt = 1; envVerdict.envDeficit && attempt <= envRetryAttempts; attempt++) {
7038
+ const kind = envVerdict.kind;
7039
+ debug.log('self-implement.gate-env-retry', 'retry-scheduled', { runId, round, attempt, attempts: envRetryAttempts, kind, delayMs: envRetryDelayMs });
7040
+ observe('gate-env-retry', { round, attempt, attempts: envRetryAttempts, kind, delayMs: envRetryDelayMs }, { level: 'warn' });
7041
+ progress('gating', `gate 환경 결손(${kind}) — 구현 유지 · ${Math.round(envRetryDelayMs / 1000)}초 뒤 gate 만 재측정 (${attempt}/${envRetryAttempts})`);
7042
+ await envRetrySleep(envRetryDelayMs);
7043
+ gate = await stepTimeout(s.gate(wt.path, { runId, round }), T.gate, 'gate');
7044
+ envVerdict = classifyGateEnvDeficit(gate);
7045
+ debug.log('self-implement.gate-env-retry', 'retry-result', {
7046
+ runId, round, attempt, kind, passed: gate.passed,
7047
+ stillEnvDeficit: envVerdict.envDeficit,
7048
+ ...(envVerdict.envDeficit ? { nextKind: envVerdict.kind } : { verdict: envVerdict.reason }),
7049
+ });
7050
+ }
7051
+ if (envVerdict.envDeficit) {
7052
+ gateEnvDeficitKind = envVerdict.kind;
7053
+ debug.log('self-implement.gate-env-retry', 'exhausted', { runId, round, attempts: envRetryAttempts, kind: envVerdict.kind });
7054
+ }
6877
7055
  } else {
6878
7056
  observe('gate-skipped-by-graph', { ...graphAuthorityFields(graphAuthority, graphTemplate), round, reason: gateRoute.reason, changedFileCount: gateRouteFiles?.length ?? null });
6879
7057
  gate = {
@@ -6960,6 +7138,26 @@ async function runSelfImplementInner(
6960
7138
  // 타임아웃«뿐» — 도입 0 · 미분류 0 · 자식 책임 없음. 재작업으로 보내지 않고 리뷰로 진행한다.
6961
7139
  // 게이트를 통과로 접지하지 않는다(allowsBaselineOnlyFailure 는 그대로 timedOut===0 을 요구).
6962
7140
  // 이 런의 병합은 뒤에서 GATE_TIMEOUT_UNMEASURED_MERGE_REASON 으로 hitl 에 고정한다.
7141
+ if (gateEnvDeficitKind !== undefined) {
7142
+ // ⭐ GATE-ENV-RETRY — 재측정 뒤에도 환경 결손이면 구현을 «버리지 않는다».
7143
+ // 브랜치·draft PR 을 남기고 `harvestable` 로 끝낸다(재작업은 이 신호를 못 바꾼다).
7144
+ const envKind = gateEnvDeficitKind;
7145
+ finalizeSupervisorDeliveries('gate-env-deficit-harvestable');
7146
+ const reason = `gate failed on an environment deficit (${envKind}) after gate-only retries; ${GATE_ENV_DEFICIT_HARVESTABLE_MARK} — implementation kept without rework`;
7147
+ bufferPrComment(round, 'reviewer', `Round ${round}: ${reason}.`, [
7148
+ `Gate: ${gate.log?.trim() || '(gate log unavailable)'}`,
7149
+ ]);
7150
+ observe('gate-env-deficit-harvestable', {
7151
+ round, kind: envKind,
7152
+ ...(facts ? { introduced: facts.introduced, preexisting: facts.preexisting, unknown: facts.unknown } : {}),
7153
+ ...(facts?.unknownReason !== undefined ? { unknownReason: facts.unknownReason } : {}),
7154
+ reason,
7155
+ }, { level: 'warn' });
7156
+ debug.log('self-implement.gate-env-retry', 'harvestable', { runId, round, kind: envKind, branch: wt.branch });
7157
+ progress('gate-failed', `중단 — 환경 결손(${envKind})이 재측정 뒤에도 남음 · 구현 유지 · 수확 대기`);
7158
+ const pr = await preserveBlockedArtifacts({ stage: 'gate-failed', reason, gate, salvageStatusExpected: false });
7159
+ return { ok: false, stage: 'gate-failed', node: 'rework', ...resolveRunOutcome({ termination: 'abandoned' }), harvestable: true, sessionId, worktreePath: wt.path, branch: wt.branch, gate, quotaExhaustionAssessment: quotaExhaustionAssessmentForRun(), ...(pr ? { prUrl: pr.url, prNumber: pr.number } : {}), detail: reason };
7160
+ }
6963
7161
  if (isTimeoutOnlyUnmeasuredGate(facts)) {
6964
7162
  observe('gate-timeout-only-continued-to-review', {
6965
7163
  round,
@@ -7536,6 +7734,16 @@ async function runSelfImplementInner(
7536
7734
  sync.status,
7537
7735
  );
7538
7736
  observe('gate.postsync', postSyncGateObservation(regate, wt.branch, hasModuleLoadFailure, sync.status));
7737
+ // MERGE-INTENT-RESOLVE — join the resolver's sibling-intent decisions with the regate verdict (OP metric).
7738
+ if (sync.intentResolve?.length) {
7739
+ debug.log('self-implement.main-sync', 'intent-resolve', {
7740
+ phase: 'regate', runId, branch: wt.branch, mergeTarget,
7741
+ adopted: [...new Set(sync.intentResolve.map((entry) => entry.adopted))],
7742
+ siblingIntentAdopted: sync.intentResolve.some((entry) => entry.adopted === 'sibling-intent'),
7743
+ files: sync.intentResolve,
7744
+ regatePassed: regate.passed,
7745
+ });
7746
+ }
7539
7747
  if (timeoutOnlyPostSync) {
7540
7748
  const facts = regate.reflectGateFacts!;
7541
7749
  observe('post-sync-gate-timeout-only-continued', {
@@ -7859,6 +8067,12 @@ async function runSelfImplementInner(
7859
8067
  } catch (error) {
7860
8068
  observe('draft-supersede-on-merge-failed', { number: pr.number, error: safeErrorDescription(error) }, { level: 'warn' });
7861
8069
  }
8070
+ // Runs after supersede: a sibling the merge already closed is no longer open, so it gets no resync.
8071
+ try {
8072
+ await s.resyncSiblingsOnMerge?.(pr.number, wt.path);
8073
+ } catch (error) {
8074
+ observe('sibling-resync-on-merge-failed', { number: pr.number, error: safeErrorDescription(error) }, { level: 'warn' });
8075
+ }
7862
8076
  const directory = releaseNotesDir(elanousStateRoot());
7863
8077
  const fragment: ReleaseNoteFragment = {
7864
8078
  pr: pr.number,
@@ -0,0 +1,123 @@
1
+ /**
2
+ * STOP-RECORD (0.2.20 P0) — 런이 «멈춘» 자리마다 런 원장에 `event='stop'` 한 줄을 «더한다».
3
+ *
4
+ * 멈추는 자리 넷이 이 함수 하나를 부른다:
5
+ * ① 병합 보류(`mergeReason !== 'auto'`) · ② implement aborted — `orchestrator.ts` `runSelfImplement`
6
+ * ③ Pod exit 비0 — `harness-cli-command.ts` `onPod`
7
+ * ④ 발사 직후 자식 비0 종료(launch-failed) — `tasks-cli.ts` `defaultTaskLauncher`
8
+ *
9
+ * ⛔ 보존 계약: 기존 PR 본문·런 원장 항목·logs.db 사건(exit-classified 등)은 손대지 않고 «더하기»만 한다.
10
+ * ⭐ 이 스키마(`RunStopRecord`·`RUN_STOP_CLASSES`)는 STOP-AUTOHEAL 이 읽는다 — 칸 이름·닫힌 목록을 바꾸지 않는다(더하기만).
11
+ */
12
+ import { debug } from '../debug/log.js';
13
+ import { appendRunLedgerEntry, type RunLedgerEntry, type RunLedgerWriter, type RunShardIdentity } from './run-ledger.js';
14
+
15
+ /** 닫힌 목록 — 못 고르면 `unclassified`. */
16
+ export const RUN_STOP_CLASSES = [
17
+ 'env-unrelated', 'main-sync', 'pod-died', 'review-repeat',
18
+ 'review-out-of-scope', 'evidence-uncovered', 'fabric', 'launch-failed', 'unclassified',
19
+ ] as const;
20
+ export type RunStopClass = typeof RUN_STOP_CLASSES[number];
21
+
22
+ /** 런 원장 `event='stop'` 줄의 `data` 모양(STOP-AUTOHEAL 이 소비한다). */
23
+ export interface RunStopRecord {
24
+ class: RunStopClass;
25
+ /** 한 줄 · ≤120자 · 결정 라벨(원 로그 꼬리 금지). */
26
+ cause: string;
27
+ /** 근거 위치 — PR URL · `run-ledger:<runId>:<event>` · 파일 경로. */
28
+ evidenceRef: string;
29
+ /** 다음 한 수(사람·힐러가 읽는다). */
30
+ nextMove: string;
31
+ }
32
+
33
+ export const RUN_STOP_EVENT = 'stop';
34
+ export const RUN_STOP_CAUSE_MAX = 120;
35
+
36
+ export interface RunStopInput {
37
+ runId: string;
38
+ goalId?: string;
39
+ shardIdentity?: RunShardIdentity;
40
+ /** 닫힌 목록 밖이거나 비면 `unclassified`. */
41
+ class?: string;
42
+ cause: string;
43
+ evidenceRef: string;
44
+ nextMove: string;
45
+ /** 관측용 — 어느 자리가 불렀나(merge-hold · implement-aborted · draft-pr-open-failed · pod-exit · launch-failed). */
46
+ site?: string;
47
+ }
48
+
49
+ export function isRunStopClass(value: unknown): value is RunStopClass {
50
+ return typeof value === 'string' && (RUN_STOP_CLASSES as readonly string[]).includes(value);
51
+ }
52
+
53
+ /** 원 로그 꼬리가 아니라 «한 줄»로 — 첫 비어 있지 않은 줄 · ANSI 제거 · 공백 접기 · ≤120자(코드 포인트). */
54
+ export function boundRunStopCause(raw: string): string {
55
+ // eslint-disable-next-line no-control-regex
56
+ const plain = raw.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, '').replace(/[\x00-\x08\x0b-\x1f\x7f]/g, (c) => (c === '\r' ? '\n' : ' '));
57
+ const first = plain.split('\n').map((line) => line.replace(/\s+/g, ' ').trim()).find((line) => line.length > 0) ?? '';
58
+ return Array.from(first).slice(0, RUN_STOP_CAUSE_MAX).join('') || 'stop reason unclassified';
59
+ }
60
+
61
+ /** 병합 보류 사유 → 닫힌 목록. 모르면 `unclassified`. */
62
+ export function classifyMergeHoldStop(mergeReason: string | undefined): RunStopClass {
63
+ const reason = mergeReason ?? '';
64
+ if (reason.startsWith('main-sync') || reason === 'default-branch-unresolved') return 'main-sync';
65
+ if (reason === 'required-evidence-uncovered') return 'evidence-uncovered';
66
+ if (reason === 'review-must-fix' || reason.startsWith('review-budget')) return 'review-repeat';
67
+ if (reason.startsWith('review-diff') || reason === 'no-real-review') return 'review-out-of-scope';
68
+ return 'unclassified';
69
+ }
70
+
71
+ /** Pod exit 분류 사유 → 닫힌 목록. */
72
+ export function classifyPodExitStop(reason: string | undefined): RunStopClass {
73
+ switch (reason) {
74
+ case 'pod-failure': case 'pod-error': case 'OOMKilled': case 'ENOBUFS': case 'signal': return 'pod-died';
75
+ case 'no-launch': return 'launch-failed';
76
+ default: return 'unclassified';
77
+ }
78
+ }
79
+
80
+ /** 이 프로세스에서 stop 을 이미 쓴 런 — 한 런이 두 자리(예: draft PR 실패 ⊕ aborted)에서 두 줄을 쓰지 않게. */
81
+ const recordedThisProcess = new Set<string>();
82
+ const RECORDED_MAX = 4096;
83
+
84
+ /** 이 프로세스가 그 런에 stop 을 이미 썼나(원장 재독 없이 · 같은 프로세스 안 중복 방지용). */
85
+ export function runStopRecorded(runId: string): boolean {
86
+ return recordedThisProcess.has(runId);
87
+ }
88
+
89
+ /** 런 시작 때 부른다 — 같은 runId 로 다시 도는 런(재시도·시험)이 지난 판의 표시에 막히지 않게. */
90
+ export function forgetRunStop(runId: string): void {
91
+ recordedThisProcess.delete(runId);
92
+ }
93
+
94
+ /** 멈춘 런에 stop 한 줄을 더한다 — 기존 관측은 바꾸지 않는다. 쓴 줄을 돌려준다. */
95
+ export function recordRunStop(input: RunStopInput, write: RunLedgerWriter = appendRunLedgerEntry): RunLedgerEntry {
96
+ const record: RunStopRecord = {
97
+ class: isRunStopClass(input.class) ? input.class : 'unclassified',
98
+ cause: boundRunStopCause(input.cause),
99
+ evidenceRef: input.evidenceRef,
100
+ nextMove: input.nextMove,
101
+ };
102
+ const entry: RunLedgerEntry = {
103
+ timestamp: new Date().toISOString(), runId: input.runId, event: RUN_STOP_EVENT,
104
+ data: { ...record },
105
+ ...(input.goalId ? { goalId: input.goalId } : {}),
106
+ ...input.shardIdentity,
107
+ };
108
+ write(entry);
109
+ if (recordedThisProcess.size >= RECORDED_MAX) recordedThisProcess.clear();
110
+ recordedThisProcess.add(input.runId);
111
+ try {
112
+ debug.log('self-implement.run-stop', 'recorded', { runId: input.runId, site: input.site ?? null, ...record, requestedClass: input.class ?? null });
113
+ } catch { /* observation is fail-soft */ }
114
+ return entry;
115
+ }
116
+
117
+ /** 원장 항목이 stop 줄이면 그 레코드를 돌려준다(STOP-AUTOHEAL 소비용). */
118
+ export function readRunStopRecord(entry: RunLedgerEntry): RunStopRecord | undefined {
119
+ if (entry.event !== RUN_STOP_EVENT) return undefined;
120
+ const data = entry.data as Partial<RunStopRecord>;
121
+ if (typeof data.cause !== 'string' || typeof data.evidenceRef !== 'string' || typeof data.nextMove !== 'string') return undefined;
122
+ return { class: isRunStopClass(data.class) ? data.class : 'unclassified', cause: data.cause, evidenceRef: data.evidenceRef, nextMove: data.nextMove };
123
+ }