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
@@ -1,20 +1,28 @@
1
1
  import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
- import { loadRunLedger as loadRunLedgerSync } from '../self-implement/run-ledger.js';
4
- import { join, resolve } from 'node:path';
3
+ import { execFileSync, spawnSync } from 'node:child_process';
4
+ import { loadRunLedger as loadRunLedgerSync, loadRunLedgerWithMetadata, runLedgerDir, resolveFederatedRunLedgerDirectories } from '../self-implement/run-ledger.js';
5
+ import { goalRunDbPath, loadGoalRunQuery } from '../self-implement/goal-run-store.js';
6
+ import { loadGoalAskRecords } from '../self-implement/goal-ask-store.js';
7
+ import { dirname, join, resolve } from 'node:path';
5
8
  import { InvalidArgumentError, Option, type Command } from 'commander';
6
9
  import { debug } from '../debug/log.js';
7
10
  import { getElanousConfigDir } from '../elanous-config-dir.js';
8
11
  import { readNexusRuntime } from '../nexus/runtime.js';
9
- import { checklistDevVersion } from '../release-loop/checklist.js';
12
+ import { checklistDevVersion, listChecklist } from '../release-loop/checklist.js';
13
+ import { assignedVersions, releasedVersion, validateVersion } from '../release-loop/feature-store.js';
14
+ import { compareVersions } from '../directives/version-at.js';
10
15
  import { cardsBoard, countBoardTasks, INTERNAL_PACK_PROJECT, mergeBoards, releasePackBoard, type BoardNode } from '../task-agent/board.js';
11
16
  import type { Task } from '../task-orchestrator/types.js';
12
- import { cardPrNumber, CLOSED_BY_HAND_EVENT, closeTaskCard, COMPLETION_KINDS, handTask, updateTaskCard, LIVE_MOVE_HISTORY_EVENT, LIVE_MOVE_RESULT_EVENT, nextMoveFor, resolveHandText, readTaskAgentState, readTaskCard, taskAgentStatePath as taskStatePathDefault, shellQuote, TA_LAND_ENV, TASK_CARD_PREFIX, TASK_SEATS, type CompletionKind, type TaskCard, type TaskLaunchContext, type TaskLaunchReceipt, type TaskLauncher, type TaskSeat } from '../task-agent/task-hand.js';
17
+ import { cardPrNumber, CLOSED_BY_HAND_EVENT, closeTaskCard, COMPLETION_KINDS, handTask, updateTaskCard, LIVE_MOVE_HISTORY_EVENT, LIVE_MOVE_RESULT_EVENT, nextMoveFor, resolveHandText, readTaskAgentState, readTaskCard, taskAgentStatePath as taskStatePathDefault, shellQuote, TASK_CARD_PREFIX, TASK_SEATS, type CompletionKind, type TaskCard, type TaskLaunchContext, type TaskLaunchReceipt, type TaskLauncher, type TaskSeat } from '../task-agent/task-hand.js';
13
18
  import { readCardSalvageBranch, recordWiringLiveVerified, refreshCardRunBinding, type CardRunEvidenceDeps } from '../task-agent/card-evidence.js';
14
19
  import { captureReviewResults } from '../task-agent/review-result-capture.js';
20
+ import { replayRemoteCardMoves } from '../task-agent/shadow.js';
21
+ import { remoteLedgerMirrorDirs } from '../task-agent/parent-host.js';
22
+ import { ambiguousGreenSweepPrs, decideGreenSweep, greenSweepCandidates, observeGreenSweep, type GreenSweepPr } from '../task-agent/green-sweep.js';
15
23
  import { HAND_MOVE_KINDS, recordHandMove } from '../task-agent/live-move-history.js';
16
24
  import { advanceMission, handMission, MissionAdvanceError, openMissionIds, recordPieceRef, type AdvanceMissionResult, type MissionDecompose, type PieceEvidenceReader } from '../task-agent/mission.js';
17
- import { formatSevenVerbLive, formatTaskAgentCover, measureSevenVerbLive, measureTaskAgentCover, type CoverRow, type SevenVerbLive, type TaskAgentCoverInput } from '../task-agent/cover.js';
25
+ import { formatSevenVerbLive, formatTaskAgentCover, measureSevenVerbLive, measureTaskAgentCover, type CoverRow, type ImplementCardRunBinding, type ImplementRunMergeEvent, type SevenVerbLive, type TaskAgentCoverInput } from '../task-agent/cover.js';
18
26
  import { LogStore, STORE_SAFETY_MAX, type LogQuery, type LogStoreRow } from '../mss/logging/log-store.js';
19
27
  import { resolveLogTargets } from './logs-cli.js';
20
28
 
