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,229 @@
1
+ #!/usr/bin/env bun
2
+ import { existsSync, mkdtempSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { basename, join, posix, resolve } from 'node:path';
5
+ import { debug } from '../../src/debug/log.js';
6
+ import { getUserConfig } from '../../src/user-config.js';
7
+ import { effectiveInstanceRoot, releaseLedgerRoot } from '../../src/instance/resolve.js';
8
+ import { getElanousConfigDirOverride } from '../../src/elanous-config-dir.js';
9
+ import { baselineCommit } from './gate-node.js';
10
+ import { diffFailures, junitFailures } from './gate-diff.js';
11
+ import { finishNode, readGraphContext, runCommand, type CommandResult, type CommandRunner, type GraphContext } from './node-verdict.js';
12
+
13
+ const sha = /^[a-f0-9]{7,40}$/i;
14
+ const testFile = /\.test\.tsx?$/;
15
+ const hostName = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
16
+ const quote = (s: string) => `'${s.replaceAll("'", "'\\''")}'`;
17
+
18
+ export interface MacSmokeDeps {
19
+ context?: GraphContext;
20
+ configHost?: () => unknown;
21
+ baseline?: (version: string) => string;
22
+ changedFiles?: (baseline: string, commit: string) => string[];
23
+ findImporters?: (sources: string[], commit: string) => string[];
24
+ repo?: string;
25
+ }
26
+
27
+ export interface MacSmokeResult extends Record<string, unknown> {
28
+ outcome: 'ok' | 'fail' | 'error';
29
+ verdict: 'pass' | 'fail';
30
+ summary: string;
31
+ files: number;
32
+ newFailures: number;
33
+ preexisting: number;
34
+ smoke: 'pass' | 'fail' | 'unmeasured' | 'skipped';
35
+ }
36
+
37
+ function requireOk(result: CommandResult, stage: string): string {
38
+ if (result.status !== 0) throw new Error(`${stage} 실패`);
39
+ return result.stdout;
40
+ }
41
+
42
+ function names(output: string): string[] {
43
+ return output.split('\0').filter(Boolean);
44
+ }
45
+
46
+ function safeFile(file: string): boolean {
47
+ return !file.startsWith('/') && !file.split('/').includes('..') && /^[\w./@+-]+$/.test(file);
48
+ }
49
+
50
+ function changedTests(run: CommandRunner, repo: string, base: string, cut: string): string[] {
51
+ return names(requireOk(run('git', ['diff', '--name-only', '-z', `${base}..${cut}`], repo), '변경 파일 조회'));
52
+ }
53
+
54
+ function importers(run: CommandRunner, repo: string, sources: string[], commit: string): string[] {
55
+ const picked = new Set<string>();
56
+ for (const source of sources) {
57
+ const stem = source.replace(/\.(?:tsx?|jsx?|mjs|cjs)$/, '');
58
+ const directoryIndex = basename(stem) === 'index' ? posix.dirname(stem) : undefined;
59
+ const search = run('git', ['grep', '-l', '-z', '-F', '-e', basename(stem), '-e', source, '-e', stem, ...(directoryIndex ? ['-e', directoryIndex] : []), commit, '--', '*.test.ts', '*.test.tsx'], repo);
60
+ // git grep returns 1 for no matches; other errors mean the selection could not be measured.
61
+ if (search.status !== 0 && search.status !== 1) throw new Error('시험 import 검색 실패');
62
+ for (const entry of names(search.stdout)) {
63
+ if (!entry.startsWith(`${commit}:`)) throw new Error('시험 import 경로 측정 불가');
64
+ const file = entry.slice(commit.length + 1);
65
+ if (!safeFile(file) || !testFile.test(file)) throw new Error('시험 import 경로 측정 불가');
66
+ const contents = requireOk(run('git', ['show', `${commit}:${file}`], repo), '컷 시험 읽기');
67
+ if (contents.includes(source)) { picked.add(file); continue; }
68
+ for (const match of contents.matchAll(/(?:\bfrom\s*|\bimport\s*(?:\(\s*)?|\brequire\s*\()\s*['"]([^'"]+)['"]/g)) {
69
+ const spec = match[1]!;
70
+ const target = spec.startsWith('.') ? posix.normalize(posix.join(posix.dirname(file), spec)) : spec.replace(/^@\//, '');
71
+ const targetStem = target.replace(/\.(?:tsx?|jsx?|mjs|cjs)$/, '');
72
+ if (target === source || targetStem === stem || (directoryIndex && targetStem === directoryIndex)) { picked.add(file); break; }
73
+ }
74
+ }
75
+ }
76
+ return [...picked];
77
+ }
78
+
79
+ // Collection proof comes from bun's junit file (one top-level <testsuite file=…> per collected file):
80
+ // non-TTY bun prints no per-file headers when everything passes, so stdout cannot prove which files ran (10-03 real run).
81
+ export function testFailures(result: CommandResult, junit: string | null, selected: string[]): string[] {
82
+ const output = `${result.stdout}\n${result.stderr}`.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
83
+ const summary = /Ran \d+ tests? across (\d+) files?/.exec(output);
84
+ if (junit === null || result.status === null || !summary || Number(summary[1]) !== selected.length) throw new Error('시험 결과 측정 불가');
85
+ const observed = new Set<string>();
86
+ let depth = 0;
87
+ for (const match of junit.matchAll(/<(\/?)testsuite\b([^>]*?)(\/?)>/g)) {
88
+ const [, closing, attrs, selfClosing] = match;
89
+ if (closing) { depth = Math.max(0, depth - 1); continue; }
90
+ if (depth === 0) {
91
+ const file = /\bfile="([^"]*)"/.exec(attrs!)?.[1] ?? /\bname="([^"]*)"/.exec(attrs!)?.[1];
92
+ if (file) observed.add(file.replace(/^\.\//, ''));
93
+ }
94
+ if (!selfClosing) depth += 1;
95
+ }
96
+ const failures = junitFailures(junit);
97
+ if (observed.size !== selected.length || selected.some((file) => !observed.has(file))
98
+ || (result.status !== 0 && failures.length === 0)) {
99
+ throw new Error('시험 결과 측정 불가');
100
+ }
101
+ return failures;
102
+ }
103
+
104
+ export function runMacSmoke(run: CommandRunner = runCommand, deps: MacSmokeDeps = {}): MacSmokeResult {
105
+ const start = Date.now();
106
+ let version = '';
107
+ let host = '';
108
+ let files = 0;
109
+ let newFailures = 0;
110
+ let preexisting = 0;
111
+ let smoke: MacSmokeResult['smoke'] = 'unmeasured';
112
+ let result: MacSmokeResult;
113
+ let work: string | undefined;
114
+ let cutCreated = false;
115
+ let baseCreated = false;
116
+ let remote = '';
117
+ let remoteAttempt = false;
118
+ const repo = resolve(deps.repo ?? process.cwd());
119
+ const command = (cmd: string, args: string[], cwd: string, timeoutMs?: number): CommandResult => host === 'local'
120
+ ? run(cmd, args, cwd, timeoutMs)
121
+ : run('ssh', [host, `PATH="$HOME/.bun/bin:$HOME/.local/bin:$PATH"; export PATH; cd ${quote(cwd)} && ${[cmd, ...args].map(quote).join(' ')}`], undefined, timeoutMs);
122
+ try {
123
+ const context = deps.context ?? readGraphContext();
124
+ version = context.input.version;
125
+ const configured = context.input.macSmokeHost ?? (deps.configHost ?? (() => (getUserConfig().raw.release as { macSmoke?: { host?: unknown } } | undefined)?.macSmoke?.host))();
126
+ if (configured === undefined || configured === null || configured === '') {
127
+ smoke = 'skipped';
128
+ result = { outcome: 'ok', verdict: 'pass', summary: 'macOS 대상 없음 — 건너뜀', files, newFailures, preexisting, smoke };
129
+ } else {
130
+ if (typeof configured !== 'string' || !hostName.test(configured)) throw new Error('macSmokeHost 호스트 이름이 유효하지 않음');
131
+ host = configured;
132
+ const commit = context.input.commit ?? context.outputs['version-release']?.commit;
133
+ if (typeof commit !== 'string' || !sha.test(commit)) throw new Error('컷 커밋 없음 또는 유효하지 않음 — 측정 불가');
134
+ const platform = host === 'local' ? run('uname', ['-s'], repo) : run('ssh', [host, 'uname -s'], repo);
135
+ if (requireOk(platform, '호스트 플랫폼 확인').trim() !== 'Darwin') throw new Error('macOS 호스트 아님 — 측정 불가');
136
+ const instance = effectiveInstanceRoot();
137
+ const root = getElanousConfigDirOverride() ? instance : releaseLedgerRoot();
138
+ const base = (deps.baseline ?? ((v) => baselineCommit(existsSync(join(root, 'release', v, 'release.json')) ? root : instance, v)))(context.input.previousVersion);
139
+ if (!sha.test(base)) throw new Error('지난 판 커밋 측정 불가');
140
+ const changed = (deps.changedFiles ?? ((b, c) => changedTests(run, repo, b, c)))(base, commit);
141
+ if (!changed.every(safeFile)) throw new Error('변경 파일 경로 측정 불가');
142
+ const sources = changed.filter((file) => /\.(?:tsx?|jsx?|mjs|cjs)$/.test(file) && !testFile.test(file));
143
+ const candidates = [...new Set([...changed.filter((file) => testFile.test(file)), ...(deps.findImporters ?? ((paths, revision) => importers(run, repo, paths, revision)))(sources, commit)])].sort();
144
+ if (!candidates.every(safeFile)) throw new Error('시험 파일 경로 측정 불가');
145
+ const cutFiles = new Set(requireOk(run('git', ['ls-tree', '-r', '--name-only', '-z', commit], repo), '컷 파일 조회').split('\0'));
146
+ const selected = candidates.filter((file) => cutFiles.has(file));
147
+ const clipped = Math.max(0, selected.length - 200);
148
+ const tests = selected.slice(0, 200);
149
+ files = tests.length;
150
+ const baseFiles = new Set(requireOk(run('git', ['ls-tree', '-r', '--name-only', '-z', base], repo), '기준 파일 조회').split('\0'));
151
+ work = mkdtempSync(join(tmpdir(), 'release-mac-smoke-'));
152
+ let source = repo;
153
+ if (host !== 'local') {
154
+ remoteAttempt = true;
155
+ remote = requireOk(run('ssh', [host, 'mktemp -d /tmp/release-mac-smoke-XXXXXXXX'], repo), '호스트 연결').trim();
156
+ if (!/^\/tmp\/release-mac-smoke-[\w-]+$/.test(remote)) throw new Error('원격 임시 경로 측정 불가');
157
+ requireOk(command('git', ['init', '--bare', `${remote}/mirror.git`], remote), '원격 저장소 생성');
158
+ for (const revision of new Set([commit, base])) {
159
+ requireOk(run('git', ['push', `${host}:${remote}/mirror.git`, `${revision}:refs/heads/mac-smoke-${revision}`], repo), '원격 커밋 전송');
160
+ }
161
+ requireOk(command('git', ['clone', '-q', '--no-checkout', `${remote}/mirror.git`, `${remote}/repo`], remote), '원격 복제');
162
+ source = `${remote}/repo`;
163
+ }
164
+ const cutTree = host === 'local' ? join(work, 'cut') : `${remote}/cut`;
165
+ requireOk(command('git', ['-C', source, 'worktree', 'add', '--detach', cutTree, commit], source), '체크아웃');
166
+ cutCreated = true;
167
+ requireOk(command('bun', ['install'], cutTree), '설치');
168
+ const execute = (tree: string, paths: string[]): string[] => {
169
+ const failures: string[] = [];
170
+ for (let i = 0; i < paths.length; i += 50) {
171
+ const batch = paths.slice(i, i + 50);
172
+ const report = `.mac-smoke-junit-${i}.xml`;
173
+ // `./` makes each path exact — bare paths are substring filters and also run same-named copies (10-03 nested worktrees).
174
+ const tested = command('bun', ['test', '--reporter=junit', `--reporter-outfile=${report}`, ...batch.map((file) => `./${file}`)], tree, 1_200_000);
175
+ const xml = command('cat', [report], tree);
176
+ failures.push(...testFailures(tested, xml.status === 0 ? xml.stdout : null, batch));
177
+ }
178
+ return failures;
179
+ };
180
+ const cutFailures = execute(cutTree, tests);
181
+ const versionSmoke = command('bun', ['bin/elanous.mjs', '--test', '--version'], cutTree);
182
+ const scheduleSmoke = command('bun', ['bin/elanous.mjs', '--test', 'release', 'schedule', 'list', '--json'], cutTree);
183
+ smoke = versionSmoke.status === 0 && scheduleSmoke.status === 0 ? 'pass' : 'fail';
184
+ let baselineFailures: string[] = [];
185
+ if (cutFailures.length) {
186
+ const baseTree = host === 'local' ? join(work, 'base') : `${remote}/base`;
187
+ requireOk(command('git', ['-C', source, 'worktree', 'add', '--detach', baseTree, base], source), '기준 체크아웃');
188
+ baseCreated = true;
189
+ requireOk(command('bun', ['install'], baseTree), '기준 설치');
190
+ baselineFailures = execute(baseTree, tests.filter((file) => baseFiles.has(file)));
191
+ }
192
+ const delta = diffFailures(cutFailures, baselineFailures);
193
+ newFailures = delta.newFailures.length;
194
+ preexisting = delta.common.length;
195
+ const outcome = newFailures || smoke !== 'pass' ? 'fail' : 'ok';
196
+ result = { outcome, verdict: outcome === 'ok' ? 'pass' : 'fail', files, newFailures, preexisting, smoke,
197
+ summary: `macOS ${outcome} · 새 실패 ${newFailures} · 기존 실패 ${preexisting} · 스모크 ${smoke}${clipped ? ` · 잘림 ${clipped}` : ''}${newFailures ? ` · ${delta.newFailures.slice(0, 5).map((id) => id.split(' > ')[0]).join(', ')}` : ''}` };
198
+ }
199
+ } catch {
200
+ result = { outcome: 'error', verdict: 'fail', summary: 'macOS 측정 불가 (연결·체크아웃·설치·시험 결과 확인 필요)', files, newFailures, preexisting, smoke };
201
+ } finally {
202
+ if (remoteAttempt && remote) {
203
+ try {
204
+ const cleanup = run('ssh', [host, `rm -rf -- ${quote(remote)}`]);
205
+ if (cleanup.status !== 0) result = { outcome: 'error', verdict: 'fail', summary: 'macOS 측정 불가 (임시 작업 정리 실패)', files, newFailures, preexisting, smoke };
206
+ } catch { result = { outcome: 'error', verdict: 'fail', summary: 'macOS 측정 불가 (임시 작업 정리 실패)', files, newFailures, preexisting, smoke }; }
207
+ }
208
+ if (work) {
209
+ if (host === 'local') {
210
+ for (const tree of [cutCreated && 'cut', baseCreated && 'base'].filter((name): name is string => Boolean(name))) {
211
+ try {
212
+ const removed = run('git', ['worktree', 'remove', '--force', join(work, tree)], repo);
213
+ if (removed.status !== 0) result = { outcome: 'error', verdict: 'fail', summary: 'macOS 측정 불가 (임시 작업 정리 실패)', files, newFailures, preexisting, smoke };
214
+ } catch { result = { outcome: 'error', verdict: 'fail', summary: 'macOS 측정 불가 (임시 작업 정리 실패)', files, newFailures, preexisting, smoke }; }
215
+ }
216
+ }
217
+ try { rmSync(work, { recursive: true, force: true }); }
218
+ catch { result = { outcome: 'error', verdict: 'fail', summary: 'macOS 측정 불가 (임시 작업 정리 실패)', files, newFailures, preexisting, smoke }; }
219
+ }
220
+ debug.log('release-loop.mac-smoke', 'judged', { version, host, files, newFailures, smoke, outcome: result!.outcome, ms: Date.now() - start });
221
+ }
222
+ return result!;
223
+ }
224
+
225
+ if (import.meta.main) {
226
+ let version = '';
227
+ try { version = readGraphContext().input.version; process.exitCode = finishNode('mac-smoke', version, runMacSmoke()); }
228
+ catch { process.exitCode = finishNode('mac-smoke', version, { outcome: 'error', verdict: 'fail', summary: 'macOS 측정 불가' }); }
229
+ }
@@ -33,9 +33,9 @@ export function emitNodeResult<T extends Record<string, unknown>>(result: T & {
33
33
  }
34
34
 
35
35
  export interface CommandResult { status: number | null; stdout: string; stderr: string }
36
- export type CommandRunner = (command: string, args: string[], cwd?: string) => CommandResult;
37
- export const runCommand: CommandRunner = (command, args, cwd) => {
38
- const result = spawnSync(command, args, { cwd, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
36
+ export type CommandRunner = (command: string, args: string[], cwd?: string, timeoutMs?: number) => CommandResult;
37
+ export const runCommand: CommandRunner = (command, args, cwd, timeoutMs) => {
38
+ const result = spawnSync(command, args, { cwd, timeout: timeoutMs, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
39
39
  return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr || (result.error ? String(result.error) : '') };
40
40
  };
41
41
 
@@ -27,3 +27,45 @@ export function tuiChecks(s: TuiScreens, readyLimitMs = 60_000): TuiCheck[] {
27
27
  export function tuiVerdict(checks: readonly TuiCheck[]): 'pass' | 'fail' {
28
28
  return checks.every((c) => c.pass) ? 'pass' : 'fail';
29
29
  }
30
+
31
+ export type TuiRegress = {
32
+ pass: number;
33
+ fail: number;
34
+ results: Array<{ id: string; title: string; ok: boolean; reason: string }>;
35
+ } | { unmeasured: string };
36
+
37
+ export function validatedTuiRegress(data: unknown): TuiRegress {
38
+ const invalid = { unmeasured: 'tui.regress 결과 없음' };
39
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return invalid;
40
+ const value = data as Record<string, unknown>;
41
+ if (typeof value.unmeasured === 'string' && value.unmeasured.trim()) return { unmeasured: value.unmeasured };
42
+ if (!Array.isArray(value.results) || value.results.length !== 7
43
+ || !Number.isInteger(value.pass) || !Number.isInteger(value.fail)
44
+ || (value.pass as number) < 0 || (value.fail as number) < 0
45
+ || (value.pass as number) + (value.fail as number) !== 7
46
+ || value.results.some((r: unknown) => !r || typeof r !== 'object'
47
+ || typeof (r as Record<string, unknown>).id !== 'string'
48
+ || !/^R[1-7]$/.test((r as Record<string, unknown>).id as string)
49
+ || typeof (r as Record<string, unknown>).title !== 'string'
50
+ || typeof (r as Record<string, unknown>).ok !== 'boolean'
51
+ || typeof (r as Record<string, unknown>).reason !== 'string')
52
+ || value.pass !== value.results.filter((r: { ok: boolean }) => r.ok).length
53
+ || value.fail !== value.results.filter((r: { ok: boolean }) => !r.ok).length
54
+ || new Set(value.results.map((r: { id: string }) => r.id)).size !== 7) return invalid;
55
+ return value as { pass: number; fail: number; results: Array<{ id: string; title: string; ok: boolean; reason: string }> };
56
+ }
57
+
58
+ export function parseTuiRegress(stdout: string): TuiRegress {
59
+ const lines = stdout.trim().split('\n');
60
+ if (lines.length !== 1) return { unmeasured: 'JSON 한 줄이 아니다' };
61
+ try { return validatedTuiRegress(JSON.parse(lines[0]!)); }
62
+ catch { return { unmeasured: 'JSON 결과를 읽을 수 없다' }; }
63
+ }
64
+
65
+ export function regressWarning(regress: TuiRegress): { level: 'ok' | 'warn' | 'unmeasured'; line: string } {
66
+ if ('unmeasured' in regress) return { level: 'unmeasured', line: `tui-regress: unmeasured — ${regress.unmeasured}` };
67
+ const failed = regress.results.filter((result) => !result.ok).map((result) => result.id);
68
+ return failed.length
69
+ ? { level: 'warn', line: `tui-regress: warn — ${failed.join(', ')}` }
70
+ : { level: 'ok', line: `tui-regress: ok — ${regress.pass} pass · 0 fail` };
71
+ }
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Steps: temp worktree at <sha> → `scripts/tui-sim.ts start --test` (that commit's code, the
8
8
  * repo's `.elanous-test` config so no first-run wizard) → first screen → `/help` → Esc → dump PNG
9
- * → stop, clean, remove worktree. Never touches the production daemon or ~/.elanous.
9
+ * → stop, clean → run seven PTY regression checks → remove worktree. Never touches the production daemon or ~/.elanous.
10
10
  * macOS only (the TUI PTY checks run on the mbp). One retry of the whole session → «flaky».
11
11
  *
12
12
  * Exit: 0 = pass or flaky · 1 = fail · 2 = could not run (not macOS, worktree).
@@ -15,7 +15,7 @@ import { spawn, spawnSync } from 'node:child_process';
15
15
  import { copyFileSync, existsSync, mkdtempSync, openSync, symlinkSync } from 'node:fs';
16
16
  import { tmpdir } from 'node:os';
17
17
  import { basename, join, resolve } from 'node:path';
18
- import { tuiChecks, tuiVerdict, type TuiCheck } from './tui-node-checks';
18
+ import { parseTuiRegress, regressWarning, tuiChecks, tuiVerdict, type TuiCheck, type TuiRegress } from './tui-node-checks';
19
19
 
20
20
  const log = (msg: string) => process.stderr.write(`[tui-node] ${msg}\n`);
21
21
  const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
@@ -64,6 +64,27 @@ async function session(worktree: string, stateDir: string, id: string, work: str
64
64
  }
65
65
  }
66
66
 
67
+ export function measureTuiRegress(worktree: string, runner: typeof spawnSync = spawnSync): TuiRegress {
68
+ try {
69
+ const measured = runner('bun', ['scripts/tui-regress.ts', '--json'], {
70
+ cwd: worktree, encoding: 'utf8', timeout: 180_000, maxBuffer: 16 * 1024 * 1024,
71
+ });
72
+ if (measured.error) return { unmeasured: measured.error.message };
73
+ if (measured.status !== 0 && measured.status !== 1) {
74
+ return { unmeasured: `실행 종료 코드 ${measured.status ?? measured.signal ?? '없음'}: ${(measured.stderr ?? '').trim().slice(0, 200)}` };
75
+ }
76
+ return parseTuiRegress(measured.stdout ?? '');
77
+ } catch (error) {
78
+ return { unmeasured: String(error) };
79
+ }
80
+ }
81
+
82
+ export function tuiNodeOutput(commit: string, attempts: Array<{ checks: TuiCheck[]; png?: string }>, regress: TuiRegress, failure?: string) {
83
+ const last = attempts.at(-1);
84
+ const verdict = !last ? 'error' : tuiVerdict(last.checks) === 'fail' ? 'fail' : attempts.length > 1 ? 'flaky' : 'pass';
85
+ return { verdict, commit, attempts: attempts.length, checks: last?.checks ?? [], png: last?.png, regress, ...(failure ? { error: failure } : {}) };
86
+ }
87
+
67
88
  async function main(): Promise<number> {
68
89
  const argv = process.argv.slice(2);
69
90
  const commit = argv[argv.indexOf('--commit') + 1] ?? '';
@@ -77,9 +98,12 @@ async function main(): Promise<number> {
77
98
  const worktree = join(work, 'tree');
78
99
  let attempts: Array<{ checks: TuiCheck[]; png?: string }> = [];
79
100
  let failure: string | undefined;
101
+ let regress: TuiRegress = { unmeasured: 'worktree를 만들지 못했다' };
102
+ let worktreeReady = false;
80
103
  try {
81
104
  const add = spawnSync('git', ['worktree', 'add', '--detach', worktree, commit], { cwd: repo, encoding: 'utf8' });
82
105
  if (add.status !== 0) throw new Error(`worktree add failed: ${(add.stderr ?? '').trim()}`);
106
+ worktreeReady = true;
83
107
  symlinkSync(join(repo, 'node_modules'), join(worktree, 'node_modules'));
84
108
  if (!existsSync(join(stateDir, 'config.json'))) log(`⚠ ${stateDir}/config.json missing — the TUI may open the first-run wizard`);
85
109
  const id = `rl-${commit.slice(0, 8)}-${Date.now().toString(36)}`;
@@ -94,20 +118,20 @@ async function main(): Promise<number> {
94
118
  log(`could not run: ${failure}`);
95
119
  attempts = [];
96
120
  } finally {
121
+ if (worktreeReady) regress = measureTuiRegress(worktree);
97
122
  spawnSync('git', ['worktree', 'remove', '--force', worktree], { cwd: repo });
98
123
  }
99
124
 
100
- const last = attempts.at(-1);
101
- const verdict = !last ? 'error' : tuiVerdict(last.checks) === 'fail' ? 'fail' : attempts.length > 1 ? 'flaky' : 'pass';
102
- const out = { verdict, commit, attempts: attempts.length, checks: last?.checks ?? [], png: last?.png, ...(failure ? { error: failure } : {}) };
125
+ const out = tuiNodeOutput(commit, attempts, regress, failure);
103
126
  if (json) console.log(JSON.stringify(out));
104
127
  else {
105
- console.log(`tui-node: ${verdict} · ${commit.slice(0, 10)} · attempts ${attempts.length}`);
128
+ console.log(`tui-node: ${out.verdict} · ${commit.slice(0, 10)} · attempts ${attempts.length}`);
106
129
  for (const c of out.checks) console.log(` ${c.pass ? '✓' : '✗'} ${c.id} — ${c.detail}`);
107
130
  if (out.png) console.log(` screen: ${out.png}`);
131
+ console.log(` ${regressWarning(regress).line}`);
108
132
  if (failure) console.log(` error: ${failure}`);
109
133
  }
110
- return verdict === 'error' ? 2 : verdict === 'fail' ? 1 : 0;
134
+ return out.verdict === 'error' ? 2 : out.verdict === 'fail' ? 1 : 0;
111
135
  }
112
136
 
113
- process.exit(await main());
137
+ if (import.meta.main) process.exit(await main());
@@ -24,6 +24,7 @@ export interface ReleaseLoopConfig {
24
24
  export interface ReleaseRunInput extends ReleaseLoopConfig {
25
25
  version: string;
26
26
  previousVersion: string;
27
+ cutCommit?: string;
27
28
  gatePodPool: string;
28
29
  }
29
30
 
@@ -67,7 +68,7 @@ export function releaseLoopConfig(configPath = getElanousConfigDirOverride() ? j
67
68
  return raw.release?.loop ?? {};
68
69
  }
69
70
 
70
- export function buildReleaseRunInput(version: string, deps: UnattendedReleaseDeps = {}): ReleaseRunInput {
71
+ export function buildReleaseRunInput(version: string, deps: UnattendedReleaseDeps = {}, cutCommit?: string): ReleaseRunInput {
71
72
  const config = deps.config ?? releaseLoopConfig(deps.configPath);
72
73
  const pool = config.gatePodPool;
73
74
  if (typeof pool !== 'string' || !pool.trim()) throw new Error('release.loop.gatePodPool is required before graph execution');
@@ -75,16 +76,17 @@ export function buildReleaseRunInput(version: string, deps: UnattendedReleaseDep
75
76
  return {
76
77
  ...config,
77
78
  version, previousVersion,
79
+ ...(cutCommit !== undefined ? { cutCommit } : {}),
78
80
  gatePodPool: pool.trim(),
79
81
  };
80
82
  }
81
83
 
82
84
  /** Fail closed at the entry boundary, before any graph node can change a release. */
83
85
  export async function runUnattendedRelease(
84
- opts: { version: string; dryRun?: boolean },
86
+ opts: { version: string; dryRun?: boolean; cutCommit?: string },
85
87
  deps: UnattendedReleaseDeps = {},
86
88
  ): Promise<{ input: ReleaseRunInput; dryRun: boolean; state?: GraphRunState }> {
87
- const input = buildReleaseRunInput(opts.version, deps);
89
+ const input = buildReleaseRunInput(opts.version, deps, opts.cutCommit);
88
90
  if (opts.dryRun) return { input, dryRun: true };
89
91
  const gate = (deps.checklist ?? checklistGate)(opts.version);
90
92
  if (!gate.ok) throw new Error(`release checklist blocked: ${[...gate.red, ...gate.undecided, ...gate.blocked].join(', ')}`);
@@ -49,9 +49,9 @@ function fetchMain(repo: string): void {
49
49
  }
50
50
  }
51
51
 
52
- function versionAt(repo: string): string {
53
- const pkg = JSON.parse(git(repo, 'show', 'origin/main:package.json')) as { version?: unknown };
54
- if (typeof pkg.version !== 'string') throw new Error('origin/main package.json has no version');
52
+ function versionAt(repo: string, ref = 'origin/main'): string {
53
+ const pkg = JSON.parse(git(repo, 'show', `${ref}:package.json`)) as { version?: unknown };
54
+ if (typeof pkg.version !== 'string') throw new Error(`${ref} package.json has no version`);
55
55
  return pkg.version;
56
56
  }
57
57
 
@@ -64,13 +64,49 @@ function shippedNextMdLines(repo: string, cut: string | undefined): { lines: Set
64
64
  return { lines: new Set(shown.stdout.split('\n').map((line) => line.trim()).filter((line) => line.startsWith('- '))) };
65
65
  }
66
66
 
67
- function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string): Output {
67
+ function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string, cutCommit?: string): Output {
68
68
  const target = kind === 'release' ? releaseVersion : nextDevVersion(releaseVersion);
69
69
  const source = kind === 'release' ? `${releaseVersion}-dev.N` : releaseVersion;
70
70
  fetchMain(repo);
71
+ if (kind === 'release' && cutCommit !== undefined) {
72
+ if (!/^[0-9a-f]{7,40}$/i.test(cutCommit)) throw new Error(`invalid cut commit SHA: ${cutCommit}`);
73
+ const local = spawnSync('git', ['rev-parse', '--verify', `${cutCommit}^{commit}`], { cwd: repo, encoding: 'utf8' });
74
+ let chosen = local.status === 0 ? local.stdout.trim() : '';
75
+ const onMain = chosen ? spawnSync('git', ['merge-base', '--is-ancestor', chosen, 'origin/main'], { cwd: repo }) : null;
76
+ if (onMain && onMain.status !== 0 && onMain.status !== 1) throw new Error(`git merge-base failed: ${onMain.error || onMain.stderr || 'unknown'}`);
77
+ if (onMain?.status !== 0) {
78
+ const remote = git(repo, 'ls-remote', '--heads', 'origin', `refs/heads/release/${releaseVersion}`);
79
+ const branchTip = remote.split(/\s+/)[0];
80
+ if (!chosen && branchTip?.toLowerCase().startsWith(cutCommit.toLowerCase())) {
81
+ git(repo, 'fetch', 'origin', `refs/heads/release/${releaseVersion}`);
82
+ chosen = git(repo, 'rev-parse', '--verify', `${cutCommit}^{commit}`);
83
+ }
84
+ if (!chosen) chosen = git(repo, 'rev-parse', '--verify', `${cutCommit}^{commit}`);
85
+ if (branchTip !== chosen) {
86
+ throw new Error(`cut commit ${chosen} is neither an ancestor of origin/main nor the tip of release/${releaseVersion}`);
87
+ }
88
+ }
89
+ const current = versionAt(repo, chosen);
90
+ if (current !== target) throw new Error(`cut commit ${chosen} package.json version ${current} is not ${target}`);
91
+ debug.log('release-loop.cut', 'chosen', { version: releaseVersion, source: 'cut-commit', commit: chosen });
92
+ return { outcome: 'ok', kind, version: target, commit: chosen, pr: null };
93
+ }
71
94
  const current = versionAt(repo);
72
95
  const before = git(repo, 'rev-parse', 'origin/main');
73
- if (current === target) return { outcome: 'ok', kind, version: target, commit: before, pr: null };
96
+ if (current === target) {
97
+ if (kind === 'release') debug.log('release-loop.cut', 'chosen', { version: releaseVersion, source: 'main', commit: before });
98
+ return { outcome: 'ok', kind, version: target, commit: before, pr: null };
99
+ }
100
+ // An older chosen cut may finish after main has already advanced to the next stable release.
101
+ // Keep that newer main untouched; a -dev.N mismatch is not evidence of a later release.
102
+ if (kind === 'dev-bump' && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(current)) {
103
+ const mainParts = current.split('.').map(BigInt);
104
+ const nextParts = target.slice(0, -'-dev.0'.length).split('.').map(BigInt);
105
+ const firstDifference = mainParts.findIndex((part, index) => part !== nextParts[index]);
106
+ if (firstDifference === -1 || mainParts[firstDifference]! > nextParts[firstDifference]!) {
107
+ return { outcome: 'ok', kind, version: current, commit: before, pr: null };
108
+ }
109
+ }
74
110
  if (kind === 'release' ? !new RegExp(`^${releaseVersion.replaceAll('.', '\\.')}\\-dev\\.(0|[1-9][0-9]*)$`).test(current) : current !== source) {
75
111
  throw new Error(`origin/main version ${current} is not ${source} (target ${target})`);
76
112
  }
@@ -84,6 +120,13 @@ function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string)
84
120
  const old = `"version": "${current}"`;
85
121
  if (!text.includes(old)) throw new Error(`package.json version field not found: ${current}`);
86
122
  writeFileSync(path, text.replace(old, `"version": "${target}"`));
123
+ const serverPath = join(worktree, 'server.json');
124
+ if (existsSync(serverPath)) {
125
+ const server = JSON.parse(readFileSync(serverPath, 'utf8')) as { version: string; packages: Array<{ version: string }> };
126
+ server.version = target;
127
+ server.packages[0]!.version = target;
128
+ writeFileSync(serverPath, `${JSON.stringify(server, null, 2)}\n`);
129
+ }
87
130
  const lockPath = join(worktree, 'bun.lock');
88
131
  const lock = readFileSync(lockPath, 'utf8');
89
132
  const root = /"workspaces"\s*:\s*\{\s*""\s*:\s*\{([\s\S]*?)(?=\s*"(?:dependencies|devDependencies|optionalDependencies)"\s*:)/.exec(lock);
@@ -143,6 +186,7 @@ function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string)
143
186
  const commit = git(repo, 'rev-parse', 'origin/main');
144
187
  if (versionAt(repo) !== target || commit === before) throw new Error(`origin/main did not advance to ${target} after merge`);
145
188
  const output: Output = { outcome: 'ok', kind, version: target, commit, pr: pr ? Number(pr[1]) : null };
189
+ if (kind === 'release') debug.log('release-loop.cut', 'chosen', { version: releaseVersion, source: 'main', commit });
146
190
  git(repo, 'worktree', 'remove', '--force', worktree);
147
191
  rmSync(temp, { recursive: true, force: true });
148
192
  return output;
@@ -156,27 +200,34 @@ function main(args: string[] = process.argv.slice(2), repo = process.cwd()): Out
156
200
  let kind: Kind | null = null;
157
201
  let version: string | null = null;
158
202
  let cut: string | undefined;
203
+ let cutCommit: string | undefined;
159
204
  try {
160
- if (args[0] !== 'release' && args[0] !== 'dev-bump') throw new Error('usage: version-node.ts <release|dev-bump> [--version <v>] [--cut <sha>] --json');
205
+ if (args[0] !== 'release' && args[0] !== 'dev-bump') throw new Error('usage: version-node.ts <release|dev-bump> [--version <v>] [--cut <sha>] [--cut-commit <sha>] --json');
161
206
  kind = args[0];
162
207
  for (let i = 1; i < args.length; i++) {
163
208
  if (args[i] === '--json') continue;
164
209
  if (args[i] === '--version' && args[i + 1]) { version = args[++i]!; continue; }
165
210
  if (args[i] === '--cut' && args[i + 1]) { cut = args[++i]!; continue; }
211
+ if (args[i] === '--cut-commit' && args[i + 1]) { cutCommit = args[++i]!; continue; }
166
212
  throw new Error(`unknown or incomplete argument: ${args[i]}`);
167
213
  }
168
214
  if (process.env.ELANOUS_GRAPH_CONTEXT) {
169
215
  const location = process.env.ELANOUS_GRAPH_CONTEXT;
170
216
  const context = JSON.parse(location.trimStart().startsWith('{') ? location : readFileSync(location, 'utf8')) as {
171
- input?: { version?: unknown }; outputs?: Record<string, { commit?: unknown } | undefined> };
217
+ input?: { version?: unknown; cutCommit?: unknown }; outputs?: Record<string, { commit?: unknown } | undefined> };
172
218
  if (!version && typeof context.input?.version === 'string') version = context.input.version;
219
+ if (kind === 'release' && cutCommit === undefined && context.input?.cutCommit !== undefined) {
220
+ if (typeof context.input.cutCommit !== 'string') throw new Error('cutCommit must be a commit SHA');
221
+ cutCommit = context.input.cutCommit;
222
+ }
173
223
  // The cut = the commit version-release landed (what cutoff and the gate measured).
174
- const cutCommit = context.outputs?.['version-release']?.commit;
175
- if (!cut && typeof cutCommit === 'string' && cutCommit) cut = cutCommit;
224
+ const releasedCommit = context.outputs?.['version-release']?.commit;
225
+ if (!cut && typeof releasedCommit === 'string' && releasedCommit) cut = releasedCommit;
176
226
  }
177
227
  if (!version) throw new Error('version required (--version or ELANOUS_GRAPH_CONTEXT.input.version)');
178
228
  nextDevVersion(version);
179
- return landing(resolve(repo), kind, version, cut);
229
+ if (kind === 'dev-bump' && cutCommit !== undefined) throw new Error('--cut-commit is only valid for release');
230
+ return landing(resolve(repo), kind, version, cut, cutCommit);
180
231
  } catch (error) {
181
232
  const message = error instanceof Error ? error.message : String(error);
182
233
  const worktree = /\[worktree: ([^\]]+)\]$/.exec(message)?.[1];
@@ -13,8 +13,7 @@ import { appendFileSync } from 'node:fs';
13
13
  import { SignalPool } from '../src/domains/signal-pool.js';
14
14
  import { runDigest, runCommunityBuzzDigest } from '../src/domains/signal-router.js';
15
15
  import { sendOutbound } from '../src/domains/outbound-alert.js';
16
- import { sendReportPhotoBuffer } from '../src/telegram-report.js';
17
- import { getUserConfig } from '../src/user-config.js';
16
+ import { createDigestPhotoSender } from './signal-digest-photo.js';
18
17
  import { ensureCronNodePath } from '../src/domains/cron-path.js';
19
18
  import { unknownCronFlag } from '../src/domains/cron-flag-contract.js';
20
19
 
@@ -35,14 +34,10 @@ function log(msg: string): void {
35
34
  try { appendFileSync(LOG, line + '\n'); } catch { /* fail-soft */ }
36
35
  }
37
36
 
38
- async function sendDigestPhoto(png: Buffer, opts?: { caption?: string }): Promise<boolean> {
39
- return sendReportPhotoBuffer(getUserConfig(), png, opts);
40
- }
41
-
42
37
  async function main(): Promise<void> {
43
38
  const pool = new SignalPool();
44
39
  try {
45
- const d = await runDigest(pool, LIVE ? { send: sendOutbound, sendPhoto: sendDigestPhoto } : {});
40
+ const d = await runDigest(pool, LIVE ? { send: sendOutbound, sendPhoto: createDigestPhotoSender() } : {});
46
41
  log(`[${d.mode}] 다이제스트 대기 ${d.count} · 발송 ${d.sent}`);
47
42
  if (!LIVE && d.count > 0) {
48
43
  log(` ▶ [digest shadow]\n${d.preview}`);
@@ -0,0 +1,11 @@
1
+ import type { UserConfig } from '../src/user-config.js';
2
+ import { getUserConfig } from '../src/user-config.js';
3
+ import { sendReportPhotoBuffer } from '../src/telegram-report.js';
4
+
5
+ /** The callback passed to runDigest for trading signal images. */
6
+ export function createDigestPhotoSender(
7
+ config: () => UserConfig = getUserConfig,
8
+ sendPhoto: typeof sendReportPhotoBuffer = sendReportPhotoBuffer,
9
+ ): (png: Buffer, opts?: { caption?: string }) => Promise<boolean> {
10
+ return (png, opts) => sendPhoto(config(), png, { ...opts, kind: 'digest' });
11
+ }