elanous 0.2.18 → 0.2.20

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 (350) 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/{1307-30dff22c9e1f3073.js → 1307-77fad80cf54477c6.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +25 -0
  14. package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d92f7ee3994e84c7.js → page-c855c24de7db74fe.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-4733c967b56a4277.js → page-85b316bb4235980b.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/board/{page-d22fd849cc22f104.js → page-9b2b26a33cb2cd42.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-bb742f995c6769e7.js → page-c9955fde67b1bcb5.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/bots/{page-02b073a2ac793b83.js → page-7a852956c74015a3.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/chat/{page-8a9b3594bc23289b.js → page-4ba4abe096b6c2a8.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/consult/page-575d4a8b4f7becb2.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/control/{page-308111abadf35670.js → page-f5d56ff5f45ad164.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/decisions/page-2260e9eef0357421.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-5d97b2901e0d1ea6.js → page-7c058069c5e7fa63.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/exec/{page-8c3d8e63ae2e1c98.js → page-466bf0ee16ad1432.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/field/{page-9b72a07d424701c6.js → page-a23bc71713bcc08f.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/intake/{page-a572f58cd3ca5864.js → page-9cd565005b93aca6.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/live/{page-be5ea965a07fbc41.js → page-474cb5888ea0e8f3.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/market/{page-fa244bd146a00ae8.js → page-9ca21f9ad3208a61.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-44d56d6391965a6a.js → page-f9ccb1b1adbea14d.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/morning/{page-83a700d4c754d802.js → page-ec102c9b8b691e37.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/{not-found-7b3170f374f77b9d.js → not-found-a9c2cfa8dda922a5.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1467047104141297.js → page-db03ae3589a81c9e.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4a6463b97f0ad94f.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-102310930791197a.js → page-75bffecc9aa1004e.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/settings/{page-6ed0b73bae8351d8.js → page-31d5b875760bb62d.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/setup/{page-43336055451f6e37.js → page-68793c6e10331d66.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/app/share/{page-3cc47dca0922ab9a.js → page-46d5201d2207a511.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-a77ee355a2304ff0.js → page-d81c07a7b4f618ae.js} +1 -1
  55. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-b17cd46cf0e9b8cf.js → page-be1c4e8697f22360.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/app/term/page-cf0a4b4abead71ea.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/trace/{page-7bf54c9bb9274af8.js → page-0fd895fab9b62c1b.js} +1 -1
  59. package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/workflows/page-0acc8e198f7b0dfb.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-134da468538baf48.js → page-34882c84f84021c5.js} +1 -1
  63. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/{webpack-c2782b330447ffb6.js → webpack-c260571513bf43c2.js} +1 -1
  65. package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
  66. package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +1 -0
  67. package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
  68. package/apps/pwa/out/approvals/index.html +1 -1
  69. package/apps/pwa/out/approvals/index.txt +14 -14
  70. package/apps/pwa/out/autopilot/index.html +1 -1
  71. package/apps/pwa/out/autopilot/index.txt +14 -14
  72. package/apps/pwa/out/board/index.html +1 -1
  73. package/apps/pwa/out/board/index.txt +14 -14
  74. package/apps/pwa/out/botlab/index.html +1 -1
  75. package/apps/pwa/out/botlab/index.txt +14 -14
  76. package/apps/pwa/out/bots/index.html +1 -1
  77. package/apps/pwa/out/bots/index.txt +14 -14
  78. package/apps/pwa/out/ceo/index.html +1 -1
  79. package/apps/pwa/out/ceo/index.txt +14 -14
  80. package/apps/pwa/out/chat/index.html +1 -1
  81. package/apps/pwa/out/chat/index.txt +14 -14
  82. package/apps/pwa/out/consult/index.html +1 -1
  83. package/apps/pwa/out/consult/index.txt +14 -14
  84. package/apps/pwa/out/control/index.html +1 -1
  85. package/apps/pwa/out/control/index.txt +14 -14
  86. package/apps/pwa/out/decisions/index.html +1 -1
  87. package/apps/pwa/out/decisions/index.txt +14 -14
  88. package/apps/pwa/out/design-check/index.html +1 -1
  89. package/apps/pwa/out/design-check/index.txt +14 -14
  90. package/apps/pwa/out/editor/index.html +1 -1
  91. package/apps/pwa/out/editor/index.txt +16 -15
  92. package/apps/pwa/out/exec/index.html +1 -1
  93. package/apps/pwa/out/exec/index.txt +14 -14
  94. package/apps/pwa/out/field/index.html +1 -1
  95. package/apps/pwa/out/field/index.txt +14 -14
  96. package/apps/pwa/out/healthz +1 -1
  97. package/apps/pwa/out/index.html +1 -1
  98. package/apps/pwa/out/index.txt +14 -14
  99. package/apps/pwa/out/inside/index.html +1 -1
  100. package/apps/pwa/out/inside/index.txt +16 -15
  101. package/apps/pwa/out/intake/index.html +1 -1
  102. package/apps/pwa/out/intake/index.txt +14 -14
  103. package/apps/pwa/out/live/index.html +1 -1
  104. package/apps/pwa/out/live/index.txt +14 -14
  105. package/apps/pwa/out/loops/index.html +1 -1
  106. package/apps/pwa/out/loops/index.txt +24 -23
  107. package/apps/pwa/out/market/index.html +1 -1
  108. package/apps/pwa/out/market/index.txt +14 -14
  109. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  110. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  111. package/apps/pwa/out/morning/index.html +1 -1
  112. package/apps/pwa/out/morning/index.txt +14 -14
  113. package/apps/pwa/out/observatory/index.html +1 -1
  114. package/apps/pwa/out/observatory/index.txt +14 -14
  115. package/apps/pwa/out/ops/checklist/index.html +1 -1
  116. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  117. package/apps/pwa/out/ops/release/index.html +1 -1
  118. package/apps/pwa/out/ops/release/index.txt +14 -14
  119. package/apps/pwa/out/ops/seats/index.html +1 -1
  120. package/apps/pwa/out/ops/seats/index.txt +14 -14
  121. package/apps/pwa/out/outputs/index.html +1 -1
  122. package/apps/pwa/out/outputs/index.txt +14 -14
  123. package/apps/pwa/out/reflection/index.html +1 -1
  124. package/apps/pwa/out/reflection/index.txt +15 -15
  125. package/apps/pwa/out/scheduler/index.html +1 -1
  126. package/apps/pwa/out/scheduler/index.txt +14 -14
  127. package/apps/pwa/out/sessions/index.html +1 -1
  128. package/apps/pwa/out/sessions/index.txt +14 -14
  129. package/apps/pwa/out/settings/devices/index.html +1 -1
  130. package/apps/pwa/out/settings/devices/index.txt +14 -14
  131. package/apps/pwa/out/settings/index.html +1 -1
  132. package/apps/pwa/out/settings/index.txt +14 -14
  133. package/apps/pwa/out/setup/done/index.html +1 -1
  134. package/apps/pwa/out/setup/done/index.txt +14 -14
  135. package/apps/pwa/out/setup/index.html +1 -1
  136. package/apps/pwa/out/setup/index.txt +14 -14
  137. package/apps/pwa/out/share/index.html +1 -1
  138. package/apps/pwa/out/share/index.txt +14 -14
  139. package/apps/pwa/out/showroom/index.html +1 -1
  140. package/apps/pwa/out/showroom/index.txt +14 -14
  141. package/apps/pwa/out/tasks/index.html +1 -1
  142. package/apps/pwa/out/tasks/index.txt +14 -14
  143. package/apps/pwa/out/term/index.html +1 -1
  144. package/apps/pwa/out/term/index.txt +14 -14
  145. package/apps/pwa/out/today/index.html +1 -1
  146. package/apps/pwa/out/today/index.txt +14 -14
  147. package/apps/pwa/out/trace/index.html +1 -1
  148. package/apps/pwa/out/trace/index.txt +14 -14
  149. package/apps/pwa/out/vault/index.html +1 -1
  150. package/apps/pwa/out/vault/index.txt +14 -14
  151. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  152. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  153. package/apps/pwa/out/workflows/index.html +1 -1
  154. package/apps/pwa/out/workflows/index.txt +14 -14
  155. package/apps/pwa/out/workspace/index.html +1 -1
  156. package/apps/pwa/out/workspace/index.txt +14 -14
  157. package/apps/pwa/out/worktrees/index.html +1 -1
  158. package/apps/pwa/out/worktrees/index.txt +14 -14
  159. package/graphs/demo/editor-recipes.yaml +22 -0
  160. package/graphs/ops/guardian.yaml +26 -0
  161. package/graphs/ops/recipes.yaml +3 -0
  162. package/graphs/retro/daily.yaml +25 -0
  163. package/graphs/retro/recipes.yaml +24 -0
  164. package/package.json +1 -1
  165. package/scripts/ask-marker-check.ts +3 -1
  166. package/scripts/coord-channel-watch.sh +2 -2
  167. package/scripts/discord-seat-probe.ts +3 -1
  168. package/scripts/land-global-checks.ts +89 -0
  169. package/scripts/lib/pch-regress-map.ts +24 -0
  170. package/scripts/morning-report.ts +18 -4
  171. package/scripts/pch-regress.ts +88 -0
  172. package/scripts/release-loop/gate-diff.ts +18 -0
  173. package/scripts/release-loop/gate-node.ts +628 -65
  174. package/scripts/release-loop/gate-partial.ts +106 -0
  175. package/scripts/release-loop/gate-timing.ts +229 -0
  176. package/scripts/release-loop/preflight.ts +6 -4
  177. package/scripts/release-loop/publish-window.ts +141 -0
  178. package/scripts/release-loop/release-docs.ts +1 -4
  179. package/scripts/release-loop/release-universe.ts +38 -0
  180. package/scripts/release-loop/resume-release.ts +67 -5
  181. package/scripts/release-loop/shard-plan.ts +65 -0
  182. package/scripts/release-loop/unattended-release.ts +12 -2
  183. package/scripts/release-story/draft.ts +5 -1
  184. package/scripts/retro/shadow-compare.ts +71 -0
  185. package/scripts/rhythm/daily.ts +6 -1
  186. package/scripts/video/scene-table.ts +159 -0
  187. package/src/acp/core-turn-bridge.ts +39 -2
  188. package/src/agent-mission/driver.ts +34 -6
  189. package/src/autopilot/build/llm-conflict-merge.ts +132 -7
  190. package/src/autopilot/build/merge-intent.ts +135 -7
  191. package/src/away/presence.ts +40 -0
  192. package/src/away/release-watch.ts +272 -0
  193. package/src/chat/index.ts +2 -1
  194. package/src/chat/short-question.ts +3 -40
  195. package/src/cli/away-cli.ts +68 -0
  196. package/src/cli/config-drift.ts +513 -0
  197. package/src/cli/guardian-cli.ts +49 -0
  198. package/src/cli/intake-cli.ts +13 -7
  199. package/src/cli/log-event-names.ts +5 -0
  200. package/src/cli/pod-cli.ts +5 -1
  201. package/src/cli/pr-cli.ts +20 -0
  202. package/src/cli/pty-drive-cli.ts +14 -3
  203. package/src/cli/release-cli.ts +52 -12
  204. package/src/cli/retro-cli.ts +78 -0
  205. package/src/cli/setup-cli.ts +31 -1
  206. package/src/cli/tasks-cli.ts +193 -14
  207. package/src/context-bus/context-now-public.ts +1 -1
  208. package/src/context-bus/context-now-surfaces.ts +16 -6
  209. package/src/context-bus/context-now.ts +97 -3
  210. package/src/core-turn/short-question.ts +100 -0
  211. package/src/dashboard/dashboard-status-lines.ts +24 -4
  212. package/src/dashboard/index.ts +50 -5
  213. package/src/dashboard/input/slash-executor.ts +6 -2
  214. package/src/dashboard/slash-runtime/dashboard-handlers.ts +68 -40
  215. package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
  216. package/src/dashboard/slash-runtime/help-from-registry.ts +62 -23
  217. package/src/dashboard/slash-runtime/help-groups.ts +44 -0
  218. package/src/dashboard/slash-runtime/model-choices.ts +125 -0
  219. package/src/domains/guardian-tick.ts +138 -0
  220. package/src/domains/outbound-alert.ts +44 -13
  221. package/src/domains/outbound-log-probe.ts +6 -1
  222. package/src/domains/schedule-registry.ts +23 -0
  223. package/src/domains/telegram-kind-route.ts +39 -0
  224. package/src/domains/watch-default-scheduler.ts +95 -0
  225. package/src/esc-abort-gate.ts +44 -0
  226. package/src/graph-runner/graph-run-api.ts +205 -0
  227. package/src/graph-runner/runner.ts +36 -6
  228. package/src/harness/harness-cli-command.ts +420 -51
  229. package/src/harness/harness-queue.ts +315 -25
  230. package/src/harness/harness-salvage-cli.ts +140 -0
  231. package/src/harness/harness-worktree-add.ts +68 -0
  232. package/src/harness/ledger-write-guard.ts +6 -16
  233. package/src/hq/fence-outcomes.ts +63 -0
  234. package/src/hq/fence-wrapper.ts +33 -0
  235. package/src/hq/hq.ts +17 -2
  236. package/src/index.ts +118 -6
  237. package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
  238. package/src/instance/test-write-guard.ts +200 -0
  239. package/src/intake-plane/digest.ts +39 -15
  240. package/src/intake-plane/harness-target.ts +4 -3
  241. package/src/intake-plane/intake-sources.ts +1 -0
  242. package/src/intake-plane/items.ts +1 -0
  243. package/src/loops/orchestrator/degradation.ts +57 -0
  244. package/src/loops/orchestrator/finish-rate.ts +33 -3
  245. package/src/loops/orchestrator/tick.ts +166 -45
  246. package/src/maturity/feature-maturity.ts +6 -4
  247. package/src/maturity/tui-slash-maturity.ts +1 -0
  248. package/src/mss/logging/log-store.ts +3 -0
  249. package/src/nexus/api/graph-kinds.ts +10 -4
  250. package/src/nexus/api/graphs-api.ts +178 -6
  251. package/src/nexus/api/http-server.ts +54 -8
  252. package/src/nexus/api/loop-edges.ts +292 -17
  253. package/src/nexus/api/ops-api.ts +112 -29
  254. package/src/nexus/api/ops-board-route.ts +48 -0
  255. package/src/nexus/index.ts +7 -0
  256. package/src/onboarding/entry-hints.ts +5 -0
  257. package/src/onboarding.ts +24 -6
  258. package/src/release-loop/checklist.ts +2 -0
  259. package/src/release-loop/feature-store.ts +68 -18
  260. package/src/release-loop/gate-shards.ts +158 -0
  261. package/src/release-loop/light-rc.ts +173 -0
  262. package/src/release-loop/placement.ts +51 -17
  263. package/src/release-loop/release-schedule.ts +6 -2
  264. package/src/release-loop/rubric.ts +57 -1
  265. package/src/retro/daily.ts +372 -0
  266. package/src/seat-loop/op-decide.ts +173 -0
  267. package/src/seat-loop/seat-loop.ts +60 -3
  268. package/src/seat-loop/seat-report.ts +58 -3
  269. package/src/self-dev/ask-launch-flow.ts +5 -4
  270. package/src/self-dev/ask-launch-io.ts +49 -1
  271. package/src/self-dev/ask-path-candidates.ts +40 -2
  272. package/src/self-dev/dev-cli.ts +36 -0
  273. package/src/self-dev/draft-sweep.ts +371 -16
  274. package/src/self-dev/draft-triage-rules.ts +19 -4
  275. package/src/self-dev/orchestrate.ts +1 -0
  276. package/src/self-dev/pr-preview.ts +247 -0
  277. package/src/self-dev/shard-run.ts +23 -7
  278. package/src/self-implement/blocked-draft-policy.ts +101 -0
  279. package/src/self-implement/execution-clarification.ts +11 -0
  280. package/src/self-implement/gate-cli.ts +29 -8
  281. package/src/self-implement/gate-env-retry.ts +83 -0
  282. package/src/self-implement/goal-author-clarification.ts +88 -13
  283. package/src/self-implement/goal-clarification-escalation.ts +2 -0
  284. package/src/self-implement/orchestrator.ts +220 -6
  285. package/src/self-implement/run-stop.ts +123 -0
  286. package/src/self-implement/run-ttl.ts +258 -0
  287. package/src/self-implement/salvage-branches.ts +254 -0
  288. package/src/self-implement/salvage-retention.ts +123 -0
  289. package/src/self-implement/seams.ts +4 -0
  290. package/src/self-implement/self-implement-cli.ts +2 -0
  291. package/src/self-implement/sibling-resync.ts +223 -0
  292. package/src/self-implement/stop-autoheal.ts +204 -0
  293. package/src/task-agent/actions.ts +63 -2
  294. package/src/task-agent/board.ts +132 -0
  295. package/src/task-agent/card-evidence.ts +292 -0
  296. package/src/task-agent/completion-evidence.ts +126 -0
  297. package/src/task-agent/judge.ts +25 -0
  298. package/src/task-agent/live-moves.ts +276 -0
  299. package/src/task-agent/mission.ts +33 -7
  300. package/src/task-agent/shadow.ts +192 -15
  301. package/src/task-agent/task-hand.ts +124 -17
  302. package/src/task-orchestrator/surfaces/pod-command-job.ts +82 -8
  303. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
  304. package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
  305. package/src/task-orchestrator/surfaces/pod-lease.ts +233 -21
  306. package/src/task-orchestrator/surfaces/pod-pool.ts +155 -34
  307. package/src/task-orchestrator/surfaces/self-implement-pod.ts +21 -5
  308. package/src/telegram-away-command.ts +38 -0
  309. package/src/telegram-commands.ts +52 -0
  310. package/src/telegram-report-log-probe.ts +21 -0
  311. package/src/telegram-report.ts +27 -3
  312. package/src/testing/real-cli-spawn.ts +106 -0
  313. package/src/tui-client/dashboard-session.ts +1 -0
  314. package/src/user-config.ts +190 -5
  315. package/src/version/packed-revision.json +1 -1
  316. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
  324. package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/editor/page-c39554c321576982.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a3c58f5c72a5f3f9.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-44d5f767491692bf.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
  348. package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
  349. /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_buildManifest.js +0 -0
  350. /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_ssgManifest.js +0 -0
@@ -0,0 +1,292 @@
1
+ /**
2
+ * TA-CARD-RUN-LINK — 과제 카드의 런 id 로 그 런이 연 골 id · PR 을 런 원장에서 읽어 카드에 «한 번» 적는다.
3
+ * - 순수 판정 `bindCardEvidence(card, snapshot)` — 호스트 원장 → Pod 자식 원장(`pod-child-run`) → 호스트 logs 의
4
+ * `self-implement.pod job-finished {childRunId, prUrl}` 순. 모르면 비운다(추측하지 않는다).
5
+ * - `refreshCardRunBinding` 은 `tasks show`·`tasks advance` 가 부른다 — 읽기 조회이고, 찾았을 때만 잠금 안에서 카드의
6
+ * `goalId`·`pr {number,url}`(UX LOOP-INTERACT 계약 칸)을 비어 있을 때만 적는다 · 수동 근거(refSource manual)는 덮지 않는다.
7
+ * - 관측: `debug.log('task-agent', 'card-pr-bound', {card, runId, pr})`.
8
+ */
9
+ import { existsSync } from 'node:fs';
10
+ import { STOP_CLASS_POD_FAILURE, STOP_CLASS_NO_LAUNCH } from '../task-orchestrator/surfaces/pod-failure-reason.js';
11
+ import { readLaunchBinding } from '../harness/launch-registry.js';
12
+ import { readRunExits, type RunExit } from '../harness/harness-incidents.js';
13
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
14
+ import { debug } from '../debug/log.js';
15
+ import { LogStore, logsDbPath } from '../mss/logging/log-store.js';
16
+ import type { RunLedgerEntry } from '../self-implement/run-ledger.js';
17
+ import { cardPrNumber, taskAgentStatePath, updateTaskCard, readTaskCard, type TaskCard } from './task-hand.js';
18
+ import { judgeNextMove } from './judge.js';
19
+
20
+ const GOAL_ID = /^[a-f0-9]{16}$/;
21
+ const PR_URL = /^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/(\d+)$/;
22
+ const RUN_ID = /^run-[A-Za-z0-9_-]{4,64}$/;
23
+
24
+ /** 카드 런 하나의 원장 사진 — host = 카드 런 원장(없으면 null) · children = `pod-child-run` 자식 원장 · podFinishes = 호스트 logs. */
25
+ export interface CardRunSnapshot {
26
+ host: readonly RunLedgerEntry[] | null;
27
+ children: Readonly<Record<string, readonly RunLedgerEntry[] | null>>;
28
+ podFinishes?: ReadonlyArray<{ childRunId: string; prUrl: string; state?: string; childError?: string }>;
29
+ /** 읽기 «실패»(원장·logs) — 부재(null)와 갈린다. 있으면 refresh 가 card-bind-failed 를 남긴다. */
30
+ readErrors?: readonly string[];
31
+ }
32
+
33
+ export interface CardRunEvidence {
34
+ goalId?: string;
35
+ pr?: { number: number; url?: string };
36
+ childRunId?: string;
37
+ /** 호스트 원장이 있었나 — 없으면 런이 아직 시작 전이거나 대기열로 갔다. */
38
+ ledgerFound: boolean;
39
+ }
40
+
41
+ function entryGoal(entry: RunLedgerEntry): string | undefined {
42
+ const value = entry.goalId ?? (typeof entry.data?.goalId === 'string' ? entry.data.goalId : undefined);
43
+ return typeof value === 'string' && GOAL_ID.test(value) ? value : undefined;
44
+ }
45
+
46
+ /**
47
+ * 그 PR 의 골 id — PR 의 `pr-opened` 줄 골이 먼저. 없으면 원장 전체의 골이 «하나뿐»이고 다른 PR 줄이 골을 싣지 않았을 때만
48
+ * 그 골(대응이 모호하면 비운다 — set-once 라 틀린 짝은 못 고친다).
49
+ */
50
+ function goalIdOf(entries: readonly RunLedgerEntry[], prNumber: number): string | undefined {
51
+ const own = [...entries].reverse().find((entry) => entry.event === 'pr-opened' && entry.data?.number === prNumber && entryGoal(entry));
52
+ if (own) return entryGoal(own);
53
+ if (entries.some((entry) => entry.event === 'pr-opened' && entry.data?.number !== prNumber && entryGoal(entry))) return undefined;
54
+ const goals = new Set(entries.map(entryGoal).filter((value): value is string => value !== undefined));
55
+ return goals.size === 1 ? [...goals][0] : undefined;
56
+ }
57
+
58
+ function lastPr(entries: readonly RunLedgerEntry[]): { number: number; url?: string } | undefined {
59
+ for (let at = entries.length - 1; at >= 0; at--) {
60
+ const entry = entries[at]!;
61
+ if (entry.event !== 'pr-opened') continue;
62
+ const number = entry.data?.number;
63
+ if (!Number.isSafeInteger(number) || (number as number) <= 0) continue;
64
+ const url = typeof entry.data.url === 'string' && PR_URL.exec(entry.data.url)?.[1] === String(number) ? entry.data.url : undefined;
65
+ return { number: number as number, ...(url ? { url } : {}) };
66
+ }
67
+ return undefined;
68
+ }
69
+
70
+ /** 자식 런 id — 호스트 원장의 `pod-child-run` 만(호스트 런 자신은 빼고 · 순서 유지). */
71
+ export function podChildRunIds(host: readonly RunLedgerEntry[] | null, runId?: string): string[] {
72
+ return [...new Set((host ?? []).filter((entry) => entry.event === 'pod-child-run' && typeof entry.data?.childRunId === 'string'
73
+ && RUN_ID.test(entry.data.childRunId) && entry.data.childRunId !== runId).map((entry) => entry.data.childRunId as string))];
74
+ }
75
+
76
+ /** 순수 — 카드 런의 골 id · PR. PR 은 호스트 → 자식(나중 자식 먼저) → job-finished 순으로 처음 찾은 것. */
77
+ export function bindCardEvidence(card: Pick<TaskCard, 'runId'>, snapshot: CardRunSnapshot): CardRunEvidence {
78
+ if (!card.runId || !snapshot.host) return { ledgerFound: false };
79
+ const children = podChildRunIds(snapshot.host, card.runId).reverse();
80
+ const childLedgers = children.map((id) => [id, snapshot.children[id] ?? null] as const);
81
+ let pr = lastPr(snapshot.host);
82
+ let childRunId: string | undefined;
83
+ for (const [id, entries] of childLedgers) {
84
+ if (pr) break;
85
+ const found = entries ? lastPr(entries) : undefined;
86
+ if (found) { pr = found; childRunId = id; }
87
+ }
88
+ for (const id of children) {
89
+ if (pr) break;
90
+ const finish = [...(snapshot.podFinishes ?? [])].reverse().find((row) => row.childRunId === id && PR_URL.test(row.prUrl));
91
+ const number = finish ? Number(PR_URL.exec(finish.prUrl)![1]) : NaN;
92
+ if (finish && Number.isSafeInteger(number) && number > 0) { pr = { number, url: finish.prUrl }; childRunId = id; }
93
+ }
94
+ // 골 id 는 PR 을 낸 런의 것뿐이고 PR 과 함께만 — 자식이 냈으면 그 자식 원장의 골(아직 없으면 비움 · 나중 조회가 채운다),
95
+ // 호스트가 냈으면 호스트 골. PR 전에는 누가 낼지 모르니 골도 묶지 않는다(set-once 라 섞이면 못 고친다).
96
+ const childGoal = (id: string) => { const entries = snapshot.children[id]; return entries && pr ? goalIdOf(entries, pr.number) : undefined; };
97
+ const goalId = pr ? (childRunId ? childGoal(childRunId) : goalIdOf(snapshot.host, pr.number)) : undefined;
98
+ return { ledgerFound: true, ...(goalId ? { goalId } : {}), ...(pr ? { pr } : {}), ...(childRunId ? { childRunId } : {}) };
99
+ }
100
+
101
+ /** DRAFT-NOT-ARCHIVE — 원장의 마지막 `salvaged`(self-implement.draft-not-archive) 줄이 남긴 수확 가지. 없으면 undefined. */
102
+ export function salvageBranchOf(entries: readonly RunLedgerEntry[] | null | undefined): string | undefined {
103
+ return lastSalvaged(entries)?.branch;
104
+ }
105
+
106
+ function lastSalvaged(entries: readonly RunLedgerEntry[] | null | undefined): { branch: string; at: number; order: number } | undefined {
107
+ for (let at = (entries?.length ?? 0) - 1; at >= 0; at--) {
108
+ const entry = entries![at]!;
109
+ const branch = entry.event === 'salvaged' ? entry.data?.salvageBranch : undefined;
110
+ if (typeof branch === 'string' && branch.startsWith('salvage/')) return { branch, at: Date.parse(entry.timestamp ?? ''), order: at };
111
+ }
112
+ return undefined;
113
+ }
114
+
115
+ /** 카드 런(호스트 → 자식 나중 것부터)의 수확 가지. 원장을 못 읽었으면 `readErrors` 로 갈린다(⛔ «없음» 이 아니다). */
116
+ export async function readCardSalvageBranch(card: Pick<TaskCard, 'runId' | 'createdAt'>, deps: CardRunEvidenceDeps = {}): Promise<{ salvageBranch: string | null; readErrors?: readonly string[] }> {
117
+ if (!card.runId) return { salvageBranch: null };
118
+ const snapshot = await readCardRunSnapshot(card, { ...deps, podFinishes: () => [] });
119
+ // 호스트 ⊕ Pod 자식 원장 전체에서 «가장 나중»(타임스탬프) 수확 가지. 시각이 같거나 못 읽으면 호스트가 이긴다.
120
+ const candidates = [lastSalvaged(snapshot.host), ...podChildRunIds(snapshot.host, card.runId).map((id) => lastSalvaged(snapshot.children[id]))]
121
+ .filter((row): row is NonNullable<typeof row> => row !== undefined);
122
+ const latest = candidates.reduce<typeof candidates[number] | undefined>((best, row) => !best || (Number.isFinite(row.at) && (!Number.isFinite(best.at) || row.at > best.at)) ? row : best, undefined);
123
+ return { salvageBranch: latest?.branch ?? null, ...(snapshot.readErrors?.length ? { readErrors: snapshot.readErrors } : {}) };
124
+ }
125
+
126
+ /** 원장·logs 읽기(시험 주입). */
127
+ export interface CardRunEvidenceDeps {
128
+ loadLedger?: (runId: string) => RunLedgerEntry[] | null;
129
+ podFinishes?: (childRunIds: readonly string[], sinceIso: string) => Array<{ childRunId: string; prUrl: string; state?: string; childError?: string }>;
130
+ now?: () => Date;
131
+ launchBound?: (runId: string) => boolean;
132
+ dispatchPod?: (runId: string, sinceIso: string) => boolean;
133
+ runExit?: (runId: string) => RunExit | undefined;
134
+ }
135
+
136
+ async function defaultLoadLedger(): Promise<(runId: string) => RunLedgerEntry[] | null> {
137
+ const { loadRunLedger, runLedgerDir, resolveFederatedRunLedgerDirectories } = await import('../self-implement/run-ledger.js');
138
+ let dirs: string[] = [runLedgerDir()];
139
+ // 연합 목록을 못 읽으면 이 우주만 읽되, 여기서 못 찾으면 «없음»이 아니라 실패로 올린다(card-bind-failed).
140
+ let federationFailure: unknown;
141
+ try { dirs = [...new Set([...dirs, ...resolveFederatedRunLedgerDirectories({})])]; } catch (error) { federationFailure = new Error(`federated run-ledger lookup failed: ${error instanceof Error ? error.message : String(error)}`); }
142
+ return ledgerLoader(dirs, (runId, dir) => loadRunLedger(runId, dir), federationFailure);
143
+ }
144
+
145
+ /** 디렉터리들에서 첫 원장 — 어디서도 못 찾았고 읽기(또는 연합 목록) 실패가 있었으면 부재(null)가 아니라 던진다. */
146
+ export function ledgerLoader(dirs: readonly string[], load: (runId: string, dir: string) => RunLedgerEntry[] | null, priorFailure?: unknown): (runId: string) => RunLedgerEntry[] | null {
147
+ return (runId) => {
148
+ let failure: unknown = priorFailure;
149
+ for (const dir of dirs) {
150
+ try { const entries = load(runId, dir); if (entries) return entries; } catch (error) { failure ??= error; }
151
+ }
152
+ if (failure !== undefined) throw failure;
153
+ return null;
154
+ };
155
+ }
156
+
157
+ function defaultDispatchPod(runId: string, sinceIso: string): boolean {
158
+ const path = logsDbPath();
159
+ if (!existsSync(path)) return false;
160
+ const store = LogStore.openReadOnly(path);
161
+ try {
162
+ const sinceMs = Date.parse(sinceIso);
163
+ return store.queryAll({ exactCategories: ['harness.substrate'], events: ['dispatch-pod'], ...(Number.isFinite(sinceMs) ? { sinceMs } : {}) })
164
+ .some((row) => { try { return (JSON.parse(row.data ?? '{}') as { runId?: string }).runId === runId; } catch { return false; } });
165
+ } finally { store.close(); }
166
+ }
167
+
168
+ async function defaultPodFinishes(): Promise<NonNullable<CardRunEvidenceDeps['podFinishes']>> {
169
+ return (childRunIds, sinceIso) => {
170
+ const path = logsDbPath();
171
+ if (!childRunIds.length || !existsSync(path)) return [];
172
+ const store = LogStore.openReadOnly(path);
173
+ try {
174
+ const wanted = new Set(childRunIds);
175
+ const rows: Array<{ childRunId: string; prUrl: string; state?: string; childError?: string }> = [];
176
+ const sinceMs = Date.parse(sinceIso);
177
+ for (const row of store.queryAll({ exactCategories: ['self-implement.pod'], events: ['job-finished'], ...(Number.isFinite(sinceMs) ? { sinceMs } : {}) })) {
178
+ let data: { childRunId?: unknown; prUrl?: unknown; state?: unknown; childError?: unknown } | null = null;
179
+ try { data = row.data ? JSON.parse(row.data) : null; } catch { continue; }
180
+ if (typeof data?.childRunId === 'string' && wanted.has(data.childRunId)) rows.push({ childRunId: data.childRunId, prUrl: typeof data.prUrl === 'string' ? data.prUrl : '', ...(typeof data.state === 'string' ? { state: data.state } : {}), ...(typeof data.childError === 'string' ? { childError: data.childError } : {}) });
181
+ }
182
+ return rows;
183
+ } finally { store.close(); }
184
+ };
185
+ }
186
+
187
+ /** 카드 런의 원장 사진을 읽는다 — 읽기 실패는 «없음»(null)이다. */
188
+ export async function readCardRunSnapshot(card: Pick<TaskCard, 'runId' | 'createdAt'>, deps: CardRunEvidenceDeps = {}): Promise<CardRunSnapshot> {
189
+ if (!card.runId) return { host: null, children: {} };
190
+ const load = deps.loadLedger ?? await defaultLoadLedger();
191
+ const readErrors: string[] = [];
192
+ const reason = (error: unknown) => (error instanceof Error ? error.message : String(error)).slice(0, 300);
193
+ const safe = (runId: string) => { try { return load(runId); } catch (error) { readErrors.push(`ledger ${runId}: ${reason(error)}`); return null; } };
194
+ const host = safe(card.runId);
195
+ const childIds = podChildRunIds(host, card.runId);
196
+ const children = Object.fromEntries(childIds.map((id) => [id, safe(id)]));
197
+ let podFinishes: NonNullable<CardRunSnapshot['podFinishes']> = [];
198
+ if (childIds.length && (!host || !lastPr(host))) {
199
+ try { podFinishes = (deps.podFinishes ?? await defaultPodFinishes())(childIds, card.createdAt); } catch (error) { readErrors.push(`logs: ${reason(error)}`); }
200
+ }
201
+ return { host, children, podFinishes, ...(readErrors.length ? { readErrors } : {}) };
202
+ }
203
+
204
+ /**
205
+ * 카드에 런 근거를 채운다 — 런 id 가 있고 아직 비어 있는 칸(goalId·pr)만, 잠금 안에서 «한 번».
206
+ * 찾지 못했으면 쓰지 않는다. 쓴(또는 그대로인) 카드를 돌려준다.
207
+ */
208
+ export async function refreshCardRunBinding(cardId: string, statePath = taskAgentStatePath(), deps: CardRunEvidenceDeps = {}): Promise<{ card: TaskCard | undefined; evidence?: CardRunEvidence }> {
209
+ const card = readTaskCard(cardId, statePath);
210
+ // 수동 근거(refSource manual)는 덮지 않는다 · 둘 다 차 있으면 다시 읽지 않는다(한 번만).
211
+ if (!card?.runId || card.refSource === 'manual' || (card.pr !== undefined && card.goalId !== undefined)) return { card };
212
+ const snapshot = await readCardRunSnapshot(card, deps);
213
+ if (snapshot.readErrors?.length) {
214
+ try { debug.log('task-agent', 'card-bind-failed', { card: cardId, runId: card.runId, errors: snapshot.readErrors }); } catch { /* fail-soft */ }
215
+ }
216
+ const evidence = bindCardEvidence(card, snapshot);
217
+ if (card.status === 'launched' && !card.pr && !evidence.pr && !snapshot.readErrors?.length) {
218
+ const rows = [...(snapshot.host ?? []), ...Object.values(snapshot.children).flatMap((entries) => entries ?? [])];
219
+ let exit: RunExit | undefined;
220
+ let exitReadFailed = false;
221
+ try { exit = (deps.runExit ?? ((id) => readRunExits(effectiveInstanceRoot()).find((row) => row.runId === id)))(card.runId); }
222
+ catch { exitReadFailed = true; }
223
+ const finished = [...(snapshot.podFinishes ?? [])].reverse().find((item) => item.state === 'failed');
224
+ const parentTerminal = [...(snapshot.host ?? [])].reverse().find((item) => item.event === 'run-status');
225
+ const parentFailed = parentTerminal?.data?.runStatus === 'failed';
226
+ const boundAt = card.history.find((item) => item.event === 'run-bound')?.at;
227
+ const elapsed = boundAt ? (deps.now ?? (() => new Date()))().getTime() - Date.parse(boundAt) : NaN;
228
+ const launched = rows.some((entry) => entry.event === 'pod-child-run');
229
+ let dispatched = false;
230
+ let dispatchReadFailed = false;
231
+ try { dispatched = (deps.dispatchPod ?? defaultDispatchPod)(card.runId, boundAt ?? card.createdAt); }
232
+ catch { dispatchReadFailed = true; }
233
+ let launchBound = false;
234
+ let launchReadFailed = false;
235
+ try { launchBound = (deps.launchBound ?? ((id) => !!readLaunchBinding(id)))(card.runId); }
236
+ catch { launchReadFailed = true; }
237
+ const timedOut = !finished && !launched && !dispatched && !dispatchReadFailed && !exit && !exitReadFailed && !launchBound && !launchReadFailed && Number.isFinite(elapsed) && elapsed >= 600_000;
238
+ const hasPr = !!card.pr || !!evidence.pr;
239
+ const exitHarvestBranch = exit?.lastLines?.some((line) => /(?:수확할 브랜치: |ELANOUS_POD_SALVAGE )(?:salvage\/|self-impl\/)[^\s]+/.test(line)) ?? false;
240
+ const ledgerFailure = rows.find((entry) => entry.event === 'run-status' && entry.data?.runStatus === 'failed'
241
+ && (entry.data?.failureKind === STOP_CLASS_POD_FAILURE || entry.data?.stop_class === STOP_CLASS_POD_FAILURE));
242
+ const resultFailure = rows.find((entry) => entry.event === 'result-without-error' && entry.data?.ok === false);
243
+ const noLaunchExit = exit?.reason === STOP_CLASS_NO_LAUNCH && exit.status !== null && exit.status !== 0 && !launched && !dispatched && !dispatchReadFailed;
244
+ // 발사 근거가 있는 런이 nonzero 로 끝났고 분류기가 pod-failure 나 unknown(원인 미상)을 냈다 = 죽은 런(TA-JUDGE-DEAD-RUN ③ «exit 1 · unknown/pod-failure»).
245
+ const deadExit = (exit?.reason === STOP_CLASS_POD_FAILURE || exit?.reason === 'unknown') && exit.status !== null && exit.status !== 0;
246
+ const podFailure = !noLaunchExit && (launched || dispatched) && (parentFailed && (finished || !!ledgerFailure || !!resultFailure) || deadExit)
247
+ && (finished || ledgerFailure || resultFailure || deadExit);
248
+ const failure = podFailure ? judgeNextMove({ terminalRun: {
249
+ kind: STOP_CLASS_POD_FAILURE,
250
+ reason: finished?.childError || (exit?.status != null ? `exit ${exit.status} · ${exit.reason || STOP_CLASS_POD_FAILURE}` : resultFailure ? 'result-without-error · failed' : finished ? `job-finished failed · ${STOP_CLASS_POD_FAILURE}` : `run-status failed · ${STOP_CLASS_POD_FAILURE}`),
251
+ hasPr,
252
+ hasHarvestBranch: exitHarvestBranch || rows.some((entry) => typeof entry.data?.harvestBranch === 'string' && !!entry.data.harvestBranch) || !!salvageBranchOf(rows),
253
+ } }) : noLaunchExit ? judgeNextMove({ terminalRun: { kind: STOP_CLASS_NO_LAUNCH, reason: `exit ${exit!.status} · ${exit!.reason}`, hasPr, hasHarvestBranch: false } })
254
+ : timedOut ? judgeNextMove({ terminalRun: { kind: STOP_CLASS_NO_LAUNCH, reason: 'card-run-bound 뒤 10분 동안 Pod 발사 근거 없음', hasPr: false, hasHarvestBranch: false } }) : undefined;
255
+ if (failure?.reason.startsWith('failed/')) {
256
+ const at = (deps.now ?? (() => new Date()))().toISOString();
257
+ const next = updateTaskCard(statePath, cardId, (current) => current?.status === 'launched' && current.runId === card.runId && !current.pr && current.refSource !== 'manual' ? {
258
+ ...current, status: 'failed', history: [...current.history, { at, event: 'failed', detail: failure.reason, runId: card.runId }],
259
+ } : undefined);
260
+ return { card: next, evidence };
261
+ }
262
+ }
263
+ // 이미 묶인 PR 이 있으면 «같은» PR(번호 ⊕ 낸 자식)을 다시 찾았을 때만 뒤늦은 골을 채운다.
264
+ const samePr = (current: TaskCard) => current.pr === undefined
265
+ || (evidence.pr !== undefined && cardPrNumber(current) === evidence.pr.number && current.runChildId === evidence.childRunId);
266
+ const wantsGoal = card.goalId === undefined && evidence.goalId && samePr(card);
267
+ const wantsPr = card.pr === undefined && evidence.pr;
268
+ if (!wantsGoal && !wantsPr) return { card, evidence };
269
+ const at = (deps.now ?? (() => new Date()))().toISOString();
270
+ const wrote = { pr: false };
271
+ const next = updateTaskCard(statePath, cardId, (current) => {
272
+ if (!current || current.runId !== card.runId || current.refSource === 'manual') return undefined;
273
+ const goal = current.goalId === undefined && evidence.goalId && samePr(current) ? evidence.goalId : undefined;
274
+ const pr = current.pr === undefined && evidence.pr ? evidence.pr : undefined;
275
+ if (!goal && !pr) return undefined;
276
+ wrote.pr = pr !== undefined;
277
+ return {
278
+ ...current,
279
+ ...(goal ? { goalId: goal } : {}),
280
+ ...(pr ? { pr, ...(evidence.childRunId && !current.runChildId ? { runChildId: evidence.childRunId } : {}) } : {}),
281
+ history: [
282
+ ...(current.history ?? []),
283
+ ...(goal ? [{ at, event: 'goal-bound', detail: goal, runId: current.runId! }] : []),
284
+ ...(pr ? [{ at, event: 'pr-bound', detail: pr.url ?? `#${pr.number}`, runId: current.runId!, pr: pr.number }] : []),
285
+ ],
286
+ };
287
+ });
288
+ if (wrote.pr && typeof next?.pr === 'object') {
289
+ try { debug.log('task-agent', 'card-pr-bound', { card: cardId, runId: next.runId ?? null, pr: next.pr.number, url: next.pr.url ?? null, childRunId: next.runChildId ?? null }); } catch { /* fail-soft */ }
290
+ }
291
+ return { card: next, evidence };
292
+ }
@@ -0,0 +1,126 @@
1
+ import { readFileSync, statSync } from 'node:fs';
2
+ import { extname, isAbsolute, relative, resolve, sep } from 'node:path';
3
+ import type { Database } from 'bun:sqlite';
4
+ import type { DecisionLedger } from '../decisions/decision-ledger.js';
5
+ import { getLastStatus } from '../domains/schedule-registry.js';
6
+
7
+ export type CompletionEvidenceKind = 'research-report' | 'artifact' | 'content' | 'ops-action' | 'watch-brief' | 'decision-support';
8
+ export interface CompletionEvidenceDeps {
9
+ ledger?: Pick<DecisionLedger, 'list'>;
10
+ schedulesDb?: Database;
11
+ }
12
+
13
+ /** A missing record is negative evidence; a failed read is explicitly unmeasured. No store is opened or mutated here. */
14
+ export function checkCompletionEvidence(
15
+ kind: CompletionEvidenceKind,
16
+ input: { dir: string; ref: string },
17
+ deps: CompletionEvidenceDeps = {},
18
+ ): { ok: boolean; ref: string; missing: string[] } {
19
+ const missing: string[] = [];
20
+ const add = (item: string) => { if (!missing.includes(item)) missing.push(item); };
21
+ const pathFor = (ref: string): string | null => {
22
+ const root = resolve(input.dir);
23
+ const path = resolve(root, ref);
24
+ const rel = relative(root, path);
25
+ return !isAbsolute(ref) && rel !== '..' && !rel.startsWith(`..${sep}`) ? path : null;
26
+ };
27
+ const read = (path: string): string | null => {
28
+ try { return readFileSync(path, 'utf8'); }
29
+ catch (error) {
30
+ add((error as NodeJS.ErrnoException).code === 'ENOENT' ? '파일' : '못 쟀다: 파일 읽기');
31
+ return null;
32
+ }
33
+ };
34
+ const file = () => {
35
+ const path = pathFor(input.ref);
36
+ if (!path) add('못 쟀다: 경로');
37
+ return path;
38
+ };
39
+
40
+ switch (kind) {
41
+ case 'research-report': {
42
+ const path = file();
43
+ if (!path) break;
44
+ if (extname(path).toLowerCase() !== '.md') { add('md 파일'); break; }
45
+ const text = read(path);
46
+ if (text === null) break;
47
+ const sources = new Set([...text.matchAll(/https?:\/\/[^\s<>"')\]]+/g)].map((match) => match[0]!.replace(/[.,;]+$/, '')));
48
+ if (sources.size < 2) add('출처');
49
+ if (!/^#{1,6}\s*반대 근거\s*$/m.test(text)) add('반대 근거');
50
+ break;
51
+ }
52
+ case 'artifact':
53
+ case 'content': {
54
+ const path = file();
55
+ if (!path) break;
56
+ try {
57
+ const stat = statSync(path);
58
+ if (!stat.isFile() || stat.size === 0) add('파일 크기');
59
+ } catch (error) { add((error as NodeJS.ErrnoException).code === 'ENOENT' ? '파일' : '못 쟀다: 파일 크기'); }
60
+ const stem = path.slice(0, path.length - extname(path).length);
61
+ let preview = false;
62
+ for (const ext of ['.png', '.pdf', '.html']) {
63
+ const candidate = `${stem}${ext}`;
64
+ if (candidate === path) continue;
65
+ try {
66
+ const stat = statSync(candidate);
67
+ if (stat.isFile() && stat.size > 0) preview = true;
68
+ } catch (error) {
69
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') add('못 쟀다: 미리보기 읽기');
70
+ }
71
+ }
72
+ if (extname(path).toLowerCase() === '.md' && !missing.includes('파일')) {
73
+ const text = read(path);
74
+ if (text !== null && /^#{1,6}\s+\S.*$/m.test(text)) preview = true;
75
+ }
76
+ if (!preview && !missing.some((item) => item.startsWith('못 쟀다: 미리보기') || item === '못 쟀다: 파일 읽기')) add('미리보기');
77
+ break;
78
+ }
79
+ case 'ops-action': {
80
+ const path = pathFor('execution.jsonl');
81
+ if (!path) { add('못 쟀다: 경로'); break; }
82
+ let text: string;
83
+ try { text = readFileSync(path, 'utf8'); }
84
+ catch (error) {
85
+ add((error as NodeJS.ErrnoException).code === 'ENOENT' ? '실행 기록' : '못 쟀다: 실행 기록 읽기');
86
+ break;
87
+ }
88
+ let execution = false;
89
+ let readback = false;
90
+ for (const line of text.split('\n').filter((line) => line.trim())) {
91
+ let record: unknown;
92
+ try { record = JSON.parse(line); }
93
+ catch { add('못 쟀다: 실행 기록 해석'); continue; }
94
+ if (!record || typeof record !== 'object') { add('못 쟀다: 실행 기록 해석'); continue; }
95
+ const entry = record as { id?: unknown; type?: unknown };
96
+ if (entry.id !== input.ref) continue;
97
+ if (entry.type === 'execution') execution = true;
98
+ if (entry.type === 'readback') readback = true;
99
+ }
100
+ if (!missing.includes('못 쟀다: 실행 기록 해석')) {
101
+ if (!execution) add('실행 기록');
102
+ if (!readback) add('되읽기');
103
+ }
104
+ break;
105
+ }
106
+ case 'watch-brief': {
107
+ if (!deps.schedulesDb) { add('못 쟀다: 스케줄 DB'); break; }
108
+ try {
109
+ const row = deps.schedulesDb.query('SELECT id FROM schedule_registry WHERE id = ?').get(input.ref);
110
+ if (!row) add('일정');
111
+ else if (getLastStatus(deps.schedulesDb, input.ref) !== 'ok') add('도착');
112
+ } catch { add('못 쟀다: 스케줄 DB 읽기'); }
113
+ break;
114
+ }
115
+ case 'decision-support': {
116
+ if (!deps.ledger) { add('못 쟀다: 결정 원장'); break; }
117
+ try {
118
+ if (!deps.ledger.list({ status: 'all' }).some((entry) => entry.id === input.ref)) add('결정 기록');
119
+ } catch { add('못 쟀다: 결정 원장 읽기'); }
120
+ break;
121
+ }
122
+ default:
123
+ add('못 쟀다: 증거 종류');
124
+ }
125
+ return { ok: missing.length === 0, ref: input.ref, missing };
126
+ }
@@ -4,8 +4,10 @@
4
4
  * RFC-task-agent-any-task-to-completion-2026-10-06 §A2 표를 순수 함수로 옮긴다.
5
5
  * 부작용이 없다(발사·리뷰·착지는 실행부 `actions.ts` 의 몫). 수확 원천 = run-c9ad6d6d `chooseTaskAction`.
6
6
  */
7
+ import { STOP_CLASS_POD_FAILURE, STOP_CLASS_NO_LAUNCH } from '../task-orchestrator/surfaces/pod-failure-reason.js';
7
8
  import type { SupervisorStopReason } from '../self-dev/run-supervisor.js';
8
9
  import type { NextActionKind, RetryVariant } from './actions.js';
10
+ import type { CompletionKind } from './task-hand.js';
9
11
 
10
12
  export type TaskMove =
11
13
  | 'propose-green'
@@ -27,6 +29,13 @@ export interface TaskJudgeInput {
27
29
  mustFix?: readonly string[];
28
30
  /** 수마다 누적 실패 수. */
29
31
  failures?: Partial<Record<TaskMove, number>>;
32
+ deliveryEvidence?: { ok: boolean; files: readonly string[]; reason: string };
33
+ /** 과제 종결 종류(§A4b③) — 비거나 code-pr 이면 종전처럼 PR 병합이 종결이다. */
34
+ completion?: CompletionKind;
35
+ /** code-pr 밖 종류의 확인 증거(`completion-evidence.ts` 판독). 비면 «아직 못 받았다» — 통과로 읽지 않는다. */
36
+ evidence?: { ok: boolean; ref: string };
37
+ /** Only classified terminal evidence; an absent input preserves the existing stop-reason table. */
38
+ terminalRun?: { kind: typeof STOP_CLASS_POD_FAILURE | typeof STOP_CLASS_NO_LAUNCH; reason: string; hasPr: boolean; hasHarvestBranch: boolean };
30
39
  }
31
40
 
32
41
  export interface TaskJudgement {
@@ -64,7 +73,23 @@ const EXECUTOR_VARIANT: Partial<Record<TaskMove, RetryVariant>> = {
64
73
  };
65
74
 
66
75
  function base(input: TaskJudgeInput): Omit<TaskJudgement, 'executorKind'> {
76
+ if (input.deliveryEvidence) return input.deliveryEvidence.ok
77
+ ? { move: 'propose-green', stage: 'closing', reason: `전달 근거 확인: ${input.deliveryEvidence.files.join(', ')}` }
78
+ : { move: 'wait', stage: 'waiting', reason: input.deliveryEvidence.reason };
79
+ if (input.terminalRun && !input.terminalRun.hasPr && !input.terminalRun.hasHarvestBranch) {
80
+ const disposition = input.terminalRun.kind === STOP_CLASS_POD_FAILURE ? 'needs-owner' : 'needs-relaunch';
81
+ return { move: 'wait', stage: 'escalated', reason: `failed/${disposition} — ${input.terminalRun.reason.replace(/[\r\n]+/gu, ' ').trim() || input.terminalRun.kind}` };
82
+ }
67
83
  const stop = input.stopReason;
84
+ const kind = input.completion;
85
+ if (kind !== undefined && kind !== 'code-pr') {
86
+ const ref = input.evidence?.ref?.trim() ?? '';
87
+ // green 은 추적 가능한 근거(비지 않은 ref)가 있을 때만 — ok 인데 ref 가 비면 «증거 없음»으로 읽는다.
88
+ if (input.evidence?.ok === true && ref) return { move: 'propose-green', stage: 'closing', reason: `확인 증거 ${ref} 근거로 칸 green 제안 — ${kind}` };
89
+ if (input.evidence?.ok === false) return { move: 'narrow-relaunch', stage: 'repairing', reason: `확인 증거 미충족(${ref || '근거 없음'}) — ${kind} · 범위를 좁혀 재발사` };
90
+ // 증거가 아직 없다 — 완주·종료 미관측이면 증거를 기다린다(PR 병합은 이 종류의 종결이 아니다). 그 밖의 종료 어휘는 아래 표 그대로.
91
+ if (stop === 'converged' || stop === undefined) return { move: 'wait', stage: 'observing', reason: `확인 증거 대기 — ${kind}${input.evidence?.ok === true ? ' (ref 비어 있음)' : ''}` };
92
+ }
68
93
  const merged = input.prState === 'MERGED' && input.pr !== undefined && input.pr !== '';
69
94
  if (merged) {
70
95
  return { move: 'propose-green', stage: 'closing', reason: `병합된 PR ${input.pr} 근거로 칸 green 제안` };