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
@@ -7,10 +7,17 @@ import { registerStandaloneLogSink } from '../src/domains/standalone-log-sink.js
7
7
  import { effectiveInstanceRoot } from '../src/instance/resolve.js';
8
8
  import trackData from './coord-tracks.json';
9
9
 
10
- type Comment = { id: number; body: string; created_at: string; html_url: string };
10
+ export type CoordChannelComment = { id: number; body: string; created_at: string; html_url: string };
11
11
  type Options = { seat: string; channel: string; since?: string; dryRun: boolean; json: boolean };
12
12
  const seatIds = new Set(trackData.tracks.map((track) => track.id));
13
13
 
14
+ /** Shared channel retrieval. Callers validate the complete result before changing a ledger. */
15
+ export function fetchCoordChannelComments(channel: string, since?: string): CoordChannelComment[] {
16
+ const endpoint = `repos/{owner}/{repo}/issues/${channel}/comments${since ? `?since=${encodeURIComponent(since)}` : ''}`;
17
+ const output = execFileSync('gh', ['api', endpoint, '--paginate', '--jq', '.[]'], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
18
+ return output.split('\n').filter(Boolean).map((line) => JSON.parse(line) as CoordChannelComment);
19
+ }
20
+
14
21
  function options(args: string[]): Options {
15
22
  let seat = '';
16
23
  let channel = '23032';
@@ -98,15 +105,13 @@ async function run(args: string[]): Promise<void> {
98
105
  seat = selected.seat;
99
106
  const { channel, since, dryRun, json } = selected;
100
107
  reason = 'gh';
101
- const endpoint = `repos/{owner}/{repo}/issues/${channel}/comments${since ? `?since=${encodeURIComponent(since)}` : ''}`;
102
- const output = execFileSync('gh', ['api', endpoint, '--paginate', '--jq', '.[]'], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
103
- const comments = output.split('\n').filter(Boolean).map((line) => JSON.parse(line) as unknown);
108
+ const comments = fetchCoordChannelComments(channel, since);
104
109
  reason = 'ledger';
105
110
  const path = join(effectiveInstanceRoot(), 'seat-requests', 'requests.jsonl');
106
111
  const seen = existingSeatComments(path);
107
112
  let picked = 0, added = 0, existing = 0;
108
113
  for (const item of comments) {
109
- const comment = item as Partial<Comment>;
114
+ const comment = item as Partial<CoordChannelComment>;
110
115
  if (typeof comment.id !== 'number' || !Number.isSafeInteger(comment.id) || typeof comment.body !== 'string'
111
116
  || typeof comment.created_at !== 'string' || typeof comment.html_url !== 'string') throw new Error('gh api 댓글 필드가 잘못됐다');
112
117
  if (since && Date.parse(comment.created_at) < Date.parse(since)) continue;
@@ -26,6 +26,9 @@ export type MeasuredRun = {
26
26
  fail: number | null;
27
27
  status: 'measured' | 'unmeasurable';
28
28
  reason?: string;
29
+ /** Names observed in Bun's failed-test output. Absent names must not be inferred from the selected files. */
30
+ failedFiles?: string[];
31
+ failedTests?: string[];
29
32
  };
30
33
 
31
34
  export type InterferenceReport = {
@@ -61,6 +64,32 @@ export function parseFailureCount(output: string): number | null {
61
64
  return Number(failures[0]![1]);
62
65
  }
63
66
 
67
+ /** Bun prints a file heading before its `(fail) name` lines. Only retain names actually printed by that run. */
68
+ export function parseFailedTestNames(output: string): { files: string[]; tests: string[] } {
69
+ const files = new Set<string>();
70
+ const tests = new Set<string>();
71
+ let currentFile: string | undefined;
72
+ for (const raw of output.replace(/\x1b\[[0-9;]*m/g, '').split(/\r?\n/)) {
73
+ const line = raw.trim();
74
+ if (/^(?:\d+\s+(?:pass|fail|skip)|Ran\s+\d+\s+tests?|Bun test v)/i.test(line)) { currentFile = undefined; continue; }
75
+ const failure = /^\(fail\)\s+(.+?)(?:\s+\[[^\]]+\])?$/.exec(line);
76
+ if (failure) {
77
+ tests.add(failure[1]!.trim());
78
+ if (currentFile) files.add(currentFile);
79
+ continue;
80
+ }
81
+ const heading = /^(.+):$/.exec(line);
82
+ if (heading) currentFile = /\.(?:test|spec)\.[cm]?[jt]sx?$/.test(heading[1]!) ? heading[1] : undefined;
83
+ }
84
+ return { files: [...files], tests: [...tests] };
85
+ }
86
+
87
+ export function failedNamesFromStreams(result: RunnerResult): { files: string[]; tests: string[] } {
88
+ const stdout = parseFailedTestNames(result.stdout);
89
+ const stderr = parseFailedTestNames(result.stderr);
90
+ return { files: [...new Set([...stdout.files, ...stderr.files])], tests: [...new Set([...stdout.tests, ...stderr.tests])] };
91
+ }
92
+
64
93
  async function measure(files: readonly string[], runner: TestRunner): Promise<MeasuredRun> {
65
94
  try {
66
95
  const result = await runner(files);
@@ -68,9 +97,10 @@ async function measure(files: readonly string[], runner: TestRunner): Promise<Me
68
97
  return { files: [...files], fail: null, status: 'unmeasurable', reason: result.signal ? `runner terminated by ${result.signal}` : 'runner exit code unavailable' };
69
98
  }
70
99
  const fail = parseFailureCount(`${result.stdout}${result.stderr}`);
100
+ const names = fail !== null && fail > 0 ? failedNamesFromStreams(result) : undefined;
71
101
  return fail === null
72
102
  ? { files: [...files], fail: null, status: 'unmeasurable', reason: 'test summary unavailable, ambiguous, or ran zero files' }
73
- : { files: [...files], fail, status: 'measured' };
103
+ : { files: [...files], fail, status: 'measured', ...(names?.files.length ? { failedFiles: names.files } : {}), ...(names?.tests.length ? { failedTests: names.tests } : {}) };
74
104
  } catch (error) {
75
105
  return { files: [...files], fail: null, status: 'unmeasurable', reason: error instanceof Error ? error.message : String(error) };
76
106
  }
@@ -28,7 +28,7 @@ function measure(run: () => Criterion): Criterion {
28
28
  export function suggestExposeRubric(root: string, files?: readonly string[], help?: HelpRunner, rulesPath?: string): Suggestion[] {
29
29
  const names = files ?? readdirSync(join(root, DOCS)).filter((name) => name.endsWith('.md')).map((name) => `${DOCS}/${name}`);
30
30
  const paths = [...new Set(names.map((name) => relative(root, resolve(root, name)).split(sep).join('/')))];
31
- if (paths.some((path) => !/^release\/public\/docs\/[^/]+\.md$/.test(path))) throw new Error('문서 경로는 release/public/docs/*.md 이어야 합니다');
31
+ if (paths.some((path) => !/^(?:release\/public\/docs\/[^/]+|docs\/marketing\/BLOG-[^/]+)\.md$/.test(path))) throw new Error('문서 경로는 release/public/docs/*.md 또는 docs/marketing/BLOG-*.md 이어야 합니다');
32
32
  type HelpResult = ReturnType<HelpRunner> & { failure?: string };
33
33
  let helpRuns = 0;
34
34
  const rootHelp: (args: string[]) => HelpResult = help ?? ((args) => {
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Measures the changed-file gate against the whole program on recently merged PRs.
3
+ * The narrowed run is the gate's default root check (changed .ts ∪ direct importers), so `missed` / `same` judge that mode:
4
+ * the run pins TSC_GATE_ROOTS to a non-`full` value and disables the rollback switch
5
+ * even when the caller's shell requests a full run.
6
+ * `missed > 0` is reverted without a code change by running the gate with env `TSC_GATE_ROOTS=full`.
7
+ */
1
8
  import { spawn } from 'node:child_process';
2
9
  import { mkdtempSync, rmSync } from 'node:fs';
3
10
  import { tmpdir } from 'node:os';
@@ -223,7 +230,9 @@ async function measurePr(repo: string, pr: Pr, run: Runner, platform: NodeJS.Pla
223
230
  const added = compareDiagnostics(current, baseline);
224
231
  if (added.kind === 'unavailable') throw new Error(added.reason);
225
232
  result.addedDiagnostics = added.added;
226
- const gateRun = await timed(run, platform, 'bun', ['scripts/ci-typecheck-changed.ts', '--local'], worktree, { ...process.env, TSC_BASE_REF: base });
233
+ const gateRun = await timed(run, platform, 'bun', ['scripts/ci-typecheck-changed.ts', '--local'], worktree, {
234
+ ...process.env, TSC_BASE_REF: base, TSC_GATE_ROOTS: 'affected', ELANOUS_TSC_AFFECTED_ROOTS: '1',
235
+ });
227
236
  result.narrowed = elapsed(gateRun, platform);
228
237
  result.gateExitCode = gateRun.status;
229
238
  const seen = gateDiagnostics(gateRun, worktree);
@@ -130,7 +130,11 @@ export function runDocs(
130
130
  const SHA = /^[0-9a-f]{40}$/i;
131
131
  const PREFETCH_DEADLINE_MS = 15 * 60_000;
132
132
  type Cut = { version: string; commit: string; manifestHash: string };
133
- type PrefetchCache = Cut & { main: string; tree: string; docs: DocsResult; tui: Record<string, unknown> | null };
133
+ // Two separately published cache files: a finished TUI must survive a docs failure or delay,
134
+ // so it never lives inside the same file as the docs result (should-fix ②).
135
+ type TuiCache = Cut & { tui: Record<string, unknown> };
136
+ type DocsCache = Cut & { main: string; tree: string; docs: DocsResult };
137
+ type PrefetchCache = DocsCache & { tui: Record<string, unknown> | null };
134
138
  type Runner = (command: string, args: string[], cwd?: string, timeout?: number, env?: NodeJS.ProcessEnv) => string;
135
139
  export const commandRunner: Runner = (command, args, cwd, timeout, env) => {
136
140
  const output = spawnSync(command, args, { cwd, env: env ?? process.env, timeout: timeout ?? 15 * 60_000, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
@@ -162,11 +166,27 @@ function claimPrefetch(dir: string, owner: 'worker' | 'docs'): boolean {
162
166
  function cacheFile(dir: string, cut: Cut): string {
163
167
  return join(dir, `${cut.commit}-${cut.manifestHash}.json`);
164
168
  }
165
- function loadCache(dir: string, cut: Cut): PrefetchCache | null {
169
+ function tuiCacheFile(dir: string, cut: Cut): string {
170
+ return join(dir, `${cut.commit}-${cut.manifestHash}.tui.json`);
171
+ }
172
+ function loadTuiCache(dir: string, cut: Cut): TuiCache | null {
173
+ try {
174
+ const value = JSON.parse(readFileSync(tuiCacheFile(dir, cut), 'utf8')) as TuiCache;
175
+ if (value.version !== cut.version || value.commit !== cut.commit || value.manifestHash !== cut.manifestHash) return null;
176
+ return value;
177
+ } catch { return null; }
178
+ }
179
+ /** Publish `data` to `path` atomically (temp + rename), so a reader never sees a partial file. */
180
+ function publish(path: string, data: unknown): void {
181
+ const staged = `${path}.${process.pid}.${randomUUID()}.tmp`;
182
+ writeFileSync(staged, JSON.stringify(data) + '\n');
183
+ try { renameSync(staged, path); } catch (error) { rmSync(staged, { force: true }); throw error; }
184
+ }
185
+ function loadCache(dir: string, cut: Cut): DocsCache | null {
166
186
  try {
167
- const value = JSON.parse(readFileSync(cacheFile(dir, cut), 'utf8')) as PrefetchCache;
187
+ const value = JSON.parse(readFileSync(cacheFile(dir, cut), 'utf8')) as DocsCache;
168
188
  if (value.version !== cut.version || value.commit !== cut.commit || value.manifestHash !== cut.manifestHash
169
- || !SHA.test(value.main) || value.tree !== join(dir, 'tree') || !existsSync(value.tree)
189
+ || !SHA.test(value.main) || dirname(value.tree) !== dir || !/^tree-[0-9a-f-]{36}$/.test(basename(value.tree)) || !existsSync(value.tree)
170
190
  || value.docs?.outcome !== 'ok' || value.docs.version !== cut.version
171
191
  || value.docs.notes !== join(value.tree, 'release/public/docs/releases', `${cut.version}.md`)
172
192
  || value.docs.pages !== join(value.tree, 'website/pages.json')
@@ -191,21 +211,34 @@ export function stagePrefetch(context: GraphContext, env: NodeJS.ProcessEnv = pr
191
211
  const cut = cutFor(context, stateRoot);
192
212
  mkdirSync(dir, { recursive: true });
193
213
  const already = loadCache(dir, cut);
194
- if (already) return already;
195
- const tree = join(dir, 'tree');
214
+ if (already) return { ...already, tui: loadTuiCache(dir, cut)?.tui ?? null };
215
+ // An attempt gets its own path. A failed add can only remove a tree belonging to this attempt,
216
+ // even when an earlier cut or a concurrent worker has a checkout in the same run directory.
217
+ const tree = join(dir, `tree-${randomUUID()}`);
196
218
  let added = false;
219
+ // `git worktree add` can fail AFTER creating the tree directory (e.g. a failed checkout): clean up
220
+ // whenever the add succeeded OR the tree is on disk, so a retry of this run is never blocked (should-fix ①).
221
+ const removeTree = () => {
222
+ if (!(added || existsSync(tree))) return;
223
+ try { runner('git', ['worktree', 'remove', '--force', tree], repo, 60_000); } catch { /* preserve original failure */ }
224
+ // A remove that failed (or could not unregister the tree) must still leave no blocking directory behind.
225
+ if (existsSync(tree)) rmSync(tree, { recursive: true, force: true });
226
+ };
197
227
  try {
198
228
  const main = run('git', ['rev-parse', '--verify', 'origin/main^{commit}'], repo);
199
229
  if (!SHA.test(main)) throw new Error('invalid origin/main commit');
200
- // TUI runs against the cut, on its own isolated worktree, before touching the docs checkout.
230
+ const cachedTui = loadTuiCache(dir, cut);
231
+ // TUI runs against the cut, on its own isolated session, before touching the docs checkout.
201
232
  // tui-sim-node.ts prints the RAW result (no `outcome`); judge it the way node-verdict.ts tui would before reuse.
202
- const tuiData = deps.tui ? deps.tui(cut.commit) : (() => {
233
+ // Publish the reusable TUI in its OWN file the moment it is confirmed — a later docs failure or
234
+ // delay must not throw away a finished TUI (should-fix ②).
235
+ const tui = cachedTui ? cachedTui.tui : reusableTui(withTuiOutcome(0, deps.tui ? deps.tui(cut.commit) : (() => {
203
236
  const tuiRun = spawnSync('bun', ['scripts/release-loop/tui-sim-node.ts', '--commit', cut.commit, '--json'], {
204
237
  cwd: repo, timeout: Math.min(6 * 60_000, left()), encoding: 'utf8', maxBuffer: 16 * 1024 * 1024,
205
238
  });
206
239
  return tuiRun.status === 0 ? lastResult({ status: tuiRun.status, stdout: tuiRun.stdout ?? '', stderr: tuiRun.stderr ?? '' }) ?? null : null;
207
- })();
208
- const tui = reusableTui(withTuiOutcome(0, tuiData), cut.commit);
240
+ })()), cut.commit);
241
+ if (tui && !cachedTui) publish(tuiCacheFile(dir, cut), { ...cut, tui });
209
242
  run('git', ['worktree', 'add', '--detach', tree, main], repo);
210
243
  added = true;
211
244
  run('bun', ['install', '--frozen-lockfile'], tree);
@@ -220,19 +253,14 @@ export function stagePrefetch(context: GraphContext, env: NodeJS.ProcessEnv = pr
220
253
  const finalCut = cutFor(context, stateRoot);
221
254
  if (finalCut.manifestHash !== cut.manifestHash) throw new Error('manifest changed during prefetch');
222
255
  const result: PrefetchCache = { ...cut, main, tree, docs, tui };
223
- const final = cacheFile(dir, cut);
224
- const staged = `${final}.${process.pid}.${randomUUID()}.tmp`;
225
- writeFileSync(staged, JSON.stringify(result) + '\n');
256
+ const docsCache: DocsCache = { ...cut, main, tree, docs };
226
257
  // Publication handshake: whoever creates `claim` first (O_EXCL) wins. The docs node claims before it reads,
227
258
  // so a worker that loses can never publish a cache that a docs attempt would consume.
228
- if (!claimPrefetch(dir, 'worker')) {
229
- rmSync(staged, { force: true });
230
- throw new Error('docs node already ran; late prefetch is discarded');
231
- }
232
- renameSync(staged, final);
259
+ if (!claimPrefetch(dir, 'worker')) throw new Error('docs node already ran; late prefetch is discarded');
260
+ publish(cacheFile(dir, cut), docsCache);
233
261
  return result;
234
262
  } catch (error) {
235
- if (added) { try { runner('git', ['worktree', 'remove', '--force', tree], repo, 60_000); } catch { /* preserve original failure */ } }
263
+ removeTree();
236
264
  throw error;
237
265
  }
238
266
  }
@@ -247,9 +275,10 @@ export function joinTui(context: GraphContext, env: NodeJS.ProcessEnv, repo: str
247
275
  }, stateRoot = effectiveInstanceRoot()): Record<string, unknown> {
248
276
  if (context.outputs.upgrade?.outcome !== 'ok') throw new Error('upgrade did not pass');
249
277
  // Any failure to key the cache (no manifest, cutoff mismatch, no run dir) is a cache miss → original TUI path.
278
+ // The TUI cache file is independent of docs (should-fix ②): a docs failure or delay never blocks this read.
250
279
  let cut: Cut | null = null;
251
- let cached: PrefetchCache | null = null;
252
- try { cut = cutFor(context, stateRoot); cached = loadCache(runCacheDir(env), cut); } catch { /* cache miss */ }
280
+ let cached: TuiCache | null = null;
281
+ try { cut = cutFor(context, stateRoot); cached = loadTuiCache(runCacheDir(env), cut); } catch { /* cache miss */ }
253
282
  const reused = cut && cached?.tui && reusableTui(cached.tui, cut.commit);
254
283
  if (reused) return { ...reused, outcome: 'ok', summary: `tui ${reused.verdict}` };
255
284
  // Preserve node-verdict.ts's exit convention and last-JSON output on a cache miss.
@@ -287,7 +316,7 @@ export function graphDocs(context: GraphContext, env: NodeJS.ProcessEnv, repo: s
287
316
  try {
288
317
  // No fetch (nor any write to remote) until every gate is verified.
289
318
  for (const node of ['gate', 'prepare', 'upgrade', 'tui']) if (context.outputs[node]?.outcome !== 'ok') throw new Error(`${node} did not pass`);
290
- let cached: PrefetchCache | null = null;
319
+ let cached: DocsCache | null = null;
291
320
  // A cache key that cannot be computed is a miss — the original fresh-worktree path runs.
292
321
  if (mayConsume && cacheDir) { try { const cut = cutFor(context, stateRoot); cached = loadCache(cacheDir, cut); } catch { cached = null; } }
293
322
  const branch = `release-docs/${version}`;
@@ -48,11 +48,14 @@ export function runExportCheck(run: CommandRunner = runCommand, repoRoot = proce
48
48
  const publicDir = join(out, 'export');
49
49
  symlinkSync(join(modulesRoot, 'node_modules'), join(publicDir, 'node_modules'), 'dir');
50
50
  const pwaModules = join(modulesRoot, 'apps', 'pwa', 'node_modules');
51
- if (existsSync(pwaModules) && existsSync(join(publicDir, 'apps', 'pwa'))) symlinkSync(pwaModules, join(publicDir, 'apps', 'pwa', 'node_modules'), 'dir');
52
- const built = run('bun', ['bin/elanous.mjs', 'nexus', 'build'], publicDir);
53
- result = built.status === 0 && existsSync(join(publicDir, 'apps', 'pwa', 'out', 'index.html'))
54
- ? { outcome: 'ok', verdict: 'pass', summary: `public export and PWA build ${commit.slice(0, 12)}`, step: 'build', tail: '' }
55
- : failed('build', 'PWA build or out/index.html missing', built);
51
+ if (!existsSync(pwaModules)) result = failed('build', 'PWA dependencies missing; run bun install --cwd apps/pwa in the release-loop repository');
52
+ else {
53
+ if (existsSync(join(publicDir, 'apps', 'pwa'))) symlinkSync(pwaModules, join(publicDir, 'apps', 'pwa', 'node_modules'), 'dir');
54
+ const built = run('bun', ['bin/elanous.mjs', 'nexus', 'build'], publicDir);
55
+ result = built.status === 0 && existsSync(join(publicDir, 'apps', 'pwa', 'out', 'index.html'))
56
+ ? { outcome: 'ok', verdict: 'pass', summary: `public export and PWA build ${commit.slice(0, 12)}`, step: 'build', tail: '' }
57
+ : failed('build', 'PWA build or out/index.html missing', built);
58
+ }
56
59
  }
57
60
  }
58
61
  }
@@ -117,6 +117,8 @@ export interface GateResult {
117
117
  const sha = /^[0-9a-f]{7,40}$/i;
118
118
  const versionPattern = /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/;
119
119
  const failureFile = (root: string, version: string) => join(root, 'release', version, 'gate-failures.json');
120
+ /** Judged identities live beside the machine release ledger; gate-failures.json keeps its raw baseline schema. */
121
+ const verdictFile = (root: string, version: string) => join(root, 'release', version, 'gate-verdict.json');
120
122
  const quote = (s: string) => `'${s.replaceAll("'", "'\\''")}'`;
121
123
  const defaultRemoteMirror = '~/mirror/elanous-agent.git';
122
124
 
@@ -1431,6 +1433,8 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
1431
1433
  };
1432
1434
  const repo = resolve(opts.repo ?? process.cwd());
1433
1435
  const result: GateResult = { outcome: 'error', commit: opts.commit, introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], rcNonzeroNoFailures: [], durationMs: 0 };
1436
+ const preexistingIds: string[] = [];
1437
+ let knownEnvIds: string[] = [];
1434
1438
  let work: string | undefined;
1435
1439
  let baseSnapshot = false;
1436
1440
  let cutFailures: SweepFailures | undefined;
@@ -1598,11 +1602,13 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
1598
1602
  const baselineIds = [...baseline.failures, ...baseline.errors];
1599
1603
  const countedCut = splitKnownEnv(cutIds, known);
1600
1604
  const countedBaseline = splitKnownEnv(baselineIds, known);
1601
- result.knownEnv = countedCut.knownEnv.length;
1605
+ knownEnvIds = countedCut.knownEnv;
1606
+ result.knownEnv = knownEnvIds.length;
1602
1607
  result.knownEnvCleared = [...new Set(cutRun.passedIds ?? [])].filter((id) => known.has(id) && !cutIds.includes(id) && !cutRun.stalledEnv?.some(({ file }) => fileOf(id) === file)).sort();
1603
1608
  const diff = diffFailures(countedCut.counted, countedBaseline.counted);
1604
1609
  result.fixed = result.baselineDeferred ? null : diff.fixed.length;
1605
- result.preexisting = diff.common.length;
1610
+ preexistingIds.push(...diff.common);
1611
+ result.preexisting = preexistingIds.length;
1606
1612
  // GATE-BASELINE-CACHE — only the files whose new failures reproduce in isolation reach the baseline, and each
1607
1613
  // (commit, file, host) is run at most once across gates: this gate's cut results also seed the next gate's baseline.
1608
1614
  const cacheHost = opts.remote ?? 'local';
@@ -1703,10 +1709,11 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
1703
1709
  debug.log('release-loop.gate', 'isolated-rc-nonzero-no-failures', { file, label: 'cut isolated', rc: rcOnly.rc, pass: rcOnly.pass, baseline });
1704
1710
  }
1705
1711
  for (const id of candidates) {
1706
- if (oldFailures.has(id)) result.preexisting++;
1712
+ if (oldFailures.has(id)) preexistingIds.push(id);
1707
1713
  else result.introduced.push(id);
1708
1714
  }
1709
1715
  }
1716
+ result.preexisting = preexistingIds.length;
1710
1717
  result.baselineCache = cacheStats;
1711
1718
  debug.log('release.gate', 'baseline-cache', { hits: cacheStats.hits, misses: cacheStats.misses, commit: baseSha });
1712
1719
  result.outcome = result.introduced.length ? 'regression' : 'ok';
@@ -1751,7 +1758,33 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
1751
1758
  result.error = `baseline persistence: ${String(error)}`;
1752
1759
  }
1753
1760
  }
1754
- if (result.outcome !== 'error') debug.log('release-loop.gate', 'judged', { version: opts.version, commit: opts.commit, introduced: result.introduced.length, preexisting: result.preexisting });
1761
+ if (result.outcome !== 'error') {
1762
+ const ids = {
1763
+ introduced: [...result.introduced].sort(),
1764
+ knownEnv: [...knownEnvIds].sort(),
1765
+ preexisting: [...preexistingIds].sort(),
1766
+ loadFlaky: [...(result.loadFlaky ?? [])].sort(),
1767
+ };
1768
+ try {
1769
+ const path = verdictFile(ledger, opts.version);
1770
+ mkdirSync(dirname(path), { recursive: true });
1771
+ const temp = join(dirname(path), `.gate-verdict-${process.pid}-${randomUUID()}.tmp`);
1772
+ try {
1773
+ writeFileSync(temp, JSON.stringify({ version: opts.version, commit: opts.commit, outcome: result.outcome, ...ids }, null, 2) + '\n', { mode: 0o600 });
1774
+ chmodSync(temp, 0o600);
1775
+ renameSync(temp, path);
1776
+ } finally { if (existsSync(temp)) rmSync(temp); }
1777
+ } catch (error) {
1778
+ result.outcome = 'error';
1779
+ result.error = `verdict persistence: ${String(error)}`;
1780
+ }
1781
+ if (result.outcome !== 'error') debug.log('release-loop.gate', 'judged', {
1782
+ version: opts.version, commit: opts.commit,
1783
+ introduced: result.introduced.length, preexisting: result.preexisting,
1784
+ knownEnv: result.knownEnv, loadFlaky: ids.loadFlaky.length,
1785
+ introducedIds: ids.introduced, knownEnvIds: ids.knownEnv, preexistingIds: ids.preexisting, loadFlakyIds: ids.loadFlaky,
1786
+ });
1787
+ }
1755
1788
  // GATE-LIVE-OBS-RC-WORDING ⓒ — the shards board says «판정 전» until this lands; then «새 실패 N» only (UX · gate-shards.ts).
1756
1789
  // Same ledger as the board's own writes (sweep's shardsFile). A write failure is logged and never changes the verdict.
1757
1790
  if (result.outcome !== 'error' && opts.pod) {
@@ -7,6 +7,7 @@ import { debug } from '../../src/debug/log.js';
7
7
  export type NodeOutcome = 'ok' | 'fail' | 'error';
8
8
  export type NodeVerdict = 'pass' | 'flaky' | 'fail';
9
9
  export interface GraphContext {
10
+ nodeId?: string;
10
11
  input: { version: string; previousVersion: string; [key: string]: unknown };
11
12
  outputs: Record<string, Record<string, unknown>>;
12
13
  }
@@ -20,7 +21,8 @@ export function readGraphContext(env: NodeJS.ProcessEnv = process.env): GraphCon
20
21
  const input = context.input as Record<string, unknown> | undefined;
21
22
  if (!input || typeof input.version !== 'string' || !isReleaseVersion(input.version)
22
23
  || typeof input.previousVersion !== 'string' || !/^\d+\.\d+\.\d+$/.test(input.previousVersion)) throw new Error('version and previousVersion required');
23
- return { input: input as GraphContext['input'], outputs: context.outputs && typeof context.outputs === 'object' ? context.outputs as GraphContext['outputs'] : {} };
24
+ return { ...(typeof context.nodeId === 'string' ? { nodeId: context.nodeId } : {}),
25
+ input: input as GraphContext['input'], outputs: context.outputs && typeof context.outputs === 'object' ? context.outputs as GraphContext['outputs'] : {} };
24
26
  }
25
27
 
26
28
  export function nodeOutput(context: GraphContext, node: string, key: string): string {
@@ -48,9 +50,14 @@ export function lastResult(run: CommandResult): Record<string, unknown> | undefi
48
50
  } catch { return undefined; }
49
51
  }
50
52
 
51
- export function finishNode(node: string, version: string, result: Record<string, unknown> & { outcome: NodeOutcome; verdict: NodeVerdict; summary: string }): number {
53
+ export function finishNode(node: string, version: string, result: Record<string, unknown> & { outcome: NodeOutcome; verdict: NodeVerdict; summary: string; tail?: string }): number {
52
54
  debug.log(`release-loop.${node}`, 'result', { version, outcome: result.outcome });
53
- emitNodeResult(result);
55
+ if (result.outcome === 'fail') {
56
+ const summary = result.summary.replace(/[\r\n]+/g, ' ').trim();
57
+ const tail = result.tail?.trim() ? result.tail : summary;
58
+ debug.log('release.node', 'failed', { node, summary, tail });
59
+ emitNodeResult({ ...result, summary, tail });
60
+ } else emitNodeResult(result);
54
61
  return result.outcome === 'ok' ? 0 : result.outcome === 'fail' ? 1 : 2;
55
62
  }
56
63
 
@@ -48,6 +48,9 @@ export function waitForAssets(run: CommandRunner, repo: string, tag: string, nam
48
48
  export function runPublish(run: CommandRunner = runCommand, prodFreezeRoot?: string,
49
49
  clock: { now: () => number; sleep: (ms: number) => void } = { now: Date.now, sleep: Bun.sleepSync }) {
50
50
  const context = readGraphContext();
51
+ // A resumed graph once invoked this irreversible node at npm-publish, docs-land, and failed.
52
+ // The runner's context is the authority: only the publish visit may publish.
53
+ if (context.nodeId !== 'publish') throw new Error(`publish node context mismatch: expected publish, got ${context.nodeId ?? 'missing'}`);
51
54
  const version = context.input.version;
52
55
  // A measured automatic decision or the existing human approval must authorize publication.
53
56
  const auto = context.outputs['auto-approve'];
@@ -11,6 +11,9 @@ export interface TuiScreens {
11
11
  export interface TuiCheck { id: string; pass: boolean; detail: string }
12
12
 
13
13
  const ERROR_TEXT = /\b(TypeError|ReferenceError|SyntaxError|Unhandled|panic|Segmentation fault)\b|Error: /;
14
+ // The startup band can say "Commands — /help lists every command" (#26954).
15
+ // Only the modal's title proves that the help overlay itself remains on screen.
16
+ const HELP_MODAL_TITLE = 'Dashboard help';
14
17
 
15
18
  export function tuiChecks(s: TuiScreens, readyLimitMs = 60_000): TuiCheck[] {
16
19
  const all = [s.first, s.help, s.afterEsc].join('\n');
@@ -18,8 +21,8 @@ export function tuiChecks(s: TuiScreens, readyLimitMs = 60_000): TuiCheck[] {
18
21
  { id: 'boot', pass: s.readyMs !== null && s.readyMs <= readyLimitMs, detail: s.readyMs === null ? 'never ready' : `ready in ${s.readyMs}ms` },
19
22
  { id: 'prompt', pass: s.first.includes('❯'), detail: 'input line (❯) on the first screen' },
20
23
  { id: 'status-bar', pass: /│.*ctx \d+/.test(s.first), detail: 'status bar with model and ctx' },
21
- { id: 'help-opens', pass: s.help.includes('help') && s.help.includes('Commands') && s.help.includes('/help'), detail: '/help shows the help overlay' },
22
- { id: 'help-closes', pass: !s.afterEsc.includes('Commands') && s.afterEsc.includes('❯'), detail: 'Esc closes the overlay' },
24
+ { id: 'help-opens', pass: s.help.includes(HELP_MODAL_TITLE) && s.help.includes('Commands') && s.help.includes('/help'), detail: '/help shows the help overlay' },
25
+ { id: 'help-closes', pass: !s.afterEsc.includes(HELP_MODAL_TITLE) && s.afterEsc.includes('❯'), detail: 'Esc closes the overlay' },
23
26
  { id: 'no-error-text', pass: !ERROR_TEXT.test(all), detail: 'no error text on any screen' },
24
27
  ];
25
28
  }
@@ -17,7 +17,9 @@ export interface PublicCheckReport {
17
17
  readonly ok: boolean;
18
18
  }
19
19
 
20
- const PERSONAL_OR_INTERNAL = /\/Users\/|~\/source(?:\/|\b)|\b(?:INTERNAL_ONLY|ELANOUS_INTERNAL|PRIVATE_INTERNAL|internal-only)\b|@internal\b|내부\s*전용/iu;
20
+ // 개인 경로(`/Users/`·`~/source`)는 대소문자를 가리고, 내부 표지는 기존처럼 `iu`.
21
+ const PERSONAL_PATH = /\/Users\/|~\/source(?:\/|\b)/u;
22
+ const INTERNAL_MARKER = /\b(?:INTERNAL_ONLY|ELANOUS_INTERNAL|PRIVATE_INTERNAL|internal-only)\b|@internal\b|내부\s*전용/iu;
21
23
  const EXECUTABLE = /\.(?:ts|tsx|js|mjs|cjs|py|sh|bash)$/iu;
22
24
  const ENTRY_DIR = /^(?:scripts|src|bin)\//u;
23
25
  const NON_ENTRY = /(?:\.d\.ts|\.(?:test|spec)\.[^.]+)$/iu;
@@ -165,7 +167,8 @@ export function checkPublicSkills(root: string, boundary: SkillBoundaryReport =
165
167
  const bytes = readFileSync(file);
166
168
  if (bytes.includes(0)) continue;
167
169
  try {
168
- if (PERSONAL_OR_INTERNAL.test(new TextDecoder('utf-8', { fatal: true }).decode(bytes))) matches.push(relative(root, file));
170
+ const decoded = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
171
+ if (PERSONAL_PATH.test(decoded) || INTERNAL_MARKER.test(decoded)) matches.push(relative(root, file));
169
172
  } catch {
170
173
  // Binary assets are not text; extensionless and dotfiles are checked when decodable.
171
174
  }