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,170 @@
1
+ /**
2
+ * GREEN-FROM-LEDGER, first slice: decide from a bound card or an explicit
3
+ * checklist evidence line. This module never writes a checklist or card.
4
+ */
5
+ import type { ChecklistItem } from '../release-loop/checklist.js';
6
+ import { debug } from '../debug/log.js';
7
+ import { cardPrNumber, type TaskCard } from './task-hand.js';
8
+
9
+ export type GreenSweepVerdict = 'green' | 'record-only' | 'skip';
10
+ export interface GreenSweepCandidate {
11
+ cell: string;
12
+ pr: number;
13
+ source: 'card' | 'evidence';
14
+ card?: TaskCard;
15
+ ambiguousAcrossCards?: boolean;
16
+ }
17
+ export interface GreenSweepPr {
18
+ number: number;
19
+ state: string;
20
+ mergeSha?: string;
21
+ url?: string;
22
+ }
23
+ export interface GreenSweepDecision {
24
+ cell: string;
25
+ pr: number;
26
+ mergeSha: string | null;
27
+ source: GreenSweepCandidate['source'];
28
+ verdict: GreenSweepVerdict;
29
+ reason: string;
30
+ evidence: string;
31
+ existingEvidence: string;
32
+ /** Preview only; a later live operation may persist this complete value. */
33
+ plannedEvidence: string;
34
+ }
35
+
36
+ /** PR-body mentions are intentionally absent from this parser. */
37
+ export function explicitEvidencePrs(evidence: string | undefined): number[] {
38
+ const numbers = new Set<number>();
39
+ for (const line of (evidence ?? '').split(/\r?\n/)) {
40
+ // A previous refusal is diagnostic text, never new landing evidence.
41
+ if (/^GREEN-FROM-LEDGER:/i.test(line) && !/· green ·/.test(line)) continue;
42
+ // A negated landing claim cannot establish this cell's PR ownership.
43
+ if (/미착지|미병합|예정|계획|아님|않음|안\s*함|보류|대기|실패|불가|아직|미완료|누락|못\s*함|(?:착지|병합)\s*전|\b(?:not\s+(?:yet\s+)?(?:merged|landed)|unmerged|pending|planned)\b/i.test(line)) continue;
44
+ // A PR number only counts when this cell's own evidence calls it landed.
45
+ for (const match of line.matchAll(/(?:착지|병합|merged|landed)\s*(?:PR\s*)?#(\d+)\b|(?:PR\s*)?#(\d+)\s*(?:착지|병합|merged|landed)(?=$|[^A-Za-z가-힣])/gi)) {
46
+ const number = Number(match[1] ?? match[2]);
47
+ if (Number.isSafeInteger(number) && number > 0) numbers.add(number);
48
+ }
49
+ for (const match of line.matchAll(/https?:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/(\d+)\b\s*(?:착지|병합|merged|landed)|(?:착지|병합|merged|landed)\s*https?:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/(\d+)\b/gi)) {
50
+ const number = Number(match[1] ?? match[2]);
51
+ if (Number.isSafeInteger(number) && number > 0) numbers.add(number);
52
+ }
53
+ }
54
+ return [...numbers];
55
+ }
56
+
57
+ export function greenSweepCandidates(item: ChecklistItem, version: string, cards: readonly TaskCard[]): GreenSweepCandidate[] {
58
+ if (item.status !== 'yellow' && item.status !== 'red') return [];
59
+ const candidates = new Map<number, GreenSweepCandidate>();
60
+ for (const number of explicitEvidencePrs(item.evidence)) candidates.set(number, { cell: item.id, pr: number, source: 'evidence' });
61
+ for (const card of cards) {
62
+ if (card.checklistId !== item.id || (card.checklistVersion && card.checklistVersion !== version)
63
+ || (card.completion ?? 'code-pr') !== 'code-pr') continue;
64
+ const pr = cardPrNumber(card);
65
+ if (!pr) continue;
66
+ // Multiple cards claiming one PR are ambiguous, even if one has a complete marker.
67
+ if (candidates.get(pr)?.source === 'card') candidates.set(pr, { cell: item.id, pr, source: 'card' });
68
+ else candidates.set(pr, { cell: item.id, pr, source: 'card', card });
69
+ }
70
+ return [...candidates.values()].map((candidate) => ({ ...candidate,
71
+ ambiguousAcrossCards: cards.filter((card) => cardPrNumber(card) === candidate.pr).length > 1,
72
+ })).sort((a, b) => a.pr - b.pr);
73
+ }
74
+
75
+ export function ambiguousGreenSweepPrs(items: readonly ChecklistItem[], version: string, cards: readonly TaskCard[]): Set<number> {
76
+ const claims = new Map<number, Set<string>>();
77
+ for (const item of items) {
78
+ const numbers = new Set(explicitEvidencePrs(item.evidence));
79
+ for (const card of cards) {
80
+ if (card.checklistId === item.id && (!card.checklistVersion || card.checklistVersion === version)
81
+ && (card.completion ?? 'code-pr') === 'code-pr') {
82
+ const pr = cardPrNumber(card);
83
+ if (pr) numbers.add(pr);
84
+ }
85
+ }
86
+ for (const pr of numbers) {
87
+ const cells = claims.get(pr) ?? new Set<string>();
88
+ cells.add(item.id);
89
+ claims.set(pr, cells);
90
+ }
91
+ }
92
+ return new Set([...claims].filter(([, cells]) => cells.size > 1).map(([pr]) => pr));
93
+ }
94
+
95
+ export function appendGreenSweepEvidence(existing: string | undefined, line: string): string {
96
+ const before = existing ?? '';
97
+ if (before.split(/\r?\n/).includes(line)) return before;
98
+ return `${before}${before && !before.endsWith('\n') ? '\n' : ''}${line}`;
99
+ }
100
+
101
+ /** A merge establishes ownership of a PR, not completion of a whole cell. */
102
+ export function decideGreenSweep(input: {
103
+ item: ChecklistItem;
104
+ candidate: GreenSweepCandidate;
105
+ candidateCount: number;
106
+ pr: GreenSweepPr | null;
107
+ ancestor: boolean | null;
108
+ hold: boolean | null;
109
+ repositoryMatches?: boolean | null;
110
+ ambiguousAcrossCells?: boolean;
111
+ }): GreenSweepDecision {
112
+ const { item, candidate, pr } = input;
113
+ const mergeSha = pr?.mergeSha && /^[a-f0-9]{40}$/i.test(pr.mergeSha) ? pr.mergeSha : null;
114
+ const result = (verdict: GreenSweepVerdict, reason: string): GreenSweepDecision => {
115
+ const evidence = `GREEN-FROM-LEDGER: PR #${candidate.pr}${mergeSha ? ` merged ${mergeSha}` : ''} · ${verdict} · ${reason}`;
116
+ return { cell: item.id, pr: candidate.pr, mergeSha, source: candidate.source, verdict, reason,
117
+ evidence, existingEvidence: item.evidence ?? '', plannedEvidence: appendGreenSweepEvidence(item.evidence, evidence) };
118
+ };
119
+ if (item.status !== 'yellow' && item.status !== 'red') return result('skip', 'cell-not-yellow');
120
+ if (item.status === 'red') return result('skip', 'red-cell-requires-owner');
121
+ if (!pr || pr.number !== candidate.pr) return result('skip', 'pr-unreadable');
122
+ if (input.repositoryMatches === false) return result('skip', 'repository-mismatch');
123
+ if (input.repositoryMatches !== true) return result('record-only', 'repository-unreadable');
124
+ if (pr.state !== 'MERGED') return result('skip', `pr-${pr.state.toLowerCase()}`);
125
+ if (!mergeSha) return result('skip', 'merge-sha-unreadable');
126
+ if (input.ancestor === null) return result('record-only', 'origin-main-ref-unverified');
127
+ if (!input.ancestor) return result('skip', 'merge-sha-not-on-origin-main');
128
+ const explicitUrls = [...(item.evidence ?? '').matchAll(/https?:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/\d+\b/gi)]
129
+ .map((match) => match[0]);
130
+ if (explicitUrls.some((url) => /\/pull\/(\d+)\b/.exec(url)?.[1] === String(candidate.pr)
131
+ && url.replace(/\/+$/, '').toLowerCase() !== (pr.url ?? '').replace(/\/+$/, '').toLowerCase()))
132
+ return result('skip', 'evidence-pr-url-mismatch');
133
+ if (/보류|중단|정지|\bhold\b/i.test(`${item.title}\n${item.evidence ?? ''}`))
134
+ return result('skip', 'cell-stop-evidence');
135
+ if (input.hold === null) return result('record-only', 'hold-state-unreadable');
136
+ if (input.hold) return result('skip', 'traffic-hold');
137
+ if (input.candidateCount !== 1) return result('record-only', 'multiple-piece-prs');
138
+ if (input.ambiguousAcrossCells) return result('record-only', 'ambiguous-pr-across-cells');
139
+ if (candidate.ambiguousAcrossCards) return result('record-only', 'ambiguous-pr-across-cards');
140
+ if (/남은|나머지|후속|조각|분할|미착지|미병합|예정|계획|(?:착지|병합)\s*(?:실패|불가|아직|미완료|누락|못\s*함)|PR\s*#\d+\s*누락|추가\s*(?:작업|실물|검증|배선|조각|PR|필요)|(?:착지|실물|판정|검증|승인|운영)\s*대기|대기\s*(?:중|필요)|[1-9]차|(?:작업|검증|확인|운영|수리|배선)\s*필요|(?:일부|부분)(?:\s*범위(?:만)?)?\s*완료|잔여|미충족|미달|미완료|대체\s*후보/i.test(`${item.title}\n${item.evidence ?? ''}`))
141
+ return result('record-only', 'remaining-or-multiple-piece-evidence');
142
+ const verdictLines = [...(item.evidence ?? '').matchAll(/(?:^|\n)\s*(?:판정|결론|상태)\s*:\s*([^\n]+)/gi)];
143
+ if (verdictLines.some((match) => !/^(?:green|완료|충족)(?:$|[\s·,.;])/i.test(match[1] ?? '')))
144
+ return result('record-only', 'conflicting-cell-verdict-evidence');
145
+ if (candidate.source === 'evidence') {
146
+ const fullCellProof = /(?:^|\n)\s*(?:판정|결론|상태)\s*:\s*(?:green|완료|충족)(?:$|[\s·,.;])/i;
147
+ return fullCellProof.test(item.evidence ?? '')
148
+ ? result('green', 'explicit-cell-completion-evidence')
149
+ : result('record-only', 'cell-completion-not-explicit');
150
+ }
151
+ const card = candidate.card;
152
+ if (!card) return result('record-only', 'ambiguous-card-binding');
153
+ if (!card.checklistVersion) return result('record-only', 'card-version-unbound');
154
+ if (card.status !== 'done') return result('record-only', 'card-not-done');
155
+ if (card.postLand && (card.postLand.outcome !== 'complete' || card.postLand.remaining.length > 0))
156
+ return result('record-only', 'card-post-land-incomplete');
157
+ if (card.history.some((event) => event.event === 'closed-by-hand'))
158
+ return result('record-only', 'card-stop-evidence');
159
+ if (typeof card.pr === 'object' && pr.url && card.pr.url
160
+ && card.pr.url.replace(/\/+$/, '') !== pr.url.replace(/\/+$/, ''))
161
+ return result('record-only', 'card-pr-url-mismatch');
162
+ if (card.prTerminal?.state === 'CLOSED') return result('record-only', 'card-terminal-conflict');
163
+ return result('green', card.postLand?.outcome === 'complete' ? 'bound-card-complete' : 'bound-card-merged');
164
+ }
165
+
166
+ /** Every candidate emits one structured decision, including refusals. */
167
+ export function observeGreenSweep(decision: GreenSweepDecision, log: typeof debug.log = debug.log.bind(debug)): void {
168
+ log('task-agent.green-sweep', decision.verdict,
169
+ { cell: decision.cell, pr: decision.pr, mergeSha: decision.mergeSha, reason: decision.reason });
170
+ }
@@ -24,7 +24,7 @@ const defaultGit: GitRun = (cwd, args) => {
24
24
 
25
25
  export interface LandWorktreeInput { card: string; pr: number; head: string; branch: string | undefined; repoCwd: string; statePath: string }
26
26
  export type LandWorktreeRemoval = { removed: boolean; reason?: string; /** 이 호출이 만든 가지를 지웠나 — 만든 가지가 없으면 null. */ branchRemoved?: boolean | null };
27
- export interface LandWorktree { cwd: string; cleanup: () => LandWorktreeRemoval }
27
+ export interface LandWorktree { cwd: string; cleanup: () => LandWorktreeRemoval; /** false means an existing owner's tree is borrowed, never removed. */ created?: boolean }
28
28
 
29
29
  export interface LandWorktreeDeps {
30
30
  git?: GitRun;
@@ -46,6 +46,14 @@ export function localHead(cwd: string, git: GitRun = defaultGit): string | null
46
46
  } catch { return null; }
47
47
  }
48
48
 
49
+ /** cwd 의 현재 가지 — detached·조회 실패는 null. */
50
+ export function localBranch(cwd: string, git: GitRun = defaultGit): string | null {
51
+ try {
52
+ const r = git(cwd, ['branch', '--show-current']);
53
+ return r.status === 0 && r.stdout.trim() ? r.stdout.trim() : null;
54
+ } catch { return null; }
55
+ }
56
+
49
57
  async function defaultProvideDeps(worktree: string, sourceRoot: string): Promise<void> {
50
58
  const { spawnSync } = await import('node:child_process');
51
59
  const { provideCheckoutDependencies } = await import('../self-implement/host-regate.js');
@@ -71,6 +79,24 @@ function registered(git: GitRun, repoCwd: string, path: string): boolean | null
71
79
  return list.stdout.split('\n').some((line) => line.startsWith('worktree ') && real(line.slice('worktree '.length).trim()) === want);
72
80
  }
73
81
 
82
+ /** A registered checkout of the exact PR branch. An unreadable/malformed registry is never treated as vacant. */
83
+ function branchOwner(git: GitRun, repoCwd: string, branch: string): { path: string | null; reason?: string } {
84
+ let list: ReturnType<GitRun>;
85
+ try { list = git(repoCwd, ['worktree', 'list', '--porcelain']); }
86
+ catch { return { path: null, reason: 'git worktree registry unreadable (owner unknown)' }; }
87
+ if (list.status !== 0 || !list.stdout.trim()) return { path: null, reason: 'git worktree registry unreadable (owner unknown)' };
88
+ const entries = list.stdout.trim().split(/\n\s*\n/);
89
+ const owners: string[] = [];
90
+ for (const entry of entries) {
91
+ const lines = entry.split('\n');
92
+ const pathLine = lines.find(line => line.startsWith('worktree '));
93
+ if (!pathLine?.slice(9).trim()) return { path: null, reason: 'git worktree registry malformed (owner unknown)' };
94
+ if (lines.some(line => line === `branch refs/heads/${branch}`)) owners.push(pathLine.slice(9).trim());
95
+ }
96
+ if (owners.length > 1) return { path: null, reason: `PR branch ${branch} has multiple registered owners (owner unknown)` };
97
+ return { path: owners[0] ?? null };
98
+ }
99
+
74
100
  /** 이 저장소의 git 공통 디렉터리(실경로) — 못 읽으면 null. */
75
101
  function commonDir(git: GitRun, repoCwd: string): string | null {
76
102
  const r = git(repoCwd, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
@@ -110,7 +136,7 @@ function removeWorktree(git: GitRun, repoCwd: string, path: string): { removed:
110
136
  }
111
137
 
112
138
  /** `created` — 트리를 만들었었나(모르면 null) · `removed` — 만들었다면 걷었나(만든 적 없거나 모르면 null). */
113
- export type LandWorktreeFailure = { reason: string; created: boolean | null; removed: boolean | null };
139
+ export type LandWorktreeFailure = { reason: string; created: boolean | null; removed: boolean | null; kind?: 'worktree-conflict'; ownerPath?: string };
114
140
 
115
141
  /** PR 머리 트리를 만든다 — 못 만들면 이유 ⊕ 만들었다 걷었는지(관측용). 던지지 않는다. */
116
142
  export async function createLandWorktree(input: LandWorktreeInput, deps: LandWorktreeDeps = {}): Promise<LandWorktree | LandWorktreeFailure> {
@@ -119,10 +145,15 @@ export async function createLandWorktree(input: LandWorktreeInput, deps: LandWor
119
145
  const head = input.head.toLowerCase();
120
146
  const branch = input.branch;
121
147
  const fail = (reason: string, created: boolean | null = false, removed: boolean | null = null): LandWorktreeFailure => ({ reason, created, removed });
148
+ const conflict = (reason: string, ownerPath?: string): LandWorktreeFailure => ({ reason: `worktree-conflict: ${reason}`, created: false, removed: null, kind: 'worktree-conflict', ...(ownerPath ? { ownerPath } : {}) });
122
149
  if (!branch) return fail('PR head branch name unknown');
123
150
  if (!/^[0-9a-f]{40}$/.test(head)) return fail('PR head is not a full sha');
124
151
  // 지난 시도가 남긴 같은 자리는 먼저 걷는다(같은 PR·머리 · 재사용하지 않는다 — 상태를 모르는 트리에서 착지하지 않게).
125
- if (existsSync(path) || registered(git, input.repoCwd, path) !== false) {
152
+ let atLandPath: boolean | null;
153
+ try { atLandPath = registered(git, input.repoCwd, path); }
154
+ catch { atLandPath = null; }
155
+ if (atLandPath === null) return conflict('git worktree list unreadable (owner unknown)');
156
+ if (existsSync(path) || atLandPath) {
126
157
  const stale = removeWorktree(git, input.repoCwd, path);
127
158
  if (!stale.removed) return fail(`stale land worktree could not be removed: ${stale.reason}`);
128
159
  }
@@ -135,9 +166,25 @@ export async function createLandWorktree(input: LandWorktreeInput, deps: LandWor
135
166
  return r.status === 0 ? r.stdout.trim().toLowerCase() : null;
136
167
  };
137
168
  const existingHead = branchHead();
138
- if (existingHead && existingHead !== head) return fail(`local branch ${branch} exists at ${existingHead.slice(0, 12)} (not the PR head) — not overwriting it`);
139
169
  const mine = commonDir(git, input.repoCwd);
140
170
  if (!mine) return fail('git common directory unreadable');
171
+ const owner = branchOwner(git, input.repoCwd, branch);
172
+ if (owner.reason) return conflict(owner.reason);
173
+ if (owner.path) {
174
+ const path = owner.path;
175
+ // The registry is only a lead: validate the checkout itself immediately before handing it to pr land.
176
+ try {
177
+ if (commonDir(git, path) !== mine) return conflict(`owner ${path} repository identity unreadable or different`, path);
178
+ const current = git(path, ['branch', '--show-current']);
179
+ if (current.status !== 0 || current.stdout.trim() !== branch) return conflict(`owner ${path} branch is ${current.stdout.trim() || 'unknown'} (expected ${branch})`, path);
180
+ const actualHead = localHead(path, git);
181
+ if (actualHead?.toLowerCase() !== head) return conflict(`owner ${path} HEAD ${actualHead?.slice(0, 12) ?? 'unknown'} ≠ reviewed ${head.slice(0, 12)}`, path);
182
+ const status = git(path, ['status', '--porcelain']);
183
+ if (status.status !== 0 || status.stdout.trim()) return conflict(`owner ${path} is ${status.status !== 0 ? 'status unreadable' : `dirty: ${status.stdout.trim().split('\n').slice(0, 3).join(' · ').slice(0, 160)}`}`, path);
184
+ } catch { return conflict(`owner ${path} verification failed`, path); }
185
+ return { cwd: path, created: false, cleanup: () => ({ removed: false, reason: `borrowed owner ${path} — left untouched` }) };
186
+ }
187
+ if (existingHead && existingHead !== head) return fail(`local branch ${branch} exists at ${existingHead.slice(0, 12)} (not the PR head) — not overwriting it`);
141
188
  try {
142
189
  mkdirSync(dirname(path), { recursive: true });
143
190
  // 표지를 먼저 — 등록 전에 죽어도 다음 시도가 «우리 잔여»임을 확인하고 걷을 수 있게.
@@ -192,7 +239,7 @@ export async function createLandWorktree(input: LandWorktreeInput, deps: LandWor
192
239
  } catch (error) {
193
240
  return abort(`land worktree check threw: ${(error instanceof Error ? error.message : String(error)).slice(-200)}`);
194
241
  }
195
- return { cwd: path, cleanup };
242
+ return { cwd: path, cleanup, created: true };
196
243
  }
197
244
 
198
245
  export function observeLandWorktree(data: { card: string; pr: number; head: string; created: boolean | null; removed: boolean | null; branchRemoved?: boolean | null; reason: string },
@@ -50,6 +50,7 @@ export function liveMoveEffect(input: Pick<LiveMoveHistoryInput, 'executorResult
50
50
  const result = input.result;
51
51
  if (!result) return LIVE_MOVE_EFFECT_UNKNOWN;
52
52
  if (!result.executed) return 'none — not executed';
53
+ if (result.kind === 'decision-card') return result.ok ? 'decision card raised' : 'raise failed';
53
54
  if (result.kind === 'review' && result.ok) return result.resultPath ? LIVE_REVIEW_EFFECT_PENDING : LIVE_REVIEW_EFFECT_REQUESTED;
54
55
  if (result.kind === 'land' && result.ok) return 'land reported ok by the executor (merge state not re-checked here) · merge sha not reported';
55
56
  if (result.kind === 'propose-green' && result.ok) return `green proposed · checklist ${checklistId ?? '-'} (checklist untouched)`;
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * - `review`: 수확 가능 런의 PR 에 elanous 리뷰를 «한 번» 요청한다 — PR 머리(sha)마다 한 번(카드 `reviewRequests` 이력).
5
5
  * 요청만 한다: 결과로 착지·재발사로 잇지 않는다(실행부 `executeNextAction` 의 review→land 사슬을 타지 않는다).
6
- * TA-REVIEW-RESULT-CAPTURE — 자식 stdout(`--json`)은 결과 파일로 받고, 회수(`review-result-capture.ts`)는 «다음» 판단 틱이 한다 —
6
+ * TA-REVIEW-RESULT-CAPTURE — 자식 stdout(`--json`)은 결과 파일로 받고, 자식 완료 뒤 해당 카드만 회수·재판정한다.
7
7
  * 회수한 pass 는 그 틱의 판단부·land 관문 입력(`selfReview`)이 될 뿐 land 판정 로직은 그대로다.
8
8
  * - `propose-green`: 카드에 green «제안»(`greenProposal`)만 적는다 — 체크리스트는 손대지 않는다.
9
9
  * - `propose-land`: 카드·현재 머리의 self review 통과(pass «또는» warn ⊕ must-fix 0 · TA-LAND-WARN-MUSTFIX0)·non-draft 를 확인하고 기존 `pr land` 를 머리 고정으로 한 번 부른다.
@@ -15,7 +15,7 @@
15
15
  * - 관측: 실행한 수마다 `debug.log('task-agent', 'live-move', {kind, card, ok, detail, live:true})`.
16
16
  */
17
17
  import { spawn, spawnSync } from 'node:child_process';
18
- import { createHash } from 'node:crypto';
18
+ import { createHash, randomUUID } from 'node:crypto';
19
19
  import { closeSync, mkdirSync, openSync, readFileSync, statSync } from 'node:fs';
20
20
  import { dirname, isAbsolute, resolve } from 'node:path';
21
21
  import { debug } from '../debug/log.js';
@@ -25,9 +25,9 @@ import type { DetachedSpawn } from '../cli/tasks-cli.js';
25
25
  import { LIVE_MOVE_HISTORY_EVENT, taskAgentStatePath, updateTaskCard, type TaskCard } from './task-hand.js';
26
26
  import { LIVE_RETRY_MOVES } from './judge.js';
27
27
  import { decisionCardGrounds, decisionRaiseArgs, type DecisionCardEvidence, type NextAction } from './actions.js';
28
- import { reviewResultPath } from './review-result-capture.js';
28
+ import { pendingReviewResults, reviewResultPath, TA_REVIEW_COMPLETION_CARD_ENV, TA_REVIEW_COMPLETION_RESULT_ENV, TA_REVIEW_COMPLETION_STATE_ENV } from './review-result-capture.js';
29
29
  import { buildPriorFixContext, PRIOR_FIX_SCOPE_LINE, priorFixBlock, priorFixObservation, type PriorDiffReader, type PriorFixContext } from './review-prior-fix.js';
30
- import { createLandWorktree, localHead, observeLandWorktree, type LandWorktree, type LandWorktreeFailure, type LandWorktreeInput, type LandWorktreeRemoval } from './land-worktree.js';
30
+ import { createLandWorktree, localBranch, localHead, observeLandWorktree, type LandWorktree, type LandWorktreeFailure, type LandWorktreeInput, type LandWorktreeRemoval } from './land-worktree.js';
31
31
 
32
32
  export const TASK_AGENT_LIVE_MOVES = ['review', 'propose-green', 'propose-land', 'decision-card', ...LIVE_RETRY_MOVES] as const;
33
33
  export type TaskAgentLiveMove = typeof TASK_AGENT_LIVE_MOVES[number];
@@ -89,7 +89,7 @@ export interface LiveMoveDeps {
89
89
  raiseDecision?: (args: string[]) => Promise<{ status: number; stdout: string; stderr?: string }>;
90
90
  log?: (category: string, event: string, data: Record<string, unknown>) => void;
91
91
  /** TA-LAND-WORKTREE — cwd HEAD 읽기 · PR 머리 임시 워크트리 만들기(시험 주입 · 기본 = git). */
92
- landWorktree?: { localHead?: (cwd: string) => string | null; create?: (input: LandWorktreeInput) => Promise<LandWorktree | LandWorktreeFailure> };
92
+ landWorktree?: { localHead?: (cwd: string) => string | null; localBranch?: (cwd: string) => string | null; create?: (input: LandWorktreeInput) => Promise<LandWorktree | LandWorktreeFailure> };
93
93
  }
94
94
 
95
95
  /** `executed` = 부작용을 실제로 냈다(리뷰를 띄웠다 · 제안을 적었다). ok 이지만 이미 한 수(같은 머리·이미 제안)면 false. */
@@ -208,13 +208,18 @@ export function liveReviewArgs(entry: string, configDir: string, pr: number, int
208
208
  * 파이프가 아니라 파일 — 부모가 먼저 끝나도 자식이 EPIPE 로 죽지 않는다). 없으면 종전과 같다(stdio ignore).
209
209
  * `opts` 는 시험 몫(spawn·진입점·우주) — 기본은 실물.
210
210
  */
211
- export async function defaultRequestReview(pr: number, intent: string, cwd?: string, opts: { spawn?: DetachedSpawn; entry?: string; configDir?: string; resultPath?: string } = {}): Promise<void> {
211
+ export async function defaultRequestReview(pr: number, intent: string, cwd?: string, opts: { spawn?: DetachedSpawn; entry?: string; configDir?: string; resultPath?: string; cardId?: string; statePath?: string } = {}): Promise<void> {
212
212
  const { spawnDetachedConfirmed, ELANOUS_CLI_ENTRY } = await import('../cli/tasks-cli.js');
213
213
  const { getElanousConfigDirOverride } = await import('../elanous-config-dir.js');
214
214
  const { effectiveInstanceRoot } = await import('../instance/resolve.js');
215
215
  const configDir = opts.configDir ?? getElanousConfigDirOverride() ?? effectiveInstanceRoot();
216
216
  const base = opts.spawn ?? (spawn as unknown as DetachedSpawn);
217
217
  const args = liveReviewArgs(opts.entry ?? ELANOUS_CLI_ENTRY, configDir, pr, intent);
218
+ const completionEnv = opts.resultPath && opts.cardId && opts.statePath ? {
219
+ [TA_REVIEW_COMPLETION_CARD_ENV]: opts.cardId,
220
+ [TA_REVIEW_COMPLETION_STATE_ENV]: opts.statePath,
221
+ [TA_REVIEW_COMPLETION_RESULT_ENV]: opts.resultPath,
222
+ } : undefined;
218
223
  if (!opts.resultPath) { await spawnDetachedConfirmed(base, process.execPath, args, undefined, cwd); return; }
219
224
  let fd: number;
220
225
  try {
@@ -229,7 +234,7 @@ export async function defaultRequestReview(pr: number, intent: string, cwd?: str
229
234
  const toFile: DetachedSpawn = (command, spawnArgs, options) =>
230
235
  (base as unknown as (c: string, a: string[], o: unknown) => ReturnType<DetachedSpawn>)(command, spawnArgs, { ...options, stdio: ['ignore', fd, 'ignore'] });
231
236
  try {
232
- await spawnDetachedConfirmed(toFile, process.execPath, args, undefined, cwd);
237
+ await spawnDetachedConfirmed(toFile, process.execPath, args, undefined, cwd, completionEnv);
233
238
  } finally {
234
239
  try { closeSync(fd); } catch { /* the child keeps its own copy */ }
235
240
  }
@@ -312,7 +317,7 @@ function observe(deps: LiveMoveDeps, result: LiveMoveResult): LiveMoveResult {
312
317
  /** `review` — PR 머리마다 한 번. 카드에 먼저 적고(잠금 안) 그다음 띄운다 · 띄우기 실패는 카드에 오류로 남기고 같은 머리는 다시 안 띄운다. */
313
318
  export async function executeLiveReview(
314
319
  card: TaskCard | undefined, pr: number | undefined,
315
- ctx: { runId: string | null; stopReason: string; cwd?: string; produced?: { headCommit?: string; branch?: string; prUrl?: string } },
320
+ ctx: { runId: string | null; stopReason: string; cwd?: string; forceNewReview?: boolean; produced?: { headCommit?: string; branch?: string; prUrl?: string } },
316
321
  deps: LiveMoveDeps = {},
317
322
  ): Promise<LiveMoveResult> {
318
323
  const kind = 'review' as const;
@@ -340,12 +345,16 @@ export async function executeLiveReview(
340
345
  const path = deps.statePath ?? taskAgentStatePath();
341
346
  const at = (deps.now ?? (() => new Date()))().toISOString();
342
347
  // TA-REVIEW-RESULT-CAPTURE — 머리마다 한 번이라 경로도 머리마다 하나다. 요청 칸에 먼저 적는다(회수가 이 칸을 본다).
343
- const resultPath = reviewResultPath(path, card.id, pr, head.head);
348
+ const resultPath = ctx.forceNewReview
349
+ ? reviewResultPath(path, card.id, pr, head.head).replace(/\.json$/, `-${randomUUID()}.json`)
350
+ : reviewResultPath(path, card.id, pr, head.head);
344
351
  const claim: { claimed: boolean; missing: boolean; before?: TaskCard } = { claimed: false, missing: false };
345
352
  updateTaskCard(path, card.id, (current) => {
346
353
  if (!current) { claim.missing = true; return undefined; }
347
354
  // 머리 «이력»으로 본다 — A→B→A 로 돌아와도 A 는 다시 요청하지 않는다.
348
- if ((current.reviewRequests ?? []).some((entry) => entry.pr === pr && entry.head === head!.head)) return undefined;
355
+ if (ctx.forceNewReview) {
356
+ if (pendingReviewResults(current).some((entry) => entry.pr === pr && entry.head === head!.head)) return undefined;
357
+ } else if ((current.reviewRequests ?? []).some((entry) => entry.pr === pr && entry.head === head!.head)) return undefined;
349
358
  claim.claimed = true;
350
359
  claim.before = current;
351
360
  return { ...current, reviewRequests: [...(current.reviewRequests ?? []), { pr, head: head!.head, at, resultPath }] };
@@ -360,7 +369,7 @@ export async function executeLiveReview(
360
369
  try { (deps.log ?? ((c, e, d) => debug.log(c, e, d)))('task-agent', 'review-prior-fix-filled', priorFixObservation(card.id, pr, prior)); } catch { /* fail-soft */ }
361
370
  }
362
371
  const intent = liveReviewIntent(card, ctx.runId, ctx.stopReason, prior);
363
- await (deps.requestReview ? deps.requestReview(pr, intent, cwd, { resultPath }) : defaultRequestReview(pr, intent, cwd, { resultPath }));
372
+ await (deps.requestReview ? deps.requestReview(pr, intent, cwd, { resultPath }) : defaultRequestReview(pr, intent, cwd, { resultPath, cardId: card.id, statePath: path }));
364
373
  } catch (error) {
365
374
  const reason = error instanceof Error ? error.message : String(error);
366
375
  updateTaskCard(path, card.id, (current) => current?.reviewRequests?.some((entry) => entry.pr === pr && entry.head === head!.head && entry.at === at)
@@ -411,6 +420,12 @@ const LAND_BASE_STALE_LINE = /^✗ overlap-evidence: (base-stale-overlap: .*)$/;
411
420
  const LAND_BASE_STALE_CONSEQUENCE_LINES: readonly RegExp[] = [/^✗ expected-head: overlap merge approval unavailable for the reviewed head$/];
412
421
  /** 실패가 아닌 ✗ 줄 — `pr land` 가 «계속합니다»로 넘어간다(판정에 쓰지 않는다). */
413
422
  const LAND_NON_FATAL_LINES: readonly RegExp[] = [/^✗ active-run-files: .*계속합니다\.?$/];
423
+ const TSC_RUNTIME_MISSING = /(?:^|\n)(?:sh: )?(?:env: )?(?:[^\s]*\/)?(?:node|bun|tsc): (?:No such file or directory|command not found)/m;
424
+ const TSC_NOT_RUN = /(?:tsc 종료코드 127|tsc 실행 실패 \([^\n]*code=ENOENT)/;
425
+ const TSC_GATE_REFUSED = '✗ typecheck: scripts/ci-typecheck-changed.ts blocked pr land.';
426
+ function missingTscRuntime(text: string): boolean {
427
+ return TSC_NOT_RUN.test(text) && TSC_RUNTIME_MISSING.test(text);
428
+ }
414
429
  /** executeLiveLand 쪽 인프라 사유(출력 줄이 아니라 실행 사실). */
415
430
  export const LAND_INFRA_EXECUTOR_REASONS = ['land-process-error', 'land-no-result-line'] as const;
416
431
  const [LAND_PROCESS_ERROR, LAND_NO_RESULT_LINE] = LAND_INFRA_EXECUTOR_REASONS;
@@ -430,6 +445,12 @@ export function classifyLandFailure(outcome: { status: number; stdout?: string;
430
445
  // 결과 줄이 «하나도» 없이 0 아닌 코드로 끝났다 — 관문까지 못 간 채 죽었다(크래시 · 신호).
431
446
  if (!marked.length) return outcome.status !== 0 ? { class: 'infra', reason: LAND_NO_RESULT_LINE } : { class: 'judgement', reason: 'no-failure-line' };
432
447
  const failures = marked.map((line) => line.replace(/^\[dry-run\] /, '')).filter((line) => line.startsWith('✗') && !LAND_NON_FATAL_LINES.some((re) => re.test(line)));
448
+ // The gate did not measure TypeScript when its interpreter was absent. The
449
+ // exact typecheck refusal alone remains judgement; require rc127/ENOENT and
450
+ // the missing runtime line, with no second failing gate.
451
+ if (failures.length === 1 && failures[0] === TSC_GATE_REFUSED && missingTscRuntime(lines.join('\n'))) {
452
+ return { class: 'infra', reason: 'tsc-runtime-missing' };
453
+ }
433
454
  const infra = new Set<string>();
434
455
  const unlisted: string[] = [];
435
456
  for (const line of failures) {
@@ -492,7 +513,15 @@ function observeLandWorktreeCreateCapped(deps: LiveMoveDeps, data: { card: strin
492
513
 
493
514
  /** 같은 머리에 한 번 더 시도해도 되나 — 앞 시도가 «전부» 끝났고(ok:false) «전부» infra 이고 상한 밑일 때만. */
494
515
  function landRetryAllowed(prior: readonly LandAttempt[]): boolean {
495
- return prior.length > 0 && prior.length < LAND_ATTEMPTS_PER_HEAD_ON_INFRA && prior.every((entry) => entry.ok === false && entry.class === 'infra');
516
+ return prior.length > 0 && prior.length < LAND_ATTEMPTS_PER_HEAD_ON_INFRA
517
+ && prior.every((entry) => entry.ok === false && (entry.class === 'infra' || legacyMissingTscRuntime(entry)));
518
+ }
519
+
520
+ /** A previous release classified a missing node interpreter as judgement; only that exact recorded shape may be reopened. */
521
+ function legacyMissingTscRuntime(entry: LandAttempt): boolean {
522
+ return entry.ok === false && entry.class === 'judgement' && entry.reason === TSC_GATE_REFUSED
523
+ && typeof entry.detail === 'string'
524
+ && classifyLandFailure({ status: 1, stderr: entry.detail }).reason === 'tsc-runtime-missing';
496
525
  }
497
526
 
498
527
  /**
@@ -560,11 +589,15 @@ export async function executeLiveLand(
560
589
  const path = deps.statePath ?? taskAgentStatePath();
561
590
  const at = (deps.now ?? (() => new Date()))().toISOString();
562
591
  const local = (deps.landWorktree?.localHead ?? localHead)(cwd);
563
- const needsLandWorktree = !!local && local.toLowerCase() !== view.head.toLowerCase();
592
+ const landKey = `land:${card.id}:${view.head.slice(0, 12)}`;
593
+ const branch = local?.toLowerCase() === view.head.toLowerCase() ? (deps.landWorktree?.localBranch ?? localBranch)(cwd) : null;
594
+ const needsLandWorktree = !!local && (local.toLowerCase() !== view.head.toLowerCase() || branch !== view.branch);
564
595
  const createCap = landWorktreeCreateCap();
565
596
  let claimed = false;
566
597
  let missing = false;
567
598
  let cappedReason: string | undefined;
599
+ // This state-file lock is the cross-process card/head land claim. Both completion workers
600
+ // must pass it; an unfinished or successful attempt makes the second worker skip.
568
601
  // TA-LAND-RETRY-ON-INFRA — 같은 머리의 앞 시도들(잠금 안에서 읽은 «지금» 카드) · 재시도면 그 근거.
569
602
  let prior: LandAttempt[] = [];
570
603
  try {
@@ -590,12 +623,13 @@ export async function executeLiveLand(
590
623
  if (!claimed) {
591
624
  const last = prior.at(-1);
592
625
  const why = last ? ` (attempts ${prior.length} · last ${last.ok === false ? `class ${last.class ?? 'unclassified'}${last.reason ? ` ${last.reason}` : ''}` : last.ok === true ? 'ok' : 'unfinished'})` : '';
593
- return observe(deps, withRepo({ kind, card: card.id, ok: true, executed: false, detail: `already attempted land for #${pr} head ${view.head.slice(0, 12)}${why}` }));
626
+ return observe(deps, withRepo({ kind, card: card.id, ok: true, executed: false, detail: `already attempted land for #${pr} head ${view.head.slice(0, 12)} · ${landKey} claimed — skip${why}` }));
594
627
  }
595
628
  const attempt = prior.length + 1;
596
629
  if (attempt > 1) {
597
630
  const last = prior.at(-1)!;
598
- try { (deps.log ?? ((c, e, d) => debug.log(c, e, d)))('task-agent', LAND_RETRY_ON_INFRA_EVENT, { card: card.id, pr, head: view.head, attempt, reason: last.reason ?? 'unknown', class: 'infra', decision: 'retrying' }); } catch { /* fail-soft */ }
631
+ const legacy = legacyMissingTscRuntime(last);
632
+ try { (deps.log ?? ((c, e, d) => debug.log(c, e, d)))('task-agent', LAND_RETRY_ON_INFRA_EVENT, { card: card.id, pr, head: view.head, attempt, reason: legacy ? 'tsc-runtime-missing-legacy' : last.reason ?? 'unknown', class: 'infra', decision: legacy ? 'legacy-reclassified-retrying' : 'retrying' }); } catch { /* fail-soft */ }
599
633
  }
600
634
  if (cycle) landedCycles.add(cycle);
601
635
  // TA-LAND-WORKTREE — `pr land --expected-head` 는 «로컬 HEAD = 고정 머리»를 요구한다. cwd 가 다른 머리면(Pod 런 PR · 호스트 checkout)
@@ -610,6 +644,11 @@ export async function executeLiveLand(
610
644
  if ('reason' in made) {
611
645
  const reason = made.reason;
612
646
  observeLandWorktree({ card: card.id, pr, head: view.head, created: made.created, removed: made.removed, reason }, deps.log);
647
+ if (made.kind === 'worktree-conflict') {
648
+ try { (deps.log ?? ((c, e, d) => debug.log(c, e, d)))('task-agent', 'worktree-conflict', {
649
+ card: card.id, pr, head: view.head, branch: view.branch ?? null, ownerPath: made.ownerPath ?? 'unknown', reason,
650
+ }); } catch { /* fail-soft */ }
651
+ }
613
652
  let failures: LandWorktreeCreateFailure[] = [];
614
653
  let recordFailureError: unknown;
615
654
  try {
@@ -647,7 +686,7 @@ export async function executeLiveLand(
647
686
  } catch (error) {
648
687
  try { (deps.log ?? ((c, e, d) => debug.log(c, e, d, { level: 'warn' })))('task-agent', 'land-worktree-create-failures-clear-failed', { card: card.id, pr, head: view.head, error: String(error).slice(-200) }); } catch { /* fail-soft */ }
649
688
  }
650
- observeLandWorktree({ card: card.id, pr, head: view.head, created: true, removed: false, reason: `cwd HEAD ${local.slice(0, 12)} ≠ PR head ${view.head.slice(0, 12)}` }, deps.log);
689
+ observeLandWorktree({ card: card.id, pr, head: view.head, created: made.created ?? null, removed: false, reason: `cwd HEAD ${local.slice(0, 12)} · branch ${branch ?? 'unknown'} differs from PR ${view.head.slice(0, 12)} · ${view.branch ?? 'unknown'}` }, deps.log);
651
690
  }
652
691
  // 증거 경로는 «원래» cwd 기준으로 고정한다 — 임시 트리로 cwd 를 바꿔도 상대경로가 다른 파일을 가리키지 않게(같은 cwd 면 그대로).
653
692
  const overlapEvidence = ctx.overlapEvidence && worktree ? resolve(cwd, ctx.overlapEvidence) : ctx.overlapEvidence;
@@ -673,7 +712,7 @@ export async function executeLiveLand(
673
712
  // 성공이든 실패든 걷는다.
674
713
  let removal: LandWorktreeRemoval;
675
714
  try { removal = worktree.cleanup(); } catch (error) { removal = { removed: false, reason: String(error).slice(-200) }; }
676
- observeLandWorktree({ card: card.id, pr, head: view.head, created: true, removed: removal.removed, ...(removal.branchRemoved !== undefined ? { branchRemoved: removal.branchRemoved } : {}),
715
+ observeLandWorktree({ card: card.id, pr, head: view.head, created: worktree.created ?? null, removed: removal.removed, ...(removal.branchRemoved !== undefined ? { branchRemoved: removal.branchRemoved } : {}),
677
716
  reason: removal.reason ?? `land ${ok ? 'ok' : 'failed'} — removed` }, deps.log);
678
717
  }
679
718
  const landAttempt: LiveMoveResult['landAttempt'] = failure
@@ -159,7 +159,7 @@ export function remoteLaunchScript(target: ParentHostConfig, args: readonly stri
159
159
  const elanous = target.elanous ?? DEFAULT_REMOTE_ELANOUS;
160
160
  const configDir = target.configDir ?? DEFAULT_REMOTE_CONFIG_DIR;
161
161
  const log = remoteParentLogPath(target, env.ELANOUS_RUN_ID ?? 'unknown');
162
- const envPart = Object.entries(env).filter(([key]) => /^[A-Z_][A-Z0-9_]*$/.test(key)).map(([key, value]) => `${key}=${shellQuote(value)}`).join(' ');
162
+ const envPart = [...Object.entries(env).filter(([key]) => key !== 'ELANOUS_STATE_DIR' && /^[A-Z_][A-Z0-9_]*$/.test(key)).map(([key, value]) => `${key}=${shellQuote(value)}`), `ELANOUS_STATE_DIR=${remotePath(configDir)}`].join(' ');
163
163
  const argv = [remotePath(elanous), '--config-dir', remotePath(configDir), ...remoteParentArgs(args, target.podPool).map(shellQuote)].join(' ');
164
164
  // 준비가 실패하면 «확실히 안 띄웠다»(not-launched=…)를 찍고 끝낸다 — 그때만 호출부가 로컬로 돌아간다.
165
165
  // 띄운 뒤에는 짧게 살아 있는지 보고 pid 를 찍는다(조기 종료면 early-exit — 역시 «안 돈다»가 확실).