@@ -31,6 +39,8 @@ export interface TasksCliDeps {
31
39
  reviewResultNow?: () => Date;
32
40
  /** 과제 카드 상태 파일(시험 주입). 기본 = `effectiveInstanceRoot()/task-agent-actions.json`. */
33
41
  taskStatePath?: string;
42
+ /** 조합 선택 시험에서만 그래프 저장소 루트를 바꾼다. */
43
+ taskCompositionRoot?: string;
34
44
  /** logs.db 싱크 등록(시험 주입). 기본 = `registerStandaloneLogSink`. `hand`·`show` 가 부른다. */
35
45
  registerSink?: (surface: string) => Promise<unknown>;
36
46
  /** `task hand --mission` 의 분해기(시험 주입). 기본 = 나열형은 결정론 · 아니면 `decomposeSelfDevGoal`. */
@@ -38,7 +48,15 @@ export interface TasksCliDeps {
38
48
  /** `task advance` 의 착지 근거 읽기(시험 주입). 기본 = `predecessorState`(gh · 런 원장). */
39
49
  pieceEvidence?: PieceEvidenceReader;
40
50
  /** `task board` 의 내부 팩 어댑터(시험 주입). 기본 = 현재 판(`checklistDevVersion()`)의 `releasePackBoard` — 읽기만. */
41
- releaseBoard?: () => BoardNode[];
51
+ releaseBoard?: (version?: string) => BoardNode[];
52
+ /** `tasks hand --checklist` 판 목록(시험 주입). 기본은 발행 전 체크리스트가 있는 판 전부. */
53
+ releaseVersions?: () => string[];
54
+ /** Isolated checklist read for PR adoption. */
55
+ adoptChecklist?: typeof listChecklist;
56
+ /** `tasks adopt-pr` live PR read seam; default is gh in this repository. */
57
+ adoptPrView?: (number: number, cwd: string) => import('../task-agent/adopt-pr.js').AdoptPrView;
58
+ /** Isolated ledger proof for a launched card being adopted. */
59
+ adoptRunTerminal?: typeof import('../task-agent/adopt-pr.js').adoptedRunTerminal;
42
60
  /** `tasks show`·`advance` 의 런 근거 읽기(시험 주입) — 기본 = 런 원장(이 우주 ⊕ 연합) ⊕ 호스트 logs 의 Pod job-finished. */
43
61
  cardRunEvidence?: CardRunEvidenceDeps;
44
62
  /** `tasks live-verified` 가 적는 런 원장 디렉터리(시험 주입). 기본 = `runLedgerDir()`. */
@@ -49,15 +67,26 @@ export interface TasksCliDeps {
49
67
  staleParents?: import('../task-agent/parent-host.js').StaleParentDeps;
50
68
  /** `tasks adopt-runs` 의 백필 의존(시험 주입 · 등록부·상태 파일). */
51
69
  adoptRuns?: import('../task-agent/run-adopt.js').RunAdoptDeps;
52
- /** `tasks cover` logs 조회 seam. 기본은 등록된 모든 우주(운영 ⊕ test)의 logs.db 를 read-only 로 연다 —
53
- * pr land 는 작업 트리마다 파생 test 우주에 기록되므로 현재 우주만 읽으면 분모가 늘 0 이다. */
54
- coverLogs?: (query: LogQuery) => LogStoreRow[] | CoverLogRead;
70
+ /** `tasks cover` logs 조회 seam. 기본은 운영 우주만, --include-test 때 시험 우주도 읽는다. */
71
+ coverLogs?: (query: LogQuery, includeTest?: boolean) => LogStoreRow[] | CoverLogRead;
55
72
  /** `tasks cover --by-card` 카드 원장 읽기 seam(읽기 전용). 플래그가 있을 때만 부른다. 기본 = TA 카드 원장(task-agent-actions.json). null = 못 읽음. */
56
73
  coverCards?: () => CoverCardRow[] | null | Promise<CoverCardRow[] | null>;
74
+ /** Read-only authored goal provenance; null means the authoring ledger could not be read. */
75
+ coverGoalAsks?: () => { authorRunId: string; goalId: string; createdAt: string }[] | null;
76
+ /** Read-only terminal goal records; null means the goal ledger could not be read. */
77
+ coverGoalRuns?: () => { goalId: string; outcome: string; stage?: string; startedAt: string; completedAt?: string }[] | null;
78
+ /** 구현 출처 시험 seam. 생략하면 task-agent-actions.json을 읽고, null이면 카드 원장을 못 읽은 것이다. */
79
+ coverImplementCardRuns?: () => readonly ImplementCardRunBinding[] | null;
80
+ /** 구현 출처 시험 seam. 생략하면 로컬·연합·원격 거울 run-ledger를 읽고, null이면 못 읽은 것이다. */
81
+ coverImplementRunMerges?: () => ReadonlyMap<string, ImplementRunMergeEvent> | null;
82
+ /** Read test-universe ledgers only when --include-test was requested. */
83
+ coverIncludeTest?: boolean;
57
84
  /** TA-REJUDGE-ON-HEAD — `tasks rejudge` 의 판단 의존(시험 주입 · 기본 = 설정 liveMoves ⊕ 실물 gh·리뷰·재게이트·land). */
58
85
  rejudge?: Partial<import('../task-agent/rejudge-on-head.js').RejudgeDeps>;
59
86
  /** TA-REJUDGE-ON-HEAD — `tasks regate-run` 의 재게이트(시험 주입 · 기본 `runHostRegate`). */
60
87
  hostRegate?: (input: import('../self-implement/host-regate.js').HostRegateInput) => Promise<import('../self-implement/host-regate.js').HostRegateResult>;
88
+ /** Isolated regate-run tests can disable the asynchronous completion callback explicitly. */
89
+ regateCompletion?: false;
61
90
  }
62
91
 
63
92
  /** `<조각id>=<값>` 반복 인자. */
@@ -152,8 +181,8 @@ async function tryRemoteParent(args: string[], cwd: string | undefined, context:
152
181
  try { debug.log('task-agent', 'parent-host-fallback', { host: target.host, runId: context.runId, gaps, facts }); } catch { /* fail-soft */ }
153
182
  notice(`[parent-host] ${target.host} 로 못 띄움 — ${multi ? '다음 호스트 또는 로컬 발사로 간다' : '로컬 발사로 간다'}: ${gaps.join(' · ')}`);
154
183
  };
155
- if (cwd || context.env[TA_LAND_ENV] === '1') {
156
- failed(targets[0]!, [cwd ? 'project-target-is-local' : 'ta-land-needs-local-parent']);
184
+ if (cwd) {
185
+ failed(targets[0]!, ['project-target-is-local']);
157
186
  return null;
158
187
  }
159
188
  let launchHoldActive = false;
@@ -311,8 +340,8 @@ async function mirrorRemoteCardLedger(cardId: string, deps: TasksCliDeps): Promi
311
340
  try {
312
341
  if (deps.remoteLedgerMirror) { deps.remoteLedgerMirror(card); return; }
313
342
  const { mirrorRemoteRunLedgers } = await import('../task-agent/parent-host.js');
314
- const { prodInstanceRoot } = await import('../instance/resolve.js');
315
- mirrorRemoteRunLedgers(card.parentHost, card.runId, prodInstanceRoot());
343
+ const { effectiveInstanceRoot } = await import('../instance/resolve.js');
344
+ mirrorRemoteRunLedgers(card.parentHost, card.runId, effectiveInstanceRoot(), deps.cardRunEvidence?.ssh);
316
345
  } catch (error) {
317
346
  try { debug.log('task-agent', 'remote-ledger-mirror-failed', { card: cardId, host: card.parentHost.host, runId: card.runId, error: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
318
347
  }
@@ -443,8 +472,20 @@ const COVER_QUERY_LIMIT = STORE_SAFETY_MAX;
443
472
  /** Federated read: rows from every store, whether any single store hit the bound, and how many stores could not be read. */
444
473
  export interface CoverLogRead { rows: LogStoreRow[]; limitReached: boolean; unreadableStores: number; storesRead: number }
445
474
 
446
- function queryCoverLogs(query: LogQuery): CoverLogRead {
447
- const { targets, error } = resolveLogTargets({ all: true, includeTest: true });
475
+ /** Land attempts are observed separately from completed land actions; an appended order or skipped move is not a merge. */
476
+ export interface CoverLandSignals {
477
+ liveMoveEvents: number | null;
478
+ executedLiveMoves: number | null;
479
+ landOrderEvents: number | null;
480
+ }
481
+
482
+ export function formatCoverLandSignals(signals: CoverLandSignals): string {
483
+ const count = (value: number | null) => value === null ? '-' : String(value);
484
+ return `착지 시도 관측: live-move ${count(signals.liveMoveEvents)} · 집행 ${count(signals.executedLiveMoves)} · land-order ${count(signals.landOrderEvents)} (병합 수와 별개)`;
485
+ }
486
+
487
+ function queryCoverLogs(query: LogQuery, includeTest = false): CoverLogRead {
488
+ const { targets, error } = resolveLogTargets({ all: true, includeTest });
448
489
  if (error) throw new Error(error);
449
490
  const read: CoverLogRead = { rows: [], limitReached: false, unreadableStores: 0, storesRead: 0 };
450
491
  for (const target of targets) {
@@ -477,6 +518,61 @@ function coverEvents<T>(rows: readonly LogStoreRow[], category: string, event: s
477
518
  return events;
478
519
  }
479
520
 
521
+ /** A malformed land record makes the numerator unknown; dropping it would assert a false zero. */
522
+ function coverLandMoves(rows: readonly LogStoreRow[]): { card: string; executed: boolean; ok: boolean }[] | null {
523
+ const moves: { card: string; executed: boolean; ok: boolean }[] = [];
524
+ for (const row of rows) {
525
+ if (row.category !== 'task-agent' || row.event !== 'live-move') continue;
526
+ if (typeof row.data !== 'string') return null;
527
+ let data: unknown;
528
+ try { data = JSON.parse(row.data); } catch { return null; }
529
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) return null;
530
+ const move = data as Record<string, unknown>;
531
+ if (typeof move.kind !== 'string') return null;
532
+ if (move.kind !== 'land') continue;
533
+ if (typeof move.card !== 'string' || !move.card || typeof move.executed !== 'boolean'
534
+ || typeof move.ok !== 'boolean') return null;
535
+ moves.push({ card: move.card, executed: move.executed, ok: move.ok });
536
+ }
537
+ return moves;
538
+ }
539
+
540
+ /** An incomplete action source cannot provide a certain land numerator. */
541
+ function coverTaskActions(rows: readonly LogStoreRow[]): NonNullable<TaskAgentCoverInput['taskAgentActions']> | null {
542
+ const actions: NonNullable<TaskAgentCoverInput['taskAgentActions']>[number][] = [];
543
+ for (const row of rows) {
544
+ if (row.category !== 'task-agent' || row.event !== 'action') continue;
545
+ if (typeof row.data !== 'string') return null;
546
+ let data: unknown;
547
+ try { data = JSON.parse(row.data); } catch { return null; }
548
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) return null;
549
+ const action = data as Record<string, unknown>;
550
+ if (typeof action.kind !== 'string' || typeof action.result !== 'string') return null;
551
+ if (action.kind === 'land' && action.result === 'done'
552
+ && (typeof action.taskId !== 'string' || !action.taskId)) return null;
553
+ actions.push({ kind: action.kind as NonNullable<TaskAgentCoverInput['taskAgentActions']>[number]['kind'],
554
+ result: action.result,
555
+ ...(typeof action.taskId === 'string' ? { taskId: action.taskId } : {}),
556
+ ...(typeof action.reason === 'string' ? { reason: action.reason } : {}) });
557
+ }
558
+ return actions;
559
+ }
560
+
561
+ function coverLandSteps(rows: readonly LogStoreRow[]): NonNullable<TaskAgentCoverInput['allLands']> | null {
562
+ const steps: NonNullable<TaskAgentCoverInput['allLands']>[number][] = [];
563
+ for (const row of rows) {
564
+ if (row.category !== 'pr.land' || row.event !== 'step') continue;
565
+ if (typeof row.data !== 'string') return null;
566
+ let data: unknown;
567
+ try { data = JSON.parse(row.data); } catch { return null; }
568
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) return null;
569
+ const step = data as Record<string, unknown>;
570
+ if (typeof step.step !== 'string' || typeof step.ok !== 'boolean') return null;
571
+ steps.push({ step: step.step, ok: step.ok });
572
+ }
573
+ return steps;
574
+ }
575
+
480
576
  export interface CoverCardRow { id: string; createdAt: string }
481
577
 
482
578
  /**
@@ -498,13 +594,89 @@ export function coverCardsInWindow(cards: readonly CoverCardRow[] | null, window
498
594
  }).map(({ id }) => ({ id }));
499
595
  }
500
596
 
597
+ /** Read the card ledger itself. A missing file or malformed row makes the aggregate unknown. */
598
+ function readImplementCardBindings(statePath: string): ImplementCardRunBinding[] {
599
+ if (!existsSync(statePath)) throw new Error('TASK-AGENT card ledger is missing');
600
+ const state = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(statePath);
601
+ if (state.tasks !== undefined && (!state.tasks || typeof state.tasks !== 'object' || Array.isArray(state.tasks))) {
602
+ throw new Error('TASK-AGENT card ledger is malformed');
603
+ }
604
+ const cards = Object.entries(state.tasks ?? {});
605
+ if (cards.length >= COVER_QUERY_LIMIT) throw new Error('TASK-AGENT card ledger reached query bound');
606
+ return cards.map(([key, card]) => {
607
+ if (!card || typeof card !== 'object' || Array.isArray(card) || typeof card.id !== 'string' || !card.id
608
+ || card.id !== key || (card.runId != null && typeof card.runId !== 'string')
609
+ || (card.runChildId != null && typeof card.runChildId !== 'string')) {
610
+ throw new Error('TASK-AGENT card binding is malformed');
611
+ }
612
+ return { cardId: card.id, runId: card.runId, runChildId: card.runChildId };
613
+ });
614
+ }
615
+
616
+ function implementLedgerDirectories(statePath: string, includeTest: boolean): string[] {
617
+ const root = dirname(statePath);
618
+ return [...new Set([
619
+ runLedgerDir(root),
620
+ runLedgerDir(),
621
+ ...resolveFederatedRunLedgerDirectories({ includeTest }),
622
+ ...remoteLedgerMirrorDirs(root),
623
+ ])];
624
+ }
625
+
626
+ /** Read only runs bound to cards. Merge time, rather than card launch time, owns the cover window. */
627
+ function readImplementRunMerges(
628
+ bindings: readonly ImplementCardRunBinding[],
629
+ directories: readonly string[],
630
+ window: { sinceMs?: number; untilMs?: number },
631
+ ): ReadonlyMap<string, ImplementRunMergeEvent> {
632
+ const runIds = new Set(bindings.flatMap(({ runId, runChildId }) => [runId, runChildId])
633
+ .filter((id): id is string => typeof id === 'string' && id.length > 0));
634
+ if (runIds.size >= COVER_QUERY_LIMIT) throw new Error('bound run ledger query reached bound');
635
+ const merges = new Map<string, ImplementRunMergeEvent>();
636
+ let eventsRead = 0;
637
+ for (const runId of runIds) {
638
+ let found = false;
639
+ let merged = false;
640
+ let failure: unknown;
641
+ for (const directory of directories) {
642
+ try {
643
+ const loaded = loadRunLedgerWithMetadata(runId, directory);
644
+ if (loaded === null) continue;
645
+ found = true;
646
+ if (loaded.skippedTrailingBytes) throw new Error(`run ledger is incomplete: ${runId}`);
647
+ eventsRead += loaded.entries.length;
648
+ if (eventsRead >= COVER_QUERY_LIMIT) throw new Error('run ledger query reached bound');
649
+ for (const entry of loaded.entries) {
650
+ if (entry.event !== 'merged') continue;
651
+ if (typeof entry.data.merged !== 'boolean') throw new Error(`merged event is malformed: ${runId}`);
652
+ if (!entry.data.merged) continue;
653
+ if (window.sinceMs !== undefined || window.untilMs !== undefined) {
654
+ const at = Date.parse(entry.timestamp ?? '');
655
+ if (!Number.isFinite(at)) throw new Error(`merged event timestamp is missing: ${runId}`);
656
+ if ((window.sinceMs !== undefined && at < window.sinceMs)
657
+ || (window.untilMs !== undefined && at > window.untilMs)) continue;
658
+ }
659
+ merged = true;
660
+ }
661
+ } catch (error) { failure ??= error; }
662
+ }
663
+ // A local parent may contain only launch events while a federated or mirrored copy has the merge.
664
+ // Read every physical copy. Any unreadable copy leaves the aggregate unknown.
665
+ if (failure !== undefined) throw failure;
666
+ if (!found) throw new Error(`bound run ledger is missing: ${runId}`);
667
+ merges.set(runId, { merged });
668
+ }
669
+ return merges;
670
+ }
671
+
501
672
  export function readTaskAgentCover(
502
673
  window: { sinceMs?: number; untilMs?: number } = {},
503
674
  query: NonNullable<TasksCliDeps['coverLogs']> = queryCoverLogs,
504
675
  taskCards?: readonly { id: string }[] | null,
505
- ): { rows: CoverRow[]; sevenVerbs: SevenVerbLive; limitReached: boolean; unreadableStores: number } {
676
+ goalReaders: Pick<TasksCliDeps, 'coverGoalAsks' | 'coverGoalRuns' | 'coverImplementCardRuns' | 'coverImplementRunMerges' | 'taskStatePath' | 'coverIncludeTest'> = {},
677
+ ): { rows: CoverRow[]; sevenVerbs: SevenVerbLive; landSignals: CoverLandSignals; limitReached: boolean; unreadableStores: number } {
506
678
  let unreadableStores = 0;
507
- const read = (category: 'task-agent' | 'pr.land', event: 'action' | 'step'): { rows: LogStoreRow[]; limitReached: boolean } | null => {
679
+ const read = (category: 'task-agent' | 'pr.land' | 'task-agent.land-order', event: 'action' | 'step' | 'live-move' | 'appended'): { rows: LogStoreRow[]; limitReached: boolean } | null => {
508
680
  try {
509
681
  const result = query({ ...window, limit: COVER_QUERY_LIMIT, exactCategories: [category], events: [event] });
510
682
  if (Array.isArray(result)) return { rows: result, limitReached: result.length >= COVER_QUERY_LIMIT };
@@ -514,19 +686,83 @@ export function readTaskAgentCover(
514
686
  };
515
687
  const actionRead = read('task-agent', 'action');
516
688
  const landRead = read('pr.land', 'step');
689
+ const moveRead = read('task-agent', 'live-move');
690
+ const orderRead = read('task-agent.land-order', 'appended');
517
691
  const actions = actionRead?.rows ?? null;
518
692
  const lands = landRead?.rows ?? null;
519
- const truncated = { taskAgentActions: actionRead?.limitReached === true, allLands: landRead?.limitReached === true };
693
+ const truncated = { taskAgentActions: actionRead?.limitReached === true, allLands: landRead?.limitReached === true,
694
+ liveLandMoves: moveRead?.limitReached === true };
695
+ const moves = moveRead && !moveRead.limitReached ? coverLandMoves(moveRead.rows) : null;
696
+ const inWindow = (value: string): boolean => {
697
+ const time = Date.parse(value);
698
+ return Number.isFinite(time) && (window.sinceMs === undefined || time >= window.sinceMs) && (window.untilMs === undefined || time <= window.untilMs);
699
+ };
700
+ let goalAsks: ReturnType<NonNullable<TasksCliDeps['coverGoalAsks']>> = null;
701
+ let goalRuns: ReturnType<NonNullable<TasksCliDeps['coverGoalRuns']>> = null;
702
+ let goalAsksTruncated = false;
703
+ let goalRunsTruncated = false;
704
+ try {
705
+ if (goalReaders.coverGoalAsks) goalAsks = goalReaders.coverGoalAsks();
706
+ else {
707
+ const asks = loadGoalAskRecords({ limit: COVER_QUERY_LIMIT }, goalRunDbPath());
708
+ goalAsksTruncated = asks.length >= COVER_QUERY_LIMIT;
709
+ goalAsks = asks;
710
+ }
711
+ } catch { goalAsks = null; }
712
+ try {
713
+ if (goalReaders.coverGoalRuns) goalRuns = goalReaders.coverGoalRuns();
714
+ else {
715
+ // Cover consumes only goal identity and terminal outcome. Child-brain enrichment would query every
716
+ // runId against every registered test logs.db, even though this measurement never reads childBrain.
717
+ const runs = loadGoalRunQuery({ limit: COVER_QUERY_LIMIT }, goalRunDbPath(), { targets: [] });
718
+ goalRunsTruncated = runs?.truncated ?? false;
719
+ goalRuns = runs?.records.map(({ goalId, record }) => ({
720
+ goalId, outcome: record.outcome, stage: record.stage, startedAt: record.startedAt ?? '', completedAt: record.completedAt,
721
+ })) ?? null;
722
+ }
723
+ } catch { goalRuns = null; }
724
+ const authoredGoalIds = new Set(goalAsks?.map((ask) => ask.goalId) ?? []);
520
725
  const input: TaskAgentCoverInput = {
521
- taskAgentActions: actions === null ? null : coverEvents(actions, 'task-agent', 'action', (data) =>
522
- typeof data.kind === 'string' && typeof data.result === 'string'
523
- ? { kind: data.kind as NonNullable<TaskAgentCoverInput['taskAgentActions']>[number]['kind'], result: data.result, ...(typeof data.taskId === 'string' ? { taskId: data.taskId } : {}) } : null),
524
- allLands: lands === null ? null : coverEvents(lands, 'pr.land', 'step', (data) =>
525
- typeof data.step === 'string' && typeof data.ok === 'boolean' ? { step: data.step, ok: data.ok } : null),
526
- truncated,
726
+ taskAgentActions: actions === null ? null : coverTaskActions(actions),
727
+ liveLandMoves: moves,
728
+ allLands: lands === null ? null : coverLandSteps(lands),
729
+ truncated: { ...truncated, goalAsks: goalAsksTruncated, goalRuns: goalRunsTruncated },
730
+ // Authoring may start before this window; completion is the event being counted.
731
+ goalAsks,
732
+ goalRuns: goalRuns && (window.sinceMs !== undefined || window.untilMs !== undefined)
733
+ ? goalRuns.some((run) => authoredGoalIds.has(run.goalId)
734
+ && run.stage === 'merged' && (run.outcome === 'completed' || run.outcome === 'merged')
735
+ && !Number.isFinite(Date.parse(run.completedAt ?? '')))
736
+ ? null : goalRuns.filter((run) => inWindow(run.completedAt ?? ''))
737
+ : goalRuns,
527
738
  ...(taskCards === undefined ? {} : { taskCards }),
528
739
  };
529
- return { rows: measureTaskAgentCover(input), sevenVerbs: measureSevenVerbLive(input), limitReached: truncated.taskAgentActions || truncated.allLands, unreadableStores };
740
+ // A supplied reader overrides only its own source. The ordinary CLI always reads both physical ledgers.
741
+ try {
742
+ input.implementCardRuns = goalReaders.coverImplementCardRuns
743
+ ? goalReaders.coverImplementCardRuns()
744
+ : readImplementCardBindings(goalReaders.taskStatePath ?? taskStatePathDefault());
745
+ } catch { input.implementCardRuns = null; }
746
+ try {
747
+ input.implementRunMerges = goalReaders.coverImplementRunMerges
748
+ ? goalReaders.coverImplementRunMerges()
749
+ : input.implementCardRuns === null
750
+ ? null
751
+ : readImplementRunMerges(
752
+ input.implementCardRuns,
753
+ implementLedgerDirectories(goalReaders.taskStatePath ?? taskStatePathDefault(), goalReaders.coverIncludeTest === true),
754
+ window,
755
+ );
756
+ } catch { input.implementRunMerges = null; }
757
+ const orders = orderRead && !orderRead.limitReached ? coverEvents(orderRead.rows, 'task-agent.land-order', 'appended', (data) =>
758
+ Number.isSafeInteger(data.pr) ? { pr: data.pr as number } : null) : null;
759
+ const landSignals: CoverLandSignals = {
760
+ liveMoveEvents: moves?.length ?? null,
761
+ executedLiveMoves: moves?.filter((move) => move.executed).length ?? null,
762
+ landOrderEvents: orders?.length ?? null,
763
+ };
764
+ return { rows: measureTaskAgentCover(input), sevenVerbs: measureSevenVerbLive(input), landSignals,
765
+ limitReached: truncated.taskAgentActions || truncated.allLands || moveRead?.limitReached === true || orderRead?.limitReached === true || goalAsksTruncated || goalRunsTruncated, unreadableStores };
530
766
  }
531
767
 
532
768
  export function registerTasksCommands(program: Command, deps: TasksCliDeps = {}): void {
@@ -541,6 +777,83 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
541
777
  };
542
778
  const tasks = program.command('tasks').alias('task').description('TOX 태스크 조회 및 승인 · TASK-AGENT 과제 넘기기(hand)');
543
779
 
780
+ tasks.command('green-sweep')
781
+ .description('병합된 과제 PR 과 칸 근거를 대조해 green/기록만/건너뜀을 미리 본다 (항상 dry-run)')
782
+ .requiredOption('--version <version>', '조회할 체크리스트 판')
783
+ .option('--cell <id>', '한 칸만 조회')
784
+ .option('--traffic <path>', '실물 green 판정에는 traffic.json 필수 — 빠지거나 못 읽으면 hold 불명으로 보수 판정')
785
+ .option('--json', 'JSON 출력')
786
+ .action((opts: { version: string; cell?: string; traffic?: string; json?: boolean }) => {
787
+ try {
788
+ validateVersion(opts.version);
789
+ const snapshot = listChecklist(opts.version);
790
+ const cards = Object.values(readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(deps.taskStatePath ?? taskStatePathDefault()).tasks ?? {});
791
+ let holds: Set<string> | null = null;
792
+ if (opts.traffic) {
793
+ try {
794
+ const traffic: unknown = JSON.parse(readFileSync(opts.traffic, 'utf8'));
795
+ if (traffic && typeof traffic === 'object' && !Array.isArray(traffic)
796
+ && Array.isArray((traffic as { hold?: unknown }).hold)
797
+ && (traffic as { hold: unknown[] }).hold.every((id) => typeof id === 'string'))
798
+ holds = new Set((traffic as { hold: string[] }).hold);
799
+ } catch { /* unknown is not an empty hold set */ }
800
+ }
801
+ const prCache = new Map<number, GreenSweepPr | null>();
802
+ const ambiguousPrs = ambiguousGreenSweepPrs(snapshot.items, opts.version, cards);
803
+ const origin = spawnSync('git', ['remote', 'get-url', 'origin'],
804
+ { cwd: process.cwd(), encoding: 'utf8', timeout: 20_000 });
805
+ const originRepo = origin.status === 0
806
+ ? /github\.com[:/]([^/\s]+\/[^/\s]+?)(?:\.git)?$/i.exec(origin.stdout.trim())?.[1]?.toLowerCase() : undefined;
807
+ const localMain = spawnSync('git', ['rev-parse', 'origin/main'],
808
+ { cwd: process.cwd(), encoding: 'utf8', timeout: 20_000 });
809
+ const remoteMain = spawnSync('git', ['ls-remote', 'origin', 'refs/heads/main'],
810
+ { cwd: process.cwd(), encoding: 'utf8', timeout: 20_000 });
811
+ const localMainSha = localMain.status === 0 ? localMain.stdout.trim() : '';
812
+ const remoteMainSha = remoteMain.status === 0 ? remoteMain.stdout.trim().split(/\s+/)[0] : '';
813
+ const mainFresh = /^[a-f0-9]{40}$/i.test(localMainSha) && localMainSha === remoteMainSha;
814
+ if (opts.cell && !snapshot.items.some((item) => item.id === opts.cell)) throw new Error(`없는 칸: ${opts.cell}`);
815
+ const decisions = snapshot.items.filter((item) => !opts.cell || item.id === opts.cell).flatMap((item) => {
816
+ const candidates = greenSweepCandidates(item, opts.version, cards);
817
+ return candidates.map((candidate) => {
818
+ let pr = prCache.get(candidate.pr);
819
+ if (pr === undefined) {
820
+ const view = spawnSync('gh', ['pr', 'view', String(candidate.pr), '--json', 'number,state,mergeCommit,url'],
821
+ { cwd: process.cwd(), encoding: 'utf8', timeout: 20_000 });
822
+ try {
823
+ if (view.status !== 0) throw new Error('gh failed');
824
+ const parsed = JSON.parse(view.stdout) as { number?: number; state?: string; mergeCommit?: { oid?: string }; url?: string };
825
+ pr = parsed.number === candidate.pr && typeof parsed.state === 'string'
826
+ ? { number: candidate.pr, state: parsed.state, mergeSha: parsed.mergeCommit?.oid, url: parsed.url } : null;
827
+ } catch { pr = null; }
828
+ prCache.set(candidate.pr, pr);
829
+ }
830
+ const sha = pr?.mergeSha;
831
+ const prRepo = /^https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/\d+(?:\/|$)/i.exec(pr?.url ?? '')?.[1]?.toLowerCase();
832
+ const repositoryMatches = originRepo && prRepo ? originRepo === prRepo : null;
833
+ const ancestor = !mainFresh || !sha || !/^[a-f0-9]{40}$/i.test(sha) ? null : (() => {
834
+ const check = spawnSync('git', ['merge-base', '--is-ancestor', sha, 'origin/main'],
835
+ { cwd: process.cwd(), encoding: 'utf8', timeout: 20_000 });
836
+ return check.status === 0 ? true : check.status === 1 ? false : null;
837
+ })();
838
+ const decision = decideGreenSweep({ item, candidate, candidateCount: candidates.length,
839
+ pr: pr ?? null, ancestor, hold: holds === null ? null : holds.has(item.id), repositoryMatches,
840
+ ambiguousAcrossCells: ambiguousPrs.has(candidate.pr) });
841
+ observeGreenSweep(decision);
842
+ return decision;
843
+ });
844
+ });
845
+ const counts = { candidate: decisions.length, green: decisions.filter((d) => d.verdict === 'green').length,
846
+ recordOnly: decisions.filter((d) => d.verdict === 'record-only').length,
847
+ skipped: decisions.filter((d) => d.verdict === 'skip').length };
848
+ if (opts.json) out(JSON.stringify({ dryRun: true, version: opts.version, counts, decisions }));
849
+ else {
850
+ out(`dry-run · ${opts.version} · 후보 ${counts.candidate} · green ${counts.green} · 기록만 ${counts.recordOnly} · 건너뜀 ${counts.skipped}`);
851
+ for (const d of decisions) out(`${d.verdict}\t${d.cell}\t#${d.pr}\t${d.mergeSha ?? '-'}\t${d.reason}\t${d.evidence}\tbefore=${JSON.stringify(d.existingEvidence)}\tafter=${JSON.stringify(d.plannedEvidence)}`);
852
+ if (holds === null) out('⚠ hold 원장을 읽지 못해 green 없이 기록만 판정');
853
+ }
854
+ } catch (error) { out(`green-sweep 조회 실패: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
855
+ });
856
+
544
857
  tasks.command('cover')
545
858
  .description('TASK-AGENT 가 대신한 착지 비율과 리뷰·재발사·green 의 아직 없는 분모를 조회(읽기 전용)')
546
859
  .option('--since <iso>', '시작 시각 (ISO)')
@@ -548,7 +861,8 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
548
861
  .option('--json', 'JSON 출력')
549
862
  .option('--lines', '동사별 한 줄 요약 출력 (읽기 전용)')
550
863
  .option('--by-card', 'review·retry·green 분모를 카드 원장(창 안 createdAt 카드 수)으로 센다 (읽기 전용)')
551
- .action(async (opts: { since?: string; until?: string; json?: boolean; lines?: boolean; byCard?: boolean }) => {
864
+ .option('--include-test', '시험 우주 logs.db 도 운영 우주와 합산(기본은 운영만)')
865
+ .action(async (opts: { since?: string; until?: string; json?: boolean; lines?: boolean; byCard?: boolean; includeTest?: boolean }) => {
552
866
  const parseIso = (value: string | undefined, flag: string): number | undefined => {
553
867
  if (value === undefined) return undefined;
554
868
  const time = Date.parse(value);
@@ -568,18 +882,23 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
568
882
  }
569
883
  taskCards = coverCardsInWindow(cards, { sinceMs, untilMs });
570
884
  }
571
- const { rows, sevenVerbs, limitReached, unreadableStores } = readTaskAgentCover({ sinceMs, untilMs }, deps.coverLogs, taskCards);
885
+ const scopedCoverLogs = (query: LogQuery) => (deps.coverLogs ?? queryCoverLogs)(query, opts.includeTest === true);
886
+ const { rows, sevenVerbs, landSignals, limitReached, unreadableStores } = readTaskAgentCover(
887
+ { sinceMs, untilMs }, scopedCoverLogs, taskCards, { ...deps, coverIncludeTest: opts.includeTest === true },
888
+ );
572
889
  const limitWarning = limitReached ? `조회 상한 ${COVER_QUERY_LIMIT} 도달 — 더 오래된 사건이 있을 수 있다(절단된 수를 전체로 읽지 말 것)` : null;
573
890
  const storeWarning = unreadableStores > 0 ? `logs.db ${unreadableStores}개를 못 읽었다 — 그 우주의 사건은 수에 없다(«없다»로 읽지 말 것)` : null;
574
- if (opts.json) out(JSON.stringify({ rows, sevenVerbs, limitReached, unreadableStores, ...(limitWarning ? { warning: limitWarning } : {}), ...(storeWarning ? { storeWarning } : {}) }));
891
+ if (opts.json) out(JSON.stringify({ rows, sevenVerbs, landSignals, limitReached, unreadableStores, ...(limitWarning ? { warning: limitWarning } : {}), ...(storeWarning ? { storeWarning } : {}) }));
575
892
  else {
576
893
  if (opts.lines) {
577
894
  for (const row of rows) out(formatTaskAgentCover(row));
578
895
  out(formatSevenVerbLive(sevenVerbs));
896
+ out(formatCoverLandSignals(landSignals));
579
897
  }
580
898
  else {
581
899
  out('동사\t대신\t전체\t비율\t상태\t관측 사건');
582
900
  for (const row of rows) out(`${row.verb}\t${row.byTaskAgent ?? '-'}\t${row.total ?? '-'}\t${row.ratio ?? '-'}\t${row.state}\t${row.observedActions ?? '-'}`);
901
+ out(formatCoverLandSignals(landSignals));
583
902
  }
584
903
  if (limitWarning) out(`⚠ ${limitWarning}`);
585
904
  if (storeWarning) out(`⚠ ${storeWarning}`);
@@ -624,6 +943,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
624
943
  .description('TASK-AGENT 에 과제 한 줄을 넘긴다 — 카드를 적고 첫 수(harness say --substrate pod --merge-by-host)를 고른다 · 기본 shadow(발사 0) · `--mission` 이면 조각 카드 ⊕ 선후로 쪼개 선행 없는 조각만 넘긴다')
625
944
  .addOption(new Option('--seat <seat>', `과제 자리 (${TASK_SEATS.join('|')})`).choices([...TASK_SEATS]))
626
945
  .option('--checklist <cellId>', '릴리스 체크리스트 칸 id')
946
+ .option('--version <version>', '체크리스트 판 고정(생략하면 발행 전 판을 가까운 순서로 탐색)')
627
947
  .addOption(new Option('--completion <kind>', `종결 종류 (${COMPLETION_KINDS.join('|')} · 기본 code-pr = PR 병합)`).choices([...COMPLETION_KINDS]))
628
948
  .option('--mission <text>', '미션 문면 — 조각(`- …`·`① …`·`1. …` ⊕ `after: 1`)으로 나열돼 있으면 결정론, 아니면 분해기로 쪼갠다')
629
949
  .option('--project <id>', '프로젝트 id — --target 과 함께')
@@ -635,7 +955,72 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
635
955
  .option('--child-llm <provider/model>', 'TASKS-HAND-CHILD-LLM — 구현 자식 LLM(예: grok/grok-4.7) → 발사 인자 --child-llm-provider <provider> --child-llm-model <model> · «첫» `/` 에서만 가른다(모델 id 의 `/` 는 보존) · openrouter 는 모델 id 가 openrouter/ 접두를 포함하므로 전체를 모델로(openrouter/z-ai/glm-5.3 → --child-llm-model openrouter/z-ai/glm-5.3) · 카드 childLlm · 안 주면 명령·카드 무변경')
636
956
  .option('--child-llm-effort <level>', '구현 자식 추론 노력 → 발사 인자 --child-llm-effort(검증은 하니스 · --child-llm 과 함께)')
637
957
  .option('--json', 'JSON 출력')
638
- .action(async (text: string | undefined, opts: { seat?: TaskSeat; checklist?: string; completion?: CompletionKind; mission?: string; project?: string; target?: string; goal?: string; milestone?: string; live?: boolean; taLand?: boolean; childLlm?: string; childLlmEffort?: string; json?: boolean }) => {
958
+ .action(async (text: string | undefined, opts: { seat?: TaskSeat; checklist?: string; version?: string; completion?: CompletionKind; mission?: string; project?: string; target?: string; goal?: string; milestone?: string; live?: boolean; taLand?: boolean; childLlm?: string; childLlmEffort?: string; json?: boolean }) => {
959
+ const suppliedText = text !== undefined;
960
+ if (opts.version !== undefined && opts.checklist === undefined) {
961
+ out('--version 은 --checklist 와 함께 준다');
962
+ process.exitCode = 1;
963
+ return;
964
+ }
965
+ if (opts.checklist !== undefined) {
966
+ const findTitle = (nodes: BoardNode[]): string | undefined => {
967
+ for (const node of nodes) {
968
+ if (node.level === 'task' && node.id === opts.checklist) return node.title;
969
+ const nested = node.children ? findTitle(node.children) : undefined;
970
+ if (nested !== undefined) return nested;
971
+ }
972
+ return undefined;
973
+ };
974
+ if (opts.version !== undefined) {
975
+ try { validateVersion(opts.version); }
976
+ catch (error) {
977
+ out(error instanceof Error ? error.message : String(error));
978
+ process.exitCode = 1;
979
+ return;
980
+ }
981
+ }
982
+ const versions = opts.version !== undefined ? [opts.version]
983
+ : [checklistDevVersion(), ...(deps.releaseVersions?.() ?? (deps.releaseBoard ? [] : (() => {
984
+ const released = releasedVersion();
985
+ return assignedVersions().filter((version) => !released || compareVersions(version, released) > 0);
986
+ })()))];
987
+ const current = checklistDevVersion();
988
+ const orderedVersions = [...new Set(versions)].sort((a, b) => {
989
+ if (a === current) return -1;
990
+ if (b === current) return 1;
991
+ const aFuture = compareVersions(a, current) > 0;
992
+ const bFuture = compareVersions(b, current) > 0;
993
+ if (aFuture !== bFuture) return aFuture ? -1 : 1;
994
+ return aFuture ? compareVersions(a, b) : compareVersions(b, a);
995
+ });
996
+ const matches: Array<{ version: string; title: string }> = [];
997
+ for (const version of orderedVersions) {
998
+ try {
999
+ const title = findTitle((deps.releaseBoard ?? releasePackBoard)(version));
1000
+ if (title !== undefined) matches.push({ version, title });
1001
+ } catch (error) {
1002
+ if (opts.version !== undefined) {
1003
+ out(`체크리스트 판 읽기 실패: ${version}`);
1004
+ process.exitCode = 1;
1005
+ return;
1006
+ }
1007
+ console.error(`⚠ 체크리스트 판 ${version} 읽기 실패 — 다른 판 계속 조회: ${error instanceof Error ? error.message : String(error)}`);
1008
+ }
1009
+ }
1010
+ const title = matches[0]?.title;
1011
+ if (title === undefined) {
1012
+ out(`체크리스트 칸 없음: ${opts.checklist}`);
1013
+ process.exitCode = 1;
1014
+ return;
1015
+ }
1016
+ if (matches.length > 1) console.error(`⚠ 체크리스트 칸 ${opts.checklist} 중복: ${matches.map((match) => match.version).join(', ')} — 가장 가까운 판 ${matches[0]!.version} 선택 (--version 으로 고정 가능)`);
1017
+ if (opts.mission === undefined && text === undefined) text = title;
1018
+ }
1019
+ if (opts.checklist !== undefined && opts.mission === undefined && suppliedText && !text!.split(/\r?\n/, 1)[0]!.startsWith('대상 경로:')) {
1020
+ out('칸 문면을 쓰려면 문면 인자를 빼라');
1021
+ process.exitCode = 1;
1022
+ return;
1023
+ }
639
1024
  await registerTaskAgentSink(deps.registerSink);
640
1025
  if ((text === undefined) === (opts.mission === undefined)) {
641
1026
  out('과제 한 줄 «또는» --mission <문면> 중 하나만 준다');
@@ -705,6 +1090,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
705
1090
  ...(opts.childLlm !== undefined ? { childLlm: opts.childLlm } : {}),
706
1091
  ...(opts.childLlmEffort !== undefined ? { childLlmEffort: opts.childLlmEffort } : {}),
707
1092
  ...(deps.taskStatePath ? { statePath: deps.taskStatePath } : {}),
1093
+ ...(deps.taskCompositionRoot ? { compositionRoot: deps.taskCompositionRoot } : {}),
708
1094
  launcher: deps.taskLauncher ?? defaultTaskLauncher,
709
1095
  });
710
1096
  if (opts.json) out(JSON.stringify(result));
@@ -712,7 +1098,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
712
1098
  out(`${result.card.id}\t${result.mode}\t${result.launched ? 'launched' : 'not launched'}`);
713
1099
  out(`elanous ${result.move.command!.map(shellQuote).join(' ')}`);
714
1100
  if (result.cwd) out(`(cwd: ${result.cwd})`);
715
- if (result.card.taLand) out('(ta-land — 발사 env ELANOUS_TA_LAND=1 · 로컬 부모 · taskAgent.liveMoves 에 propose-land 가 있으면 병합은 TASK-AGENT 몫, 없으면 종전대로 호스트 병합)');
1101
+ if (result.card.taLand) out('(ta-land — 발사 env ELANOUS_TA_LAND=1 · 원격 또는 로컬 부모 · taskAgent.liveMoves 에 propose-land 가 있으면 병합은 TASK-AGENT 몫, 없으면 종전대로 호스트 병합)');
716
1102
  if (!result.launched) out('(shadow — 띄우지 않았다 · 띄우려면 --live)');
717
1103
  }
718
1104
  } catch (error) {
@@ -820,6 +1206,74 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
820
1206
  for (const row of report.rows) out([row.pid, row.ageHours, row.seat ?? '-', row.version ?? '-', row.runId ?? '-', row.reasons.join(',')].join('\t'));
821
1207
  });
822
1208
 
1209
+ tasks.command('adopt-pr <number>')
1210
+ .description('기존 OPEN/main PR을 TA 카드에 묶고 다음 tasks rejudge 대상에 넣는다(구현 발사 0)')
1211
+ .option('--card <id>', '기존 launched 카드(미바인딩 또는 같은 PR이 등록부에서 바인딩됨)')
1212
+ .option('--checklist <id>', '새 카드에 붙일 체크리스트 칸')
1213
+ .option('--version <version>', '체크리스트 판(기존 카드에도 고정 가능)')
1214
+ .option('--unlinked-terminal', '기존 카드의 모든 구현 자식이 종결되고 PR 사건이 전혀 없을 때만 별도로 열린 PR을 명시적으로 입양')
1215
+ .option('--json', 'JSON 출력')
1216
+ .action(async (value: string, opts: { card?: string; checklist?: string; version?: string; unlinkedTerminal?: boolean; json?: boolean }) => {
1217
+ await registerTaskAgentSink(deps.registerSink);
1218
+ try {
1219
+ if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value))) throw new Error(`PR 번호가 아니다: ${value}`);
1220
+ if ((opts.card === undefined) === (opts.checklist === undefined))
1221
+ throw new Error('--card 또는 --checklist [--version] 중 하나만 준다');
1222
+ if (opts.unlinkedTerminal && !opts.card) throw new Error('--unlinked-terminal은 --card와 함께 준다');
1223
+ if (opts.version) validateVersion(opts.version);
1224
+ let cardVersion = opts.version;
1225
+ let observedChecklist: { id: string; version: string; status: string; cardVersion?: string } | undefined;
1226
+ if (opts.card) {
1227
+ const current = readTaskCard(opts.card, deps.taskStatePath ?? taskStatePathDefault());
1228
+ if (!current?.checklistId) throw new Error(`카드 ${opts.card}의 칸이 없다`);
1229
+ const versions = opts.version ? [opts.version] : [...new Set([checklistDevVersion(), ...(deps.releaseVersions?.() ?? assignedVersions())])];
1230
+ const matches = versions.filter((version) => {
1231
+ try { return (deps.adoptChecklist ?? listChecklist)(version).items.some((item) => item.id === current.checklistId); }
1232
+ catch (error) { if (opts.version) throw error; return false; }
1233
+ });
1234
+ if (matches.length !== 1) throw new Error(matches.length ? `칸 ${current.checklistId}가 여러 판에 있다 — --version 지정: ${matches.join(', ')}` : `체크리스트 칸 없음: ${current.checklistId}`);
1235
+ cardVersion = matches[0]!;
1236
+ const item = (deps.adoptChecklist ?? listChecklist)(cardVersion).items.find((candidate) => candidate.id === current.checklistId)!;
1237
+ if (item.status === 'green' || item.status === 'done') throw new Error(`이미 완료된 칸은 새 PR 입양 대상이 아니다: ${cardVersion}/${item.id}`);
1238
+ observedChecklist = { id: item.id, version: cardVersion, status: item.status, cardVersion: current.checklistVersion };
1239
+ }
1240
+ let checklist: { version: string; id: string; title: string; status: string } | undefined;
1241
+ if (opts.checklist) {
1242
+ const versions = opts.version ? [opts.version] : [...new Set([checklistDevVersion(), ...(deps.releaseVersions?.() ?? assignedVersions())])];
1243
+ const matches = versions.flatMap((version) => {
1244
+ try { return (deps.adoptChecklist ?? listChecklist)(version).items.filter((item) => item.id === opts.checklist).map((item) => ({ version, id: item.id, title: item.title, status: item.status })); }
1245
+ catch (error) { if (opts.version) throw error; return []; }
1246
+ });
1247
+ if (matches.length !== 1) throw new Error(matches.length ? `칸 ${opts.checklist}가 여러 판에 있다 — --version 지정: ${matches.map((item) => item.version).join(', ')}` : `체크리스트 칸 없음: ${opts.checklist}`);
1248
+ checklist = matches[0];
1249
+ }
1250
+ const number = Number(value);
1251
+ const origin = execFileSync('git', ['remote', 'get-url', 'origin'], { cwd: process.cwd(), encoding: 'utf8' }).trim().replace(/\.git\/?$/, '');
1252
+ const repo = origin.match(/^(?:git@github\.com:|https:\/\/github\.com\/|ssh:\/\/git@github\.com\/)([^/\s]+\/[^/\s]+)\/?$/)?.[1];
1253
+ if (!repo) throw new Error(`현재 worktree origin이 GitHub 저장소가 아니다: ${origin}`);
1254
+ const view = (deps.adoptPrView ?? ((pr: number, cwd: string) => JSON.parse(execFileSync('gh', ['pr', 'view', String(pr), '--repo', repo, '--json', 'number,url,state,baseRefName,headRefOid'], { cwd, encoding: 'utf8' }))))(number, process.cwd());
1255
+ if (view.number !== number) throw new Error(`조회 PR 번호가 다르다: 요청 #${number} · 응답 #${view.number}`);
1256
+ const { adoptExistingPr, adoptedRunTerminal } = await import('../task-agent/adopt-pr.js');
1257
+ const targetCard = opts.card ? readTaskCard(opts.card, deps.taskStatePath ?? taskStatePathDefault()) : undefined;
1258
+ const checkRun = deps.adoptRunTerminal ?? adoptedRunTerminal;
1259
+ const linkedEvidence = targetCard ? checkRun(targetCard, view) : undefined;
1260
+ const terminalRunEvidence = linkedEvidence || (opts.unlinkedTerminal && targetCard ? checkRun(targetCard, view, undefined, true) : false);
1261
+ const unlinkedTerminalUsed = opts.unlinkedTerminal === true && linkedEvidence !== true;
1262
+ const chosen = observedChecklist ?? checklist;
1263
+ const result = adoptExistingPr({ pr: view, repo, statePath: deps.taskStatePath ?? taskStatePathDefault(),
1264
+ ...(opts.card ? { cardId: opts.card, version: cardVersion, terminalRunEvidence, unlinkedTerminal: unlinkedTerminalUsed, observedChecklist,
1265
+ observedRun: { runId: targetCard?.runId, runChildId: targetCard?.runChildId, parentHost: targetCard?.parentHost?.host, launchId: targetCard?.launchId } } : {}),
1266
+ ...(chosen ? { checklistAtWrite: () => (deps.adoptChecklist ?? listChecklist)(chosen.version).items.find((item) => item.id === chosen.id) } : {}),
1267
+ ...(checklist ? { checklist } : {}) });
1268
+ try { debug.log('task-agent', 'pr-adopted', { card: result.card.id, pr: number, head: view.headRefOid, created: result.created, changed: result.changed, launched: false }); } catch { /* fail-soft */ }
1269
+ if (opts.json) out(JSON.stringify({ card: result.card.id, pr: number, head: view.headRefOid, created: result.created, changed: result.changed, next: `tasks rejudge ${result.card.id}` }));
1270
+ else out(`${result.card.id}\tPR #${number}\t${result.changed ? 'adopted' : 'already-adopted'}\t구현 발사 0 · 다음: tasks rejudge ${result.card.id}`);
1271
+ } catch (error) {
1272
+ out(error instanceof Error ? error.message : String(error));
1273
+ process.exitCode = 1;
1274
+ }
1275
+ });
1276
+
823
1277
  tasks.command('adopt-runs')
824
1278
  .description('TA-OWN-ALL-RUNS 백필 — 발사 등록부의 이 우주 런 중 카드 없는 것에 `ta-<runId>` 카드를 만든다(발사 훅 이전 런 몫 · 있으면 건드리지 않는다)')
825
1279
  .option('--json', 'JSON 출력')
@@ -961,6 +1415,9 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
961
1415
  for (const cardId of bindable) {
962
1416
  // HARNESS-PARENT-ON-MSB1: 원격 부모 런은 원장이 그 호스트에 있다 — HQ 거울로 먼저 당긴다(읽기 전용).
963
1417
  await mirrorRemoteCardLedger(cardId, deps);
1418
+ try { await replayRemoteCardMoves(cardId, deps.taskStatePath ?? taskStatePathDefault(), deps.cardRunEvidence ?? {}); } catch (error) {
1419
+ try { debug.log('task-agent', 'card-binding-replay-failed', { card: cardId, reason: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
1420
+ }
964
1421
  try { await refreshCardRunBinding(cardId, deps.taskStatePath ?? taskStatePathDefault(), deps.cardRunEvidence ?? {}); } catch (error) {
965
1422
  // 근거 없음으로 보인다 — 원장 부재와 갈리게 실패는 남긴다.
966
1423
  try { debug.log('task-agent', 'card-bind-failed', { card: cardId, via: 'show', error: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
@@ -1033,13 +1490,25 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
1033
1490
  const { rejudgeCandidates, rejudgeCardOnHead } = await import('../task-agent/rejudge-on-head.js');
1034
1491
  const statePath = deps.taskStatePath ?? taskStatePathDefault();
1035
1492
  let ids: string[];
1036
- try { ids = id ? [id] : rejudgeCandidates(readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(statePath).tasks ?? {}); } catch (error) {
1493
+ try {
1494
+ if (!id) {
1495
+ const { bindMirroredRemoteRejudgeCards } = await import('../task-agent/remote-rejudge-bind.js');
1496
+ const cards = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(statePath).tasks ?? {};
1497
+ const binding = await bindMirroredRemoteRejudgeCards(cards, statePath);
1498
+ try { debug.log('task-agent', 'remote-rejudge-bind', binding); } catch { /* fail-soft */ }
1499
+ }
1500
+ ids = id ? [id] : rejudgeCandidates(readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(statePath).tasks ?? {});
1501
+ } catch (error) {
1037
1502
  out(error instanceof Error ? error.message : '과제 카드 읽기 실패'); process.exitCode = 1; return;
1038
1503
  }
1039
1504
  const { configuredTaskAgentLiveMoves } = await import('../task-agent/live-moves.js');
1040
1505
  const liveMoves = deps.rejudge?.liveMoves ?? await configuredTaskAgentLiveMoves();
1041
1506
  const all = [];
1042
1507
  for (const cardId of ids) {
1508
+ await mirrorRemoteCardLedger(cardId, deps);
1509
+ try { await replayRemoteCardMoves(cardId, statePath, deps.cardRunEvidence ?? {}); } catch (error) {
1510
+ try { debug.log('task-agent', 'card-binding-replay-failed', { card: cardId, reason: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
1511
+ }
1043
1512
  let outcomes: Awaited<ReturnType<typeof rejudgeCardOnHead>>;
1044
1513
  try { outcomes = await rejudgeCardOnHead(cardId, { ...deps.rejudge, liveMoves, live: { ...deps.rejudge?.live, statePath } }); } catch (error) {
1045
1514
  // 카드 하나의 예외가 --all 의 나머지 카드 판단을 멈추지 않게 — 사유를 남기고 다음 카드로.
@@ -1073,9 +1542,37 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
1073
1542
  try { debug.log('task-agent', 'regate-run', { card: opts.card, ...record, failures: result.failures.slice(0, 3) }); } catch { /* fail-soft */ }
1074
1543
  mkdir(dir(opts.out), { recursive: true });
1075
1544
  write(opts.out, JSON.stringify(record));
1545
+ if (deps.regateCompletion !== false) {
1546
+ try {
1547
+ const { rejudgeCompletedTaskRegate } = await import('../task-agent/rejudge-on-head.js');
1548
+ await rejudgeCompletedTaskRegate(opts.card, pr, opts.head, opts.out, deps.taskStatePath ?? taskStatePathDefault(), undefined, deps.rejudge);
1549
+ } catch (error) {
1550
+ try { debug.log('task-agent', 'regate-completion-rejudge-failed', { card: opts.card, pr, head: opts.head, reason: String(error).slice(-300) }, { level: 'warn' }); } catch { /* fail-soft */ }
1551
+ }
1552
+ }
1076
1553
  if (!result.passed) process.exitCode = 1;
1077
1554
  });
1078
1555
 
1556
+ tasks.command('repair-run', { hidden: true })
1557
+ .description('TA-REPAIR-SAME-BRANCH 내부 — source PR 머리에서 commit-only Pod 수리를 실행하고 결과를 파일로 남긴다')
1558
+ .requiredOption('--card <id>').requiredOption('--pr <n>').requiredOption('--head <sha>')
1559
+ .requiredOption('--repo <root>').requiredOption('--out <path>').requiredOption('--goal-file <path>')
1560
+ .requiredOption('--run-id <id>').requiredOption('--model-provider <provider>').requiredOption('--model-id <id>')
1561
+ .option('--seat <seat>')
1562
+ .action(async (opts: { card: string; pr: string; head: string; repo: string; out: string; goalFile: string;
1563
+ runId: string; modelProvider: string; modelId: string; seat?: string }) => {
1564
+ const { readFileSync } = await import('node:fs');
1565
+ const { getElanousConfigDirOverride } = await import('../elanous-config-dir.js');
1566
+ const { effectiveInstanceRoot } = await import('../instance/resolve.js');
1567
+ const { runRepairWorker } = await import('../task-agent/repair-worker.js');
1568
+ const result = await runRepairWorker({ card: opts.card, pr: Number(opts.pr), head: opts.head,
1569
+ repo: opts.repo, resultPath: opts.out, entry: ELANOUS_CLI_ENTRY,
1570
+ configDir: getElanousConfigDirOverride() ?? effectiveInstanceRoot(), runId: opts.runId,
1571
+ model: { provider: opts.modelProvider, model: opts.modelId }, seat: opts.seat,
1572
+ goal: readFileSync(opts.goalFile, 'utf8') });
1573
+ if (!result.ok) process.exitCode = 1;
1574
+ });
1575
+
1079
1576
  tasks.command('approve <id...>')
1080
1577
  .description('외부 태스크 승인')
1081
1578
  .action(async (ids: string[]) => {