elanous 0.2.25 → 0.2.28

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 (337) 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/{1454-37fd16f479f5554a.js → 1454-29d1d773d5996a49.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{599-a914cc0468f56143.js → 599-56f2c0a5cc4a7295.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-24453f6a302816b4.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/app/board/page-6c131d30bb1cf871.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-b12149c51f7d04b1.js → page-fa46d4588e8442c2.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/bots/page-dc0772b11861af51.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-305cd294e754906e.js → page-256ffd365d32e100.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-dea4b795741126fd.js → page-e0de78ae7b99f01c.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/exec/{page-45889e7070d11126.js → page-4c741eb760f2f77c.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/field/{page-16a2e893f83ae4d0.js → page-e9ba98f5f1440a21.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/inside/page-dafc901bb1b72b5f.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/intake/{page-56888bc744e08dac.js → page-2f6f303fa114ec24.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/layout-1634081c5272006c.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/live/{page-952c6ceec6d9de38.js → page-ced0bcfc6542b3fc.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live-run/{page-6d0202af568f4c63.js → page-bb1f05cd51c38944.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/loops/page-646b50f63b6fe732.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-4fb6fc34e20c07b7.js → page-f736324afa2117c1.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-e9502921f4f01b1f.js → page-6f131b51193a36f9.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/{not-found-b6169ea2f09cf287.js → not-found-435f5e2faf02bc01.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/{page-6a14333d1b1b3967.js → page-497e0eedf20e7890.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-8c91a9a66c9ec90a.js → page-a8d28fdf995e16ea.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8966452228753c5e.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/{page-3d3fc73dd187ed52.js → page-66a67eb19ebd976b.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-9c04dac0831c2bbb.js → page-c71267e7681da982.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a21d1e1797172692.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-04de9ca6d52a3893.js → page-cb249e81ed339318.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/settings/{page-62527677bbc2fdc4.js → page-781cfc5114e097da.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-9a2e09c4a311764c.js → page-478157214db6a023.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/setup/{page-bfeb35d69e8b24bb.js → page-86141f12cf090b7f.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/share/{page-89cb33bced824e4a.js → page-7256c73bb1c36751.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/today/{page-6445ac4cbb8dd869.js → page-979d318a534e92da.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/page-21050928a697ed7a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-20162174b77f96cf.js → page-601fb77340e63c9a.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-05a4b2c9b91034aa.js → page-11bf9e5725a72c22.js} +1 -1
  53. package/apps/pwa/out/approvals/index.html +1 -1
  54. package/apps/pwa/out/approvals/index.txt +13 -13
  55. package/apps/pwa/out/autopilot/index.html +1 -1
  56. package/apps/pwa/out/autopilot/index.txt +13 -13
  57. package/apps/pwa/out/board/index.html +1 -1
  58. package/apps/pwa/out/board/index.txt +13 -13
  59. package/apps/pwa/out/botlab/index.html +1 -1
  60. package/apps/pwa/out/botlab/index.txt +13 -13
  61. package/apps/pwa/out/bots/index.html +1 -1
  62. package/apps/pwa/out/bots/index.txt +13 -13
  63. package/apps/pwa/out/ceo/index.html +1 -1
  64. package/apps/pwa/out/ceo/index.txt +13 -13
  65. package/apps/pwa/out/chat/index.html +1 -1
  66. package/apps/pwa/out/chat/index.txt +13 -13
  67. package/apps/pwa/out/consult/index.html +1 -1
  68. package/apps/pwa/out/consult/index.txt +13 -13
  69. package/apps/pwa/out/control/index.html +1 -1
  70. package/apps/pwa/out/control/index.txt +13 -13
  71. package/apps/pwa/out/decisions/index.html +1 -1
  72. package/apps/pwa/out/decisions/index.txt +13 -13
  73. package/apps/pwa/out/design-check/index.html +1 -1
  74. package/apps/pwa/out/design-check/index.txt +13 -13
  75. package/apps/pwa/out/editor/index.html +1 -1
  76. package/apps/pwa/out/editor/index.txt +13 -13
  77. package/apps/pwa/out/exec/index.html +1 -1
  78. package/apps/pwa/out/exec/index.txt +13 -13
  79. package/apps/pwa/out/field/index.html +1 -1
  80. package/apps/pwa/out/field/index.txt +13 -13
  81. package/apps/pwa/out/healthz +1 -1
  82. package/apps/pwa/out/index.html +1 -1
  83. package/apps/pwa/out/index.txt +13 -13
  84. package/apps/pwa/out/inside/index.html +1 -1
  85. package/apps/pwa/out/inside/index.txt +13 -13
  86. package/apps/pwa/out/intake/index.html +1 -1
  87. package/apps/pwa/out/intake/index.txt +13 -13
  88. package/apps/pwa/out/live/index.html +1 -1
  89. package/apps/pwa/out/live/index.txt +13 -13
  90. package/apps/pwa/out/live-run/index.html +1 -1
  91. package/apps/pwa/out/live-run/index.txt +13 -13
  92. package/apps/pwa/out/loops/index.html +1 -1
  93. package/apps/pwa/out/loops/index.txt +13 -13
  94. package/apps/pwa/out/market/index.html +1 -1
  95. package/apps/pwa/out/market/index.txt +13 -13
  96. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  97. package/apps/pwa/out/missions/placeholder/index.txt +13 -13
  98. package/apps/pwa/out/morning/index.html +1 -1
  99. package/apps/pwa/out/morning/index.txt +13 -13
  100. package/apps/pwa/out/observatory/index.html +1 -1
  101. package/apps/pwa/out/observatory/index.txt +13 -13
  102. package/apps/pwa/out/ops/checklist/index.html +1 -1
  103. package/apps/pwa/out/ops/checklist/index.txt +13 -13
  104. package/apps/pwa/out/ops/release/index.html +1 -1
  105. package/apps/pwa/out/ops/release/index.txt +13 -13
  106. package/apps/pwa/out/ops/seats/index.html +1 -1
  107. package/apps/pwa/out/ops/seats/index.txt +13 -13
  108. package/apps/pwa/out/outputs/index.html +1 -1
  109. package/apps/pwa/out/outputs/index.txt +13 -13
  110. package/apps/pwa/out/reflection/index.html +1 -1
  111. package/apps/pwa/out/reflection/index.txt +14 -14
  112. package/apps/pwa/out/scheduler/index.html +1 -1
  113. package/apps/pwa/out/scheduler/index.txt +13 -13
  114. package/apps/pwa/out/sessions/index.html +1 -1
  115. package/apps/pwa/out/sessions/index.txt +13 -13
  116. package/apps/pwa/out/settings/devices/index.html +1 -1
  117. package/apps/pwa/out/settings/devices/index.txt +13 -13
  118. package/apps/pwa/out/settings/index.html +1 -1
  119. package/apps/pwa/out/settings/index.txt +13 -13
  120. package/apps/pwa/out/setup/done/index.html +1 -1
  121. package/apps/pwa/out/setup/done/index.txt +13 -13
  122. package/apps/pwa/out/setup/index.html +1 -1
  123. package/apps/pwa/out/setup/index.txt +13 -13
  124. package/apps/pwa/out/share/index.html +1 -1
  125. package/apps/pwa/out/share/index.txt +13 -13
  126. package/apps/pwa/out/showroom/index.html +1 -1
  127. package/apps/pwa/out/showroom/index.txt +13 -13
  128. package/apps/pwa/out/tasks/index.html +1 -1
  129. package/apps/pwa/out/tasks/index.txt +13 -13
  130. package/apps/pwa/out/term/index.html +1 -1
  131. package/apps/pwa/out/term/index.txt +13 -13
  132. package/apps/pwa/out/today/index.html +1 -1
  133. package/apps/pwa/out/today/index.txt +13 -13
  134. package/apps/pwa/out/trace/index.html +1 -1
  135. package/apps/pwa/out/trace/index.txt +13 -13
  136. package/apps/pwa/out/vault/index.html +1 -1
  137. package/apps/pwa/out/vault/index.txt +13 -13
  138. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  139. package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
  140. package/apps/pwa/out/workflows/index.html +1 -1
  141. package/apps/pwa/out/workflows/index.txt +13 -13
  142. package/apps/pwa/out/workspace/index.html +1 -1
  143. package/apps/pwa/out/workspace/index.txt +13 -13
  144. package/apps/pwa/out/worktrees/index.html +1 -1
  145. package/apps/pwa/out/worktrees/index.txt +13 -13
  146. package/graphs/implement-loop.yaml +38 -1
  147. package/graphs/steward/recipes.yaml +6 -0
  148. package/graphs/steward/release-day.yaml +23 -0
  149. package/graphs/steward/steward.yaml +9 -1
  150. package/graphs/work/code-pr.yaml +23 -0
  151. package/package.json +1 -1
  152. package/scripts/browser-observe-cycle.ts +76 -2
  153. package/scripts/ci-typecheck-changed.ts +119 -7
  154. package/scripts/coord-msg-bridge.ts +209 -0
  155. package/scripts/coord-post.sh +40 -7
  156. package/scripts/coord-requests-bridge.ts +10 -5
  157. package/scripts/detect-test-interference.ts +31 -1
  158. package/scripts/expose-rubric-suggest.ts +1 -1
  159. package/scripts/measure-tsc-affected-roots.ts +10 -1
  160. package/scripts/release-loop/docs-node.ts +51 -22
  161. package/scripts/release-loop/export-check-node.ts +8 -5
  162. package/scripts/release-loop/gate-node.ts +37 -4
  163. package/scripts/release-loop/node-verdict.ts +10 -3
  164. package/scripts/release-loop/publish-node.ts +3 -0
  165. package/scripts/release-loop/tui-node-checks.ts +5 -2
  166. package/scripts/skills/public-check.ts +5 -2
  167. package/scripts/test-deterministic.ts +520 -37
  168. package/scripts/ux-sim.ts +7 -1
  169. package/scripts/verify-kpack6-live.ts +230 -0
  170. package/src/acp/codex-plugins.ts +17 -0
  171. package/src/agent/task-notification.ts +32 -2
  172. package/src/agent-mission/in-agent-plugin.ts +8 -3
  173. package/src/agent-substrate/review-observation.ts +9 -2
  174. package/src/agent-substrate/self-review-cli.ts +62 -16
  175. package/src/boot/daemon-tools/self-implement.ts +72 -4
  176. package/src/briefing/brief-items.ts +151 -35
  177. package/src/budget/llm-cost.ts +30 -3
  178. package/src/budget/usage-outcomes.ts +32 -0
  179. package/src/claims/claims-ledger.ts +23 -0
  180. package/src/claims/claims-render.ts +22 -2
  181. package/src/cli/brief-cli.ts +131 -10
  182. package/src/cli/claims-cli.ts +24 -4
  183. package/src/cli/control-cli.ts +29 -0
  184. package/src/cli/doctor-cli.ts +90 -8
  185. package/src/cli/msg-cli.ts +177 -11
  186. package/src/cli/plugin-cli.ts +11 -3
  187. package/src/cli/pr-cli.ts +133 -18
  188. package/src/cli/pr-land-overlap-evidence.ts +18 -10
  189. package/src/cli/pr-land-refusal.ts +20 -0
  190. package/src/cli/release-cli.ts +34 -5
  191. package/src/cli/rfc-cli.ts +24 -0
  192. package/src/cli/seat-cli.ts +121 -0
  193. package/src/cli/self-utterances-cli.ts +140 -0
  194. package/src/cli/tailscale-serve.ts +5 -8
  195. package/src/cli/tasks-cli.ts +528 -31
  196. package/src/cli/usage-cli.ts +9 -4
  197. package/src/context-card/cell.ts +40 -0
  198. package/src/dashboard/boot-timing.ts +35 -0
  199. package/src/dashboard/first-screen-band.ts +63 -13
  200. package/src/dashboard/index.ts +94 -27
  201. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
  202. package/src/dashboard/skill-detection-runtime.ts +24 -4
  203. package/src/dashboard/slash-runtime/dashboard-handlers.ts +6 -0
  204. package/src/debug/log.ts +1 -1
  205. package/src/domains/morning-report.ts +3 -1
  206. package/src/domains/release-tool.ts +29 -12
  207. package/src/domains/surface-events.ts +16 -0
  208. package/src/elanous-config-dir.ts +2 -2
  209. package/src/fleet/host-doctor.ts +73 -2
  210. package/src/github/pr-labels.ts +10 -4
  211. package/src/graph-runner/runner.ts +104 -15
  212. package/src/harness/harness-clean.ts +21 -3
  213. package/src/harness/harness-cli-command.ts +68 -10
  214. package/src/harness/harness-pod-dispatch.ts +11 -4
  215. package/src/harness/harness-stop.ts +11 -7
  216. package/src/hq/seats.ts +17 -3
  217. package/src/index.ts +69 -9
  218. package/src/instance/current.ts +2 -6
  219. package/src/instance/resolve.ts +5 -6
  220. package/src/intelligence-map/model-bench-scores.ts +102 -0
  221. package/src/llm/openai-sampling-fields.ts +89 -0
  222. package/src/llm/runtime-model-family.ts +61 -0
  223. package/src/llm/zai-coding-plan.ts +27 -1
  224. package/src/llm.ts +13 -89
  225. package/src/loops/orchestrator/inbox.ts +73 -0
  226. package/src/loops/orchestrator/resync-shadow.ts +100 -12
  227. package/src/loops/orchestrator/tick.ts +73 -2
  228. package/src/machines/machine-ledger.ts +31 -3
  229. package/src/maturity/cli-maturity.ts +13 -3
  230. package/src/maturity/feature-maturity.ts +1 -1
  231. package/src/msg/msg-sse-server.ts +141 -0
  232. package/src/msg/msg-store.ts +149 -17
  233. package/src/ops/overview-collect.ts +3 -2
  234. package/src/ops/overview-owner.ts +29 -0
  235. package/src/patents/ledger.ts +128 -0
  236. package/src/plugin-hooks/shell-hook.ts +17 -2
  237. package/src/plugin-missions/evaluator-runner.ts +18 -2
  238. package/src/plugins/install/market-fetch.ts +32 -1
  239. package/src/plugins/install/plugin-install.ts +11 -1
  240. package/src/pod-lease/admission.ts +116 -41
  241. package/src/prompt-enhance/enhance.ts +11 -0
  242. package/src/release-loop/checklist.ts +52 -4
  243. package/src/release-loop/feature-store.ts +10 -9
  244. package/src/release-loop/merged-pr-checklist.ts +24 -5
  245. package/src/release-loop/rubric.ts +70 -0
  246. package/src/rfc-registry/registry.ts +88 -0
  247. package/src/seat-dispatch/seat-request-ledger.ts +4 -0
  248. package/src/seat-loop/knowhow-lint.ts +233 -0
  249. package/src/seat-loop/seat-loop.ts +5 -1
  250. package/src/seat-loop/seat-pack.ts +16 -4
  251. package/src/seat-loop/traffic-signal.ts +7 -0
  252. package/src/self-dev/ask-launch-flow.ts +48 -8
  253. package/src/self-dev/ask-path-candidates.ts +69 -14
  254. package/src/self-dev/decompose-proposal.ts +43 -1
  255. package/src/self-dev/draft-sweep.ts +23 -14
  256. package/src/self-dev/graph-journey-nodes.ts +2 -0
  257. package/src/self-dev/launch-preflight-text.ts +5 -1
  258. package/src/self-dev/launch-preflight.ts +143 -10
  259. package/src/self-dev/orchestrate.ts +6 -2
  260. package/src/self-dev/run-supervisor.ts +35 -1
  261. package/src/self-implement/gate-cli.ts +6 -2
  262. package/src/self-implement/goal-author.ts +5 -28
  263. package/src/self-implement/goal-command-execution.ts +26 -0
  264. package/src/self-implement/goal-new-paths.ts +4 -5
  265. package/src/self-implement/graph-authority.ts +17 -3
  266. package/src/self-implement/graph-yaml.ts +15 -7
  267. package/src/self-implement/headless-elanous-driver.ts +16 -1
  268. package/src/self-implement/host-regate.ts +44 -9
  269. package/src/self-implement/orchestrator.ts +5 -11
  270. package/src/self-implement/pwa-import-graph.ts +194 -7
  271. package/src/self-implement/rework-policy.ts +80 -2
  272. package/src/self-implement/self-implement-runtime.ts +126 -21
  273. package/src/self-implement/speed-claims.ts +145 -6
  274. package/src/session/index.ts +14 -4
  275. package/src/session-runtime/index.ts +55 -11
  276. package/src/skills/tools/persistent-grounding.ts +59 -4
  277. package/src/skills/tools/shell.ts +79 -1
  278. package/src/steward/rejudge-tick.ts +12 -1
  279. package/src/steward/release-day.ts +158 -0
  280. package/src/steward/resync-tick.ts +123 -0
  281. package/src/task-agent/actions.ts +1 -0
  282. package/src/task-agent/adopt-pr.ts +140 -0
  283. package/src/task-agent/board.ts +11 -1
  284. package/src/task-agent/card-evidence.ts +37 -5
  285. package/src/task-agent/cover.ts +105 -19
  286. package/src/task-agent/failure-notify.ts +61 -0
  287. package/src/task-agent/green-sweep.ts +170 -0
  288. package/src/task-agent/land-worktree.ts +52 -5
  289. package/src/task-agent/live-move-history.ts +1 -0
  290. package/src/task-agent/live-moves.ts +56 -17
  291. package/src/task-agent/parent-host.ts +1 -1
  292. package/src/task-agent/postland-card.ts +139 -0
  293. package/src/task-agent/rejudge-on-head.ts +321 -25
  294. package/src/task-agent/remote-rejudge-bind.ts +62 -0
  295. package/src/task-agent/repair-branch-cas.ts +182 -0
  296. package/src/task-agent/repair-cycle.ts +275 -0
  297. package/src/task-agent/repair-next-move.ts +31 -0
  298. package/src/task-agent/repair-review.ts +24 -0
  299. package/src/task-agent/repair-worker.ts +87 -0
  300. package/src/task-agent/review-result-capture.ts +66 -2
  301. package/src/task-agent/shadow.ts +150 -29
  302. package/src/task-agent/task-hand.ts +133 -8
  303. package/src/task-orchestrator/surfaces/pod-repair-commit.ts +96 -0
  304. package/src/task-orchestrator/surfaces/self-implement-pod.ts +71 -12
  305. package/src/telegram.ts +30 -1
  306. package/src/tool-runtime/tool-search-runtime.ts +25 -0
  307. package/src/tui.ts +20 -1
  308. package/src/user-config.ts +11 -60
  309. package/src/ux-sim/fault-sqlite-busy.ts +50 -0
  310. package/src/version/packed-revision.json +1 -1
  311. package/apps/pwa/out/_next/static/chunks/app/approvals/page-9360b1285b2a346e.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f08f6529d09ffae1.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/app/board/page-f000d54d258f9ef6.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/app/bots/page-22f674e7e4c2c9d5.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8888733e40cea6d1.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/app/chat/page-9717ec6964b92276.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/app/consult/page-44eaad486bc98a45.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/app/control/page-97fa6cf3d34c7eb9.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/app/editor/page-35e05d7450461a67.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/app/inside/page-b2638013c0bf7dfd.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/app/layout-c44f93a41167536f.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/app/loops/page-1b810474507f3702.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/app/market/page-74366ae2693a7d95.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4e634c5b98fd4e93.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ed3070d637850548.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/outputs/page-b9cc8a10bc6078b4.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/reflection/page-128458c8d86fbb5f.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/sessions/page-cf0c2b0183991d09.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/showroom/page-b008aff96be56654.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/tasks/page-814394dcbae789b1.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/term/page-6a87325c87f09dd2.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/trace/page-852170303c6cd755.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/vault/page-087ceeb975ef272b.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-97c7361cc2a19319.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/workspace/page-8c6901833f897496.js +0 -1
  336. /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_buildManifest.js +0 -0
  337. /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_ssgManifest.js +0 -0
@@ -11,6 +11,10 @@ import { execFileSync } from 'node:child_process';
11
11
  import { createHash, randomBytes } from 'node:crypto';
12
12
  import { mkdirSync, readFileSync, statSync, writeFileSync, type Stats } from 'node:fs';
13
13
  import { dirname, join, resolve } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { parse as parseYaml } from 'yaml';
16
+ import { validateUnitCalls } from '../graph-runner/runner.js';
17
+ import { parseGraphTemplateYaml } from '../self-implement/graph-yaml.js';
14
18
  import { resolveDaemonHarnessTarget } from '../intake-plane/harness-target.js';
15
19
  import { HARNESS_RUN_ID_ENV, mintRunId, normalizeRunId } from '../harness/harness-space.js';
16
20
  import { debug } from '../debug/log.js';
@@ -18,6 +22,7 @@ import { effectiveInstanceRoot } from '../instance/resolve.js';
18
22
  import { withFileLockSync } from '../storage/file-lock.js';
19
23
  import { checkCompletionEvidence, deliverableReceiptEvidence } from './completion-evidence.js';
20
24
  import { judgeNextMove } from './judge.js';
25
+ import { loadRunLedger, runLedgerDir } from '../self-implement/run-ledger.js';
21
26
 
22
27
  export interface ResolveHandTextDeps {
23
28
  cwd: string;
@@ -88,12 +93,74 @@ export function taskCardLabelContext(card: Pick<TaskCard, 'id'>): TaskCardLabelC
88
93
  export const COMPLETION_KINDS = ['code-pr', 'artifact', 'research-report', 'ops-action', 'content', 'watch-brief', 'decision-support', 'deliverable'] as const;
89
94
  export type CompletionKind = typeof COMPLETION_KINDS[number];
90
95
 
96
+ export interface TaskComposition {
97
+ file: string;
98
+ /** null = 파일은 있으나 파싱 실패로 판을 확인하지 못했다. */
99
+ version: number | null;
100
+ }
101
+
102
+ export interface CompositionSelection {
103
+ composition?: TaskComposition;
104
+ unitsResolved: boolean;
105
+ missingUnits: string[];
106
+ launcher?: 'harness-say' | 'graph-run';
107
+ error?: string;
108
+ }
109
+
110
+ /** 1판은 조합을 고르고 단위 해석 결과를 기록한다. 단위 실행은 2판의 몫이다. */
111
+ export function selectTaskComposition(completion: CompletionKind, root = join(dirname(fileURLToPath(import.meta.url)), '..', '..')): CompositionSelection {
112
+ const file = `graphs/work/${completion}.yaml`;
113
+ const path = join(root, file);
114
+ let source: string;
115
+ try { source = readFileSync(path, 'utf8'); } catch (error) {
116
+ if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') return { unitsResolved: false, missingUnits: [] };
117
+ return { composition: { file, version: null }, unitsResolved: false, missingUnits: [],
118
+ error: `조합 읽기 오류: ${file}: ${error instanceof Error ? error.message : String(error)}` };
119
+ }
120
+ const parsed = parseGraphTemplateYaml(source, path);
121
+ if (!parsed.template || parsed.errors.length) {
122
+ let version: number | null = null;
123
+ try {
124
+ const raw: unknown = parseYaml(source);
125
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
126
+ const candidate = (raw as Record<string, unknown>).version;
127
+ if (typeof candidate === 'number' && Number.isInteger(candidate)) version = candidate;
128
+ }
129
+ } catch { /* 파싱 실패 자체를 아래 원장에 적는다. */ }
130
+ return { composition: { file, version }, unitsResolved: false, missingUnits: [],
131
+ error: `조합 오류: ${file}: ${parsed.errors.map(e => e.message).join('; ')}` };
132
+ }
133
+ const raw: unknown = parseYaml(source);
134
+ const launcher = raw && typeof raw === 'object' && !Array.isArray(raw) ? (raw as Record<string, unknown>).launcher : undefined;
135
+ if (launcher !== undefined && launcher !== 'harness-say' && launcher !== 'graph-run') {
136
+ return { composition: { file, version: parsed.template.version }, unitsResolved: false, missingUnits: [],
137
+ error: `조합 오류: ${file}: 모르는 launcher` };
138
+ }
139
+ if (completion === 'code-pr' && launcher === undefined) {
140
+ return { composition: { file, version: parsed.template.version }, unitsResolved: false, missingUnits: [],
141
+ error: `조합 오류: ${file}: launcher 없음` };
142
+ }
143
+ const missingUnits: string[] = [];
144
+ let unitError: string | undefined;
145
+ try { validateUnitCalls(parsed.template, path, [], call => missingUnits.push(call)); }
146
+ catch (error) { unitError = error instanceof Error ? error.message : String(error); }
147
+ return {
148
+ composition: { file, version: parsed.template.version },
149
+ unitsResolved: missingUnits.length === 0 && !unitError,
150
+ missingUnits,
151
+ ...(launcher === 'harness-say' || launcher === 'graph-run' ? { launcher } : {}),
152
+ ...(unitError ? { error: `조합 단위 해석 오류: ${file}: ${unitError}` } : {}),
153
+ };
154
+ }
155
+
91
156
  /**
92
157
  * `run-bound`·`goal-bound`·`pr-bound` 은 runId(⊕ pr)를 싣는다 — 옛 카드의 칸은 at·event·detail 뿐이다.
93
158
  * TA-LIVE-MOVE-CARD-HISTORY — `live-move` 줄만 kind·executed·ok·executorResult·head·effect 를 더 싣는다(`live-move-history.ts`).
94
159
  */
95
160
  export interface TaskCardEvent {
96
161
  at: string; event: string; detail?: string; runId?: string; pr?: number;
162
+ /** 원격 미결 수를 HQ 에 한 번만 회수하기 위한 원장 사건 id. */
163
+ sourceId?: string;
97
164
  /** `closed-by-hand` 줄만 — 닫은 사유 · 닫은 자리(`closeTaskCard`). */
98
165
  reason?: string; by?: string;
99
166
  kind?: string; executed?: boolean; ok?: boolean; executorResult?: 'shadow' | 'live'; head?: string; effect?: string;
@@ -101,6 +168,8 @@ export interface TaskCardEvent {
101
168
  resultPath?: string;
102
169
  /** TA-REVIEW-RESULT-CAPTURE — `live-move-result` 줄만: 회수한 `self review --json` 의 칸(못 읽은 칸은 null). */
103
170
  verdict?: string | null; reviewed?: boolean | null; mustFix?: number | null; reviewRoute?: string | null;
171
+ /** Exact bytes of the captured review stdout; repair must read the same resultPath and digest. */
172
+ reviewSha256?: string;
104
173
  }
105
174
  /** 카드 history 의 live-move 줄 이벤트 이름 — 상태 사유(«마지막 칸») 판정에서 뺀다. */
106
175
  export const LIVE_MOVE_HISTORY_EVENT = 'live-move';
@@ -114,8 +183,15 @@ export interface TaskCard {
114
183
  text: string;
115
184
  seat?: TaskSeat;
116
185
  checklistId?: string;
186
+ /** Explicit release version for an adopted PR; post-land still confirms the cell exists there. */
187
+ checklistVersion?: string;
117
188
  /** 종결 종류 — 비면 code-pr. */
118
189
  completion?: CompletionKind;
190
+ /** 선택된 일 조합 파일과 판(1판: 실행은 launcher 에 위임). */
191
+ composition?: TaskComposition;
192
+ graphSelectedBy?: 'completion';
193
+ unitsResolved?: boolean;
194
+ missingUnits?: string[];
119
195
  /** 과제 대상 프로젝트(§A4b②) — target 은 절대 경로(git 이 아닐 수 있다). id 는 `task hand --project` 가 싣는다(옛 카드엔 없을 수 있다). */
120
196
  project?: { id?: string; target: string };
121
197
  /** 보드(§A4b① `board.ts`) 소속 — 목표 id · 이정표 id(`task hand --goal/--milestone`). 조각 카드의 `goalId`(골 sha 16자)와 다른 칸이다. */
@@ -127,7 +203,7 @@ export interface TaskCard {
127
203
  * failed = 발사된 런이 PR·수확 가지 없이 죽었거나 끝내 안 떴다(TA-JUDGE-DEAD-RUN · `card-evidence.ts`) — 사유
128
204
  * (`failed/needs-owner|needs-relaunch — …`)는 history 마지막 칸 · 자동 재발사하지 않는다(사람/판단부 몫).
129
205
  */
130
- status: 'handed' | 'launched' | 'launch-failed' | 'failed';
206
+ status: 'handed' | 'launched' | 'launch-failed' | 'failed' | 'done';
131
207
  history: TaskCardEvent[];
132
208
  /** 미션 카드만 — 조각 카드 id(순서대로) · 쪼갠 출처(`mission.ts` splitMission). */
133
209
  pieces?: string[];
@@ -142,6 +218,12 @@ export interface TaskCard {
142
218
  * 번호는 `cardPrNumber` 로 읽는다(옛 카드의 number 도 그대로 읽힌다).
143
219
  */
144
220
  pr?: number | TaskCardPr;
221
+ /** Host-verified existing OPEN PR adopted without launching a new implementation run. */
222
+ adoptedPr?: { pr: number; url: string; head: string; at: string };
223
+ /** Bound PR observed in a terminal GitHub state. Only applies while the card still binds the same PR and URL. */
224
+ prTerminal?: { pr: number; url: string; head: string; state: 'MERGED' | 'CLOSED'; at: string };
225
+ /** Post-land checklist judgement. A partial slice closes this card while leaving the checklist cell yellow. */
226
+ postLand?: { pr: number; head: string; at: string; outcome: 'complete' | 'partial'; evidence: string[]; remaining: string[] };
145
227
  goalId?: string;
146
228
  /** `manual` = pr/goalId 를 사람이 적었다(`recordPieceRef`) — 런 묶기가 건드리지 않는다. */
147
229
  refSource?: 'manual';
@@ -167,7 +249,9 @@ export interface TaskCard {
167
249
  */
168
250
  regates?: Array<{ pr: number; head: string; at: string; resultPath?: string; passed?: boolean; status?: string; baseCommit?: string; detail?: string;
169
251
  /** REJUDGE base-stale — 같은 머리를 새 base 위에서 다시 잰 재게이트면, land 가 낡았다고 거절한 앞 재게이트의 base(멱등 키 = 머리 ⊕ 이 base). */
170
- staleBase?: string }>;
252
+ staleBase?: string;
253
+ /** TA-READY-BEFORE-REGATE — draft 때문에 실패한 같은 머리를 ready 뒤 딱 한 번 다시 잰다. */
254
+ readyRetry?: true }>;
171
255
  /**
172
256
  * Live land attempts, claimed before invocation, once per PR head even if the command fails.
173
257
  * TA-LAND-RETRY-ON-INFRA — a failed attempt carries `class`/`reason`: `infra` (TA-side, allow-listed) leaves one more attempt on
@@ -180,6 +264,10 @@ export interface TaskCard {
180
264
  taLand?: true;
181
265
  /** TASKS-HAND-CHILD-LLM — `tasks hand --child-llm <provider>/<model> [--child-llm-effort <level>]` → 발사 인자 `--child-llm-provider/-model/-effort`. */
182
266
  childLlm?: TaskChildLlm;
267
+ /** Same-PR repair launches; exact source head and worker result are retained for CAS and retry limits. */
268
+ repairs?: Array<{ pr: number; head: string; branch: string; at: string; move: string; runId: string;
269
+ resultPath: string; fromModel: string; toModel: string; status: 'requested' | 'unconfirmed' | 'failed' | 'applied';
270
+ detail?: string; commit?: string; bundleSha256?: string; bundlePath?: string }>;
183
271
  }
184
272
 
185
273
  /** 구현 자식 LLM 선택 — 하니스 `--child-llm-provider/--child-llm-model/--child-llm-effort` 로 그대로 넘긴다(검증 권위는 하니스). */
@@ -210,6 +298,12 @@ export function cardPrNumber(card: Pick<TaskCard, 'pr'>): number | undefined {
210
298
  return typeof card.pr === 'number' ? card.pr : card.pr?.number;
211
299
  }
212
300
 
301
+ /** A stale terminal marker must never close a card after its PR binding changes. */
302
+ export function boundPrTerminal(card: Pick<TaskCard, 'pr' | 'prTerminal'>): TaskCard['prTerminal'] | undefined {
303
+ const marker = card.prTerminal;
304
+ return marker && typeof card.pr === 'object' && card.pr.number === marker.pr && card.pr.url === marker.url ? marker : undefined;
305
+ }
306
+
213
307
  /** A uniquely launched code-PR card may own a draft through its PR or its opening run. */
214
308
  export function draftTaskOwner(cards: readonly TaskCard[], draft: { number: number; runId?: string | null }): TaskCard | undefined {
215
309
  const owners = cards.filter((card) => card.status === 'launched' && (card.completion ?? 'code-pr') === 'code-pr'
@@ -316,7 +410,8 @@ export function launchArgs(card: Pick<TaskCard, 'text' | 'seat' | 'childLlm'>):
316
410
  * 이유에 «착지 미확인»을 단다(착지했다/안 했다를 단정하지 않는다).
317
411
  * 미션 카드는 스스로 발사하지 않는다 · 아직 안 뜬 조각의 선행이 덜 착지했으면 `wait after <id>`.
318
412
  */
319
- export function nextMoveFor(card: TaskCard, landed?: ReadonlySet<string>): TaskCardMove {
413
+ export function nextMoveFor(card: TaskCard, landed?: ReadonlySet<string>, deps: { ledgerDir?: string } = {}): TaskCardMove {
414
+ // Completion callbacks record a shadow decision; do not suggest the live CLI here.
320
415
  if (card.pieces) return { kind: 'wait', reason: `미션 — 조각 ${card.pieces.length}장이 선후대로 발사된다` };
321
416
  const unmet = (card.after ?? []).filter((id) => !landed?.has(id));
322
417
  if (card.status !== 'launched' && unmet.length > 0) return { kind: 'wait', reason: `after ${unmet.join(', ')}${landed ? '' : ' · 착지 미확인'}` };
@@ -324,6 +419,16 @@ export function nextMoveFor(card: TaskCard, landed?: ReadonlySet<string>): TaskC
324
419
  if (card.status === 'launch-failed') return { kind: 'launch', command: launchArgs(card), reason: '직전 발사 실패 — 다시 발사' };
325
420
  // live-move·hand-move 줄(관측 기록)은 실패 사유가 아니다 — 그 줄을 뺀 마지막 칸이 사유다(live-move 줄 없는 카드는 종전과 같다).
326
421
  if (card.status === 'failed') return { kind: 'wait', reason: card.history.filter((item) => item.event !== LIVE_MOVE_HISTORY_EVENT && item.event !== LIVE_MOVE_RESULT_EVENT && item.event !== HAND_MOVE_HISTORY_EVENT).at(-1)?.detail ?? '런 실패 — PR·수확 가지 없음' };
422
+ if (card.status === 'launched' && card.runId) {
423
+ try {
424
+ const ledger = loadRunLedger(card.runId, deps.ledgerDir ?? runLedgerDir());
425
+ const terminal = ledger?.filter((entry) => entry.event === 'run-status').at(-1);
426
+ if (terminal && ['succeeded', 'failed', 'cancelled', 'abandoned'].includes(String(terminal.data.runStatus))) {
427
+ if (cardPrNumber(card) === undefined) return { kind: 'wait', reason: `런 원장 종료 — ${terminal.data.runStatus} · PR 없음` };
428
+ return { kind: 'wait', reason: `런 원장 종료 — ${terminal.data.runStatus} · 그림자 재판정은 완료 훅이 기록` };
429
+ }
430
+ } catch { /* 원장 결손·손상은 종료로 단정하지 않는다. */ }
431
+ }
327
432
  return { kind: 'wait', reason: '발사됨 — 런 멈춤을 슈퍼바이저 그림자 판단(task-agent.shadow-move)이 받는다' };
328
433
  }
329
434
 
@@ -358,6 +463,8 @@ export interface HandTaskOptions {
358
463
  childLlm?: string;
359
464
  childLlmEffort?: string;
360
465
  statePath?: string;
466
+ /** 시험에서만 조합 저장소를 바꿔 해석·거부를 검증한다. */
467
+ compositionRoot?: string;
361
468
  /** `--live` 일 때만 불린다. */
362
469
  launcher?: TaskLauncher;
363
470
  now?: () => Date;
@@ -427,6 +534,11 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
427
534
  }
428
535
  if (opts.childLlmEffort !== undefined && opts.childLlm === undefined) throw new Error('--child-llm-effort 는 --child-llm <provider>/<model> 과 함께 준다');
429
536
  const childLlm = opts.childLlm !== undefined ? parseChildLlm(opts.childLlm, opts.childLlmEffort) : undefined;
537
+ const completion = opts.completion ?? 'code-pr';
538
+ const selected = selectTaskComposition(completion, opts.compositionRoot);
539
+ const selectionFailure = !selected.composition ? `조합 없음: graphs/work/${completion}.yaml`
540
+ : selected.error ?? (completion !== 'code-pr' || selected.launcher !== 'harness-say'
541
+ ? `조합 실행기 없음: ${selected.composition.file} (1판은 code-pr harness-say만 발사)` : undefined);
430
542
  let project: TaskCard['project'];
431
543
  let targetIsGit: boolean | null = null;
432
544
  if (opts.project) {
@@ -451,19 +563,25 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
451
563
  ...(opts.seat ? { seat: opts.seat } : {}),
452
564
  ...(opts.checklistId ? { checklistId: opts.checklistId } : {}),
453
565
  ...(opts.completion ? { completion: opts.completion } : {}),
566
+ ...(selected.composition ? { composition: selected.composition, graphSelectedBy: 'completion' as const } : {}),
567
+ unitsResolved: selected.unitsResolved,
568
+ missingUnits: selected.missingUnits,
454
569
  ...(project ? { project } : {}),
455
570
  ...(opts.goal ? { goal: opts.goal } : {}),
456
571
  ...(opts.milestone ? { milestone: opts.milestone } : {}),
457
572
  ...(opts.taLand === true ? { taLand: true as const } : {}),
458
573
  ...(childLlm ? { childLlm } : {}),
459
574
  createdAt: now.toISOString(),
460
- status: 'handed',
461
- history: [],
575
+ status: selectionFailure ? 'failed' : 'handed',
576
+ history: selectionFailure ? [{ at: now.toISOString(), event: !selected.composition ? 'composition-missing'
577
+ : selected.error ? 'composition-invalid' : 'composition-launcher-unavailable', detail: selectionFailure }] : [],
462
578
  ...(opts.mission ? { mission: opts.mission, after: [...(opts.after ?? [])] } : {}),
463
- ...(opts.cardFields ?? {}),
579
+ ...(opts.cardFields?.pr !== undefined ? { pr: opts.cardFields.pr } : {}),
580
+ ...(opts.cardFields?.goalId !== undefined ? { goalId: opts.cardFields.goalId } : {}),
581
+ ...(opts.cardFields?.handed !== undefined ? { handed: opts.cardFields.handed } : {}),
464
582
  };
465
- const move = nextMoveFor(card, opts.landed);
466
- if (move.kind !== 'launch') throw new Error(`발사할 수 없는 과제다 — ${move.reason}`);
583
+ const move = selectionFailure ? undefined : nextMoveFor(card, opts.landed);
584
+ if (move && move.kind !== 'launch') throw new Error(`발사할 수 없는 과제다 — ${move.reason}`);
467
585
  const guard = opts.writeGuard;
468
586
  const writeCard = (target: string, next: TaskCard): void => {
469
587
  if (!guard) { writeTaskCards(target, [next]); return; }
@@ -479,6 +597,13 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
479
597
  if (!owned.ok) throw new TaskCardSupersededError(next.id);
480
598
  };
481
599
  writeCard(path, card);
600
+ try { debug.log('task-agent.graph-select', !selected.composition ? 'composition-missing' : selected.error ? 'composition-invalid' : 'composition-selected', {
601
+ completion, file: selected.composition?.file ?? `graphs/work/${completion}.yaml`,
602
+ unitsResolved: selected.unitsResolved, missingUnits: selected.missingUnits,
603
+ ...(selected.error ? { error: selected.error } : {}),
604
+ }); } catch { /* fail-soft */ }
605
+ if (selectionFailure) throw new Error(`${selectionFailure} (카드 ${id})`);
606
+ if (!move || move.kind !== 'launch') throw new Error('조합 선택 뒤 발사 수가 없다');
482
607
  try { debug.log('task-agent', 'handed', { taskId: id, seat: card.seat ?? null, checklistId: card.checklistId ?? null, mode, projectId: project?.id ?? null, targetIsGit, taLand: card.taLand === true }); } catch { /* fail-soft */ }
483
608
  if (childLlm) {
484
609
  try { debug.log('task-agent', 'child-llm', { taskId: id, input: opts.childLlm, provider: childLlm.provider, model: childLlm.model, effort: childLlm.effort ?? null, mode }); } catch { /* fail-soft */ }
@@ -0,0 +1,96 @@
1
+ /** Pod repair result: a commit based on the exact PR head, returned without opening a PR. */
2
+ import { execFileSync } from 'node:child_process';
3
+ import { createHash } from 'node:crypto';
4
+ import { mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
5
+ import { isAbsolute, join, resolve } from 'node:path';
6
+
7
+ export const REPAIR_REF = 'refs/elanous/repair/result';
8
+ export const REPAIR_BUNDLE_MAX_BYTES = 5 * 1024 * 1024;
9
+ const SHA40 = /^[0-9a-f]{40}$/;
10
+
11
+ export interface PodRepairResult {
12
+ pr: number;
13
+ baseHead: string;
14
+ runId: string;
15
+ commit: string;
16
+ bundleSha256: string;
17
+ }
18
+
19
+ /** Host-side receipt gate. A missing, mismatched, or truncated return is never a repair result. */
20
+ export function readPodRepairArtifact(artifactDir: string, expected: { pr: number; baseHead: string; runId?: string }): PodRepairResult {
21
+ const result = JSON.parse(readFileSync(join(artifactDir, 'result.json'), 'utf8')) as Partial<PodRepairResult>;
22
+ if (result.pr !== expected.pr || result.baseHead !== expected.baseHead || (expected.runId && result.runId !== expected.runId)
23
+ || !result.runId || !SHA40.test(result.commit ?? '') || !/^[0-9a-f]{64}$/.test(result.bundleSha256 ?? '')) {
24
+ throw new Error('repair result mismatch');
25
+ }
26
+ const bundle = readFileSync(join(artifactDir, 'commit.bundle'));
27
+ if (bundle.length > REPAIR_BUNDLE_MAX_BYTES) throw new Error('repair bundle too large');
28
+ if (createHash('sha256').update(bundle).digest('hex') !== result.bundleSha256) throw new Error('repair bundle checksum mismatch');
29
+ return result as PodRepairResult;
30
+ }
31
+
32
+ function git(cwd: string, ...args: string[]): string {
33
+ return execFileSync('git', ['-C', cwd, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
34
+ }
35
+
36
+ function completedWorktree(output: string): { path: string; runId: string } {
37
+ for (const line of output.trimEnd().split(/\r?\n/).reverse()) {
38
+ if (!line.startsWith('{')) continue;
39
+ let value: Record<string, unknown>;
40
+ try { value = JSON.parse(line) as Record<string, unknown>; } catch { continue; }
41
+ if (value.kind === 'self' && value.result && typeof value.result === 'object') value = value.result as Record<string, unknown>;
42
+ if (value.stage !== 'worktree-completed' || value.ok !== true || typeof value.worktreePath !== 'string'
43
+ || typeof value.runId !== 'string' || !value.runId)
44
+ throw new Error('repair child final result is not a successful worktree-only result');
45
+ return { path: value.worktreePath, runId: value.runId };
46
+ }
47
+ throw new Error('repair child did not return a successful worktree-only result');
48
+ }
49
+
50
+ /** Called in the Pod after the child exits successfully and before artifact export. */
51
+ export function writePodRepairCommit(input: { pr: number; baseHead: string; runId: string; output: string; repo: string; outbox: string }): PodRepairResult {
52
+ if (!Number.isSafeInteger(input.pr) || input.pr < 1 || !SHA40.test(input.baseHead)) throw new Error('invalid repair PR or base head');
53
+ if (!input.runId.trim()) throw new Error('repair parent run id missing');
54
+ const { path, runId: childRunId } = completedWorktree(input.output);
55
+ if (!isAbsolute(path)) throw new Error('repair worktree path is not absolute');
56
+ const worktree = realpathSync(path);
57
+ const repo = realpathSync(input.repo);
58
+ if (worktree === repo) throw new Error('repair result must be a separate worktree');
59
+ if (git(worktree, 'rev-parse', '--is-inside-work-tree') !== 'true') throw new Error('repair result is not a git worktree');
60
+ const commonDir = (cwd: string) => realpathSync(resolve(cwd, git(cwd, 'rev-parse', '--git-common-dir')));
61
+ if (commonDir(worktree) !== commonDir(repo)) throw new Error('repair worktree belongs to another repository');
62
+ if (git(worktree, 'config', '--worktree', '--get', 'elanous.harness.owner') !== `dev:${childRunId}`
63
+ || git(worktree, 'config', '--worktree', '--get', 'elanous.harness.command') !== 'elanous dev')
64
+ throw new Error('repair worktree is not owned by the completed child run');
65
+ if (git(repo, 'rev-parse', 'HEAD') !== input.baseHead) throw new Error('Pod source head changed before repair export');
66
+ git(worktree, 'cat-file', '-e', `${input.baseHead}^{commit}`);
67
+ execFileSync('git', ['-C', worktree, 'add', '-A', '--', '.'], { stdio: ['ignore', 'pipe', 'pipe'] });
68
+ const staged = git(worktree, 'diff', '--cached', '--name-only');
69
+ if (staged) git(worktree, 'commit', '-m', `repair: PR #${input.pr} from ${input.baseHead.slice(0, 12)}`);
70
+ const commit = git(worktree, 'rev-parse', 'HEAD');
71
+ if (!SHA40.test(commit) || commit === input.baseHead) throw new Error('repair made no new commit');
72
+ execFileSync('git', ['-C', worktree, 'merge-base', '--is-ancestor', input.baseHead, commit], { stdio: ['ignore', 'pipe', 'pipe'] });
73
+ git(worktree, 'update-ref', REPAIR_REF, commit);
74
+ const dir = join(input.outbox, 'repair');
75
+ mkdirSync(dir, { recursive: true });
76
+ const bundle = join(dir, 'commit.bundle');
77
+ execFileSync('git', ['-C', worktree, 'bundle', 'create', bundle, REPAIR_REF, `^${input.baseHead}`], { stdio: ['ignore', 'pipe', 'pipe'] });
78
+ const size = statSync(bundle).size;
79
+ if (size > REPAIR_BUNDLE_MAX_BYTES) throw new Error(`repair bundle too large: ${size} > ${REPAIR_BUNDLE_MAX_BYTES}`);
80
+ const bundleSha256 = createHash('sha256').update(readFileSync(bundle)).digest('hex');
81
+ const result = { pr: input.pr, baseHead: input.baseHead, runId: input.runId, commit, bundleSha256 };
82
+ writeFileSync(join(dir, 'result.json'), JSON.stringify(result) + '\n', { flag: 'wx', mode: 0o600 });
83
+ return result;
84
+ }
85
+
86
+ if (import.meta.main) {
87
+ try {
88
+ const [pr, baseHead, outputPath, repo, outbox] = process.argv.slice(2);
89
+ if (!pr || !baseHead || !outputPath || !repo || !outbox) throw new Error('usage: pod-repair-commit <pr> <base-head> <child-output> <repo> <outbox>');
90
+ const result = writePodRepairCommit({ pr: Number(pr), baseHead, runId: process.env.ELANOUS_PARENT_RUN_ID ?? '', output: readFileSync(outputPath, 'utf8'), repo, outbox });
91
+ console.log(`ELANOUS_POD_REPAIR_COMMIT ${result.pr} ${result.baseHead} ${result.commit}`);
92
+ } catch (error) {
93
+ console.error(`ELANOUS_POD_REPAIR_FAILED ${error instanceof Error ? error.message : String(error)}`);
94
+ process.exitCode = 1;
95
+ }
96
+ }
@@ -19,6 +19,7 @@ import { coalescedInstallationCredential } from '../../auth/github-app-token.js'
19
19
  import { POD_CREDENTIAL_TOKEN_ENV, POD_CREDENTIAL_URL_ENV } from '../../grok/credential.js';
20
20
  import { collectPodLedgers, createPodLedgerFollower } from './pod-ledger-collect.js';
21
21
  import { collectPodArtifacts, podNoResultDiagnostic } from './pod-artifact-return.js';
22
+ import { readPodRepairArtifact } from './pod-repair-commit.js';
22
23
  import { isValidAccountName, resolveCodexAccount } from '../../oauth/codex-account.js';
23
24
  import { effectiveInstanceRoot } from '../../instance/resolve.js';
24
25
  import { controlInboxEnv } from '../../harness/control-inbox.js';
@@ -27,7 +28,7 @@ import { mintRunId, normalizeRunId } from '../../harness/harness-space.js';
27
28
  import { TA_LAND_ENV } from '../../task-agent/task-hand.js';
28
29
  import { appendRunLedgerEntry, loadRunLedger, runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
29
30
  import type { GoalExecutionRecord } from '../../self-implement/orchestrator.js';
30
- import { runHostRegate, type HostRegateResult } from '../../self-implement/host-regate.js';
31
+ import { hostRepositoryUnavailable, runHostRegate, type HostRegateResult } from '../../self-implement/host-regate.js';
31
32
  import { resolvePodHostAcpReview, runPodHostAcpReview, type PodHostAcpReviewOutcome } from './pod-host-acp-review.js';
32
33
  import { REVIEW_DEFERRED_TO_HOST_ENV, REVIEW_DEFERRED_TO_HOST_MERGE_REASON } from '../../self-implement/review-deferred-to-host.js';
33
34
  import { classifyPodTerminal, applyPodTerminal, podTerminalPrBody, type PrTerminalRecord, type DraftResidueContext } from '../../self-dev/draft-residue.js';
@@ -137,6 +138,8 @@ export interface PodSpawnOptions {
137
138
  authorConfig?: () => UserConfig;
138
139
  /** 원천 — 없으면 종전 `git clone --depth 50`. bundle 이면 apply 뒤 kubectl cp 로 싣는다. */
139
140
  source?: PodSource;
141
+ /** Existing PR repair: pin the source head and export a commit bundle instead of opening a PR. */
142
+ repairCommitOnly?: { pr: number; baseHead: string };
140
143
  /** 호스트 환경에서 읽어 Pod env 로 넣을 키 이름(예: OPENROUTER_API_KEY · ANTHROPIC_API_KEY) — 벤치마크 과금 경로. */
141
144
  passEnv?: readonly string[];
142
145
  /** 호스트가 재발사를 소유하면 Pod 자식의 자체 감독을 끈다. 독립 호출 기본 true; 실제 CLI 호스트는 자기 감독 상태를 명시한다. */
@@ -953,7 +956,10 @@ export function podSigtermTrapScript(seqFile: string = POD_USAGE_PARTIAL_SEQ_FIL
953
956
  return `trap '{ [ -n "$si_pid" ] && kill "$si_pid" 2>/dev/null || true; [ -n "\${usage_partial_pid:-}" ] && kill "$usage_partial_pid" 2>/dev/null || true; si_seq=$(cat ${seqFile} 2>/dev/null || true); case "$si_seq" in ""|*[!0-9]*) si_seq=0;; esac; si_seq=$((10#$si_seq + 1)); { [ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h --partial --seq "$si_seq"; } & si_rollup_pid=$!; for si_i in 1 2 3 4 5 6 7 8; do kill -0 "$si_rollup_pid" 2>/dev/null || break; sleep 1; done; kill "$si_rollup_pid" 2>/dev/null || true; wait "$si_rollup_pid" 2>/dev/null || true; exit 143; }' TERM`;
954
957
  }
955
958
 
956
- export function podJobManifest(o: { name: string; namespace: string; image: string; workIdentity?: PodWorkIdentity; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; memoryRequest?: string; goalDoc?: string; /** AUTHOR-POD2 — run `harness say` on /creds/feature inside the Pod (authoring happens off the host). */ authorSentence?: boolean; authorGrade?: 'full' | 'lite'; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number; /** Which goal execution and attempt this Job is — a resumed host verifies it before following the Job (POD9). */ execution?: { key: string; attempt: number }; hostLeaseAdmitted?: boolean; /** POD-ROLE-LLM — OpenRouter 자식 Pod 에 리뷰어용 codex 자격을 깐다. */ reviewerCodexAuth?: boolean; /** BEDROCK-PROVIDER ② — bedrock 자식만: 이름 있는 AWS Secret 의 키(secretKeyRef) ⊕ 리전 ⊕ 이그레스 라벨. */ bedrock?: BedrockPodSpec; /** ZAI-POD-CHILD — glm(ZAI) 자식만: 이름 있는 ZAI Secret 의 키(secretKeyRef) ⊕ 이그레스·동시 상한 라벨(Pod ⊕ Job). */ zai?: ZaiPodSpec }): Record<string, unknown> {
959
+ export function podJobManifest(o: { name: string; namespace: string; image: string; workIdentity?: PodWorkIdentity; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; repairCommitOnly?: { pr: number; baseHead: string }; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; memoryRequest?: string; goalDoc?: string; /** AUTHOR-POD2 — run `harness say` on /creds/feature inside the Pod (authoring happens off the host). */ authorSentence?: boolean; authorGrade?: 'full' | 'lite'; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number; /** Which goal execution and attempt this Job is — a resumed host verifies it before following the Job (POD9). */ execution?: { key: string; attempt: number }; hostLeaseAdmitted?: boolean; /** POD-ROLE-LLM — OpenRouter 자식 Pod 에 리뷰어용 codex 자격을 깐다. */ reviewerCodexAuth?: boolean; /** BEDROCK-PROVIDER ② — bedrock 자식만: 이름 있는 AWS Secret 의 키(secretKeyRef) ⊕ 리전 ⊕ 이그레스 라벨. */ bedrock?: BedrockPodSpec; /** ZAI-POD-CHILD — glm(ZAI) 자식만: 이름 있는 ZAI Secret 의 키(secretKeyRef) ⊕ 이그레스·동시 상한 라벨(Pod ⊕ Job). */ zai?: ZaiPodSpec }): Record<string, unknown> {
960
+ if (o.repairCommitOnly && (o.source?.kind !== 'pr' || o.source.number !== o.repairCommitOnly.pr || !/^[0-9a-f]{40}$/.test(o.repairCommitOnly.baseHead) || o.args.includes('--open-pr') || o.args.includes('--merge-by-host') || o.args.includes('--auto-merge'))) {
961
+ throw new Error('repair commit-only requires matching PR source and no PR/merge flags');
962
+ }
957
963
  const quoted = o.args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(' ');
958
964
  const childProviderIndex = o.args.indexOf('--child-llm-provider');
959
965
  const childModelIndex = o.args.indexOf('--child-llm-model');
@@ -962,7 +968,7 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
962
968
  // POD-ANTHROPIC-PROVIDER — anthropic 자식은 키(env · secretKeyRef)만 쓴다. codex·grok 자격 파일을 깔지 않는다.
963
969
  const anthropicChild = childProviderIndex >= 0 && o.args[childProviderIndex + 1] === 'anthropic';
964
970
  const goalPath = o.goalDoc ? `'${(o.goalDoc.startsWith('-') ? `./${o.goalDoc}` : o.goalDoc).replace(/'/g, `'\\''`)}'` : undefined;
965
- const delegated = Boolean(o.goalDoc) || o.authorSentence === true;
971
+ const delegated = !o.repairCommitOnly && (Boolean(o.goalDoc) || o.authorSentence === true);
966
972
  const askBaseIndex = delegated ? o.args.indexOf('--base') : -1;
967
973
  const quotedAsk = (askBaseIndex >= 0 && o.args[askBaseIndex + 1]
968
974
  ? ` '--base' '${o.args[askBaseIndex + 1]!.replace(/'/g, `'\\''`)}'`
@@ -997,12 +1003,13 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
997
1003
  podGitPushGuardScript(),
998
1004
  'curl -s -m 3 -o /dev/null http://host.orb.internal:31415/health && { echo "[pod] ISOLATION FAIL"; exit 3; }',
999
1005
  podSourceScript(o.source ?? { kind: 'default' }, o.repoUrl),
1006
+ ...(o.repairCommitOnly ? [`[ "$(git rev-parse HEAD)" = '${o.repairCommitOnly.baseHead}' ] || { echo 'ELANOUS_POD_REPAIR_FAILED source-head-mismatch'; exit 8; }`] : []),
1000
1007
  ...(goalPath ? [
1001
1008
  `mkdir -p -- "$(dirname -- ${goalPath})" && cp -- /creds/goal-doc ${goalPath} || exit 6`,
1002
1009
  ] : []),
1003
1010
  // Only fixed command shapes leave the Pod; arbitrary argv (including shell -c text) is never emitted.
1004
1011
  `(while :; do { mem=$(if [ -r /sys/fs/cgroup/memory.current ]; then cat /sys/fs/cgroup/memory.current; elif [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then cat /sys/fs/cgroup/memory/memory.usage_in_bytes; else printf -- -; fi); top=$(ps -eo rss=,comm=,args= --sort=-rss | head -5 | awk 'function encode(s) { gsub(/%/, "%25", s); gsub(/:/, "%3A", s); gsub(/ /, "%20", s); gsub(/\\t/, "%09", s); return s } { rss=$1; name=$2; sub(/^[[:space:]]*[0-9]+[[:space:]]+[^[:space:]]+[[:space:]]*/, ""); n=split($0, a, /[[:space:]]+/); cmd="<redacted>"; if (name=="sleep" && a[2]=="30") cmd="sleep 30" (n>2 ? " <redacted>" : ""); else if (name=="bun") { cmd="bun <redacted>"; if (a[2]=="test") cmd="bun test <redacted>"; else if (a[2]=="run") cmd="bun run <redacted>"; else if ((a[2]=="x" || a[2]=="exec") && a[3]=="tsc") cmd="bun x tsc <redacted>" } else if (name=="tsc") cmd="tsc <redacted>"; else if (name=="elanous") { cmd="elanous <redacted>"; if (a[2]=="self") cmd="elanous self <redacted>"; else if (a[2]=="harness") cmd="elanous harness <redacted>" } else if (name=="node") cmd="node <redacted>"; if (name=="sleep" && cmd=="<redacted>") cmd="sleep <redacted>"; else if (cmd=="<redacted>" && name!="bash" && name!="sh") name="other"; printf " %s:%s:%s", rss, encode(name), encode(substr(cmd,1,120)) }'); printf "ELANOUS_MEM %s %s%s\\n" "$(date +%s)" "$mem" "$top"; } || true; sleep 15 || break; done) & mem_sampler_pid=$!`,
1005
- ...(o.appCredential ? [podGithubWatchdogScript(o.githubStaleSeconds ?? POD_GH_STALE_SECONDS)] : []),
1012
+ ...(o.appCredential && !o.repairCommitOnly ? [podGithubWatchdogScript(o.githubStaleSeconds ?? POD_GH_STALE_SECONDS)] : []),
1006
1013
  // METER-POD-ROLLUP-GAP — 부분 롤업 표본기: 최종 줄까지 못 사는 Pod 도 N 분마다(기본 5 · ELANOUS_USAGE_PARTIAL_MINUTES) PARTIAL 줄을 하나씩 남긴다.
1007
1014
  `(usage_partial_seq=0; while :; do { usage_partial_seq=$((usage_partial_seq+1)); printf '%s\\n' "$usage_partial_seq" > ${POD_USAGE_PARTIAL_SEQ_FILE} 2>/dev/null || true; [ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h --partial --seq "$usage_partial_seq" || true; } || true; usage_partial_sleep=$(( \${ELANOUS_USAGE_PARTIAL_MINUTES:-5} * 60 )); sleep "$usage_partial_sleep" || break; done) & usage_partial_pid=$!`,
1008
1015
  // ⭐ MF-d3b5a70f — si_pid 초기화 + trap: 메인 명령을 아래에서 `& si_pid=$!` 로 띄우고 `wait "$si_pid"` 로
@@ -1013,7 +1020,9 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
1013
1020
  // 마지막 줄 JSON 이 «맨 끝»이어야 한다(parseSelfImplementJson) — rollup 은 그 앞에.
1014
1021
  // ⛔ `--author-grade` 는 Pod 안 `elanous`(이미지에 깔린 판)가 모를 수 있어 넘기지 않는다 — 10-06 «unknown option» 즉사(#24445 되돌림).
1015
1022
  // AUTHOR-LITE2-POD — 등급은 환경변수 `ELANOUS_AUTHOR_GRADE` 로 싣는다(옛 Pod elanous 는 모르는 환경변수를 무시하고 full 로 돈다).
1016
- o.authorSentence
1023
+ o.repairCommitOnly
1024
+ ? `export ${OLD_DOOR_STAMP_ENV}=self-implement; elanous self implement "$(cat /creds/feature)" --json --no-open-pr ${quoted} > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; if [ "$rc" -eq 0 ]; then elanous self repair-commit-export '${o.repairCommitOnly.pr}' '${o.repairCommitOnly.baseHead}' /tmp/si.out "$PWD" "$HOME/outbox" || rc=8; fi`
1025
+ : o.authorSentence
1017
1026
  ? `echo "ELANOUS_AUTHOR_ON_POD started host=$(hostname) at=$(date -u +%Y-%m-%dT%H:%M:%SZ)"; author_t0=$(date +%s); ${o.authorGrade === 'lite' || o.authorGrade === 'full' ? `ELANOUS_AUTHOR_GRADE=${o.authorGrade} ` : ''}elanous harness say --substrate local --json${quotedAsk}${quotedChildLlm} -- "$(cat /creds/feature)" > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; echo "ELANOUS_AUTHOR_ON_POD finished host=$(hostname) rc=$rc seconds=$(( $(date +%s) - author_t0 ))"; ${POD_RESULT_FLATTEN}`
1018
1027
  : goalPath
1019
1028
  ? `elanous harness ask ${goalPath} --json${quotedAsk}${quotedChildLlm} > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; ${POD_RESULT_FLATTEN}`
@@ -1074,12 +1083,12 @@ for ledger in "\${ELANOUS_STATE_DIR:-$HOME/.elanous}"/run-ledger/*.jsonl; do
1074
1083
  done
1075
1084
  if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
1076
1085
  'kill "$mem_sampler_pid" 2>/dev/null || true',
1077
- ...(o.appCredential ? ['kill "$gh_watch_pid" 2>/dev/null || true'] : []),
1086
+ ...(o.appCredential && !o.repairCommitOnly ? ['kill "$gh_watch_pid" 2>/dev/null || true'] : []),
1078
1087
  '[ -n "${usage_partial_pid:-}" ] && kill "$usage_partial_pid" 2>/dev/null || true',
1079
1088
  'wait "$mem_sampler_pid" 2>/dev/null || true',
1080
1089
  '[ -n "${usage_partial_pid:-}" ] && wait "$usage_partial_pid" 2>/dev/null || true',
1081
1090
  'tail -n 1 /tmp/si.out',
1082
- podSalvageScript(),
1091
+ ...(o.repairCommitOnly ? [] : [podSalvageScript()]),
1083
1092
  'exit $rc',
1084
1093
  ].join('\n');
1085
1094
  return {
@@ -1108,6 +1117,7 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
1108
1117
  env: [
1109
1118
  ...(o.runId ? [{ name: 'ELANOUS_RUN_ID', value: o.runId }] : []),
1110
1119
  ...(o.parentRunId ? [{ name: 'ELANOUS_PARENT_RUN_ID', value: o.parentRunId }] : []),
1120
+ ...(o.workIdentity?.taskCard ? [{ name: 'ELANOUS_TASK_CARD_ID', value: o.workIdentity.taskCard }] : []),
1111
1121
  { name: 'ELANOUS_SUBSTRATE', value: 'pod' },
1112
1122
  ...Object.entries(controlInboxEnv(POD_CONTROL_INBOX_DIR)).map(([name, value]) => ({ name, value })),
1113
1123
  // ⭐ 런 출처(🅣 RFC run-origin §A3 · #20457/#20468 칸 이름) — Pod 는 자기 elanous_id 를 쓰지 않고 «띄운 감독기»의 hostId 를 물려받는다.
@@ -1148,6 +1158,9 @@ function defaultGhComment(prNumber: number, body: string): void {
1148
1158
  }
1149
1159
 
1150
1160
  export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplementJobSpawn {
1161
+ if (options.repairCommitOnly && (options.source?.kind !== 'pr' || options.source.number !== options.repairCommitOnly.pr || !/^[0-9a-f]{40}$/.test(options.repairCommitOnly.baseHead))) {
1162
+ throw new Error('repair commit-only requires a matching PR source and exact head');
1163
+ }
1151
1164
  const baseKubectl = options.kubectl ?? defaultKubectl;
1152
1165
  const sleep = options.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));
1153
1166
  const namespace = options.namespace ?? 'elanous-test';
@@ -1159,6 +1172,9 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1159
1172
  if (hostMirror && !isAbsolute(hostMirror)) throw new Error('pod.hostMirror must be an absolute directory path');
1160
1173
  const hostSupervised = options.hostSupervised ?? true;
1161
1174
  return (input) => {
1175
+ if (options.repairCommitOnly && (input.autoMerge || input.openPr || input.base !== `pr-${options.repairCommitOnly.pr}`)) {
1176
+ throw new Error('repair commit-only refuses auto merge, new PR, or a different base');
1177
+ }
1162
1178
  const name = podJobName(input.spaceId);
1163
1179
  let parentGoal: string | undefined;
1164
1180
  let parentGoalUnreadable = false;
@@ -1211,6 +1227,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1211
1227
  const childRunId = mintRunId();
1212
1228
  const executionKey = createHash('sha256').update(JSON.stringify({ spaceId: input.spaceId, feature: input.feature, base: input.base, autoMerge: input.autoMerge, autoReview: input.autoReview, openPr: input.openPr, draft: input.draft })).digest('hex');
1213
1229
  const parentRunId = env.ELANOUS_RUN_ID?.trim();
1230
+ if (options.repairCommitOnly && !parentRunId) throw new Error('repair commit-only requires a parent run id');
1214
1231
  // Standalone callers journal retries without altering the existing Pod manifest.
1215
1232
  const ledgerRunId = parentRunId || `run-${executionKey.slice(0, 8)}-${executionKey.slice(8, 12)}-${executionKey.slice(12, 16)}-${executionKey.slice(16, 20)}-${executionKey.slice(20, 32)}`;
1216
1233
  const address = `self-impl:${input.spaceId}`;
@@ -1236,7 +1253,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1236
1253
  ...(input.autoReview ? ['--auto-review'] : []),
1237
1254
  // ⭐ 계약이 미래 노드를 바꾼다(첫 규칙): Pod 는 끝나면 사라지므로 완료 하한 = PR. 작업 트리로만 끝나면 결과가 Pod 와 함께 없어진다.
1238
1255
  // ⚠️ 임시 안전망(🅢 2026-09-26) — 🅣 의 «종결 하한 오버레이»(그래프 쪽)가 서면 이 줄을 지우고 그것으로 바꾼다(채널 합의).
1239
- ...(input.openPr || (!input.autoMerge && completionFloorFor({ substrate: 'pod' }) === 'pr') ? ['--open-pr'] : []),
1256
+ ...(!options.repairCommitOnly && (input.openPr || (!input.autoMerge && completionFloorFor({ substrate: 'pod' }) === 'pr')) ? ['--open-pr'] : []),
1240
1257
  ...(input.draft === false ? ['--no-draft'] : []),
1241
1258
  ...(hostSupervised ? ['--no-supervise'] : []),
1242
1259
  ...(options.extraArgs ?? []),
@@ -1326,7 +1343,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1326
1343
  if (unfitDone) return unfitDone;
1327
1344
  // HARNESS-FULL-GRAPH — Pod 입장(입장 관문 ⊕ 멤버 슬롯)은 그래프 노드 pool-admit 이다.
1328
1345
  // 2판: decompose →(substrate=pod) pool-admit 간선은 «여기»(Pod 입장 직전 · 실제 단계 경계)에서 남긴다.
1329
- routeJourneyEdge('decompose', 'pod', { substrate: 'pod' });
1346
+ if (options.repairCommitOnly) routeJourneyEdge('dispatch', 'pr-source', { source: 'pr', substrate: 'pod' });
1347
+ else routeJourneyEdge('decompose', 'pod', { substrate: 'pod' });
1330
1348
  enterJourneyNode('pool-admit', { provenance: 'self-implement-pod', data: { spaceId: input.spaceId } });
1331
1349
  // ⛔ 감싸는 함수(async 래퍼)를 쓰지 않는다 — 마이크로태스크가 늘면 Job apply 순서가 밀린다(시험이 잡았다). 같은 자리 try/catch 로.
1332
1350
  const podAdmitError = (error: unknown): void => {
@@ -1353,6 +1371,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1353
1371
  try {
1354
1372
  if (!context) return { exitCode: 1, output: '', error: { code: 'pod-context', message: 'Pod Job context를 확인할 수 없다' } };
1355
1373
  const cleanupSecret = () => { kubectl(['-n', namespace, 'delete', 'secret', `${name}-creds`, '--ignore-not-found']); };
1374
+ let repairResultAnnounced = false;
1375
+ let repairArtifactReady = false;
1356
1376
  const collectFullLogs = (unavailableEvent: string, replace?: ReadonlySet<string>, onEvent?: (event: string, data: Record<string, unknown>) => void, childId?: string): { status: 'ok' | 'unavailable' | 'incomplete'; childLedgerIncomplete: boolean; samples: ReturnType<typeof parseMemSamples> } => {
1357
1377
  let full: ReturnType<Kubectl>;
1358
1378
  try {
@@ -1383,6 +1403,20 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1383
1403
  collectPodArtifacts(full.stdout, { dir: join(effectiveInstanceRoot(), 'pod-artifacts'), job: name, log: record });
1384
1404
  }
1385
1405
  catch (error) { record('self-implement.pod', 'artifact-collect-incomplete', { job: name, reason: error instanceof Error ? error.message : String(error) }); }
1406
+ if (options.repairCommitOnly) {
1407
+ const artifactDir = join(effectiveInstanceRoot(), 'pod-artifacts', name, 'repair');
1408
+ try {
1409
+ const result = readPodRepairArtifact(artifactDir, { ...options.repairCommitOnly, runId: parentRunId });
1410
+ repairArtifactReady = true;
1411
+ if (!repairResultAnnounced) {
1412
+ console.log(`ELANOUS_POD_REPAIR_RESULT ${JSON.stringify({ job: name, artifactDir, pr: result.pr, baseHead: result.baseHead, runId: result.runId, commit: result.commit, bundleSha256: result.bundleSha256 })}`);
1413
+ repairResultAnnounced = true;
1414
+ }
1415
+ } catch (error) {
1416
+ repairArtifactReady = false;
1417
+ record('self-implement.pod', 'repair-artifact-incomplete', { job: name, reason: error instanceof Error ? error.message : String(error) });
1418
+ }
1419
+ }
1386
1420
  return { status: ledgerIncomplete || artifactIncomplete ? 'incomplete' : 'ok', childLedgerIncomplete, samples: parseMemSamples(full.stdout) };
1387
1421
  };
1388
1422
  let retryAccount: string | undefined;
@@ -1838,7 +1872,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1838
1872
  slotId: env.ELANOUS_POD_SLOT_ID,
1839
1873
  work: !goalDoc && env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? 'authoring' : (oomRetried ? retryTier : memoryTier) === 'lite' ? 'lite' : 'harness',
1840
1874
  };
1841
- const job = podJobManifest({ name, namespace, image: jobImage, workIdentity, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), args: jobArgs, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(goalDoc ? { goalDoc } : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? { authorSentence: true, ...(env.ELANOUS_POD_AUTHOR_GRADE === 'lite' || env.ELANOUS_POD_AUTHOR_GRADE === 'full' ? { authorGrade: env.ELANOUS_POD_AUTHOR_GRADE } : {}) } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, memoryRequest, imageCommit: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}), execution: { key: executionKey, attempt: oomRetried ? 2 : 1 }, hostLeaseAdmitted: !!releaseAdmission, ...(reviewerCredential ? { reviewerCodexAuth: true } : {}), ...(bedrockSpec ? { bedrock: bedrockSpec } : {}), ...(zaiSpec ? { zai: zaiSpec } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
1875
+ const job = podJobManifest({ name, namespace, image: jobImage, workIdentity, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(options.repairCommitOnly ? { repairCommitOnly: options.repairCommitOnly } : {}), ...(hostMirror ? { hostMirror } : {}), args: jobArgs, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(goalDoc ? { goalDoc } : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? { authorSentence: true, ...(env.ELANOUS_POD_AUTHOR_GRADE === 'lite' || env.ELANOUS_POD_AUTHOR_GRADE === 'full' ? { authorGrade: env.ELANOUS_POD_AUTHOR_GRADE } : {}) } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, memoryRequest, imageCommit: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}), execution: { key: executionKey, attempt: oomRetried ? 2 : 1 }, hostLeaseAdmitted: !!releaseAdmission, ...(reviewerCredential ? { reviewerCodexAuth: true } : {}), ...(bedrockSpec ? { bedrock: bedrockSpec } : {}), ...(zaiSpec ? { zai: zaiSpec } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
1842
1876
  const carriedBranch = goalDoc ? 'goal-doc' as const : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? 'author' as const : undefined;
1843
1877
  const carriedChildLlm = carriedBranch ? podDelegatedChildLlm(jobArgs) : undefined;
1844
1878
  const a = kubectl(['apply', '-f', '-'], JSON.stringify(job));
@@ -2046,6 +2080,9 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2046
2080
  }
2047
2081
  // Pod 안 경로는 호스트에서 쓸 수 없다.
2048
2082
  let disposition = parsed ? { ...parsed, worktreePath: undefined } : undefined;
2083
+ if (options.repairCommitOnly && state === 'complete' && !repairArtifactReady) {
2084
+ disposition = { ...disposition, worktreePath: undefined, ok: false, stage: 'repair-artifact-missing', detail: 'Pod repair result or bundle was not collected' };
2085
+ }
2049
2086
  if (input.autoMerge && state === 'complete' && disposition?.stage === 'merge-ready') {
2050
2087
  const prNumber = disposition.prNumber;
2051
2088
  const headCommit = disposition.checkedHeadCommit;
@@ -2096,13 +2133,35 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2096
2133
  debug.log('harness.host-regate', 'unmeasured', { pr: prNumber ?? null, files: [], os: process.platform, failedStep: 'pod-result' });
2097
2134
  regate = { passed: false, failures: [{ step: 'pod-result', detail: 'missing PR number or checked head commit' }], os: process.platform };
2098
2135
  } else {
2136
+ let hostRoot: string | undefined;
2137
+ let hostCwd: string | undefined;
2099
2138
  try {
2100
- const hostRoot = findGitDir(process.cwd())?.root;
2139
+ hostCwd = process.cwd();
2140
+ hostRoot = findGitDir(hostCwd)?.root;
2101
2141
  if (!hostRoot) throw new Error('host repository unavailable');
2102
2142
  regate = await (options.hostRegate ?? runHostRegate)({ prNumber, headCommit, repoRoot: hostRoot, ...(goalFile ? { goalFile } : {}), ...(taLandOwner ? { noMerge: true as const } : {}) });
2103
2143
  } catch (error) {
2104
2144
  debug.log('harness.host-regate', 'unmeasured', { pr: prNumber, files: [], os: process.platform, failedStep: 'host-regate' });
2105
- regate = { passed: false, failures: [{ step: 'host-regate', detail: error instanceof Error ? error.message : String(error) }], os: process.platform };
2145
+ regate = { passed: false, ...(taLandOwner ? { status: 'unmeasured' as const } : {}), failures: [{ step: 'host-regate', detail: error instanceof Error ? error.message : String(error) }], os: process.platform };
2146
+ }
2147
+ if (taLandOwner && !hostRoot && hostRepositoryUnavailable(regate)) {
2148
+ let recordedRoot: string | undefined;
2149
+ try {
2150
+ const recorded = loadRunLedger(ledgerRunId, ledgerDir)?.find((entry) => entry.event === 'start')?.data.targetRoot;
2151
+ if (typeof recorded === 'string' && isAbsolute(recorded)) {
2152
+ const foundRoot = findGitDir(recorded)?.root;
2153
+ // The recorded path must itself name the repository root, including symlink aliases.
2154
+ if (foundRoot && realpathSync(foundRoot) === realpathSync(recorded)) recordedRoot = foundRoot;
2155
+ }
2156
+ } catch { /* unreadable ledger or path leaves the original unmeasured result intact */ }
2157
+ if (recordedRoot) {
2158
+ try {
2159
+ regate = await (options.hostRegate ?? runHostRegate)({ prNumber, headCommit, repoRoot: recordedRoot, ...(goalFile ? { goalFile } : {}), noMerge: true });
2160
+ } catch (error) {
2161
+ regate = { passed: false, status: 'unmeasured', failures: [{ step: 'host-regate', detail: error instanceof Error ? error.message : String(error) }], os: process.platform };
2162
+ }
2163
+ debug.log('harness.host-regate', 'regate-retry', { pr: prNumber, from: hostCwd, to: recordedRoot, result: regate.status ?? (regate.passed ? 'passed' : 'failed') });
2164
+ }
2106
2165
  }
2107
2166
  }
2108
2167
  // runHostRegate comments on its own failures; these two paths never reached it.