elanous 0.2.10 → 0.2.11

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 (584) 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/Y4zKzL62sakG5momz3SoQ/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1394-556de6d71bbdb0fb.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1452.93700483b9d4fc61.js +9 -0
  7. package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 1829-b453a1b22d3999bb.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/2310-7a59f043f6db207e.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/2548-6e316138d347cefe.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3869-a39d1d9921e10d43.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/390.c373e31b3b9d0c41.js +5 -0
  19. package/apps/pwa/out/_next/static/chunks/3992-2e522a8ac5f2afde.js +25 -0
  20. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/4685-c3c1c625b71accd1.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +2 -0
  25. package/apps/pwa/out/_next/static/chunks/507-5acd98bd6dab2d8a.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/5445-745eb795c5963d79.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/{8067-87ce253ec2f23697.js → 5845-e6c003ce8d5fd36a.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → 5cd7e3a9-404fd6b8d0ecd44c.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 7334.a6679d9168f37aa8.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/79f8be9e-957d68913b590b08.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8039-df92e08fede5de8f.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/8421-1c4acfb1395a0edf.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/8605-ea5bee9dfae512c7.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/{2415.ed6428ff334c7ce2.js → 9269.d2a4795bc6812717.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/9656-c1540088f5859c80.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/9906-482c6619b3fdda20.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/approvals/page-1dcdc58fc2de2454.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-6a061bd97f0b9398.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/board/page-cb95a7432c9ee744.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-54b32bd27726588d.js → page-b12b6d2f8d2d1aea.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/app/bots/page-b9efe868bfd36255.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/chat/page-7c97dcdfe2e37a60.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/consult/page-8fd36984f549270a.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/control/page-5721537ca8a3598c.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/design-check/page-b654240a572e4c48.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/editor/page-b9750abc897508c3.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/exec/page-4cd1de371f8ad048.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/field/page-d99f89657828421c.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/inside/page-50cb2a0eb15e6c83.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/intake/page-c8b1ccda6ba7ae98.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/layout-d6b1d156fb95e74b.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/live/page-2a9932ddb62d76bd.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/market/page-9688f5b4f1ff950f.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6b15dd6456c5df09.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/morning/page-35414e0a007b4082.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/not-found-e7a529e0507ac86c.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/observatory/page-e001823c35c9dd67.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-371423d1a573d64b.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4b764b125339ac17.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cdf8e4b01067bcf1.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a24072b35470157.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/page-94c82eb0ae5abb32.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/reflection/page-0be8d4b2ced75ba0.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d37cb0d52f9b6bd5.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/sessions/page-686da0df9bf23e5c.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4dc614f3ce043e63.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/settings/page-53f80edc6b8dc07c.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d02c782fc792e2af.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/setup/page-239c17b911b84e72.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/share/page-dc6a79fb003f746c.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/showroom/page-0fffb84cafdc9da6.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/tasks/page-1218753c82d585f6.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/term/page-4b5935e67531efb5.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/today/page-3285bc87caf1eaa5.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/trace/page-ad553b0e06505ecd.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/vault/page-fca096e3e860306f.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ebfa78f0d9885c3.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/workflows/page-3dc5f21f7e39622d.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/workspace/page-c6da2b468454532d.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-93bc70e36f595414.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → f48170cc-d05d28bf87a43bf4.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +1 -0
  105. package/apps/pwa/out/_next/static/css/543f56bcb1f79fca.css +1 -0
  106. package/apps/pwa/out/approvals/index.html +1 -1
  107. package/apps/pwa/out/approvals/index.txt +23 -23
  108. package/apps/pwa/out/autopilot/index.html +1 -1
  109. package/apps/pwa/out/autopilot/index.txt +23 -23
  110. package/apps/pwa/out/board/index.html +1 -1
  111. package/apps/pwa/out/board/index.txt +23 -23
  112. package/apps/pwa/out/botlab/index.html +1 -1
  113. package/apps/pwa/out/botlab/index.txt +22 -22
  114. package/apps/pwa/out/bots/index.html +1 -1
  115. package/apps/pwa/out/bots/index.txt +22 -22
  116. package/apps/pwa/out/chat/index.html +1 -1
  117. package/apps/pwa/out/chat/index.txt +22 -22
  118. package/apps/pwa/out/consult/index.html +1 -1
  119. package/apps/pwa/out/consult/index.txt +23 -23
  120. package/apps/pwa/out/control/index.html +1 -1
  121. package/apps/pwa/out/control/index.txt +23 -23
  122. package/apps/pwa/out/design-check/index.html +1 -1
  123. package/apps/pwa/out/design-check/index.txt +23 -23
  124. package/apps/pwa/out/editor/index.html +1 -1
  125. package/apps/pwa/out/editor/index.txt +23 -23
  126. package/apps/pwa/out/exec/index.html +1 -1
  127. package/apps/pwa/out/exec/index.txt +23 -23
  128. package/apps/pwa/out/field/index.html +1 -1
  129. package/apps/pwa/out/field/index.txt +23 -23
  130. package/apps/pwa/out/healthz +1 -1
  131. package/apps/pwa/out/index.html +1 -1
  132. package/apps/pwa/out/index.txt +22 -22
  133. package/apps/pwa/out/inside/index.html +1 -0
  134. package/apps/pwa/out/inside/index.txt +31 -0
  135. package/apps/pwa/out/intake/index.html +1 -1
  136. package/apps/pwa/out/intake/index.txt +23 -23
  137. package/apps/pwa/out/live/index.html +1 -1
  138. package/apps/pwa/out/live/index.txt +23 -23
  139. package/apps/pwa/out/market/index.html +1 -1
  140. package/apps/pwa/out/market/index.txt +22 -22
  141. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  142. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  143. package/apps/pwa/out/morning/index.html +1 -1
  144. package/apps/pwa/out/morning/index.txt +23 -23
  145. package/apps/pwa/out/observatory/index.html +1 -1
  146. package/apps/pwa/out/observatory/index.txt +23 -23
  147. package/apps/pwa/out/ops/checklist/index.html +1 -1
  148. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  149. package/apps/pwa/out/ops/release/index.html +1 -1
  150. package/apps/pwa/out/ops/release/index.txt +22 -22
  151. package/apps/pwa/out/ops/seats/index.html +1 -1
  152. package/apps/pwa/out/ops/seats/index.txt +22 -22
  153. package/apps/pwa/out/outputs/index.html +1 -0
  154. package/apps/pwa/out/outputs/index.txt +30 -0
  155. package/apps/pwa/out/reflection/index.html +1 -1
  156. package/apps/pwa/out/reflection/index.txt +23 -23
  157. package/apps/pwa/out/scheduler/index.html +1 -1
  158. package/apps/pwa/out/scheduler/index.txt +23 -23
  159. package/apps/pwa/out/sessions/index.html +1 -1
  160. package/apps/pwa/out/sessions/index.txt +22 -22
  161. package/apps/pwa/out/settings/devices/index.html +1 -1
  162. package/apps/pwa/out/settings/devices/index.txt +23 -23
  163. package/apps/pwa/out/settings/index.html +1 -1
  164. package/apps/pwa/out/settings/index.txt +23 -23
  165. package/apps/pwa/out/setup/done/index.html +1 -1
  166. package/apps/pwa/out/setup/done/index.txt +23 -23
  167. package/apps/pwa/out/setup/index.html +1 -1
  168. package/apps/pwa/out/setup/index.txt +23 -23
  169. package/apps/pwa/out/share/index.html +1 -1
  170. package/apps/pwa/out/share/index.txt +23 -23
  171. package/apps/pwa/out/showroom/index.html +1 -1
  172. package/apps/pwa/out/showroom/index.txt +22 -22
  173. package/apps/pwa/out/tasks/index.html +1 -1
  174. package/apps/pwa/out/tasks/index.txt +23 -23
  175. package/apps/pwa/out/term/index.html +1 -1
  176. package/apps/pwa/out/term/index.txt +23 -24
  177. package/apps/pwa/out/today/index.html +1 -1
  178. package/apps/pwa/out/today/index.txt +23 -23
  179. package/apps/pwa/out/trace/index.html +1 -1
  180. package/apps/pwa/out/trace/index.txt +23 -23
  181. package/apps/pwa/out/vault/index.html +1 -1
  182. package/apps/pwa/out/vault/index.txt +23 -23
  183. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  184. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  185. package/apps/pwa/out/workflows/index.html +1 -1
  186. package/apps/pwa/out/workflows/index.txt +23 -23
  187. package/apps/pwa/out/workspace/index.html +1 -1
  188. package/apps/pwa/out/workspace/index.txt +23 -24
  189. package/apps/pwa/out/worktrees/index.html +1 -1
  190. package/apps/pwa/out/worktrees/index.txt +23 -23
  191. package/graphs/drafts/draft-cleanup.yaml +19 -0
  192. package/graphs/drafts/recipes.yaml +23 -0
  193. package/graphs/intake/intake-daily.yaml +2 -1
  194. package/graphs/ops/recipes.yaml +3 -0
  195. package/graphs/ops/secret-exposure.yaml +24 -0
  196. package/graphs/release/recipes.yaml +3 -0
  197. package/graphs/release/release-loop.yaml +4 -1
  198. package/graphs/video/explainer-line.yaml +2 -2
  199. package/package.json +2 -1
  200. package/plugins/elanous-hwp/README.md +25 -0
  201. package/plugins/elanous-hwp/cli.ts +22 -0
  202. package/plugins/elanous-hwp/converter.ts +237 -0
  203. package/plugins/elanous-hwp/fixtures/fixed-controls.hwpx +0 -0
  204. package/plugins/elanous-hwp/fixtures/fixed-table-controls.hwpx +0 -0
  205. package/plugins/elanous-hwp/fixtures/fixed.hwpx +0 -0
  206. package/plugins/elanous-hwp/plugin.json +16 -0
  207. package/plugins/elanous-hwp/plugin.ts +40 -0
  208. package/scripts/ci-public-export-leak-gate.ts +75 -14
  209. package/scripts/ci-public-leak-gate.ts +44 -9
  210. package/scripts/context-hooks/claude-stop.sh +4 -0
  211. package/scripts/context-hooks/emit.ts +24 -0
  212. package/scripts/context-hooks/post-tool-use.sh +4 -0
  213. package/scripts/coord-channel-watch.sh +12 -0
  214. package/scripts/coord-requests-bridge.ts +143 -0
  215. package/scripts/field-feed/lib.ts +4 -0
  216. package/scripts/fleet/node-setup.sh +20 -3
  217. package/scripts/homebrew-formula-bump.ts +69 -0
  218. package/scripts/marketing/measure-site-numbers.ts +169 -0
  219. package/scripts/measure-gate-shards.ts +309 -0
  220. package/scripts/mission-request-judge.ts +31 -1
  221. package/scripts/morning-report.ts +2 -2
  222. package/scripts/opportunity-scan.ts +1 -1
  223. package/scripts/release-loop/auto-approve-node.ts +21 -4
  224. package/scripts/release-loop/cut-branch.ts +84 -0
  225. package/scripts/release-loop/gate-node.ts +14 -13
  226. package/scripts/release-loop/mac-smoke-node.ts +229 -0
  227. package/scripts/release-loop/node-verdict.ts +3 -3
  228. package/scripts/release-loop/tui-node-checks.ts +42 -0
  229. package/scripts/release-loop/tui-sim-node.ts +32 -8
  230. package/scripts/release-loop/unattended-release.ts +5 -3
  231. package/scripts/release-loop/version-node.ts +61 -10
  232. package/scripts/signal-digest-cycle.ts +2 -7
  233. package/scripts/signal-digest-photo.ts +11 -0
  234. package/scripts/tech1-public-measure.ts +94 -0
  235. package/scripts/test-diet/node.ts +1 -1
  236. package/scripts/tui-regress.ts +2 -2
  237. package/scripts/ux/demo-rehearsal.ts +243 -0
  238. package/scripts/ux/lib/rehearsal-verdict.ts +73 -0
  239. package/skills/diagram-master/SKILL.md +366 -0
  240. package/skills/diagram-master/references/engines/excalidraw/color-palette.md +67 -0
  241. package/skills/diagram-master/references/engines/excalidraw/element-templates.md +182 -0
  242. package/skills/diagram-master/references/engines/excalidraw/json-schema.md +71 -0
  243. package/skills/diagram-master/references/engines/excalidraw/render_excalidraw.py +189 -0
  244. package/skills/diagram-master/references/engines/excalidraw/render_template.html +57 -0
  245. package/skills/diagram-master/references/engines/matplotlib/matplotlib-patterns.md +235 -0
  246. package/skills/diagram-master/references/engines/matplotlib/render_matplotlib.py +184 -0
  247. package/skills/diagram-master/references/engines/mermaid/domain-templates.md +435 -0
  248. package/skills/diagram-master/references/engines/mermaid/render_mermaid.py +116 -0
  249. package/skills/diagram-master/references/engines/mermaid/syntax-reference.md +2247 -0
  250. package/skills/diagram-master/references/engines/nanobannana/render_nanobannana2.py +168 -0
  251. package/skills/diagram-master/references/engines/svg/color-palette.md +188 -0
  252. package/skills/diagram-master/references/engines/svg/html-base-template.html +332 -0
  253. package/skills/diagram-master/references/engines/svg/interactivity-patterns.md +435 -0
  254. package/skills/diagram-master/references/engines/svg/render_interactive_svg.py +212 -0
  255. package/skills/diagram-master/references/engines/svg/svg-element-templates.md +349 -0
  256. package/skills/diagram-master/references/examples/README.md +69 -0
  257. package/skills/diagram-master/references/examples/ref_grid_architecture.png +0 -0
  258. package/skills/diagram-master/references/examples/ref_layered_flow.png +0 -0
  259. package/skills/diagram-master/references/pyproject.toml +16 -0
  260. package/skills/diagram-master/references/quality-checklist.md +33 -0
  261. package/skills/diagram-master/references/routing-guide.md +164 -0
  262. package/skills/diagram-master/references/uv.lock +1356 -0
  263. package/skills/google-workspace/SKILL.md +33 -0
  264. package/skills/google-workspace/scripts/gws-safe.sh +177 -0
  265. package/skills/grill-me/SKILL.md +1 -1
  266. package/skills/lecture-note-digitizer/SKILL.md +926 -0
  267. package/skills/lecture-note-digitizer/references/convert_to_pdf.py +30 -0
  268. package/skills/lecture-note-digitizer/references/extract_slide_diagram.py +73 -0
  269. package/skills/lecture-note-digitizer/references/html-template.md +167 -0
  270. package/skills/lecture-note-digitizer/references/katex-style-guide.md +165 -0
  271. package/skills/omni-crawl/SKILL.md +21 -11
  272. package/skills/omni-crawl/scripts/main.ts +6 -6
  273. package/skills/omni-crawl/src/router.ts +3 -3
  274. package/skills/omni-digest/SKILL.md +17 -9
  275. package/skills/omni-digest/digest.md +2 -2
  276. package/skills/omni-digest/src/fetch.ts +30 -3
  277. package/skills/pitch-deck/SKILL.md +2 -2
  278. package/skills/pitch-deck/template.html +2 -2
  279. package/skills/youtube-master/SKILL.md +163 -0
  280. package/skills/youtube-master/package-lock.json +558 -0
  281. package/skills/youtube-master/package.json +13 -0
  282. package/skills/youtube-master/references/study-note-template.md +124 -0
  283. package/skills/youtube-master/scripts/main.ts +435 -0
  284. package/skills/youtube-master/scripts/transcribe-local.ts +24 -0
  285. package/skills/youtube-master/src/audio.ts +63 -0
  286. package/skills/youtube-master/src/domain-staging.ts +50 -0
  287. package/skills/youtube-master/src/env.ts +36 -0
  288. package/skills/youtube-master/src/obsidian.ts +155 -0
  289. package/skills/youtube-master/src/router.ts +110 -0
  290. package/skills/youtube-master/src/stt.ts +199 -0
  291. package/skills/youtube-master/src/study-note.ts +175 -0
  292. package/skills/youtube-master/src/summarize.ts +358 -0
  293. package/skills/youtube-master/src/transcript.ts +76 -0
  294. package/skills/youtube-master/src/types.ts +91 -0
  295. package/skills/youtube-master/src/util.ts +93 -0
  296. package/skills/youtube-master/src/youtube.ts +70 -0
  297. package/skills/youtube-master/tsconfig.json +13 -0
  298. package/src/acp/server.ts +5 -2
  299. package/src/agent-mission/driver.ts +80 -10
  300. package/src/agent-mission/pty-decision.ts +71 -0
  301. package/src/agent-mission/review-loop.ts +30 -0
  302. package/src/autopilot/build/llm-conflict-merge.ts +16 -3
  303. package/src/autopilot/tool-registry.ts +2 -0
  304. package/src/boot/daemon-prompt-turn.ts +5 -0
  305. package/src/boot/daemon-runtime.ts +9 -3
  306. package/src/boot/daemon-tools/web-search.ts +3 -0
  307. package/src/card-followup/core.ts +109 -0
  308. package/src/chat/index.ts +1 -0
  309. package/src/cli/drive-input-jail.ts +101 -0
  310. package/src/cli/hooks-cli.ts +17 -3
  311. package/src/cli/make-cli.ts +43 -0
  312. package/src/cli/persona-cli.ts +90 -4
  313. package/src/cli/plugin-cli.ts +31 -22
  314. package/src/cli/pod-cli.ts +19 -9
  315. package/src/cli/pr-cli.ts +51 -15
  316. package/src/cli/pty-drive-cli.ts +41 -7
  317. package/src/cli/pty-drive-verdict.ts +40 -0
  318. package/src/cli/release-cli.ts +17 -4
  319. package/src/cli/setup-cli.ts +43 -1
  320. package/src/cli/skills-cli.ts +17 -1
  321. package/src/context-bus/claude-hooks-install.ts +10 -0
  322. package/src/context-bus/context-now-surfaces.ts +48 -0
  323. package/src/context-bus/context-now.ts +104 -0
  324. package/src/context-bus/coord-events.ts +2 -2
  325. package/src/context-bus/external-events.ts +52 -0
  326. package/src/context-bus/long-term-memory.ts +108 -0
  327. package/src/dashboard/index.ts +9 -4
  328. package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -1
  329. package/src/decisions/decision-cards.ts +10 -3
  330. package/src/decisions/decision-ledger.ts +13 -5
  331. package/src/discord/sprint21-runtime.ts +5 -1
  332. package/src/discord-self-message.ts +32 -0
  333. package/src/discord-slash-wire.ts +5 -1
  334. package/src/discord.ts +17 -1
  335. package/src/domains/core-tools.ts +3 -2
  336. package/src/domains/outbound-alert.ts +6 -1
  337. package/src/domains/telegram-kind-route.ts +51 -13
  338. package/src/exec-requests/default-outputs.ts +25 -0
  339. package/src/exec-requests/planner.ts +10 -2
  340. package/src/exec-requests/runner.ts +20 -6
  341. package/src/exec-requests/store.ts +3 -0
  342. package/src/execution-loop/launch-gate.ts +100 -0
  343. package/src/field/field-reel.ts +5 -1
  344. package/src/field-note/index.ts +201 -0
  345. package/src/field-note/transcribe.ts +248 -0
  346. package/src/graph-kinds/registry.ts +3 -3
  347. package/src/graph-runner/graph-cli.ts +61 -1
  348. package/src/graph-runner/graph-grow-llm.ts +37 -0
  349. package/src/graph-runner/graph-grow.ts +47 -0
  350. package/src/graph-runner/runner.ts +317 -25
  351. package/src/grok/agent-search.ts +8 -2
  352. package/src/guardian/secret-exposure-scan.ts +101 -0
  353. package/src/harness/harness-cli-command.ts +247 -7
  354. package/src/harness/harness-pod-dispatch.ts +43 -23
  355. package/src/harness/harness-seams.ts +38 -4
  356. package/src/harness/harness-stop.ts +23 -4
  357. package/src/harness/launch-registry.ts +91 -0
  358. package/src/harness/process-garbage.ts +5 -3
  359. package/src/hooks/dispatch.ts +82 -0
  360. package/src/hooks/expose.ts +254 -0
  361. package/src/hooks/github-shadow.ts +108 -0
  362. package/src/hooks/providers.ts +71 -5
  363. package/src/hooks/queue.ts +1 -1
  364. package/src/hooks/receiver.ts +21 -20
  365. package/src/index.ts +52 -14
  366. package/src/intake-plane/categorize.ts +18 -46
  367. package/src/intake-plane/discord-seat-work.ts +10 -0
  368. package/src/intake-plane/front-route-classifier.ts +33 -39
  369. package/src/intake-plane/persona-answer.ts +30 -0
  370. package/src/intake-plane/runtime-callables.ts +8 -1
  371. package/src/intake-plane/telegram-seat-work.ts +36 -2
  372. package/src/intake-plane/wish-folder.ts +67 -0
  373. package/src/intent-gate/gate.ts +30 -10
  374. package/src/live/detail-switch.ts +5 -0
  375. package/src/llm/difficulty.ts +57 -0
  376. package/src/llm/judge-layer.ts +101 -0
  377. package/src/llm/mission-router.ts +33 -19
  378. package/src/loops/contract/examples/cmo-seat.loop.yaml +51 -0
  379. package/src/loops/contract/examples/orchestrator.loop.yaml +57 -0
  380. package/src/loops/contract/examples/steward.loop.yaml +50 -0
  381. package/src/loops/contract/loop-agent.schema.json +138 -0
  382. package/src/loops/contract/validate.ts +64 -0
  383. package/src/loops/registry.ts +20 -6
  384. package/src/market/signed-index.ts +4 -0
  385. package/src/maturity/bot-command-maturity.ts +45 -0
  386. package/src/maturity/feature-maturity.ts +26 -4
  387. package/src/model-tier/index.ts +2 -2
  388. package/src/model-tier/tier-resolver.ts +20 -20
  389. package/src/model-tier/types.ts +6 -6
  390. package/src/mss/logging/log-store.ts +15 -0
  391. package/src/nexus/api/config-model-tier.ts +5 -4
  392. package/src/nexus/api/fabric-plans.ts +53 -0
  393. package/src/nexus/api/harness-api.ts +5 -1
  394. package/src/nexus/api/health.ts +2 -0
  395. package/src/nexus/api/http-server.ts +36 -0
  396. package/src/nexus/api/inside-events.ts +226 -0
  397. package/src/nexus/api/meta-api.ts +21 -8
  398. package/src/nexus/api/missions.ts +6 -1
  399. package/src/nexus/api/ops-seats.ts +33 -0
  400. package/src/nexus/api/outputs-route.ts +44 -0
  401. package/src/nexus/api/seat-requests.ts +44 -2
  402. package/src/nexus/api/sessions-store.ts +17 -38
  403. package/src/nexus/api/sub-seat-charters.ts +34 -0
  404. package/src/nexus/api/tabs-mutations.ts +3 -1
  405. package/src/nexus/api/trace.ts +18 -6
  406. package/src/nexus/api/workflow-run-event-bridge.ts +1 -0
  407. package/src/nexus/api/workflows.ts +80 -6
  408. package/src/nexus/index.ts +2 -0
  409. package/src/nexus/kinds/webterm.ts +2 -1
  410. package/src/nexus/webterm/pty.ts +6 -2
  411. package/src/onboarding/cli-auth-status.ts +80 -0
  412. package/src/onboarding/voice-ai.ts +3 -3
  413. package/src/org/seat-budget.ts +48 -0
  414. package/src/org/stall-escalation.ts +63 -0
  415. package/src/outputs/ledger.ts +120 -0
  416. package/src/persona/persona-todo.ts +79 -0
  417. package/src/persona/presets.ts +116 -2
  418. package/src/persona/team-set.ts +155 -0
  419. package/src/plugins/install/plugin-install.ts +14 -0
  420. package/src/plugins/maker/market-bundle.ts +109 -0
  421. package/src/plugins/maker/plugin-maker.ts +254 -11
  422. package/src/plugins/maker/wizard-generate.ts +103 -0
  423. package/src/pod-lease/admission.ts +150 -0
  424. package/src/pod-lease/dependency-state.ts +85 -0
  425. package/src/pod-lease/host-lease.ts +127 -0
  426. package/src/project/project-suggestion.ts +16 -0
  427. package/src/project/session-context.ts +24 -0
  428. package/src/release-loop/checklist.ts +2 -2
  429. package/src/release-loop/feature-store.ts +5 -3
  430. package/src/release-loop/next-md-merge.ts +58 -0
  431. package/src/release-loop/release-note.ts +59 -4
  432. package/src/seat-dispatch/seat-ask.ts +130 -0
  433. package/src/seat-dispatch/seat-questions.ts +100 -0
  434. package/src/seat-loop/seat-loop.ts +548 -29
  435. package/src/seat-loop/seat-report.ts +37 -2
  436. package/src/self-dev/dev-cli.ts +4 -1
  437. package/src/self-dev/dev-pipeline.ts +37 -1
  438. package/src/self-dev/draft-sweep.ts +38 -33
  439. package/src/self-dev/draft-triage-rules.ts +8 -5
  440. package/src/self-dev/fabric-plan-core.ts +297 -0
  441. package/src/self-dev/orchestrate.ts +36 -4
  442. package/src/self-dev/release-path-guard.ts +62 -0
  443. package/src/self-dev/shard-aggregate.ts +230 -0
  444. package/src/self-dev/shard-placement.ts +60 -0
  445. package/src/self-dev/shard-plan.ts +48 -0
  446. package/src/self-dev/shard-pod-run.ts +77 -0
  447. package/src/self-dev/shard-run.ts +105 -0
  448. package/src/self-implement/gate-baseline.ts +1 -1
  449. package/src/self-implement/gate-cli.ts +40 -1
  450. package/src/self-implement/gate-scope.ts +37 -7
  451. package/src/self-implement/graph-variant.ts +171 -0
  452. package/src/self-implement/harness-policy-gates.ts +1 -1
  453. package/src/self-implement/headless-elanous-driver.ts +22 -3
  454. package/src/self-implement/host-regate.ts +22 -1
  455. package/src/self-implement/importer-test-index.ts +2 -1
  456. package/src/self-implement/l8-merge-queue.ts +130 -0
  457. package/src/self-implement/orchestrator.ts +242 -9
  458. package/src/self-implement/route-consumer-test-index.ts +122 -0
  459. package/src/self-implement/seams.ts +72 -11
  460. package/src/self-implement/test-seams.ts +1 -0
  461. package/src/self-implement/uncommitted-work.ts +59 -0
  462. package/src/session/index.ts +36 -13
  463. package/src/skills/gift-install.ts +215 -0
  464. package/src/skills/tools/web-search.ts +4 -1
  465. package/src/steward/launch.ts +42 -26
  466. package/src/steward/steward-cards.ts +17 -9
  467. package/src/steward/triage.ts +46 -14
  468. package/src/task-cards/card-cli.ts +25 -1
  469. package/src/task-orchestrator/mission.ts +5 -0
  470. package/src/task-orchestrator/store.ts +12 -3
  471. package/src/task-orchestrator/surfaces/pod-command-job.ts +29 -7
  472. package/src/task-orchestrator/surfaces/pod-lease.ts +156 -19
  473. package/src/task-orchestrator/surfaces/pod-pool.ts +127 -26
  474. package/src/task-orchestrator/surfaces/self-implement-pod.ts +214 -40
  475. package/src/task-orchestrator/surfaces/self-implement.ts +15 -2
  476. package/src/task-orchestrator/types.ts +2 -0
  477. package/src/telegram-card-followup.ts +16 -96
  478. package/src/telegram-commands.ts +10 -1
  479. package/src/telegram-project-command.ts +71 -0
  480. package/src/telegram-report.ts +21 -17
  481. package/src/telegram.ts +29 -3
  482. package/src/tool-runtime/self-cognition-runtimes.ts +13 -3
  483. package/src/user-config.ts +102 -20
  484. package/src/version/packed-revision.json +1 -1
  485. package/src/web-search/grok.ts +7 -1
  486. package/src/web-search/index.ts +28 -1
  487. package/src/web-search/provider.ts +4 -0
  488. package/src/web-search/x-intent.ts +5 -0
  489. package/src/workflow-runtime/executor.ts +277 -47
  490. package/src/workflow-runtime/index.ts +1 -0
  491. package/src/workflow-runtime/nodes/subworkflow.ts +60 -0
  492. package/src/workflow-runtime/plugin-kind-node.ts +1 -1
  493. package/src/workflow-runtime/schema.ts +79 -4
  494. package/src/workflow-runtime/types.ts +35 -4
  495. package/apps/pwa/out/_next/static/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/2375-cb6debea56cce4fb.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
  505. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
  506. package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
  517. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
  518. package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
  520. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
  521. package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
  523. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +0 -1
  524. package/apps/pwa/out/_next/static/chunks/8195-6fde3d7a39859206.js +0 -10
  525. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
  526. package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +0 -1
  527. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
  528. package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +0 -1
  529. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
  530. package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +0 -1
  531. package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +0 -1
  532. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
  533. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
  534. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +0 -1
  535. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +0 -1
  536. package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +0 -1
  537. package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +0 -1
  538. package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +0 -1
  539. package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +0 -1
  540. package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +0 -1
  541. package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +0 -1
  542. package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +0 -1
  543. package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +0 -1
  544. package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +0 -1
  545. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
  546. package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +0 -1
  547. package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +0 -1
  548. package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +0 -1
  549. package/apps/pwa/out/_next/static/chunks/app/market/page-b31610deda76337b.js +0 -1
  550. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6cb0e1bbefe5fb3d.js +0 -1
  551. package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +0 -1
  552. package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +0 -1
  553. package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +0 -1
  554. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +0 -1
  555. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +0 -1
  556. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +0 -1
  557. package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +0 -1
  558. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +0 -1
  559. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +0 -1
  560. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +0 -1
  561. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e168fa1030af0548.js +0 -1
  562. package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +0 -1
  563. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +0 -1
  564. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
  565. package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +0 -1
  566. package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +0 -1
  567. package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +0 -1
  568. package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +0 -1
  569. package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +0 -1
  570. package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +0 -1
  571. package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +0 -1
  572. package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +0 -1
  573. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-26102fd7350f171e.js +0 -1
  574. package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +0 -1
  575. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +0 -1
  576. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-139ba5c367bbf60a.js +0 -1
  577. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
  578. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
  579. package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +0 -1
  580. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
  581. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
  582. package/apps/pwa/out/_next/static/chunks/webpack-e5bea36f183b25e6.js +0 -1
  583. package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +0 -1
  584. /package/apps/pwa/out/_next/static/{2DsiDfDCks44g1cPUMhiW → Y4zKzL62sakG5momz3SoQ}/_ssgManifest.js +0 -0
@@ -0,0 +1,230 @@
1
+ import {
2
+ allowsBaselineOnlyFailure,
3
+ buildGateBaselineReport,
4
+ type GateBaselineReport,
5
+ type GatePassedTestEvidence,
6
+ } from '../self-implement/gate-baseline.js';
7
+ import type { GateTestShard } from './shard-plan.js';
8
+
9
+ export interface GateShardAttempt {
10
+ shardId: string;
11
+ attempt: number;
12
+ /** A process death, missing report, or incomplete XML is not a test failure. */
13
+ currentJUnit?: string;
14
+ baselineJUnit?: string;
15
+ currentExitCode: number | null;
16
+ baselineExitCode: number | null;
17
+ currentSignal?: string | null;
18
+ baselineSignal?: string | null;
19
+ }
20
+
21
+ export interface GateShardAggregate {
22
+ status: 'passed' | 'failed' | 'unmeasured';
23
+ /** Only these shard identities need another assignment; completed siblings stay intact. */
24
+ retryShardIds: string[];
25
+ report?: GateBaselineReport;
26
+ }
27
+
28
+ interface JUnitCase { file: string; name: string; failure?: string }
29
+
30
+ const decode = (text: string): string => text.replace(/&(#x[0-9a-f]+|#\d+|amp|lt|gt|quot|apos);/gi, (entity, key: string) => {
31
+ if (key[0] === '#') {
32
+ const code = key[1]?.toLowerCase() === 'x' ? parseInt(key.slice(2), 16) : parseInt(key.slice(1), 10);
33
+ return code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : entity;
34
+ }
35
+ return ({ amp: '&', lt: '<', gt: '>', quot: '"', apos: "'" } as Record<string, string>)[key.toLowerCase()] ?? entity;
36
+ });
37
+
38
+ interface XmlElement { name: string; attrs: Record<string, string>; children: XmlElement[]; text: string }
39
+
40
+ /** Strict, dependency-free XML reader for JUnit reports: the whole document must be well formed
41
+ * (one root, balanced tags, nothing but whitespace/comments after the root). CDATA and comments are
42
+ * text, never elements — so a `<testcase>` inside a failure message is not a test case, and a report
43
+ * followed by a truncated tail is rejected as unmeasured rather than read as complete. */
44
+ function parseXml(xml: string): XmlElement | undefined {
45
+ const stack: XmlElement[] = [];
46
+ let root: XmlElement | undefined;
47
+ let i = xml.charCodeAt(0) === 0xfeff ? 1 : 0;
48
+ const appendText = (text: string): boolean => {
49
+ const top = stack.at(-1);
50
+ if (!top) return text.trim() === '';
51
+ top.text += text;
52
+ return true;
53
+ };
54
+ while (i < xml.length) {
55
+ const lt = xml.indexOf('<', i);
56
+ if (lt === -1) return appendText(xml.slice(i)) && !stack.length && root ? root : undefined;
57
+ if (lt > i) {
58
+ const raw = xml.slice(i, lt);
59
+ if (!appendText(decode(raw))) return undefined;
60
+ if (/&(?!(#x[0-9a-f]+|#\d+|amp|lt|gt|quot|apos);)/i.test(raw)) return undefined;
61
+ }
62
+ if (xml.startsWith('<!--', lt)) {
63
+ const close = xml.indexOf('-->', lt + 4);
64
+ if (close === -1) return undefined;
65
+ i = close + 3;
66
+ } else if (xml.startsWith('<![CDATA[', lt)) {
67
+ const close = xml.indexOf(']]>', lt + 9);
68
+ if (close === -1 || !stack.length) return undefined;
69
+ appendText(xml.slice(lt + 9, close));
70
+ i = close + 3;
71
+ } else if (xml.startsWith('<?', lt)) {
72
+ const close = xml.indexOf('?>', lt + 2);
73
+ if (close === -1 || stack.length || root) return undefined;
74
+ i = close + 2;
75
+ } else if (xml.startsWith('<!', lt)) {
76
+ return undefined;
77
+ } else {
78
+ let j = lt + 1;
79
+ let quote: string | undefined;
80
+ for (; j < xml.length; j++) {
81
+ const ch = xml[j]!;
82
+ if (quote) { if (ch === quote) quote = undefined; else if (ch === '<') return undefined; }
83
+ else if (ch === '"' || ch === "'") quote = ch;
84
+ else if (ch === '>') break;
85
+ else if (ch === '<') return undefined;
86
+ }
87
+ if (j >= xml.length) return undefined;
88
+ const inner = xml.slice(lt + 1, j);
89
+ i = j + 1;
90
+ if (inner.startsWith('/')) {
91
+ const name = inner.slice(1).trim();
92
+ const open = stack.pop();
93
+ if (!open || open.name !== name) return undefined;
94
+ if (!stack.length) root = open;
95
+ continue;
96
+ }
97
+ const selfClosing = inner.endsWith('/');
98
+ const body = selfClosing ? inner.slice(0, -1) : inner;
99
+ const head = /^([A-Za-z_][\w.:-]*)/.exec(body);
100
+ if (!head || (!stack.length && root)) return undefined;
101
+ const attrs: Record<string, string> = {};
102
+ let rest = body.slice(head[1]!.length);
103
+ const attrPattern = /^\s+([A-Za-z_][\w.:-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/;
104
+ for (let m = attrPattern.exec(rest); m; m = attrPattern.exec(rest)) {
105
+ if (m[1]! in attrs) return undefined;
106
+ attrs[m[1]!] = decode(m[2] ?? m[3] ?? '');
107
+ rest = rest.slice(m[0].length);
108
+ }
109
+ if (rest.trim() !== '') return undefined;
110
+ const element: XmlElement = { name: head[1]!, attrs, children: [], text: '' };
111
+ stack.at(-1)?.children.push(element);
112
+ if (selfClosing) { if (!stack.length) root = element; }
113
+ else stack.push(element);
114
+ }
115
+ }
116
+ return !stack.length && root ? root : undefined;
117
+ }
118
+
119
+ function textOf(element: XmlElement): string {
120
+ return element.text + element.children.map(textOf).join('');
121
+ }
122
+
123
+ function readJUnit(xml: string, files: readonly string[]): JUnitCase[] | undefined {
124
+ const root = parseXml(xml);
125
+ if (!root || (root.name !== 'testsuites' && root.name !== 'testsuite')) return undefined;
126
+ const expected = new Set(files);
127
+ if (expected.size === 0) return undefined;
128
+ const cases: JUnitCase[] = [];
129
+ const counts = new Map<string, number>();
130
+ const executed = new Set<string>();
131
+ let failures = 0;
132
+ let errors = 0;
133
+ const matchesCount = (value: string | undefined, actual: number) => value === undefined
134
+ || (/^(0|[1-9]\d*)$/.test(value) && Number(value) === actual);
135
+ // Each suite's tests attribute covers its descendants too: compare subtree totals.
136
+ const walkSuite = (suite: XmlElement, parentFile: string | undefined): { tests: number; failures: number; errors: number } | undefined => {
137
+ const file = suite.attrs.file ?? parentFile;
138
+ const declared = Number(suite.attrs.tests);
139
+ if ((file && !expected.has(file)) || !Number.isSafeInteger(declared) || declared < 0) return undefined;
140
+ const total = { tests: 0, failures: 0, errors: 0 };
141
+ for (const child of suite.children) {
142
+ if (child.name === 'testsuite') {
143
+ const sub = walkSuite(child, file);
144
+ if (!sub) return undefined;
145
+ total.tests += sub.tests; total.failures += sub.failures; total.errors += sub.errors;
146
+ } else if (child.name === 'testcase') {
147
+ const caseFile = child.attrs.file ?? file;
148
+ const leaf = child.attrs.name;
149
+ if (!caseFile || !expected.has(caseFile) || !leaf || (file && file !== caseFile)) return undefined;
150
+ total.tests++;
151
+ counts.set(caseFile, (counts.get(caseFile) ?? 0) + 1);
152
+ if (child.children.some((c) => c.name === 'skipped')) return undefined;
153
+ const classname = child.attrs.classname;
154
+ const name = classname && classname !== caseFile ? `${classname} > ${leaf}` : leaf;
155
+ const identity = `${caseFile}\u0000${name}`;
156
+ if (executed.has(identity)) return undefined;
157
+ executed.add(identity);
158
+ const failure = child.children.find((c) => c.name === 'failure' || c.name === 'error');
159
+ if (failure?.name === 'failure') { failures++; total.failures++; }
160
+ else if (failure) { errors++; total.errors++; }
161
+ const diagnostic = failure ? [failure.attrs.message, textOf(failure).trim()].filter(Boolean).join('\n') : undefined;
162
+ cases.push({ file: caseFile, name, ...(failure ? { failure: diagnostic || 'JUnit test failed' } : {}) });
163
+ }
164
+ }
165
+ if (total.tests !== declared || !matchesCount(suite.attrs.failures, total.failures) || !matchesCount(suite.attrs.errors, total.errors)) return undefined;
166
+ return total;
167
+ };
168
+ const suites = root.name === 'testsuite' ? [root] : root.children.filter((c) => c.name === 'testsuite');
169
+ if (suites.length === 0 || (root.name === 'testsuites' && root.children.some((c) => c.name === 'testcase'))) return undefined;
170
+ let counted = 0;
171
+ for (const suite of suites) {
172
+ const total = walkSuite(suite, undefined);
173
+ if (!total) return undefined;
174
+ counted += total.tests;
175
+ }
176
+ if (root.name === 'testsuites' && (!matchesCount(root.attrs.tests, counted)
177
+ || !matchesCount(root.attrs.failures, failures) || !matchesCount(root.attrs.errors, errors))) return undefined;
178
+ if (cases.length === 0 || [...expected].some((file) => !counts.has(file))) return undefined;
179
+ return cases;
180
+ }
181
+
182
+ function asGateLog(cases: readonly JUnitCase[]): string {
183
+ return cases.map(({ file, name, failure }) => `${file}:\n${failure === undefined ? `(pass) ${name}` : `${failure}\n(fail) ${name}`}`).join('\n');
184
+ }
185
+
186
+ /** Select the latest complete attempt per shard, then classify the union with the existing full-suite gate ruler. */
187
+ export function aggregateGateTestShards(shards: readonly GateTestShard[], attempts: readonly GateShardAttempt[]): GateShardAggregate {
188
+ const selected = new Map<string, GateShardAttempt>();
189
+ for (const attempt of attempts) {
190
+ if (!Number.isSafeInteger(attempt.attempt) || attempt.attempt < 1) throw new Error('invalid shard attempt');
191
+ if (!shards.some((shard) => shard.id === attempt.shardId)) throw new Error(`unknown shard: ${attempt.shardId}`);
192
+ const prior = selected.get(attempt.shardId);
193
+ if (prior?.attempt === attempt.attempt) throw new Error(`duplicate shard attempt: ${attempt.shardId}`);
194
+ if (!prior || prior.attempt < attempt.attempt) selected.set(attempt.shardId, attempt);
195
+ }
196
+ const retryShardIds: string[] = [];
197
+ const current: JUnitCase[] = [];
198
+ const baseline: JUnitCase[] = [];
199
+ for (const shard of shards) {
200
+ const attempt = selected.get(shard.id);
201
+ const head = attempt?.currentJUnit === undefined ? undefined : readJUnit(attempt.currentJUnit, shard.files);
202
+ const base = attempt?.baselineJUnit === undefined ? undefined : readJUnit(attempt.baselineJUnit, shard.files);
203
+ if (!attempt || attempt.currentExitCode === null || attempt.baselineExitCode === null
204
+ || attempt.currentSignal || attempt.baselineSignal
205
+ || ![0, 1].includes(attempt.currentExitCode) || ![0, 1].includes(attempt.baselineExitCode)
206
+ || !head || !base
207
+ || (attempt.currentExitCode === 0 && head.some((test) => test.failure !== undefined))
208
+ || (attempt.baselineExitCode === 0 && base.some((test) => test.failure !== undefined))
209
+ || (attempt.currentExitCode !== 0 && head.every((test) => test.failure === undefined))
210
+ || (attempt.baselineExitCode !== 0 && base.every((test) => test.failure === undefined))) {
211
+ retryShardIds.push(shard.id);
212
+ continue;
213
+ }
214
+ current.push(...head);
215
+ baseline.push(...base);
216
+ }
217
+ if (retryShardIds.length > 0 || shards.length === 0) return { status: 'unmeasured', retryShardIds };
218
+ if (current.every((test) => test.failure === undefined)) return { status: 'passed', retryShardIds: [] };
219
+ const passedTestEvidence: GatePassedTestEvidence = {
220
+ status: 'available',
221
+ tests: baseline.filter((test) => test.failure === undefined).map(({ file, name }) => ({ file, name: `${file} > ${name}` })),
222
+ };
223
+ const report = buildGateBaselineReport(asGateLog(current), {
224
+ status: baseline.some((test) => test.failure !== undefined) ? 'test-fail' : 'pass',
225
+ output: asGateLog(baseline),
226
+ passedTestEvidence,
227
+ log: 'shard baseline JUnit complete',
228
+ });
229
+ return { status: allowsBaselineOnlyFailure(report) ? 'passed' : 'failed', retryShardIds: [], report };
230
+ }
@@ -0,0 +1,60 @@
1
+ import type { PoolLeaseMeasure } from '../task-orchestrator/surfaces/pod-lease.js';
2
+ import type { GateTestShard } from './shard-plan.js';
3
+
4
+ export interface ShardPlacement {
5
+ shardId: string;
6
+ context: string;
7
+ }
8
+
9
+ /** Assign only to measurable, ready Pod lease members. No remote work is performed here. */
10
+ export function assignGateShardsToPool(
11
+ shards: readonly GateTestShard[],
12
+ pool: PoolLeaseMeasure,
13
+ previous: readonly ShardPlacement[] = [],
14
+ failedContext?: string,
15
+ ): ShardPlacement[] {
16
+ const ids = new Set(shards.map((shard) => shard.id));
17
+ if (ids.size !== shards.length) throw new Error('duplicate shard id');
18
+ if (shards.some((shard) => !Number.isFinite(shard.plannedRssMb) || shard.plannedRssMb <= 0)) throw new Error('invalid shard RSS');
19
+ const reservationBytes = (shard: GateTestShard) => Math.max(4, Math.ceil(shard.plannedRssMb / 1024)) * 1024 ** 3;
20
+ if (previous.some((placement) => !ids.has(placement.shardId))) throw new Error('unknown shard placement');
21
+ const retained = previous.filter((placement) => placement.context !== failedContext);
22
+ if (new Set(previous.map((placement) => placement.shardId)).size !== previous.length) throw new Error('duplicate shard placement');
23
+ const members = pool.members.filter((member) => member.context !== failedContext && member.reason === null
24
+ && member.running !== null && member.pending !== null && member.availableMemoryByNodeBytes !== null
25
+ && Number.isSafeInteger(member.capacity) && member.capacity > 0
26
+ && member.availableMemoryByNodeBytes.every((bytes) => Number.isFinite(bytes) && bytes >= 0));
27
+ if (new Set(members.map((member) => member.context)).size !== members.length) throw new Error('duplicate pool context');
28
+ const state = members.map((member) => ({
29
+ context: member.context,
30
+ slots: Math.max(0, member.capacity - member.running! - member.pending!),
31
+ nodes: [...member.availableMemoryByNodeBytes!],
32
+ weight: member.availableMemoryByNodeBytes!.reduce((sum, bytes) => sum + Math.max(0, bytes), 0),
33
+ assigned: 0,
34
+ }));
35
+ for (const placement of retained) {
36
+ const member = state.find((entry) => entry.context === placement.context);
37
+ const shard = shards.find((entry) => entry.id === placement.shardId)!;
38
+ if (!member) throw new Error(`placed shard has no healthy pool member: ${placement.shardId}`);
39
+ const node = member.nodes.findIndex((bytes) => bytes >= reservationBytes(shard));
40
+ if (node < 0 || member.slots < 1) throw new Error(`placed shard exceeds pool lease: ${placement.shardId}`);
41
+ member.nodes[node]! -= reservationBytes(shard);
42
+ member.slots--;
43
+ member.assigned++;
44
+ }
45
+ const assigned = new Map(retained.map((placement) => [placement.shardId, placement.context]));
46
+ for (const shard of shards) {
47
+ if (assigned.has(shard.id)) continue;
48
+ const bytes = reservationBytes(shard);
49
+ const eligible = state.filter((member) => member.slots > 0 && member.nodes.some((free) => free >= bytes))
50
+ .sort((a, b) => a.assigned / a.weight - b.assigned / b.weight || b.weight - a.weight || a.context.localeCompare(b.context));
51
+ const member = eligible[0];
52
+ if (!member) throw new Error(`no pool lease memory for shard: ${shard.id}`);
53
+ const node = member.nodes.findIndex((free) => free >= bytes);
54
+ member.nodes[node]! -= bytes;
55
+ member.slots--;
56
+ member.assigned++;
57
+ assigned.set(shard.id, member.context);
58
+ }
59
+ return shards.map((shard) => ({ shardId: shard.id, context: assigned.get(shard.id)! }));
60
+ }
@@ -0,0 +1,48 @@
1
+ export interface GateTestShard {
2
+ id: string;
3
+ files: string[];
4
+ plannedRssMb: number;
5
+ plannedSeconds: number;
6
+ }
7
+
8
+ /** RSS values are the per-file rss_mb observations; the budget is in GiB (1 GiB = 1024 MB here). */
9
+ export function planGateTestShards(
10
+ files: readonly string[],
11
+ rssMb: ReadonlyMap<string, number>,
12
+ seconds: ReadonlyMap<string, number>,
13
+ budgetGiB: number,
14
+ shardCount = Number.POSITIVE_INFINITY,
15
+ ): GateTestShard[] {
16
+ if (!Number.isFinite(budgetGiB) || budgetGiB <= 0) throw new Error('invalid shard memory budget');
17
+ if (shardCount !== Number.POSITIVE_INFINITY && (!Number.isSafeInteger(shardCount) || shardCount < 1)) throw new Error('invalid shard count');
18
+ const capacity = budgetGiB * 1024;
19
+ const unique = [...new Set(files)];
20
+ for (const file of unique) {
21
+ const rss = rssMb.get(file);
22
+ const duration = seconds.get(file);
23
+ if (!file || rss === undefined || !Number.isFinite(rss) || rss <= 0
24
+ || duration === undefined || !Number.isFinite(duration) || duration < 0) {
25
+ throw new Error(`missing or invalid RSS/seconds measurement: ${file}`);
26
+ }
27
+ if (rss > capacity) throw new Error(`test file exceeds shard memory budget: ${file}`);
28
+ }
29
+ const sorted = unique.sort((a, b) =>
30
+ rssMb.get(b)! - rssMb.get(a)! || seconds.get(b)! - seconds.get(a)! || (a < b ? -1 : a > b ? 1 : 0));
31
+ const shards: GateTestShard[] = [];
32
+ for (const file of sorted) {
33
+ const rss = rssMb.get(file)!;
34
+ const duration = seconds.get(file)!;
35
+ const eligible = shards.filter((shard) => shard.plannedRssMb + rss <= capacity)
36
+ .sort((a, b) => a.plannedSeconds - b.plannedSeconds || a.plannedRssMb - b.plannedRssMb || a.id.localeCompare(b.id));
37
+ let shard = eligible[0];
38
+ if ((!shard || (shardCount !== Number.POSITIVE_INFINITY && shards.length < Math.min(shardCount, sorted.length))) && shards.length < shardCount) {
39
+ shard = { id: `shard-${shards.length + 1}`, files: [], plannedRssMb: 0, plannedSeconds: 0 };
40
+ shards.push(shard);
41
+ }
42
+ if (!shard) throw new Error(`test files exceed ${shardCount} shard memory budgets`);
43
+ shard.files.push(file);
44
+ shard.plannedRssMb += rss;
45
+ shard.plannedSeconds += duration;
46
+ }
47
+ return shards;
48
+ }
@@ -0,0 +1,77 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import type { runPodCommand } from '../task-orchestrator/surfaces/pod-command-job.js';
4
+ import type { PoolLeaseMeasure } from '../task-orchestrator/surfaces/pod-lease.js';
5
+ import { aggregateGateTestShards, type GateShardAttempt } from './shard-aggregate.js';
6
+ import { assignGateShardsToPool, type ShardPlacement } from './shard-placement.js';
7
+ import type { GateTestShard } from './shard-plan.js';
8
+ import type { ShardRunResult } from './shard-run.js';
9
+
10
+ type PodShardCommand = typeof runPodCommand;
11
+
12
+ /** Execute already-measured GT2 bundles through the existing Pod command Job, never through SSH. */
13
+ export async function runGateShardsOnPod(
14
+ shards: readonly GateTestShard[],
15
+ pool: PoolLeaseMeasure,
16
+ poolSpec: string,
17
+ currentCommit: string,
18
+ baselineCommit: string,
19
+ deps: { run?: PodShardCommand; read?: (path: string) => string; measure?: () => PoolLeaseMeasure } = {},
20
+ ): Promise<ShardRunResult & { placements: ShardPlacement[] }> {
21
+ const run = deps.run ?? (await import('../task-orchestrator/surfaces/pod-command-job.js')).runPodCommand;
22
+ const read = deps.read ?? ((path: string) => readFileSync(path, 'utf8'));
23
+ const { parsePodPool } = await import('../task-orchestrator/surfaces/pod-pool.js');
24
+ const poolMembers = parsePodPool(poolSpec);
25
+ const specs = poolSpec.split(',').map((spec) => spec.trim()).filter(Boolean);
26
+ const members = new Map(poolMembers.map((member, index) => [member.context, specs[index]!]));
27
+ const measure = deps.measure ?? (async () => (await import('../task-orchestrator/surfaces/pod-lease.js')).measurePoolLease(poolMembers));
28
+ for (const commit of [currentCommit, baselineCommit]) {
29
+ if (!/^[0-9a-f]{40}$/.test(commit)) throw new Error('Pod shard source requires a 40-character commit SHA');
30
+ }
31
+ const attempts: GateShardAttempt[] = [];
32
+ const planned = [...shards];
33
+ let placements = assignGateShardsToPool(planned, pool);
34
+ if (placements.some((placement) => !members.has(placement.context))) throw new Error('leased pool member is not in Pod pool spec');
35
+ let pending = planned;
36
+ for (let round = 1; round <= 2 && pending.length; round++) {
37
+ const failed = new Set<string>();
38
+ for (const shard of pending) {
39
+ const context = placements.find((entry) => entry.shardId === shard.id)!.context;
40
+ let current: { exitCode: number | null; junit?: string } = { exitCode: null };
41
+ let base: { exitCode: number | null; junit?: string } = { exitCode: null };
42
+ try {
43
+ // Pin the context by narrowing the existing pool spec; do not send a command over SSH.
44
+ const member = members.get(context);
45
+ if (!member) throw new Error(`pool context not in spec: ${context}`);
46
+ const executeOnMember = async (commit: string) => {
47
+ const report = `shard-${shard.id}.xml`;
48
+ const result = await run({ command: ['bash', '-c', 'mkdir -p "$HOME/outbox"; bun install --frozen-lockfile >/dev/null && bun test --reporter=junit --reporter-outfile="$HOME/outbox/$1" "${@:2}"', 'shard', report, ...shard.files],
49
+ pool: member,
50
+ clone: true, memoryLimit: `${Math.max(4, Math.ceil(shard.plannedRssMb / 1024))}Gi`,
51
+ source: { kind: 'commit', sha: commit } });
52
+ return { exitCode: result.exitCode, junit: read(join(result.artifactsDir, report)) };
53
+ };
54
+ current = await executeOnMember(currentCommit);
55
+ base = await executeOnMember(baselineCommit);
56
+ } catch {
57
+ failed.add(context);
58
+ }
59
+ attempts.push({ shardId: shard.id, attempt: round, currentExitCode: current.exitCode, baselineExitCode: base.exitCode,
60
+ ...(current.junit ? { currentJUnit: current.junit } : {}), ...(base.junit ? { baselineJUnit: base.junit } : {}) });
61
+ }
62
+ const retry = new Set(aggregateGateTestShards(planned, attempts).retryShardIds);
63
+ pending = planned.filter((shard) => retry.has(shard.id));
64
+ if (pending.length && round === 1 && failed.size) {
65
+ try {
66
+ const available = await measure();
67
+ const toMove = new Set(pending.filter((shard) => failed.has(placements.find((entry) => entry.shardId === shard.id)!.context)).map((shard) => shard.id));
68
+ const reassigned = assignGateShardsToPool(planned.filter((shard) => toMove.has(shard.id)),
69
+ { members: available.members.filter((member) => !failed.has(member.context) && members.has(member.context)) });
70
+ placements = placements.map((placement) => reassigned.find((next) => next.shardId === placement.shardId) ?? placement);
71
+ } catch (error) {
72
+ return { aggregate: { status: 'unmeasured', retryShardIds: [...retry] }, shards: planned, attempts, placements, reason: String(error) };
73
+ }
74
+ }
75
+ }
76
+ return { aggregate: aggregateGateTestShards(planned, attempts), shards: planned, attempts, placements };
77
+ }
@@ -0,0 +1,105 @@
1
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { prepareDeterministicChildEnvironment } from '../../scripts/lib/deterministic-env.js';
5
+ import { withBaselineWorktree } from '../self-implement/gate-baseline.js';
6
+ import { aggregateGateTestShards, type GateShardAttempt, type GateShardAggregate } from './shard-aggregate.js';
7
+ import { planGateTestShards, type GateTestShard } from './shard-plan.js';
8
+
9
+ export interface ShardRunResult {
10
+ aggregate: GateShardAggregate;
11
+ shards: GateTestShard[];
12
+ attempts: GateShardAttempt[];
13
+ reason?: string;
14
+ }
15
+
16
+ export type ShardProcess = (cwd: string, files: readonly string[]) => {
17
+ exitCode: number | null;
18
+ signal?: string | null;
19
+ junit?: string;
20
+ rssMb?: number;
21
+ seconds?: number;
22
+ };
23
+
24
+ /** One process per bundle and per revision; a failed runner is retried only for its own bundle. */
25
+ export function runShardedGateTests(
26
+ cwd: string,
27
+ files: readonly string[],
28
+ baseRef: string,
29
+ count = 2,
30
+ run: ShardProcess = runBunShard,
31
+ baselineWorktree: typeof withBaselineWorktree = withBaselineWorktree,
32
+ budgetGiB = 8,
33
+ ): ShardRunResult {
34
+ if (!Number.isSafeInteger(count) || count < 1) throw new Error('invalid --shards count');
35
+ const rss = new Map<string, number>();
36
+ const seconds = new Map<string, number>();
37
+ for (const file of [...new Set(files)]) {
38
+ let sample: ReturnType<ShardProcess>;
39
+ try {
40
+ sample = run(cwd, [file]);
41
+ } catch (error) {
42
+ return { aggregate: { status: 'unmeasured', retryShardIds: [] }, shards: [], attempts: [], reason: `measurement failed: ${file}: ${String(error)}` };
43
+ }
44
+ if (![0, 1].includes(sample.exitCode ?? -1) || sample.signal || !sample.junit || !Number.isFinite(sample.rssMb) || !Number.isFinite(sample.seconds)) {
45
+ return { aggregate: { status: 'unmeasured', retryShardIds: [] }, shards: [], attempts: [], reason: `measurement unavailable: ${file}` };
46
+ }
47
+ rss.set(file, sample.rssMb!);
48
+ seconds.set(file, sample.seconds!);
49
+ }
50
+ let shards: GateTestShard[];
51
+ try {
52
+ shards = planGateTestShards(files, rss, seconds, budgetGiB, count);
53
+ } catch (error) {
54
+ return { aggregate: { status: 'unmeasured', retryShardIds: [] }, shards: [], attempts: [], reason: String(error) };
55
+ }
56
+ const attempts: GateShardAttempt[] = [];
57
+ const baseline = baselineWorktree(cwd, baseRef, (dir) => {
58
+ let pending = shards;
59
+ for (let round = 1; round <= 2 && pending.length; round++) {
60
+ for (const shard of pending) {
61
+ const runSafely = (location: string): ReturnType<ShardProcess> => {
62
+ try { return run(location, shard.files); }
63
+ catch { return { exitCode: null }; }
64
+ };
65
+ const current = runSafely(cwd);
66
+ const base = runSafely(dir);
67
+ attempts.push({
68
+ shardId: shard.id, attempt: round,
69
+ currentJUnit: current.junit, baselineJUnit: base.junit,
70
+ currentExitCode: current.exitCode, baselineExitCode: base.exitCode,
71
+ currentSignal: current.signal, baselineSignal: base.signal,
72
+ });
73
+ }
74
+ const retry = new Set(aggregateGateTestShards(shards, attempts).retryShardIds);
75
+ pending = shards.filter((shard) => retry.has(shard.id));
76
+ }
77
+ return aggregateGateTestShards(shards, attempts);
78
+ });
79
+ if ('retryShardIds' in baseline) return { aggregate: baseline, shards, attempts };
80
+ return { aggregate: { status: 'unmeasured', retryShardIds: shards.map((shard) => shard.id) }, shards, attempts, reason: baseline.log };
81
+ }
82
+
83
+ export function runBunShard(cwd: string, files: readonly string[]): ReturnType<ShardProcess> {
84
+ const dir = mkdtempSync(join(tmpdir(), 'elanous-shard-junit-'));
85
+ const report = join(dir, 'result.xml');
86
+ const isolated = prepareDeterministicChildEnvironment('elanous-shard-test-env-');
87
+ try {
88
+ const measured = files.length === 1;
89
+ const args = ['test', '--reporter=junit', `--reporter-outfile=${report}`, ...files];
90
+ const started = performance.now();
91
+ const result = Bun.spawnSync(['bun', ...args], {
92
+ cwd, env: isolated.env, stdout: 'pipe', stderr: 'pipe', timeout: 300_000,
93
+ });
94
+ return {
95
+ exitCode: result.exitCode,
96
+ signal: result.signalCode ? String(result.signalCode) : undefined,
97
+ junit: existsSync(report) ? readFileSync(report, 'utf8') : undefined,
98
+ rssMb: measured ? result.resourceUsage.maxRSS / (1024 * 1024) : undefined,
99
+ seconds: measured ? (performance.now() - started) / 1000 : undefined,
100
+ };
101
+ } finally {
102
+ isolated.cleanup();
103
+ rmSync(dir, { recursive: true, force: true });
104
+ }
105
+ }
@@ -764,7 +764,7 @@ export function classifyBaselineProcess(result: ProcessResult, budget?: { timeou
764
764
  };
765
765
  }
766
766
 
767
- function withBaselineWorktree<T>(cwd: string, baseRef: string, run: (baselineDir: string) => T): T | BaselineProcessResult {
767
+ export function withBaselineWorktree<T>(cwd: string, baseRef: string, run: (baselineDir: string) => T): T | BaselineProcessResult {
768
768
  const baselineDir = mkdtempSync(join(tmpdir(), 'elanous-gate-baseline-'));
769
769
  let attached = false;
770
770
  try {
@@ -15,16 +15,19 @@ import { resolveGateScope } from './gate-scope.js';
15
15
  import { debug } from '../debug/log.js';
16
16
  import { buildDocumentGuardianIndex, formatDocumentGuardians } from './document-guardian-index.js';
17
17
  import { buildImporterTestIndex, isTestPath, type ImporterTestIndex } from './importer-test-index.js';
18
+ import { buildRouteConsumerTestIndex } from './route-consumer-test-index.js';
18
19
  import { gitChangedFiles, observeGateBaselineLocation } from './seams.js';
19
20
  import {
20
21
  collectBaseLandingCommits,
21
22
  overlapWithCurrentChanges,
22
23
  } from '../cli/pr-granularity.js';
23
24
  import { listWorktrees } from '../git-fs/worktree.js';
25
+ import { runShardedGateTests } from '../self-dev/shard-run.js';
24
26
 
25
27
  interface SelfGateCliOptions {
26
28
  base?: string;
27
29
  pr?: string;
30
+ shards?: number;
28
31
  }
29
32
 
30
33
  interface ProcessResult {
@@ -74,6 +77,7 @@ interface SelfGateCliDeps {
74
77
  readFile?: (path: string) => string | undefined;
75
78
  runTests?: (cwd: string, files: readonly string[]) => ProcessResult;
76
79
  runBaseline?: (cwd: string, files: readonly string[], baseRef: string) => BaselineProcessResult;
80
+ runShards?: typeof runShardedGateTests;
77
81
  runIsolationGate?: (out: GateOutput) => number;
78
82
  runMockModuleRestoreGate?: (out: GateOutput) => number;
79
83
  /** 모델 이름 하드코딩 래칫(🅕 #20521) — `pr land` 만 물고 하니스 무인 병합은 안 물면 위반이 main 에 들어가 남의 착지를 막는다(🅣 2026-09-25). */
@@ -571,11 +575,16 @@ function logGateCliBaseline(cwd: string, payload: GateCliBaselinePayload): void
571
575
 
572
576
  export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, deps: SelfGateCliDeps = {}): SelfGateCliResult {
573
577
  if (options.base && options.pr) throw new Error('use either --base <ref> or --pr <number>, not both');
578
+ if (options.shards !== undefined && (!Number.isSafeInteger(options.shards) || options.shards < 1)) throw new Error('invalid --shards count');
574
579
  const selection = (deps.changedFiles ?? ((dir, input) => defaultChangedFiles(dir, input, deps.runCommand)))(cwd, options);
575
580
  const exists = deps.exists ?? ((path) => existsSync(`${cwd}/${path}`));
576
581
  const importerIndex = buildGateImporterTestIndex(selection.files, cwd, deps.runCommand);
577
582
  const lookupFailed = importerIndex && 'lookupFailed' in importerIndex ? importerIndex.lookupFailed : undefined;
578
- const scope = resolveGateScope(selection.files, exists, importerIndex && !('lookupFailed' in importerIndex) ? importerIndex : undefined);
583
+ const availableIndex = importerIndex && !('lookupFailed' in importerIndex) ? importerIndex : undefined;
584
+ const routeIndex = availableIndex?.testPaths
585
+ ? buildRouteConsumerTestIndex(cwd, availableIndex.testPaths, selection.files, selection.baseRef) : null;
586
+ const scope = resolveGateScope(selection.files, exists, availableIndex,
587
+ { routeConsumerTestIndex: routeIndex ?? undefined });
579
588
  const testFiles = [...(scope.testArgs ?? [])];
580
589
  const unrunImporterTotal = lookupFailed ? null : (scope.importerTestsNotRun?.total ?? 0);
581
590
  const importerLookupFailed = Boolean(lookupFailed);
@@ -601,6 +610,9 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
601
610
  // 🚨 러너 우주 «밖» — 형제들과 같은 정책으로 «말만» 한다(`OBS-T418`).
602
611
  formatUnrunnableChangeKinds(selection.files),
603
612
  formatUnrunImporterTests(scope.importerTestsNotRun, lookupFailed),
613
+ `caller tests: ${lookupFailed ? `lookup failed (${lookupFailed})` : scope.callerTests.map(({ file, reasons }) => `${file} (${reasons.join('+')})`).join(', ') || (routeIndex?.lookupFailures.length ? 'route lookup incomplete' : '(none)')}`,
614
+ ...(routeIndex?.lookupFailures.length ? [`⚠️ caller route base lookup failed — unmeasured: ${routeIndex.lookupFailures.map(({ source, reason }) => `${source} (${reason})`).join(', ')}`] : []),
615
+ ...(scope.callerTestsOverflow.length ? [`⚠️ caller test cap exceeded — not run: ${scope.callerTestsOverflow.map(({ file, reasons }) => `${file} (${reasons.join('+')})`).join(', ')}`] : []),
604
616
  changedTestCountNote(cwd, selection.files, selection.baseRef, deps.runCommand, deps.readFile),
605
617
  ];
606
618
  if (options.pr) {
@@ -629,6 +641,33 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
629
641
  return { exitCode: androidPassed && iosPassed && pwaPassed && policyPassed ? 0 : 1, lines, changedFiles: selection.files, testFiles: [], unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
630
642
  }
631
643
 
644
+ if (options.shards !== undefined) {
645
+ const sharded = (deps.runShards ?? runShardedGateTests)(cwd, testFiles, selection.baseRef, options.shards);
646
+ lines.push(`shards: ${sharded.shards.length} (${sharded.shards.map((shard) => `${shard.id}=${shard.files.join(',')}`).join('; ')})`);
647
+ for (const attempt of sharded.attempts) lines.push(`shard attempt: ${attempt.shardId} #${attempt.attempt}`);
648
+ const { aggregate } = sharded;
649
+ if (aggregate.status === 'unmeasured') lines.push(`shards: unmeasured (${aggregate.retryShardIds.join(', ') || sharded.reason || 'no complete JUnit'})`);
650
+ else if (aggregate.report) {
651
+ const report = aggregate.report;
652
+ logGateCliBaseline(cwd, {
653
+ introduced: report.introduced, preexisting: report.preexisting, unknown: report.unknown,
654
+ preconditionUnmet: report.preconditionUnmet, timedOut: report.timedOut,
655
+ timeoutPassedAtBase: report.timeoutPassedAtBase, failures: report.failures.slice(0, 20),
656
+ baselineFiles: report.files, baselineStatus: report.baselineStatus, unrunImporterTotal, lookupFailed: importerLookupFailed,
657
+ });
658
+ lines.push(formatPartialObservationNote(unrunImporterTotal ?? 0, formatGateBaselineNote(report, unrunImporterTotal ?? 0)));
659
+ } else {
660
+ lines.push(formatPartialObservationNote(unrunImporterTotal ?? 0, `tests: pass (${testFiles.length} files)`));
661
+ logGateCliBaseline(cwd, { ...ZERO_GATE_BASELINE, unrunImporterTotal, lookupFailed: importerLookupFailed });
662
+ }
663
+ const policyPassed = runPolicyGates(selection.files, cwd, lines, deps);
664
+ return {
665
+ exitCode: aggregate.status !== 'passed' || !policyPassed || !androidPassed || !iosPassed || !pwaPassed ? 1 : 0,
666
+ lines, changedFiles: selection.files, testFiles, unverified: scope.unverified,
667
+ documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests,
668
+ };
669
+ }
670
+
632
671
  const test = (deps.runTests ?? ((dir, files) => (deps.runCommand ?? defaultRunCommand)('bun', ['test', ...files], dir)))(cwd, testFiles);
633
672
  const worktreeLog = formatProcessOutput(test);
634
673
  if (test.status === 0 && !test.error) {