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
@@ -0,0 +1,62 @@
1
+ /** Bind remote TA parents from the HQ ledger mirror before `rejudge --all` selects PR cards. */
2
+ import { dirname } from 'node:path';
3
+ import { debug } from '../debug/log.js';
4
+ import { loadRunLedger } from '../self-implement/run-ledger.js';
5
+ import { bindCardEvidence, podChildRunIds, readCardRunSnapshot, refreshCardRunBinding } from './card-evidence.js';
6
+ import { remoteLedgerMirrorDir } from './parent-host.js';
7
+ import { cardPrNumber, type TaskCard } from './task-hand.js';
8
+
9
+ /**
10
+ * A remote child PR can appear after the last HQ card refresh. A card without a PR is otherwise absent from
11
+ * `rejudgeCandidates`, so the normal rejudge tick never reaches `mirrorRemoteCardLedger` or the binding replay.
12
+ * Only an existing, launched TA-owned parent with a parent→child edge in its own host mirror is admitted. The
13
+ * launch registry (including foreign/test universes) and child-only ledgers are never scanned or adopted here.
14
+ */
15
+ export async function bindMirroredRemoteRejudgeCards(
16
+ tasks: Readonly<Record<string, TaskCard>>, statePath: string,
17
+ ): Promise<{ scanned: number; bound: number; skipped: number }> {
18
+ const root = dirname(statePath);
19
+ let scanned = 0;
20
+ let bound = 0;
21
+ let skipped = 0;
22
+ for (const card of Object.values(tasks)) {
23
+ if (card.status !== 'launched' || card.taLand !== true || !card.parentHost || !card.runId
24
+ || cardPrNumber(card) !== undefined || card.refSource === 'manual') continue;
25
+ scanned++;
26
+ const dir = remoteLedgerMirrorDir(root, card.parentHost.host);
27
+ try {
28
+ const snapshot = await readCardRunSnapshot(card, {
29
+ loadLedger: (id) => loadRunLedger(id, dir),
30
+ podFinishes: () => [],
31
+ });
32
+ const parent = snapshot.host;
33
+ const evidence = bindCardEvidence(card, snapshot);
34
+ const childRunId = evidence.childRunId;
35
+ const pr = evidence.pr;
36
+ const child = childRunId ? snapshot.children[childRunId] : null;
37
+ // A PR event alone is not a completed remote run. The rejudge candidate must have the same exact
38
+ // child PR and terminal status that remoteBoundRunTerminal will later require.
39
+ if (snapshot.readErrors?.length || !parent || !childRunId || !pr?.url || !child
40
+ || !podChildRunIds(parent, card.runId).includes(childRunId)
41
+ || !child.some((entry) => entry.event === 'pr-opened' && entry.data?.number === pr.number
42
+ && entry.data?.url === pr.url)
43
+ || !child.some((entry) => entry.event === 'run-status'
44
+ && ['completed', 'failed', 'cancelled'].includes(String(entry.data?.runStatus)))) { skipped++; continue; }
45
+ const result = await refreshCardRunBinding(card.id, statePath, {
46
+ // Reuse the verified snapshot so a second mirror read cannot silently change the selected PR.
47
+ loadLedger: (id) => id === card.runId ? [...parent] : snapshot.children[id] ? [...snapshot.children[id]!] : null,
48
+ // The remote mirror is the sole evidence for this prepass. Host logs can contain unrelated child receipts.
49
+ podFinishes: () => [],
50
+ });
51
+ if (result.card?.runId === card.runId && cardPrNumber(result.card) !== undefined
52
+ && result.card.runChildId && podChildRunIds(parent, card.runId).includes(result.card.runChildId)) bound++;
53
+ else skipped++;
54
+ } catch (error) {
55
+ skipped++;
56
+ try { debug.log('task-agent', 'remote-rejudge-bind-failed', {
57
+ card: card.id, runId: card.runId, host: card.parentHost.host, reason: error instanceof Error ? error.message : String(error),
58
+ }); } catch { /* observation must not stop the other cards */ }
59
+ }
60
+ }
61
+ return { scanned, bound, skipped };
62
+ }
@@ -0,0 +1,182 @@
1
+ /** Apply a Pod repair commit to the already bound PR branch after exact-head checks. */
2
+ import { createHash } from 'node:crypto';
3
+ import { readFileSync } from 'node:fs';
4
+ import { spawnSync } from 'node:child_process';
5
+
6
+ const SHA40 = /^[0-9a-f]{40}$/i;
7
+ const SHA256 = /^[0-9a-f]{64}$/i;
8
+ export const REPAIR_BUNDLE_REF = 'refs/elanous/repair/result';
9
+
10
+ export interface RepairCommitResult {
11
+ pr: number;
12
+ baseHead: string;
13
+ /** Parent repair run binding; required by the HQ worker before CAS. */
14
+ runId?: string;
15
+ commit: string;
16
+ bundleSha256: string;
17
+ }
18
+
19
+ export interface RepairCasRequest {
20
+ repo: string;
21
+ pr: number;
22
+ expectedHead: string;
23
+ bundlePath: string;
24
+ result: RepairCommitResult;
25
+ /** A previous push succeeded. Confirmation may inspect the remote and PR, but must never push again. */
26
+ confirmationOnly?: boolean;
27
+ branch?: string;
28
+ /** Persist a single-use push intent after bundle/ancestry checks and before the push side effect. */
29
+ claimBeforePush?: () => boolean;
30
+ }
31
+
32
+ export interface RepairCasDeps {
33
+ git(cwd: string, args: string[]): { status: number | null; stdout: string; stderr: string };
34
+ prView(cwd: string, pr: number): { state: string; head: string; branch: string; crossRepository: boolean; headRepo: string } | null;
35
+ originRepo(cwd: string): string | null;
36
+ sleep?(ms: number): void;
37
+ }
38
+
39
+ export type RepairCasOutcome = { ok: true; fromHead: string; toHead: string; branch: string }
40
+ | { ok: false; reason: string; unconfirmed?: true };
41
+
42
+ const CONFIRM_DELAYS_MS = [0, 150, 350, 700] as const;
43
+ const sleep = (ms: number): void => { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); };
44
+
45
+ const defaultDeps: RepairCasDeps = {
46
+ git(cwd, args) {
47
+ const r = spawnSync('git', args, { cwd, encoding: 'utf8', timeout: 120_000 });
48
+ return { status: r.status, stdout: String(r.stdout ?? ''), stderr: String(r.stderr ?? '') };
49
+ },
50
+ prView(cwd, pr) {
51
+ const r = spawnSync('gh', ['pr', 'view', String(pr), '--json', 'state,headRefOid,headRefName,isCrossRepository,headRepository'], { cwd, encoding: 'utf8', timeout: 30_000 });
52
+ if (r.status !== 0) return null;
53
+ try {
54
+ const v = JSON.parse(String(r.stdout)) as Record<string, unknown>;
55
+ const headRepo = v.headRepository && typeof v.headRepository === 'object'
56
+ ? (v.headRepository as Record<string, unknown>).nameWithOwner : undefined;
57
+ if (typeof v.state !== 'string' || typeof v.headRefOid !== 'string' || typeof v.headRefName !== 'string' || typeof headRepo !== 'string'
58
+ || typeof v.isCrossRepository !== 'boolean') return null;
59
+ return { state: v.state, head: v.headRefOid, branch: v.headRefName, crossRepository: v.isCrossRepository, headRepo };
60
+ } catch { return null; }
61
+ },
62
+ originRepo(cwd) {
63
+ const r = spawnSync('git', ['remote', 'get-url', 'origin'], { cwd, encoding: 'utf8', timeout: 30_000 });
64
+ if (r.status !== 0) return null;
65
+ const url = String(r.stdout).trim();
66
+ const match = /^(?:git@github\.com:|ssh:\/\/git@github\.com\/|https:\/\/github\.com\/)([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+?)(?:\.git)?\/?$/.exec(url);
67
+ return match?.[1]?.toLowerCase() ?? null;
68
+ },
69
+ };
70
+
71
+ /** Bundle verification and fetch use an isolated ref: concurrent FETCH_HEAD writers cannot substitute another commit. */
72
+ export function applyRepairBundle(request: RepairCasRequest, deps: RepairCasDeps = defaultDeps): RepairCasOutcome {
73
+ const { repo, pr, expectedHead, bundlePath, result } = request;
74
+ const fail = (reason: string): RepairCasOutcome => ({ ok: false, reason });
75
+ if (!Number.isSafeInteger(pr) || pr < 1 || result.pr !== pr || !SHA40.test(expectedHead) || !SHA40.test(result.baseHead)
76
+ || !SHA40.test(result.commit) || !SHA256.test(result.bundleSha256) || result.baseHead.toLowerCase() !== expectedHead.toLowerCase()) {
77
+ return fail('repair result identity or head mismatch');
78
+ }
79
+ if (result.commit.toLowerCase() === expectedHead.toLowerCase()) return fail('repair produced no new commit');
80
+ const git = (args: string[]) => deps.git(repo, args);
81
+ const initial = deps.prView(repo, pr);
82
+ const originRepo = deps.originRepo(repo);
83
+ const pending = (reason: string): RepairCasOutcome => ({ ok: false, unconfirmed: true, reason });
84
+ if (request.confirmationOnly) {
85
+ if (!request.branch || git(['check-ref-format', '--branch', request.branch]).status !== 0) return fail('confirmed repair branch name invalid');
86
+ if (!originRepo) return pending('repair origin identity unavailable; PR head confirmation pending');
87
+ if (!initial) return pending('PR head unavailable; confirmation pending');
88
+ if (initial.state !== 'OPEN' || initial.crossRepository || initial.headRepo.toLowerCase() !== originRepo || initial.branch !== request.branch)
89
+ return pending('repair PR identity not yet stable; confirmation pending');
90
+ const remoteRef = `refs/heads/${request.branch}`;
91
+ const remote = git(['ls-remote', '--exit-code', 'origin', remoteRef]);
92
+ const [remoteHead, remoteName] = remote.stdout.trim().split('\t');
93
+ if (remote.status !== 0 || remoteName !== remoteRef || remoteHead?.toLowerCase() !== result.commit.toLowerCase())
94
+ return pending('repair remote head differs from pushed commit; confirmation pending');
95
+ if (initial.head.toLowerCase() !== result.commit.toLowerCase()) return pending('repair commit is on the remote branch; PR head confirmation pending');
96
+ return { ok: true, fromHead: expectedHead, toHead: result.commit, branch: request.branch };
97
+ }
98
+ let bytes: Buffer;
99
+ try { bytes = readFileSync(bundlePath); } catch { return fail('repair bundle unreadable'); }
100
+ if (createHash('sha256').update(bytes).digest('hex') !== result.bundleSha256.toLowerCase()) return fail('repair bundle sha256 mismatch');
101
+ if (!initial || !originRepo || initial.headRepo.toLowerCase() !== originRepo) return fail('PR head repository differs from git origin');
102
+ const alreadyApplied = initial?.head.toLowerCase() === result.commit.toLowerCase();
103
+ if (!initial || initial.state !== 'OPEN' || initial.crossRepository || (!alreadyApplied && initial.head.toLowerCase() !== expectedHead.toLowerCase()))
104
+ return fail('PR head CAS mismatch or PR unavailable');
105
+ if (!initial.branch || git(['check-ref-format', '--branch', initial.branch]).status !== 0) return fail('PR branch name invalid');
106
+ const initialRemoteRef = `refs/heads/${initial.branch}`;
107
+ const verifyImportedCommit = (): string | null => {
108
+ if (git(['cat-file', '-e', `${expectedHead}^{commit}`]).status !== 0 || git(['bundle', 'verify', bundlePath]).status !== 0)
109
+ return 'repair base or bundle unavailable';
110
+ const localRef = `refs/elanous/repair/import/${pr}-${expectedHead.slice(0, 12)}-${result.commit.slice(0, 12)}`;
111
+ if (git(['fetch', bundlePath, `${REPAIR_BUNDLE_REF}:${localRef}`]).status !== 0
112
+ || git(['rev-parse', localRef]).stdout.trim().toLowerCase() !== result.commit.toLowerCase()
113
+ || git(['merge-base', '--is-ancestor', expectedHead, result.commit]).status !== 0)
114
+ return 'repair commit verification failed';
115
+ return null;
116
+ };
117
+ // A successful push can be visible on origin before gh pr view catches up. Never issue a
118
+ // second leased push for that same commit while the PR API is still reporting the old head.
119
+ if (!alreadyApplied) {
120
+ const remote = git(['ls-remote', '--exit-code', 'origin', initialRemoteRef]);
121
+ const [remoteHead, remoteName] = remote.stdout.trim().split('\t');
122
+ if (remote.status === 0 && remoteName === initialRemoteRef && remoteHead?.toLowerCase() === result.commit.toLowerCase()) {
123
+ const invalid = verifyImportedCommit();
124
+ return invalid ? fail(invalid) : pending('repair commit is on the remote branch; PR head confirmation pending');
125
+ }
126
+ }
127
+ if (alreadyApplied) {
128
+ // A prior process may have pushed and died before recording the card result. Recheck the artifact
129
+ // and exact remote ref before returning the same success; never push a second time.
130
+ const advertised = git(['ls-remote', '--exit-code', 'origin', initialRemoteRef]);
131
+ const [remoteHead, remoteName] = advertised.stdout.trim().split('\t');
132
+ if (advertised.status !== 0 || remoteName !== initialRemoteRef || remoteHead?.toLowerCase() !== result.commit.toLowerCase()) return fail('already-applied PR head remote mismatch');
133
+ const invalid = verifyImportedCommit();
134
+ if (invalid) return fail(`already-applied ${invalid}`);
135
+ return { ok: true, fromHead: expectedHead, toHead: result.commit, branch: initial.branch };
136
+ }
137
+ // The HQ checkout need not have fetched this PR before. Pin its source commit in an isolated ref;
138
+ // never use FETCH_HEAD, which another concurrent worker may replace.
139
+ const baseRef = `refs/elanous/repair/base/${pr}-${expectedHead.slice(0, 12)}`;
140
+ if (git(['fetch', 'origin', `refs/heads/${initial.branch}:${baseRef}`]).status !== 0
141
+ || git(['rev-parse', baseRef]).stdout.trim().toLowerCase() !== expectedHead.toLowerCase()) return fail('reviewed PR head fetch mismatch');
142
+ if (git(['bundle', 'verify', bundlePath]).status !== 0) return fail('repair bundle verification failed');
143
+ const localRef = `refs/elanous/repair/import/${pr}-${expectedHead.slice(0, 12)}-${result.commit.slice(0, 12)}`;
144
+ if (git(['fetch', bundlePath, `${REPAIR_BUNDLE_REF}:${localRef}`]).status !== 0) return fail('repair bundle fetch failed');
145
+ const imported = git(['rev-parse', localRef]).stdout.trim();
146
+ if (imported.toLowerCase() !== result.commit.toLowerCase()) return fail('repair bundle commit mismatch');
147
+ if (git(['merge-base', '--is-ancestor', expectedHead, result.commit]).status !== 0) return fail('repair commit is not a descendant of reviewed PR head');
148
+ if (expectedHead.toLowerCase() === result.commit.toLowerCase()) return fail('repair produced no new commit');
149
+ const view = deps.prView(repo, pr);
150
+ if (!view || view.headRepo.toLowerCase() !== originRepo || view.state !== 'OPEN' || view.crossRepository || view.head.toLowerCase() !== expectedHead.toLowerCase()
151
+ || view.branch !== initial.branch) return fail('PR head CAS mismatch or PR unavailable');
152
+ if (!view.branch || git(['check-ref-format', '--branch', view.branch]).status !== 0) return fail('PR branch name invalid');
153
+ const remoteRef = `refs/heads/${view.branch}`;
154
+ const advertised = git(['ls-remote', '--exit-code', 'origin', remoteRef]);
155
+ const [remoteHead, remoteName] = advertised.stdout.trim().split('\t');
156
+ if (advertised.status !== 0 || remoteName !== remoteRef || remoteHead?.toLowerCase() !== expectedHead.toLowerCase()) return fail('remote branch CAS mismatch');
157
+ if (request.claimBeforePush) {
158
+ try { if (!request.claimBeforePush()) return fail('repair push intent claim refused'); }
159
+ catch (error) { return fail(`repair push intent claim failed: ${error instanceof Error ? error.message : String(error)}`); }
160
+ }
161
+ // OP 2026-10-13 #1109, RFC §6a D3: the contract is "do not overwrite another commit".
162
+ // Two conditions enforce it: result.commit descends from expectedHead (checked above), and
163
+ // the exact-head lease rejects any intervening remote move. No '+' refspec is retained for clarity,
164
+ // but --force-with-lease can force non-FF even without '+': it is NOT a separate safety condition.
165
+ const push = git(['push', `--force-with-lease=${remoteRef}:${expectedHead}`, 'origin', `${result.commit}:${remoteRef}`]);
166
+ if (push.status !== 0) {
167
+ const reason = `exact-head fast-forward push result uncertain: ${(push.stderr || push.stdout).trim().slice(-200)}`;
168
+ return request.claimBeforePush ? pending(reason) : fail(`exact-head fast-forward push rejected: ${(push.stderr || push.stdout).trim().slice(-200)}`);
169
+ }
170
+ for (const delay of CONFIRM_DELAYS_MS) {
171
+ if (delay) (deps.sleep ?? sleep)(delay);
172
+ const after = deps.prView(repo, pr);
173
+ if (after?.state === 'OPEN' && !after.crossRepository && after.headRepo.toLowerCase() === originRepo
174
+ && after.head.toLowerCase() === result.commit.toLowerCase() && after.branch === view.branch) {
175
+ const remote = git(['ls-remote', '--exit-code', 'origin', remoteRef]);
176
+ const [remoteHead, remoteName] = remote.stdout.trim().split('\t');
177
+ if (remote.status === 0 && remoteName === remoteRef && remoteHead?.toLowerCase() === result.commit.toLowerCase())
178
+ return { ok: true, fromHead: expectedHead, toHead: result.commit, branch: view.branch };
179
+ }
180
+ }
181
+ return { ok: false, unconfirmed: true, reason: 'push succeeded; PR head confirmation pending' };
182
+ }
@@ -0,0 +1,275 @@
1
+ /** HQ same-PR repair launch and result collection; the reviewed head is the sole source of truth. */
2
+ import { spawn } from 'node:child_process';
3
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import { dirname, join, resolve, sep } from 'node:path';
5
+ import { mintRunId } from '../harness/harness-space.js';
6
+ import { spawnElanousSync } from '../instance/spawn-elanous.js';
7
+ import { passJourneyNode } from '../self-dev/graph-journey-nodes.js';
8
+ import { recordJourneyEdge } from '../self-dev/graph-journey-route.js';
9
+ import { checklistHistory, listChecklist, parseOwner } from '../release-loop/checklist.js';
10
+ import { applyRepairBundle, type RepairCasOutcome, type RepairCasRequest } from './repair-branch-cas.js';
11
+ import { repairNextMove } from './repair-next-move.js';
12
+ import { parseRepairWorkerResult } from './repair-worker.js';
13
+ import { TASK_SEATS, readTaskCard, updateTaskCard, type TaskCard, type TaskChildLlm, type TaskSeat } from './task-hand.js';
14
+
15
+ const WAIT_MS = 90 * 60_000;
16
+ const safe = (value: string) => value.replace(/[^A-Za-z0-9._-]/g, '_');
17
+
18
+ export interface RepairLaunchRequest {
19
+ card: string; pr: number; head: string; repo: string; resultPath: string;
20
+ goalFile: string; runId: string; model: TaskChildLlm; seat?: string;
21
+ }
22
+
23
+ export interface RepairCycleInput {
24
+ card: TaskCard; statePath: string; repo: string; pr: number; head: string; branch: string;
25
+ mustFix: readonly string[]; liveMoves: ReadonlySet<string>; now: Date;
26
+ readFile?: (path: string) => string;
27
+ request?: (input: RepairLaunchRequest) => Promise<void>;
28
+ apply?: (input: RepairCasRequest) => RepairCasOutcome;
29
+ notify?: (seat: string, body: string) => void;
30
+ observeLaunch?: (move: string, runId: string) => void;
31
+ resolveOwner?: (card: TaskCard) => RepairOwner;
32
+ }
33
+
34
+ export type RepairCycleOutcome = { decision: 'would-repair' | 'repair-requested' | 'repair-pending' | 'repair-applied' | 'repair-failed' | 'repair-stopped'; reason: string };
35
+ export type RepairOwner = { seat?: TaskSeat; source: string; reason?: string };
36
+
37
+ /** A missing card seat may use only a uniquely identified checklist cell's valid seat. */
38
+ export function resolveRepairOwner(card: TaskCard, deps: {
39
+ history?: (id: string) => readonly { version: string }[];
40
+ checklist?: (version: string) => { items: readonly { id: string; owner?: string }[] };
41
+ } = {}): RepairOwner {
42
+ if (card.seat) return { seat: card.seat, source: 'card' };
43
+ if (!card.checklistId) return { source: 'none', reason: 'checklist id missing' };
44
+ const history = deps.history ?? checklistHistory;
45
+ const checklist = deps.checklist ?? listChecklist;
46
+ let versions: string[];
47
+ try {
48
+ versions = card.checklistVersion ? [card.checklistVersion]
49
+ : [...new Set(history(card.checklistId).map((entry) => entry.version))];
50
+ } catch (error) { return { source: 'none', reason: `checklist history unavailable: ${String(error).slice(0, 160)}` }; }
51
+ if (!versions.length) return { source: 'none', reason: 'checklist version unavailable' };
52
+ let cells: Array<{ version: string; owner?: string }>;
53
+ try {
54
+ cells = versions.flatMap((version) => checklist(version).items
55
+ .filter((row) => row.id === card.checklistId)
56
+ .map((item) => ({ version, owner: item.owner })));
57
+ } catch (error) { return { source: 'none', reason: `checklist lookup unavailable: ${String(error).slice(0, 160)}` }; }
58
+ if (cells.length !== 1) return { source: 'none', reason: cells.length ? `checklist cell ambiguous: ${cells.length} matches` : 'checklist cell absent' };
59
+ const cell = cells[0]!;
60
+ if (!cell.owner) return { source: 'none', reason: `checklist owner missing: ${cell.version}/${card.checklistId}` };
61
+ try {
62
+ const seat = parseOwner(cell.owner).seat;
63
+ if (!(TASK_SEATS as readonly string[]).includes(seat)) return { source: 'none', reason: `checklist owner seat unknown: ${cell.version}/${card.checklistId}` };
64
+ return { seat: seat as TaskSeat, source: `checklist ${cell.version}/${card.checklistId}` };
65
+ } catch { return { source: 'none', reason: `checklist owner invalid: ${cell.version}/${card.checklistId}` }; }
66
+ }
67
+
68
+ export function alternateRepairModel(card: TaskCard): { from: string; to: TaskChildLlm } {
69
+ const last = card.repairs?.at(-1);
70
+ const from = last?.toModel ?? (card.childLlm ? `${card.childLlm.provider}/${card.childLlm.model}` : 'unknown');
71
+ const to = from.includes('openrouter/') || from.includes('glm')
72
+ ? { provider: 'openai-codex', model: 'gpt-6-sol' }
73
+ : { provider: 'openrouter', model: 'openrouter/z-ai/glm-5.3' };
74
+ return { from, to };
75
+ }
76
+
77
+ export function repairResultPath(statePath: string, card: string, pr: number, head: string, number: number, runId: string): string {
78
+ return join(dirname(statePath), 'repair-results', `${safe(card)}-${pr}-${safe(head.slice(0, 12))}-${number}-${safe(runId)}.json`);
79
+ }
80
+
81
+ /** Spawn the same installed CLI in the same config universe; its hidden worker writes an atomic result file. */
82
+ export async function defaultRequestRepair(input: RepairLaunchRequest): Promise<void> {
83
+ const { spawnDetachedConfirmed, ELANOUS_CLI_ENTRY } = await import('../cli/tasks-cli.js');
84
+ const { getElanousConfigDirOverride } = await import('../elanous-config-dir.js');
85
+ const { effectiveInstanceRoot } = await import('../instance/resolve.js');
86
+ const configDir = getElanousConfigDirOverride() ?? effectiveInstanceRoot();
87
+ const args = [ELANOUS_CLI_ENTRY, '--config-dir', configDir, 'tasks', 'repair-run',
88
+ '--card', input.card, '--pr', String(input.pr), '--head', input.head, '--repo', input.repo,
89
+ '--out', input.resultPath, '--goal-file', input.goalFile, '--run-id', input.runId,
90
+ '--model-provider', input.model.provider, '--model-id', input.model.model,
91
+ ...(input.seat ? ['--seat', input.seat] : [])];
92
+ await spawnDetachedConfirmed(spawn as never, process.execPath, args, undefined, input.repo);
93
+ }
94
+
95
+ function defaultNotify(seat: string, body: string): void {
96
+ const result = spawnElanousSync(['msg', 'post', '--from', 'TA', '--to', seat, '--kind', 'incident', '--body', body],
97
+ { encoding: 'utf8', timeout: 15_000 });
98
+ if (result.status !== 0) throw new Error(`owner message failed: ${String(result.stderr || result.stdout).slice(-200)}`);
99
+ }
100
+
101
+ export async function runRepairCycle(input: RepairCycleInput): Promise<RepairCycleOutcome> {
102
+ const { card, statePath, pr, head, branch, repo, now } = input;
103
+ const read = input.readFile ?? ((path: string) => readFileSync(path, 'utf8'));
104
+ const boundPr = card.repairs?.[0]?.pr ?? (typeof card.pr === 'number' ? card.pr : card.pr?.number);
105
+ if (boundPr !== undefined && boundPr !== pr) {
106
+ const reason = `repair source PR changed: card ${card.id} bound to #${boundPr}, received #${pr}; launch refused`;
107
+ updateTaskCard(statePath, card.id, (current) => current && current.status === 'launched'
108
+ ? { ...current, status: 'failed', history: [...(current.history ?? []), { at: now.toISOString(), event: 'repair-source-pr-changed', pr, head, detail: reason }] } : undefined);
109
+ return { decision: 'repair-stopped', reason };
110
+ }
111
+ const attempts = card.repairs ?? [];
112
+ const latest = attempts.at(-1);
113
+ if (latest?.status === 'unconfirmed') {
114
+ if (!latest.commit || !latest.bundleSha256 || !latest.bundlePath)
115
+ return { decision: 'repair-pending', reason: `repair run ${latest.runId} confirmation evidence missing; no repush` };
116
+ const applied = (input.apply ?? applyRepairBundle)({ repo, pr, expectedHead: latest.head,
117
+ bundlePath: latest.bundlePath, result: { pr, baseHead: latest.head, runId: latest.runId,
118
+ commit: latest.commit, bundleSha256: latest.bundleSha256 }, confirmationOnly: true, branch: latest.branch });
119
+ if (!applied.ok && applied.unconfirmed) return { decision: 'repair-pending', reason: applied.reason };
120
+ const status = applied.ok ? 'applied' : 'failed';
121
+ try {
122
+ updateTaskCard(statePath, card.id, (current) => current?.repairs?.some((entry) => entry.runId === latest.runId && entry.status === 'unconfirmed' && entry.commit === latest.commit)
123
+ ? { ...current, repairs: current.repairs.map((entry) => entry.runId === latest.runId
124
+ ? { ...entry, status, detail: applied.ok ? `same PR #${pr} fast-forward ${latest.head.slice(0, 12)} → ${applied.toHead.slice(0, 12)}` : applied.reason } : entry),
125
+ history: [...(current.history ?? []), { at: now.toISOString(), event: 'repair-result', pr, head: latest.head,
126
+ runId: latest.runId, effect: status, detail: applied.ok ? applied.toHead : applied.reason }] } : undefined);
127
+ } catch (error) { return { decision: 'repair-pending', reason: `repair confirmation card write failed: ${String(error).slice(-200)}` }; }
128
+ const recorded = readTaskCard(card.id, statePath)?.repairs?.find((entry) => entry.runId === latest.runId)?.status === status;
129
+ return recorded ? { decision: applied.ok ? 'repair-applied' : 'repair-failed', reason: applied.ok ? `same PR #${pr} fast-forward ${latest.head.slice(0, 12)} → ${applied.toHead.slice(0, 12)}` : applied.reason }
130
+ : { decision: 'repair-pending', reason: 'repair confirmation card changed concurrently' };
131
+ }
132
+ if (latest?.status === 'requested') {
133
+ let text: string | null = null;
134
+ try { text = read(latest.resultPath); } catch { /* worker still running */ }
135
+ const result = text === null ? null : parseRepairWorkerResult(text, pr, latest.head, latest.runId);
136
+ if (!result && now.getTime() - Date.parse(latest.at) < WAIT_MS)
137
+ return { decision: 'repair-pending', reason: `repair run ${latest.runId} pending` };
138
+ let detail = result?.reason ?? 'repair worker produced no valid result before timeout';
139
+ let applied: RepairCasOutcome | null = null;
140
+ let claimedThisCycle = false;
141
+ if (result?.ok && result.artifactDir && result.result) {
142
+ const expectedRoot = resolve(dirname(statePath), 'pod-artifacts');
143
+ const artifactDir = resolve(result.artifactDir);
144
+ const inside = artifactDir === join(expectedRoot, result.job ?? '', 'repair') && artifactDir.startsWith(`${expectedRoot}${sep}`);
145
+ let artifact: unknown = null;
146
+ if (inside) try { artifact = JSON.parse(read(join(artifactDir, 'result.json'))); } catch { /* unreadable */ }
147
+ const a = artifact && typeof artifact === 'object' && !Array.isArray(artifact) ? artifact as Record<string, unknown> : null;
148
+ if (!inside) detail = 'repair artifact outside the HQ instance root';
149
+ else if (!a || a.runId !== latest.runId || a.runId !== result.result.runId || a.pr !== result.result.pr || a.baseHead !== result.result.baseHead
150
+ || a.commit !== result.result.commit || a.bundleSha256 !== result.result.bundleSha256) detail = 'repair result artifact differs from stdout marker';
151
+ else {
152
+ const bundlePath = join(artifactDir, 'commit.bundle');
153
+ let pushClaimed = false;
154
+ applied = (input.apply ?? applyRepairBundle)({ repo, pr, expectedHead: latest.head,
155
+ bundlePath, result: result.result,
156
+ claimBeforePush: () => {
157
+ let claimed = false;
158
+ updateTaskCard(statePath, card.id, (current) => current?.repairs?.some((entry) => entry.runId === latest.runId && entry.status === 'requested')
159
+ ? (claimed = true, { ...current, repairs: current.repairs.map((entry) => entry.runId === latest.runId
160
+ ? { ...entry, status: 'unconfirmed' as const, commit: result.result!.commit,
161
+ bundleSha256: result.result!.bundleSha256, bundlePath, detail: 'verified repair push intent recorded' } : entry) }) : undefined);
162
+ pushClaimed = claimed;
163
+ return claimed;
164
+ } });
165
+ detail = applied.ok ? `same PR #${pr} fast-forward ${latest.head.slice(0, 12)} → ${applied.toHead.slice(0, 12)}` : applied.reason;
166
+ if (!applied.ok && applied.reason.startsWith('repair push intent claim'))
167
+ return { decision: 'repair-pending', reason: detail };
168
+ if (!pushClaimed && readTaskCard(card.id, statePath)?.repairs?.some((entry) => entry.runId === latest.runId && entry.status === 'unconfirmed'))
169
+ return { decision: 'repair-pending', reason: `repair run ${latest.runId} already has another collector's push intent` };
170
+ claimedThisCycle = pushClaimed;
171
+ }
172
+ }
173
+ const status = applied?.ok ? 'applied' : applied?.unconfirmed ? 'unconfirmed' : 'failed';
174
+ try {
175
+ updateTaskCard(statePath, card.id, (current) => current?.repairs?.some((entry) => entry.runId === latest.runId
176
+ && (entry.status === 'requested' || (claimedThisCycle && entry.status === 'unconfirmed' && entry.commit === result?.result?.commit)))
177
+ ? { ...current, repairs: current.repairs.map((entry) => entry.runId === latest.runId
178
+ ? { ...entry, status, detail, ...(result?.result && status === 'unconfirmed' ? { commit: result.result.commit,
179
+ bundleSha256: result.result.bundleSha256, bundlePath: join(resolve(result.artifactDir!), 'commit.bundle') } : {}),
180
+ ...(applied?.ok ? { commit: applied.toHead } : {}) } : entry),
181
+ history: [...(current.history ?? []), { at: now.toISOString(), event: 'repair-result', pr, head: latest.head,
182
+ runId: latest.runId, effect: status, detail }] } : undefined);
183
+ } catch (error) { return { decision: 'repair-pending', reason: `repair card write failed after CAS: ${String(error).slice(-200)}` }; }
184
+ const recorded = readTaskCard(card.id, statePath)?.repairs?.find((entry) => entry.runId === latest.runId)?.status === status;
185
+ return recorded ? { decision: applied?.ok ? 'repair-applied' : applied?.unconfirmed ? 'repair-pending' : 'repair-failed', reason: detail }
186
+ : { decision: 'repair-pending', reason: 'repair result card changed concurrently' };
187
+ }
188
+ if (latest?.status === 'applied' && latest.head.toLowerCase() === head.toLowerCase())
189
+ return { decision: 'repair-pending', reason: `repair pushed; await PR head ${latest.commit?.slice(0, 12) ?? '?'}` };
190
+ const judged = repairNextMove({ pr, head, branch, mustFix: input.mustFix,
191
+ priorNarrowAttempts: attempts.filter((entry) => entry.move === 'narrow-relaunch' || entry.move === 'salvage-relaunch').length,
192
+ priorAlternativeAttempts: attempts.filter((entry) => entry.move === 'alternative').length });
193
+ if (judged.kind === 'stop') {
194
+ if (attempts.length >= 2) {
195
+ const detail = `수리 수렴 실패: PR #${pr} head ${head.slice(0, 12)} · ${attempts.length}회 재발사 · ${judged.reason}`;
196
+ updateTaskCard(statePath, card.id, (current) => {
197
+ if (!current || current.status !== 'launched' || (current.repairs ?? []).filter((entry) => entry.pr === pr).length < 2) return undefined;
198
+ return { ...current, status: 'failed', history: [...(current.history ?? []),
199
+ { at: now.toISOString(), event: 'repair-convergence-failed', pr, head, detail }] };
200
+ });
201
+ const current = readTaskCard(card.id, statePath);
202
+ const exhausted = current?.history?.some((entry) => entry.event === 'repair-convergence-failed' && entry.pr === pr);
203
+ const notified = current?.history?.some((entry) => entry.event === 'repair-owner-notified' && entry.pr === pr);
204
+ if (exhausted && !notified && current) {
205
+ const owner = (input.resolveOwner ?? resolveRepairOwner)(current);
206
+ if (!owner.seat) {
207
+ const reason = `repair exhaustion owner unresolved: ${owner.reason ?? 'owner unresolved'}; notification refused`;
208
+ updateTaskCard(statePath, card.id, (latestCard) => latestCard && !latestCard.history?.some((entry) => entry.event === 'repair-owner-missing' && entry.pr === pr)
209
+ ? { ...latestCard, history: [...(latestCard.history ?? []), { at: now.toISOString(), event: 'repair-owner-missing', pr, head, detail: reason }] } : undefined);
210
+ return { decision: 'repair-stopped', reason };
211
+ }
212
+ try {
213
+ (input.notify ?? defaultNotify)(owner.seat, `${detail} · card ${card.id} · owner ${owner.seat} via ${owner.source}`);
214
+ updateTaskCard(statePath, card.id, (latestCard) => latestCard && !latestCard.history?.some((entry) => entry.event === 'repair-owner-notified' && entry.pr === pr)
215
+ ? { ...latestCard, history: [...(latestCard.history ?? []), { at: now.toISOString(), event: 'repair-owner-notified', pr, head, detail }] } : undefined);
216
+ } catch (error) {
217
+ const reason = error instanceof Error ? error.message : String(error);
218
+ updateTaskCard(statePath, card.id, (latestCard) => latestCard
219
+ ? { ...latestCard, history: [...(latestCard.history ?? []), { at: now.toISOString(), event: 'repair-owner-notify-failed', pr, head, detail: reason }] } : undefined);
220
+ }
221
+ }
222
+ }
223
+ return { decision: 'repair-stopped', reason: judged.reason };
224
+ }
225
+ if (!input.liveMoves.has(judged.judgement.move)) return { decision: 'would-repair', reason: `${judged.judgement.move} via graph next-move → dispatch (shadow)` };
226
+ const owner = (input.resolveOwner ?? resolveRepairOwner)(card);
227
+ if (!owner.seat) {
228
+ const reason = `repair owner seat missing: card ${card.id}; ${owner.reason ?? 'owner unresolved'}; launch refused`;
229
+ updateTaskCard(statePath, card.id, (current) => current && current.status === 'launched'
230
+ ? { ...current, status: 'failed', history: [...(current.history ?? []), { at: now.toISOString(), event: 'repair-owner-missing', pr, head, detail: reason }] } : undefined);
231
+ return { decision: 'repair-stopped', reason };
232
+ }
233
+ const next = attempts.length + 1;
234
+ const runId = mintRunId();
235
+ const { from, to } = alternateRepairModel(card);
236
+ const resultPath = repairResultPath(statePath, card.id, pr, head, next, runId);
237
+ const goalFile = resultPath.replace(/\.json$/, '.goal.txt');
238
+ const goal = `기존 PR #${pr}의 리뷰 때 head ${head}를 같은 가지에서 수리한다. 새 PR을 열지 말고 아래 must-fix 원문만 고쳐 commit-only 결과를 반환한다.\n\n${input.mustFix.map((item, i) => `${i + 1}. ${item}`).join('\n')}`;
239
+ mkdirSync(dirname(goalFile), { recursive: true });
240
+ writeFileSync(goalFile, goal);
241
+ let claimed = false;
242
+ updateTaskCard(statePath, card.id, (current) => {
243
+ if (!current || current.status !== 'launched' || (current.repairs ?? []).length !== (card.repairs ?? []).length) return undefined;
244
+ claimed = true;
245
+ return { ...current, repairs: [...(current.repairs ?? []), { pr, head, branch, at: now.toISOString(),
246
+ move: judged.judgement.move, runId, resultPath, fromModel: from, toModel: `${to.provider}/${to.model}`, status: 'requested' }],
247
+ history: [...(current.history ?? []), { at: now.toISOString(), event: 'repair-launched', pr, head, runId,
248
+ effect: judged.judgement.move, detail: `source pr:${pr} · owner ${owner.seat} via ${owner.source} · must-fix ${input.mustFix.length} · childLlm ${from} → ${to.provider}/${to.model}` }] };
249
+ });
250
+ if (!claimed) return { decision: 'repair-pending', reason: 'another tick claimed repair launch' };
251
+ try {
252
+ await (input.request ?? defaultRequestRepair)({ card: card.id, pr, head, repo, resultPath, goalFile,
253
+ runId, model: to, seat: owner.seat });
254
+ } catch (error) {
255
+ const reason = `repair launch failed: ${error instanceof Error ? error.message : String(error)}`;
256
+ updateTaskCard(statePath, card.id, (current) => current?.repairs?.some((entry) => entry.runId === runId && entry.status === 'requested')
257
+ ? { ...current, repairs: current.repairs.map((entry) => entry.runId === runId ? { ...entry, status: 'failed' as const, detail: reason } : entry) } : undefined);
258
+ return { decision: 'repair-failed', reason };
259
+ }
260
+ try {
261
+ if (input.observeLaunch) input.observeLaunch(judged.judgement.move, runId);
262
+ else {
263
+ passJourneyNode('next-move', { provenance: 'task-agent-repair', outcome: judged.judgement.move, runId,
264
+ data: { card: card.id, source: `pr:${pr}`, pr, head, branch, mustFix: input.mustFix } });
265
+ recordJourneyEdge(judged.route, 'dispatch');
266
+ }
267
+ } catch (error) {
268
+ const detail = `repair observation failed after worker launch: ${error instanceof Error ? error.message : String(error)}`;
269
+ try { updateTaskCard(statePath, card.id, (current) => current
270
+ ? { ...current, history: [...(current.history ?? []), { at: now.toISOString(), event: 'repair-observation-failed', pr, head, runId, detail }] } : undefined); }
271
+ catch { /* launch remains requested even if its diagnostic cannot be persisted */ }
272
+ return { decision: 'repair-requested', reason: `${judged.judgement.move} → dispatch · run ${runId} · ${detail}` };
273
+ }
274
+ return { decision: 'repair-requested', reason: `${judged.judgement.move} → dispatch · run ${runId} · ${from} → ${to.provider}/${to.model}` };
275
+ }
@@ -0,0 +1,31 @@
1
+ /** Existing TA retry judgement, routed through the declared recover → dispatch graph edge. */
2
+ import { judgeNextMove, type TaskJudgement } from './judge.js';
3
+ import { readJourneyEdge, type JourneyRoute } from '../self-dev/graph-journey-route.js';
4
+
5
+ export interface RepairNextMoveInput {
6
+ pr: number;
7
+ head: string;
8
+ branch: string;
9
+ mustFix: readonly string[];
10
+ priorNarrowAttempts: number;
11
+ priorAlternativeAttempts: number;
12
+ }
13
+
14
+ export type RepairNextMove = { kind: 'dispatch'; judgement: TaskJudgement; route: JourneyRoute; source: `pr:${number}` }
15
+ | { kind: 'stop'; judgement: TaskJudgement; route: JourneyRoute; reason: string };
16
+
17
+ export function repairNextMove(input: RepairNextMoveInput): RepairNextMove {
18
+ const judged = judgeNextMove({ stopReason: 'needs-human', pr: input.pr, prState: 'OPEN', review: 'fail', mustFix: input.mustFix,
19
+ failures: { 'narrow-relaunch': input.priorNarrowAttempts, alternative: input.priorAlternativeAttempts } });
20
+ const route = readJourneyEdge('next-move', judged.move, { move: judged.move, source: `pr:${input.pr}`, head: input.head, branch: input.branch });
21
+ if (!input.mustFix.length) return { kind: 'stop', judgement: judged, route, reason: 'review must-fix text unavailable' };
22
+ // RFC §6a D4 is a tighter cap for same-PR repair than the general retry vocabulary.
23
+ if (input.priorNarrowAttempts + input.priorAlternativeAttempts >= 2) {
24
+ return { kind: 'stop', judgement: judged, route, reason: 'repair convergence failed after two relaunches' };
25
+ }
26
+ if (route.source !== 'graph' || route.to !== 'dispatch') return { kind: 'stop', judgement: judged, route, reason: `graph next-move ${judged.move} routes to ${route.to ?? 'none'}` };
27
+ if (judged.move !== 'narrow-relaunch' && judged.move !== 'salvage-relaunch' && judged.move !== 'alternative') {
28
+ return { kind: 'stop', judgement: judged, route, reason: `next move ${judged.move} is not a repair launch` };
29
+ }
30
+ return { kind: 'dispatch', judgement: judged, route, source: `pr:${input.pr}` };
31
+ }
@@ -0,0 +1,24 @@
1
+ /** Recover the exact must-fix text from the captured self-review stdout. */
2
+ import { isNoneSentinelFinding } from '../agent-substrate/pr-reviewer.js';
3
+ import { createHash } from 'node:crypto';
4
+
5
+ const SHA40 = /^[0-9a-f]{40}$/i;
6
+ export const repairReviewDigest = (text: string): string => createHash('sha256').update(text).digest('hex');
7
+
8
+ export function repairMustFixFromReview(text: string, pr: number, head: string): string[] | null {
9
+ if (!Number.isSafeInteger(pr) || pr < 1 || !SHA40.test(head)) return null;
10
+ for (const line of text.split(/\r?\n/).reverse()) {
11
+ if (!line.trimStart().startsWith('{')) continue;
12
+ let value: unknown;
13
+ try { value = JSON.parse(line); } catch { continue; }
14
+ if (!value || typeof value !== 'object' || Array.isArray(value)) continue;
15
+ const v = value as Record<string, unknown>;
16
+ const foundPr = typeof v.pr === 'number' ? v.pr : typeof v.pr === 'string' ? Number(v.pr.replace(/^#/, '')) : NaN;
17
+ if (foundPr !== pr || typeof v.headCommit !== 'string' || v.headCommit.toLowerCase() !== head.toLowerCase()
18
+ || v.reviewed !== true || (v.verdict !== 'fail' && v.verdict !== 'pass' && v.verdict !== 'warn')
19
+ || typeof v.error === 'string' || !Array.isArray(v.mustFix)
20
+ || !v.mustFix.every((item) => typeof item === 'string')) return null;
21
+ return v.mustFix.filter((item): item is string => typeof item === 'string' && !isNoneSentinelFinding(item) && !!item.trim());
22
+ }
23
+ return null;
24
+ }