elanous 0.2.15 → 0.2.17

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 (309) 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-33ffcc49ab45123b.js → 1307-aac7332576c4d6fc.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{6877-7c6208705c284124.js → 6877-fe94e76a5841ee9b.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-0e2c57fe6add4b2a.js → page-3143b07a817d1da9.js} +1 -1
  10. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-e05a77df7e005b04.js → page-17ba086ef82d339b.js} +1 -1
  11. package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/bots/{page-b8c49f6b4fa2df91.js → page-6e5c3a867557298b.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/chat/{page-976813c91d7394ea.js → page-9a41d65355fc25b9.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/control/{page-1d4901eb7c2d8822.js → page-41afec0b255ed2bb.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-72d3e7ff35a8c954.js → page-4187666132fc98c4.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/editor/{page-60c7a8e1a4fe62ce.js → page-09328d499162bae4.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/exec/{page-2a52b316cb143d9c.js → page-29ff3a8f84f03b6a.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/field/{page-c3ce483c8d5db370.js → page-80ec7401bc83f511.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/intake/{page-96f074f6538e8a2c.js → page-fc7d86467291ae38.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/{layout-e97be5334d83a9f5.js → layout-de43a186e5ba5683.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/morning/{page-c1343f9499d86f3f.js → page-043a9111e74f6b24.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/{not-found-8739a2e0ae793130.js → not-found-23a4601ceb7f452e.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-d23b12bb92242558.js → page-10d958605a77b3bd.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-c7fea475488201a8.js → page-a04260b8ccc968d4.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-5efbf81124c474ae.js → page-12916918988ac97e.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-06ee266953d9c879.js → page-5a64cb5208c6a8fd.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/settings/{page-153c2b3bcc89e8de.js → page-1407ec83ea64c1f9.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-1d824510df025d18.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/share/{page-0d6d23b40263a7ea.js → page-7f49fe645d5132f7.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/tasks/page-27d71f131acd9320.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/trace/{page-73735b64bfe1ae67.js → page-424659bacfb8cdd7.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-f7e631e9b4daf66e.js → page-fc2df261f8a2c7df.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-9817c7f9007466cc.js → page-5d4fef9ccd7d406b.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +1 -0
  55. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
  56. package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
  57. package/apps/pwa/out/approvals/index.html +1 -1
  58. package/apps/pwa/out/approvals/index.txt +15 -15
  59. package/apps/pwa/out/autopilot/index.html +1 -1
  60. package/apps/pwa/out/autopilot/index.txt +15 -15
  61. package/apps/pwa/out/board/index.html +1 -1
  62. package/apps/pwa/out/board/index.txt +15 -15
  63. package/apps/pwa/out/botlab/index.html +1 -1
  64. package/apps/pwa/out/botlab/index.txt +15 -15
  65. package/apps/pwa/out/bots/index.html +1 -1
  66. package/apps/pwa/out/bots/index.txt +15 -15
  67. package/apps/pwa/out/ceo/index.html +1 -1
  68. package/apps/pwa/out/ceo/index.txt +15 -15
  69. package/apps/pwa/out/chat/index.html +1 -1
  70. package/apps/pwa/out/chat/index.txt +15 -15
  71. package/apps/pwa/out/consult/index.html +1 -1
  72. package/apps/pwa/out/consult/index.txt +15 -15
  73. package/apps/pwa/out/control/index.html +1 -1
  74. package/apps/pwa/out/control/index.txt +15 -15
  75. package/apps/pwa/out/design-check/index.html +1 -1
  76. package/apps/pwa/out/design-check/index.txt +15 -15
  77. package/apps/pwa/out/editor/index.html +1 -1
  78. package/apps/pwa/out/editor/index.txt +15 -15
  79. package/apps/pwa/out/exec/index.html +1 -1
  80. package/apps/pwa/out/exec/index.txt +15 -15
  81. package/apps/pwa/out/field/index.html +1 -1
  82. package/apps/pwa/out/field/index.txt +15 -15
  83. package/apps/pwa/out/healthz +1 -1
  84. package/apps/pwa/out/index.html +1 -1
  85. package/apps/pwa/out/index.txt +15 -15
  86. package/apps/pwa/out/inside/index.html +1 -1
  87. package/apps/pwa/out/inside/index.txt +15 -15
  88. package/apps/pwa/out/intake/index.html +1 -1
  89. package/apps/pwa/out/intake/index.txt +15 -15
  90. package/apps/pwa/out/live/index.html +1 -1
  91. package/apps/pwa/out/live/index.txt +15 -15
  92. package/apps/pwa/out/loops/index.html +1 -1
  93. package/apps/pwa/out/loops/index.txt +15 -15
  94. package/apps/pwa/out/market/index.html +1 -1
  95. package/apps/pwa/out/market/index.txt +15 -15
  96. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  97. package/apps/pwa/out/missions/placeholder/index.txt +15 -15
  98. package/apps/pwa/out/morning/index.html +1 -1
  99. package/apps/pwa/out/morning/index.txt +15 -15
  100. package/apps/pwa/out/observatory/index.html +1 -1
  101. package/apps/pwa/out/observatory/index.txt +15 -15
  102. package/apps/pwa/out/ops/checklist/index.html +1 -1
  103. package/apps/pwa/out/ops/checklist/index.txt +15 -15
  104. package/apps/pwa/out/ops/release/index.html +1 -1
  105. package/apps/pwa/out/ops/release/index.txt +15 -15
  106. package/apps/pwa/out/ops/seats/index.html +1 -1
  107. package/apps/pwa/out/ops/seats/index.txt +15 -15
  108. package/apps/pwa/out/outputs/index.html +1 -1
  109. package/apps/pwa/out/outputs/index.txt +15 -15
  110. package/apps/pwa/out/reflection/index.html +1 -1
  111. package/apps/pwa/out/reflection/index.txt +16 -16
  112. package/apps/pwa/out/scheduler/index.html +1 -1
  113. package/apps/pwa/out/scheduler/index.txt +15 -15
  114. package/apps/pwa/out/sessions/index.html +1 -1
  115. package/apps/pwa/out/sessions/index.txt +15 -15
  116. package/apps/pwa/out/settings/devices/index.html +1 -1
  117. package/apps/pwa/out/settings/devices/index.txt +15 -15
  118. package/apps/pwa/out/settings/index.html +1 -1
  119. package/apps/pwa/out/settings/index.txt +15 -15
  120. package/apps/pwa/out/setup/done/index.html +1 -1
  121. package/apps/pwa/out/setup/done/index.txt +15 -15
  122. package/apps/pwa/out/setup/index.html +1 -1
  123. package/apps/pwa/out/setup/index.txt +15 -15
  124. package/apps/pwa/out/share/index.html +1 -1
  125. package/apps/pwa/out/share/index.txt +15 -15
  126. package/apps/pwa/out/showroom/index.html +1 -1
  127. package/apps/pwa/out/showroom/index.txt +15 -15
  128. package/apps/pwa/out/tasks/index.html +1 -1
  129. package/apps/pwa/out/tasks/index.txt +15 -15
  130. package/apps/pwa/out/term/index.html +1 -1
  131. package/apps/pwa/out/term/index.txt +15 -15
  132. package/apps/pwa/out/today/index.html +1 -1
  133. package/apps/pwa/out/today/index.txt +15 -15
  134. package/apps/pwa/out/trace/index.html +1 -1
  135. package/apps/pwa/out/trace/index.txt +15 -15
  136. package/apps/pwa/out/vault/index.html +1 -1
  137. package/apps/pwa/out/vault/index.txt +15 -15
  138. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  139. package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
  140. package/apps/pwa/out/workflows/index.html +1 -1
  141. package/apps/pwa/out/workflows/index.txt +15 -15
  142. package/apps/pwa/out/workspace/index.html +1 -1
  143. package/apps/pwa/out/workspace/index.txt +15 -15
  144. package/apps/pwa/out/worktrees/index.html +1 -1
  145. package/apps/pwa/out/worktrees/index.txt +15 -15
  146. package/graphs/doc-gen/doc-gen.yaml +24 -0
  147. package/graphs/doc-gen/recipes.yaml +3 -0
  148. package/graphs/intake/intake-daily.yaml +9 -2
  149. package/graphs/intake/news-sources.yaml +4 -0
  150. package/graphs/intake/recipes.yaml +6 -1
  151. package/graphs/orchestrator/orchestrator.yaml +7 -0
  152. package/graphs/orchestrator/recipes.yaml +3 -0
  153. package/graphs/steward/recipes.yaml +2 -1
  154. package/package.json +1 -1
  155. package/scripts/__fixtures__/intake-news.xml +7 -0
  156. package/scripts/ci-public-export-import-gate.ts +154 -0
  157. package/scripts/ci-test-interference-gate.ts +5 -1
  158. package/scripts/ci-typecheck-changed.ts +34 -15
  159. package/scripts/docs/doc-rot.ts +164 -0
  160. package/scripts/docs/generate-graph-anatomy.ts +131 -9
  161. package/scripts/docs-cli-check.ts +4 -4
  162. package/scripts/goals/goal-census.ts +343 -0
  163. package/scripts/intake-news.ts +169 -0
  164. package/scripts/measure/author-section-contribution.ts +84 -0
  165. package/scripts/release-loop/cut-branch.ts +43 -13
  166. package/scripts/release-loop/docs-land-node.ts +69 -10
  167. package/scripts/release-loop/gate-node.ts +3 -1
  168. package/scripts/release-loop/publish-node.ts +2 -2
  169. package/scripts/release-loop/release-docs.ts +134 -0
  170. package/scripts/release-loop/vault-note-node.ts +19 -2
  171. package/scripts/release-loop/version-node.ts +7 -4
  172. package/scripts/release-story/draft.ts +42 -14
  173. package/scripts/rhythm/monthly.ts +97 -0
  174. package/src/acp/client.ts +17 -2
  175. package/src/agent-mission/driver.ts +104 -11
  176. package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
  177. package/src/agent-mission/supervisor-metrics.ts +218 -0
  178. package/src/autopilot/track-agent-shadow.ts +2 -1
  179. package/src/autopilot/track-agent.ts +5 -3
  180. package/src/briefing/brief-items.ts +191 -36
  181. package/src/budget/usage-outcomes.ts +204 -0
  182. package/src/cli/autopilot-cli.ts +66 -0
  183. package/src/cli/brief-cli.ts +73 -0
  184. package/src/cli/decisions-cli.ts +4 -1
  185. package/src/cli/intake-cli.ts +1 -1
  186. package/src/cli/know-cli.ts +20 -0
  187. package/src/cli/log-event-names.ts +1 -0
  188. package/src/cli/logs-cli.ts +43 -1
  189. package/src/cli/pr-cli.ts +92 -13
  190. package/src/cli/provider-cli.ts +14 -0
  191. package/src/cli/release-cli.ts +11 -8
  192. package/src/cli/self-update-canary.ts +46 -0
  193. package/src/cli/self-update.ts +399 -1
  194. package/src/cli/usage-cli.ts +26 -0
  195. package/src/context-card/index.ts +45 -0
  196. package/src/context-card/task.ts +76 -0
  197. package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
  198. package/src/decisions/decision-ledger.ts +10 -1
  199. package/src/discord-self-message.ts +9 -0
  200. package/src/flow-loop/split.ts +24 -0
  201. package/src/harness/harness-cli-command.ts +178 -12
  202. package/src/harness/harness-pod-dispatch.ts +36 -55
  203. package/src/harness/harness-queue.ts +86 -7
  204. package/src/harness/harness-seams.ts +13 -3
  205. package/src/harness/helper-repair.ts +139 -8
  206. package/src/harness/helper-scan.ts +13 -2
  207. package/src/harness/nested-elanous-policy.ts +133 -0
  208. package/src/hq/fence-wrapper.ts +123 -0
  209. package/src/hq/hq.ts +16 -2
  210. package/src/hq/lease.ts +5 -3
  211. package/src/hq/transfer.ts +185 -0
  212. package/src/index.ts +168 -13
  213. package/src/intake-plane/digest.ts +85 -28
  214. package/src/knowledge/know-find.ts +105 -0
  215. package/src/llm.ts +75 -19
  216. package/src/loops/orchestrator/finish-rate.ts +14 -3
  217. package/src/loops/orchestrator/tick.ts +96 -25
  218. package/src/maturity/bot-slash-catalog.ts +11 -2
  219. package/src/maturity/feature-maturity.ts +2 -2
  220. package/src/maturity/tui-slash-availability.ts +9 -0
  221. package/src/nexus/api/grid.ts +61 -0
  222. package/src/nexus/api/http-server.ts +19 -1
  223. package/src/nexus/api/task-cards-api.ts +19 -0
  224. package/src/oauth/codex.ts +82 -2
  225. package/src/oauth/overload-failover-launch.ts +75 -0
  226. package/src/oauth/overload-failover.ts +137 -0
  227. package/src/release-loop/checklist.ts +10 -5
  228. package/src/release-loop/feature-store.ts +10 -9
  229. package/src/release-loop/landing-freeze.ts +18 -10
  230. package/src/release-loop/placement.ts +17 -8
  231. package/src/seat-dispatch/seat-ask.ts +33 -6
  232. package/src/seat-loop/seat-loop.ts +172 -21
  233. package/src/self-dev/ask-launch-flow.ts +48 -8
  234. package/src/self-dev/authored-goal-trace.ts +64 -0
  235. package/src/self-dev/dev-cli.ts +19 -1
  236. package/src/self-dev/dev-pipeline.ts +6 -5
  237. package/src/self-dev/entrance-registry.ts +10 -6
  238. package/src/self-dev/old-door.ts +73 -0
  239. package/src/self-dev/run-store.ts +5 -1
  240. package/src/self-dev/run-supervisor.ts +7 -1
  241. package/src/self-dev/supervisor-verdict-edges.ts +46 -0
  242. package/src/self-implement/app-api-budget.ts +166 -0
  243. package/src/self-implement/budget-gate.ts +7 -5
  244. package/src/self-implement/follow-up-goals.ts +289 -0
  245. package/src/self-implement/frozen-merges.ts +25 -20
  246. package/src/self-implement/goal-author-clarification.ts +81 -0
  247. package/src/self-implement/goal-author-cli.ts +33 -5
  248. package/src/self-implement/goal-author.ts +107 -25
  249. package/src/self-implement/goal-clarification-escalation.ts +37 -1
  250. package/src/self-implement/goal-document-write.ts +28 -0
  251. package/src/self-implement/heal-intake.ts +4 -0
  252. package/src/self-implement/heal-triage.ts +44 -0
  253. package/src/self-implement/host-regate.ts +3 -1
  254. package/src/self-implement/orchestrator.ts +157 -8
  255. package/src/self-implement/run-ledger.ts +4 -0
  256. package/src/self-implement/seams.ts +44 -15
  257. package/src/session-runtime/index.ts +2 -0
  258. package/src/session-runtime/retry-api.ts +87 -0
  259. package/src/session-runtime/retry-policy.ts +107 -6
  260. package/src/steward/working-backwards.ts +16 -1
  261. package/src/task-agent/actions.ts +118 -0
  262. package/src/task-agent/judge.ts +96 -0
  263. package/src/task-cards/card-cli.ts +25 -0
  264. package/src/task-cards/card-store.ts +9 -4
  265. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +33 -4
  266. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  267. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
  268. package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
  269. package/src/task-orchestrator/surfaces/pod-pool.ts +95 -18
  270. package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
  271. package/src/task-orchestrator/surfaces/self-implement-pod.ts +168 -37
  272. package/src/task-orchestrator/surfaces/self-implement.ts +2 -0
  273. package/src/telegram-commands.ts +6 -1
  274. package/src/telegram-loops-command.ts +21 -14
  275. package/src/telegram.ts +47 -0
  276. package/src/user-config.ts +59 -4
  277. package/src/version/packed-revision.json +1 -1
  278. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +0 -1
  279. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
  280. package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
  281. package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +0 -1
  282. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +0 -1
  283. package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +0 -1
  284. package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +0 -1
  285. package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +0 -1
  286. package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +0 -1
  287. package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +0 -1
  288. package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +0 -1
  289. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +0 -1
  290. package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +0 -1
  291. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +0 -1
  292. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +0 -1
  293. package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +0 -1
  294. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +0 -1
  295. package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +0 -1
  296. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +0 -1
  297. package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +0 -1
  298. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +0 -1
  299. package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +0 -1
  300. package/apps/pwa/out/_next/static/chunks/app/term/page-f5597e894c02cd33.js +0 -1
  301. package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +0 -1
  302. package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +0 -1
  303. package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +0 -1
  304. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +0 -1
  305. package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +0 -1
  306. package/src/launch-head/seat-goal-queue-lock.fixture.ts +0 -15
  307. package/src/launch-head/seat-goal-queue.ts +0 -198
  308. /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → yRA5ur6DcPl0YHSRheH2O}/_buildManifest.js +0 -0
  309. /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → yRA5ur6DcPl0YHSRheH2O}/_ssgManifest.js +0 -0
@@ -364,7 +364,61 @@ export function decideProviderFallback(input: {
364
364
  return { action: 'advance', nextProvider, category, attempts };
365
365
  }
366
366
 
367
- export type RetryAction = 'retry' | 'abort' | 'ask-user' | 'auto-undo';
367
+ /** Consecutive overload/5xx/rate-limit outcomes that switch the child off codex
368
+ * before the same provider spends its remaining retries. Other errors (400)
369
+ * never count. Recovery is this many later non-overload outcomes. */
370
+ export const OVERLOAD_FAILOVER_STREAK = 3;
371
+
372
+ export type LlmCallOutcomeKind = 'ok' | 'overloaded' | '5xx' | 'rate-limit' | 'other';
373
+
374
+ /** One finished LLM call. Launch failover reads only this shape. */
375
+ export interface LlmCallOutcome {
376
+ readonly provider: string;
377
+ readonly status: number;
378
+ readonly kind: LlmCallOutcomeKind;
379
+ }
380
+
381
+ const CODEX_CALL_PROVIDERS = new Set(['codex', 'openai-codex']);
382
+
383
+ export function llmCallOutcomeKind(status: number, message = ''): LlmCallOutcomeKind {
384
+ if (status >= 200 && status < 300) return 'ok';
385
+ const msg = message.toLowerCase();
386
+ if (status === 429 || msg.includes('rate limit') || msg.includes('too many requests')) return 'rate-limit';
387
+ if (
388
+ status === 500 || status === 502 || status === 503 || status === 504 ||
389
+ msg.includes('overloaded') || msg.includes('server_busy') || msg.includes('server_error')
390
+ ) {
391
+ return status >= 500 ? '5xx' : 'overloaded';
392
+ }
393
+ if (msg.includes('overloaded')) return 'overloaded';
394
+ return 'other';
395
+ }
396
+
397
+ export function isOverloadOutcomeKind(kind: LlmCallOutcomeKind): boolean {
398
+ return kind === 'overloaded' || kind === '5xx' || kind === 'rate-limit';
399
+ }
400
+
401
+ /** True when the newest `limit` codex outcomes are all overload-class.
402
+ * A non-codex row or a non-overload row breaks the streak. Empty input is false. */
403
+ export function codexOverloadStreakReached(
404
+ outcomes: readonly LlmCallOutcome[],
405
+ limit = OVERLOAD_FAILOVER_STREAK,
406
+ ): boolean {
407
+ if (limit < 1 || outcomes.length < limit) return false;
408
+ const tail = outcomes.slice(-limit);
409
+ return tail.every((row) => CODEX_CALL_PROVIDERS.has(row.provider) && isOverloadOutcomeKind(row.kind));
410
+ }
411
+
412
+ /** True when the newest `limit` outcomes are successful (any provider). */
413
+ export function overloadRecovered(
414
+ outcomes: readonly LlmCallOutcome[],
415
+ limit = OVERLOAD_FAILOVER_STREAK,
416
+ ): boolean {
417
+ if (limit < 1 || outcomes.length < limit) return false;
418
+ return outcomes.slice(-limit).every((row) => row.kind === 'ok');
419
+ }
420
+
421
+ export type RetryAction = 'retry' | 'abort' | 'ask-user' | 'auto-undo' | 'switch-provider';
368
422
 
369
423
  export interface RetryDecision {
370
424
  action: RetryAction;
@@ -417,6 +471,16 @@ export interface RetryContext {
417
471
  /** Optional: retry-after header string from a 429 response (ms or
418
472
  * seconds or HTTP date). decideRetry parses it. */
419
473
  retryAfter?: string;
474
+ /** Configured fallbacks still unused. When > 0, an overload-class
475
+ * failure that has already repeated `overloadStreak` times switches
476
+ * provider instead of spending another retry on the same one.
477
+ * Omitted or 0 keeps the historical retry-until-exhausted behaviour. */
478
+ remainingFallbacks?: number;
479
+ /** Consecutive overload-class failures already seen for this call,
480
+ * including the one under decision. Default threshold is
481
+ * OVERLOAD_FAILOVER_STREAK. */
482
+ overloadStreak?: number;
483
+ overloadStreakLimit?: number;
420
484
  }
421
485
 
422
486
  /** Classify an error into a RetryCategory without making an action
@@ -519,10 +583,42 @@ export function classifyError(err: unknown): RetryCategory {
519
583
  return 'unknown';
520
584
  }
521
585
 
586
+ function isOverloadSwitchCandidate(msg: string, code: string | null): boolean {
587
+ return (
588
+ code === '429' || code === '500' || code === '502' || code === '503' || code === '504' ||
589
+ msg.includes('rate limit') || msg.includes('too many requests') ||
590
+ msg.includes('overloaded') || msg.includes('server_busy') || msg.includes('server_error')
591
+ );
592
+ }
593
+
594
+ function overloadSwitchDecision(ctx: RetryContext): RetryDecision | undefined {
595
+ const remaining = ctx.remainingFallbacks ?? 0;
596
+ if (remaining <= 0) return undefined;
597
+ const limit = ctx.overloadStreakLimit ?? OVERLOAD_FAILOVER_STREAK;
598
+ const streak = ctx.overloadStreak ?? 1;
599
+ if (streak < limit) return undefined;
600
+ return {
601
+ action: 'switch-provider',
602
+ category: 'overloaded',
603
+ reason: `overload streak ${streak} with ${remaining} fallback(s) left; switch before spending retries`,
604
+ };
605
+ }
606
+
522
607
  /** Pure verdict. Caller sleeps + retries or aborts per the returned
523
608
  * action. Exponential backoff baseline: 500ms * 2^attempt, clamped
524
609
  * to [500, 30_000]. */
525
610
  export function decideRetry(err: unknown, ctx: RetryContext): RetryDecision {
611
+ const msg = extractMessage(err).toLowerCase();
612
+ const code = extractErrorCode(err);
613
+
614
+ // Overload with a fallback still unused switches before the doom
615
+ // window (k === 3) turns the same 503 into auto-undo. A 400 is not
616
+ // a candidate. Doom still wins for every other repeated failure.
617
+ if (isOverloadSwitchCandidate(msg, code)) {
618
+ const switched = overloadSwitchDecision(ctx);
619
+ if (switched) return switched;
620
+ }
621
+
526
622
  // Doom-loop always wins — even a technically-retryable error
527
623
  // becomes auto-undo after N identical failures. The caller may
528
624
  // still degrade to ask-user when plan mode is active or when the
@@ -535,9 +631,6 @@ export function decideRetry(err: unknown, ctx: RetryContext): RetryDecision {
535
631
  };
536
632
  }
537
633
 
538
- const msg = extractMessage(err).toLowerCase();
539
- const code = extractErrorCode(err);
540
-
541
634
  // Context window exhausted — never retry (the next call will also
542
635
  // be over-sized). Abort and surface the error to the caller; upstream
543
636
  // can trigger compaction.
@@ -589,8 +682,16 @@ export function decideRetry(err: unknown, ctx: RetryContext): RetryDecision {
589
682
  };
590
683
  }
591
684
 
592
- // Overloaded / server busy.
593
- if (msg.includes('overloaded') || msg.includes('server_busy') || code === '503') {
685
+ // Overloaded / server busy / 5xx.
686
+ if (
687
+ msg.includes('overloaded') ||
688
+ msg.includes('server_busy') ||
689
+ msg.includes('server_error') ||
690
+ code === '500' ||
691
+ code === '502' ||
692
+ code === '503' ||
693
+ code === '504'
694
+ ) {
594
695
  return {
595
696
  action: 'retry',
596
697
  delayMs: backoffMs(ctx.attempt),
@@ -57,7 +57,22 @@ export async function recordWorkingBackwardsOnCards(
57
57
  debug.log('steward.working-backwards', 'skipped-launched-before-draft', { issueId: issue.identifier });
58
58
  continue;
59
59
  }
60
- const draft = await draftWorkingBackwards(issue, ask);
60
+ // An invalid draft for the same issue text is not re-asked every tick — each ask is a 70-100 s LLM call (10-05 ELA-29 timed the schedule stage out 3×).
61
+ const invalidKey = `hitl:draft-invalid-${createHash('sha256').update(JSON.stringify({ title: issue.title, body: issue.body ?? '' })).digest('hex').slice(0, 16)}`;
62
+ if (card.sections.some(section => section.key === invalidKey)) {
63
+ debug.log('steward.working-backwards', 'skipped-known-invalid', { issueId: issue.identifier });
64
+ throw new InvalidWorkingBackwardsDraft();
65
+ }
66
+ let draft: WorkingBackwardsDraft;
67
+ try { draft = await draftWorkingBackwards(issue, ask); }
68
+ catch (error) {
69
+ if (error instanceof InvalidWorkingBackwardsDraft) {
70
+ // The memo is best-effort — failing to write it must not replace the invalid-draft error the caller routes to hitl.
71
+ try { store.appendSection(card.id, { key: invalidKey, owner: 'steward', content: 'working-backwards draft invalid for this issue text — not re-asked until the issue changes' }); }
72
+ catch (memoError) { debug.log('steward.working-backwards', 'invalid-memo-failed', { issueId: issue.identifier, error: memoError instanceof Error ? memoError.message : String(memoError) }); }
73
+ }
74
+ throw error;
75
+ }
61
76
  for (const [section, content] of [['prfaq', draft.prfaq], ['manual', draft.manual]] as const) {
62
77
  const key = `${section}:${createHash('sha256').update(content).digest('hex')}`;
63
78
  if (!card.sections.some(item => item.key === key)) store.appendSection(card.id, { key, owner: 'steward', content });
@@ -0,0 +1,118 @@
1
+ export type NextActionKind = 'review' | 'land' | 'retry' | 'green' | 'decision';
2
+ export interface NextAction {
3
+ kind: NextActionKind;
4
+ taskId: string;
5
+ rationale: string;
6
+ pr: number;
7
+ runId: string;
8
+ original: string;
9
+ checklistId: string;
10
+ history?: string[];
11
+ mustFix?: string[];
12
+ cwd?: string;
13
+ risk?: 'money' | 'security' | 'irreversible-publication';
14
+ }
15
+ export interface ActionDeps {
16
+ command(args: string[]): Promise<{ status: number; stdout: string; stderr?: string }>;
17
+ mode?: string;
18
+ landingsToday?: number;
19
+ landingDay?: string;
20
+ saveState?(): void;
21
+ dailyLimit?: number;
22
+ failures?: number;
23
+ failureCounts?: Record<string, number>;
24
+ /** 하루 착지 칸을 잠금 안에서 먼저 확보한다 — 다른 프로세스와 같은 잔여 칸을 두 번 쓰지 않게. 없으면 메모리 셈. */
25
+ reserveLanding?(day: string, limit: number): boolean;
26
+ /** 병합이 확인되지 않으면 확보한 칸을 돌려준다. */
27
+ releaseLanding?(day: string): void;
28
+ /** 실패 한 번을 잠금 안에서 디스크에 더한다(다른 프로세스의 실패를 덮지 않게) · 더한 뒤의 값을 돌려준다. */
29
+ incrementFailure?(key: string): number;
30
+ observe(event: 'task-agent.action', data: { taskId: string; kind: NextActionKind; result: string; reason: string }): void;
31
+ }
32
+
33
+ export async function executeNextAction(action: NextAction, deps: ActionDeps): Promise<string> {
34
+ const record = (result: string, reason: string) => {
35
+ if (result === 'failed' && (deps.failureCounts || deps.incrementFailure)) {
36
+ const key = JSON.stringify([action.taskId, action.kind]);
37
+ const next = deps.incrementFailure ? deps.incrementFailure(key) : (deps.failureCounts![key] ?? 0) + 1;
38
+ if (deps.failureCounts) deps.failureCounts[key] = next;
39
+ if (!deps.incrementFailure) deps.saveState?.();
40
+ }
41
+ deps.observe('task-agent.action', { taskId: action.taskId, kind: action.kind, result, reason });
42
+ return result;
43
+ };
44
+ if (deps.mode !== 'live') return record('shadow', `would ${action.kind}: ${action.rationale}`);
45
+ const card = async (reason: string) => {
46
+ const evidence = JSON.stringify({ runId: action.runId, pr: action.pr, mustFix: action.mustFix ?? [], history: action.history ?? [], selected: action.kind, reason });
47
+ const result = await deps.command(['decisions', 'raise', '--title', `${action.taskId}: ${reason}`, '--category', action.risk === 'money' ? 'money' : action.risk === 'security' ? 'security' : action.risk === 'irreversible-publication' ? 'publish' : 'other', '--s', `Task ${action.taskId} needs a decision.`, '--c', reason, '--ref', evidence, '--q', 'Which action should be taken?', '--option', 'h=Keep on hold:no automatic execution', '--option', 'p=Approve action:execute after approval', '--skip-recommend', 'requires human decision', '--no-xcheck', 'automated escalation']);
48
+ return record(result.status === 0 ? 'decision' : 'failed', reason);
49
+ };
50
+ if (action.risk) return card(`risk: ${action.risk}`);
51
+ if ((deps.failureCounts?.[JSON.stringify([action.taskId, action.kind])] ?? deps.failures ?? 0) >= 2) return card('same task/action failed twice');
52
+ if (action.kind === 'land') {
53
+ if (!action.cwd) return card('landing worktree unknown');
54
+ const view = await deps.command(['gh', 'pr', 'view', String(action.pr), '--json', 'labels']);
55
+ if (view.status !== 0) return card('PR labels unavailable');
56
+ let labels: unknown;
57
+ try { labels = (JSON.parse(view.stdout) as { labels?: unknown }).labels; }
58
+ catch { return card('PR labels unavailable'); }
59
+ if (!Array.isArray(labels) || !labels.every(label => label && typeof label === 'object' && typeof label.name === 'string')) return card('PR labels unavailable');
60
+ if (labels.some(label => label.name === 'elanous:release-path')) return card('release-path');
61
+ const day = new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' });
62
+ if (deps.landingDay && deps.landingDay !== day) deps.landingsToday = 0;
63
+ deps.landingDay = day;
64
+ const limit = deps.dailyLimit ?? 5;
65
+ if (deps.reserveLanding ? !deps.reserveLanding(day, limit) : (deps.landingsToday ?? 0) >= limit) return card('daily landing limit');
66
+ }
67
+ if (action.kind === 'decision') return card(action.rationale);
68
+ if (action.kind === 'green') return card('green requires a verified landing in this execution');
69
+ if (action.kind === 'retry' && !action.mustFix?.length) return card('must-fix missing; cannot narrow retry');
70
+ if (action.kind === 'review') {
71
+ const review = await deps.command(['self', 'review', String(action.pr), '--json']);
72
+ if (review.status !== 0) return record('failed', 'review command failed');
73
+ let parsed: { verdict?: string; mustFix?: string[]; reviewed?: boolean };
74
+ try {
75
+ const payload: unknown = JSON.parse(review.stdout);
76
+ const item = Array.isArray(payload) ? payload.find((entry: unknown) => entry && typeof entry === 'object' && 'pr' in entry && Number(entry.pr) === action.pr) : payload;
77
+ if (!item || typeof item !== 'object' || Array.isArray(item)) return record('failed', 'invalid review JSON');
78
+ parsed = item as typeof parsed;
79
+ } catch { return record('failed', 'invalid review JSON'); }
80
+ if (parsed.reviewed !== true) return record('failed', 'review was not performed');
81
+ if (parsed.verdict === 'pass') { record('done', 'review pass'); return executeNextAction({ ...action, kind: 'land', history: [...(action.history ?? []), 'review pass'] }, deps); }
82
+ if (parsed.verdict === 'fail' && Array.isArray(parsed.mustFix) && parsed.mustFix.length > 0 && parsed.mustFix.every(fix => typeof fix === 'string' && fix.trim())) { record('done', 'review fail'); return executeNextAction({ ...action, kind: 'retry', mustFix: parsed.mustFix, history: [...(action.history ?? []), 'review fail'] }, deps); }
83
+ return card('review verdict or must-fix unavailable');
84
+ }
85
+ // 여기 닿는 종류는 land · retry 둘뿐이다(green·decision·review 는 위에서 끝난다).
86
+ const args = action.kind === 'land' ? ['pr', 'land', '--cwd', action.cwd!]
87
+ : ['harness', 'say', `${action.original}\n\nMust-fix:\n${(action.mustFix ?? []).join('\n')}\n\nPrevious run: ${action.runId}`];
88
+ const day = deps.landingDay ?? new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' });
89
+ const outcome = await deps.command(args);
90
+ if (outcome.status !== 0) {
91
+ if (action.kind === 'land') deps.releaseLanding?.(day);
92
+ return record('failed', `${action.kind} command failed: ${outcome.stderr ?? ''}`);
93
+ }
94
+ if (action.kind === 'land') {
95
+ const merged = await deps.command(['gh', 'pr', 'view', String(action.pr), '--json', 'state,mergeCommit']);
96
+ let sha: string | undefined;
97
+ let notMerged = false;
98
+ try {
99
+ const value = JSON.parse(merged.stdout) as { state?: string; mergeCommit?: { oid?: string } };
100
+ if (merged.status === 0 && value.state === 'MERGED' && /^[a-f0-9]{40}$/i.test(value.mergeCommit?.oid ?? '')) sha = value.mergeCommit?.oid;
101
+ else if (merged.status === 0 && typeof value.state === 'string' && value.state !== 'MERGED') notMerged = true;
102
+ } catch { /* unknown merge state is never green */ }
103
+ if (!sha) {
104
+ // «열림»으로 확인됐을 때만 칸을 돌려준다. 상태를 못 읽었으면 병합됐을 수 있으니 칸은 쓴 것으로 둔다(상한을 넘지 않게).
105
+ if (notMerged) deps.releaseLanding?.(day);
106
+ return card('landing commit unknown; checklist remains unchanged');
107
+ }
108
+ record('done', action.rationale);
109
+ if (!deps.reserveLanding) deps.landingsToday = (deps.landingsToday ?? 0) + 1;
110
+ deps.saveState?.();
111
+ const green = await deps.command(['release', 'checklist', 'set', action.checklistId, '--status', 'green', '--evidence', `${action.pr}·${sha}`]);
112
+ // 체크리스트 실패는 이 과제·land 의 실패로 센다(두 번이면 다음엔 카드).
113
+ if (green.status !== 0) return record('failed', `green command failed: ${green.stderr ?? ''}`);
114
+ deps.observe('task-agent.action', { taskId: action.taskId, kind: 'green', result: 'done', reason: action.rationale });
115
+ return 'done';
116
+ }
117
+ return record('done', action.rationale);
118
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * TASK-AGENT 판단부 — 슈퍼바이저 종료 어휘(+ PR·리뷰 관측) → «다음 한 수».
3
+ *
4
+ * RFC-task-agent-any-task-to-completion-2026-10-06 §A2 표를 순수 함수로 옮긴다.
5
+ * 부작용이 없다(발사·리뷰·착지는 실행부 `actions.ts` 의 몫). 수확 원천 = run-c9ad6d6d `chooseTaskAction`.
6
+ */
7
+ import type { SupervisorStopReason } from '../self-dev/run-supervisor.js';
8
+ import type { NextActionKind } from './actions.js';
9
+
10
+ export type TaskMove =
11
+ | 'propose-green'
12
+ | 'review'
13
+ | 'propose-land'
14
+ | 'narrow-relaunch'
15
+ | 'wait-retry'
16
+ | 'salvage-relaunch'
17
+ | 'alternative'
18
+ | 'decision-card'
19
+ | 'wait';
20
+
21
+ export interface TaskJudgeInput {
22
+ stopReason?: SupervisorStopReason;
23
+ prState?: 'OPEN' | 'MERGED' | 'CLOSED';
24
+ pr?: string | number;
25
+ /** `self review --json` 판정. 관측 못 했으면 비운다(«통과»로 읽지 않는다). */
26
+ review?: 'pass' | 'fail';
27
+ mustFix?: readonly string[];
28
+ /** 수마다 누적 실패 수. */
29
+ failures?: Partial<Record<TaskMove, number>>;
30
+ }
31
+
32
+ export interface TaskJudgement {
33
+ move: TaskMove;
34
+ stage: 'observing' | 'closing' | 'reviewing' | 'repairing' | 'waiting' | 'escalated';
35
+ reason: string;
36
+ /** 실행부(`executeNextAction`)가 바로 받는 수면 그 종류. 없으면 발사·대기 계열(실행부 밖). */
37
+ executorKind?: NextActionKind;
38
+ }
39
+
40
+ /** 같은 수가 이 횟수만큼 실패하면 다른 수로 넘어간다(RFC §A2 «같은 수 2번 실패»). */
41
+ export const SAME_MOVE_FAILURE_LIMIT = 2;
42
+
43
+ const EXECUTOR_KIND: Partial<Record<TaskMove, NextActionKind>> = {
44
+ 'propose-green': 'green',
45
+ review: 'review',
46
+ 'propose-land': 'land',
47
+ 'narrow-relaunch': 'retry',
48
+ 'wait-retry': 'retry',
49
+ 'salvage-relaunch': 'retry',
50
+ alternative: 'retry',
51
+ 'decision-card': 'decision',
52
+ };
53
+
54
+ function base(input: TaskJudgeInput): Omit<TaskJudgement, 'executorKind'> {
55
+ const stop = input.stopReason;
56
+ const merged = input.prState === 'MERGED' && input.pr !== undefined && input.pr !== '';
57
+ if (merged) {
58
+ return { move: 'propose-green', stage: 'closing', reason: `병합된 PR ${input.pr} 근거로 칸 green 제안` };
59
+ }
60
+ switch (stop) {
61
+ case 'harvestable-awaiting-human':
62
+ case 'needs-human':
63
+ if (input.review === 'pass') return { move: 'propose-land', stage: 'reviewing', reason: 'self review 통과 — 착지 제안' };
64
+ if (input.review === 'fail') {
65
+ return { move: 'narrow-relaunch', stage: 'repairing', reason: `must-fix 로 좁혀 재발사: ${(input.mustFix ?? []).join(' · ') || '(must-fix 비어 있음)'}` };
66
+ }
67
+ return { move: 'review', stage: 'reviewing', reason: '수확 가능 런 — self review 필요' };
68
+ case 'no-progress':
69
+ case 'decomposable-no-progress':
70
+ return { move: 'narrow-relaunch', stage: 'repairing', reason: `${stop} — 범위를 좁혀 한 번 재발사(판정선 유지)` };
71
+ case 'provider-exhausted':
72
+ case 'step-timeout':
73
+ return { move: 'wait-retry', stage: 'waiting', reason: `${stop} — 대기 후 재시도` };
74
+ case 'handed-off-to-salvage':
75
+ return { move: 'salvage-relaunch', stage: 'repairing', reason: '수확 브랜치 diff 를 가져와 이어 발사' };
76
+ case 'converged':
77
+ return { move: 'wait', stage: 'observing', reason: '완주했으나 PR 병합 근거 대기' };
78
+ case undefined:
79
+ return { move: 'wait', stage: 'observing', reason: '종료 관측 대기' };
80
+ default:
81
+ return { move: 'decision-card', stage: 'escalated', reason: `표에 없는 종료 어휘 ${stop} — 근거 붙여 사람 결정` };
82
+ }
83
+ }
84
+
85
+ /** 종료 어휘·관측에서 다음 한 수를 고른다. 같은 수 2회 실패 → 다른 수 → 그것도 실패 → 결정 카드. */
86
+ export function judgeNextMove(input: TaskJudgeInput): TaskJudgement {
87
+ const failures = input.failures ?? {};
88
+ let judged = base(input);
89
+ if ((failures.alternative ?? 0) >= 1) {
90
+ judged = { move: 'decision-card', stage: 'escalated', reason: `대안도 실패 — 런·PR·must-fix 근거로 사람 결정 (직전: ${judged.move})` };
91
+ } else if (judged.move !== 'wait' && judged.move !== 'decision-card' && (failures[judged.move] ?? 0) >= SAME_MOVE_FAILURE_LIMIT) {
92
+ judged = { move: 'alternative', stage: 'repairing', reason: `같은 수 ${SAME_MOVE_FAILURE_LIMIT}회 실패 — 다른 수 선택 (${judged.move})` };
93
+ }
94
+ const executorKind = EXECUTOR_KIND[judged.move];
95
+ return executorKind ? { ...judged, executorKind } : judged;
96
+ }
@@ -46,6 +46,31 @@ export function registerCardCommand(program: Command, deps: CardCliDeps = {}): C
46
46
  }
47
47
  });
48
48
 
49
+ card.command('close <id>')
50
+ .description('Close a task card with a reason (owner operation)')
51
+ .requiredOption('--reason <text>', 'One-line close reason')
52
+ .action((id: string, options: { reason: string }) => {
53
+ const store = createStore();
54
+ try {
55
+ const reason = options.reason.trim();
56
+ if (!reason || /[\r\n]/.test(options.reason)) throw new Error('Close reason must be one line');
57
+ const existing = store.getCard(id);
58
+ if (!existing) {
59
+ process.stderr.write(`Card not found: ${id}\n`);
60
+ process.exitCode = 1;
61
+ return;
62
+ }
63
+ if (existing.status === 'closed') {
64
+ write(`이미 닫힘: ${id}\n`);
65
+ return;
66
+ }
67
+ store.closeCard(id, reason);
68
+ write(`닫힘: ${id}\n`);
69
+ } finally {
70
+ store.close();
71
+ }
72
+ });
73
+
49
74
  card.command('list')
50
75
  .description('List task cards')
51
76
  .option('--open', 'Only show open cards')
@@ -16,6 +16,7 @@ export interface TaskCard {
16
16
  goalId: string;
17
17
  title: string;
18
18
  status: CardStatus;
19
+ closedReason?: string;
19
20
  createdAt: string;
20
21
  sections: CardSection[];
21
22
  }
@@ -32,7 +33,7 @@ export interface AppendSectionInput {
32
33
  type CardEvent =
33
34
  | { type: 'created'; id: string; goalId: string; title: string; createdAt: string }
34
35
  | { type: 'section'; key: string; owner: string; content: string; createdAt: string }
35
- | { type: 'closed'; createdAt: string };
36
+ | { type: 'closed'; createdAt: string; reason?: string };
36
37
 
37
38
  /** RFC §3 (내부 문서 `RFC-execution-and-landing-loop-agents-parallel-first-2026-09-29`) — each card section has one owner.
38
39
  * A key is `<section>` or `<section>:<idempotency key>`: the same section can be appended many times over a
@@ -197,14 +198,16 @@ export class CardStore {
197
198
  }).immediate();
198
199
  }
199
200
 
200
- closeCard(id: string): TaskCard {
201
+ closeCard(id: string, reason?: string): TaskCard {
202
+ if (reason !== undefined && !reason.trim()) throw new Error('Close reason is required');
201
203
  return this.db.transaction(() => {
202
204
  this.reconcile();
203
205
  const card = this.readCard(id);
204
206
  if (!card) throw new Error(`Card not found: ${id}`);
205
207
  if (card.status === 'closed') return card;
206
- this.append(id, { type: 'closed', createdAt: new Date().toISOString() });
207
- return { ...card, status: 'closed' as const };
208
+ this.append(id, { type: 'closed', createdAt: new Date().toISOString(),
209
+ ...(reason === undefined ? {} : { reason: redactSecrets(reason.trim()) }) });
210
+ return { ...card, status: 'closed' as const, ...(reason === undefined ? {} : { closedReason: redactSecrets(reason.trim()) }) };
208
211
  }).immediate();
209
212
  }
210
213
 
@@ -214,8 +217,10 @@ export class CardStore {
214
217
  } | null;
215
218
  if (!row) return null;
216
219
  const events = readFileSync(cardEventsPath(id, this.root), 'utf8').trimEnd().split('\n').map((line) => JSON.parse(line) as CardEvent);
220
+ const closed = events.find((event): event is Extract<CardEvent, { type: 'closed' }> => event.type === 'closed');
217
221
  return {
218
222
  id: row.id, goalId: row.goal_id, title: row.title, status: row.status, createdAt: row.created_at,
223
+ ...(closed?.reason === undefined ? {} : { closedReason: closed.reason }),
219
224
  sections: events.filter((event): event is Extract<CardEvent, { type: 'section' }> => event.type === 'section')
220
225
  .map(({ key, owner, content, createdAt }) => ({ key, owner, content, createdAt })),
221
226
  };
@@ -1,3 +1,31 @@
1
+ /** Executor kinds of the dev child's `--json` result line (`src/index.ts` writeStdoutJson · DevExecutor.kind). */
2
+ const CHILD_RESULT_KINDS = new Set(['self', 'external']);
3
+
4
+ /** The dev child's terminal line nests the stage: {ok, kind, result:{stage, ok, node, outcome, …}}. Other JSON rows are not results. */
5
+ export function nestedPodResult(row: Record<string, unknown>): { stage: string; ok: boolean; error?: string } | null {
6
+ if (typeof row.kind !== 'string' || !CHILD_RESULT_KINDS.has(row.kind) || typeof row.ok !== 'boolean') return null;
7
+ if (!row.result || typeof row.result !== 'object' || Array.isArray(row.result)) return null;
8
+ const result = row.result as Record<string, unknown>;
9
+ if (typeof result.stage !== 'string' || typeof result.ok !== 'boolean') return null;
10
+ if (result.ok) return { stage: result.stage, ok: true };
11
+ const classification = result.abandonedClassification;
12
+ const detail = typeof result.supervisorVerdict === 'string' ? result.supervisorVerdict
13
+ : classification && typeof classification === 'object' && !Array.isArray(classification)
14
+ ? (classification as Record<string, unknown>).classification : undefined;
15
+ const reason = [result.node, result.outcome].filter((part): part is string => typeof part === 'string' && part.length > 0).join(' ');
16
+ const error = [reason || (typeof result.error === 'string' ? result.error : ''), typeof detail === 'string' && detail ? detail : ''].filter(Boolean).join(' · ');
17
+ return { stage: result.stage, ok: false, ...(error ? { error } : {}) };
18
+ }
19
+
20
+ /** A child terminal row: the legacy flat form {stage, ok, error?} (no kind, or a child kind) or the nested dev result. Any other kind is not a result. */
21
+ export function podTerminalRow(row: Record<string, unknown>): { stage: string; ok: boolean; error?: string } | null {
22
+ if (typeof row.kind === 'string' && !CHILD_RESULT_KINDS.has(row.kind)) return null;
23
+ if (typeof row.stage === 'string' && typeof row.ok === 'boolean') {
24
+ return { stage: row.stage, ok: row.ok, ...(typeof row.error === 'string' ? { error: row.error } : {}) };
25
+ }
26
+ return nestedPodResult(row);
27
+ }
28
+
1
29
  /** Extract a human-readable reason from a failed child Pod without treating transfer markers as child errors. */
2
30
  export function extractPodFailureReason(input: {
3
31
  logs: string;
@@ -12,7 +40,7 @@ export function extractPodFailureReason(input: {
12
40
  if (input.containerReason === 'OOMKilled') return 'OOMKilled: 컨테이너 메모리 한도 초과';
13
41
 
14
42
  const isBookkeeping = (line: string): boolean =>
15
- /^ELANOUS_(?:MEM|RUN_LEDGER|USAGE_ROLLUP|POD_)/u.test(line) || /^(?:at\s+\S|\.\.\.\s+\d+\s+more\b|cleanup\s+(?:complete|done|finished)\b)/iu.test(line);
43
+ /^(?:ELANOUS_(?:MEM|RUN_LEDGER|USAGE_ROLLUP|POD_)|\[graph\] )/u.test(line) || /^(?:at\s+\S|\.\.\.\s+\d+\s+more\b|cleanup\s+(?:complete|done|finished)\b)/iu.test(line);
16
44
  const isError = (line: string): boolean =>
17
45
  /\berror\b|\bfail(?:ed|ure)?\b|exception|fatal|denied|timed?\s*out|exceeded|overloaded|unavailable|exhausted|오류|실패/iu.test(line);
18
46
  const meaningful: Array<{ text: string; terminal: boolean }> = [];
@@ -24,10 +52,11 @@ export function extractPodFailureReason(input: {
24
52
  try { parsed = JSON.parse(trimmed); } catch { /* An ordinary line may begin with a brace. */ }
25
53
  if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
26
54
  const row = parsed as Record<string, unknown>;
27
- if (typeof row.ok === 'boolean' && typeof row.stage === 'string') {
55
+ const terminal = podTerminalRow(row);
56
+ if (terminal) {
28
57
  meaningful.length = 0;
29
- if (row.ok === false && typeof row.error === 'string') {
30
- const errorLines = row.error.split(/\r\n|\n|\r/u).map((part) => part.trim())
58
+ if (terminal.ok === false && typeof terminal.error === 'string') {
59
+ const errorLines = terminal.error.split(/\r\n|\n|\r/u).map((part) => part.trim())
31
60
  .filter((part) => part && !isBookkeeping(part));
32
61
  const errorLine = [...errorLines].reverse().find(isError) ?? errorLines.at(-1);
33
62
  if (errorLine) meaningful.push({ text: errorLine, terminal: true });
@@ -64,6 +64,9 @@ export interface PoolLeaseRecommendation {
64
64
  }
65
65
 
66
66
  /** Kubernetes quantity to bytes (memory) or millicores (CPU). */
67
+ /** Kubernetes memory quantity (`16Gi`, `2Gi`, `512Mi`) → bytes; null when unparseable. */
68
+ export function memoryQuantityBytes(raw: string): number | null { return quantity(raw, 'memory'); }
69
+
67
70
  function quantity(raw: string, unit: 'memory' | 'cpu'): number | null {
68
71
  const m = /^(\d+(?:\.\d+)?)(n|u|m|k|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$/.exec(raw);
69
72
  if (!m) return null;
@@ -235,7 +238,7 @@ function runPoolDnsProbe(context: string, kubectl: PoolKubectl): PoolDnsProbe {
235
238
  }
236
239
 
237
240
  /** Each cluster reads resource reservations; DNS requires a bounded disposable Pod. */
238
- export function measurePoolLease(members: readonly PodPoolMember[], deps: { kubectl?: PoolKubectl; dns?: (context: string) => PoolDnsProbe } = {}): PoolLeaseMeasure {
241
+ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kubectl?: PoolKubectl; dns?: (context: string) => PoolDnsProbe; /** Read-only snapshots must not create the disposable DNS Pod or update its cache. */ skipDnsProbe?: boolean } = {}): PoolLeaseMeasure {
239
242
  const kubectl = deps.kubectl ?? leaseKubectl;
240
243
  return { members: members.map((member) => {
241
244
  const base = ['--context', member.context, '--request-timeout=10s'];
@@ -354,7 +357,7 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
354
357
  else if (!reservationsValid && !unknownPhase) reasons.push('cluster reservations: node assignment or memory request/limit missing/invalid');
355
358
  }
356
359
  result.reason = reasons.length ? reasons.join('; ').slice(0, 240) : null;
357
- if (nodeItems?.length && jobItems && podItems) {
360
+ if (!deps.skipDnsProbe && nodeItems?.length && jobItems && podItems) {
358
361
  let dnsState: PoolDnsProbe = 'unknown';
359
362
  try { dnsState = (deps.dns ?? ((context) => probePoolDns(context, kubectl)))(member.context); } catch { /* probe could not be measured */ }
360
363
  if (dnsState === 'dns') { result.capacity = 0; result.availableMemoryByNodeBytes = []; result.reason = 'dns'; }
@@ -3,6 +3,7 @@ import { gunzipSync } from 'node:zlib';
3
3
  import { debug } from '../../debug/log.js';
4
4
  import { runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
5
5
  import { ledgerLineToLogEvent } from './pod-ledger-events.js';
6
+ import { defaultPodReemit } from './pod-ledger-prod-sink.js';
6
7
 
7
8
  type Ledger = { runId: string; jsonl: string };
8
9
  type Incomplete = { runId: string; reason: string };
@@ -94,7 +95,7 @@ export function parsePodLedgerChunks(logs: string): ParseResult {
94
95
  * except a ledger that THIS run's live follower created (`replace`): that one is a partial copy and the final is complete. */
95
96
  export function collectPodLedgers(
96
97
  logs: string,
97
- { dir = runLedgerDir(), log = (c, e, d) => debug.log(c, e, d), emit = (c, e, d) => debug.log(c, e, d), replace = new Set<string>() }: {
98
+ { dir = runLedgerDir(), log = defaultPodReemit(), emit = defaultPodReemit(), replace = new Set<string>() }: {
98
99
  dir?: string;
99
100
  log?: (category: string, event: string, data: Record<string, unknown>) => void;
100
101
  emit?: Emit;
@@ -146,8 +147,8 @@ export function createPodLedgerFollower(opts: {
146
147
  onStall?: (message: string) => void;
147
148
  }): { poll(): void; readonly owned: boolean } {
148
149
  const dir = opts.dir ?? runLedgerDir();
149
- const log = opts.log ?? ((c, e, d) => debug.log(c, e, d));
150
- const emit: Emit = opts.emit ?? ((c, e, d) => debug.log(c, e, d));
150
+ const log = opts.log ?? defaultPodReemit();
151
+ const emit: Emit = opts.emit ?? defaultPodReemit();
151
152
  const now = opts.now ?? Date.now;
152
153
  const stallMinutes = opts.stallMinutes ?? 30;
153
154
  if (!Number.isFinite(stallMinutes) || stallMinutes <= 0) throw new RangeError('stallMinutes must be positive');