elanous 0.2.21 → 0.2.22

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 (315) 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-08b716476f1d50af.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{2489-ff55a35b202c0f94.js → 2489-d238d3e2eb5b2bf5.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/approvals/page-88459e0903da2803.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-d781be3e3a9cfd74.js → page-d85c43f46f58716b.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/board/{page-b0a80997903852d8.js → page-4e645600579ae952.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-2b1263165630d9c8.js → page-87edbfd19117823b.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/app/chat/{page-e2d6bed48ac20120.js → page-cbaf66b1e1804adf.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/app/consult/{page-111cabe4d1fd69ae.js → page-80ed511e02c81412.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/control/{page-544dd4bd4847eeb7.js → page-9f5610caaa06178c.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-3ee191319bf97d87.js → page-ec8f3575e339de74.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/exec/{page-a449f1ed472b81c2.js → page-2a19c5cd3b562580.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/field/{page-9045094d6d3b5cd0.js → page-b6a09cde2e07521a.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/inside/{page-c06673596849ef45.js → page-f7eae1518ca0c353.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/{layout-9f81233f29dcd409.js → layout-8462d6a543cbb2d9.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/loops/page-abd86cd832aaa011.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8c3f168fc3f91939.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/morning/{page-7a12f61be4a579d1.js → page-786fb4504d0f2fa9.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/{not-found-c85b0589591a99c9.js → not-found-75209f14597dcb8f.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/observatory/{page-725a4da02dd129eb.js → page-bf30affe3a85af7e.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/{page-21bec4afeb2f0917.js → page-76cf838050a8d681.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-dc39ee7a36d26385.js → page-7ec8e289b4c14df6.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-84ae759f76053672.js → page-8c8ac17d52be2a79.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-3db58ddb61fe2be9.js → page-d359ab048d63a9d9.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-c5315e30f3528dc4.js → page-b8b345c880bf78d1.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/settings/{page-2fe368efb6de3b6a.js → page-f151cc10ad026940.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/share/{page-c69762987b751174.js → page-bf495a1e284f4e00.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/term/page-9abcfac07e02a542.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-7f8d7fcdf058a845.js → page-69cb93d1a2e57e0e.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-22163556cd38bc88.js → page-72ec543928d67c3e.js} +1 -1
  57. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +1 -0
  58. package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +1 -0
  59. package/apps/pwa/out/approvals/index.html +1 -1
  60. package/apps/pwa/out/approvals/index.txt +14 -14
  61. package/apps/pwa/out/autopilot/index.html +1 -1
  62. package/apps/pwa/out/autopilot/index.txt +14 -14
  63. package/apps/pwa/out/board/index.html +1 -1
  64. package/apps/pwa/out/board/index.txt +14 -14
  65. package/apps/pwa/out/botlab/index.html +1 -1
  66. package/apps/pwa/out/botlab/index.txt +14 -14
  67. package/apps/pwa/out/bots/index.html +1 -1
  68. package/apps/pwa/out/bots/index.txt +14 -14
  69. package/apps/pwa/out/ceo/index.html +1 -1
  70. package/apps/pwa/out/ceo/index.txt +14 -14
  71. package/apps/pwa/out/chat/index.html +1 -1
  72. package/apps/pwa/out/chat/index.txt +14 -14
  73. package/apps/pwa/out/consult/index.html +1 -1
  74. package/apps/pwa/out/consult/index.txt +14 -14
  75. package/apps/pwa/out/control/index.html +1 -1
  76. package/apps/pwa/out/control/index.txt +14 -14
  77. package/apps/pwa/out/decisions/index.html +1 -1
  78. package/apps/pwa/out/decisions/index.txt +14 -14
  79. package/apps/pwa/out/design-check/index.html +1 -1
  80. package/apps/pwa/out/design-check/index.txt +14 -14
  81. package/apps/pwa/out/editor/index.html +1 -1
  82. package/apps/pwa/out/editor/index.txt +14 -14
  83. package/apps/pwa/out/exec/index.html +1 -1
  84. package/apps/pwa/out/exec/index.txt +14 -14
  85. package/apps/pwa/out/field/index.html +1 -1
  86. package/apps/pwa/out/field/index.txt +14 -14
  87. package/apps/pwa/out/healthz +1 -1
  88. package/apps/pwa/out/index.html +1 -1
  89. package/apps/pwa/out/index.txt +14 -14
  90. package/apps/pwa/out/inside/index.html +1 -1
  91. package/apps/pwa/out/inside/index.txt +14 -14
  92. package/apps/pwa/out/intake/index.html +1 -1
  93. package/apps/pwa/out/intake/index.txt +14 -14
  94. package/apps/pwa/out/live/index.html +1 -1
  95. package/apps/pwa/out/live/index.txt +14 -14
  96. package/apps/pwa/out/live-run/index.html +1 -1
  97. package/apps/pwa/out/live-run/index.txt +14 -14
  98. package/apps/pwa/out/loops/index.html +1 -1
  99. package/apps/pwa/out/loops/index.txt +14 -14
  100. package/apps/pwa/out/market/index.html +1 -1
  101. package/apps/pwa/out/market/index.txt +14 -14
  102. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  103. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  104. package/apps/pwa/out/morning/index.html +1 -1
  105. package/apps/pwa/out/morning/index.txt +14 -14
  106. package/apps/pwa/out/observatory/index.html +1 -1
  107. package/apps/pwa/out/observatory/index.txt +14 -14
  108. package/apps/pwa/out/ops/checklist/index.html +1 -1
  109. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  110. package/apps/pwa/out/ops/release/index.html +1 -1
  111. package/apps/pwa/out/ops/release/index.txt +14 -14
  112. package/apps/pwa/out/ops/seats/index.html +1 -1
  113. package/apps/pwa/out/ops/seats/index.txt +14 -14
  114. package/apps/pwa/out/outputs/index.html +1 -1
  115. package/apps/pwa/out/outputs/index.txt +14 -14
  116. package/apps/pwa/out/reflection/index.html +1 -1
  117. package/apps/pwa/out/reflection/index.txt +15 -15
  118. package/apps/pwa/out/scheduler/index.html +1 -1
  119. package/apps/pwa/out/scheduler/index.txt +14 -14
  120. package/apps/pwa/out/sessions/index.html +1 -1
  121. package/apps/pwa/out/sessions/index.txt +14 -14
  122. package/apps/pwa/out/settings/devices/index.html +1 -1
  123. package/apps/pwa/out/settings/devices/index.txt +14 -14
  124. package/apps/pwa/out/settings/index.html +1 -1
  125. package/apps/pwa/out/settings/index.txt +14 -14
  126. package/apps/pwa/out/setup/done/index.html +1 -1
  127. package/apps/pwa/out/setup/done/index.txt +14 -14
  128. package/apps/pwa/out/setup/index.html +1 -1
  129. package/apps/pwa/out/setup/index.txt +14 -14
  130. package/apps/pwa/out/share/index.html +1 -1
  131. package/apps/pwa/out/share/index.txt +14 -14
  132. package/apps/pwa/out/showroom/index.html +1 -1
  133. package/apps/pwa/out/showroom/index.txt +14 -14
  134. package/apps/pwa/out/tasks/index.html +1 -1
  135. package/apps/pwa/out/tasks/index.txt +14 -14
  136. package/apps/pwa/out/term/index.html +1 -1
  137. package/apps/pwa/out/term/index.txt +14 -14
  138. package/apps/pwa/out/today/index.html +1 -1
  139. package/apps/pwa/out/today/index.txt +14 -14
  140. package/apps/pwa/out/trace/index.html +1 -1
  141. package/apps/pwa/out/trace/index.txt +14 -14
  142. package/apps/pwa/out/vault/index.html +1 -1
  143. package/apps/pwa/out/vault/index.txt +14 -14
  144. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  145. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  146. package/apps/pwa/out/workflows/index.html +1 -1
  147. package/apps/pwa/out/workflows/index.txt +14 -14
  148. package/apps/pwa/out/workspace/index.html +1 -1
  149. package/apps/pwa/out/workspace/index.txt +14 -14
  150. package/apps/pwa/out/worktrees/index.html +1 -1
  151. package/apps/pwa/out/worktrees/index.txt +14 -14
  152. package/graphs/catalog/catalog.yaml +1 -1
  153. package/graphs/drafts/recipes.yaml +2 -2
  154. package/graphs/test-diet/recipes.yaml +3 -0
  155. package/graphs/test-diet/test-diet.yaml +3 -1
  156. package/package.json +1 -1
  157. package/scripts/f-session-monitor.ts +69 -0
  158. package/scripts/hq/standby-snapshot.ts +7 -3
  159. package/scripts/knowledge-compress.ts +11 -0
  160. package/scripts/nightly-docops.ts +90 -3
  161. package/scripts/research-coo-admin-dates.ts +39 -0
  162. package/scripts/skills/public-check.ts +7 -2
  163. package/scripts/smart-merge/resync-shadow.ts +5 -3
  164. package/scripts/test-diet/lib.ts +81 -1
  165. package/scripts/test-diet/node.ts +48 -2
  166. package/scripts/verify-coo-admin-evidence.ts +83 -0
  167. package/src/acp/client.ts +4 -2
  168. package/src/agent/run-context.ts +4 -0
  169. package/src/agent-env/profile.ts +25 -5
  170. package/src/agent-mission/driver.ts +26 -12
  171. package/src/agent-mission/supervisor-metrics.ts +17 -2
  172. package/src/agent-substrate/acp-reviewer.ts +12 -5
  173. package/src/agent-substrate/pr-reviewer.ts +11 -4
  174. package/src/agent-substrate/review-intent.ts +102 -3
  175. package/src/autopilot/build/llm-conflict-merge.ts +22 -8
  176. package/src/autopilot/pr-manager.ts +18 -8
  177. package/src/away/release-watch.ts +47 -8
  178. package/src/briefing/brief-items.ts +104 -11
  179. package/src/chat/index.ts +1 -0
  180. package/src/cli/brief-cli.ts +20 -2
  181. package/src/cli/config-classify.ts +83 -0
  182. package/src/cli/control-cli.ts +14 -0
  183. package/src/cli/decisions-cli.ts +26 -16
  184. package/src/cli/logs-model-input.ts +1 -1
  185. package/src/cli/pr-cli.ts +65 -8
  186. package/src/cli/release-cli.ts +21 -4
  187. package/src/cli/tasks-cli.ts +108 -1
  188. package/src/connectors/types.ts +6 -6
  189. package/src/context-bus/context-now-surfaces.ts +18 -0
  190. package/src/context-bus/coord-events.ts +32 -4
  191. package/src/context-bus/long-term-memory.ts +23 -6
  192. package/src/context-bus/nightly-condense.ts +169 -21
  193. package/src/control/control-status.ts +87 -0
  194. package/src/control-plane/light-member.ts +5 -3
  195. package/src/control-plane/member.ts +172 -7
  196. package/src/core-turn/run-goal-loop.ts +22 -1
  197. package/src/dashboard/slash-runtime/dashboard-handlers.ts +9 -0
  198. package/src/decisions/decision-cards.ts +20 -1
  199. package/src/decisions/decision-ledger.ts +11 -8
  200. package/src/decisions/web-push-decision-cards.ts +19 -3
  201. package/src/discord-self-message.ts +2 -2
  202. package/src/domains/coo-admin-dates.evidence.json +876 -0
  203. package/src/domains/coo-admin-tool.ts +48 -14
  204. package/src/domains/ops-status.ts +53 -0
  205. package/src/domains/outbound-alert.ts +24 -1
  206. package/src/exec-requests/planner.ts +6 -0
  207. package/src/graph-kinds/registry.ts +6 -6
  208. package/src/graph-runner/graph-cli.ts +34 -2
  209. package/src/graph-runner/graph-run-api.ts +7 -0
  210. package/src/graph-runner/graph-tick.ts +73 -12
  211. package/src/graph-runner/runner.ts +43 -11
  212. package/src/graph-unify/wf-to-graph.ts +79 -0
  213. package/src/graph-wizard/generate.ts +34 -2
  214. package/src/graph-wizard/steps.ts +13 -0
  215. package/src/harness/harness-cli-command.ts +121 -23
  216. package/src/harness/harness-queue.ts +58 -14
  217. package/src/harness/launch-advice.ts +23 -0
  218. package/src/index.ts +36 -7
  219. package/src/knowledge/conclusion-compress.ts +93 -0
  220. package/src/knowledge/know-find.ts +9 -2
  221. package/src/knowledge/query.ts +20 -0
  222. package/src/llm.ts +26 -16
  223. package/src/loops/neighbor-runtime.ts +88 -0
  224. package/src/loops/orchestrator/author-depth.ts +29 -11
  225. package/src/loops/orchestrator/overlap.ts +7 -1
  226. package/src/loops/orchestrator/resync-shadow.ts +1 -1
  227. package/src/loops/orchestrator/tick.ts +35 -2
  228. package/src/loops/orchestrator/traffic.ts +2 -0
  229. package/src/loops/registry.ts +5 -0
  230. package/src/market/publish.ts +17 -1
  231. package/src/maturity/feature-maturity.ts +2 -1
  232. package/src/mss/logging/redaction.ts +17 -0
  233. package/src/nexus/api/draft-metrics.ts +22 -8
  234. package/src/nexus/api/graph-peer-edit.ts +77 -0
  235. package/src/nexus/api/graph-wizard-api.ts +9 -1
  236. package/src/nexus/api/graphs-api.ts +145 -7
  237. package/src/nexus/api/http-server.ts +93 -8
  238. package/src/nexus/api/plugins-market.ts +24 -0
  239. package/src/nexus/index.ts +3 -2
  240. package/src/oauth/claude-pty-maturity.ts +24 -0
  241. package/src/oauth/fallback-chain.ts +27 -14
  242. package/src/plugins/maker/market-bundle.ts +9 -0
  243. package/src/plugins/maker/plugin-maker.ts +9 -3
  244. package/src/plugins/maker/wizard-generate.ts +73 -9
  245. package/src/policy/llm-policy.ts +11 -6
  246. package/src/release-loop/checklist-board.ts +27 -0
  247. package/src/release-loop/checklist.ts +38 -0
  248. package/src/seat-address/seat-address.ts +1 -0
  249. package/src/seat-loop/seat-loop.ts +174 -20
  250. package/src/seat-loop/seat-report.ts +29 -1
  251. package/src/self-dev/dev-cli.ts +90 -8
  252. package/src/self-dev/dev-pipeline.ts +20 -1
  253. package/src/self-dev/draft-sweep.ts +45 -8
  254. package/src/self-dev/orchestrate.ts +3 -0
  255. package/src/self-dev/review-provider-fallback.ts +83 -0
  256. package/src/self-dev/run-supervisor.ts +5 -1
  257. package/src/self-implement/harness-policy-gates.ts +7 -5
  258. package/src/self-implement/host-regate.ts +28 -14
  259. package/src/self-implement/l8-merge-queue.ts +80 -8
  260. package/src/self-implement/orchestrator.ts +68 -27
  261. package/src/self-implement/seams.ts +5 -3
  262. package/src/self-implement/self-implement-cli.ts +4 -0
  263. package/src/shell-env-bootstrap.ts +19 -4
  264. package/src/steward/triage.ts +25 -8
  265. package/src/storage/file-lock.ts +33 -4
  266. package/src/task-agent/completion-evidence.ts +31 -3
  267. package/src/task-agent/cover.ts +61 -0
  268. package/src/task-agent/live-moves.ts +126 -3
  269. package/src/task-agent/shadow.ts +19 -4
  270. package/src/task-agent/task-hand.ts +2 -0
  271. package/src/task-orchestrator/surfaces/pod-pool.ts +138 -15
  272. package/src/task-orchestrator/surfaces/self-implement.ts +7 -0
  273. package/src/telegram-commands.ts +8 -0
  274. package/src/telegram-project-command.ts +6 -0
  275. package/src/telegram.ts +2 -2
  276. package/src/typecheck-ratchet.ts +2 -1
  277. package/src/user-config.ts +35 -4
  278. package/src/version/packed-revision.json +1 -1
  279. package/src/workflow-runtime/executor.ts +24 -2
  280. package/src/workflow-runtime/triggers/registry.ts +27 -0
  281. package/src/workflow-runtime/triggers/schedule-source.ts +30 -3
  282. package/apps/pwa/out/_next/static/chunks/1307-c97eba9fb72a9a5e.js +0 -1
  283. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +0 -1
  284. package/apps/pwa/out/_next/static/chunks/5977-59fedea50d13c762.js +0 -1
  285. package/apps/pwa/out/_next/static/chunks/8857-f1762140ef9a96b1.js +0 -1
  286. package/apps/pwa/out/_next/static/chunks/9247-7581825d511a64c0.js +0 -1
  287. package/apps/pwa/out/_next/static/chunks/9497-b0e758d1cd4ae8d7.js +0 -1
  288. package/apps/pwa/out/_next/static/chunks/app/approvals/page-937c92292a4ab4f0.js +0 -1
  289. package/apps/pwa/out/_next/static/chunks/app/bots/page-ea998f7f5d156bea.js +0 -1
  290. package/apps/pwa/out/_next/static/chunks/app/ceo/page-33c328b64ca508f8.js +0 -1
  291. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e4bcb2c503dab04b.js +0 -1
  292. package/apps/pwa/out/_next/static/chunks/app/editor/page-300897dcbbd5ed76.js +0 -1
  293. package/apps/pwa/out/_next/static/chunks/app/intake/page-103fa3191c1b2e08.js +0 -1
  294. package/apps/pwa/out/_next/static/chunks/app/live/page-be8b4615c919e36c.js +0 -1
  295. package/apps/pwa/out/_next/static/chunks/app/live-run/page-c685b104e0dfa609.js +0 -1
  296. package/apps/pwa/out/_next/static/chunks/app/loops/page-8d693331cfbb6add.js +0 -1
  297. package/apps/pwa/out/_next/static/chunks/app/market/page-cef4f30a4c22fae1.js +0 -1
  298. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66ea1b54fc83fa15.js +0 -1
  299. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8cc707bd09a497d9.js +0 -1
  300. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-90b55be1f15ce0e8.js +0 -1
  301. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cb16606e08be65db.js +0 -1
  302. package/apps/pwa/out/_next/static/chunks/app/outputs/page-16dc64b74ed6a40a.js +0 -1
  303. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8d04132dd0ac5e10.js +0 -1
  304. package/apps/pwa/out/_next/static/chunks/app/setup/page-f23484de467c9ea3.js +0 -1
  305. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5456692bda39aafb.js +0 -1
  306. package/apps/pwa/out/_next/static/chunks/app/tasks/page-d75f7cd65845ce98.js +0 -1
  307. package/apps/pwa/out/_next/static/chunks/app/term/page-5603f5e568b25ce3.js +0 -1
  308. package/apps/pwa/out/_next/static/chunks/app/today/page-d2cc0f4e89f42028.js +0 -1
  309. package/apps/pwa/out/_next/static/chunks/app/trace/page-a6eb73b8a4840f1c.js +0 -1
  310. package/apps/pwa/out/_next/static/chunks/app/vault/page-c0a3f4d10edb83fa.js +0 -1
  311. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6c0b88068c143f15.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-141436fd26dd56fc.js +0 -1
  313. package/apps/pwa/out/_next/static/css/e81df11490b4413b.css +0 -1
  314. /package/apps/pwa/out/_next/static/{MObU8ujkMciAKr6ljAV05 → aBi6EyMtLsrl7J8eH4kF7}/_buildManifest.js +0 -0
  315. /package/apps/pwa/out/_next/static/{MObU8ujkMciAKr6ljAV05 → aBi6EyMtLsrl7J8eH4kF7}/_ssgManifest.js +0 -0
@@ -7,6 +7,10 @@ import { debug } from '../debug/log.js';
7
7
  import { effectiveInstanceRoot } from '../instance/resolve.js';
8
8
  import { getUserConfig, type SeatLoopConfig } from '../user-config.js';
9
9
  import { collectDraftMetrics, collectOverlapMetrics, countSalvagedToday, type DraftMetrics, type OverlapMetrics } from '../self-dev/draft-sweep.js';
10
+ import { queryRunningRuns } from '../self-implement/running-runs.js';
11
+ import { readTaskAgentCover } from '../cli/tasks-cli.js';
12
+ import { formatTaskAgentCover } from '../task-agent/cover.js';
13
+ import type { TasksCliDeps } from '../cli/tasks-cli.js';
10
14
  import { seatDay, seatLedgerPath, type OpCandidate, type SeatEntry } from './seat-loop.js';
11
15
 
12
16
  export type SeatReportDeps = {
@@ -19,8 +23,10 @@ export type SeatReportDeps = {
19
23
  send?: (body: string, seat: string, pr: number) => Promise<void> | void;
20
24
  draftMetrics?: (now: Date) => Promise<DraftMetrics>;
21
25
  overlapMetrics?: (now: Date) => Promise<OverlapMetrics>;
26
+ coverLogs?: TasksCliDeps['coverLogs'];
22
27
  /** DRAFT-NOT-ARCHIVE salvage branches since the KST day start; null = unreadable. */
23
28
  salvagedToday?: (dayStart: Date, now: Date) => Promise<number | null>;
29
+ runningRuns?: (runIds: readonly string[]) => { running: number; unknown: number } | null;
24
30
  };
25
31
  export type SeatReportResult = { seat: string; date: string; body: string; posted: boolean };
26
32
  const repoRoot = resolve(import.meta.dir, '../..');
@@ -144,8 +150,30 @@ export async function seatReport(seat: string, deps: SeatReportDeps = {}): Promi
144
150
  }))(new Date(`${date}T00:00:00+09:00`), now);
145
151
  } catch { salvagedToday = null; }
146
152
  draftDetail += ` · 수확 가지 오늘 ${salvagedToday === null ? '못 잼' : salvagedToday}`;
153
+ try {
154
+ const cover = readTaskAgentCover({}, deps.coverLogs);
155
+ draftDetail += ` · TASK-AGENT cover ${formatTaskAgentCover(cover.rows[0]!)}${cover.unreadableStores ? ` (logs.db ${cover.unreadableStores}개 못 읽음)` : ''}`;
156
+ } catch {
157
+ draftDetail += ` · TASK-AGENT cover ${formatTaskAgentCover({ verb: 'land', byTaskAgent: null, total: null, observedActions: null, ratio: null, state: 'unreadable', liveActions: null, shadowActions: null, stewardTransition: 'unmeasured' })}`;
158
+ }
159
+ }
160
+ const runIds = [...new Set(entries.filter((entry) => entry.status === 'launched' && entry.runId).map((entry) => entry.runId!))];
161
+ let runningDetail = ' · 도는 런 0';
162
+ if (runIds.length) {
163
+ try {
164
+ const counts = (deps.runningRuns ?? ((ids: readonly string[]) => {
165
+ const observed = queryRunningRuns({ runIds: ids, caller: 'seat-report' });
166
+ if (observed.completeness !== 'complete') return null;
167
+ return {
168
+ running: observed.entries.filter((entry) => ids.includes(entry.runId) && entry.status === 'running').length,
169
+ unknown: observed.entries.filter((entry) => ids.includes(entry.runId) && (entry.status === 'probable-running' || entry.status === 'unknown')).length,
170
+ };
171
+ }))(runIds);
172
+ runningDetail = counts === null ? ' · 도는 런 못 잼'
173
+ : ` · 도는 런 ${counts.running}(불확실 ${counts.unknown})`;
174
+ } catch { runningDetail = ' · 도는 런 못 잼'; }
147
175
  }
148
- const body = `**[${seat}]** {{TS}} → 보고 ${date}: ${detail.join(' · ') || (classified.length ? 'OP 그림자 판단' : '원장 기록 없음')}${opDetail}${draftDetail}`;
176
+ const body = `**[${seat}]** {{TS}} → 보고 ${date}: ${detail.join(' · ') || (classified.length ? 'OP 그림자 판단' : '원장 기록 없음')}${opDetail}${draftDetail}${runningDetail}`;
149
177
  const pr = (deps.config ?? getUserConfig().loops?.seat)?.reportPr;
150
178
  const posted = deps.post === true && pr !== undefined;
151
179
  if (posted) await (deps.send ?? ((text, id, number) => defaultSend(text, id, number, deps.repo ?? repoRoot)))(body, seat, pr);
@@ -45,6 +45,7 @@ import { getUserConfig, type UserConfig } from '../user-config.js';
45
45
  import { resolveChildLlmPreference, type ChildLlmPreferenceInput, type ResolvedChildLlmPreference } from '../self-implement/child-llm-preference.js';
46
46
  import { applyLaunchOverloadFailover, codexProviderNamed } from '../oauth/overload-failover-launch.js';
47
47
  import { readCachedGrokQuota } from '../oauth/codex-account-store.js';
48
+ import { appendRunLedgerEntry } from '../self-implement/run-ledger.js';
48
49
  import { queryAbandonedDraftPrs, type AbandonedDraftPr } from '../cli/logs-abandoned-draft-prs.js';
49
50
  import { branchLineageSlug } from '../cli/pr-lineage.js';
50
51
  import { decideDraft, type DraftTriagePr } from './draft-triage-rules.js';
@@ -1494,6 +1495,20 @@ export function formatChildLlmInterpretationLine(
1494
1495
  return `[dev] child-llm: entered=${resolution.entered} · resolved=${resolution.resolvedId} · tier=${resolution.tier} · thinking=${resolution.supportsThinking}${tail}`;
1495
1496
  }
1496
1497
 
1498
+ /** Only the weekly Grok credits period measures the weekly cap; missing or malformed periods are unmeasured. */
1499
+ export function grokWeeklyUsedPctFromUsageJson(stdout: string): number | undefined {
1500
+ try {
1501
+ const parsed = JSON.parse(stdout) as { rows?: Array<{ provider?: string; credits?: { status?: string; periodType?: string | null; usedPercent?: unknown } }> };
1502
+ if (!Array.isArray(parsed.rows)) return undefined;
1503
+ const percents = parsed.rows
1504
+ .filter((row) => row?.provider === 'grok' && row.credits?.status === 'ok'
1505
+ && row.credits.periodType === 'USAGE_PERIOD_TYPE_WEEKLY')
1506
+ .map((row) => row.credits?.usedPercent)
1507
+ .filter((pct): pct is number => typeof pct === 'number' && Number.isFinite(pct) && pct >= 0 && pct <= 100);
1508
+ return percents.length > 0 ? Math.max(...percents) : undefined;
1509
+ } catch { return undefined; }
1510
+ }
1511
+
1497
1512
  /** `elanous usage --json` 의 grok 행 → 잔량 판정. 한 행이라도 100% 미만이면 usable, 모두 100% 이상이면 exhausted. */
1498
1513
  export function grokQuotaFromUsageJson(stdout: string): 'usable' | 'exhausted' | 'unknown' {
1499
1514
  try {
@@ -1519,17 +1534,75 @@ export function setLaunchGrokQuotaReaderForTesting(reader: (() => 'usable' | 'ex
1519
1534
  launchGrokQuotaReaderForTesting = reader;
1520
1535
  }
1521
1536
 
1537
+ let launchGrokWeeklyReaderForTesting: (() => number | undefined) | undefined;
1538
+ export function setLaunchGrokWeeklyReaderForTesting(reader: (() => number | undefined) | undefined): void {
1539
+ launchGrokWeeklyReaderForTesting = reader;
1540
+ }
1541
+
1542
+ /**
1543
+ * 발사 한 번에 `elanous usage --json`(약 3초) 을 «한 번만» 부른다 — 주간 사용률(재배분)과 잔량(경고)이 같은 출력을 나눈다.
1544
+ * 한 CLI 프로세스 = 한 발사이므로 프로세스 안 60초 캐시로 충분하다(실패 null 도 캐시 — 같은 발사에서 20초 타임아웃을 두 번 물지 않게).
1545
+ */
1546
+ let launchUsageJsonCache: { at: number; stdout: string | null } | undefined;
1547
+ const LAUNCH_USAGE_CACHE_MS = 60_000;
1548
+ export function runLaunchUsageJsonOnce(): string | null {
1549
+ const now = Date.now();
1550
+ if (launchUsageJsonCache && now - launchUsageJsonCache.at < LAUNCH_USAGE_CACHE_MS) return launchUsageJsonCache.stdout;
1551
+ let stdout: string | null = null;
1552
+ try {
1553
+ const cli = new URL('../../bin/elanous.mjs', import.meta.url).pathname;
1554
+ const r = spawnSync(process.execPath, [cli, ...(IN_TEST_PROCESS() || process.env.ELANOUS_TEST_MODE === '1' ? ['--test'] : []), 'usage', '--json'], { encoding: 'utf8', timeout: 20_000 });
1555
+ stdout = r.status === 0 ? r.stdout : null;
1556
+ } catch { stdout = null; }
1557
+ launchUsageJsonCache = { at: now, stdout };
1558
+ return stdout;
1559
+ }
1560
+
1561
+ /** Launch's weekly percentage comes from the live usage period, not the coarse cached usable flag. */
1562
+ export function readGrokWeeklyUsedPctForLaunch(runUsage?: () => string | null): number | undefined {
1563
+ if (launchGrokWeeklyReaderForTesting && !runUsage) return launchGrokWeeklyReaderForTesting();
1564
+ try {
1565
+ const stdout = (runUsage ?? runLaunchUsageJsonOnce)();
1566
+ return stdout ? grokWeeklyUsedPctFromUsageJson(stdout) : undefined;
1567
+ } catch { return undefined; }
1568
+ }
1569
+
1570
+ export function rebalanceGrokChildForLaunch(
1571
+ child: ChildLlmSelection,
1572
+ cap: number,
1573
+ used: number | undefined,
1574
+ runId?: string,
1575
+ deps: { write?: (line: string) => void; record?: typeof appendRunLedgerEntry } = {},
1576
+ ): ChildLlmSelection {
1577
+ if (child.provider !== 'grok') return child;
1578
+ const measured = typeof used === 'number' && Number.isFinite(used) && used >= 0 && used <= 100;
1579
+ const rebalanced = measured && used >= cap;
1580
+ const selected = rebalanced
1581
+ ? { ...child, provider: 'openai-codex', model: defaultChildLlmModel('openai-codex'), codexQuotaPolicy: 'credits' as const }
1582
+ : child;
1583
+ const reason = !measured ? '측정 불가' : rebalanced ? `grok 주간 사용률 ${used}% ≥ 상한 ${cap}%` : `grok 주간 사용률 ${used}% < 상한 ${cap}%`;
1584
+ if (rebalanced || !measured) (deps.write ?? ((line) => process.stderr.write(line)))(
1585
+ `[dev] child-llm ${child.provider} → ${selected.provider} · ${reason}\n`,
1586
+ );
1587
+ if (rebalanced || !measured) {
1588
+ const data = { requestedProvider: child.provider, selectedProvider: selected.provider,
1589
+ weeklyUsedPct: measured ? used : null, capPct: cap, reason, selection: child.source };
1590
+ // 실제 전환과 «측정 못 해 그대로 둠» 을 다른 이벤트로 — 관측에서 둘을 섞지 않는다.
1591
+ const event = rebalanced ? 'launch-provider-rebalanced' : 'launch-provider-weekly-unmeasured';
1592
+ debug.log('self-implement', event, { runId: runId ?? null, ...data });
1593
+ if (runId) (deps.record ?? appendRunLedgerEntry)({ timestamp: new Date().toISOString(), runId,
1594
+ event, data: { runId, ...data } });
1595
+ }
1596
+ return selected;
1597
+ }
1598
+
1522
1599
  export function readGrokQuotaForLaunch(
1523
1600
  deps: { readCached?: typeof readCachedGrokQuota; runUsage?: () => string | null } = {},
1524
1601
  ): 'usable' | 'exhausted' | 'unknown' {
1525
1602
  if (launchGrokQuotaReaderForTesting && !deps.readCached && !deps.runUsage) return launchGrokQuotaReaderForTesting();
1526
1603
  const cached = (deps.readCached ?? readCachedGrokQuota)();
1527
1604
  if (cached !== 'unknown') return cached;
1528
- const runUsage = deps.runUsage ?? (() => {
1529
- const cli = new URL('../../bin/elanous.mjs', import.meta.url).pathname;
1530
- const r = spawnSync(process.execPath, [cli, 'usage', '--json'], { encoding: 'utf8', timeout: 20_000 });
1531
- return r.status === 0 ? r.stdout : null;
1532
- });
1605
+ const runUsage = deps.runUsage ?? runLaunchUsageJsonOnce;
1533
1606
  let stdout: string | null = null;
1534
1607
  try { stdout = runUsage(); } catch { stdout = null; }
1535
1608
  return stdout ? grokQuotaFromUsageJson(stdout) : 'unknown';
@@ -1851,6 +1924,7 @@ export function buildDriveAliasDevSpec(
1851
1924
  command: string | undefined,
1852
1925
  opts: DevCliOpts,
1853
1926
  explicitOptionNames?: readonly string[],
1927
+ runId?: string,
1854
1928
  ): DevPipelineSpec {
1855
1929
  if (opts.elanous === true) {
1856
1930
  throw new DevPipelineError('drive: --elanous 는 지원하지 않음 — TUI target은 elanous dev --elanous 를 사용');
@@ -1861,7 +1935,7 @@ export function buildDriveAliasDevSpec(
1861
1935
  if (!opts.goal?.trim()) {
1862
1936
  throw new DevPipelineError('drive: --goal 필요');
1863
1937
  }
1864
- const spec = buildDevCliSpec({ text: command.trim() }, { kind: 'self' }, opts, explicitOptionNames, 'cli-drive');
1938
+ const spec = buildDevCliSpec({ text: command.trim() }, { kind: 'self' }, opts, explicitOptionNames, 'cli-drive', undefined, undefined, runId);
1865
1939
  return { ...spec, completion: 'worktree-only', autoReview: false };
1866
1940
  }
1867
1941
 
@@ -1872,6 +1946,9 @@ export function buildDevCliSpec(
1872
1946
  explicitOptionNames?: readonly string[],
1873
1947
  entrance: EntranceId = 'cli-dev-ask',
1874
1948
  readGrokQuota: typeof readCachedGrokQuota = readGrokQuotaForLaunch,
1949
+ readGrokWeekly: () => number | undefined = readGrokWeeklyUsedPctForLaunch,
1950
+ runId?: string,
1951
+ recordLaunch?: typeof appendRunLedgerEntry,
1875
1952
  ): DevPipelineSpec {
1876
1953
  if (opts.plan === true) {
1877
1954
  throw new DevPipelineError(`--plan 은 은퇴했고 명시적으로 거부됨 · 대응 문: ${DEV_PLAN_REPLACEMENT}`);
@@ -2016,8 +2093,13 @@ export function buildDevCliSpec(
2016
2093
  }
2017
2094
 
2018
2095
  if (path === 'self-mission') {
2019
- const builtChild = buildChildLlmSelection({ ...opts, readPreference: () => getUserConfig() });
2020
- const childLlm = applyLaunchOverloadFailover(builtChild, {
2096
+ const requestedChildLlm = buildChildLlmSelection({ ...opts, readPreference: () => getUserConfig() });
2097
+ // 주간 상한 재배분(grok → codex)을 먼저, 과부하 페일오버(codex → grok)를 그 뒤에 — 과부하는 «지금 실패 중»이라 더 강한 신호다.
2098
+ const capped = requestedChildLlm?.provider === 'grok'
2099
+ ? rebalanceGrokChildForLaunch(requestedChildLlm, getUserConfig().llm.grokWeeklyCapPct ?? 80, readGrokWeekly(), runId,
2100
+ recordLaunch ? { record: recordLaunch } : {})
2101
+ : requestedChildLlm;
2102
+ const childLlm = applyLaunchOverloadFailover(capped, {
2021
2103
  provider: opts.childLlmProvider,
2022
2104
  model: opts.childLlmModel,
2023
2105
  codexPinned: childLlmCodexPinned(opts.childLlmProvider),
@@ -43,7 +43,8 @@ import { addSelfDevRunParticipant, processBirthId, saveSelfDevRun } from './run-
43
43
  import { getUserConfig, resolveRoleLlm } from '../user-config.js';
44
44
  import { reviewReasoningEffort } from '../model-tier/review-effort.js';
45
45
  import { getProvider, inferProviderFromModel } from '../llm.js';
46
- import { buildReviewProviderAttempts, defaultReviewFallbackModel, runReviewWithFallback, reviewFallbackModelsFromConfig } from './review-provider-fallback.js';
46
+ import { buildReviewProviderAttempts, defaultReviewFallbackModel, runReviewWithFallback, reviewFallbackModelsFromConfig, runSubscriptionReviewer, type SubscriptionReviewerDeps } from './review-provider-fallback.js';
47
+ import { loadLlmPolicy } from '../policy/llm-policy.js';
47
48
  import type { DefaultSeamsOptions } from '../self-implement/seams.js';
48
49
  import { harnessTargetOptions, resolveHarnessTarget, revalidateHarnessTarget, type HarnessTargetResolution } from '../self-implement/harness-target-options.js';
49
50
  import { provisionRepository, type RepoProvisionResult } from '../self-implement/repo-provision.js';
@@ -1077,6 +1078,8 @@ export async function buildDefaultSelfImplementSeams(
1077
1078
  toolReviewerRoll?: () => number;
1078
1079
  /** Tool-reviewer factory (same prompt→text shape as llmReview). Tests stub this; default is ACP. */
1079
1080
  makeToolReviewLLM?: () => UnmannedToolReviewLLM | Promise<UnmannedToolReviewLLM>;
1081
+ subscriptionReviewerDeps?: SubscriptionReviewerDeps;
1082
+ makeSubscriptionReviewLLM?: (spawn: ReturnType<typeof import('./review-provider-fallback.js').subscriptionReviewerSpawn>) => (prompt: string) => Promise<string>;
1080
1083
  } = {},
1081
1084
  ): Promise<SelfImplementSeams> {
1082
1085
  const { defaultSeams } = await import('../self-implement/seams.js');
@@ -1143,6 +1146,21 @@ export async function buildDefaultSelfImplementSeams(
1143
1146
  reviewerCanSelfRead = false;
1144
1147
  }
1145
1148
  }
1149
+ let subscriptionReviewLLM: ((prompt: string) => Promise<string>) | undefined;
1150
+ if (reviewerIsDefaultApiCall) {
1151
+ const userConfig = getUserConfig();
1152
+ const policy = loadLlmPolicy();
1153
+ const spec = policy.policy.roles.reviewer ?? userConfig.roleLlm?.reviewer;
1154
+ if (spec) {
1155
+ const cap = policy.policy.caps.claude.harness;
1156
+ subscriptionReviewLLM = (prompt) => runSubscriptionReviewer(spec, cap, prompt, defaultApiReview, async (text, spawn) => {
1157
+ if (deps.makeSubscriptionReviewLLM) return deps.makeSubscriptionReviewLLM(spawn)(text);
1158
+ const { makeAcpReviewLLM } = await import('../agent-substrate/acp-reviewer.js');
1159
+ return makeAcpReviewLLM({ cwd: targetOptions?.repoRoot ?? childScope.elanousBinRoot, backend: 'claude', backendSpec: spawn.backendSpec, env: spawn.env })(text);
1160
+ }, process.env, deps.subscriptionReviewerDeps);
1161
+ reviewerCanSelfRead = undefined;
1162
+ }
1163
+ }
1146
1164
  const opensPr = plan.completion !== 'worktree-only';
1147
1165
  const target = plan.target === undefined ? undefined : revalidateHarnessTarget(plan.target);
1148
1166
  if (target?.status === 'outside-home') {
@@ -1159,6 +1177,7 @@ export async function buildDefaultSelfImplementSeams(
1159
1177
  ...(plan.self?.maxWaitSec !== undefined ? { implementMaxWaitSec: plan.self.maxWaitSec } : {}),
1160
1178
  ...(plan.self?.activityGraceSec !== undefined ? { implementActivityGraceSec: plan.self.activityGraceSec } : {}),
1161
1179
  llmReview,
1180
+ ...(subscriptionReviewLLM ? { subscriptionReviewLLM } : {}),
1162
1181
  ...(reviewerCanSelfRead !== undefined ? { reviewerCanSelfRead } : {}),
1163
1182
  ...(deps.reviewScopeDiff ? { reviewScopeDiff: deps.reviewScopeDiff } : {}),
1164
1183
  onProgress: ({ stage, message }) => {
@@ -62,6 +62,9 @@ export interface DraftSweepOptions {
62
62
  adapters: DraftSweepAdapters;
63
63
  apply?: boolean;
64
64
  now?: Date;
65
+ /** Per-tick close cap; resolve it with resolveDraftSweepCloseCap. Absent or invalid ⇒ DRAFT_SWEEP_CLOSE_CAP. */
66
+ closeCap?: number;
67
+ closeCapSource?: DraftSweepCloseCapSource;
65
68
  }
66
69
 
67
70
  export interface DraftSweepEntry {
@@ -83,8 +86,11 @@ export interface DraftSweepResult {
83
86
  counts: Record<string, number>;
84
87
  /** Drafts whose run could not be observed from here (kept, or closed by the idle rule). */
85
88
  unobserved?: number;
86
- /** Closes decided this tick (at most DRAFT_SWEEP_CLOSE_CAP). */
89
+ /** Closes decided this tick (at most `closeCap`). */
87
90
  closed?: number;
91
+ /** The per-tick close cap this sweep used, and where it came from. */
92
+ closeCap?: number;
93
+ closeCapSource?: DraftSweepCloseCapSource;
88
94
  /** Fresh running claims protected this tick. */
89
95
  claimed?: number;
90
96
  /** Running claims older than the idle window, including those kept by liveness. */
@@ -420,8 +426,36 @@ const approvalLabel = PR_LABELS.find((entry) => entry.axis === 'state' && entry.
420
426
  const keepLabel = PR_LABELS.find((entry) => entry.sweep.action === 'exclude')?.name;
421
427
  const releaseHoldLabel = PR_LABELS.find((entry) => entry.axis === 'addon' && entry.sweep.action === 'none')?.name;
422
428
  const PAGE_SIZE = 100;
423
- /** At most this many closes per tick, so a wrong rule cannot close everything at once. */
429
+ /** At most this many closes per tick, so a wrong rule cannot close everything at once. Default for `tools.selfImplement.draftSweepCloseCap`. */
424
430
  export const DRAFT_SWEEP_CLOSE_CAP = 10;
431
+ export const DRAFT_SWEEP_CLOSE_CAP_MIN = 1;
432
+ export const DRAFT_SWEEP_CLOSE_CAP_MAX = 100;
433
+ export type DraftSweepCloseCapSource = 'flag' | 'config' | 'default';
434
+
435
+ export interface DraftSweepCloseCapResolution {
436
+ readonly closeCap: number;
437
+ readonly closeCapSource: DraftSweepCloseCapSource;
438
+ /** One line per rejected input (flag or config); each rejection falls through to the next source. */
439
+ readonly warnings: readonly string[];
440
+ }
441
+
442
+ function validCloseCap(value: unknown): number | undefined {
443
+ const parsed = typeof value === 'string' && /^\s*\d+\s*$/.test(value) ? Number(value) : value;
444
+ return typeof parsed === 'number' && Number.isSafeInteger(parsed)
445
+ && parsed >= DRAFT_SWEEP_CLOSE_CAP_MIN && parsed <= DRAFT_SWEEP_CLOSE_CAP_MAX ? parsed : undefined;
446
+ }
447
+
448
+ /** Flag over config over default; a value outside 1–100 (or not an integer) is rejected with a warning. */
449
+ export function resolveDraftSweepCloseCap(input: { flag?: unknown; config?: unknown }): DraftSweepCloseCapResolution {
450
+ const warnings: string[] = [];
451
+ for (const [source, value, name] of [['flag', input.flag, '--close-cap'], ['config', input.config, 'tools.selfImplement.draftSweepCloseCap']] as const) {
452
+ if (value === undefined || value === null) continue;
453
+ const cap = validCloseCap(value);
454
+ if (cap !== undefined) return { closeCap: cap, closeCapSource: source, warnings };
455
+ warnings.push(`${name}=${JSON.stringify(value)} 무시 — ${DRAFT_SWEEP_CLOSE_CAP_MIN}~${DRAFT_SWEEP_CLOSE_CAP_MAX} 정수만 (기본 ${DRAFT_SWEEP_CLOSE_CAP})`);
456
+ }
457
+ return { closeCap: DRAFT_SWEEP_CLOSE_CAP, closeCapSource: 'default', warnings };
458
+ }
425
459
 
426
460
  /** Post-merge shortcut: only the existing superseded-by decision can authorize a close. */
427
461
  export async function supersedeDraftsOnMerge(input: {
@@ -571,8 +605,11 @@ export function draftSweepDaily(
571
605
  }
572
606
 
573
607
  /** A failed inventory or liveness lookup is never treated as proof that a draft can be closed. */
574
- export async function runDraftSweep({ repository, adapters, apply = false, now = new Date() }: DraftSweepOptions): Promise<DraftSweepResult> {
575
- const result: DraftSweepResult = { repository, apply, complete: false, entries: [], counts: {} };
608
+ export async function runDraftSweep({ repository, adapters, apply = false, now = new Date(), closeCap: requestedCap, closeCapSource: requestedSource }: DraftSweepOptions): Promise<DraftSweepResult> {
609
+ const validCap = validCloseCap(requestedCap);
610
+ const closeCap = validCap ?? DRAFT_SWEEP_CLOSE_CAP;
611
+ const closeCapSource: DraftSweepCloseCapSource = validCap === undefined ? 'default' : requestedSource ?? 'flag';
612
+ const result: DraftSweepResult = { repository, apply, complete: false, entries: [], counts: {}, closeCap, closeCapSource };
576
613
  const record = (entry: DraftSweepEntry): void => {
577
614
  result.entries.push(entry);
578
615
  result.counts[entry.reason] = (result.counts[entry.reason] ?? 0) + 1;
@@ -591,7 +628,7 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
591
628
  const at = Date.now();
592
629
  try {
593
630
  debug.log('self-dev.draft-sweep', 'timing', { phase, ms: at - phaseStarted, count, ...(outcome ? { outcome } : {}) });
594
- if (outcome) debug.log('self-dev.draft-sweep', 'timing', { phase: 'total', ms: at - started, count, outcome });
631
+ if (outcome) debug.log('self-dev.draft-sweep', 'timing', { phase: 'total', ms: at - started, count, outcome, closeCap, closeCapSource });
595
632
  } catch { /* fail-soft */ }
596
633
  phaseStarted = at;
597
634
  };
@@ -706,7 +743,7 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
706
743
  record({ number: draft.number, action: 'keep', reason: 'claim-expired-but-live', applied: false });
707
744
  continue;
708
745
  }
709
- const action = closes >= DRAFT_SWEEP_CLOSE_CAP ? 'keep' : 'close';
746
+ const action = closes >= closeCap ? 'keep' : 'close';
710
747
  if (action === 'close') closes += 1;
711
748
  const entry: DraftSweepEntry = { number: draft.number, action,
712
749
  reason: action === 'close' ? runStatus === 'self-implement.result final'
@@ -737,7 +774,7 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
737
774
  finalRunResult, ageHours: runStatus ? ageHours : idleHours });
738
775
  if (!runStatus && (decision.reason === 'unobserved' || decision.reason === 'stale-unobserved')) unobserved += 1;
739
776
  if (!contradiction && decision.action === 'close') {
740
- if (closes >= DRAFT_SWEEP_CLOSE_CAP) decision = { action: 'keep', reason: 'close-cap' };
777
+ if (closes >= closeCap) decision = { action: 'keep', reason: 'close-cap' };
741
778
  else closes += 1;
742
779
  }
743
780
  const action = contradiction ? 'report' : decision.action;
@@ -772,7 +809,7 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
772
809
  }
773
810
  }
774
811
  timing('decide', drafts.length);
775
- try { debug.log('self-dev.draft-sweep', 'timing', { phase: 'total', ms: Date.now() - started, count: drafts.length }); } catch { /* fail-soft */ }
812
+ try { debug.log('self-dev.draft-sweep', 'timing', { phase: 'total', ms: Date.now() - started, count: drafts.length, closed: closes, closeCap, closeCapSource }); } catch { /* fail-soft */ }
776
813
  result.unobserved = unobserved;
777
814
  result.closed = closes;
778
815
  result.claimed = claimed;
@@ -298,6 +298,8 @@ export interface SelfDevJobResult {
298
298
  providerErrors?: { count: number; provider: string; category: 'quota' | 'credential' | 'request' | 'other' };
299
299
  /** Stable child run identity used to join its run-ledger proposal. */
300
300
  runId?: string;
301
+ /** Self review verdict bound to the PR head observed by the child; absent if either observation is missing. */
302
+ selfReview?: { verdict: 'pass' | 'fail'; head: string };
301
303
  /** Whether the child review actually ran; omitted preserves legacy producers. */
302
304
  reviewed?: boolean;
303
305
  /** Why the child did not run review; omitted preserves legacy review-unobserved handling. */
@@ -1499,6 +1501,7 @@ export function orchestrateSelfDev(opts: OrchestrateSelfDevOptions): Promise<Sel
1499
1501
  ...(disp?.prNumber !== undefined ? { prNumber: disp.prNumber } : {}),
1500
1502
  ...(disp?.merged !== undefined ? { merged: disp.merged } : {}),
1501
1503
  ...(disp?.checkedHeadCommit ? { checkedHeadCommit: disp.checkedHeadCommit } : {}),
1504
+ ...(disp?.selfReview ? { selfReview: disp.selfReview } : {}),
1502
1505
  // ⭐ `A1` — 판정 3종을 «끝까지» 옮긴다. 여기서 빠지면 트리아지가 눈을 잃는다.
1503
1506
  ...(disp?.mergeReason ? { mergeReason: disp.mergeReason } : {}),
1504
1507
  ...(!cancelling && disp?.stopReason ? { stopReason: disp.stopReason } : {}),
@@ -19,6 +19,89 @@
19
19
  // 쿼터 축은 과부하에 «반응하지 않는다».
20
20
  import { resolveModelAlias } from '../intelligence-map/model-alias.js';
21
21
  import type { LLMProvider } from '../llm.js';
22
+ import { spawnSync } from 'node:child_process';
23
+ import { claudeBackend, resolveBackendSpawn } from '../agent-mission/driver.js';
24
+ import { checkClaudeSubscription } from '../agent-mission/claude-subscription.js';
25
+ import { getAcpBackend, type AcpBackendSpec } from '../acp/backend-registry.js';
26
+ import type { SubscriptionReviewerSpec } from '../user-config.js';
27
+
28
+ export interface SubscriptionReviewerDeps {
29
+ /** Returns measured Claude subscription usage; unknown usage fails closed. */
30
+ usedPercent?: () => number | undefined;
31
+ checkSubscription?: (env: Record<string, string>) => boolean;
32
+ runStatus?: (command: string, args: string[], env: Record<string, string>) => { status: number | null; stdout?: string | Buffer | null };
33
+ }
34
+
35
+ /** The runner and the subscription probe share the PTY backend's billing-env scrub. */
36
+ export function subscriptionReviewerSpawn(spec: SubscriptionReviewerSpec, env: NodeJS.ProcessEnv = process.env): {
37
+ command: string; args: string[]; env: Record<string, string>; backendSpec: AcpBackendSpec;
38
+ } {
39
+ const executor = spec.executor ?? 'cct';
40
+ const { env: cleanEnv } = resolveBackendSpawn(claudeBackend, env);
41
+ const command = executor === 'cct' ? 'teamclaude' : 'claude';
42
+ const args = executor === 'cct'
43
+ ? ['run', '--auto-fallback', '--', '--dangerously-skip-permissions']
44
+ : ['--dangerously-skip-permissions'];
45
+ return { command, args, env: cleanEnv,
46
+ backendSpec: { ...getAcpBackend('claude'), command, args } };
47
+ }
48
+
49
+ /** Guard the ACP review before spawning. An unmeasured quota never authorizes subscription use. */
50
+ export function subscriptionReviewerAvailability(
51
+ spec: SubscriptionReviewerSpec,
52
+ cap: number,
53
+ env: NodeJS.ProcessEnv = process.env,
54
+ deps: SubscriptionReviewerDeps = {},
55
+ ): { spawn?: ReturnType<typeof subscriptionReviewerSpawn>; reason?: string } {
56
+ const spawn = subscriptionReviewerSpawn(spec, env);
57
+ const executor = spec.executor ?? 'cct';
58
+ const runStatus = deps.runStatus ?? ((command: string, args: string[], cleanEnv: Record<string, string>) =>
59
+ spawnSync(command, args, { env: { ...cleanEnv, NODE_ENV: process.env.NODE_ENV }, encoding: 'utf8', timeout: 5_000 }));
60
+ const usedPercent = deps.usedPercent ?? (() => {
61
+ const result = runStatus('teamclaude', ['status'], spawn.env);
62
+ if (result.status !== 0) return undefined;
63
+ // Only a named usage percentage is accepted; a random percentage in status is not quota evidence.
64
+ const match = String(result.stdout).match(/(?:usage|used)\s*[:=]?\s*(\d+(?:\.\d+)?)\s*%/i);
65
+ return match ? Number(match[1]) : undefined;
66
+ });
67
+ try {
68
+ const subscribed = deps.checkSubscription ?? ((cleanEnv: Record<string, string>) => {
69
+ if (executor === 'cc') return checkClaudeSubscription({ env: cleanEnv }).ok;
70
+ const status = runStatus('teamclaude', ['status'], cleanEnv);
71
+ return status.status === 0 && /(?:subscription|claude\.ai|claude\s+(?:max|pro))/i.test(String(status.stdout))
72
+ && !/(?:logged\s*out|not\s+logged\s+in|api[_ -]?key|not\s+authenticated)/i.test(String(status.stdout));
73
+ });
74
+ if (!subscribed(spawn.env)) return { reason: `${executor} subscription check failed` };
75
+ const used = usedPercent();
76
+ if (!Number.isFinite(cap) || cap < 0 || cap > 100 || used === undefined
77
+ || !Number.isFinite(used) || used < 0 || used >= cap) return { reason: `Claude subscription usage unavailable or at harness cap (${cap}%)` };
78
+ return { spawn };
79
+ } catch {
80
+ return { reason: `${executor} subscription check failed` };
81
+ }
82
+ }
83
+
84
+ /** Keep a single notice attached to the actual default review result. */
85
+ export async function runSubscriptionReviewer(
86
+ spec: SubscriptionReviewerSpec | undefined,
87
+ cap: number,
88
+ prompt: string,
89
+ defaultReview: (prompt: string) => Promise<string>,
90
+ acpReview: (prompt: string, spawn: ReturnType<typeof subscriptionReviewerSpawn>) => Promise<string>,
91
+ env: NodeJS.ProcessEnv = process.env,
92
+ deps: SubscriptionReviewerDeps = {},
93
+ ): Promise<string> {
94
+ if (!spec) return defaultReview(prompt);
95
+ const availability = subscriptionReviewerAvailability(spec, cap, env, deps);
96
+ if (availability.spawn) {
97
+ try {
98
+ const text = await acpReview(prompt, availability.spawn);
99
+ if (text.trim()) return text;
100
+ } catch { /* ACP failure is not a completed review. */ }
101
+ }
102
+ const reason = availability.reason ?? 'ACP review failed';
103
+ return `${await defaultReview(prompt)}\n[reviewer fallback: ${reason}; default reviewer used]`;
104
+ }
22
105
 
23
106
  export type ReviewFailureReason =
24
107
  | 'overloaded'
@@ -623,7 +623,11 @@ export async function superviseRun(opts: SuperviseRunOptions): Promise<Superviso
623
623
  if (decision.stopReason && opts.taskAgentShadow !== false) {
624
624
  // TASK-AGENT-SHADOW — 판단부를 그림자로 부른다(실행 0). 어떤 오류도 런의 결말을 바꾸지 않는다.
625
625
  try {
626
- await (opts.taskAgentShadow ?? recordTaskAgentShadowMove)({ runId, stopReason: decision.stopReason, results });
626
+ const pr = results.find((result) => typeof result.prNumber === 'number' && result.prNumber > 0)?.prNumber;
627
+ const reviewed = results.filter((result) => result.prNumber === pr && result.selfReview && result.merged !== true);
628
+ const selfReview = pr && reviewed.length === 1 ? reviewed[0]!.selfReview : undefined;
629
+ await (opts.taskAgentShadow ?? recordTaskAgentShadowMove)({ runId, stopReason: decision.stopReason, results,
630
+ ...(selfReview ? { selfReview } : {}), cycleId: `${runId ?? 'unknown'}:${decision.round}` });
627
631
  } catch (error) {
628
632
  try { debug.log('task-agent', 'shadow-move-failed', { runId, stopReason: decision.stopReason, error: String(error) }); } catch { /* fail-open */ }
629
633
  }
@@ -5,16 +5,17 @@ import { runMockModuleRestoreGate } from '../../scripts/ci-mock-module-restore-g
5
5
  import { runModelHardcodeGate } from '../../scripts/ci-model-hardcode-gate.js';
6
6
  import { runDaemonPortGate } from '../../scripts/ci-daemon-port-gate.js';
7
7
  import { runPublicExportLeakGate } from '../../scripts/ci-public-export-leak-gate.js';
8
+ import { runPublicExportImportGate } from '../../scripts/ci-public-export-import-gate.js';
8
9
 
9
- type GateName = 'isolation-gate' | 'mock-module-restore-gate' | 'model-hardcode-gate' | 'daemon-port-gate' | 'public-export-leak';
10
+ type GateName = 'isolation-gate' | 'mock-module-restore-gate' | 'model-hardcode-gate' | 'daemon-port-gate' | 'public-export-leak' | 'public-export-import';
10
11
  type GateOutput = { args: string[]; cwd: string; log: (line: string) => void; error: (line: string) => void };
11
- type GateRunner = (out: GateOutput) => number;
12
+ type GateRunner = (out: GateOutput) => number | Promise<number>;
12
13
 
13
- export function runHarnessPolicyGates(input: {
14
+ export async function runHarnessPolicyGates(input: {
14
15
  cwd: string;
15
16
  changedFiles: readonly string[];
16
17
  gates?: Partial<Record<GateName, GateRunner>>;
17
- }): { passed: boolean; failures: Array<{ gate: GateName; lines: string[] }>; skipped?: string } {
18
+ }): Promise<{ passed: boolean; failures: Array<{ gate: GateName; lines: string[] }>; skipped?: string }> {
18
19
  try {
19
20
  if (!statSync(join(input.cwd, 'scripts')).isDirectory()) {
20
21
  return { passed: true, failures: [], skipped: 'no-scripts' };
@@ -30,12 +31,13 @@ export function runHarnessPolicyGates(input: {
30
31
  ['daemon-port-gate', input.gates?.['daemon-port-gate'] ?? ((out) => runDaemonPortGate({ log: out.log, error: out.error, cwd: out.cwd, args: [] }))],
31
32
  // LEAK1 — the same pre-export check as pr land; an unmeasured result is a nonzero failure, not a pass.
32
33
  ['public-export-leak', input.gates?.['public-export-leak'] ?? runPublicExportLeakGate],
34
+ ['public-export-import', input.gates?.['public-export-import'] ?? runPublicExportImportGate],
33
35
  ];
34
36
  const failures: Array<{ gate: GateName; lines: string[] }> = [];
35
37
  for (const [gate, run] of runners) {
36
38
  const lines: string[] = [];
37
39
  try {
38
- const status = run({ cwd: input.cwd, args: ['--changed-files', ...input.changedFiles], log: (line) => lines.push(line), error: (line) => lines.push(line) });
40
+ const status = await run({ cwd: input.cwd, args: ['--changed-files', ...input.changedFiles], log: (line) => lines.push(line), error: (line) => lines.push(line) });
39
41
  if (status !== 0) failures.push({ gate, lines: [...lines, `✗ ${gate}: violation detected`] });
40
42
  } catch (error) {
41
43
  failures.push({ gate, lines: [...lines, `⚠ ${gate}: 게이트가 «못 쟀다» — ${error instanceof Error ? error.message : String(error)}`] });
@@ -8,7 +8,7 @@ import { publicExposureFiles, runExposeGate } from './expose-gate.js';
8
8
  import { admitLandingMerge } from './frozen-merges.js';
9
9
  import { releaseGitDiffPaths, releasePathHold, releasePathHoldShouldPost, releasePathHoldComment, releasePathHoldCommentsArgs, RELEASE_PATH_LABEL } from '../self-dev/release-path-guard.js';
10
10
  import { detectTestInterference, parseFailureCount, runBunTest } from '../../scripts/detect-test-interference.js';
11
- import { runTestInterferenceGate } from '../../scripts/ci-test-interference-gate.js';
11
+ import { MAX_INSPECTED_TEST_FILES, runTestInterferenceGate } from '../../scripts/ci-test-interference-gate.js';
12
12
 
13
13
  /**
14
14
  * The PWA's static build type-checks every `src/**` file it imports, under Next's stricter
@@ -22,9 +22,10 @@ export function needsPwaBuild(files: readonly string[]): boolean {
22
22
 
23
23
  export interface HostRegateInput { prNumber: number; headCommit: string; repoRoot: string; goalFile?: string; verifyOnly?: boolean; /** Called by a resume sweep that already holds this PR's claim. */ resumed?: true }
24
24
  export interface HostRegateResult { passed: boolean; failures: Array<{ step: string; detail: string }>; os: string; status?: 'passed' | 'failed' | 'unmeasured' | 'frozen'; /** verifyOnly: 실제로 얹어 잰 base 끝 */ baseCommit?: string }
25
+ export type InterferenceVerdict = { passed: boolean; detail?: string; unmeasured?: boolean; /** neighbour tests left out so the selection fits the inspection cap */ neighborsTrimmed?: number };
25
26
  export type HostRegateDeps = {
26
27
  command?: (bin: string, args: readonly string[], cwd: string, env?: NodeJS.ProcessEnv) => { status: number | null; stdout: string; stderr: string };
27
- interference?: (files: readonly string[], cwd: string) => Promise<{ passed: boolean; detail?: string; unmeasured?: boolean }>;
28
+ interference?: (files: readonly string[], cwd: string) => Promise<InterferenceVerdict>;
28
29
  makeTemp?: () => string;
29
30
  removeTemp?: (path: string) => void;
30
31
  acquire?: (repoRoot: string) => Promise<() => void>;
@@ -46,17 +47,28 @@ const defaultCommand: NonNullable<HostRegateDeps['command']> = (bin, args, cwd,
46
47
  };
47
48
 
48
49
  /** The informational pr-land gate always returns zero; enforce its measured report and the combined test result instead. */
49
- async function defaultInterference(files: readonly string[], cwd: string, opts: { neighbors?: boolean } = {}): Promise<{ passed: boolean; detail?: string; unmeasured?: boolean }> {
50
- const tests = files.filter((file) => /\.test\.tsx?$/.test(file));
51
- const neighbors = new Set<string>(tests);
52
- // verifyOnly(승인 탭)는 «이 PR 이 바꾼 시험»만 잰다 — 이웃 시험을 넣으면 간섭 관문의 상한(8)에 잘려 영영 «측정 불가»가 되고,
53
- // 이웃의 기존 실패가 이 PR 을 막는다(2026-09-28 #21239 실측: scripts/lib 이웃 9 → 상한 8 · nl-routing-measurement 기존 1 fail).
50
+ export async function defaultInterference(files: readonly string[], cwd: string, opts: { neighbors?: boolean; detect?: typeof detectTestInterference } = {}): Promise<InterferenceVerdict> {
51
+ const tests = [...new Set(files.filter((file) => /\.test\.tsx?$/.test(file)))].sort();
52
+ const changed = new Set(tests);
53
+ const neighbors = new Set<string>();
54
+ // verifyOnly(승인 탭)는 «이 PR 이 바꾼 시험»만 잰다 — 이웃의 기존 실패가 이 PR 을 막지 않게
55
+ // (2026-09-28 #21239 실측: scripts/lib 이웃 9 · nl-routing-measurement 기존 1 fail).
54
56
  if (opts.neighbors !== false) for (const file of tests) {
55
57
  for (const sibling of readdirSync(join(cwd, dirname(file)))) {
56
- if (/\.test\.tsx?$/.test(sibling)) neighbors.add(join(dirname(file), sibling));
58
+ const path = join(dirname(file), sibling);
59
+ if (/\.test\.tsx?$/.test(sibling) && !changed.has(path)) neighbors.add(path);
57
60
  }
58
61
  }
59
- const selected = [...neighbors].sort();
62
+ // 이웃이 간섭 관문의 상한(MAX_INSPECTED_TEST_FILES)을 넘기면 그 관문이 잘라 «영영 측정 불가»가 된다
63
+ // (2026-10-08 #25180·#25187 실측: src/self-implement 시험 175 · src/seat-loop 11). 바뀐 시험은 전부 두고 남는 칸만 이웃으로 채운다.
64
+ // 바뀐 시험만으로 상한을 넘으면 정직하게 «측정 불가»로 남는다.
65
+ const sortedNeighbors = [...neighbors].sort();
66
+ const room = Math.max(0, MAX_INSPECTED_TEST_FILES - tests.length);
67
+ const kept = tests.length > MAX_INSPECTED_TEST_FILES ? sortedNeighbors : sortedNeighbors.slice(0, room);
68
+ const neighborsTrimmed = sortedNeighbors.length - kept.length;
69
+ const trimmed = neighborsTrimmed > 0 ? { neighborsTrimmed } : {};
70
+ const trimNote = neighborsTrimmed > 0 ? ` · neighbours trimmed ${neighborsTrimmed} to fit inspection cap ${MAX_INSPECTED_TEST_FILES}` : '';
71
+ const selected = [...tests, ...kept].sort();
60
72
  if (!selected.length) return { passed: true };
61
73
  // 간섭 관문은 시험 파일이 둘 이상일 때만 잰다 — 하나면 간섭이라는 말이 없으니 그 파일을 한 번 돌려 실패 수로 본다.
62
74
  if (selected.length === 1) {
@@ -69,13 +81,13 @@ async function defaultInterference(files: readonly string[], cwd: string, opts:
69
81
  let report: Awaited<ReturnType<typeof detectTestInterference>> | undefined;
70
82
  await runTestInterferenceGate({
71
83
  args: ['--changed-files', ...selected], log: () => {},
72
- detect: async (paths) => (report = await detectTestInterference(paths, (batch) => runBunTest(batch, undefined, cwd))),
84
+ detect: async (paths) => (report = await (opts.detect ?? detectTestInterference)(paths, (batch) => runBunTest(batch, undefined, cwd))),
73
85
  });
74
- if (!report || report.status === 'unmeasurable' || selected.length > report.order.length) return { passed: false, unmeasured: true, detail: 'test interference measurement unavailable or truncated' };
86
+ if (!report || report.status === 'unmeasurable' || selected.length > report.order.length) return { passed: false, unmeasured: true, ...trimmed, detail: `test interference measurement unavailable or truncated (selected ${selected.length}, changed tests ${tests.length}, cap ${MAX_INSPECTED_TEST_FILES})${trimNote}` };
75
87
  if (report.isolatedFailures !== 0 || report.combinedFailures !== 0 || report.status === 'interference') {
76
- return { passed: false, detail: `isolated=${report.isolatedFailures}, combined=${report.combinedFailures}, interference=${report.status}` };
88
+ return { passed: false, ...trimmed, detail: `isolated=${report.isolatedFailures}, combined=${report.combinedFailures}, interference=${report.status}${trimNote}` };
77
89
  }
78
- return { passed: true };
90
+ return { passed: true, ...trimmed, ...(trimNote ? { detail: trimNote.slice(3) } : {}) };
79
91
  }
80
92
 
81
93
  const SLOT_WAIT_MS = 30 * 60_000;
@@ -133,9 +145,10 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
133
145
  let endLanding: ((merged?: boolean) => void) | undefined;
134
146
  let landed = false;
135
147
  let verifiedBase: string | undefined;
148
+ let interferenceNote: Record<string, unknown> = {};
136
149
  const result = (event: 'passed' | 'failed' | 'unmeasured', step?: string, detail?: string): HostRegateResult => {
137
150
  if (step) failures.push({ step, detail: detail ?? 'unknown' });
138
- log(event, { pr: input.prNumber, files, os: process.platform, ...(step ? { failedStep: step } : {}) });
151
+ log(event, { pr: input.prNumber, files, os: process.platform, ...interferenceNote, ...(step ? { failedStep: step, detail: (detail ?? 'unknown').slice(0, 300) } : {}) });
139
152
  return { passed: event === 'passed', failures, os: process.platform, ...(input.verifyOnly ? { status: event, ...(verifiedBase ? { baseCommit: verifiedBase } : {}) } : {}) };
140
153
  };
141
154
  const run = (bin: string, args: readonly string[], cwd: string, env?: NodeJS.ProcessEnv): string => {
@@ -211,6 +224,7 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
211
224
  } catch (e) { return result('unmeasured', 'worktree', String(e)); }
212
225
  try {
213
226
  const interference = await (deps.interference ?? ((f: readonly string[], c: string) => defaultInterference(f, c, { neighbors: !input.verifyOnly })))(files, worktree);
227
+ if (interference.neighborsTrimmed) interferenceNote = { neighborsTrimmed: interference.neighborsTrimmed, interferenceDetail: (interference.detail ?? '').slice(0, 300) };
214
228
  if (!interference.passed) return result(interference.unmeasured ? 'unmeasured' : 'failed', 'test-interference', interference.detail);
215
229
  } catch (e) { return result('unmeasured', 'test-interference', String(e)); }
216
230
  // Landing can also be initiated by the host after the child gate; judge the checked PR here too.