elanous 0.2.25 → 0.2.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/chunks/{1454-37fd16f479f5554a.js → 1454-29d1d773d5996a49.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{599-a914cc0468f56143.js → 599-56f2c0a5cc4a7295.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-24453f6a302816b4.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/app/board/page-6c131d30bb1cf871.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-b12149c51f7d04b1.js → page-fa46d4588e8442c2.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/bots/page-dc0772b11861af51.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-305cd294e754906e.js → page-256ffd365d32e100.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-dea4b795741126fd.js → page-e0de78ae7b99f01c.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/exec/{page-45889e7070d11126.js → page-4c741eb760f2f77c.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/field/{page-16a2e893f83ae4d0.js → page-e9ba98f5f1440a21.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/inside/page-dafc901bb1b72b5f.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/intake/{page-56888bc744e08dac.js → page-2f6f303fa114ec24.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/layout-1634081c5272006c.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/live/{page-952c6ceec6d9de38.js → page-ced0bcfc6542b3fc.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live-run/{page-6d0202af568f4c63.js → page-bb1f05cd51c38944.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/loops/page-646b50f63b6fe732.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-4fb6fc34e20c07b7.js → page-f736324afa2117c1.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-e9502921f4f01b1f.js → page-6f131b51193a36f9.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/{not-found-b6169ea2f09cf287.js → not-found-435f5e2faf02bc01.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/{page-6a14333d1b1b3967.js → page-497e0eedf20e7890.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-8c91a9a66c9ec90a.js → page-a8d28fdf995e16ea.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8966452228753c5e.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/{page-3d3fc73dd187ed52.js → page-66a67eb19ebd976b.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-9c04dac0831c2bbb.js → page-c71267e7681da982.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a21d1e1797172692.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-04de9ca6d52a3893.js → page-cb249e81ed339318.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/settings/{page-62527677bbc2fdc4.js → page-781cfc5114e097da.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-9a2e09c4a311764c.js → page-478157214db6a023.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/setup/{page-bfeb35d69e8b24bb.js → page-86141f12cf090b7f.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/share/{page-89cb33bced824e4a.js → page-7256c73bb1c36751.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/today/{page-6445ac4cbb8dd869.js → page-979d318a534e92da.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/page-21050928a697ed7a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-20162174b77f96cf.js → page-601fb77340e63c9a.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-05a4b2c9b91034aa.js → page-11bf9e5725a72c22.js} +1 -1
  53. package/apps/pwa/out/approvals/index.html +1 -1
  54. package/apps/pwa/out/approvals/index.txt +13 -13
  55. package/apps/pwa/out/autopilot/index.html +1 -1
  56. package/apps/pwa/out/autopilot/index.txt +13 -13
  57. package/apps/pwa/out/board/index.html +1 -1
  58. package/apps/pwa/out/board/index.txt +13 -13
  59. package/apps/pwa/out/botlab/index.html +1 -1
  60. package/apps/pwa/out/botlab/index.txt +13 -13
  61. package/apps/pwa/out/bots/index.html +1 -1
  62. package/apps/pwa/out/bots/index.txt +13 -13
  63. package/apps/pwa/out/ceo/index.html +1 -1
  64. package/apps/pwa/out/ceo/index.txt +13 -13
  65. package/apps/pwa/out/chat/index.html +1 -1
  66. package/apps/pwa/out/chat/index.txt +13 -13
  67. package/apps/pwa/out/consult/index.html +1 -1
  68. package/apps/pwa/out/consult/index.txt +13 -13
  69. package/apps/pwa/out/control/index.html +1 -1
  70. package/apps/pwa/out/control/index.txt +13 -13
  71. package/apps/pwa/out/decisions/index.html +1 -1
  72. package/apps/pwa/out/decisions/index.txt +13 -13
  73. package/apps/pwa/out/design-check/index.html +1 -1
  74. package/apps/pwa/out/design-check/index.txt +13 -13
  75. package/apps/pwa/out/editor/index.html +1 -1
  76. package/apps/pwa/out/editor/index.txt +13 -13
  77. package/apps/pwa/out/exec/index.html +1 -1
  78. package/apps/pwa/out/exec/index.txt +13 -13
  79. package/apps/pwa/out/field/index.html +1 -1
  80. package/apps/pwa/out/field/index.txt +13 -13
  81. package/apps/pwa/out/healthz +1 -1
  82. package/apps/pwa/out/index.html +1 -1
  83. package/apps/pwa/out/index.txt +13 -13
  84. package/apps/pwa/out/inside/index.html +1 -1
  85. package/apps/pwa/out/inside/index.txt +13 -13
  86. package/apps/pwa/out/intake/index.html +1 -1
  87. package/apps/pwa/out/intake/index.txt +13 -13
  88. package/apps/pwa/out/live/index.html +1 -1
  89. package/apps/pwa/out/live/index.txt +13 -13
  90. package/apps/pwa/out/live-run/index.html +1 -1
  91. package/apps/pwa/out/live-run/index.txt +13 -13
  92. package/apps/pwa/out/loops/index.html +1 -1
  93. package/apps/pwa/out/loops/index.txt +13 -13
  94. package/apps/pwa/out/market/index.html +1 -1
  95. package/apps/pwa/out/market/index.txt +13 -13
  96. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  97. package/apps/pwa/out/missions/placeholder/index.txt +13 -13
  98. package/apps/pwa/out/morning/index.html +1 -1
  99. package/apps/pwa/out/morning/index.txt +13 -13
  100. package/apps/pwa/out/observatory/index.html +1 -1
  101. package/apps/pwa/out/observatory/index.txt +13 -13
  102. package/apps/pwa/out/ops/checklist/index.html +1 -1
  103. package/apps/pwa/out/ops/checklist/index.txt +13 -13
  104. package/apps/pwa/out/ops/release/index.html +1 -1
  105. package/apps/pwa/out/ops/release/index.txt +13 -13
  106. package/apps/pwa/out/ops/seats/index.html +1 -1
  107. package/apps/pwa/out/ops/seats/index.txt +13 -13
  108. package/apps/pwa/out/outputs/index.html +1 -1
  109. package/apps/pwa/out/outputs/index.txt +13 -13
  110. package/apps/pwa/out/reflection/index.html +1 -1
  111. package/apps/pwa/out/reflection/index.txt +14 -14
  112. package/apps/pwa/out/scheduler/index.html +1 -1
  113. package/apps/pwa/out/scheduler/index.txt +13 -13
  114. package/apps/pwa/out/sessions/index.html +1 -1
  115. package/apps/pwa/out/sessions/index.txt +13 -13
  116. package/apps/pwa/out/settings/devices/index.html +1 -1
  117. package/apps/pwa/out/settings/devices/index.txt +13 -13
  118. package/apps/pwa/out/settings/index.html +1 -1
  119. package/apps/pwa/out/settings/index.txt +13 -13
  120. package/apps/pwa/out/setup/done/index.html +1 -1
  121. package/apps/pwa/out/setup/done/index.txt +13 -13
  122. package/apps/pwa/out/setup/index.html +1 -1
  123. package/apps/pwa/out/setup/index.txt +13 -13
  124. package/apps/pwa/out/share/index.html +1 -1
  125. package/apps/pwa/out/share/index.txt +13 -13
  126. package/apps/pwa/out/showroom/index.html +1 -1
  127. package/apps/pwa/out/showroom/index.txt +13 -13
  128. package/apps/pwa/out/tasks/index.html +1 -1
  129. package/apps/pwa/out/tasks/index.txt +13 -13
  130. package/apps/pwa/out/term/index.html +1 -1
  131. package/apps/pwa/out/term/index.txt +13 -13
  132. package/apps/pwa/out/today/index.html +1 -1
  133. package/apps/pwa/out/today/index.txt +13 -13
  134. package/apps/pwa/out/trace/index.html +1 -1
  135. package/apps/pwa/out/trace/index.txt +13 -13
  136. package/apps/pwa/out/vault/index.html +1 -1
  137. package/apps/pwa/out/vault/index.txt +13 -13
  138. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  139. package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
  140. package/apps/pwa/out/workflows/index.html +1 -1
  141. package/apps/pwa/out/workflows/index.txt +13 -13
  142. package/apps/pwa/out/workspace/index.html +1 -1
  143. package/apps/pwa/out/workspace/index.txt +13 -13
  144. package/apps/pwa/out/worktrees/index.html +1 -1
  145. package/apps/pwa/out/worktrees/index.txt +13 -13
  146. package/graphs/implement-loop.yaml +38 -1
  147. package/graphs/steward/recipes.yaml +6 -0
  148. package/graphs/steward/release-day.yaml +23 -0
  149. package/graphs/steward/steward.yaml +9 -1
  150. package/graphs/work/code-pr.yaml +23 -0
  151. package/package.json +1 -1
  152. package/scripts/browser-observe-cycle.ts +76 -2
  153. package/scripts/ci-typecheck-changed.ts +119 -7
  154. package/scripts/coord-msg-bridge.ts +209 -0
  155. package/scripts/coord-post.sh +40 -7
  156. package/scripts/coord-requests-bridge.ts +10 -5
  157. package/scripts/detect-test-interference.ts +31 -1
  158. package/scripts/expose-rubric-suggest.ts +1 -1
  159. package/scripts/measure-tsc-affected-roots.ts +10 -1
  160. package/scripts/release-loop/docs-node.ts +51 -22
  161. package/scripts/release-loop/export-check-node.ts +8 -5
  162. package/scripts/release-loop/gate-node.ts +37 -4
  163. package/scripts/release-loop/node-verdict.ts +10 -3
  164. package/scripts/release-loop/publish-node.ts +3 -0
  165. package/scripts/release-loop/tui-node-checks.ts +5 -2
  166. package/scripts/skills/public-check.ts +5 -2
  167. package/scripts/test-deterministic.ts +520 -37
  168. package/scripts/ux-sim.ts +7 -1
  169. package/scripts/verify-kpack6-live.ts +230 -0
  170. package/src/acp/codex-plugins.ts +17 -0
  171. package/src/agent/task-notification.ts +32 -2
  172. package/src/agent-mission/in-agent-plugin.ts +8 -3
  173. package/src/agent-substrate/review-observation.ts +9 -2
  174. package/src/agent-substrate/self-review-cli.ts +62 -16
  175. package/src/boot/daemon-tools/self-implement.ts +72 -4
  176. package/src/briefing/brief-items.ts +151 -35
  177. package/src/budget/llm-cost.ts +30 -3
  178. package/src/budget/usage-outcomes.ts +32 -0
  179. package/src/claims/claims-ledger.ts +23 -0
  180. package/src/claims/claims-render.ts +22 -2
  181. package/src/cli/brief-cli.ts +131 -10
  182. package/src/cli/claims-cli.ts +24 -4
  183. package/src/cli/control-cli.ts +29 -0
  184. package/src/cli/doctor-cli.ts +90 -8
  185. package/src/cli/msg-cli.ts +177 -11
  186. package/src/cli/plugin-cli.ts +11 -3
  187. package/src/cli/pr-cli.ts +133 -18
  188. package/src/cli/pr-land-overlap-evidence.ts +18 -10
  189. package/src/cli/pr-land-refusal.ts +20 -0
  190. package/src/cli/release-cli.ts +34 -5
  191. package/src/cli/rfc-cli.ts +24 -0
  192. package/src/cli/seat-cli.ts +121 -0
  193. package/src/cli/self-utterances-cli.ts +140 -0
  194. package/src/cli/tailscale-serve.ts +5 -8
  195. package/src/cli/tasks-cli.ts +528 -31
  196. package/src/cli/usage-cli.ts +9 -4
  197. package/src/context-card/cell.ts +40 -0
  198. package/src/dashboard/boot-timing.ts +35 -0
  199. package/src/dashboard/first-screen-band.ts +63 -13
  200. package/src/dashboard/index.ts +94 -27
  201. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
  202. package/src/dashboard/skill-detection-runtime.ts +24 -4
  203. package/src/dashboard/slash-runtime/dashboard-handlers.ts +6 -0
  204. package/src/debug/log.ts +1 -1
  205. package/src/domains/morning-report.ts +3 -1
  206. package/src/domains/release-tool.ts +29 -12
  207. package/src/domains/surface-events.ts +16 -0
  208. package/src/elanous-config-dir.ts +2 -2
  209. package/src/fleet/host-doctor.ts +73 -2
  210. package/src/github/pr-labels.ts +10 -4
  211. package/src/graph-runner/runner.ts +104 -15
  212. package/src/harness/harness-clean.ts +21 -3
  213. package/src/harness/harness-cli-command.ts +68 -10
  214. package/src/harness/harness-pod-dispatch.ts +11 -4
  215. package/src/harness/harness-stop.ts +11 -7
  216. package/src/hq/seats.ts +17 -3
  217. package/src/index.ts +69 -9
  218. package/src/instance/current.ts +2 -6
  219. package/src/instance/resolve.ts +5 -6
  220. package/src/intelligence-map/model-bench-scores.ts +102 -0
  221. package/src/llm/openai-sampling-fields.ts +89 -0
  222. package/src/llm/runtime-model-family.ts +61 -0
  223. package/src/llm/zai-coding-plan.ts +27 -1
  224. package/src/llm.ts +13 -89
  225. package/src/loops/orchestrator/inbox.ts +73 -0
  226. package/src/loops/orchestrator/resync-shadow.ts +100 -12
  227. package/src/loops/orchestrator/tick.ts +73 -2
  228. package/src/machines/machine-ledger.ts +31 -3
  229. package/src/maturity/cli-maturity.ts +13 -3
  230. package/src/maturity/feature-maturity.ts +1 -1
  231. package/src/msg/msg-sse-server.ts +141 -0
  232. package/src/msg/msg-store.ts +149 -17
  233. package/src/ops/overview-collect.ts +3 -2
  234. package/src/ops/overview-owner.ts +29 -0
  235. package/src/patents/ledger.ts +128 -0
  236. package/src/plugin-hooks/shell-hook.ts +17 -2
  237. package/src/plugin-missions/evaluator-runner.ts +18 -2
  238. package/src/plugins/install/market-fetch.ts +32 -1
  239. package/src/plugins/install/plugin-install.ts +11 -1
  240. package/src/pod-lease/admission.ts +116 -41
  241. package/src/prompt-enhance/enhance.ts +11 -0
  242. package/src/release-loop/checklist.ts +52 -4
  243. package/src/release-loop/feature-store.ts +10 -9
  244. package/src/release-loop/merged-pr-checklist.ts +24 -5
  245. package/src/release-loop/rubric.ts +70 -0
  246. package/src/rfc-registry/registry.ts +88 -0
  247. package/src/seat-dispatch/seat-request-ledger.ts +4 -0
  248. package/src/seat-loop/knowhow-lint.ts +233 -0
  249. package/src/seat-loop/seat-loop.ts +5 -1
  250. package/src/seat-loop/seat-pack.ts +16 -4
  251. package/src/seat-loop/traffic-signal.ts +7 -0
  252. package/src/self-dev/ask-launch-flow.ts +48 -8
  253. package/src/self-dev/ask-path-candidates.ts +69 -14
  254. package/src/self-dev/decompose-proposal.ts +43 -1
  255. package/src/self-dev/draft-sweep.ts +23 -14
  256. package/src/self-dev/graph-journey-nodes.ts +2 -0
  257. package/src/self-dev/launch-preflight-text.ts +5 -1
  258. package/src/self-dev/launch-preflight.ts +143 -10
  259. package/src/self-dev/orchestrate.ts +6 -2
  260. package/src/self-dev/run-supervisor.ts +35 -1
  261. package/src/self-implement/gate-cli.ts +6 -2
  262. package/src/self-implement/goal-author.ts +5 -28
  263. package/src/self-implement/goal-command-execution.ts +26 -0
  264. package/src/self-implement/goal-new-paths.ts +4 -5
  265. package/src/self-implement/graph-authority.ts +17 -3
  266. package/src/self-implement/graph-yaml.ts +15 -7
  267. package/src/self-implement/headless-elanous-driver.ts +16 -1
  268. package/src/self-implement/host-regate.ts +44 -9
  269. package/src/self-implement/orchestrator.ts +5 -11
  270. package/src/self-implement/pwa-import-graph.ts +194 -7
  271. package/src/self-implement/rework-policy.ts +80 -2
  272. package/src/self-implement/self-implement-runtime.ts +126 -21
  273. package/src/self-implement/speed-claims.ts +145 -6
  274. package/src/session/index.ts +14 -4
  275. package/src/session-runtime/index.ts +55 -11
  276. package/src/skills/tools/persistent-grounding.ts +59 -4
  277. package/src/skills/tools/shell.ts +79 -1
  278. package/src/steward/rejudge-tick.ts +12 -1
  279. package/src/steward/release-day.ts +158 -0
  280. package/src/steward/resync-tick.ts +123 -0
  281. package/src/task-agent/actions.ts +1 -0
  282. package/src/task-agent/adopt-pr.ts +140 -0
  283. package/src/task-agent/board.ts +11 -1
  284. package/src/task-agent/card-evidence.ts +37 -5
  285. package/src/task-agent/cover.ts +105 -19
  286. package/src/task-agent/failure-notify.ts +61 -0
  287. package/src/task-agent/green-sweep.ts +170 -0
  288. package/src/task-agent/land-worktree.ts +52 -5
  289. package/src/task-agent/live-move-history.ts +1 -0
  290. package/src/task-agent/live-moves.ts +56 -17
  291. package/src/task-agent/parent-host.ts +1 -1
  292. package/src/task-agent/postland-card.ts +139 -0
  293. package/src/task-agent/rejudge-on-head.ts +321 -25
  294. package/src/task-agent/remote-rejudge-bind.ts +62 -0
  295. package/src/task-agent/repair-branch-cas.ts +182 -0
  296. package/src/task-agent/repair-cycle.ts +275 -0
  297. package/src/task-agent/repair-next-move.ts +31 -0
  298. package/src/task-agent/repair-review.ts +24 -0
  299. package/src/task-agent/repair-worker.ts +87 -0
  300. package/src/task-agent/review-result-capture.ts +66 -2
  301. package/src/task-agent/shadow.ts +150 -29
  302. package/src/task-agent/task-hand.ts +133 -8
  303. package/src/task-orchestrator/surfaces/pod-repair-commit.ts +96 -0
  304. package/src/task-orchestrator/surfaces/self-implement-pod.ts +71 -12
  305. package/src/telegram.ts +30 -1
  306. package/src/tool-runtime/tool-search-runtime.ts +25 -0
  307. package/src/tui.ts +20 -1
  308. package/src/user-config.ts +11 -60
  309. package/src/ux-sim/fault-sqlite-busy.ts +50 -0
  310. package/src/version/packed-revision.json +1 -1
  311. package/apps/pwa/out/_next/static/chunks/app/approvals/page-9360b1285b2a346e.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f08f6529d09ffae1.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/app/board/page-f000d54d258f9ef6.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/app/bots/page-22f674e7e4c2c9d5.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8888733e40cea6d1.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/app/chat/page-9717ec6964b92276.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/app/consult/page-44eaad486bc98a45.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/app/control/page-97fa6cf3d34c7eb9.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/app/editor/page-35e05d7450461a67.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/app/inside/page-b2638013c0bf7dfd.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/app/layout-c44f93a41167536f.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/app/loops/page-1b810474507f3702.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/app/market/page-74366ae2693a7d95.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4e634c5b98fd4e93.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ed3070d637850548.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/outputs/page-b9cc8a10bc6078b4.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/reflection/page-128458c8d86fbb5f.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/sessions/page-cf0c2b0183991d09.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/showroom/page-b008aff96be56654.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/tasks/page-814394dcbae789b1.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/term/page-6a87325c87f09dd2.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/trace/page-852170303c6cd755.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/vault/page-087ceeb975ef272b.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-97c7361cc2a19319.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/workspace/page-8c6901833f897496.js +0 -1
  336. /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_buildManifest.js +0 -0
  337. /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_ssgManifest.js +0 -0
@@ -280,11 +280,24 @@ export function graphAuthorityFields(authority: GraphAuthority, template: GraphT
280
280
  * (`wrong-stage` · `does-not-apply` · `key-absent` · `unparseable` 이 서로 다른 처방이다).
281
281
  * ⛔ **거절은 조용하지 않다** — 패치가 안 물면 `rejections` 로 나오고 «기준 선언»이 그대로 산다. */
282
282
  /** 오버레이 조건이 «읽을 수 있는» 상태 키. ⛔ 여기 없는 키는 원장에 값이 안 실린다.
283
- * 🔑 그래서 «떨어뜨리되 말은 한다» — 아래 `stateExtraKeys` 가 그 이름을 남긴다. */
284
- export const OVERLAY_STATE_KEYS = ['attempts', 'goal_id'] as const;
283
+ * 🔑 그래서 «떨어뜨리되 말은 한다» — 아래 `stateExtraKeys` 가 그 이름을 남긴다.
284
+ * ⭐ `author_grade` — 적응형 저작(GRAPH-AUTHOR-GRADE) 첫 조각: 저작 등급을 오버레이 상태의 «이름»으로 싣는다. */
285
+ export const OVERLAY_STATE_KEYS = ['attempts', 'author_grade', 'goal_id'] as const;
286
+
287
+ /** 저작 등급의 오버레이 어휘 — `goal-author` 의 `GoalAuthorGrade`(`full|lite`) 를 오버레이 등급(`normal`) 로 옮긴다.
288
+ * ⛔ 모르는 값(빈 값·오타)은 `undefined` — 등급을 «지어내지 않는다». */
289
+ export function normalizeAuthorGrade(value: unknown): 'lite' | 'normal' | 'complex' | undefined {
290
+ if (value === 'lite') return 'lite';
291
+ if (value === 'full') return 'normal';
292
+ if (value === 'complex') return 'complex';
293
+ return undefined;
294
+ }
285
295
 
286
296
  /** `graph-overlay-decision` 원장에 «판정에 실제로 쓴» 상태를 싣는다.
287
297
  * ⛔ `hasOwn` 으로 복사한다 — 그래야 「없음」과 「false」와 「0」이 원장에서 «다른 값»이다.
298
+ * ⭐ `author_grade` 만은 `normalizeAuthorGrade` 를 «거쳐» 실린다 — 저작 어휘(`full`)가 원장에
299
+ * 그대로 «사실»로 남으면 오버레이 어휘(`normal`)와 섞인다. 모르는 등급은 키는 남되 값이
300
+ * `undefined` 다(등급을 지어내지 않는다 · «있었다»는 사실도 조용히 버리지 않는다).
288
301
  * ⛔⭐ 목록 밖 키는 값을 «안» 싣지만 ***조용히 버리지 않는다*** — `stateExtraKeys` 로 이름을 남긴다.
289
302
  * 🩸 이 골이 생긴 이유가 「원장이 판정의 입력을 안 남겨서 손으로 재현하다 오판했다」이므로,
290
303
  * 여기서 «조용히» 떨어뜨리면 같은 병을 한 층 아래에서 다시 짓는 것이다. */
@@ -293,7 +306,8 @@ export function overlayDecisionObservation<T extends Readonly<Record<string, unk
293
306
  }): T & { readonly state: Readonly<Record<string, unknown>>; readonly stateExtraKeys?: readonly string[] } {
294
307
  const state: Record<string, unknown> = {};
295
308
  for (const key of OVERLAY_STATE_KEYS) {
296
- if (Object.hasOwn(input.state, key)) state[key] = input.state[key];
309
+ if (!Object.hasOwn(input.state, key)) continue;
310
+ state[key] = key === 'author_grade' ? normalizeAuthorGrade(input.state[key]) : input.state[key];
297
311
  }
298
312
  const extra = Object.keys(input.state).filter((key) => !(OVERLAY_STATE_KEYS as readonly string[]).includes(key)).sort();
299
313
  return { ...input, state, ...(extra.length > 0 ? { stateExtraKeys: extra } : {}) };
@@ -33,6 +33,8 @@ export interface GraphNodeSpec {
33
33
  readonly fanOut?: string;
34
34
  readonly contract?: GraphNodeContract;
35
35
  readonly call?: string;
36
+ /** Entry selected by this call when a unit exposes more than one entry. */
37
+ readonly callEntry?: string;
36
38
  readonly budget?: { readonly innerSteps: number };
37
39
  }
38
40
 
@@ -60,7 +62,7 @@ export interface GraphTemplateSpec {
60
62
  readonly state?: readonly string[];
61
63
  /** ⭐ 런 계약(선택) — 이 그래프가 «기본으로» 요구하는 실행 칸 등. 발사 인자가 덮는다(graph-run-contract.ts). */
62
64
  readonly runContract?: GraphRunContractSpec;
63
- readonly unit?: { readonly inputs: readonly string[]; readonly outputs: readonly string[]; readonly exits: readonly string[] };
65
+ readonly unit?: { readonly inputs: readonly string[]; readonly outputs: readonly string[]; readonly exits: readonly string[]; readonly entries?: readonly string[] };
64
66
  readonly nodes: readonly GraphNodeSpec[];
65
67
  readonly edges: readonly GraphEdgeSpec[];
66
68
  }
@@ -155,7 +157,7 @@ export function parseGraphTemplateYaml(source: string, label = '<inline>'): Grap
155
157
  : undefined;
156
158
  // A subgraph node becomes a unit call only when it names `call:`; without it the node keeps the
157
159
  // pre-unit meaning (catalog role / recipe node), so existing declarations stay valid.
158
- if (kind === 'subgraph' && (n.call !== undefined || n.budget !== undefined)) {
160
+ if (kind === 'subgraph' && (n.call !== undefined || n.budget !== undefined || n.call_entry !== undefined)) {
159
161
  if (typeof n.call !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]*@(?:\^?[1-9]\d*)$/.test(n.call))
160
162
  errors.push({ path: `${at}/call`, message: 'call 은 id@정수판 또는 id@^주판이어야 한다' });
161
163
  const budget = n.budget as Record<string, unknown> | undefined;
@@ -166,8 +168,10 @@ export function parseGraphTemplateYaml(source: string, label = '<inline>'): Grap
166
168
  !Array.isArray(contractRaw.outputs) || !contractRaw.outputs.every(v => typeof v === 'string'))
167
169
  errors.push({ path: `${at}/contract`, message: 'subgraph 에는 inputs·outputs 계약이 필요하다' });
168
170
  if (recipe !== 'none') errors.push({ path: `${at}/recipe`, message: 'subgraph 는 recipe 를 실행하지 않는다' });
169
- } else if (kind !== 'subgraph' && (n.call !== undefined || n.budget !== undefined)) {
170
- errors.push({ path: at, message: 'call·budget 은 subgraph 전용이다' });
171
+ if (n.call_entry !== undefined && (typeof n.call_entry !== 'string' || !n.call_entry.trim()))
172
+ errors.push({ path: `${at}/call_entry`, message: 'call_entry 는 비어 있지 않은 노드 id 여야 한다' });
173
+ } else if (kind !== 'subgraph' && (n.call !== undefined || n.budget !== undefined || n.call_entry !== undefined)) {
174
+ errors.push({ path: at, message: 'call·call_entry·budget 은 subgraph 전용이다' });
171
175
  }
172
176
  // ⛔ 계약이 없으면 「이 노드가 무엇을 할 수 있나」를 답할 수 없다 — 경고로 남긴다(거절은 오버레이 쪽).
173
177
  if (contract === undefined) warnings.push({ path: `${at}/contract`, message: '계약(inputs·tools·outputs)이 없다 — 권한을 코드에서만 알 수 있다' });
@@ -182,6 +186,7 @@ export function parseGraphTemplateYaml(source: string, label = '<inline>'): Grap
182
186
  ...(typeof n.fan_out === 'string' ? { fanOut: n.fan_out } : {}),
183
187
  ...(contract ? { contract } : {}),
184
188
  ...(kind === 'subgraph' && typeof n.call === 'string' ? { call: n.call } : {}),
189
+ ...(kind === 'subgraph' && typeof n.call_entry === 'string' ? { callEntry: n.call_entry } : {}),
185
190
  ...(kind === 'subgraph' && n.budget && typeof n.budget === 'object' && !Array.isArray(n.budget) &&
186
191
  typeof (n.budget as Record<string, unknown>).inner_steps === 'number'
187
192
  ? { budget: { innerSteps: (n.budget as { inner_steps: number }).inner_steps } } : {}),
@@ -242,15 +247,18 @@ export function parseGraphTemplateYaml(source: string, label = '<inline>'): Grap
242
247
  if (doc.unit !== undefined) {
243
248
  const rawUnit = doc.unit as Record<string, unknown>;
244
249
  if (!rawUnit || typeof rawUnit !== 'object' || Array.isArray(rawUnit) ||
245
- Object.keys(rawUnit).some(key => !['inputs', 'outputs', 'exits'].includes(key)) ||
250
+ Object.keys(rawUnit).some(key => !['inputs', 'outputs', 'exits', 'entries'].includes(key)) ||
246
251
  !['inputs', 'outputs', 'exits'].every(key => Array.isArray(rawUnit[key]) &&
247
252
  (rawUnit[key] as unknown[]).every(v => typeof v === 'string' && v.length > 0) &&
248
253
  new Set(rawUnit[key] as string[]).size === (rawUnit[key] as string[]).length) ||
249
254
  !(rawUnit.exits as string[]).length ||
250
255
  (rawUnit.exits as string[]).some(exit => !terminalNodes.includes(exit)) ||
251
- terminalNodes.some(exit => !(rawUnit.exits as string[]).includes(exit))) {
256
+ terminalNodes.some(exit => !(rawUnit.exits as string[]).includes(exit)) ||
257
+ (rawUnit.entries !== undefined && (!Array.isArray(rawUnit.entries) || !(rawUnit.entries as unknown[]).every(v => typeof v === 'string' && v.length > 0 && known.has(v)) ||
258
+ new Set(rawUnit.entries as string[]).size !== (rawUnit.entries as string[]).length || !(rawUnit.entries as string[]).includes(entryNode!)))) {
252
259
  errors.push({ path: `${label}/unit`, message: 'unit 의 inputs·outputs·exits 및 terminal_nodes 가 일치해야 한다' });
253
- } else unit = { inputs: rawUnit.inputs as string[], outputs: rawUnit.outputs as string[], exits: rawUnit.exits as string[] };
260
+ } else unit = { inputs: rawUnit.inputs as string[], outputs: rawUnit.outputs as string[], exits: rawUnit.exits as string[],
261
+ ...(rawUnit.entries === undefined ? {} : { entries: rawUnit.entries as string[] }) };
254
262
  }
255
263
  const runContract = parseGraphRunContract(doc.run_contract);
256
264
  if (runContract.error) errors.push({ path: `${label}/run_contract`, message: runContract.error });
@@ -1361,7 +1361,22 @@ export async function runHeadlessGoalLoopPty(opts: HeadlessGoalLoopPtyOptions):
1361
1361
  return {};
1362
1362
  }
1363
1363
  const target = childLlm
1364
- ? resolveExplicitChildEscalateTarget(tier, process.env, { provider: childLlm.provider, model: childLlm.model, ...(childLlm.effort ? { effort: childLlm.effort } : {}) })
1364
+ ? resolveExplicitChildEscalateTarget(tier, process.env,
1365
+ { provider: childLlm.provider, model: childLlm.model, ...(childLlm.effort ? { effort: childLlm.effort } : {}) },
1366
+ {
1367
+ escalateChild: tier === 'none' ? undefined : (() => {
1368
+ try { return getUserConfig().tools.selfImplement.escalateChild; }
1369
+ catch (error) {
1370
+ try { debug.log('self-dev.rework', 'escalate-config-unavailable', { reason: errorMessage(error) }); }
1371
+ catch { /* Observability cannot block child launch. */ }
1372
+ return undefined;
1373
+ }
1374
+ })(),
1375
+ observe: observation => {
1376
+ try { debug.log(observation.category, observation.event, observation.data); }
1377
+ catch { /* Observability cannot block child launch. */ }
1378
+ },
1379
+ })
1365
1380
  : resolveEscalateTarget(tier);
1366
1381
  if (!target) return {};
1367
1382
  return {
@@ -1,4 +1,5 @@
1
1
  import { spawnSync } from 'node:child_process';
2
+ import { randomUUID } from 'node:crypto';
2
3
  import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
3
4
  import { tmpdir } from 'node:os';
4
5
  import { dirname, join, resolve } from 'node:path';
@@ -7,7 +8,7 @@ import { getUserConfig } from '../user-config.js';
7
8
  import { publicExposureFiles, runExposeGate } from './expose-gate.js';
8
9
  import { admitLandingMerge } from './frozen-merges.js';
9
10
  import { releaseGitDiffPaths, releasePathHold, releasePathHoldShouldPost, releasePathHoldComment, releasePathHoldCommentsArgs, RELEASE_PATH_LABEL } from '../self-dev/release-path-guard.js';
10
- import { detectTestInterference, parseFailureCount, runBunTest } from '../../scripts/detect-test-interference.js';
11
+ import { detectTestInterference, failedNamesFromStreams, parseFailureCount, runBunTest, type MeasuredRun } from '../../scripts/detect-test-interference.js';
11
12
  import { MAX_INSPECTED_TEST_FILES, runTestInterferenceGate } from '../../scripts/ci-test-interference-gate.js';
12
13
  import { pwaGraph, pwaReach } from '../../scripts/ci-pwa-build-gate.js';
13
14
  import { classifyMergeFailure, clipRegateDetail, parseConflictFiles, type HostRegateFailure } from './regate-detail.js';
@@ -32,6 +33,12 @@ export function provideCheckoutDependencies(candidate: string, sourceRoot: strin
32
33
 
33
34
  export interface HostRegateInput { prNumber: number; headCommit: string; repoRoot: string; goalFile?: string; verifyOnly?: boolean; /** TA-LIVE-LAND-2: run the full landing gates (release-path hold · neighbours · PR re-read) but stop before the freeze admission and `gh pr merge` — the task agent lands it later via `pr land --expected-head`. */ noMerge?: true; /** Called by a resume sweep that already holds this PR's claim. */ resumed?: true }
34
35
  export interface HostRegateResult { passed: boolean; failures: HostRegateFailure[]; os: string; status?: 'passed' | 'failed' | 'unmeasured' | 'frozen'; /** verifyOnly: 실제로 얹어 잰 base 끝 */ baseCommit?: string }
36
+ /** Only a missing host repository is recoverable by selecting the run's recorded root. Gate failures are final. */
37
+ export function hostRepositoryUnavailable(result: HostRegateResult): boolean {
38
+ return !result.passed && result.status === 'unmeasured'
39
+ && result.failures[0]?.step === 'host-regate'
40
+ && result.failures[0].detail === 'host repository unavailable';
41
+ }
35
42
  export type InterferenceVerdict = { passed: boolean; detail?: string; unmeasured?: boolean; /** neighbour tests left out so the selection fits the inspection cap */ neighborsTrimmed?: number; /** importer tests left out after changed tests and all selected neighbors take priority */ importerTrimmed?: number };
36
43
  export type HostRegateDeps = {
37
44
  command?: (bin: string, args: readonly string[], cwd: string, env?: NodeJS.ProcessEnv) => { status: number | null; stdout: string; stderr: string };
@@ -57,6 +64,18 @@ const defaultCommand: NonNullable<HostRegateDeps['command']> = (bin, args, cwd,
57
64
  return { status: overflow ? null : r.status, stdout: r.stdout ?? '', stderr: (r.stderr ?? '') + (r.error ? `${r.error.message} (${overflow ? `output exceeded ${SPAWN_MAX_BUFFER} bytes` : 'command unavailable'})` : '') };
58
65
  };
59
66
 
67
+ /** Keep the failure identity in both the returned detail and the host-regate ledger event. */
68
+ function failureNames(runs: readonly MeasuredRun[], fileCap: number, testCap: number): string {
69
+ const failed = runs.filter((run) => run.fail !== null && run.fail > 0);
70
+ if (!failed.length) return 'none';
71
+ const files = [...new Set(failed.flatMap((run) => run.failedFiles ?? []))];
72
+ const tests = [...new Set(failed.flatMap((run) => run.failedTests ?? []))];
73
+ const bounded = (names: string[], cap: number): string => names.length
74
+ ? `${names.slice(0, cap).map((name) => name.slice(0, 120)).join(' | ')}${names.length > cap ? ` | +${names.length - cap} more` : ''}`
75
+ : 'unknown';
76
+ return `files=${bounded(files, fileCap)}; tests=${bounded(tests, testCap)}`;
77
+ }
78
+
60
79
  /** The informational pr-land gate always returns zero; enforce its measured report and the combined test result instead. */
61
80
  export async function defaultInterference(files: readonly string[], cwd: string, opts: {
62
81
  neighbors?: boolean;
@@ -136,7 +155,9 @@ export async function defaultInterference(files: readonly string[], cwd: string,
136
155
  const run = await (opts.run ?? ((paths, dir) => runBunTest(paths, undefined, dir)))(selected, cwd);
137
156
  const fail = run.signal || run.exitCode === null ? null : parseFailureCount(`${run.stdout}${run.stderr}`);
138
157
  if (fail === null) return { passed: false, unmeasured: true, ...trimmed, detail: `single test run unmeasured: ${selected[0]}` };
139
- return fail === 0 ? { passed: true, ...trimmed } : { passed: false, ...trimmed, detail: `${selected[0]} — ${fail} fail` };
158
+ if (fail === 0) return { passed: true, ...trimmed };
159
+ const names = failedNamesFromStreams(run);
160
+ return { passed: false, ...trimmed, detail: `single=${fail} fail; ${failureNames([{ files: selected, fail, status: 'measured', failedFiles: names.files, failedTests: names.tests }], 8, 20)}` };
140
161
  }
141
162
  // The informational pr-land gate always returns zero; its measured report is the verdict.
142
163
  let report: Awaited<ReturnType<typeof detectTestInterference>> | undefined;
@@ -146,7 +167,10 @@ export async function defaultInterference(files: readonly string[], cwd: string,
146
167
  });
147
168
  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}` };
148
169
  if (report.isolatedFailures !== 0 || report.combinedFailures !== 0 || report.status === 'interference') {
149
- return { passed: false, ...trimmed, detail: `isolated=${report.isolatedFailures}, combined=${report.combinedFailures}, interference=${report.status}${trimNote}` };
170
+ const bothFailed = report.isolatedFailures! > 0 && report.combinedFailures! > 0;
171
+ const isolatedNames = failureNames(report.isolated, bothFailed ? 4 : 8, bothFailed ? 10 : 20);
172
+ const combinedNames = failureNames([report.combined], bothFailed ? 4 : 8, bothFailed ? 10 : 20);
173
+ return { passed: false, ...trimmed, detail: `isolated=${report.isolatedFailures}, combined=${report.combinedFailures}, interference=${report.status}; isolated ${isolatedNames}; combined ${combinedNames}${trimNote}` };
150
174
  }
151
175
  return { passed: true, ...trimmed, ...(trimNote ? { detail: trimNote.slice(3) } : {}) };
152
176
  }
@@ -237,6 +261,10 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
237
261
  let endLanding: ((merged?: boolean) => void) | undefined;
238
262
  let landed = false;
239
263
  let verifiedBase: string | undefined;
264
+ // Two host-regate slots may fetch in the same repository at once. FETCH_HEAD is shared;
265
+ // each measurement needs its own ref so another fetch cannot replace its evidence.
266
+ const fetchNamespace = `refs/elanous/regate/${process.pid}-${randomUUID()}`;
267
+ const fetchedRefs = new Set<string>();
240
268
  let interferenceNote: Record<string, unknown> = {};
241
269
  // HOST-REGATE-REASON-LOST: `facts` carries the expected/actual head, conflict files and merge failure kind of the
242
270
  // pr-head · merge-conflict · merge steps; the detail keeps a bounded head ⊕ tail instead of the first 300 characters.
@@ -250,6 +278,12 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
250
278
  if (r.status !== 0) throw new Error(clipRegateDetail((r.stderr || r.stdout || `${bin} exited ${r.status}`).trim()));
251
279
  return r.stdout.trim();
252
280
  };
281
+ const fetchRef = (source: string, name: 'base' | 'head'): string => {
282
+ const ref = `${fetchNamespace}/${name}`;
283
+ fetchedRefs.add(ref);
284
+ run('git', ['fetch', '--no-write-fetch-head', 'origin', `+${source}:${ref}`], input.repoRoot);
285
+ return run('git', ['rev-parse', ref], input.repoRoot);
286
+ };
253
287
  try {
254
288
  if (!Number.isSafeInteger(input.prNumber) || input.prNumber <= 0 || !/^[0-9a-f]{40}$/i.test(input.headCommit)) return result('unmeasured', 'input', 'invalid PR number or checked head SHA');
255
289
  release = await (deps.acquire ?? acquireSlot)(input.repoRoot);
@@ -272,8 +306,7 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
272
306
  const initialPrBase = baseCommit;
273
307
  let buildPwa = false;
274
308
  try {
275
- run('git', ['fetch', 'origin', `refs/heads/${baseRefName}`], input.repoRoot);
276
- let fetchedBase = run('git', ['rev-parse', 'FETCH_HEAD'], input.repoRoot);
309
+ let fetchedBase = fetchRef(`refs/heads/${baseRefName}`, 'base');
277
310
  if (input.verifyOnly) { baseCommit = fetchedBase; verifiedBase = fetchedBase; }
278
311
  else {
279
312
  for (let attempt = 1; fetchedBase !== baseCommit; attempt++) {
@@ -290,12 +323,10 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
290
323
  return prHeadFailure(view);
291
324
  }
292
325
  baseCommit = view.baseRefOid!;
293
- run('git', ['fetch', 'origin', `refs/heads/${baseRefName}`], input.repoRoot);
294
- fetchedBase = run('git', ['rev-parse', 'FETCH_HEAD'], input.repoRoot);
326
+ fetchedBase = fetchRef(`refs/heads/${baseRefName}`, 'base');
295
327
  }
296
328
  }
297
- run('git', ['fetch', 'origin', `refs/pull/${input.prNumber}/head`], input.repoRoot);
298
- if (run('git', ['rev-parse', 'FETCH_HEAD'], input.repoRoot) !== input.headCommit) throw new Error('fetched PR head differs from checked SHA');
329
+ if (fetchRef(`refs/pull/${input.prNumber}/head`, 'head') !== input.headCommit) throw new Error('fetched PR head differs from checked SHA');
299
330
  const slotWorktree = (release as (() => void) & { worktree?: string }).worktree;
300
331
  ephemeral = !!deps.makeTemp || !slotWorktree;
301
332
  worktree = deps.makeTemp?.() ?? slotWorktree ?? mkdtempSync(join(tmpdir(), 'host-regate-'));
@@ -463,6 +494,10 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
463
494
  return result('unmeasured', 'host-regate', String(e));
464
495
  } finally {
465
496
  try { endLanding?.(landed); } catch (e) { log('unmeasured', { pr: input.prNumber, files, os: process.platform, failedStep: 'landing-release', detail: String(e) }); }
497
+ for (const ref of fetchedRefs) {
498
+ try { run('git', ['update-ref', '-d', ref], input.repoRoot); }
499
+ catch (e) { log('unmeasured', { pr: input.prNumber, files, os: process.platform, failedStep: 'fetch-ref-cleanup', detail: String(e) }); }
500
+ }
466
501
  try {
467
502
  if (attached && worktree) {
468
503
  if (slotCommonDir) verifySlot();
@@ -94,6 +94,7 @@ import { boundaryGlobs, boundaryViolations } from './goal-boundary.js';
94
94
  import { askAtExecution, type ExecutionClarificationResult } from './execution-clarification.js';
95
95
  import { classifyOutOfScope } from './scope-adjacency.js';
96
96
  import { targetScopedGoalText } from './goal-text-path-scope.js';
97
+ import { declaredNewFilePaths } from './goal-new-paths.js';
97
98
  import { dropStaleReverts, type RevertGuardResult } from './revert-guard.js';
98
99
  import type { SelfDevGoalType } from '../self-dev/orchestrate.js';
99
100
  import { DEFAULT_BRANCH_WORKTREE_BASE, gateGitResidue, observeGitResidue } from '../git-fs/worktree.js';
@@ -813,10 +814,8 @@ function parseDeclaredTargetPathsFromAsk(askText: string): { readable: boolean;
813
814
  return { readable: true, paths: [...new Set(paths.filter(Boolean))] };
814
815
  }
815
816
 
816
- // SCOPE-NEW-FILES-LINE — 칸 문면은 새로 만들 파일을 «신설:» 줄로 따로 적는다(OP D칸 규칙). 그 줄도 선언 범위다.
817
+ // SCOPE-NEW-FILES-LINE — 칸 문면의 신설 경로도 선언 범위다.
817
818
  // 10-11 FLEX-SIGNALS run-95799887: 칸이 적은 신설 파일 둘을 «범위 밖»으로 세어 구현을 보류했다.
818
- const DECLARED_NEW_FILES_LINE = /^\s*(?:신설|new\s*files?)\s*[::]\s*(.*)$/i;
819
-
820
819
  function declaredPathsFromGoalDocumentText(document: string): { readable: boolean; paths: readonly string[] } {
821
820
  const originalAsk = extractVerbatimOriginalAsk(document)?.ask ?? null;
822
821
  if (originalAsk === null) return { readable: false, paths: [] };
@@ -826,13 +825,6 @@ function declaredPathsFromGoalDocumentText(document: string): { readable: boolea
826
825
  return newFiles.length > 0 ? { readable: true, paths: [...new Set([...declared.paths, ...newFiles])] } : declared;
827
826
  }
828
827
 
829
- function declaredNewFilePaths(askText: string): readonly string[] {
830
- return askText.split(/\r?\n/).flatMap((line) => {
831
- const match = DECLARED_NEW_FILES_LINE.exec(line);
832
- return match ? parseDeclaredTargetPathsFromAsk(`대상 경로: ${match[1]}`).paths : [];
833
- });
834
- }
835
-
836
828
  /** P3 전제의 경계 — ask 머리줄 «대상 경로:» ⊕ «신설:» 줄의 합집합. 못 읽으면 빈 목록(그때 P3 는 unknown). */
837
829
  function declaredPremiseScopePaths(askText: string): string[] {
838
830
  return [...new Set([...parseDeclaredTargetPathsFromAsk(askText).paths, ...declaredNewFilePaths(askText)])];
@@ -3647,7 +3639,8 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
3647
3639
  try {
3648
3640
  // 10-05 PODPROVIDER: record which child provider this run uses — «default» when the launch named none.
3649
3641
  const launch = opts.launch ?? resolveLaunchStamp();
3650
- const origin = { ...runOriginData(), childProvider: opts.childLlm?.provider ?? 'default', ...(opts.childLlm?.model ? { childModel: opts.childLlm.model } : {}), launch };
3642
+ const origin = { ...runOriginData(), childProvider: opts.childLlm?.provider ?? 'default', ...(opts.childLlm?.model ? { childModel: opts.childLlm.model } : {}),
3643
+ ...(process.env.ELANOUS_TASK_CARD_ID ? { taskCardId: process.env.ELANOUS_TASK_CARD_ID } : {}), launch };
3651
3644
  (opts.seams.writeRunLedger ?? appendRunLedgerEntry)({
3652
3645
  timestamp: new Date().toISOString(), runId, event: 'run-origin', data: origin,
3653
3646
  ...(opts.goalId ? { goalId: opts.goalId } : {}), ...shardIdentity,
@@ -8262,6 +8255,7 @@ async function runSelfImplementInner(
8262
8255
  url: pr.url,
8263
8256
  number: pr.number,
8264
8257
  branch: wt.branch,
8258
+ headCommit: runGitCommand(wt.path, ['rev-parse', '--verify', 'HEAD'], { encoding: 'utf8', timeout: 10_000 }).stdout.trim(),
8265
8259
  autoMerge: canAuto,
8266
8260
  autoReview: !!labels,
8267
8261
  ...(preparedPrBody.truncated ? { prBodyTruncated: true, prBodyOriginalChars: preparedPrBody.originalChars } : {}),
@@ -1,5 +1,7 @@
1
- import { readdirSync, readFileSync } from 'node:fs';
1
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
2
2
  import { dirname, join, relative, resolve, sep } from 'node:path';
3
+ import ts from 'typescript';
4
+ import { debug } from '../debug/log.js';
3
5
 
4
6
  type GraphFiles = {
5
7
  readFile?: (path: string) => string;
@@ -24,6 +26,16 @@ function sourceFiles(cwd: string): string[] {
24
26
  return files;
25
27
  }
26
28
 
29
+ const IMPORT_SPECIFIER = /\b(?:import|export)\s+(?:[^;'"]*?\s+from\s*)?['"](\.[^'"]+)['"]|\bimport\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
30
+
31
+ /** Repo-relative file candidates a relative import specifier (already joined to its importer's directory) can resolve to. */
32
+ function importCandidates(base: string): string[] {
33
+ return /\.js$/.test(base)
34
+ // A real .js file wins over a same-named .ts source (the actual resolution order).
35
+ ? [base, base.replace(/\.js$/, '.ts'), base.replace(/\.js$/, '.tsx'), `${base.slice(0, -3)}/index.ts`, `${base.slice(0, -3)}/index.tsx`, `${base.slice(0, -3)}/index.js`]
36
+ : /\.tsx?$/.test(base) ? [base] : [base + '.ts', base + '.tsx', base + '.js', base + '/index.ts', base + '/index.tsx', base + '/index.js'];
37
+ }
38
+
27
39
  /** PWA source entrypoints and the transitive relative imports they resolve inside repository src/. */
28
40
  export function pwaReachableSrcFiles(cwd: string, { readFile = (path) => readFileSync(path, 'utf8'), listFiles = sourceFiles }: GraphFiles = {}): Set<string> {
29
41
  const root = resolve(cwd);
@@ -33,7 +45,6 @@ export function pwaReachableSrcFiles(cwd: string, { readFile = (path) => readFil
33
45
  // Start from everything apps/pwa/tsconfig.json `include`s — its `src/**/*.ts(x)` also matches *.test.* files, and the Next
34
46
  // build type-checks them. Excluding tests hid the real entry (tool-result-chain.test.ts → src/nexus/api/meta-api → most of src).
35
47
  const queue = [...available].filter((file) => /^apps\/pwa\/src\/.*\.tsx?$/.test(file));
36
- const imports = /\b(?:import|export)\s+(?:[^;'"]*?\s+from\s*)?['"](\.[^'"]+)['"]|\bimport\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
37
48
  while (queue.length) {
38
49
  const file = queue.pop()!;
39
50
  if (visited.has(file)) continue;
@@ -42,16 +53,13 @@ export function pwaReachableSrcFiles(cwd: string, { readFile = (path) => readFil
42
53
  let content: string;
43
54
  try { content = readFile(join(root, file)); }
44
55
  catch { continue; }
45
- for (const match of content.matchAll(imports)) {
56
+ for (const match of content.matchAll(IMPORT_SPECIFIER)) {
46
57
  const specifier = match[1] ?? match[2];
47
58
  if (!specifier) continue;
48
59
  const target = resolve(root, dirname(file), specifier);
49
60
  const base = relative(root, target).split(sep).join('/');
50
61
  if (!base.startsWith('src/')) continue;
51
- const candidates = /\.js$/.test(base)
52
- // A real .js file wins over a same-named .ts source (the actual resolution order).
53
- ? [base, base.replace(/\.js$/, '.ts'), base.replace(/\.js$/, '.tsx'), `${base.slice(0, -3)}/index.ts`, `${base.slice(0, -3)}/index.tsx`, `${base.slice(0, -3)}/index.js`]
54
- : /\.tsx?$/.test(base) ? [base] : [base + '.ts', base + '.tsx', base + '.js', base + '/index.ts', base + '/index.tsx', base + '/index.js'];
62
+ const candidates = importCandidates(base);
55
63
  const resolved = candidates.find((candidate) => available.has(candidate));
56
64
  if (resolved) {
57
65
  if (!visited.has(resolved)) queue.push(resolved);
@@ -63,3 +71,182 @@ export function pwaReachableSrcFiles(cwd: string, { readFile = (path) => readFil
63
71
  }
64
72
  return reached;
65
73
  }
74
+
75
+ export type AffectedRoots = { roots: Set<string>; failure?: undefined } | { roots?: undefined; failure: string };
76
+
77
+ function astImportSpecifiers(source: ts.SourceFile): { imports: string[]; ignoredDynamic: number; failure?: string } {
78
+ const parseError = (source as ts.SourceFile & { parseDiagnostics?: readonly ts.Diagnostic[] }).parseDiagnostics?.[0];
79
+ if (parseError) return { imports: [], ignoredDynamic: 0, failure: `${source.fileName} AST 파싱 실패: ${ts.flattenDiagnosticMessageText(parseError.messageText, ' ')}` };
80
+ const imports: string[] = [];
81
+ let ignoredDynamic = 0;
82
+ const literal = (node: ts.Node | undefined): string | undefined => node && (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) ? node.text : undefined;
83
+ const visit = (node: ts.Node): void => {
84
+ if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
85
+ const specifier = literal(node.moduleSpecifier);
86
+ if (specifier) imports.push(specifier);
87
+ } else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference)) {
88
+ const specifier = literal(node.moduleReference.expression);
89
+ if (specifier) imports.push(specifier);
90
+ } else if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument)) {
91
+ const specifier = literal(node.argument.literal);
92
+ if (specifier) imports.push(specifier);
93
+ } else if (ts.isCallExpression(node) && (node.expression.kind === ts.SyntaxKind.ImportKeyword
94
+ || (ts.isIdentifier(node.expression) && node.expression.text === 'require'))) {
95
+ const specifier = literal(node.arguments[0]);
96
+ if (specifier) imports.push(specifier);
97
+ else ignoredDynamic += 1;
98
+ }
99
+ ts.forEachChild(node, visit);
100
+ };
101
+ visit(source);
102
+ return { imports, ignoredDynamic };
103
+ }
104
+
105
+ function reExportSpecifiers(source: ts.SourceFile): string[] {
106
+ const importedLocals = new Map<string, string>();
107
+ for (const statement of source.statements) {
108
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) continue;
109
+ const from = statement.moduleSpecifier.text;
110
+ const clause = statement.importClause;
111
+ if (clause?.name) importedLocals.set(clause.name.text, from);
112
+ const bindings = clause?.namedBindings;
113
+ if (bindings && ts.isNamespaceImport(bindings)) importedLocals.set(bindings.name.text, from);
114
+ if (bindings && ts.isNamedImports(bindings)) {
115
+ for (const element of bindings.elements) importedLocals.set(element.name.text, from);
116
+ }
117
+ }
118
+ const specifiers: string[] = [];
119
+ for (const statement of source.statements) {
120
+ if (ts.isExportDeclaration(statement)) {
121
+ if (statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)) specifiers.push(statement.moduleSpecifier.text);
122
+ else if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
123
+ for (const element of statement.exportClause.elements) {
124
+ const from = importedLocals.get((element.propertyName ?? element.name).text);
125
+ if (from) specifiers.push(from);
126
+ }
127
+ }
128
+ } else if (ts.isExportAssignment(statement) && ts.isIdentifier(statement.expression)) {
129
+ const from = importedLocals.get(statement.expression.text);
130
+ if (from) specifiers.push(from);
131
+ }
132
+ }
133
+ return specifiers;
134
+ }
135
+
136
+ /** Declarations visible without an import edge must enter every scoped program. */
137
+ function suppliesAmbientTypes(source: ts.SourceFile): boolean {
138
+ if (source.fileName.endsWith('.d.ts') || !ts.isExternalModule(source)) return true;
139
+ const visit = (node: ts.Node): boolean => {
140
+ if (ts.isModuleDeclaration(node) && (node.name.text === 'global' || ts.isStringLiteral(node.name))) return true;
141
+ return ts.forEachChild(node, visit) ?? false;
142
+ };
143
+ return visit(source);
144
+ }
145
+
146
+ function gateTypeScriptSources(cwd: string): string[] {
147
+ const files: string[] = [];
148
+ const walk = (dir: string): void => {
149
+ // A partial directory walk is not a valid affected-root measurement: an
150
+ // unreadable subtree may contain the only importer of a changed file.
151
+ const entries = readdirSync(join(cwd, dir), { withFileTypes: true });
152
+ for (const entry of entries) {
153
+ const path = `${dir}/${entry.name}`;
154
+ if (entry.isDirectory() && entry.name !== 'node_modules' && !entry.name.startsWith('.')) walk(path);
155
+ else if (entry.isFile() && /\.ts$/.test(path)) files.push(path);
156
+ }
157
+ };
158
+ for (const root of ['src', 'scripts', 'test']) {
159
+ // An optional include glob may match no directory in a fixture. Only a
160
+ // confirmed ENOENT is empty; permission/I/O errors must fail closed.
161
+ try { statSync(join(cwd, root)); }
162
+ catch (cause) {
163
+ if ((cause as NodeJS.ErrnoException).code === 'ENOENT') continue;
164
+ throw cause;
165
+ }
166
+ walk(root);
167
+ }
168
+ return files;
169
+ }
170
+
171
+ /**
172
+ * Changed .ts files plus the listed files that import one of them directly (one reverse-import step).
173
+ * Every changed file and every listed file must be readable — an unreadable file could be a hidden importer, so the
174
+ * result is a failure the caller turns into a full-program run, never a smaller root set.
175
+ */
176
+ export function reverseImportAffectedRoots(cwd: string, changed: ReadonlySet<string>, { readFile = (path) => readFileSync(path, 'utf8'), listFiles = gateTypeScriptSources }: GraphFiles = {}): AffectedRoots {
177
+ const root = resolve(cwd);
178
+ const normalize = (file: string): string => file.replaceAll('\\', '/').replace(/^\.\//, '');
179
+ const read = (file: string): string | { failure: string } => {
180
+ try { return readFile(join(root, file)); }
181
+ catch (cause) { return { failure: `${file} 을 읽지 못했다: ${cause instanceof Error ? cause.message : String(cause)}` }; }
182
+ };
183
+ let listed: string[];
184
+ try { listed = listFiles(cwd).map(normalize); }
185
+ catch (cause) { return { failure: `소스 목록을 읽지 못했다: ${cause instanceof Error ? cause.message : String(cause)}` }; }
186
+ const available = new Set(listed);
187
+ const targets = new Set([...changed].map(normalize));
188
+ for (const file of targets) {
189
+ const content = read(file);
190
+ if (typeof content !== 'string') return content;
191
+ }
192
+ const importsByFile = new Map<string, Set<string>>();
193
+ const barrelsByTarget = new Map<string, Set<string>>();
194
+ const ambientRoots = new Set<string>();
195
+ let ignoredDynamic = 0;
196
+ const resolveImport = (file: string, specifier: string): { target?: string; ignored?: true; unresolved?: true } => {
197
+ if (!specifier.startsWith('.')) return { ignored: true };
198
+ const base = relative(root, resolve(root, dirname(file), specifier)).split(sep).join('/');
199
+ // The gate's tsconfig includes these roots. A relative import outside them
200
+ // cannot lead back to a changed gate source; asset imports are not TS
201
+ // source edges either.
202
+ if (!/^(?:src|scripts|test)\//.test(base) || /\.(?:css|json|svg|png|jpg|jpeg|webp|wasm)$/.test(base)) return { ignored: true };
203
+ const target = importCandidates(base).find((candidate) => available.has(candidate));
204
+ return target ? { target } : { unresolved: true };
205
+ };
206
+ for (const file of listed) {
207
+ const content = read(file);
208
+ if (typeof content !== 'string') return content;
209
+ let source: ts.SourceFile;
210
+ try { source = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true); }
211
+ catch (cause) { return { failure: `${file} AST 파싱 실패: ${cause instanceof Error ? cause.message : String(cause)}` }; }
212
+ const scanned = astImportSpecifiers(source);
213
+ if (scanned.failure) return { failure: scanned.failure };
214
+ if (suppliesAmbientTypes(source)) ambientRoots.add(file);
215
+ ignoredDynamic += scanned.ignoredDynamic;
216
+ const imports = new Set<string>();
217
+ for (const specifier of scanned.imports) {
218
+ if (!specifier.startsWith('.')) continue;
219
+ const resolved = resolveImport(file, specifier);
220
+ if (resolved.unresolved) return { failure: `${file} 의 리터럴 import ${specifier} 해석 실패` };
221
+ if (resolved.target) imports.add(resolved.target);
222
+ }
223
+ importsByFile.set(file, imports);
224
+ for (const specifier of reExportSpecifiers(source)) {
225
+ const resolved = resolveImport(file, specifier);
226
+ if (resolved.unresolved) return { failure: `${file} 의 리터럴 re-export ${specifier} 해석 실패` };
227
+ if (!resolved.target) continue;
228
+ const barrels = barrelsByTarget.get(resolved.target) ?? new Set<string>();
229
+ barrels.add(file);
230
+ barrelsByTarget.set(resolved.target, barrels);
231
+ }
232
+ }
233
+ if (ignoredDynamic) debug.log('typecheck.gate', 'nonliteral-import-ignored', { count: ignoredDynamic });
234
+ // A changed export can break an importer through any number of re-export
235
+ // barrels. Walk that chain to a fixed point, then include direct importers
236
+ // of each barrel. The visited set also terminates circular barrels.
237
+ const exported = new Set(targets);
238
+ const queue = [...targets];
239
+ while (queue.length) {
240
+ const target = queue.pop()!;
241
+ for (const barrel of barrelsByTarget.get(target) ?? []) {
242
+ if (exported.has(barrel)) continue;
243
+ exported.add(barrel);
244
+ queue.push(barrel);
245
+ }
246
+ }
247
+ const roots = new Set([...exported, ...ambientRoots]);
248
+ for (const [file, imports] of importsByFile) {
249
+ if ([...imports].some((target) => exported.has(target))) roots.add(file);
250
+ }
251
+ return { roots };
252
+ }