elanous 0.2.14 → 0.2.15

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 (293) 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/LZcstaO9_NXypPFmujolv/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{1307-6e7e0377c1db0bdc.js → 1307-33ffcc49ab45123b.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-74383f890f075511.js → page-0e2c57fe6add4b2a.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-7b2b4f378354869a.js → page-e05a77df7e005b04.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/bots/{page-1512d91c7491ed62.js → page-b8c49f6b4fa2df91.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/chat/{page-a627db94ec6904a0.js → page-976813c91d7394ea.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/consult/{page-fb260b9283dbf646.js → page-28b9448091776c06.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/app/design-check/page-72d3e7ff35a8c954.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/editor/{page-fed69f9759d248d0.js → page-60c7a8e1a4fe62ce.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/exec/{page-15374909d8f1aebd.js → page-2a52b316cb143d9c.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/field/{page-012a742f81218b01.js → page-c3ce483c8d5db370.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/intake/{page-99effd9d893a43dc.js → page-96f074f6538e8a2c.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/{layout-938de57ae0315e46.js → layout-e97be5334d83a9f5.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/market/{page-629e1ede482ec6eb.js → page-955ec2da3be4c560.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-27e99b4e54a849d4.js → page-c1343f9499d86f3f.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/not-found-8739a2e0ae793130.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-32cde713be47cddb.js → page-d23b12bb92242558.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c7fea475488201a8.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-04250f0e2c0d03df.js → page-e2ee2896811e98d0.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-e64caff084f7ce21.js → page-5efbf81124c474ae.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-db07eaffba0df7e3.js → page-85f1a1fc0a82738c.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-8ac8d71c2adf9c8a.js → page-da6c08aae0f92768.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-06ee266953d9c879.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/settings/{page-86f91034bd5cabe5.js → page-153c2b3bcc89e8de.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-94dc383d3d54db1a.js → page-87a11e6a05f15199.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/setup/{page-b3759cc14da80908.js → page-7d4182964407c09c.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/share/{page-ce4d8a1215ce53a3.js → page-0d6d23b40263a7ea.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/term/{page-274703bc41b71333.js → page-f5597e894c02cd33.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/trace/{page-35bdeae2e2c17147.js → page-73735b64bfe1ae67.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/vault/{page-3b7e821dc492bdfd.js → page-8fcec4c9ea2e7f70.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f7e631e9b4daf66e.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-2f810b18d89ecba6.js → page-9817c7f9007466cc.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-134c1bc762aee62c.js → page-97f9f43172a3a90e.js} +1 -1
  53. package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +1 -0
  54. package/apps/pwa/out/approvals/index.html +1 -1
  55. package/apps/pwa/out/approvals/index.txt +14 -14
  56. package/apps/pwa/out/autopilot/index.html +1 -1
  57. package/apps/pwa/out/autopilot/index.txt +14 -14
  58. package/apps/pwa/out/board/index.html +1 -1
  59. package/apps/pwa/out/board/index.txt +14 -14
  60. package/apps/pwa/out/botlab/index.html +1 -1
  61. package/apps/pwa/out/botlab/index.txt +14 -14
  62. package/apps/pwa/out/bots/index.html +1 -1
  63. package/apps/pwa/out/bots/index.txt +14 -14
  64. package/apps/pwa/out/ceo/index.html +1 -0
  65. package/apps/pwa/out/ceo/index.txt +33 -0
  66. package/apps/pwa/out/chat/index.html +1 -1
  67. package/apps/pwa/out/chat/index.txt +14 -14
  68. package/apps/pwa/out/consult/index.html +1 -1
  69. package/apps/pwa/out/consult/index.txt +14 -14
  70. package/apps/pwa/out/control/index.html +1 -1
  71. package/apps/pwa/out/control/index.txt +14 -14
  72. package/apps/pwa/out/design-check/index.html +1 -1
  73. package/apps/pwa/out/design-check/index.txt +14 -14
  74. package/apps/pwa/out/editor/index.html +1 -1
  75. package/apps/pwa/out/editor/index.txt +14 -14
  76. package/apps/pwa/out/exec/index.html +1 -1
  77. package/apps/pwa/out/exec/index.txt +14 -14
  78. package/apps/pwa/out/field/index.html +1 -1
  79. package/apps/pwa/out/field/index.txt +14 -14
  80. package/apps/pwa/out/healthz +1 -1
  81. package/apps/pwa/out/index.html +1 -1
  82. package/apps/pwa/out/index.txt +14 -14
  83. package/apps/pwa/out/inside/index.html +1 -1
  84. package/apps/pwa/out/inside/index.txt +14 -14
  85. package/apps/pwa/out/intake/index.html +1 -1
  86. package/apps/pwa/out/intake/index.txt +14 -14
  87. package/apps/pwa/out/live/index.html +1 -1
  88. package/apps/pwa/out/live/index.txt +14 -14
  89. package/apps/pwa/out/loops/index.html +1 -1
  90. package/apps/pwa/out/loops/index.txt +14 -14
  91. package/apps/pwa/out/market/index.html +1 -1
  92. package/apps/pwa/out/market/index.txt +14 -14
  93. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  94. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  95. package/apps/pwa/out/morning/index.html +1 -1
  96. package/apps/pwa/out/morning/index.txt +14 -14
  97. package/apps/pwa/out/observatory/index.html +1 -1
  98. package/apps/pwa/out/observatory/index.txt +14 -14
  99. package/apps/pwa/out/ops/checklist/index.html +1 -1
  100. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  101. package/apps/pwa/out/ops/release/index.html +1 -1
  102. package/apps/pwa/out/ops/release/index.txt +14 -14
  103. package/apps/pwa/out/ops/seats/index.html +1 -1
  104. package/apps/pwa/out/ops/seats/index.txt +14 -14
  105. package/apps/pwa/out/outputs/index.html +1 -1
  106. package/apps/pwa/out/outputs/index.txt +14 -14
  107. package/apps/pwa/out/reflection/index.html +1 -1
  108. package/apps/pwa/out/reflection/index.txt +15 -15
  109. package/apps/pwa/out/scheduler/index.html +1 -1
  110. package/apps/pwa/out/scheduler/index.txt +14 -14
  111. package/apps/pwa/out/sessions/index.html +1 -1
  112. package/apps/pwa/out/sessions/index.txt +14 -14
  113. package/apps/pwa/out/settings/devices/index.html +1 -1
  114. package/apps/pwa/out/settings/devices/index.txt +14 -14
  115. package/apps/pwa/out/settings/index.html +1 -1
  116. package/apps/pwa/out/settings/index.txt +14 -14
  117. package/apps/pwa/out/setup/done/index.html +1 -1
  118. package/apps/pwa/out/setup/done/index.txt +14 -14
  119. package/apps/pwa/out/setup/index.html +1 -1
  120. package/apps/pwa/out/setup/index.txt +14 -14
  121. package/apps/pwa/out/share/index.html +1 -1
  122. package/apps/pwa/out/share/index.txt +14 -14
  123. package/apps/pwa/out/showroom/index.html +1 -1
  124. package/apps/pwa/out/showroom/index.txt +14 -14
  125. package/apps/pwa/out/tasks/index.html +1 -1
  126. package/apps/pwa/out/tasks/index.txt +14 -14
  127. package/apps/pwa/out/term/index.html +1 -1
  128. package/apps/pwa/out/term/index.txt +14 -14
  129. package/apps/pwa/out/today/index.html +1 -1
  130. package/apps/pwa/out/today/index.txt +14 -14
  131. package/apps/pwa/out/trace/index.html +1 -1
  132. package/apps/pwa/out/trace/index.txt +14 -14
  133. package/apps/pwa/out/vault/index.html +1 -1
  134. package/apps/pwa/out/vault/index.txt +14 -14
  135. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  136. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  137. package/apps/pwa/out/workflows/index.html +1 -1
  138. package/apps/pwa/out/workflows/index.txt +14 -14
  139. package/apps/pwa/out/workspace/index.html +1 -1
  140. package/apps/pwa/out/workspace/index.txt +14 -14
  141. package/apps/pwa/out/worktrees/index.html +1 -1
  142. package/apps/pwa/out/worktrees/index.txt +14 -14
  143. package/graphs/heal/heal-loop.yaml +1 -1
  144. package/graphs/implement-loop.yaml +1 -1
  145. package/graphs/release/recipes.yaml +3 -0
  146. package/graphs/release/release-loop.yaml +4 -2
  147. package/package.json +1 -1
  148. package/scripts/brand/check.ts +7 -5
  149. package/scripts/ci-typecheck-changed.ts +50 -18
  150. package/scripts/docs/generate-graph-anatomy.ts +315 -0
  151. package/scripts/expose-rubric-suggest.ts +18 -12
  152. package/scripts/lecture-note/node.ts +31 -11
  153. package/scripts/lessons/handbook-page.ts +30 -3
  154. package/scripts/marketing/generate-ax-brief.ts +140 -0
  155. package/scripts/marketing/generate-feature-map.ts +118 -0
  156. package/scripts/release-loop/checklist-gate-node.ts +53 -14
  157. package/scripts/release-loop/gate-node.ts +80 -14
  158. package/scripts/release-loop/vault-note-node.ts +240 -8
  159. package/scripts/release-story/draft.ts +57 -9
  160. package/scripts/rhythm/daily.ts +263 -76
  161. package/scripts/rhythm/weekly.ts +30 -9
  162. package/scripts/smart-merge/resync-shadow.ts +147 -0
  163. package/src/agent-env/profile.ts +161 -0
  164. package/src/agent-substrate/pr-reviewer.ts +2 -0
  165. package/src/ask-user-question/pending-questions.ts +5 -0
  166. package/src/auth/github-app-token.ts +112 -13
  167. package/src/autopilot/build/llm-conflict-merge.ts +32 -6
  168. package/src/briefing/brief-items.ts +121 -0
  169. package/src/claims/claims-render.ts +14 -5
  170. package/src/cli/agent-env-cli.ts +21 -0
  171. package/src/cli/decisions-cli.ts +9 -0
  172. package/src/cli/doctor-cli.ts +23 -1
  173. package/src/cli/doctor-node-modules.ts +147 -0
  174. package/src/cli/intake-cli.ts +46 -17
  175. package/src/cli/pod-cli.ts +2 -0
  176. package/src/cli/release-cli.ts +61 -27
  177. package/src/cli/seat-requests-cli.ts +52 -0
  178. package/src/context-bus/context-now-public.ts +16 -6
  179. package/src/context-bus/context-now.ts +3 -1
  180. package/src/context-bus/coord-events.ts +6 -4
  181. package/src/decisions/decision-cards.ts +33 -7
  182. package/src/decisions/proact-meter.ts +286 -0
  183. package/src/decisions/proact1-lite.ts +86 -0
  184. package/src/decisions/web-push-decision-cards.ts +1 -1
  185. package/src/domains/alert-audit.ts +271 -0
  186. package/src/domains/core-tools.ts +4 -3
  187. package/src/domains/ops-facts-tool.ts +31 -1
  188. package/src/harness/harness-cli-command.ts +111 -15
  189. package/src/harness/harness-incidents.ts +2 -0
  190. package/src/harness/harness-pod-dispatch.ts +100 -11
  191. package/src/harness/harness-queue.ts +380 -50
  192. package/src/harness/helper-repair.ts +87 -0
  193. package/src/harness/helper-scan.ts +16 -4
  194. package/src/harness/launch-stamp.ts +236 -0
  195. package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
  196. package/src/harness/ledger-write-guard.ts +35 -0
  197. package/src/harness/pod-memory-policy.ts +15 -0
  198. package/src/harness/repository-name.ts +55 -0
  199. package/src/hq/fence-audit.ts +90 -0
  200. package/src/hq/hq.ts +62 -4
  201. package/src/hq/lease.ts +4 -4
  202. package/src/hq/move-plan.ts +126 -0
  203. package/src/hq/promote.ts +31 -0
  204. package/src/index.ts +77 -7
  205. package/src/intake-plane/check.ts +63 -0
  206. package/src/intake-plane/digest.ts +37 -15
  207. package/src/intake-plane/front-classifier.ts +6 -0
  208. package/src/intake-plane/items.ts +3 -2
  209. package/src/intake-plane/lens.ts +177 -0
  210. package/src/intake-plane/route.ts +145 -1
  211. package/src/launch-head/seat-goal-queue-lock.fixture.ts +15 -0
  212. package/src/launch-head/seat-goal-queue.ts +198 -0
  213. package/src/loops/budget.ts +36 -5
  214. package/src/loops/checker.ts +2 -2
  215. package/src/loops/orchestrator/author-depth.ts +65 -9
  216. package/src/loops/orchestrator/author-input-check.ts +8 -3
  217. package/src/loops/orchestrator/author-ledger.ts +3 -1
  218. package/src/loops/orchestrator/seat-share.ts +56 -0
  219. package/src/loops/orchestrator/tick.ts +11 -4
  220. package/src/loops/orchestrator/traffic.ts +117 -11
  221. package/src/loops/registry.ts +142 -61
  222. package/src/maturity/feature-maturity.ts +2 -1
  223. package/src/maturity/tui-slash-availability.ts +31 -0
  224. package/src/mss/logging/log-store.ts +35 -4
  225. package/src/nexus/api/http-server.ts +28 -14
  226. package/src/nexus/api/loop-edges.ts +173 -0
  227. package/src/nexus/api/schedules-read.ts +13 -0
  228. package/src/nexus/api/seat-requests.ts +79 -56
  229. package/src/prompt-enhance/enhance.ts +6 -1
  230. package/src/release-loop/auto-start.ts +6 -3
  231. package/src/release-loop/checklist.ts +13 -4
  232. package/src/release-loop/feature-store.ts +58 -31
  233. package/src/release-loop/merged-pr-checklist.ts +125 -0
  234. package/src/release-loop/placement.ts +86 -26
  235. package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
  236. package/src/seat-dispatch/seat-request-ledger.ts +133 -0
  237. package/src/seat-loop/seat-loop.ts +126 -10
  238. package/src/self-dev/dev-pipeline.ts +8 -4
  239. package/src/self-dev/draft-sweep.ts +90 -10
  240. package/src/self-dev/draft-triage-rules.ts +25 -8
  241. package/src/self-dev/orchestrate.ts +15 -1
  242. package/src/self-dev/review-provider-fallback.ts +6 -1
  243. package/src/self-dev/run-supervisor.ts +49 -2
  244. package/src/self-implement/frozen-merges.ts +3 -2
  245. package/src/self-implement/goal-author.ts +43 -5
  246. package/src/self-implement/goal-clarification-escalation.ts +5 -0
  247. package/src/self-implement/heal-intake.ts +120 -2
  248. package/src/self-implement/host-regate.ts +9 -2
  249. package/src/self-implement/l8-merge-queue.ts +132 -0
  250. package/src/self-implement/orchestrator.ts +187 -40
  251. package/src/self-implement/pipeline-shape.ts +1 -1
  252. package/src/self-implement/revert-guard.ts +42 -7
  253. package/src/self-implement/run-ledger.ts +2 -0
  254. package/src/self-implement/run-status-mapping.ts +2 -0
  255. package/src/self-implement/seams.ts +1 -1
  256. package/src/self-implement/self-implement-cli.ts +1 -0
  257. package/src/self-implement/speed-claims.ts +338 -0
  258. package/src/steward/triage.ts +74 -3
  259. package/src/steward/working-backwards.ts +4 -1
  260. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
  261. package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
  262. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
  263. package/src/task-orchestrator/surfaces/pod-pool.ts +60 -8
  264. package/src/task-orchestrator/surfaces/self-implement-pod.ts +147 -36
  265. package/src/task-orchestrator/surfaces/self-implement.ts +5 -1
  266. package/src/telegram.ts +25 -4
  267. package/src/user-config.ts +29 -5
  268. package/src/version/packed-revision.json +1 -1
  269. package/src/workflow-runtime/nodes/bash.ts +9 -1
  270. package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +0 -1
  271. package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +0 -1
  272. package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +0 -1
  273. package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +0 -1
  274. package/apps/pwa/out/_next/static/chunks/app/control/page-e6b84ab07d3567d5.js +0 -1
  275. package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +0 -1
  276. package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +0 -1
  277. package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +0 -1
  278. package/apps/pwa/out/_next/static/chunks/app/loops/page-160089123a40dd91.js +0 -1
  279. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2ce5471926761fdd.js +0 -1
  280. package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +0 -1
  281. package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +0 -1
  282. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e7e10cd3386222e5.js +0 -1
  283. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +0 -1
  284. package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +0 -1
  285. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +0 -1
  286. package/apps/pwa/out/_next/static/chunks/app/showroom/page-58043db78ba519ae.js +0 -1
  287. package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.js +0 -1
  288. package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +0 -1
  289. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +0 -1
  290. package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.js +0 -1
  291. package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +0 -1
  292. package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +0 -1
  293. /package/apps/pwa/out/_next/static/{x6mjI6vqOkVVu0jFuDkz1 → LZcstaO9_NXypPFmujolv}/_ssgManifest.js +0 -0
@@ -8,6 +8,20 @@ import { getDefaultLogStore } from '../mss/logging/log-store.js';
8
8
  type Command = (bin: string, args: readonly string[], cwd: string) => { status: number | null; stdout: string; stderr: string };
9
9
  export type L8MergeQueueInput = { number: number; cwd: string; matchHeadCommit: string };
10
10
  export type L8MergeQueueResult = { merged: boolean; baseRefName?: string; mergeCommit?: string; detail?: string };
11
+ /** Read-only integration report. Never a landing decision and never a remote write. */
12
+ export type L8ChangedTestResult = { file: string; passed: boolean; pass: number; fail: number; detail?: string };
13
+ export type L8IntegrationEvaluation = {
14
+ number: number;
15
+ head: string;
16
+ base?: string;
17
+ /** True only when the candidate integrates (next.md-only conflicts resolved) and every changed test passed. */
18
+ feasible: boolean;
19
+ conflictingFiles: string[];
20
+ /** True when there is no conflict, or every conflict path is exactly release/next.md. */
21
+ conflictsLimitedToNextMd: boolean;
22
+ changedTests: L8ChangedTestResult[];
23
+ detail?: string;
24
+ };
11
25
  export type L8MergeQueueDeps = {
12
26
  command?: Command;
13
27
  /** Must identify the same shared repository across all harness processes. */
@@ -195,6 +209,124 @@ export async function acquireL8MergeQueue(cwd: string): Promise<() => void> {
195
209
  }
196
210
  }
197
211
 
212
+ const TEST_PATH = /(?:^|\/)[^/]+\.(?:test|spec)\.(?:[cm]?[jt]sx?)$/;
213
+
214
+ function parseChangedTestCounts(output: string): { pass: number; fail: number; ran: number; files: number } | null {
215
+ const counts = /\b(\d+) pass\b[\s\S]*?\b(\d+) fail\b[\s\S]*?Ran ([1-9]\d*) tests? across ([1-9]\d*) files?/.exec(output);
216
+ if (!counts) return null;
217
+ return { pass: Number(counts[1]), fail: Number(counts[2]), ran: Number(counts[3]), files: Number(counts[4]) };
218
+ }
219
+
220
+ /**
221
+ * Report whether one pinned PR can integrate onto freshly fetched main.
222
+ * Resolves an append-only release/next.md conflict in a disposable worktree only.
223
+ * Never pushes, never calls a merge API, and never moves a remote ref.
224
+ */
225
+ export async function evaluateL8Integration(input: L8MergeQueueInput, deps: L8MergeQueueDeps = {}): Promise<L8IntegrationEvaluation> {
226
+ const report: L8IntegrationEvaluation = {
227
+ number: input.number,
228
+ head: input.matchHeadCommit,
229
+ feasible: false,
230
+ conflictingFiles: [],
231
+ conflictsLimitedToNextMd: false,
232
+ changedTests: [],
233
+ };
234
+ if (!Number.isSafeInteger(input.number) || input.number <= 0 || !/^[0-9a-f]{40}$/i.test(input.matchHeadCommit)) {
235
+ return { ...report, detail: 'invalid PR number or pinned head' };
236
+ }
237
+ const execute = deps.command ?? command;
238
+ const run = (bin: string, args: readonly string[], cwd: string): string => {
239
+ const result = execute(bin, args, cwd);
240
+ if (result.status !== 0) throw new Error(`${bin} ${args.join(' ')}: ${(result.stderr || result.stdout || `exit ${result.status}`).trim().slice(0, 500)}`);
241
+ return args.includes('-z') ? result.stdout : result.stdout.trim();
242
+ };
243
+ const refuseRemote = (bin: string, args: readonly string[]) => {
244
+ if (bin === 'git' && args.some((arg) => arg === 'push' || arg === 'update-ref')) throw new Error('evaluation must not push');
245
+ if (bin === 'gh' && args.some((arg) => arg === 'merge')) throw new Error('evaluation must not merge');
246
+ };
247
+ const guarded: Command = (bin, args, cwd) => {
248
+ refuseRemote(bin, args);
249
+ return execute(bin, args, cwd);
250
+ };
251
+ let temp: string | undefined;
252
+ let attached = false;
253
+ try {
254
+ type View = { headRefOid?: string; headRefName?: string; baseRefName?: string; state?: string; isDraft?: boolean; isCrossRepository?: boolean };
255
+ const view = JSON.parse(run('gh', ['pr', 'view', String(input.number), '--json', 'headRefOid,headRefName,baseRefName,state,isDraft,isCrossRepository'], input.cwd)) as View;
256
+ if (view.headRefOid !== input.matchHeadCommit || view.baseRefName !== 'main' || view.state !== 'OPEN' || view.isDraft !== false || view.isCrossRepository !== false) {
257
+ throw new Error('PR head, base, or ready/open state changed');
258
+ }
259
+ run('git', ['fetch', 'origin', 'refs/heads/main'], input.cwd);
260
+ const base = run('git', ['rev-parse', 'FETCH_HEAD'], input.cwd);
261
+ if (!/^[0-9a-f]{40}$/i.test(base)) throw new Error('main commit unavailable');
262
+ report.base = base;
263
+ run('git', ['fetch', 'origin', `refs/pull/${input.number}/head`], input.cwd);
264
+ if (run('git', ['rev-parse', 'FETCH_HEAD'], input.cwd) !== input.matchHeadCommit) throw new Error('fetched PR head changed');
265
+ temp = mkdtempSync(join(tmpdir(), 'elanous-l8-eval-'));
266
+ run('git', ['worktree', 'add', '--detach', temp, input.matchHeadCommit], input.cwd);
267
+ attached = true;
268
+ const fork = run('git', ['merge-base', base, input.matchHeadCommit], temp);
269
+ const paths = run('git', ['diff', '--name-only', '-z', fork, input.matchHeadCommit], temp).split('\0').filter(Boolean);
270
+ if (!paths.length) throw new Error('PR changed-file paths unavailable');
271
+ if (paths.some((path) => path.startsWith('-') || path.startsWith('/') || path.split('/').includes('..') || path.includes('\\'))) throw new Error('unsafe PR changed-file path');
272
+ const livePaths = run('git', ['diff', '--name-only', '-z', '--diff-filter=ACMRT', fork, input.matchHeadCommit], temp).split('\0').filter(Boolean);
273
+ const tests = livePaths.filter((path) => TEST_PATH.test(path));
274
+ const merge = guarded('git', ['-c', 'user.name=elanous merge queue', '-c', 'user.email=queue@localhost', 'merge', '--no-ff', '--no-edit', base], temp);
275
+ if (merge.status !== 0) {
276
+ const unresolved = run('git', ['diff', '--name-only', '--diff-filter=U'], temp).split('\n').filter(Boolean);
277
+ if (!unresolved.length) throw new Error(`git merge failed: ${(merge.stderr || `exit ${merge.status}`).trim()}`);
278
+ report.conflictingFiles = unresolved;
279
+ report.conflictsLimitedToNextMd = unresolved.length === 1 && unresolved[0] === NEXT_MD_PATH;
280
+ if (!report.conflictsLimitedToNextMd) {
281
+ report.detail = `merge conflicts: ${unresolved.join(', ')}`;
282
+ return report;
283
+ }
284
+ const stage = (n: number): string => {
285
+ const result = guarded('git', ['show', `:${n}:${NEXT_MD_PATH}`], temp!);
286
+ if (result.status !== 0) throw new Error(`release/next.md stage ${n} unavailable: ${result.stderr}`);
287
+ return result.stdout;
288
+ };
289
+ const resolved = resolveNextMdConflict(stage(1), stage(3), stage(2));
290
+ if (resolved === null) {
291
+ report.feasible = false;
292
+ report.detail = 'release/next.md conflict is not append-only';
293
+ return report;
294
+ }
295
+ writeFileSync(join(temp, NEXT_MD_PATH), resolved);
296
+ run('git', ['add', '--', NEXT_MD_PATH], temp);
297
+ run('git', ['-c', 'user.name=elanous merge queue', '-c', 'user.email=queue@localhost', 'commit', '-m', 'Integrate latest main for L8 evaluation'], temp);
298
+ } else {
299
+ report.conflictsLimitedToNextMd = true;
300
+ }
301
+ if (tests.length) {
302
+ const unavailable = tests.filter((path) => !existsSync(join(temp!, path)));
303
+ if (unavailable.length) throw new Error(`changed test paths unavailable: ${unavailable.join(', ')}`);
304
+ run('bun', ['install', '--frozen-lockfile'], temp);
305
+ for (const file of tests) {
306
+ const result = guarded('bun', ['test', file], temp);
307
+ const output = `${result.stdout}\n${result.stderr}`;
308
+ const counts = parseChangedTestCounts(output);
309
+ if (!counts || counts.pass + counts.fail !== counts.ran || counts.files !== 1 || (result.status !== 0 && counts.fail === 0)) {
310
+ throw new Error(`changed tests unmeasured: ${output.slice(-400)}`);
311
+ }
312
+ const passed = result.status === 0 && counts.fail === 0 && counts.pass > 0;
313
+ report.changedTests.push({ file, passed, pass: counts.pass, fail: counts.fail, ...(passed ? {} : { detail: output.slice(-400) }) });
314
+ }
315
+ if (report.changedTests.some((entry) => !entry.passed)) {
316
+ report.detail = 'changed tests failed';
317
+ return report;
318
+ }
319
+ }
320
+ report.feasible = true;
321
+ return report;
322
+ } catch (error) {
323
+ return { ...report, feasible: false, detail: String(error) };
324
+ } finally {
325
+ if (attached && temp) execute('git', ['worktree', 'remove', '--force', temp], input.cwd);
326
+ if (temp) rmSync(temp, { recursive: true, force: true });
327
+ }
328
+ }
329
+
198
330
  /** One PR per shared git directory, integrated on a freshly fetched main in a disposable worktree. */
199
331
  export async function runL8MergeQueue(input: L8MergeQueueInput, deps: L8MergeQueueDeps = {}): Promise<L8MergeQueueResult> {
200
332
  if (!Number.isSafeInteger(input.number) || input.number <= 0 || !/^[0-9a-f]{40}$/i.test(input.matchHeadCommit)) {
@@ -94,6 +94,7 @@ import type { DocumentReferenceStatus } from './self-implement-runtime.js';
94
94
  import { DEV_PIPELINE_SINK_SURFACE } from './self-cli-sink-surface.js';
95
95
  import { registerLoopAgentSafe, type LoopAgentInput } from '../domains/loop-agent-registry.js';
96
96
  import { originObservationFields } from '../agent/origin-observation.js';
97
+ import { resolveLaunchStamp, type LaunchStamp } from '../harness/launch-stamp.js';
97
98
  import { getHarnessSpace, normalizeSpaceId, resolveRunIdentity } from '../harness/harness-space.js';
98
99
  import { getElanousConfigDir } from '../elanous-config-dir.js';
99
100
  import { elanousStateRoot } from '../autopilot/state-paths.js';
@@ -183,7 +184,7 @@ import type { RunOutcome, ReworkBudgetVerdict } from './run-outcome.js';
183
184
  import type { EscalateTier } from './rework-policy.js';
184
185
  import type { ClassifyShadowCallLLM } from './classify-shadow.js';
185
186
  import { hasModuleLoadFailure, type GateTestFailure } from './gate-baseline.js';
186
- import { commitWorktree, defaultBranchRef } from './seams.js';
187
+ import { commitWorktree, defaultBranchRef, preservationHasChanges } from './seams.js';
187
188
  import { detectUncommittedWork } from './uncommitted-work.js';
188
189
  import { spawnSync } from 'node:child_process';
189
190
  import { blockedDraftDisposition } from './blocked-draft-policy.js';
@@ -1037,10 +1038,13 @@ export interface SelfImplementSeams {
1037
1038
  /** Read-only cited-path observer. Omission uses the deterministic target-worktree filesystem observation. */
1038
1039
  observeCitedPathFacts?: (mustFix: readonly string[], cwd: string, round: number) => MustFixCitedPathFact[] | Promise<MustFixCitedPathFact[]>;
1039
1040
  /** PR을 열기 전에 보존할 산출물이 있는지 판별한다. `base` 대비 branch 고유 변경과 미커밋 변경을 함께 본다.
1040
- * 생략 시 기존 주입 seam 호환을 위해 산출물이 있다고 본다. */
1041
+ * Pod에서 생략 시 실제 git 상태로 판정한다. */
1041
1042
  preservationHasChanges?: (ctx: { cwd: string; base?: string }) => boolean | Promise<boolean>;
1042
1043
  /** Test seam for blocked environment branch persistence; return whether the branch was pushed. */
1043
- preserveBlockedBranch?: (ctx: { cwd: string; branch: string; title: string }) => boolean | Promise<boolean>;
1044
+ /** Test seam — the worktree commit to pin before main sync (default `git rev-parse HEAD`). */
1045
+ headCommit?: (cwd: string) => string | undefined;
1046
+ /** `commit` pins the pushed state; without it the worktree HEAD is pushed. */
1047
+ preserveBlockedBranch?: (ctx: { cwd: string; branch: string; title: string; commit?: string }) => boolean | Promise<boolean>;
1044
1048
  /** Hard-cap salvage evidence: a clean worktree and commits ahead of origin/main. */
1045
1049
  readReworkSalvageEvidence?: (cwd: string) => Promise<ReworkSalvageEvidence>;
1046
1050
  /** Starts exactly one detached `elanous dev --file <goal> --base <branch>` follow-up. */
@@ -1995,6 +1999,8 @@ export interface SelfImplementOptions {
1995
1999
  graphOverlays?: readonly GraphOverlaySpec[];
1996
2000
  /** Validated, run-local graph variant injected at launch; rejected plans never start the pipeline. */
1997
2001
  graphVariantPlan?: import('./graph-variant.js').GraphVariantPlan;
2002
+ /** Door this run was launched through. Absent means the ledger stamp reads `unknown`. */
2003
+ launch?: LaunchStamp;
1998
2004
  /** ★ K run-identity — 이 self-implement 호출의 per-run join anchor. 미지정 시 상속(env)→canonical mint.
1999
2005
  * 리워크 라운드 전부가 이 값을 공유한다(`elanous self run <runId>` 가 호출 전체를 조인). */
2000
2006
  runId?: string;
@@ -2163,6 +2169,8 @@ export interface SelfImplementResult {
2163
2169
  runId: string;
2164
2170
  ok: boolean;
2165
2171
  stage: SelfImplementStage;
2172
+ /** Verbatim must-fix findings retained on a review-blocked terminal result. */
2173
+ mustFix?: readonly string[];
2166
2174
  /** Pipeline node that produced this terminal result. */
2167
2175
  node: PipelineNodeId;
2168
2176
  /** Structured terminal disposition for every normal run exit. */
@@ -2212,6 +2220,8 @@ export interface SelfImplementResult {
2212
2220
  quotaExhaustionAssessment?: QuotaExhaustionAssessment;
2213
2221
  /** Automatic merge was authorized before this run reached its terminal result. */
2214
2222
  mergeApprovalReceived?: true;
2223
+ /** Completed implementation survived a failed main sync on the pushed branch. */
2224
+ harvestable?: true;
2215
2225
  merged?: boolean;
2216
2226
  /** Observed PR base for a confirmed merge; never inferred from the requested launch base. */
2217
2227
  mergedBase?: string;
@@ -2619,7 +2629,9 @@ function prBody(feature: string, implSummary: string, gateLog?: string, review?:
2619
2629
  ...((gateLog ?? '').split('\n').filter((line) => line.startsWith('⚠️ 동작 검증 0:'))),
2620
2630
  ...exposeGateLines(gateLog),
2621
2631
  ...(gateLog ? ['```', gateLog.trim().slice(0, 3000), '```'] : []),
2622
- ...(revertGuard?.reverted.length ? [`- 되돌림 방지: ${revertGuard.reverted.length}파일 제외`] : []),
2632
+ ...(revertGuard?.reverted.length && revertGuard.reverted.length > (revertGuard.staleDeleted?.length ?? 0) ? [`- 되돌림 방지: ${revertGuard.reverted.length - (revertGuard.staleDeleted?.length ?? 0)}파일 제외`] : []),
2633
+ ...(revertGuard?.staleDeleted?.length ? [`- 되돌림 방지: 삭제 ${revertGuard.staleDeleted.length}파일 제외`] : []),
2634
+ ...(revertGuard?.outsideDeleted?.length ? [`- 삭제 경고: 대상 밖 ${revertGuard.outsideDeleted.length}파일 유지 (${revertGuard.outsideDeleted.join(', ')})`] : []),
2623
2635
  ...(revertGuard?.protected.length ? [`- 되돌림 경고: 대상 경로 ${revertGuard.protected.length}파일 유지 (${revertGuard.protected.join(', ')})`] : []),
2624
2636
  ...(revertGuard?.warning ? [`- 되돌림 검사 실패(커밋 계속): ${revertGuard.warning}`] : [])] : []),
2625
2637
  ...relaxation,
@@ -2968,6 +2980,7 @@ export function assembleBlockedDraftPrBody(
2968
2980
  JSON.stringify(classificationRecord),
2969
2981
  ...(state.reasonTruncated ? ['- reason truncated: true'] : []),
2970
2982
  ...(state.terminalStatus ? [`- child terminal status: reached=${state.terminalStatus.reached}; changed=${state.terminalStatus.changed}; toolCalls=${state.terminalStatus.toolCalls}; timedOut=${state.terminalStatus.timedOut}`] : []),
2983
+ ...(state.review?.mustFix.length ? ['## 마지막 리뷰 must-fix', ...state.review.mustFix.map((item) => `- ${item}`)] : []),
2971
2984
  ].join('\n');
2972
2985
  return preparePrBody(raw, persist, 'self-implement-blocked-draft-pr', essentialSuffix);
2973
2986
  }
@@ -3413,7 +3426,9 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
3413
3426
  const shardIdentity = parseRunShardIdentity(opts.feature);
3414
3427
  const observeOuter = makeRunObserver(runId, opts.goalId, debug.log.bind(debug), opts.seams.writeRunLedger, shardIdentity);
3415
3428
  try {
3416
- const origin = runOriginData();
3429
+ // 10-05 PODPROVIDER: record which child provider this run uses — «default» when the launch named none.
3430
+ const launch = opts.launch ?? resolveLaunchStamp();
3431
+ const origin = { ...runOriginData(), childProvider: opts.childLlm?.provider ?? 'default', ...(opts.childLlm?.model ? { childModel: opts.childLlm.model } : {}), launch };
3417
3432
  (opts.seams.writeRunLedger ?? appendRunLedgerEntry)({
3418
3433
  timestamp: new Date().toISOString(), runId, event: 'run-origin', data: origin,
3419
3434
  ...(opts.goalId ? { goalId: opts.goalId } : {}), ...shardIdentity,
@@ -3491,6 +3506,30 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
3491
3506
  let loopRegistered = false;
3492
3507
  let mergeApprovalReceived = false;
3493
3508
  let autoMergeAuthorizedBeforeSync = false;
3509
+ let finishedImplementationAtMainSync = false;
3510
+ // The finished implementation's commit, pinned before main sync starts. A timed-out mergeMain is not
3511
+ // cancellable and may keep moving HEAD in the same worktree, so the harvest pushes this SHA, never HEAD.
3512
+ let preSyncCommit: string | undefined;
3513
+ const preserveMergeHarvest = async (reason: string, inputChars?: number): Promise<boolean> => {
3514
+ if (!finishedImplementationAtMainSync || !worktreePath || !branch) return false;
3515
+ if (!preSyncCommit) {
3516
+ observeOuter('merge-harvest-push-failed', { branch, reason, error: 'pre-sync commit was not pinned' }, { level: 'warn' });
3517
+ return false;
3518
+ }
3519
+ try {
3520
+ const pushed = opts.seams.preserveBlockedBranch
3521
+ ? await opts.seams.preserveBlockedBranch({ cwd: worktreePath, branch, title: prTitle(opts.feature), commit: preSyncCommit })
3522
+ : runGitCommand(worktreePath, ['push', '-u', 'origin', `${preSyncCommit}:refs/heads/${branch}`]).status === 0;
3523
+ if (!pushed) throw new Error('branch push failed');
3524
+ // The worktree is left untouched: a late merge may still be writing it. The harvest source is the pinned commit.
3525
+ try { observeOuter('merge-abandoned-harvestable', { branch, reason, inputChars: inputChars ?? null, commit: preSyncCommit, worktree: 'left-as-is' }); }
3526
+ catch { /* observation cannot undo a confirmed push */ }
3527
+ return true;
3528
+ } catch (error) {
3529
+ observeOuter('merge-harvest-push-failed', { branch, reason, error: safeErrorDescription(error) }, { level: 'warn' });
3530
+ return false;
3531
+ }
3532
+ };
3494
3533
  let lastParsedSupervisorVerdict: ReworkBudgetVerdict | undefined;
3495
3534
  let lastParsedSupervisorReason: string | undefined;
3496
3535
  let providerErrorCount = 0;
@@ -3970,6 +4009,8 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
3970
4009
  () => credentialFailureCount > 0,
3971
4010
  hasCurrentProviderError,
3972
4011
  hasCurrentCredentialFailure,
4012
+ (commit: string | undefined) => { finishedImplementationAtMainSync = true; preSyncCommit = commit; },
4013
+ preserveMergeHarvest,
3973
4014
  );
3974
4015
  terminalNode = result.node;
3975
4016
  await mergeChildProviderErrors();
@@ -4042,9 +4083,11 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
4042
4083
  } catch (e) {
4043
4084
  if (e instanceof StepTimeoutError) {
4044
4085
  observeOuter('step-timeout', { step: e.step, ms: e.ms, branch, feature: opts.feature.slice(0, 80) }, { level: 'warn' });
4086
+ const harvestable = e.step === 'merge' && await preserveMergeHarvest('step-timeout');
4045
4087
  const result: SelfImplementResult = {
4046
4088
  runId,
4047
4089
  ok: false,
4090
+ ...(harvestable ? { harvestable: true } : {}),
4048
4091
  stage: 'timed-out',
4049
4092
  node: 'implement',
4050
4093
  ...resolveRunOutcome({ termination: 'abandoned' }),
@@ -4052,7 +4095,9 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
4052
4095
  ...(worktreePath ? { worktreePath } : {}),
4053
4096
  ...(lastParsedSupervisorReason ? { supervisorReason: lastParsedSupervisorReason } : {}),
4054
4097
  ...(mergeApprovalReceived || (e.step === 'merge' && autoMergeAuthorizedBeforeSync) ? { mergeApprovalReceived: true } : {}),
4055
- detail: worktreePath
4098
+ detail: harvestable
4099
+ ? `자율 단계 '${e.step}' wall-clock ${e.ms}ms 초과 — 구현 완료 · 수확 대기 (${branch}, worktree: ${worktreePath})`
4100
+ : worktreePath
4056
4101
  ? `자율 단계 '${e.step}' wall-clock ${e.ms}ms 초과 — hang 방지 종결(worktree 회수 경로: ${worktreePath})`
4057
4102
  : `자율 단계 '${e.step}' wall-clock ${e.ms}ms 초과 — hang 방지 종결(worktree 생성 전)`,
4058
4103
  };
@@ -4545,6 +4590,8 @@ async function runSelfImplementInner(
4545
4590
  hasObservedCredentialFailure: () => boolean = hasCredentialFailure,
4546
4591
  hasCurrentProviderError: () => boolean = hasProviderError,
4547
4592
  hasCurrentCredentialFailure: () => boolean = hasCredentialFailure,
4593
+ onMainSyncStarted: (commit: string | undefined) => void = () => {},
4594
+ preserveMergeHarvest: (reason: string, inputChars?: number) => Promise<boolean> = async () => false,
4548
4595
  ): Promise<Omit<SelfImplementResult, 'runId'>> {
4549
4596
  const s = opts.seams;
4550
4597
  const pipelineStartedAtMs = Date.now();
@@ -5338,9 +5385,11 @@ async function runSelfImplementInner(
5338
5385
  let finishedChildCommitted = false;
5339
5386
  const revertGuard: RevertGuardResult = { reverted: [], protected: [] };
5340
5387
  const inspectStaleReverts = (targets: readonly string[]): void => {
5341
- const inspected = dropStaleReverts(wt.path, targets, runId, getUserConfig().harness?.revertGuard?.depth);
5388
+ const inspected = dropStaleReverts(wt.path, targets, runId, getUserConfig().harness?.revertGuard?.depth, wt.resolvedBase);
5342
5389
  for (const file of inspected.reverted) if (!revertGuard.reverted.includes(file)) revertGuard.reverted.push(file);
5343
5390
  for (const file of inspected.protected) if (!revertGuard.protected.includes(file)) revertGuard.protected.push(file);
5391
+ for (const file of inspected.staleDeleted ?? []) if (!revertGuard.staleDeleted?.includes(file)) (revertGuard.staleDeleted ??= []).push(file);
5392
+ for (const file of inspected.outsideDeleted ?? []) if (!revertGuard.outsideDeleted?.includes(file)) (revertGuard.outsideDeleted ??= []).push(file);
5344
5393
  if (inspected.warning) revertGuard.warning = inspected.warning;
5345
5394
  };
5346
5395
  const classifyUnfinishedRun = (stage: SelfImplementStage, verdict?: ReworkBudgetVerdict, decomposition?: TerminalDecomposition): AbandonedClassificationResult => {
@@ -5363,6 +5412,14 @@ async function runSelfImplementInner(
5363
5412
  mustFixReported: (review?.mustFix.length ?? 0) > 0,
5364
5413
  });
5365
5414
  };
5415
+ const podReviewBlockedDraft = process.env.ELANOUS_SUBSTRATE === 'pod';
5416
+ let blockedDraftNoChanges = false;
5417
+ let blockedDraftPrFailure: string | undefined;
5418
+ const reviewBlockedResult = (pr: { url: string; number: number } | undefined) => ({
5419
+ stage: blockedDraftNoChanges && podReviewBlockedDraft ? 'no-changes' as const : podReviewBlockedDraft && !pr ? 'aborted' as const : 'review-blocked' as const,
5420
+ mustFix: [...(review?.mustFix ?? [])],
5421
+ ...(pr ? { prUrl: pr.url, prNumber: pr.number } : {}),
5422
+ });
5366
5423
  const preserveBlockedArtifacts = async ({ stage, reason, verdict, gate: preservedGate, typecheck, decomposition, childSummary, reasonTruncated, failureKind, terminalStatus, salvageStatusExpected }: { stage: 'review-blocked' | 'gate-failed' | 'aborted'; reason: string; verdict?: ReworkBudgetVerdict; gate?: SelfImplementGateResult; typecheck?: { passed: boolean; log?: string }; decomposition?: TerminalDecomposition; childSummary?: string; reasonTruncated?: boolean; failureKind?: ReturnType<typeof classifyReviewProviderFailure> | null; terminalStatus?: ImplementTerminalStatus; salvageStatusExpected: boolean }): Promise<{ url: string; number: number } | undefined> => {
5367
5424
  let childSummaryArtifact: ChildSummaryArtifactRef | undefined;
5368
5425
  if (childSummary !== undefined) {
@@ -5455,6 +5512,7 @@ async function runSelfImplementInner(
5455
5512
  ...(childSummaryArtifact ? { childSummaryArtifactPath: childSummaryArtifact.path } : {}),
5456
5513
  };
5457
5514
  if (s.preservationHasChanges && !await s.preservationHasChanges({ cwd: wt.path, base: preservationBase })) {
5515
+ if (stage === 'review-blocked' && podReviewBlockedDraft) blockedDraftNoChanges = true;
5458
5516
  observe('rework-blocked-draft-pr', { ...observation, skipped: 'no-changes' });
5459
5517
  progress(stage, '보존할 변경이 없어 draft PR을 열지 않음');
5460
5518
  return undefined;
@@ -5488,8 +5546,9 @@ async function runSelfImplementInner(
5488
5546
  s.persistPrBodyArtifact,
5489
5547
  );
5490
5548
  // no-changes wins; worktree-only remains local even for environment stops.
5491
- const environmentStop = blockedDraftDisposition(classificationRecord.classification) === 'preserve-branch';
5492
- const worktreeOnly = opts.completion === 'worktree-only';
5549
+ const environmentStop = blockedDraftDisposition(classificationRecord.classification) === 'preserve-branch'
5550
+ && !(stage === 'review-blocked' && podReviewBlockedDraft);
5551
+ const worktreeOnly = opts.completion === 'worktree-only' && !(stage === 'review-blocked' && podReviewBlockedDraft);
5493
5552
  if (worktreeOnly || environmentStop) {
5494
5553
  let artifactPath: string | undefined;
5495
5554
  let artifactError: string | undefined;
@@ -5559,7 +5618,54 @@ async function runSelfImplementInner(
5559
5618
  }
5560
5619
  return undefined;
5561
5620
  }
5621
+ if (stage === 'review-blocked' && podReviewBlockedDraft && !s.preservationHasChanges
5622
+ && !preservationHasChanges(wt.path, preservationBase)) {
5623
+ blockedDraftNoChanges = true;
5624
+ observe('rework-blocked-draft-pr', { ...observation, skipped: 'no-changes' });
5625
+ progress(stage, '보존할 변경이 없어 draft PR을 열지 않음');
5626
+ return undefined;
5627
+ }
5628
+ // Pod review-blocked: a failed commit or PR still pushes the branch, and the result detail says what survived.
5629
+ const preserveReviewBlockedBranch = async (cause: string, commitFirst: boolean, suffix = ''): Promise<undefined> => {
5630
+ let pushed = false;
5631
+ try {
5632
+ if (s.preserveBlockedBranch) {
5633
+ pushed = await s.preserveBlockedBranch({ cwd: wt.path, branch: wt.branch, title: prTitle(opts.feature) });
5634
+ } else {
5635
+ if (commitFirst) {
5636
+ const commit = commitWorktree(wt.path, prTitle(opts.feature));
5637
+ if (!commit.ok && !/nothing to commit/.test(commit.out)) throw new Error(`로컬 브랜치 커밋 실패: ${commit.out}`);
5638
+ }
5639
+ const push = runGitCommand(wt.path, ['push', '-u', 'origin', `HEAD:refs/heads/${wt.branch}`]);
5640
+ if (push.status !== 0) throw new Error(push.stderr || push.stdout || 'git push failed');
5641
+ pushed = true;
5642
+ }
5643
+ if (!pushed) throw new Error('브랜치 push가 성공하지 않았음');
5644
+ } catch (pushError) {
5645
+ const pushReason = safeErrorDescription(pushError);
5646
+ blockedDraftPrFailure = `${cause}; 원격 브랜치 보존 실패: ${pushReason}; worktree: ${wt.path}`;
5647
+ observe('rework-blocked-draft-pr', { ...observation, error: cause, pushed: false, pushError: pushReason, branch: wt.branch }, { level: 'warn' });
5648
+ progress('aborted', blockedDraftPrFailure);
5649
+ return undefined;
5650
+ }
5651
+ blockedDraftPrFailure = `${cause}; 원격 브랜치 보존: ${wt.branch}${suffix}`;
5652
+ observe('rework-blocked-draft-pr', { ...observation, error: cause, pushed: true, branch: wt.branch }, { level: 'warn' });
5653
+ progress('aborted', blockedDraftPrFailure);
5654
+ return undefined;
5655
+ };
5562
5656
  progress('pr-opening', '중단 산출물을 draft PR로 보존…');
5657
+ if (stage === 'review-blocked' && podReviewBlockedDraft) {
5658
+ let commitError: string | undefined;
5659
+ try {
5660
+ const commit = commitWorktree(wt.path, prTitle(opts.feature));
5661
+ if (!commit.ok && !/nothing to commit/.test(commit.out)) commitError = commit.out.trim() || 'git commit failed';
5662
+ } catch (error) {
5663
+ commitError = safeErrorDescription(error);
5664
+ }
5665
+ if (commitError !== undefined) {
5666
+ return preserveReviewBlockedBranch(`review-blocked 작업 커밋 실패: ${commitError}`, false, `; 미커밋 작업은 worktree 에 남음: ${wt.path}`);
5667
+ }
5668
+ }
5563
5669
  let pr: { url: string; number: number };
5564
5670
  try {
5565
5671
  pr = await withStepTimeout(s.openPr({
@@ -5568,6 +5674,7 @@ async function runSelfImplementInner(
5568
5674
  head: wt.branch,
5569
5675
  ...(opts.base ? { base: opts.base } : {}),
5570
5676
  draft: true,
5677
+ ...(stage === 'review-blocked' ? { labels: ['review-blocked'] } : {}),
5571
5678
  cwd: wt.path,
5572
5679
  }), T.pr, 'pr');
5573
5680
  } catch (error) {
@@ -5589,6 +5696,10 @@ async function runSelfImplementInner(
5589
5696
  ...(artifactPath ? { prBodyArtifactPath: artifactPath } : {}),
5590
5697
  ...(artifactError ? { prBodyArtifactError: artifactError } : {}),
5591
5698
  }, { level: 'warn' });
5699
+ if (stage === 'review-blocked' && podReviewBlockedDraft) {
5700
+ return preserveReviewBlockedBranch(`draft PR 생성 실패: ${prError}`, true,
5701
+ `; ${artifactPath ? `판정 본문: ${artifactPath}` : `판정 본문 보존 실패: ${artifactError ?? 'unknown error'}`}`);
5702
+ }
5592
5703
  progress('aborted', artifactPath
5593
5704
  ? `중단 산출물의 draft PR 생성 실패(worktree·branch 보존 · 판정 본문 보존: ${artifactPath})`
5594
5705
  : `중단 산출물의 draft PR 생성 실패(worktree·branch 보존 · 판정 본문 보존 실패: ${artifactError ?? 'unknown error'})`);
@@ -5601,6 +5712,9 @@ async function runSelfImplementInner(
5601
5712
  ...(assembled.truncated ? { prBodyTruncated: true, prBodyOriginalChars: assembled.originalChars } : {}),
5602
5713
  });
5603
5714
  await flushPrComments(pr);
5715
+ if (stage === 'review-blocked') {
5716
+ try { debug.log('self-implement.terminal', 'review-blocked-pr', { runId, prNumber: pr.number, round: Math.max(0, round - 1) }); } catch { /* observation is fail-soft */ }
5717
+ }
5604
5718
  progress('pr-opened', `중단 산출물 draft PR 개설 (#${pr.number}·사람 판단 대기)`);
5605
5719
  return pr;
5606
5720
  };
@@ -6113,7 +6227,7 @@ async function runSelfImplementInner(
6113
6227
  if (reflectRejectedAudit.length && review) review.shouldFix = [...review.shouldFix, ...reflectRejectedAudit];
6114
6228
  const pr = await preserveBlockedArtifacts({ stage: 'review-blocked', verdict: parsedDecision.verdict, reason: parsedDecision.reason, gate, decomposition: terminalDecomposition, salvageStatusExpected: false });
6115
6229
  const quotaExhaustionAssessment = quotaExhaustionAssessmentForRun();
6116
- return { ok: false, stage: 'review-blocked', node: 'rework', ...terminal, ...supervisorOutcome, sessionId, worktreePath: wt.path, branch: wt.branch, gate, review, quotaExhaustionAssessment, ...(lastSupervisorReason ? { supervisorReason: lastSupervisorReason } : {}), ...(pr ? { prUrl: pr.url, prNumber: pr.number } : {}), detail: `rework judged unconvergeable after ${round} rework round(s): ${parsedDecision.reason}` };
6230
+ return { ok: false, node: 'rework', ...terminal, ...supervisorOutcome, sessionId, worktreePath: wt.path, branch: wt.branch, gate, review, quotaExhaustionAssessment, ...(lastSupervisorReason ? { supervisorReason: lastSupervisorReason } : {}), ...reviewBlockedResult(pr), detail: `${blockedDraftPrFailure ? `${blockedDraftPrFailure}; ` : ''}rework judged unconvergeable after ${round} rework round(s): ${parsedDecision.reason}` };
6117
6231
  }
6118
6232
  const baselineStop = noRetryBaseline();
6119
6233
  const reason = baselineStop.terminationReason ?? parsedDecision.reason;
@@ -6141,7 +6255,7 @@ async function runSelfImplementInner(
6141
6255
  if (isReviewRework(reworkKind)) {
6142
6256
  if (reflectRejectedAudit.length && review) review.shouldFix = [...review.shouldFix, ...reflectRejectedAudit];
6143
6257
  const pr = await preserveBlockedArtifacts({ stage: 'review-blocked', verdict: lastReworkVerdict, reason, gate, salvageStatusExpected: false });
6144
- return { ok: false, stage: 'review-blocked', node: 'rework', ...terminal, sessionId, worktreePath: wt.path, branch: wt.branch, gate, review, quotaExhaustionAssessment: quotaExhaustionAssessmentForRun(), ...(lastSupervisorVerdict === 'CONTRACT-CONFLICT' ? { supervisorVerdict: lastSupervisorVerdict } : {}), ...(pr ? { prUrl: pr.url, prNumber: pr.number } : {}), detail: reason };
6258
+ return { ok: false, node: 'rework', ...terminal, sessionId, worktreePath: wt.path, branch: wt.branch, gate, review, quotaExhaustionAssessment: quotaExhaustionAssessmentForRun(), ...(lastSupervisorVerdict === 'CONTRACT-CONFLICT' ? { supervisorVerdict: lastSupervisorVerdict } : {}), ...reviewBlockedResult(pr), detail: blockedDraftPrFailure ?? reason };
6145
6259
  }
6146
6260
  const baselineStop = noRetryBaseline();
6147
6261
  const pr = await preserveBlockedArtifacts({ stage: 'gate-failed', verdict: lastReworkVerdict, reason: baselineStop.terminationReason ?? reason, gate, salvageStatusExpected: false });
@@ -6157,32 +6271,34 @@ async function runSelfImplementInner(
6157
6271
  if (isReviewRework(reworkKind)) {
6158
6272
  if (reflectRejectedAudit.length && review) review.shouldFix = [...review.shouldFix, ...reflectRejectedAudit];
6159
6273
  const reason = `review must-fix unresolved after ${round - 1} rework round(s)`;
6160
- const acceptance = await acceptReviewBudget(
6161
- review?.mustFix ?? [],
6162
- reworkParts.some((part) => part.source === 'supervisor'),
6163
- );
6164
- if (acceptance.decision.action === 'accept') {
6165
- return {
6166
- ok: true,
6167
- stage: 'pr-opened',
6168
- node: 'open-pr',
6169
- ...terminal,
6170
- sessionId,
6171
- worktreePath: wt.path,
6172
- branch: wt.branch,
6173
- gate,
6174
- review,
6175
- mergeReason: 'review-budget-follow-up-required',
6176
- followUpMustFix: acceptance.decision.unresolvedMustFix,
6177
- followUpMustFixCount: acceptance.decision.unresolvedMustFix.length,
6178
- ...(acceptance.pr ? { prUrl: acceptance.pr.url, prNumber: acceptance.pr.number } : {}),
6179
- detail: `${reason}; accepted with ${acceptance.decision.unresolvedMustFix.length} follow-up must-fix item(s)`,
6180
- };
6274
+ if (!podReviewBlockedDraft) {
6275
+ const acceptance = await acceptReviewBudget(
6276
+ review?.mustFix ?? [],
6277
+ reworkParts.some((part) => part.source === 'supervisor'),
6278
+ );
6279
+ if (acceptance.decision.action === 'accept') {
6280
+ return {
6281
+ ok: true,
6282
+ stage: 'pr-opened',
6283
+ node: 'open-pr',
6284
+ ...terminal,
6285
+ sessionId,
6286
+ worktreePath: wt.path,
6287
+ branch: wt.branch,
6288
+ gate,
6289
+ review,
6290
+ mergeReason: 'review-budget-follow-up-required',
6291
+ followUpMustFix: acceptance.decision.unresolvedMustFix,
6292
+ followUpMustFixCount: acceptance.decision.unresolvedMustFix.length,
6293
+ ...(acceptance.pr ? { prUrl: acceptance.pr.url, prNumber: acceptance.pr.number } : {}),
6294
+ detail: `${reason}; accepted with ${acceptance.decision.unresolvedMustFix.length} follow-up must-fix item(s)`,
6295
+ };
6296
+ }
6181
6297
  }
6182
6298
  const pr = await preserveBlockedArtifacts({ stage: 'review-blocked', reason, gate, salvageStatusExpected: true });
6183
- const salvage = await salvageHardCap(canSalvage, pr);
6299
+ const salvage = blockedDraftNoChanges || blockedDraftPrFailure ? 'parked' : await salvageHardCap(canSalvage, pr);
6184
6300
  const disposition = { ...terminal, ...(salvage ? { salvage } : {}) };
6185
- return { ok: false, stage: 'review-blocked', node: 'rework', ...disposition, sessionId, worktreePath: wt.path, branch: wt.branch, gate, review, quotaExhaustionAssessment: quotaExhaustionAssessmentForRun(), ...(lastSupervisorVerdict === 'CONTRACT-CONFLICT' ? { supervisorVerdict: lastSupervisorVerdict } : {}), ...(pr ? { prUrl: pr.url, prNumber: pr.number } : {}), detail: reason };
6301
+ return { ok: false, node: 'rework', ...disposition, sessionId, worktreePath: wt.path, branch: wt.branch, gate, review, quotaExhaustionAssessment: quotaExhaustionAssessmentForRun(), ...(lastSupervisorVerdict === 'CONTRACT-CONFLICT' ? { supervisorVerdict: lastSupervisorVerdict } : {}), ...reviewBlockedResult(pr), detail: blockedDraftPrFailure ?? reason };
6186
6302
  }
6187
6303
  const baselineStop = noRetryBaseline();
6188
6304
  const reason = baselineStop.terminationReason ?? `gate failed after ${round - 1} rework round(s)`;
@@ -7073,9 +7189,13 @@ async function runSelfImplementInner(
7073
7189
  `Child completion: ${impl.summary.trim() || '(summary unavailable)'}`,
7074
7190
  ]);
7075
7191
  if (review) {
7076
- bufferPrComment(round, 'reviewer', `Round ${round}: review completed (${review.verdict}).`, [
7077
- `Review summary: ${review.summary.trim() || '(summary unavailable)'}`,
7078
- ]);
7192
+ if (review.reviewed) {
7193
+ bufferPrComment(round, 'reviewer', `Round ${round}: review completed (${review.verdict}).`, [
7194
+ `Review summary: ${review.summary.trim() || '(summary unavailable)'}`,
7195
+ ]);
7196
+ } else {
7197
+ bufferPrComment(round, 'reviewer', `Round ${round}: 리뷰 못 함(사유: ${review.failureReason?.trim() || '사유 미상'}) — 통과 판정 아님`, []);
7198
+ }
7079
7199
  }
7080
7200
  finalizeSupervisorDeliveries('no-next-rework-round');
7081
7201
  break;
@@ -7193,7 +7313,23 @@ async function runSelfImplementInner(
7193
7313
  return { ok: false, stage: 'merge-conflict', node: 'main-sync', ...resolveRunOutcome({ termination: 'abandoned' }), ...decisionSignalResult, sessionId, worktreePath: wt.path, branch: wt.branch, gate, ...(review ? { review } : {}), detail: 'pre-PR main sync default-branch-unresolved' };
7194
7314
  }
7195
7315
  progress('gating', `최신 ${mergeTarget} 정합 (병렬 드리프트·LLM 충돌해결)…`);
7196
- const sync = await withStepTimeout(s.mergeMain(wt.path, mergeTarget), T.merge, 'merge');
7316
+ onMainSyncStarted((s.headCommit ?? ((cwd: string) => {
7317
+ const head = runGitCommand(cwd, ['rev-parse', '--verify', 'HEAD'], { encoding: 'utf8' });
7318
+ return head.status === 0 ? String(head.stdout).trim() || undefined : undefined;
7319
+ }))(wt.path));
7320
+ let sync: Awaited<ReturnType<NonNullable<SelfImplementSeams['mergeMain']>>>;
7321
+ try {
7322
+ sync = await withStepTimeout(s.mergeMain(wt.path, mergeTarget), T.merge, 'merge');
7323
+ } catch (error) {
7324
+ if (error instanceof StepTimeoutError) throw error;
7325
+ const harvestable = await preserveMergeHarvest('merge-interrupted');
7326
+ if (!harvestable) throw error;
7327
+ canAuto = false;
7328
+ mergeReason = 'main-sync-interrupted';
7329
+ recordMergeDecision();
7330
+ progress('merge-conflict', `⚠️ ${mergeTarget} 정합 중단 — 사람 수확 (${wt.branch})`);
7331
+ return { ok: false, stage: 'merge-conflict', node: 'main-sync', ...resolveRunOutcome({ termination: 'abandoned' }), ...decisionSignalResult, sessionId, worktreePath: wt.path, branch: wt.branch, gate, ...(review ? { review } : {}), harvestable: true, detail: `pre-PR main sync interrupted: ${safeErrorDescription(error)}` };
7332
+ }
7197
7333
  // ⛔⭐ `OBS-T96` 🅐 — 「무엇을 봤는지」를 남긴다. 수만 남기면 정합 실패가 «재현 불가»가 된다.
7198
7334
  observe('pre-pr-sync', mainSyncObservation(sync, mergeTarget, opts.base, wt.branch));
7199
7335
  if (sync.status === 'conflict-unresolved' || sync.status === 'error') {
@@ -7201,7 +7337,9 @@ async function runSelfImplementInner(
7201
7337
  canAuto = false;
7202
7338
  mergeReason = `main-sync-${sync.status}`;
7203
7339
  recordMergeDecision();
7204
- return { ok: false, stage: 'merge-conflict', node: 'main-sync', ...resolveRunOutcome({ termination: 'abandoned' }), ...decisionSignalResult, sessionId, worktreePath: wt.path, branch: wt.branch, gate, ...(review ? { review } : {}), detail: `pre-PR main sync ${sync.status}` };
7340
+ const harvestable = (sync.reason === 'conflict-input-too-large' || sync.reason === 'conflict-resolver-interrupted')
7341
+ && await preserveMergeHarvest(sync.reason, sync.inputChars);
7342
+ return { ok: false, stage: 'merge-conflict', node: 'main-sync', ...resolveRunOutcome({ termination: 'abandoned' }), ...decisionSignalResult, sessionId, worktreePath: wt.path, branch: wt.branch, gate, ...(review ? { review } : {}), ...(harvestable ? { harvestable: true } : {}), detail: harvestable ? sync.reason === 'conflict-input-too-large' ? `충돌 큼 — 사람 수확 (${wt.branch}, 입력 ${sync.inputChars}자)` : `충돌 해결 중단 — 사람 수확 (${wt.branch})` : `pre-PR main sync ${sync.status}` };
7205
7343
  }
7206
7344
  if (sync.status === 'llm-resolved' || sync.status === 'merged') {
7207
7345
  preservationBase = opts.base ?? mergeTarget;
@@ -7522,6 +7660,16 @@ async function runSelfImplementInner(
7522
7660
  const m = await withStepTimeout(merging, T.pr, 'pr');
7523
7661
  observe('merged', { number: pr.number, merged: m.merged, detail: m.detail ?? null });
7524
7662
  if (m.merged) {
7663
+ const mergedBase = m.baseRefName?.trim() || observedPrBase;
7664
+ if (mergedBase && displayBranchName(mergedBase) === 'main') {
7665
+ try {
7666
+ const { syncMergedPrChecklist } = await import('../release-loop/merged-pr-checklist.js');
7667
+ const goalFile = opts.goalFile ?? (opts.goalId ? s.goalDocumentPathByGoalId?.(opts.goalId) : undefined);
7668
+ syncMergedPrChecklist(pr.number, wt.path, goalFile);
7669
+ } catch (error) {
7670
+ observe('merged-pr-checklist-sync-failed', { number: pr.number, error: safeErrorDescription(error) }, { level: 'warn' });
7671
+ }
7672
+ }
7525
7673
  try {
7526
7674
  await s.supersedeDraftsOnMerge?.(pr.number, wt.path);
7527
7675
  } catch (error) {
@@ -7540,7 +7688,6 @@ async function runSelfImplementInner(
7540
7688
  observe('release-note-write-failed', { number: pr.number, prUrl: pr.url, directory, fragment, error: safeErrorDescription(error) }, { level: 'error' });
7541
7689
  throw new MergedReleaseNoteWriteError(pr.url, directory, fragment, error);
7542
7690
  }
7543
- const mergedBase = m.baseRefName?.trim() || observedPrBase;
7544
7691
  const defaultBranch = resolveDefaultBranchTarget(s.defaultBranchRef ?? defaultBranchRef, wt.path).target ?? undefined;
7545
7692
  progress('merged', formatAutoMergeSuccessMessage({ prNumber: pr.number, mergedBase, defaultBranch }));
7546
7693
  await runPostMergeCleanup(s.postMergeCleanup, wt.path, wt.branch, opts.goalFile, mergedBase && defaultBranch && displayBranchName(mergedBase) === displayBranchName(defaultBranch) ? defaultBranch : undefined, observe);
@@ -16,7 +16,7 @@ export const TERMINAL_STAGES_BY_NODE = {
16
16
  implement: ['aborted', 'timed-out', 'soft-stopped', 'parked'],
17
17
  gate: [],
18
18
  review: ['review-blocked'],
19
- rework: ['gate-failed', 'review-blocked'],
19
+ rework: ['gate-failed', 'review-blocked', 'no-changes'],
20
20
  'main-sync': ['merge-conflict'],
21
21
  regate: ['gate-failed'],
22
22
  'open-pr': ['pr-declined', 'pr-opened', 'worktree-completed'],