elanous 0.2.16 → 0.2.18

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 (354) 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/P6YTVgzuZz5oTZhY4Din_/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{1307-b46d45b705e8c9ae.js → 1307-30dff22c9e1f3073.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{6158.9c0449a9a8d0064b.js → 6158.af2e93b7adff709b.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d92f7ee3994e84c7.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-3cb52e5ea8fc2fa5.js → page-4733c967b56a4277.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/board/page-d22fd849cc22f104.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c1e20d7d338713f1.js → page-bb742f995c6769e7.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/bots/{page-482c5dbde699947c.js → page-02b073a2ac793b83.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/control/{page-a0e2a6d1bfb52fc5.js → page-308111abadf35670.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-c53598cfce6156f2.js → page-5d97b2901e0d1ea6.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/editor/{page-13a63953f53c6a23.js → page-c39554c321576982.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/exec/{page-5d3c8429d04f3831.js → page-8c3d8e63ae2e1c98.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/field/{page-3193bdc0bbab047a.js → page-9b72a07d424701c6.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/intake/{page-6107ca8ddd5e3bdd.js → page-a572f58cd3ca5864.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/market/page-fa244bd146a00ae8.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-44d56d6391965a6a.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/morning/{page-4167348049166852.js → page-83a700d4c754d802.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/{not-found-de52e8fdf9095f7a.js → not-found-7b3170f374f77b9d.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-ca3952cd78a83ca2.js → page-a3c58f5c72a5f3f9.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1ebfe049c640e171.js → page-1467047104141297.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-abe8e3b74bea1ffc.js → page-44d5f767491692bf.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/share/{page-d28573d6792b4275.js → page-3cc47dca0922ab9a.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-d751de5f8062ff5e.js → page-b17cd46cf0e9b8cf.js} +1 -1
  58. package/apps/pwa/out/_next/static/chunks/app/term/{page-fe1759f94003b32b.js → page-61553851aa606146.js} +1 -1
  59. package/apps/pwa/out/_next/static/chunks/app/today/{page-d64c984e855c8c68.js → page-a1f585e154e61c59.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/app/trace/page-7bf54c9bb9274af8.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/vault/{page-9afdee66b820c76e.js → page-5150ef17b5383a57.js} +1 -1
  62. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-3544f154c87b43ee.js → page-134da468538baf48.js} +1 -1
  65. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/{webpack-539cde99554be20f.js → webpack-c2782b330447ffb6.js} +1 -1
  67. package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +1 -0
  68. package/apps/pwa/out/approvals/index.html +1 -1
  69. package/apps/pwa/out/approvals/index.txt +14 -14
  70. package/apps/pwa/out/autopilot/index.html +1 -1
  71. package/apps/pwa/out/autopilot/index.txt +14 -14
  72. package/apps/pwa/out/board/index.html +1 -1
  73. package/apps/pwa/out/board/index.txt +14 -14
  74. package/apps/pwa/out/botlab/index.html +1 -1
  75. package/apps/pwa/out/botlab/index.txt +14 -14
  76. package/apps/pwa/out/bots/index.html +1 -1
  77. package/apps/pwa/out/bots/index.txt +14 -14
  78. package/apps/pwa/out/ceo/index.html +1 -1
  79. package/apps/pwa/out/ceo/index.txt +14 -14
  80. package/apps/pwa/out/chat/index.html +1 -1
  81. package/apps/pwa/out/chat/index.txt +14 -14
  82. package/apps/pwa/out/consult/index.html +1 -1
  83. package/apps/pwa/out/consult/index.txt +14 -14
  84. package/apps/pwa/out/control/index.html +1 -1
  85. package/apps/pwa/out/control/index.txt +14 -14
  86. package/apps/pwa/out/decisions/index.html +1 -0
  87. package/apps/pwa/out/decisions/index.txt +30 -0
  88. package/apps/pwa/out/design-check/index.html +1 -1
  89. package/apps/pwa/out/design-check/index.txt +14 -14
  90. package/apps/pwa/out/editor/index.html +1 -1
  91. package/apps/pwa/out/editor/index.txt +14 -14
  92. package/apps/pwa/out/exec/index.html +1 -1
  93. package/apps/pwa/out/exec/index.txt +14 -14
  94. package/apps/pwa/out/field/index.html +1 -1
  95. package/apps/pwa/out/field/index.txt +14 -14
  96. package/apps/pwa/out/healthz +1 -1
  97. package/apps/pwa/out/index.html +1 -1
  98. package/apps/pwa/out/index.txt +14 -14
  99. package/apps/pwa/out/inside/index.html +1 -1
  100. package/apps/pwa/out/inside/index.txt +14 -14
  101. package/apps/pwa/out/intake/index.html +1 -1
  102. package/apps/pwa/out/intake/index.txt +14 -14
  103. package/apps/pwa/out/live/index.html +1 -1
  104. package/apps/pwa/out/live/index.txt +14 -14
  105. package/apps/pwa/out/loops/index.html +1 -1
  106. package/apps/pwa/out/loops/index.txt +14 -14
  107. package/apps/pwa/out/market/index.html +1 -1
  108. package/apps/pwa/out/market/index.txt +14 -14
  109. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  110. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  111. package/apps/pwa/out/morning/index.html +1 -1
  112. package/apps/pwa/out/morning/index.txt +14 -14
  113. package/apps/pwa/out/observatory/index.html +1 -1
  114. package/apps/pwa/out/observatory/index.txt +14 -14
  115. package/apps/pwa/out/ops/checklist/index.html +1 -1
  116. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  117. package/apps/pwa/out/ops/release/index.html +1 -1
  118. package/apps/pwa/out/ops/release/index.txt +14 -14
  119. package/apps/pwa/out/ops/seats/index.html +1 -1
  120. package/apps/pwa/out/ops/seats/index.txt +14 -14
  121. package/apps/pwa/out/outputs/index.html +1 -1
  122. package/apps/pwa/out/outputs/index.txt +14 -14
  123. package/apps/pwa/out/reflection/index.html +1 -1
  124. package/apps/pwa/out/reflection/index.txt +15 -15
  125. package/apps/pwa/out/scheduler/index.html +1 -1
  126. package/apps/pwa/out/scheduler/index.txt +14 -14
  127. package/apps/pwa/out/sessions/index.html +1 -1
  128. package/apps/pwa/out/sessions/index.txt +14 -14
  129. package/apps/pwa/out/settings/devices/index.html +1 -1
  130. package/apps/pwa/out/settings/devices/index.txt +14 -14
  131. package/apps/pwa/out/settings/index.html +1 -1
  132. package/apps/pwa/out/settings/index.txt +14 -14
  133. package/apps/pwa/out/setup/done/index.html +1 -1
  134. package/apps/pwa/out/setup/done/index.txt +14 -14
  135. package/apps/pwa/out/setup/index.html +1 -1
  136. package/apps/pwa/out/setup/index.txt +14 -14
  137. package/apps/pwa/out/share/index.html +1 -1
  138. package/apps/pwa/out/share/index.txt +14 -14
  139. package/apps/pwa/out/showroom/index.html +1 -1
  140. package/apps/pwa/out/showroom/index.txt +14 -14
  141. package/apps/pwa/out/tasks/index.html +1 -1
  142. package/apps/pwa/out/tasks/index.txt +14 -14
  143. package/apps/pwa/out/term/index.html +1 -1
  144. package/apps/pwa/out/term/index.txt +14 -14
  145. package/apps/pwa/out/today/index.html +1 -1
  146. package/apps/pwa/out/today/index.txt +14 -14
  147. package/apps/pwa/out/trace/index.html +1 -1
  148. package/apps/pwa/out/trace/index.txt +14 -14
  149. package/apps/pwa/out/vault/index.html +1 -1
  150. package/apps/pwa/out/vault/index.txt +14 -14
  151. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  152. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  153. package/apps/pwa/out/workflows/index.html +1 -1
  154. package/apps/pwa/out/workflows/index.txt +14 -14
  155. package/apps/pwa/out/workspace/index.html +1 -1
  156. package/apps/pwa/out/workspace/index.txt +14 -14
  157. package/apps/pwa/out/worktrees/index.html +1 -1
  158. package/apps/pwa/out/worktrees/index.txt +14 -14
  159. package/graphs/intake/intake-daily.yaml +9 -2
  160. package/graphs/intake/news-sources.yaml +4 -0
  161. package/graphs/intake/recipes.yaml +6 -1
  162. package/package.json +2 -1
  163. package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
  164. package/samples/workflows/templates/chat-driven-research.yaml +35 -0
  165. package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
  166. package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
  167. package/samples/workflows/templates/manual-quickstart.yaml +21 -0
  168. package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
  169. package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
  170. package/scripts/__fixtures__/intake-news.xml +7 -0
  171. package/scripts/ci-public-export-import-gate.ts +7 -2
  172. package/scripts/ci-typecheck-changed.ts +21 -6
  173. package/scripts/discord-seat-probe.ts +3 -2
  174. package/scripts/docs/generate-cli-anatomy.ts +162 -0
  175. package/scripts/goals/authoring-metrics.ts +73 -0
  176. package/scripts/intake-news.ts +169 -0
  177. package/scripts/landing-heal/must-fix-match.ts +7 -7
  178. package/scripts/landing-heal/run-mission.ts +3 -2
  179. package/scripts/public-export-test-run.ts +37 -2
  180. package/scripts/release-loop/checklist-gate-node.ts +13 -11
  181. package/scripts/release-loop/cut-branch.ts +2 -1
  182. package/scripts/release-loop/cutoff.ts +4 -2
  183. package/scripts/release-loop/docs-land-node.ts +77 -11
  184. package/scripts/release-loop/docs-node.ts +1 -1
  185. package/scripts/release-loop/gate-node.ts +99 -14
  186. package/scripts/release-loop/node-verdict.ts +2 -1
  187. package/scripts/release-loop/npm-publish-node.ts +11 -7
  188. package/scripts/release-loop/ops-upgrade-node.ts +8 -1
  189. package/scripts/release-loop/preflight.ts +246 -0
  190. package/scripts/release-loop/publish-node.ts +10 -3
  191. package/scripts/release-loop/release-docs.ts +134 -0
  192. package/scripts/release-loop/release-readiness.ts +11 -6
  193. package/scripts/release-loop/release-version.ts +48 -0
  194. package/scripts/release-loop/resume-release.ts +115 -0
  195. package/scripts/release-loop/unattended-release.ts +43 -7
  196. package/scripts/release-loop/version-node.ts +149 -41
  197. package/scripts/release-story/draft.ts +42 -14
  198. package/scripts/rhythm/daily.ts +25 -4
  199. package/scripts/skills/public-check.ts +62 -12
  200. package/scripts/test-deterministic.ts +51 -33
  201. package/scripts/test-diet/effectiveness.ts +62 -1
  202. package/scripts/test-diet/lib.ts +1 -1
  203. package/scripts/test-diet/node.ts +8 -1
  204. package/src/agent-mission/driver.ts +53 -10
  205. package/src/agent-mission/review-loop.ts +4 -3
  206. package/src/agent-substrate/pr-comment-meta.ts +20 -0
  207. package/src/autopilot/build/llm-conflict-merge.ts +28 -8
  208. package/src/autopilot/track-agent.ts +5 -3
  209. package/src/briefing/brief-items.ts +86 -17
  210. package/src/cli/autopilot-cli.ts +86 -1
  211. package/src/cli/brief-cli.ts +36 -2
  212. package/src/cli/decisions-cli.ts +4 -1
  213. package/src/cli/freeze-cli.ts +3 -2
  214. package/src/cli/intake-cli.ts +74 -2
  215. package/src/cli/log-event-names.ts +1 -0
  216. package/src/cli/notify-cli.ts +75 -0
  217. package/src/cli/pr-cli.ts +12 -1
  218. package/src/cli/provider-cli.ts +24 -0
  219. package/src/cli/release-cli.ts +101 -17
  220. package/src/cli/self-update-canary.ts +46 -0
  221. package/src/cli/self-update.ts +200 -1
  222. package/src/cli/tasks-cli.ts +309 -3
  223. package/src/context-bus/condense.ts +19 -3
  224. package/src/context-bus/context-day.ts +77 -24
  225. package/src/context-bus/context-first.ts +33 -0
  226. package/src/context-card/index.ts +45 -0
  227. package/src/context-card/task.ts +76 -0
  228. package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
  229. package/src/decisions/decision-ledger.ts +10 -1
  230. package/src/discord-self-message.ts +20 -2
  231. package/src/discord-slash-wire.ts +1 -0
  232. package/src/domains/outbound-alert.ts +200 -27
  233. package/src/domains/outbound-log-probe.ts +16 -1
  234. package/src/domains/release-tool.ts +2 -2
  235. package/src/execution-loop/gates.ts +11 -2
  236. package/src/flow-loop/split.ts +8 -4
  237. package/src/git-fs/worktree.ts +23 -3
  238. package/src/harness/harness-cli-command.ts +151 -11
  239. package/src/harness/harness-pod-dispatch.ts +11 -0
  240. package/src/harness/harness-queue.ts +466 -32
  241. package/src/harness/helper-scan.ts +10 -3
  242. package/src/harness/nested-elanous-policy.ts +23 -4
  243. package/src/hq/fence-wrapper.ts +123 -0
  244. package/src/hq/hq.ts +8 -0
  245. package/src/hq/seats.ts +369 -0
  246. package/src/index.ts +190 -23
  247. package/src/intake-plane/adopt-gate.ts +49 -0
  248. package/src/intake-plane/check.ts +3 -0
  249. package/src/intake-plane/collect-github.ts +57 -25
  250. package/src/intake-plane/digest.ts +39 -4
  251. package/src/intake-plane/discord-seat-work.ts +8 -2
  252. package/src/intake-plane/requirement-funnel.ts +316 -0
  253. package/src/knowledge/know-find.ts +10 -1
  254. package/src/loops/budget.ts +36 -0
  255. package/src/loops/orchestrator/finish-rate.ts +73 -1
  256. package/src/loops/orchestrator/grid-place.ts +11 -0
  257. package/src/loops/orchestrator/tick.ts +295 -20
  258. package/src/maturity/feature-maturity.ts +3 -2
  259. package/src/nexus/api/decisions-route.ts +11 -1
  260. package/src/nexus/api/grid.ts +61 -0
  261. package/src/nexus/api/http-server.ts +19 -1
  262. package/src/nexus/api/loop-edges.ts +69 -13
  263. package/src/nexus/api/ops-seats-sources.ts +2 -2
  264. package/src/nexus/api/task-cards-api.ts +19 -0
  265. package/src/nexus/api/workflow-templates.ts +3 -0
  266. package/src/oauth/codex.ts +120 -2
  267. package/src/onboarding/first-chat-gate.ts +29 -0
  268. package/src/release-loop/checklist.ts +104 -6
  269. package/src/release-loop/feature-store.ts +10 -9
  270. package/src/release-loop/landing-freeze.ts +24 -14
  271. package/src/release-loop/placement.ts +13 -3
  272. package/src/release-loop/rubric.ts +80 -0
  273. package/src/seat-dispatch/seat-ask.ts +79 -16
  274. package/src/seat-loop/seat-loop.ts +107 -37
  275. package/src/self-dev/ask-launch-flow.ts +50 -10
  276. package/src/self-dev/ask-path-candidates.ts +68 -0
  277. package/src/self-dev/dev-pipeline.ts +4 -0
  278. package/src/self-dev/draft-triage-rules.ts +1 -1
  279. package/src/self-dev/orchestrate-cli.ts +22 -0
  280. package/src/self-dev/round-pr-supersede.ts +244 -0
  281. package/src/self-dev/run-store.ts +5 -1
  282. package/src/self-dev/run-supervisor.ts +21 -1
  283. package/src/self-dev/supervisor-verdict-edges.ts +46 -0
  284. package/src/self-implement/follow-up-goals.ts +2 -2
  285. package/src/self-implement/frozen-merges.ts +25 -20
  286. package/src/self-implement/gate-remote.ts +427 -0
  287. package/src/self-implement/goal-author-clarification.ts +81 -0
  288. package/src/self-implement/goal-author-cli.ts +33 -5
  289. package/src/self-implement/goal-author.ts +93 -12
  290. package/src/self-implement/goal-clarification-escalation.ts +37 -1
  291. package/src/self-implement/goal-document-write.ts +28 -0
  292. package/src/self-implement/heal-intake.ts +4 -0
  293. package/src/self-implement/host-regate.ts +3 -1
  294. package/src/self-implement/l8-merge-queue.ts +29 -5
  295. package/src/self-implement/orchestrator.ts +78 -13
  296. package/src/self-implement/prior-draft-findings.ts +4 -3
  297. package/src/skills/skill-feedback.ts +76 -0
  298. package/src/task-agent/actions.ts +151 -0
  299. package/src/task-agent/judge.ts +122 -0
  300. package/src/task-agent/mission.ts +423 -0
  301. package/src/task-agent/shadow.ts +90 -0
  302. package/src/task-agent/task-hand.ts +235 -0
  303. package/src/task-cards/card-store.ts +4 -1
  304. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +43 -4
  305. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  306. package/src/task-orchestrator/surfaces/pod-pool.ts +81 -13
  307. package/src/task-orchestrator/surfaces/self-implement-pod.ts +225 -35
  308. package/src/telegram-commands.ts +1 -1
  309. package/src/telegram-loops-command.ts +22 -15
  310. package/src/telegram.ts +5 -20
  311. package/src/user-config.ts +86 -4
  312. package/src/version/packed-revision.json +1 -1
  313. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/chat/page-8c2d9526164f5d09.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/layout-dce686e582c23937.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/loops/page-1a61c7ca1ceb3ae7.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/market/page-51f811fa119308b6.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-5b042736aa113756.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-57924393e3e4e539.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/settings/page-0f5cd57ca928f42b.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/showroom/page-59f03fda76c03d22.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/workflows/page-1ea64acadd704ace.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +0 -1
  352. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
  353. package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +0 -1
  354. /package/apps/pwa/out/_next/static/{rv9cierV0jSOvDBm1g0XB → P6YTVgzuZz5oTZhY4Din_}/_ssgManifest.js +0 -0
@@ -0,0 +1,427 @@
1
+ /** GATE-REMOTE (0.2.18 · 10-06) — run a heavy pre-landing check (self gate · changed-file tsc · public-export test run)
2
+ * on a remote host at the SAME commit and bring back only its result (exit code · stdout · stderr).
3
+ * Reach: the release gate's bare mirror (`~/mirror/elanous-agent.git`, refs/elanous/gate/<sha>) — copied minimally from
4
+ * scripts/release-loop/gate-node.ts (RELGUARD path, not edited); gate-node could later switch to this module. */
5
+ import { spawnSync } from 'node:child_process';
6
+ import { existsSync, lstatSync, realpathSync } from 'node:fs';
7
+ import { loadavg } from 'node:os';
8
+ import { isAbsolute, join, relative, resolve, sep } from 'node:path';
9
+ import { debug } from '../debug/log.js';
10
+ import type { GateRemoteConfig } from '../user-config.js';
11
+
12
+ export type GateRemoteTool = 'self-gate' | 'ci-typecheck-changed' | 'public-export-test-run';
13
+
14
+ export interface GateRemoteSettings {
15
+ enabled: boolean;
16
+ host: string;
17
+ loadThreshold: number;
18
+ hostCap: number;
19
+ mirror: string;
20
+ slotWaitSeconds: number;
21
+ }
22
+
23
+ export const MAX_HOST_CAP = 2;
24
+
25
+ export const GATE_REMOTE_DEFAULTS: GateRemoteSettings = {
26
+ enabled: true, host: 'node-b', loadThreshold: 20, hostCap: 2, mirror: '~/mirror/elanous-agent.git', slotWaitSeconds: 600,
27
+ };
28
+
29
+ const hostPattern = /^(?:[\w.-]+@)?[\w.-]+$/;
30
+ export const isValidGateHost = (host: string): boolean => hostPattern.test(host) && !host.startsWith('-');
31
+ const mirrorPattern = /^(?:\/|~\/)[\w./-]+$/;
32
+ const shaPattern = /^[0-9a-f]{40}$/;
33
+
34
+ /** config > env > default (user-config hierarchy). Env: ELANOUS_GATE_REMOTE=off|on · ELANOUS_GATE_REMOTE_HOST · ELANOUS_GATE_REMOTE_LOAD. */
35
+ export function resolveGateRemoteSettings(config: GateRemoteConfig | undefined, env: NodeJS.ProcessEnv = process.env): GateRemoteSettings {
36
+ const envEnabled = env.ELANOUS_GATE_REMOTE === 'off' || env.ELANOUS_GATE_REMOTE === '0' ? false
37
+ : env.ELANOUS_GATE_REMOTE === 'on' || env.ELANOUS_GATE_REMOTE === '1' ? true : undefined;
38
+ const envLoadRaw = env.ELANOUS_GATE_REMOTE_LOAD && /^\d+(?:\.\d+)?$/.test(env.ELANOUS_GATE_REMOTE_LOAD) ? Number(env.ELANOUS_GATE_REMOTE_LOAD) : undefined;
39
+ const envLoad = envLoadRaw !== undefined && Number.isFinite(envLoadRaw) ? envLoadRaw : undefined;
40
+ const host = config?.host ?? env.ELANOUS_GATE_REMOTE_HOST?.trim() ?? GATE_REMOTE_DEFAULTS.host;
41
+ const mirror = config?.mirror ?? GATE_REMOTE_DEFAULTS.mirror;
42
+ return {
43
+ enabled: config?.enabled ?? envEnabled ?? GATE_REMOTE_DEFAULTS.enabled,
44
+ host: isValidGateHost(host) ? host : GATE_REMOTE_DEFAULTS.host,
45
+ loadThreshold: config?.loadThreshold ?? envLoad ?? GATE_REMOTE_DEFAULTS.loadThreshold,
46
+ // The cell's ceiling is 2 per host (GATE-REMOTE acceptance); config may only lower it.
47
+ hostCap: Math.min(MAX_HOST_CAP, Math.max(1, Math.floor(config?.hostCap ?? GATE_REMOTE_DEFAULTS.hostCap))),
48
+ mirror: mirrorPattern.test(mirror) && !mirror.split('/').includes('..') ? mirror : GATE_REMOTE_DEFAULTS.mirror,
49
+ slotWaitSeconds: config?.slotWaitSeconds ?? GATE_REMOTE_DEFAULTS.slotWaitSeconds,
50
+ };
51
+ }
52
+
53
+ /** The token after a bare `--remote` is its host when it is a valid ssh destination that is not an existing path and does
54
+ * not look like a test/list file — so `--remote node-b.lan a.test.ts` takes the host and `--remote a.test.ts` keeps the file. */
55
+ export function looksLikeHost(token: string, exists: (path: string) => boolean = existsSync): boolean {
56
+ if (!isValidGateHost(token) || exists(token)) return false;
57
+ return !/\.(?:test\.)?(?:[cm]?[jt]sx?|txt|json|md)$/i.test(token);
58
+ }
59
+
60
+ export interface GateRemoteFlags { remote?: string | true; local: boolean; rest: string[]; error?: string }
61
+
62
+ /** Pull `--remote [host]` / `--remote=<host>` / `--local` out of argv; everything else is left for the tool. */
63
+ export function extractGateRemoteFlags(argv: readonly string[]): GateRemoteFlags {
64
+ const out: GateRemoteFlags = { local: false, rest: [] };
65
+ for (let i = 0; i < argv.length; i++) {
66
+ const arg = argv[i]!;
67
+ if (arg === '--local') out.local = true;
68
+ else if (arg.startsWith('--remote=')) out.remote = arg.slice('--remote='.length);
69
+ else if (arg === '--remote') {
70
+ const next = argv[i + 1];
71
+ if (next !== undefined && looksLikeHost(next)) { out.remote = next; i++; } else out.remote = true;
72
+ } else out.rest.push(arg);
73
+ }
74
+ if (typeof out.remote === 'string' && !isValidGateHost(out.remote)) out.error = `invalid --remote host: ${out.remote}`;
75
+ if (out.remote !== undefined && out.local) out.error = '--remote and --local cannot be combined';
76
+ return out;
77
+ }
78
+
79
+ export interface GateDispatchDecision { mode: 'local' | 'remote'; host?: string; reason: string; load1: number }
80
+
81
+ export function decideGateDispatch(input: { flags: Pick<GateRemoteFlags, 'remote' | 'local'>; settings: GateRemoteSettings; load1: number; env?: NodeJS.ProcessEnv }): GateDispatchDecision {
82
+ const { flags, settings, load1 } = input;
83
+ const env = input.env ?? process.env;
84
+ if (flags.local) return { mode: 'local', reason: 'flag-local', load1 };
85
+ if (env.ELANOUS_GATE_REMOTE_CHILD === '1') return { mode: 'local', reason: 'remote-child', load1 };
86
+ if (flags.remote !== undefined) return { mode: 'remote', host: flags.remote === true ? settings.host : flags.remote, reason: 'flag-remote', load1 };
87
+ if (!settings.enabled) return { mode: 'local', reason: 'config-off', load1 };
88
+ // Tests never reach a real host on their own (an explicit --remote with an injected runner still can).
89
+ if (env.NODE_ENV === 'test') return { mode: 'local', reason: 'test-env', load1 };
90
+ if (load1 > settings.loadThreshold) return { mode: 'remote', host: settings.host, reason: `load ${load1.toFixed(1)} > ${settings.loadThreshold}`, load1 };
91
+ return { mode: 'local', reason: `load ${load1.toFixed(1)} <= ${settings.loadThreshold}`, load1 };
92
+ }
93
+
94
+ // ── host lock (on the remote host, cap N) ───────────────────────
95
+ // The slots live on the host itself, so every machine that dispatches there shares one cap. Each slot is a kernel file
96
+ // lock (flock(1) / BSD lockf(1)) held for exactly the life of the run — a crashed or killed run releases it, so there is
97
+ // no staleness guess and no reclaim race.
98
+
99
+ export const SLOT_DIR = '/tmp/elanous-gate-remote-slots';
100
+ const BUSY = '__GATE_REMOTE_BUSY';
101
+
102
+ /** Shell lines that run "$W/run.sh" under the first free of `cap` slot locks, or print the busy marker and exit 98.
103
+ * run.sh must create "$W/started" first: that is how a lock refusal is told apart from the run's own exit code. */
104
+ export type SlotLockTool = 'flock' | 'lockf';
105
+ const SLOT_LOCK = { flock: 'flock -n', lockf: 'lockf -k -s -t 0' } as const;
106
+
107
+ /** `tools` is the preference order (default: Linux flock, then BSD/macOS lockf); a test can pin one path. */
108
+ export function hostSlotScript(cap: number, dir = SLOT_DIR, tools: readonly SlotLockTool[] = ['flock', 'lockf']): string[] {
109
+ const n = Math.min(MAX_HOST_CAP, Math.max(1, Math.floor(cap)));
110
+ const pick = tools.map((tool, i) => `${i ? 'elif' : 'if'} command -v ${tool} >/dev/null 2>&1; then GL="${SLOT_LOCK[tool]}"; `).join('');
111
+ return [
112
+ `GS=${quote(dir)}; mkdir -p "$GS" || infra slots`,
113
+ `${pick}else infra lock-tool; fi`,
114
+ `n=1; while [ $n -le ${n} ]; do $GL "$GS/slot$n" sh "$W/run.sh"; s=$?; [ -f "$W/started" ] && exit $s; n=$((n+1)); done`,
115
+ `echo "${BUSY}" >&2; exit 98`,
116
+ ];
117
+ }
118
+
119
+ // ── remote execution ───────────────────────────────────────────────
120
+
121
+ export interface ExecResult { rc: number | null; stdout: string; stderr: string; error?: string }
122
+ /** Raw bytes from the host — the tool's output is passed on byte for byte (strings are accepted from test runners). */
123
+ export interface SshResult { rc: number | null; stdout: Buffer | string; stderr: Buffer | string; error?: string }
124
+ export interface GateRemoteRunner {
125
+ /** Run a command locally (git rev-parse · status · push). */
126
+ local(cmd: string, args: string[], cwd: string, options?: { timeoutMs?: number; env?: NodeJS.ProcessEnv }): ExecResult;
127
+ /** Run a shell script on the host over ssh. */
128
+ ssh(host: string, script: string, timeoutMs: number): SshResult;
129
+ }
130
+
131
+ export const realGateRemoteRunner: GateRemoteRunner = {
132
+ local(cmd, args, cwd, options) {
133
+ const run = spawnSync(cmd, args, { cwd, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024,
134
+ ...(options?.env ? { env: options.env } : {}), ...(options?.timeoutMs ? { timeout: options.timeoutMs, killSignal: 'SIGKILL' as const } : {}) });
135
+ return { rc: run.status, stdout: run.stdout ?? '', stderr: run.stderr ?? '', ...(run.error ? { error: String(run.error) } : {}) };
136
+ },
137
+ ssh(host, script, timeoutMs) {
138
+ const run = spawnSync('ssh', ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', host, 'sh', '-s'], {
139
+ input: script, cwd: '/tmp', maxBuffer: 256 * 1024 * 1024, timeout: timeoutMs, killSignal: 'SIGKILL',
140
+ });
141
+ return { rc: run.status, stdout: run.stdout ?? Buffer.alloc(0), stderr: run.stderr ?? Buffer.alloc(0), ...(run.error ? { error: String(run.error) } : {}) };
142
+ },
143
+ };
144
+
145
+ const quote = (value: string) => `'${value.replaceAll("'", `'\\''`)}'`;
146
+ const INFRA = '__GATE_REMOTE_INFRA=';
147
+ const RC = '__GATE_REMOTE_RC=';
148
+ export const PUSH_TIMEOUT_MS = 10 * 60_000;
149
+
150
+ export type RemoteRunOutcome =
151
+ | { kind: 'ran'; rc: number; stdout: Buffer; stderr: Buffer; commit: string; host: string; ms: number }
152
+ | { kind: 'busy' }
153
+ | { kind: 'infra'; reason: string };
154
+
155
+ export interface RemoteRunOptions {
156
+ repo: string;
157
+ host: string;
158
+ mirror: string;
159
+ /** argv run from the remote checkout root, e.g. ['bun', 'bin/elanous.mjs', 'self', 'gate', ...]; a function receives
160
+ * `sha(ref)` for each of `refs` so the remote command names the exact commit, not a ref the host may not have. */
161
+ argv: RemoteArgv;
162
+ hostCap?: number;
163
+ /** Host-side slot lock directory (default SLOT_DIR) — tests point it at a scratch dir. */
164
+ slotDir?: string;
165
+ /** Extra local refs whose commits must exist remotely (e.g. a --base ref). Resolved to shas. */
166
+ refs?: string[];
167
+ installPwa?: boolean;
168
+ timeoutMs?: number;
169
+ /** Files the remote run needs from this machine (e.g. the private export redaction table): sent over ssh stdin, written
170
+ * mode 600 inside the scratch directory, exposed to the tool via `env`, removed with it. Never on argv. */
171
+ payloadFiles?: PayloadFile[];
172
+ }
173
+
174
+ export interface PayloadFile { env: string; content: string }
175
+ export type RemoteArgv = string[] | ((sha: (ref: string) => string) => string[]);
176
+
177
+ /** Build the remote shell script (exported for tests). Every setup failure ends in an INFRA marker, the tool's rc in an RC marker. */
178
+ export function remoteScript(input: { mirror: string; commit: string; fetchShas: string[]; mainSha?: string; argv: string[]; installPwa: boolean; payloadFiles?: PayloadFile[]; hostCap?: number; slotDir?: string }): string {
179
+ const mirror = input.mirror.startsWith('~/') ? `"$HOME"/${quote(input.mirror.slice(2))}` : quote(input.mirror);
180
+ const fetches = input.fetchShas.map((sha) => `refs/elanous/gate/${sha}:refs/elanous/gate/${sha}`).join(' ');
181
+ const payloads = (input.payloadFiles ?? []).filter((file) => /^[A-Z_][A-Z0-9_]*$/.test(file.env));
182
+ const payloadLines = payloads.flatMap((file, i) => {
183
+ const delimiter = `__GATE_REMOTE_PAYLOAD_${i}__`;
184
+ if (file.content.split('\n').includes(delimiter)) throw new Error('payload contains its delimiter');
185
+ return [`cat > "$W/payload-${i}" <<'${delimiter}' || infra payload`, file.content.replace(/\n$/, ''), delimiter];
186
+ });
187
+ const payloadEnv = payloads.map((file, i) => `${file.env}="$W/payload-${i}" `).join('');
188
+ // One brace group: sh parses the whole script off stdin before running anything, so no child can swallow the rest of it.
189
+ // The work itself is written to "$W/run.sh" and runs under a host slot lock.
190
+ const RUN = '__GATE_REMOTE_RUN__';
191
+ const work = [
192
+ `infra() { echo "${INFRA}$1" >&2; exit 97; }`,
193
+ ': > "$W/started"',
194
+ `git clone -q --shared --no-checkout ${mirror} "$W/t" >"$W/setup.log" 2>&1 || infra clone`,
195
+ 'cd "$W/t" || infra cd',
196
+ `git fetch -q origin ${fetches} >>"$W/setup.log" 2>&1 || infra fetch`,
197
+ `git checkout -q --detach ${input.commit} >>"$W/setup.log" 2>&1 || infra checkout`,
198
+ ...(input.mainSha ? [`git update-ref refs/remotes/origin/main ${input.mainSha} || infra origin-main`] : []),
199
+ 'bun install >>"$W/setup.log" 2>&1 || infra bun-install',
200
+ ...(input.installPwa ? ['(cd apps/pwa && bun install) >>"$W/setup.log" 2>&1 || infra bun-install-pwa'] : []),
201
+ // A scratch universe: the remote run never touches the host's operational ~/.elanous state.
202
+ 'mkdir -p "$W/state" || infra scratch',
203
+ `${payloadEnv}ELANOUS_STATE_DIR="$W/state" ELANOUS_GATE_REMOTE_CHILD=1 ${input.argv.map(quote).join(' ')} </dev/null`,
204
+ // The marker always starts on its own line, whatever the tool's stderr ended with; the parser strips that newline.
205
+ `rc=$?; printf '\\n%s%s\\n' '${RC}' "$rc" >&2`,
206
+ 'exit 0',
207
+ ];
208
+ // Any argument line equal to the delimiter would end run.sh early and run the rest outside the slot lock.
209
+ if (work.join('\n').split('\n').includes(RUN)) throw new Error('run script contains its delimiter');
210
+ return [
211
+ '{',
212
+ 'umask 077',
213
+ 'PATH="$HOME/.bun/bin:/opt/homebrew/bin:$PATH"; export PATH',
214
+ `infra() { echo "${INFRA}$1" >&2; exit 97; }`,
215
+ 'W=$(mktemp -d /tmp/gate-remote-XXXXXXXX) || infra mktemp',
216
+ 'export W',
217
+ 'trap \'rm -rf "$W"\' EXIT',
218
+ ...payloadLines,
219
+ `cat > "$W/run.sh" <<'${RUN}' || infra run-script`,
220
+ ...work,
221
+ RUN,
222
+ ...hostSlotScript(input.hostCap ?? GATE_REMOTE_DEFAULTS.hostCap, input.slotDir),
223
+ '}',
224
+ '',
225
+ ].join('\n');
226
+ }
227
+
228
+ function revParse(runner: GateRemoteRunner, repo: string, ref: string): string | undefined {
229
+ const r = runner.local('git', ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], repo);
230
+ const sha = r.stdout.trim();
231
+ return r.rc === 0 && shaPattern.test(sha) ? sha : undefined;
232
+ }
233
+
234
+ export function runOnRemote(opts: RemoteRunOptions, runner: GateRemoteRunner = realGateRemoteRunner): RemoteRunOutcome {
235
+ const started = Date.now();
236
+ const status = runner.local('git', ['status', '--porcelain'], opts.repo);
237
+ if (status.rc !== 0) return { kind: 'infra', reason: 'git-status-failed' };
238
+ // A remote checkout can only reproduce a commit — uncommitted or untracked files would silently not be measured.
239
+ if (status.stdout.trim()) return { kind: 'infra', reason: 'dirty-tree' };
240
+ const commit = revParse(runner, opts.repo, 'HEAD');
241
+ if (!commit) return { kind: 'infra', reason: 'head-unresolved' };
242
+ // Every check measures against merge-base(origin/main); without the local one the host would fall back to its mirror's
243
+ // (possibly stale) branch — not the same measurement.
244
+ const mainSha = revParse(runner, opts.repo, 'origin/main');
245
+ if (!mainSha) return { kind: 'infra', reason: 'origin-main-unresolved' };
246
+ const resolved = new Map<string, string>();
247
+ for (const ref of opts.refs ?? []) {
248
+ const sha = revParse(runner, opts.repo, ref);
249
+ if (!sha) return { kind: 'infra', reason: `ref-unresolved:${ref}` };
250
+ resolved.set(ref, sha);
251
+ }
252
+ const extra = [...resolved.values()];
253
+ let argv: string[];
254
+ try {
255
+ argv = typeof opts.argv === 'function' ? opts.argv((ref) => {
256
+ const sha = resolved.get(ref);
257
+ if (!sha) throw new Error(`ref not declared: ${ref}`);
258
+ return sha;
259
+ }) : opts.argv;
260
+ } catch (error) { return { kind: 'infra', reason: `argv: ${error instanceof Error ? error.message : String(error)}` }; }
261
+ const shas = [...new Set([commit, mainSha, ...extra])];
262
+ const pushTarget = `${opts.host}:${opts.mirror.startsWith('~/') ? opts.mirror.slice(2) : opts.mirror}`;
263
+ // Non-interactive and time-bounded: a stuck connection or auth prompt must end in a local fallback, not a hang.
264
+ const push = runner.local('git', ['push', '--quiet', pushTarget, ...shas.map((sha) => `${sha}:refs/elanous/gate/${sha}`)], opts.repo, {
265
+ timeoutMs: PUSH_TIMEOUT_MS,
266
+ env: { ...process.env, GIT_SSH_COMMAND: 'ssh -o BatchMode=yes -o ConnectTimeout=10', GIT_TERMINAL_PROMPT: '0' },
267
+ });
268
+ if (push.rc !== 0 || push.error) return { kind: 'infra', reason: `push-failed rc=${push.rc}: ${(push.stderr || push.error || '').trim().split('\n').slice(-2).join(' | ')}` };
269
+ const script = remoteScript({ mirror: opts.mirror, commit, fetchShas: shas, mainSha, argv, installPwa: opts.installPwa ?? false, payloadFiles: opts.payloadFiles, hostCap: opts.hostCap, slotDir: opts.slotDir });
270
+ const run = runner.ssh(opts.host, script, opts.timeoutMs ?? 120 * 60_000);
271
+ // The script prints "\n<marker><rc>\n": everything before that leading newline is the tool's own stderr, byte for byte.
272
+ const stdoutBytes = Buffer.from(run.stdout);
273
+ const stderrBytes = Buffer.from(run.stderr);
274
+ const stderrText = stderrBytes.toString('utf8');
275
+ const marker = stderrBytes.lastIndexOf(Buffer.from(`\n${RC}`));
276
+ // Busy (98) and setup failure (97) are the script's own exits, each with its marker as the last stderr line. A completed
277
+ // run exits 0 after the rc marker, and its stderr belongs to the tool — never scanned for markers.
278
+ if (!(run.rc === 0 && marker >= 0)) {
279
+ const last = stderrText.trimEnd().split('\n').at(-1) ?? '';
280
+ if (run.rc === 98 && last === BUSY) return { kind: 'busy' };
281
+ if (run.rc === 97 && last.startsWith(INFRA)) return { kind: 'infra', reason: `remote-${last.slice(INFRA.length).trim()}` };
282
+ }
283
+ // No marker, or ssh did not end cleanly after it (the script always exits 0 once the marker is written): output may be cut.
284
+ if (marker < 0) return { kind: 'infra', reason: `ssh rc=${run.rc}${run.error ? ` ${run.error}` : ''}: ${stderrText.trim().split('\n').slice(-2).join(' | ')}` };
285
+ if (run.rc !== 0 || run.error) return { kind: 'infra', reason: `ssh-incomplete rc=${run.rc}${run.error ? ` ${run.error}` : ''}` };
286
+ // The marker is the last thing the script writes: exactly "\n<marker><digits>\n". Anything else (empty, cut) is infra.
287
+ const tail = /^\n__GATE_REMOTE_RC=(\d{1,3})\n$/.exec(stderrBytes.subarray(marker).toString('utf8'));
288
+ const rc = tail ? Number(tail[1]) : NaN;
289
+ if (!(rc >= 0 && rc <= 255)) return { kind: 'infra', reason: 'rc-unparsed' };
290
+ // The tool's stderr is exactly what came before the marker line.
291
+ return { kind: 'ran', rc, stdout: stdoutBytes, stderr: stderrBytes.subarray(0, marker), commit, host: opts.host, ms: Date.now() - started };
292
+ }
293
+
294
+ /** An absolute argument outside the repo names a file the remote checkout does not have — such a run is not reproducible. */
295
+ export function outsideRepoArgs(args: readonly string[], repo: string): string[] {
296
+ // The tool resolves file arguments against the repository root; anything that lands outside it (absolute or ../) is not
297
+ // in the remote checkout.
298
+ return args.filter((arg) => {
299
+ if (arg.startsWith('-')) return false;
300
+ const rel = relative(repo, resolve(repo, arg));
301
+ return rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel);
302
+ });
303
+ }
304
+
305
+ /** File arguments the remote checkout cannot reproduce: outside the repo (by string or after symlinks resolve), or not
306
+ * tracked at HEAD (ignored/untracked files are not in the pushed commit). `tracked` defaults to `git ls-files`. */
307
+ export function unreproducibleArgs(paths: readonly string[], repo: string, tracked: (path: string) => boolean = (path) =>
308
+ spawnSync('git', ['ls-files', '--error-unmatch', '--', path], { cwd: repo, stdio: 'ignore', maxBuffer: 1024 * 1024 }).status === 0): string[] {
309
+ let realRepo = repo;
310
+ try { realRepo = realpathSync(repo); } catch { /* keep as given */ }
311
+ return paths.filter((arg) => {
312
+ if (outsideRepoArgs([arg], repo).length) return true;
313
+ const abs = resolve(repo, arg);
314
+ if (!tracked(relative(repo, abs))) return true;
315
+ // Conservative: any symlink on the way (the file itself or a directory above it, inside the repo) means local —
316
+ // a link's target may be untracked, outside, looped or dangling, and none of that is worth reproducing remotely.
317
+ const parts = relative(repo, abs).split(sep);
318
+ let walk = realRepo;
319
+ for (const part of parts) {
320
+ walk = join(walk, part);
321
+ try { if (lstatSync(walk).isSymbolicLink()) return true; } catch { return false; } // missing locally: tracked() already answered
322
+ }
323
+ return false;
324
+ });
325
+ }
326
+
327
+ /** Rewrite absolute paths under the repo to repo-relative so they mean the same file in the remote checkout. */
328
+ export function relativizeArgs(args: readonly string[], repo: string): string[] {
329
+ return args.map((arg) => {
330
+ if (!isAbsolute(arg)) return arg;
331
+ const rel = relative(repo, arg);
332
+ return rel && rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel) ? rel : arg;
333
+ });
334
+ }
335
+
336
+ /** For a JSON object stdout: add `host` and `remote: true` by splicing them in before the closing brace — every other byte
337
+ * of the tool's output (spacing, escapes, key order, trailing newline) is kept. Anything else passes through unchanged. */
338
+ export function annotateJsonStdout(stdout: string, host: string): string {
339
+ const trimmed = stdout.trim();
340
+ if (!trimmed.startsWith('{')) return stdout;
341
+ try {
342
+ const parsed = JSON.parse(trimmed) as unknown;
343
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return stdout;
344
+ } catch { return stdout; }
345
+ const close = stdout.lastIndexOf('}');
346
+ const empty = Object.keys(JSON.parse(trimmed) as object).length === 0;
347
+ return `${stdout.slice(0, close)}${empty ? '' : ','}"host":${JSON.stringify(host)},"remote":true${stdout.slice(close)}`;
348
+ }
349
+
350
+ export interface DispatchOptions {
351
+ tool: GateRemoteTool;
352
+ repo: string;
353
+ flags: Pick<GateRemoteFlags, 'remote' | 'local'>;
354
+ /** argv for the remote run (the tool re-invoked with `--local` so it never recurses). */
355
+ remoteArgv: RemoteArgv;
356
+ refs?: string[];
357
+ installPwa?: boolean;
358
+ payloadFiles?: PayloadFile[];
359
+ json?: boolean;
360
+ /** Set when this invocation cannot be reproduced remotely (e.g. self gate without --base): a remote decision becomes a
361
+ * logged fallback with this reason instead of a silent local run. */
362
+ localOnlyReason?: string;
363
+ runLocal: () => number | Promise<number>;
364
+ }
365
+
366
+ export interface DispatchDeps {
367
+ runner?: GateRemoteRunner;
368
+ settings?: GateRemoteSettings;
369
+ load1?: number;
370
+ env?: NodeJS.ProcessEnv;
371
+ sleep?: (ms: number) => Promise<void>;
372
+ now?: () => number;
373
+ write?: { out: (data: string | Uint8Array) => void; err: (data: string | Uint8Array) => void };
374
+ }
375
+
376
+ async function loadSettings(): Promise<GateRemoteSettings> {
377
+ try {
378
+ const { getUserConfig } = await import('../user-config.js');
379
+ return resolveGateRemoteSettings(getUserConfig().gateRemote);
380
+ } catch { return resolveGateRemoteSettings(undefined); }
381
+ }
382
+
383
+ /** Decide local/remote, run, and return the tool's exit code. Infra failure ⇒ local fallback (never a test result). */
384
+ export async function dispatchHeavyCheck(opts: DispatchOptions, deps: DispatchDeps = {}): Promise<number> {
385
+ const settings = deps.settings ?? await loadSettings();
386
+ const env = deps.env ?? process.env;
387
+ const write = deps.write ?? { out: (d: string | Uint8Array) => process.stdout.write(d), err: (d: string | Uint8Array) => process.stderr.write(d) };
388
+ const decision = decideGateDispatch({ flags: opts.flags, settings, load1: deps.load1 ?? loadavg()[0]!, env });
389
+ debug.log('gate.remote', 'decision', { tool: opts.tool, mode: decision.mode, host: decision.host, reason: decision.reason, load1: decision.load1 });
390
+ if (decision.mode === 'local') return opts.runLocal();
391
+ const host = decision.host!;
392
+ const fallback = (reason: string) => {
393
+ debug.log('gate.remote', 'fallback', { tool: opts.tool, host, reason });
394
+ write.err(`[gate.remote] fallback to local — ${reason}\n`);
395
+ return opts.runLocal();
396
+ };
397
+ if (opts.localOnlyReason) return fallback(opts.localOnlyReason);
398
+ const sleep = deps.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));
399
+ const now = deps.now ?? Date.now;
400
+ const deadline = now() + settings.slotWaitSeconds * 1000;
401
+ debug.log('gate.remote', 'dispatch', { tool: opts.tool, host, reason: decision.reason });
402
+ let outcome: RemoteRunOutcome;
403
+ for (;;) {
404
+ try {
405
+ outcome = runOnRemote({ repo: opts.repo, host, mirror: settings.mirror, argv: opts.remoteArgv, refs: opts.refs, installPwa: opts.installPwa, payloadFiles: opts.payloadFiles, hostCap: settings.hostCap }, deps.runner);
406
+ } catch (error) {
407
+ outcome = { kind: 'infra', reason: `exception: ${error instanceof Error ? error.message : String(error)}` };
408
+ }
409
+ if (outcome.kind !== 'busy') break;
410
+ debug.log('gate.remote', 'host-busy', { tool: opts.tool, host, cap: settings.hostCap });
411
+ // Never start an attempt past the deadline: an expired wait means local, even if a slot would free a moment later.
412
+ const left = deadline - now();
413
+ if (left <= 0) break;
414
+ await sleep(Math.min(15_000, left));
415
+ if (now() >= deadline) break;
416
+ }
417
+ if (outcome.kind === 'busy') return fallback(`host-busy (cap ${settings.hostCap} held for ${settings.slotWaitSeconds}s)`);
418
+ if (outcome.kind === 'infra') return fallback(outcome.reason);
419
+ // Parity: the tool's own stdout/stderr/rc pass through unchanged — only a JSON stdout gains host · remote:true.
420
+ debug.log('gate.remote', 'result', { tool: opts.tool, host: outcome.host, commit: outcome.commit, rc: outcome.rc, ms: outcome.ms });
421
+ // Bytes pass through untouched; only a JSON object stdout is re-serialised (to add host · remote:true).
422
+ const text = opts.json ? outcome.stdout.toString('utf8') : undefined;
423
+ const annotated = text !== undefined ? annotateJsonStdout(text, outcome.host) : undefined;
424
+ write.out(annotated !== undefined && annotated !== text ? annotated : outcome.stdout);
425
+ write.err(outcome.stderr);
426
+ return outcome.rc;
427
+ }
@@ -681,6 +681,87 @@ export function injectGoalDocumentClarificationOtherAnswer(document: string, que
681
681
  * 「답이 한 번도 안 들어가서 효과를 못 쟀다」***일 수 있다 — 두 무리 다 «답이 없는» 무리였다.
682
682
  * ⛔ 무인 계약은 유지한다 — 대화형이 아니면 종전대로 지나가되 «그 사실을 값으로» 낸다.
683
683
  */
684
+ export type GoalClarificationSelfAnswerPath = 'say-first' | 'ask' | 'rework';
685
+ export type GoalClarificationSelfResolver = NonNullable<GoalAuthorClarificationResolutionDeps['selfResolve']>;
686
+
687
+ /** Attempt only unanswered document questions; retain every unanswered line if evidence is insufficient. */
688
+ export async function selfAnswerGoalDocumentClarifications(
689
+ document: string,
690
+ options: {
691
+ path: GoalClarificationSelfAnswerPath;
692
+ ask?: string;
693
+ evidence?: readonly string[];
694
+ resolver?: GoalClarificationSelfResolver;
695
+ },
696
+ ): Promise<{ document: string; asked: number; answered: number; unanswered: number; newlyAnswered: number }> {
697
+ const clarifications = parseGoalDocumentClarifications(document);
698
+ const pending = clarifications.filter((item) => !item.answered);
699
+ // The first say pass may already have self-answered some questions while authoring: count those,
700
+ // never re-ask them, and still attempt every question that is left unanswered.
701
+ const alreadySelfAnswered = options.path === 'say-first'
702
+ ? clarifications.filter((item) => item.answered && item.provenanceSource === 'self-authored').length
703
+ : 0;
704
+ let updated = document;
705
+ let answered = 0;
706
+ const evidence = options.evidence ?? document.split(/\r?\n/)
707
+ .filter((line) => /^(?:\s*- (?:\[code:|\[document:|TRACED PATHS|evidence:|Evidence:)|\s*-?\s*`?(?:src|test|scripts)\/[^\s`]+:\d+)/i.test(line))
708
+ .slice(0, 40);
709
+ for (const item of pending) {
710
+ if (parseGoalDocumentClarifications(updated).filter((entry) => entry.questionId === item.questionId).length !== 1) continue;
711
+ let response: Awaited<ReturnType<typeof resolveGoalAuthorClarification>>;
712
+ try { response = await resolveGoalAuthorClarification({
713
+ questionId: item.questionId,
714
+ kind: 'scope',
715
+ header: item.header,
716
+ question: item.question,
717
+ options: item.options.map((option) => ({ label: option.label, description: option.description })),
718
+ blocking: false,
719
+ }, {
720
+ selfResolve: options.resolver ?? defaultGoalAuthorSelfResolve,
721
+ evidence,
722
+ ask: options.ask,
723
+ selfResolutionSelected: true,
724
+ }); } catch {
725
+ // A resolver failure leaves this question unanswered; the remaining questions are still attempted.
726
+ continue;
727
+ }
728
+ if (response.answer === null || response.provenance?.source !== 'self-authored'
729
+ || response.answer.startsWith(DEFERRED_ANSWER_PREFIX)) continue;
730
+ try {
731
+ if (!response.answer.trim() || /[\r\n]/.test(response.answer)
732
+ || (!item.includeOther && !item.options.some((option) => option.label === response.answer))) continue;
733
+ const current = findUnansweredGoalDocumentClarification(updated, item.questionId);
734
+ const lines = updated.split(/\r?\n/);
735
+ const provenanceLines = current.provenanceLines ?? [];
736
+ for (const line of [...provenanceLines].sort((left, right) => right - left)) lines.splice(line, 1);
737
+ const answerLine = current.answerLine - provenanceLines.filter((line) => line < current.answerLine).length;
738
+ lines[answerLine] = ` - answer: ${response.answer}`;
739
+ lines.splice(answerLine + 1, 0,
740
+ ' - provenance.source: self-authored',
741
+ ...response.provenance.evidence!.map((entry) => ` - evidence: ${entry.replace(/[\r\n]/g, ' ')}`));
742
+ updateWhatToBuildRenderAnswers(lines, item.questionId, response.answer);
743
+ if (hasWhatToBuildUnansweredRenderAnswer(lines, item.questionId)) continue;
744
+ const candidate = lines.join(document.includes('\r\n') ? '\r\n' : '\n');
745
+ if (!parseGoalDocumentClarifications(candidate).some((entry) => entry.questionId === item.questionId
746
+ && entry.answered && entry.answer === response.answer && entry.provenanceSource === 'self-authored')) continue;
747
+ updated = candidate;
748
+ answered += 1;
749
+ } catch {
750
+ // Leave malformed or disallowed answers deferred.
751
+ }
752
+ }
753
+ const asked = pending.length + alreadySelfAnswered;
754
+ const unanswered = pending.length - answered;
755
+ const newlyAnswered = answered;
756
+ answered += alreadySelfAnswered;
757
+ debug.log('self-implement.clarification-escalation', 'self-answer', {
758
+ path: options.path, asked, answered, unanswered,
759
+ reason: unanswered === 0 ? 'answered' : 'insufficient-evidence',
760
+ });
761
+ return { document: updated, asked, answered, unanswered, newlyAnswered };
762
+ }
763
+
764
+
684
765
  export type ClarificationIntakeMode = 'none' | 'deferred-noninteractive' | 'ask';
685
766
 
686
767
  export interface ClarificationIntakePlan {
@@ -1,9 +1,11 @@
1
1
  import { readFileSync, realpathSync } from 'node:fs';
2
+ import { writeGoalDocumentAtomic } from './goal-document-write.js';
2
3
  import { isAbsolute, relative, resolve } from 'node:path';
3
4
  import { debug } from '../debug/log.js';
5
+ import { getUserConfig } from '../user-config.js';
4
6
  import { parseAskProseTitle } from '../self-dev/launch-preflight.js';
5
7
  import { createGoalAuthorDecomposeSteps, readRecentStepCountsFailOpen, type RecentStepCountReader } from './goal-author-decompose.js';
6
- import { defaultGoalAuthorSelfResolve, parseGoalDocumentClarifications, seedGoalAuthorFromAnsweredClarification, seedGoalAuthorFromClarification } from './goal-author-clarification.js';
8
+ import { defaultGoalAuthorSelfResolve, parseGoalDocumentClarifications, seedGoalAuthorFromAnsweredClarification, seedGoalAuthorFromClarification, selfAnswerGoalDocumentClarifications } from './goal-author-clarification.js';
7
9
  import { classifyGoalInterviewRound, type GoalInterviewRoundStatus } from './goal-author-closure.js';
8
10
  import { recordGoalAsk } from './goal-ask-store.js';
9
11
  import { pressDecisionSignal } from './decision-signal-press.js';
@@ -15,6 +17,8 @@ import {
15
17
  verbatimOriginalAsk,
16
18
  writeAuthoredGoal,
17
19
  type GoalAuthorDeps,
20
+ type GoalAuthorGradeSelection,
21
+ resolveGoalAuthorGrade,
18
22
  type GoalType,
19
23
  type GoalFileLintFinding,
20
24
  } from './goal-author.js';
@@ -77,6 +81,7 @@ interface GoalAuthorCliOptions {
77
81
  supersedes?: string;
78
82
  rootIntent?: string;
79
83
  goalType?: string;
84
+ authorGrade?: GoalAuthorGradeSelection;
80
85
  selfResolveClarifications?: boolean;
81
86
  adversarialReview?: boolean;
82
87
  disableAdversarialReview?: boolean;
@@ -97,6 +102,7 @@ export interface GoalAuthorCliDeps {
97
102
  recentStepCountReader?: RecentStepCountReader;
98
103
  /** Resolver supplied only by the explicit CLI self-resolution opt-in. */
99
104
  selfResolveClarification?: NonNullable<GoalAuthorDeps['selfResolveClarification']>;
105
+ writeFile?: (path: string, document: string) => void;
100
106
  recordAsk?: typeof recordGoalAsk;
101
107
  }
102
108
 
@@ -268,8 +274,9 @@ export async function runGoalAuthorCli(
268
274
  : opts.reauthorFromAnsweredClarification
269
275
  ? { origin: 'answered-clarification-reauthor' }
270
276
  : { origin: 'direct-request' };
271
- const recentStepCounts = readRecentStepCountsFailOpen(deps.recentStepCountReader);
272
- const decomposeSteps = deps.decomposeSteps ?? (deps.createDecomposeSteps ?? createGoalAuthorDecomposeSteps)(undefined, {
277
+ const authorGrade = opts.authorGrade ?? resolveGoalAuthorGrade(undefined, getUserConfig().harness?.authorGrade);
278
+ const recentStepCounts = authorGrade.grade === 'lite' ? undefined : readRecentStepCountsFailOpen(deps.recentStepCountReader);
279
+ const decomposeSteps = authorGrade.grade === 'lite' ? undefined : deps.decomposeSteps ?? (deps.createDecomposeSteps ?? createGoalAuthorDecomposeSteps)(undefined, {
273
280
  ...(adversarialReview !== undefined ? { adversarialReview, adversarialReviewSource: 'cli' as const } : {}),
274
281
  ...(recentStepCounts !== undefined ? { recentStepCounts } : {}),
275
282
  });
@@ -283,13 +290,34 @@ export async function runGoalAuthorCli(
283
290
  debug.log('goal-author', 'goal-title-forwarded', proseTitle === undefined
284
291
  ? { passed: false }
285
292
  : { passed: true, titleChars: proseTitle.length });
293
+ const gradeDeps = authorGrade.grade === 'lite' || opts.authorGrade ? { authorGrade } : {};
286
294
  const authorDeps: Partial<GoalAuthorDeps> = parentGoalFile && parentQuestionId
287
- ? { parent: { goalFile: parentGoalFile, questionId: parentQuestionId }, parentDocument, clarificationAnswers, decomposeSteps, ...selfResolutionDeps, ...progressDeps, ...launchPreflightDeps, ...(opts.rootIntent !== undefined && { rootIntent: opts.rootIntent }), ...(goalType !== undefined && { goalType }), ...goalTitleDeps }
288
- : { ...(clarificationAnswers && { clarificationAnswers }), decomposeSteps, ...selfResolutionDeps, ...progressDeps, ...launchPreflightDeps, ...(opts.rootIntent !== undefined && { rootIntent: opts.rootIntent }), ...(goalType !== undefined && { goalType }), ...goalTitleDeps };
295
+ ? { parent: { goalFile: parentGoalFile, questionId: parentQuestionId }, parentDocument, clarificationAnswers, ...(decomposeSteps ? { decomposeSteps } : {}), ...gradeDeps, ...selfResolutionDeps, ...progressDeps, ...launchPreflightDeps, ...(opts.rootIntent !== undefined && { rootIntent: opts.rootIntent }), ...(goalType !== undefined && { goalType }), ...goalTitleDeps }
296
+ : { ...(clarificationAnswers && { clarificationAnswers }), ...(decomposeSteps ? { decomposeSteps } : {}), ...gradeDeps, ...selfResolutionDeps, ...progressDeps, ...launchPreflightDeps, ...(opts.rootIntent !== undefined && { rootIntent: opts.rootIntent }), ...(goalType !== undefined && { goalType }), ...goalTitleDeps };
289
297
  const write = deps.write ?? writeAuthoredGoal;
290
298
  const result = opts.groundingCwd === undefined
291
299
  ? await write(ask, opts.cwd, authorDeps, superseded ? { supersedes: superseded } : undefined)
292
300
  : await write(ask, opts.cwd, authorDeps, superseded ? { supersedes: superseded } : undefined, opts.groundingCwd);
301
+ if (opts.selfResolveClarifications) {
302
+ const rework = Boolean(supersededDocument || opts.reauthorFromAnsweredClarification);
303
+ {
304
+ try {
305
+ const resolved = await selfAnswerGoalDocumentClarifications(result.authored.document, {
306
+ path: rework ? 'rework' : 'say-first', ask, resolver: deps.selfResolveClarification ?? defaultGoalAuthorSelfResolve,
307
+ evidence: result.authored.facts
308
+ ? [...(result.authored.facts.persistentEvidence ?? []), ...result.authored.facts.codeFacts, ...result.authored.facts.documentFacts]
309
+ : undefined,
310
+ });
311
+ if (resolved.newlyAnswered > 0) {
312
+ (deps.writeFile ?? writeGoalDocumentAtomic)(result.path, resolved.document);
313
+ result.authored.document = resolved.document;
314
+ }
315
+ } catch (error) {
316
+ // The previous goal file is left intact (atomic replace); implementation proceeds from it with the questions deferred.
317
+ debug.log('goal-author.clarify', 'self-answer-deferred', { reason: error instanceof Error ? error.message : String(error) });
318
+ }
319
+ }
320
+ }
293
321
  try {
294
322
  const recorded = (deps.recordAsk ?? recordGoalAsk)({
295
323
  authorRunId: result.authored.authorRunId,