elanous 0.2.17 → 0.2.19

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 (491) 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/J5H7BvqgSI8DUjtRRcA_S/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{726.83532d69452ee24b.js → 1470.98a6ab61ccb4131c.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{9247-269e4464cc4b491c.js → 2375-e061bb24cd2b5cde.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{211-9c9090c59e2c5907.js → 2977-e38442e86289f1d3.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → 2ea96958.e8c7e112de50b067.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +2 -0
  16. package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
  19. package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 4323.c5dfb6f503304775.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 4847.56f5bc454fbcc827.js} +2 -2
  28. package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 6062-73e68343a9814f6a.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/{935.4ddaa99fc1d1126c.js → 7647.2fcd30d894d5ee30.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/{f6231284-45726550aba08fca.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +25 -0
  49. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/live/page-b70cd005d9169e25.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +1 -0
  110. package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
  111. package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +1 -0
  112. package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
  113. package/apps/pwa/out/approvals/index.html +1 -1
  114. package/apps/pwa/out/approvals/index.txt +23 -23
  115. package/apps/pwa/out/autopilot/index.html +1 -1
  116. package/apps/pwa/out/autopilot/index.txt +23 -23
  117. package/apps/pwa/out/board/index.html +1 -1
  118. package/apps/pwa/out/board/index.txt +23 -23
  119. package/apps/pwa/out/botlab/index.html +1 -1
  120. package/apps/pwa/out/botlab/index.txt +22 -22
  121. package/apps/pwa/out/bots/index.html +1 -1
  122. package/apps/pwa/out/bots/index.txt +22 -22
  123. package/apps/pwa/out/ceo/index.html +1 -1
  124. package/apps/pwa/out/ceo/index.txt +23 -23
  125. package/apps/pwa/out/chat/index.html +1 -1
  126. package/apps/pwa/out/chat/index.txt +22 -22
  127. package/apps/pwa/out/consult/index.html +1 -1
  128. package/apps/pwa/out/consult/index.txt +23 -23
  129. package/apps/pwa/out/control/index.html +1 -1
  130. package/apps/pwa/out/control/index.txt +23 -23
  131. package/apps/pwa/out/decisions/index.html +1 -0
  132. package/apps/pwa/out/decisions/index.txt +30 -0
  133. package/apps/pwa/out/design-check/index.html +1 -1
  134. package/apps/pwa/out/design-check/index.txt +23 -23
  135. package/apps/pwa/out/editor/index.html +1 -1
  136. package/apps/pwa/out/editor/index.txt +25 -24
  137. package/apps/pwa/out/exec/index.html +1 -1
  138. package/apps/pwa/out/exec/index.txt +23 -23
  139. package/apps/pwa/out/field/index.html +1 -1
  140. package/apps/pwa/out/field/index.txt +23 -23
  141. package/apps/pwa/out/healthz +1 -1
  142. package/apps/pwa/out/index.html +1 -1
  143. package/apps/pwa/out/index.txt +22 -22
  144. package/apps/pwa/out/inside/index.html +1 -1
  145. package/apps/pwa/out/inside/index.txt +24 -23
  146. package/apps/pwa/out/intake/index.html +1 -1
  147. package/apps/pwa/out/intake/index.txt +23 -23
  148. package/apps/pwa/out/live/index.html +1 -1
  149. package/apps/pwa/out/live/index.txt +23 -23
  150. package/apps/pwa/out/loops/index.html +1 -1
  151. package/apps/pwa/out/loops/index.txt +28 -27
  152. package/apps/pwa/out/market/index.html +1 -1
  153. package/apps/pwa/out/market/index.txt +22 -22
  154. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  155. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  156. package/apps/pwa/out/morning/index.html +1 -1
  157. package/apps/pwa/out/morning/index.txt +23 -23
  158. package/apps/pwa/out/observatory/index.html +1 -1
  159. package/apps/pwa/out/observatory/index.txt +23 -23
  160. package/apps/pwa/out/ops/checklist/index.html +1 -1
  161. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  162. package/apps/pwa/out/ops/release/index.html +1 -1
  163. package/apps/pwa/out/ops/release/index.txt +22 -22
  164. package/apps/pwa/out/ops/seats/index.html +1 -1
  165. package/apps/pwa/out/ops/seats/index.txt +22 -22
  166. package/apps/pwa/out/outputs/index.html +1 -1
  167. package/apps/pwa/out/outputs/index.txt +22 -22
  168. package/apps/pwa/out/reflection/index.html +1 -1
  169. package/apps/pwa/out/reflection/index.txt +23 -23
  170. package/apps/pwa/out/scheduler/index.html +1 -1
  171. package/apps/pwa/out/scheduler/index.txt +23 -23
  172. package/apps/pwa/out/sessions/index.html +1 -1
  173. package/apps/pwa/out/sessions/index.txt +22 -22
  174. package/apps/pwa/out/settings/devices/index.html +1 -1
  175. package/apps/pwa/out/settings/devices/index.txt +23 -23
  176. package/apps/pwa/out/settings/index.html +1 -1
  177. package/apps/pwa/out/settings/index.txt +23 -23
  178. package/apps/pwa/out/setup/done/index.html +1 -1
  179. package/apps/pwa/out/setup/done/index.txt +23 -23
  180. package/apps/pwa/out/setup/index.html +1 -1
  181. package/apps/pwa/out/setup/index.txt +23 -23
  182. package/apps/pwa/out/share/index.html +1 -1
  183. package/apps/pwa/out/share/index.txt +23 -23
  184. package/apps/pwa/out/showroom/index.html +1 -1
  185. package/apps/pwa/out/showroom/index.txt +22 -22
  186. package/apps/pwa/out/tasks/index.html +1 -1
  187. package/apps/pwa/out/tasks/index.txt +23 -23
  188. package/apps/pwa/out/term/index.html +1 -1
  189. package/apps/pwa/out/term/index.txt +23 -23
  190. package/apps/pwa/out/today/index.html +1 -1
  191. package/apps/pwa/out/today/index.txt +23 -23
  192. package/apps/pwa/out/trace/index.html +1 -1
  193. package/apps/pwa/out/trace/index.txt +23 -23
  194. package/apps/pwa/out/vault/index.html +1 -1
  195. package/apps/pwa/out/vault/index.txt +23 -23
  196. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  197. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  198. package/apps/pwa/out/workflows/index.html +1 -1
  199. package/apps/pwa/out/workflows/index.txt +23 -23
  200. package/apps/pwa/out/workspace/index.html +1 -1
  201. package/apps/pwa/out/workspace/index.txt +23 -23
  202. package/apps/pwa/out/worktrees/index.html +1 -1
  203. package/apps/pwa/out/worktrees/index.txt +23 -23
  204. package/graphs/ops/guardian.yaml +26 -0
  205. package/graphs/ops/recipes.yaml +3 -0
  206. package/package.json +2 -1
  207. package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
  208. package/samples/workflows/templates/chat-driven-research.yaml +35 -0
  209. package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
  210. package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
  211. package/samples/workflows/templates/manual-quickstart.yaml +21 -0
  212. package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
  213. package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
  214. package/scripts/ci-public-export-import-gate.ts +7 -2
  215. package/scripts/ci-typecheck-changed.ts +21 -6
  216. package/scripts/coord-channel-watch.sh +2 -2
  217. package/scripts/discord-seat-probe.ts +6 -3
  218. package/scripts/docs/generate-cli-anatomy.ts +162 -0
  219. package/scripts/goals/authoring-metrics.ts +73 -0
  220. package/scripts/landing-heal/must-fix-match.ts +7 -7
  221. package/scripts/landing-heal/run-mission.ts +3 -2
  222. package/scripts/lib/pch-regress-map.ts +23 -0
  223. package/scripts/morning-report.ts +18 -4
  224. package/scripts/pch-regress.ts +88 -0
  225. package/scripts/public-export-test-run.ts +37 -2
  226. package/scripts/release-loop/checklist-gate-node.ts +13 -11
  227. package/scripts/release-loop/cut-branch.ts +2 -1
  228. package/scripts/release-loop/cutoff.ts +4 -2
  229. package/scripts/release-loop/docs-land-node.ts +8 -1
  230. package/scripts/release-loop/docs-node.ts +1 -1
  231. package/scripts/release-loop/gate-diff.ts +18 -0
  232. package/scripts/release-loop/gate-node.ts +436 -59
  233. package/scripts/release-loop/gate-partial.ts +106 -0
  234. package/scripts/release-loop/gate-timing.ts +120 -0
  235. package/scripts/release-loop/node-verdict.ts +2 -1
  236. package/scripts/release-loop/npm-publish-node.ts +11 -7
  237. package/scripts/release-loop/ops-upgrade-node.ts +8 -1
  238. package/scripts/release-loop/preflight.ts +246 -0
  239. package/scripts/release-loop/publish-node.ts +8 -1
  240. package/scripts/release-loop/release-readiness.ts +11 -6
  241. package/scripts/release-loop/release-version.ts +48 -0
  242. package/scripts/release-loop/resume-release.ts +174 -0
  243. package/scripts/release-loop/shard-plan.ts +65 -0
  244. package/scripts/release-loop/unattended-release.ts +45 -7
  245. package/scripts/release-loop/version-node.ts +145 -40
  246. package/scripts/rhythm/daily.ts +25 -4
  247. package/scripts/skills/public-check.ts +62 -12
  248. package/scripts/test-deterministic.ts +51 -33
  249. package/scripts/test-diet/effectiveness.ts +62 -1
  250. package/scripts/test-diet/lib.ts +1 -1
  251. package/scripts/test-diet/node.ts +8 -1
  252. package/scripts/video/scene-table.ts +159 -0
  253. package/src/acp/core-turn-bridge.ts +39 -2
  254. package/src/agent-mission/review-loop.ts +4 -3
  255. package/src/agent-substrate/pr-comment-meta.ts +20 -0
  256. package/src/autopilot/build/llm-conflict-merge.ts +28 -8
  257. package/src/away/presence.ts +40 -0
  258. package/src/away/release-watch.ts +255 -0
  259. package/src/briefing/brief-items.ts +86 -17
  260. package/src/chat/index.ts +2 -1
  261. package/src/chat/short-question.ts +3 -40
  262. package/src/cli/autopilot-cli.ts +30 -11
  263. package/src/cli/away-cli.ts +68 -0
  264. package/src/cli/brief-cli.ts +36 -2
  265. package/src/cli/freeze-cli.ts +3 -2
  266. package/src/cli/guardian-cli.ts +49 -0
  267. package/src/cli/intake-cli.ts +85 -7
  268. package/src/cli/log-event-names.ts +1 -0
  269. package/src/cli/notify-cli.ts +75 -0
  270. package/src/cli/pod-cli.ts +5 -1
  271. package/src/cli/provider-cli.ts +12 -2
  272. package/src/cli/pty-drive-cli.ts +14 -3
  273. package/src/cli/release-cli.ts +128 -19
  274. package/src/cli/setup-cli.ts +31 -1
  275. package/src/cli/tasks-cli.ts +403 -4
  276. package/src/context-bus/condense.ts +19 -3
  277. package/src/context-bus/context-day.ts +77 -24
  278. package/src/context-bus/context-first.ts +33 -0
  279. package/src/context-bus/context-now-public.ts +1 -1
  280. package/src/context-bus/context-now-surfaces.ts +16 -6
  281. package/src/context-bus/context-now.ts +97 -3
  282. package/src/core-turn/short-question.ts +100 -0
  283. package/src/dashboard/dashboard-status-lines.ts +24 -4
  284. package/src/dashboard/index.ts +50 -5
  285. package/src/dashboard/input/slash-executor.ts +6 -2
  286. package/src/dashboard/slash-runtime/dashboard-handlers.ts +63 -36
  287. package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
  288. package/src/dashboard/slash-runtime/help-from-registry.ts +21 -8
  289. package/src/dashboard/slash-runtime/help-groups.ts +44 -0
  290. package/src/dashboard/slash-runtime/model-choices.ts +125 -0
  291. package/src/discord-self-message.ts +20 -2
  292. package/src/discord-slash-wire.ts +1 -0
  293. package/src/domains/guardian-tick.ts +138 -0
  294. package/src/domains/outbound-alert.ts +244 -40
  295. package/src/domains/outbound-log-probe.ts +16 -1
  296. package/src/domains/release-tool.ts +2 -2
  297. package/src/domains/schedule-registry.ts +23 -0
  298. package/src/domains/telegram-kind-route.ts +39 -0
  299. package/src/domains/watch-default-scheduler.ts +95 -0
  300. package/src/esc-abort-gate.ts +44 -0
  301. package/src/execution-loop/gates.ts +11 -2
  302. package/src/flow-loop/split.ts +8 -4
  303. package/src/git-fs/worktree.ts +23 -3
  304. package/src/graph-runner/runner.ts +25 -5
  305. package/src/harness/harness-cli-command.ts +12 -2
  306. package/src/harness/harness-queue.ts +388 -33
  307. package/src/harness/harness-salvage-cli.ts +78 -0
  308. package/src/harness/harness-worktree-add.ts +68 -0
  309. package/src/harness/helper-scan.ts +10 -3
  310. package/src/harness/ledger-write-guard.ts +6 -16
  311. package/src/hq/hq.ts +6 -1
  312. package/src/hq/seats.ts +369 -0
  313. package/src/index.ts +185 -18
  314. package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
  315. package/src/instance/test-write-guard.ts +200 -0
  316. package/src/intake-plane/adopt-gate.ts +49 -0
  317. package/src/intake-plane/check.ts +3 -0
  318. package/src/intake-plane/collect-github.ts +57 -25
  319. package/src/intake-plane/digest.ts +39 -15
  320. package/src/intake-plane/discord-seat-work.ts +8 -2
  321. package/src/intake-plane/harness-target.ts +4 -3
  322. package/src/intake-plane/intake-sources.ts +1 -0
  323. package/src/intake-plane/items.ts +1 -0
  324. package/src/intake-plane/requirement-funnel.ts +316 -0
  325. package/src/knowledge/know-find.ts +10 -1
  326. package/src/loops/budget.ts +36 -0
  327. package/src/loops/orchestrator/degradation.ts +57 -0
  328. package/src/loops/orchestrator/finish-rate.ts +106 -4
  329. package/src/loops/orchestrator/grid-place.ts +11 -0
  330. package/src/loops/orchestrator/tick.ts +450 -54
  331. package/src/maturity/feature-maturity.ts +8 -5
  332. package/src/maturity/tui-slash-maturity.ts +1 -0
  333. package/src/mss/logging/log-store.ts +3 -0
  334. package/src/nexus/api/decisions-route.ts +11 -1
  335. package/src/nexus/api/http-server.ts +16 -5
  336. package/src/nexus/api/loop-edges.ts +352 -23
  337. package/src/nexus/api/ops-api.ts +96 -28
  338. package/src/nexus/api/ops-seats-sources.ts +2 -2
  339. package/src/nexus/api/workflow-templates.ts +3 -0
  340. package/src/nexus/index.ts +7 -0
  341. package/src/oauth/codex.ts +38 -0
  342. package/src/onboarding/first-chat-gate.ts +29 -0
  343. package/src/release-loop/checklist.ts +106 -6
  344. package/src/release-loop/feature-store.ts +64 -16
  345. package/src/release-loop/landing-freeze.ts +6 -4
  346. package/src/release-loop/light-rc.ts +173 -0
  347. package/src/release-loop/placement.ts +64 -20
  348. package/src/release-loop/rubric.ts +136 -0
  349. package/src/seat-dispatch/seat-ask.ts +79 -16
  350. package/src/seat-loop/seat-loop.ts +12 -6
  351. package/src/self-dev/ask-launch-flow.ts +24 -3
  352. package/src/self-dev/ask-path-candidates.ts +68 -0
  353. package/src/self-dev/dev-cli.ts +36 -0
  354. package/src/self-dev/dev-pipeline.ts +4 -0
  355. package/src/self-dev/draft-triage-rules.ts +1 -1
  356. package/src/self-dev/orchestrate-cli.ts +22 -0
  357. package/src/self-dev/orchestrate.ts +1 -0
  358. package/src/self-dev/pr-preview.ts +179 -0
  359. package/src/self-dev/round-pr-supersede.ts +244 -0
  360. package/src/self-dev/run-supervisor.ts +14 -0
  361. package/src/self-dev/shard-run.ts +23 -7
  362. package/src/self-implement/follow-up-goals.ts +2 -2
  363. package/src/self-implement/gate-cli.ts +29 -8
  364. package/src/self-implement/gate-remote.ts +427 -0
  365. package/src/self-implement/goal-author-clarification.ts +88 -13
  366. package/src/self-implement/goal-author.ts +8 -3
  367. package/src/self-implement/goal-clarification-escalation.ts +2 -0
  368. package/src/self-implement/l8-merge-queue.ts +29 -5
  369. package/src/self-implement/orchestrator.ts +61 -13
  370. package/src/self-implement/prior-draft-findings.ts +4 -3
  371. package/src/self-implement/salvage-branches.ts +254 -0
  372. package/src/skills/skill-feedback.ts +76 -0
  373. package/src/task-agent/actions.ts +101 -7
  374. package/src/task-agent/board.ts +132 -0
  375. package/src/task-agent/card-evidence.ts +203 -0
  376. package/src/task-agent/completion-evidence.ts +126 -0
  377. package/src/task-agent/judge.ts +49 -5
  378. package/src/task-agent/live-moves.ts +176 -0
  379. package/src/task-agent/mission.ts +449 -0
  380. package/src/task-agent/shadow.ts +258 -0
  381. package/src/task-agent/task-hand.ts +337 -0
  382. package/src/task-orchestrator/surfaces/pod-command-job.ts +64 -4
  383. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +10 -0
  384. package/src/task-orchestrator/surfaces/pod-lease.ts +142 -13
  385. package/src/task-orchestrator/surfaces/pod-pool.ts +13 -5
  386. package/src/task-orchestrator/surfaces/self-implement-pod.ts +88 -20
  387. package/src/telegram-away-command.ts +38 -0
  388. package/src/telegram-commands.ts +11 -0
  389. package/src/telegram-loops-command.ts +1 -1
  390. package/src/telegram-report-log-probe.ts +21 -0
  391. package/src/telegram-report.ts +27 -3
  392. package/src/telegram.ts +5 -20
  393. package/src/tui-client/dashboard-session.ts +1 -0
  394. package/src/user-config.ts +196 -4
  395. package/src/version/packed-revision.json +1 -1
  396. package/apps/pwa/out/_next/static/chunks/1307-aac7332576c4d6fc.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
  401. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +0 -2
  420. package/apps/pwa/out/_next/static/chunks/6877-fe94e76a5841ee9b.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
  429. package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/approvals/page-3143b07a817d1da9.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-17ba086ef82d339b.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/bots/page-6e5c3a867557298b.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/chat/page-9a41d65355fc25b9.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/app/control/page-41afec0b255ed2bb.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/app/design-check/page-4187666132fc98c4.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/app/editor/page-09328d499162bae4.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/app/exec/page-29ff3a8f84f03b6a.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/app/field/page-80ec7401bc83f511.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/app/intake/page-fc7d86467291ae38.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/app/layout-de43a186e5ba5683.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/app/morning/page-043a9111e74f6b24.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/app/not-found-23a4601ceb7f452e.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-10d958605a77b3bd.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a04260b8ccc968d4.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/app/reflection/page-12916918988ac97e.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-5a64cb5208c6a8fd.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/app/settings/page-1407ec83ea64c1f9.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-1d824510df025d18.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/share/page-7f49fe645d5132f7.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/tasks/page-27d71f131acd9320.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/trace/page-424659bacfb8cdd7.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-fc2df261f8a2c7df.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d4fef9ccd7d406b.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +0 -1
  489. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
  490. package/apps/pwa/out/_next/static/yRA5ur6DcPl0YHSRheH2O/_buildManifest.js +0 -1
  491. /package/apps/pwa/out/_next/static/{yRA5ur6DcPl0YHSRheH2O → J5H7BvqgSI8DUjtRRcA_S}/_ssgManifest.js +0 -0
@@ -5,6 +5,8 @@ import { Database } from 'bun:sqlite';
5
5
  import { releaseLedgerRoot } from '../instance/resolve.js';
6
6
  import { CliUserError } from '../cli/cli-user-error.js';
7
7
  import { debug } from '../debug/log.js';
8
+ import { getUserConfig } from '../user-config.js';
9
+ import { hqSeenGenerationPath, readHqLease, readSeenGeneration } from '../hq/hq.js';
8
10
  import { devVersion, type Checklist, type ChecklistHistory, type ChecklistItem } from './checklist.js';
9
11
 
10
12
  export function validateVersion(version: string): void {
@@ -32,8 +34,8 @@ export function releasedVersion(ledgerRoot = releaseLedgerRoot()): string {
32
34
  const walWait = new Int32Array(new SharedArrayBuffer(4));
33
35
  const SCHEMA_COLUMNS: Record<string, readonly string[]> = {
34
36
  imported_versions: ['json_hash', 'imported_at'],
35
- events: ['reason'],
36
- assignments: ['priority', 'predecessors', 'deadline_version', 'ceo_minutes', 'ceo_date', 'accelerator'],
37
+ events: ['reason', 'generation'],
38
+ assignments: ['priority', 'predecessors', 'deadline_version', 'ceo_minutes', 'ceo_date', 'accelerator', 'refs'],
37
39
  release_schedules: ['freeze_from', 'freeze_until'],
38
40
  };
39
41
  function schemaCurrent(db: Database): boolean {
@@ -72,10 +74,12 @@ function open(root = releaseLedgerRoot()): Database {
72
74
  const columns = db.query('PRAGMA table_info(imported_versions)').all() as Array<{ name: string }>;
73
75
  if (!columns.some((column) => column.name === 'json_hash')) db.exec('ALTER TABLE imported_versions ADD COLUMN json_hash TEXT');
74
76
  if (!columns.some((column) => column.name === 'imported_at')) db.exec('ALTER TABLE imported_versions ADD COLUMN imported_at TEXT');
75
- if (!(db.query('PRAGMA table_info(events)').all() as Array<{ name: string }>).some((column) => column.name === 'reason')) db.exec('ALTER TABLE events ADD COLUMN reason TEXT');
77
+ const eventColumns = db.query('PRAGMA table_info(events)').all() as Array<{ name: string }>;
78
+ if (!eventColumns.some((column) => column.name === 'reason')) db.exec('ALTER TABLE events ADD COLUMN reason TEXT');
79
+ if (!eventColumns.some((column) => column.name === 'generation')) db.exec('ALTER TABLE events ADD COLUMN generation INTEGER');
76
80
  db.exec('CREATE TABLE IF NOT EXISTS release_schedules (version TEXT PRIMARY KEY, cut_at TEXT NOT NULL, land_by TEXT, updated_at TEXT NOT NULL, updated_by TEXT NOT NULL)');
77
81
  const assignmentColumns = db.query('PRAGMA table_info(assignments)').all() as Array<{ name: string }>;
78
- for (const [name, sql] of [['priority', 'TEXT'], ['predecessors', 'TEXT'], ['deadline_version', 'TEXT'], ['ceo_minutes', 'INTEGER'], ['ceo_date', 'TEXT'], ['accelerator', 'INTEGER']] as const) {
82
+ for (const [name, sql] of [['priority', 'TEXT'], ['predecessors', 'TEXT'], ['deadline_version', 'TEXT'], ['ceo_minutes', 'INTEGER'], ['ceo_date', 'TEXT'], ['accelerator', 'INTEGER'], ['refs', 'TEXT']] as const) {
79
83
  if (!assignmentColumns.some((column) => column.name === name)) db.exec(`ALTER TABLE assignments ADD COLUMN ${name} ${sql}`);
80
84
  }
81
85
  const scheduleColumns = db.query('PRAGMA table_info(release_schedules)').all() as Array<{ name: string }>;
@@ -155,21 +159,21 @@ function readTransaction<T>(db: Database, work: () => T): T {
155
159
  catch (error) { db.exec('ROLLBACK'); throw error; }
156
160
  }
157
161
 
158
- type EventRow = { seq: number; at: string; by: string; feature_id: string; version: string; field: string; from: string; to: string; released: string; dev: string; reason: string | null };
162
+ type EventRow = { seq: number; at: string; by: string; feature_id: string; version: string; field: string; from: string; to: string; released: string; dev: string; reason: string | null; generation: number | null };
159
163
  function decode(value: string | null): unknown { return value === null ? null : JSON.parse(value); }
160
164
  function historyRow(row: EventRow): ChecklistHistory {
161
165
  return { at: row.at, by: row.by, id: row.feature_id, field: row.field, from: decode(row.from), to: decode(row.to), released: row.released, dev: row.dev,
162
- ...(row.reason !== null ? { reason: row.reason } : {}) };
166
+ ...(row.reason !== null ? { reason: row.reason } : {}), ...(row.generation !== null ? { generation: row.generation } : {}) };
163
167
  }
164
168
  function record(db: Database, version: string, entry: ChecklistHistory): void {
165
- db.query('INSERT INTO events (at, by, feature_id, version, field, "from", "to", released, dev, reason) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
166
- .run(entry.at, entry.by, entry.id, version, entry.field, JSON.stringify(entry.from ?? null), JSON.stringify(entry.to ?? null), entry.released, entry.dev, entry.reason ?? null);
169
+ db.query('INSERT INTO events (at, by, feature_id, version, field, "from", "to", released, dev, reason, generation) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
170
+ .run(entry.at, entry.by, entry.id, version, entry.field, JSON.stringify(entry.from ?? null), JSON.stringify(entry.to ?? null), entry.released, entry.dev, entry.reason ?? null, entry.generation ?? null);
167
171
  }
168
172
  function evidenceRefs(db: Database, version: string, id: string): string[] {
169
173
  return (db.query('SELECT ref FROM evidence WHERE feature_id = ? AND version = ? ORDER BY rowid').all(id, version) as Array<{ ref: string }>).map((row) => row.ref);
170
174
  }
171
175
  function items(db: Database, version: string): ChecklistItem[] {
172
- const rows = db.query(`SELECT f.id, COALESCE(a.title_override, f.title) AS title, a.owner, a.kind, a.priority, a.predecessors, a.deadline_version, a.ceo_minutes, a.ceo_date, a.accelerator, a.status, a.disposition, a.evidence, a.updated_at, a.updated_by
176
+ const rows = db.query(`SELECT f.id, COALESCE(a.title_override, f.title) AS title, a.owner, a.kind, a.priority, a.predecessors, a.deadline_version, a.ceo_minutes, a.ceo_date, a.accelerator, a.refs, a.status, a.disposition, a.evidence, a.updated_at, a.updated_by
173
177
  FROM assignments a JOIN features f ON f.id = a.feature_id WHERE a.version = ? ORDER BY a.rowid`).all(version) as Array<Record<string, string | number | null>>;
174
178
  return rows.map((r) => {
175
179
  const evidence = [r.evidence, ...evidenceRefs(db, version, r.id as string)].filter((value) => value !== null).join('\n');
@@ -179,6 +183,7 @@ function items(db: Database, version: string): ChecklistItem[] {
179
183
  ...(r.deadline_version !== null ? { deadlineVersion: r.deadline_version as string } : {}),
180
184
  ...(r.ceo_minutes !== null ? { ceoMinutes: r.ceo_minutes as number } : {}), ...(r.ceo_date !== null ? { ceoDate: r.ceo_date as string } : {}),
181
185
  ...(r.accelerator === 1 ? { accelerator: true as const } : {}),
186
+ ...(r.refs !== null && r.refs !== undefined ? { refs: JSON.parse(r.refs as string) as string[] } : {}),
182
187
  ...(r.evidence !== null || evidence ? { evidence } : {}), ...(r.disposition !== null ? { disposition: r.disposition as ChecklistItem['disposition'] } : {}),
183
188
  updatedAt: r.updated_at as string, updatedBy: r.updated_by as string };
184
189
  });
@@ -192,9 +197,9 @@ function putItem(db: Database, version: string, item: ChecklistItem): void {
192
197
  if (!feature) db.query('INSERT INTO features (id, title, owner, kind, created_at) VALUES (?, ?, ?, ?, ?)').run(item.id, item.title, item.owner ?? null, item.kind ?? null, item.updatedAt);
193
198
  // features.owner/kind follow the latest assignment so details() and the checklist never disagree after a set.
194
199
  else if (item.owner !== undefined || item.kind !== undefined) db.query('UPDATE features SET owner = COALESCE(?, owner), kind = COALESCE(?, kind) WHERE id = ?').run(item.owner ?? null, item.kind ?? null, item.id);
195
- db.query(`INSERT INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, ceo_minutes, ceo_date, accelerator, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
196
- ON CONFLICT(feature_id, version) DO UPDATE SET status=excluded.status, disposition=excluded.disposition, evidence=excluded.evidence, owner=excluded.owner, kind=excluded.kind, priority=excluded.priority, predecessors=excluded.predecessors, deadline_version=excluded.deadline_version, ceo_minutes=excluded.ceo_minutes, ceo_date=excluded.ceo_date, accelerator=excluded.accelerator, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
197
- .run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, feature && feature.title !== item.title ? item.title : null, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.ceoMinutes ?? null, item.ceoDate ?? null, item.accelerator === true ? 1 : null, item.updatedAt, item.updatedBy);
200
+ db.query(`INSERT INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, ceo_minutes, ceo_date, accelerator, refs, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
201
+ ON CONFLICT(feature_id, version) DO UPDATE SET status=excluded.status, disposition=excluded.disposition, evidence=excluded.evidence, owner=excluded.owner, kind=excluded.kind, priority=excluded.priority, predecessors=excluded.predecessors, deadline_version=excluded.deadline_version, ceo_minutes=excluded.ceo_minutes, ceo_date=excluded.ceo_date, accelerator=excluded.accelerator, refs=excluded.refs, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
202
+ .run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, feature && feature.title !== item.title ? item.title : null, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.ceoMinutes ?? null, item.ceoDate ?? null, item.accelerator === true ? 1 : null, item.refs?.length ? JSON.stringify(item.refs) : null, item.updatedAt, item.updatedBy);
198
203
  }
199
204
  function jsonHash(contents: string | Buffer): string { return createHash('sha256').update(contents).digest('hex'); }
200
205
 
@@ -235,8 +240,8 @@ function importOnDb(db: Database, version: string): boolean {
235
240
  // Keep a legacy per-version title until an explicit retitle unifies the feature identity.
236
241
  if (!existing) putItem(db, version, item);
237
242
  else {
238
- db.query('INSERT OR IGNORE INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, ceo_minutes, ceo_date, accelerator, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
239
- .run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, item.title === existing.title ? null : item.title, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.ceoMinutes ?? null, item.ceoDate ?? null, item.accelerator === true ? 1 : null, item.updatedAt, item.updatedBy);
243
+ db.query('INSERT OR IGNORE INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, ceo_minutes, ceo_date, accelerator, refs, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
244
+ .run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, item.title === existing.title ? null : item.title, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.ceoMinutes ?? null, item.ceoDate ?? null, item.accelerator === true ? 1 : null, item.refs?.length ? JSON.stringify(item.refs) : null, item.updatedAt, item.updatedBy);
240
245
  }
241
246
  }
242
247
  for (const entry of data.history) record(db, version, entry);
@@ -263,7 +268,7 @@ function importOnDb(db: Database, version: string): boolean {
263
268
  record(db, version, { at: new Date().toISOString(), by: item.updatedBy, id: item.id, field: 'reimport', from: null, to: item, released: data.released, dev: data.dev });
264
269
  continue;
265
270
  }
266
- const fields = ['title', 'status', 'evidence', 'owner', 'disposition', 'kind', 'priority', 'predecessors', 'deadlineVersion', 'ceoMinutes', 'ceoDate', 'accelerator'] as const;
271
+ const fields = ['title', 'status', 'evidence', 'owner', 'disposition', 'kind', 'priority', 'predecessors', 'deadlineVersion', 'ceoMinutes', 'ceoDate', 'accelerator', 'refs'] as const;
267
272
  const baseEvidence = (db.query('SELECT evidence FROM assignments WHERE feature_id = ? AND version = ?').get(item.id, version) as { evidence: string | null }).evidence ?? undefined;
268
273
  const preserveRefs = item.evidence === baseEvidence || item.evidence === current.evidence;
269
274
  const differs = fields.some((field) => field === 'evidence'
@@ -333,6 +338,30 @@ export interface ChecklistCollision { version: string; owner?: string; title: st
333
338
 
334
339
  export function mutate(version: string, released: string, dev: string, apply: (data: Checklist, otherItems: (id: string) => ChecklistCollision[]) => boolean): Checklist {
335
340
  validateVersion(version);
341
+ // COORD-HA ①(RFC-loop-agent-map §A8b): a write carries the generation this process saw; a stale one is refused only
342
+ // under hq.boardFencing 'enforce' (default shadow = write and log). The lease is read ONCE, before the write transaction —
343
+ // with an ssh arbiter a lease read inside the transaction would hold the ledger write lock across the network.
344
+ // Only 'enforce' (opt-in) re-reads once inside the transaction just before writing, so a lease taken over meanwhile
345
+ // still rolls the write back; shadow never touches the lease under the lock.
346
+ const hq = getUserConfig().hq;
347
+ const writerGeneration = hq ? readSeenGeneration(hqSeenGenerationPath(hq)) : null;
348
+ let recordedGeneration: number | null = null;
349
+ let firstMeasured = false;
350
+ if (hq) {
351
+ let lease: ReturnType<typeof readHqLease>['record'] | undefined;
352
+ try { lease = readHqLease({ config: hq }).record; }
353
+ catch (error) { debug.log('hq.fence', 'board-lease-unmeasured', { writer: writerGeneration, version, reason: String(error).slice(0, 200) }); }
354
+ // A missing lease (null) and a broken one (parseLease throws → caught above with its reason) are both «unmeasured».
355
+ if (lease === null) debug.log('hq.fence', 'board-lease-unmeasured', { writer: writerGeneration, version, reason: 'no-lease' });
356
+ if (lease) {
357
+ firstMeasured = true;
358
+ recordedGeneration = writerGeneration;
359
+ if (writerGeneration !== null && writerGeneration < lease.generation) {
360
+ debug.log('hq.fence', 'board-stale-write', { writer: writerGeneration, lease: lease.generation, version });
361
+ if (hq.boardFencing === 'enforce') throw new CliUserError(`낡은 세대 ${writerGeneration} < ${lease.generation} — 쓰기 거부`);
362
+ }
363
+ }
364
+ }
336
365
  const db = open();
337
366
  try {
338
367
  let imported = false;
@@ -351,6 +380,22 @@ export function mutate(version: string, released: string, dev: string, apply: (d
351
380
  return { version: item.version, ...(item.owner !== null ? { owner: item.owner } : {}), title: item.title };
352
381
  });
353
382
  })) {
383
+ if (hq?.boardFencing === 'enforce') {
384
+ let late: ReturnType<typeof readHqLease>['record'] = null;
385
+ let lateReason = 'no-lease';
386
+ try { late = readHqLease({ config: hq }).record; }
387
+ catch (error) { late = null; lateReason = String(error).slice(0, 200); }
388
+ if (!late) {
389
+ // Unmeasured right before the write: the write still goes through, but carries no generation it cannot vouch for.
390
+ // (Logged only when the first read had measured — an unmeasured first read was already logged once.)
391
+ if (firstMeasured) debug.log('hq.fence', 'board-lease-unmeasured', { writer: writerGeneration, version, reason: lateReason, phase: 'before-write' });
392
+ recordedGeneration = null;
393
+ }
394
+ if (late && writerGeneration !== null && writerGeneration < late.generation) {
395
+ debug.log('hq.fence', 'board-stale-write', { writer: writerGeneration, lease: late.generation, version });
396
+ throw new CliUserError(`낡은 세대 ${writerGeneration} < ${late.generation} — 쓰기 거부`);
397
+ }
398
+ }
354
399
  for (const item of data.items) if (before.get(item.id) !== JSON.stringify(item)) {
355
400
  const previous = before.get(item.id);
356
401
  const evidenceChanged = previous && (JSON.parse(previous) as ChecklistItem).evidence !== item.evidence;
@@ -364,7 +409,10 @@ export function mutate(version: string, released: string, dev: string, apply: (d
364
409
  db.query('DELETE FROM evidence WHERE feature_id = ? AND version = ?').run(id, version);
365
410
  db.query('DELETE FROM assignments WHERE feature_id = ? AND version = ?').run(id, version);
366
411
  }
367
- for (const entry of data.history.slice(count)) record(db, version, entry);
412
+ for (const entry of data.history.slice(count)) {
413
+ if (recordedGeneration !== null) entry.generation = recordedGeneration;
414
+ record(db, version, entry);
415
+ }
368
416
  }
369
417
  return data;
370
418
  });
@@ -8,6 +8,7 @@ export interface LandingFreeze {
8
8
  startedAt: string;
9
9
  until: string | null;
10
10
  by: string;
11
+ holdLaunches?: boolean;
11
12
  }
12
13
 
13
14
  export function landingFreezePath(root = effectiveInstanceRoot()): string {
@@ -25,21 +26,22 @@ export function readLandingFreeze(root = effectiveInstanceRoot(), now = new Date
25
26
  const freeze = record as Partial<LandingFreeze>;
26
27
  if (typeof freeze.reason !== 'string' || typeof freeze.by !== 'string' ||
27
28
  typeof freeze.startedAt !== 'string' || !Number.isFinite(Date.parse(freeze.startedAt)) ||
28
- !(freeze.until === null || (typeof freeze.until === 'string' && Number.isFinite(Date.parse(freeze.until))))) {
29
+ !(freeze.until === null || (typeof freeze.until === 'string' && Number.isFinite(Date.parse(freeze.until)))) ||
30
+ (freeze.holdLaunches !== undefined && typeof freeze.holdLaunches !== 'boolean')) {
29
31
  throw new Error(`invalid landing freeze: ${path}`);
30
32
  }
31
33
  // An expired freeze reads as «off». The file is left alone: deleting it here could race a concurrent `freeze on`
32
34
  // that just wrote a new one (only `freeze off` removes it; the next `freeze on` overwrites it).
33
35
  if (freeze.until && Date.parse(freeze.until) <= now.getTime()) return null;
34
- return freeze as LandingFreeze;
36
+ return { ...freeze, holdLaunches: freeze.holdLaunches ?? false } as LandingFreeze;
35
37
  }
36
38
 
37
- export function enableLandingFreeze(opts: { reason?: string; until?: string; by?: string }, root = effectiveInstanceRoot(), now = new Date()): LandingFreeze {
39
+ export function enableLandingFreeze(opts: { reason?: string; until?: string; by?: string; holdLaunches?: boolean }, root = effectiveInstanceRoot(), now = new Date()): LandingFreeze {
38
40
  const until = opts.until === undefined ? null : opts.until;
39
41
  if (until !== null && (!/^\d{4}-\d\d-\d\dT\d\d:\d\d(?::\d\d(?:\.\d+)?)?(?:Z|[+-]\d\d:\d\d)$/.test(until) || !Number.isFinite(Date.parse(until)) || Date.parse(until) <= now.getTime())) {
40
42
  throw new Error('--until must be a future ISO timestamp with timezone');
41
43
  }
42
- const freeze: LandingFreeze = { reason: opts.reason?.trim() || 'operator freeze', startedAt: now.toISOString(), until: until === null ? null : new Date(until).toISOString(), by: opts.by?.trim() || process.env.ELANOUS_TRACK || process.env.USER || 'cli' };
44
+ const freeze: LandingFreeze = { reason: opts.reason?.trim() || 'operator freeze', startedAt: now.toISOString(), until: until === null ? null : new Date(until).toISOString(), by: opts.by?.trim() || process.env.ELANOUS_TRACK || process.env.USER || 'cli', holdLaunches: opts.holdLaunches ?? false };
43
45
  const path = landingFreezePath(root);
44
46
  mkdirSync(root, { recursive: true });
45
47
  const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
@@ -0,0 +1,173 @@
1
+ /** LIGHT-RC (0.2.19) — a light release-candidate check between cuts: the changed-file self gate over everything that
2
+ * landed since the previous release's cut, plus an always-run test list (release/light-rc-always.txt), on the remote
3
+ * gate host (GATE-REMOTE). Read-only for the release ledger: it reads the previous published version and never writes.
4
+ * It does not touch the landing freeze or launch Pods. */
5
+ import { spawnSync } from 'node:child_process';
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { debug } from '../debug/log.js';
9
+ import { runSelfGateCli, type SelfGateCliOptions, type SelfGateCliResult } from '../self-implement/gate-cli.js';
10
+ import { dispatchHeavyCheck, isValidGateHost, type DispatchDeps, type DispatchOptions } from '../self-implement/gate-remote.js';
11
+ import { latestPublishedPreviousVersion } from '../../scripts/release-loop/unattended-release.js';
12
+ import { baseVersion, isReleaseVersion } from '../../scripts/release-loop/release-version.js';
13
+
14
+ export const LIGHT_RC_ALWAYS_FILE = 'release/light-rc-always.txt';
15
+ export const LIGHT_RC_DEFAULT_SHARDS = 4;
16
+ export const LIGHT_RC_NO_PREVIOUS_CUT = '직전 판 컷을 못 찾았다 — --base 로 준다';
17
+
18
+ export interface LightRcOptions {
19
+ base?: string;
20
+ /** Gate host; absent = config gateRemote.host (the dispatch's own default). */
21
+ remote?: string;
22
+ /** Run on this machine (the remote child is re-invoked with it). */
23
+ local?: boolean;
24
+ shards?: number;
25
+ /** Release ledger root for the previous-version lookup (release run's deps.ledgerRoot). */
26
+ ledgerRoot?: string;
27
+ log?: (line: string) => void;
28
+ }
29
+
30
+ export interface LightRcResult {
31
+ version: string;
32
+ base: string;
33
+ /** New failures vs. the base; null = the gate produced no measurement (never read as green). */
34
+ introduced: number | null;
35
+ preexisting: number | null;
36
+ /** Failures the baseline could not classify (unknown + precondition-unmet); null = unmeasured. Non-zero is not green. */
37
+ unclassified: number | null;
38
+ alwaysInclude: number;
39
+ alwaysIncludeMissing: string[];
40
+ shards: number;
41
+ /** Host the gate ran on; null = this machine. */
42
+ remote: string | null;
43
+ gateExitCode: number;
44
+ ok: boolean;
45
+ }
46
+
47
+ export interface LightRcDeps {
48
+ cwd?: string;
49
+ git?: (args: string[], cwd: string) => { rc: number | null; stdout: string };
50
+ previousVersion?: (version: string, ledgerRoot?: string) => string;
51
+ readFile?: (path: string) => string | undefined;
52
+ runGate?: (cwd: string, options: SelfGateCliOptions) => SelfGateCliResult;
53
+ dispatch?: (opts: DispatchOptions, deps?: DispatchDeps) => Promise<number>;
54
+ /** Extra dispatch deps (runner · settings) — tests drive the real dispatch with a fake host. */
55
+ dispatchDeps?: Omit<DispatchDeps, 'write'>;
56
+ }
57
+
58
+ /** Shown when the gate ran remotely but the host name did not come back. */
59
+ export const LIGHT_RC_UNKNOWN_HOST = '(remote · host unknown)';
60
+
61
+ const defaultGit = (args: string[], cwd: string) => {
62
+ const run = spawnSync('git', args, { cwd, encoding: 'utf8' });
63
+ return { rc: run.error ? null : run.status, stdout: run.stdout ?? '' };
64
+ };
65
+
66
+ /** One repository-relative path per line; `#` starts a comment; blank lines are skipped. */
67
+ export function parseAlwaysIncludeList(text: string): string[] {
68
+ return [...new Set(text.split('\n').map((line) => line.replace(/#.*/, '').trim()).filter(Boolean))];
69
+ }
70
+
71
+ /** The previous release's cut: merge-base of origin/main and origin/release/<previous published version>. */
72
+ export function previousCutBase(version: string, deps: LightRcDeps & { cwd: string; ledgerRoot?: string }): string {
73
+ let previous: string;
74
+ try {
75
+ previous = (deps.previousVersion ?? latestPublishedPreviousVersion)(baseVersion(version), deps.ledgerRoot);
76
+ } catch (error) {
77
+ throw new Error(`${LIGHT_RC_NO_PREVIOUS_CUT} (${error instanceof Error ? error.message : String(error)})`);
78
+ }
79
+ const mergeBase = (deps.git ?? defaultGit)(['merge-base', 'origin/main', `origin/release/${previous}`], deps.cwd);
80
+ const sha = mergeBase.stdout.trim();
81
+ if (mergeBase.rc !== 0 || !sha) throw new Error(`${LIGHT_RC_NO_PREVIOUS_CUT} (git merge-base origin/main origin/release/${previous} rc=${mergeBase.rc})`);
82
+ return sha;
83
+ }
84
+
85
+ function repoRoot(deps: LightRcDeps): string {
86
+ if (deps.cwd) return deps.cwd;
87
+ const top = defaultGit(['rev-parse', '--show-toplevel'], process.cwd());
88
+ if (top.rc !== 0 || !top.stdout.trim()) throw new Error('release light-rc: not inside a git repository');
89
+ return top.stdout.trim();
90
+ }
91
+
92
+ function parseRemoteResult(stdout: string): Partial<LightRcResult> | undefined {
93
+ const line = stdout.split('\n').map((l) => l.trim()).reverse().find((l) => l.startsWith('{'));
94
+ if (!line) return undefined;
95
+ try {
96
+ const parsed = JSON.parse(line) as Partial<LightRcResult> & { host?: unknown };
97
+ return parsed && typeof parsed === 'object' ? parsed : undefined;
98
+ } catch { return undefined; }
99
+ }
100
+
101
+ export async function runLightRc(version: string, opts: LightRcOptions = {}, deps: LightRcDeps = {}): Promise<LightRcResult> {
102
+ if (!isReleaseVersion(version)) throw new Error(`release version must be x.y.z: ${version}`);
103
+ const shards = opts.shards ?? LIGHT_RC_DEFAULT_SHARDS;
104
+ if (!Number.isSafeInteger(shards) || shards < 1) throw new Error(`invalid --shards count: ${opts.shards}`);
105
+ if (opts.remote !== undefined && !isValidGateHost(opts.remote)) throw new Error(`invalid --remote host: ${opts.remote}`);
106
+ const log = opts.log ?? ((line: string) => process.stderr.write(`${line}\n`));
107
+ const cwd = repoRoot(deps);
108
+ const base = opts.base ?? previousCutBase(version, { ...deps, cwd, ledgerRoot: opts.ledgerRoot });
109
+ const listPath = join(cwd, LIGHT_RC_ALWAYS_FILE);
110
+ const listText = (deps.readFile ?? ((path: string) => existsSync(path) ? readFileSync(path, 'utf8') : undefined))(listPath);
111
+ if (listText === undefined) throw new Error(`release light-rc: ${LIGHT_RC_ALWAYS_FILE} not found`);
112
+ const alwaysInclude = parseAlwaysIncludeList(listText);
113
+
114
+ let local: SelfGateCliResult | undefined;
115
+ const runLocal = () => {
116
+ local = (deps.runGate ?? runSelfGateCli)(cwd, { base, shards, alwaysInclude });
117
+ for (const line of local.lines) log(line);
118
+ return local.exitCode;
119
+ };
120
+ const captured: Buffer[] = [];
121
+ const rc = await (deps.dispatch ?? dispatchHeavyCheck)({
122
+ tool: 'self-gate', repo: cwd, installPwa: true, refs: [base], json: true,
123
+ // The host re-runs this command with --local (and ELANOUS_GATE_REMOTE_CHILD=1), so it never recurses.
124
+ remoteArgv: (sha) => ['bun', 'bin/elanous.mjs', 'release', 'light-rc', '--version', version, '--base', sha(base), '--shards', String(shards), '--local', '--json'],
125
+ flags: { remote: opts.local ? undefined : opts.remote ?? true, local: !!opts.local },
126
+ runLocal,
127
+ }, {
128
+ ...deps.dispatchDeps,
129
+ write: {
130
+ out: (data) => { captured.push(Buffer.from(data)); },
131
+ err: (data) => { process.stderr.write(data); },
132
+ },
133
+ });
134
+
135
+ let result: LightRcResult;
136
+ if (local) {
137
+ result = {
138
+ version, base,
139
+ introduced: local.baseline?.introduced ?? null,
140
+ preexisting: local.baseline?.preexisting ?? null,
141
+ unclassified: local.baseline ? local.baseline.unknown + local.baseline.preconditionUnmet : null,
142
+ alwaysInclude: alwaysInclude.length,
143
+ alwaysIncludeMissing: [...local.alwaysIncludeMissing],
144
+ shards, remote: null, gateExitCode: local.exitCode, ok: false,
145
+ };
146
+ } else {
147
+ const remote = parseRemoteResult(Buffer.concat(captured).toString('utf8'));
148
+ if (!remote) throw new Error(`release light-rc: remote gate returned no result (rc=${rc})`);
149
+ const host = (remote as { host?: unknown }).host;
150
+ result = {
151
+ version, base,
152
+ introduced: typeof remote.introduced === 'number' ? remote.introduced : null,
153
+ preexisting: typeof remote.preexisting === 'number' ? remote.preexisting : null,
154
+ unclassified: typeof remote.unclassified === 'number' ? remote.unclassified : null,
155
+ alwaysInclude: alwaysInclude.length,
156
+ alwaysIncludeMissing: Array.isArray(remote.alwaysIncludeMissing) ? remote.alwaysIncludeMissing.map(String) : [],
157
+ shards, remote: typeof host === 'string' && host ? host : opts.remote ?? LIGHT_RC_UNKNOWN_HOST,
158
+ gateExitCode: typeof remote.gateExitCode === 'number' ? remote.gateExitCode : rc, ok: false,
159
+ };
160
+ }
161
+ // Unmeasured (null) or unclassified failures are never green; policy-gate findings in the gate output are reported
162
+ // (gate rc), not a regression.
163
+ result.ok = result.introduced === 0 && result.unclassified === 0;
164
+ debug.log('release.light-rc', 'done', { version, base, introduced: result.introduced, preexisting: result.preexisting, unclassified: result.unclassified, shards, remote: result.remote });
165
+ return result;
166
+ }
167
+
168
+ export function formatLightRc(result: LightRcResult): string[] {
169
+ return [
170
+ `${result.ok ? '✅' : '⛔'} light-rc ${result.version} · base ${result.base.slice(0, 12)} · ${result.remote ? `host ${result.remote}` : 'local'} · shards ${result.shards}`,
171
+ ` introduced ${result.introduced ?? '못 쟀다'} · preexisting ${result.preexisting ?? '못 쟀다'} · unclassified ${result.unclassified ?? '못 쟀다'} · always-include ${result.alwaysInclude}${result.alwaysIncludeMissing.length ? ` (missing: ${result.alwaysIncludeMissing.join(', ')})` : ''} · gate rc ${result.gateExitCode}`,
172
+ ];
173
+ }
@@ -2,12 +2,14 @@ import { spawnSync } from 'node:child_process';
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { CliUserError } from '../cli/cli-user-error.js';
5
+ import { resolveRepositoryName } from '../harness/repository-name.js';
5
6
  import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
6
7
  import { effectiveInstanceRoot } from '../instance/resolve.js';
7
8
  import { userConfigPath } from '../user-config.js';
8
9
  import { addItem, listChecklist, ownerMatches, parseOwner, setItem, validateCeoLoad, type ChecklistItem } from './checklist.js';
9
10
  import { move, releasedVersion } from './feature-store.js';
10
11
  import { listSchedules, type ReleaseSchedule } from './release-schedule.js';
12
+ import { parseRubric, rubricScore } from './rubric.js';
11
13
 
12
14
  export type PlacementPriority = 'P0' | 'P1' | 'P2';
13
15
  export interface PlacementCell { id: string; title: string; owner: string; priority: PlacementPriority; predecessors: string[]; deadlineVersion?: string; ceoMinutes?: number; ceoDate?: string; accelerator?: boolean }
@@ -39,10 +41,41 @@ function versionOrder(a: string, b: string): number {
39
41
  return 0;
40
42
  }
41
43
 
44
+ /** Where each cell id lives. An id may repeat when one copy is in an already released version — history kept two
45
+ * different cells under one id (IA1 · EV10a … in 0.2.6~0.2.15) and later cells reused old ids (INSIDE1 0.2.11 ⊕
46
+ * 0.2.19). A released copy is history: an open copy wins over it, and between released copies the latest wins. Only
47
+ * two open copies are a real conflict (ORCH-LIVE-1008 rehearsal 1: every placement failed on «여러 판의 같은 칸»). */
48
+ function cellLocations(snapshots: Iterable<[string, readonly { id: string }[]]>, released: string | null | undefined, skip: readonly string[] = []): { locations: Map<string, string>; conflict: string | null } {
49
+ const locations = new Map<string, string>();
50
+ const shipped = (version: string) => !!released && versionOrder(version, released) <= 0;
51
+ for (const [version, items] of snapshots) {
52
+ if (skip.includes(version)) continue;
53
+ for (const item of items) {
54
+ const previous = locations.get(item.id);
55
+ if (previous !== undefined) {
56
+ if (!shipped(previous) && !shipped(version)) return { locations, conflict: item.id };
57
+ // Keep the open copy; between two released copies keep the later one.
58
+ const keepPrevious = shipped(version) && (!shipped(previous) || versionOrder(version, previous) < 0);
59
+ if (keepPrevious) continue;
60
+ }
61
+ locations.set(item.id, version);
62
+ }
63
+ }
64
+ return { locations, conflict: null };
65
+ }
66
+
42
67
  /** The merged PR list is a first-class GitHub query, not a count inferred from checklist status. */
68
+ // ORCH-LIVE-1008: the orchestrator tick runs from the installed package (not a git checkout), where a bare `gh pr list`
69
+ // fails «not a git repository» and placement silently left every cell unplaced — so the repository is named explicitly
70
+ // (--repo > harness.repo > GH_REPO > the checkout's remote · `resolveRepositoryName`).
43
71
  export function mergedPrsLast24h(now = new Date()): number {
44
72
  const since = new Date(now.getTime() - 86_400_000).toISOString();
45
- const r = spawnSync('gh', ['pr', 'list', '--state', 'merged', '--search', `merged:>=${since.slice(0, 10)}`, '--limit', '1000', '--json', 'number,mergedAt'], { encoding: 'utf8' });
73
+ let repo: string;
74
+ try { repo = resolveRepositoryName(); }
75
+ catch (error) {
76
+ throw new CliUserError(`병합 PR 속도 조회 실패: 저장소를 못 정했다 — ${(error instanceof Error ? error.message : String(error)).split('\n')[0]}`, 'harness.repo 설정(owner/name) 또는 GH_REPO');
77
+ }
78
+ const r = spawnSync('gh', ['pr', 'list', '--repo', repo, '--state', 'merged', '--search', `merged:>=${since.slice(0, 10)}`, '--limit', '1000', '--json', 'number,mergedAt'], { encoding: 'utf8' });
46
79
  if (r.status !== 0) throw new CliUserError(`병합 PR 속도 조회 실패: ${(r.stderr || r.error || '').toString().trim()}`);
47
80
  let rows: Array<{ number: number; mergedAt: string }>;
48
81
  try { rows = JSON.parse(r.stdout) as typeof rows; }
@@ -91,10 +124,16 @@ function dependencyViolation(
91
124
  locations: Map<string, string>,
92
125
  overrides: Map<string, string>,
93
126
  input: PlacementCell,
127
+ released?: string | null,
94
128
  ): string | null {
95
129
  for (const [version, snapshot] of snapshots) for (const item of snapshot.items) {
96
- const dependentVersion = overrides.get(item.id) ?? version;
130
+ // A released version is history — its cells (and what they once depended on) never constrain a new placement.
131
+ if (released && versionOrder(version, released) <= 0) continue;
97
132
  const predecessors = item.id === input.id ? input.predecessors : item.predecessors ?? [];
133
+ // Only the cells this placement moves, and cells that depend on them, can be made wrong by it — a pre-existing
134
+ // violation elsewhere on the board must not make every placement infeasible (ORCH-LIVE-1008 rehearsal 1).
135
+ if (!overrides.has(item.id) && !predecessors.some((predecessor) => overrides.has(predecessor))) continue;
136
+ const dependentVersion = overrides.get(item.id) ?? version;
98
137
  for (const predecessor of predecessors) {
99
138
  const predecessorVersion = overrides.get(predecessor) ?? locations.get(predecessor);
100
139
  if (!predecessorVersion || versionOrder(dependentVersion, predecessorVersion) <= 0)
@@ -130,12 +169,8 @@ export function placeCell(input: PlacementCell, deps: PlacementDeps = {}): Place
130
169
  const open = schedules.filter((row) => (!released || versionOrder(row.version, released) > 0) && row.landBy && available(row, now, backlog));
131
170
  const sources = [...new Set([...schedules.map((row) => row.version), ...backlog])];
132
171
  const snapshots = new Map(sources.map((version) => [version, (deps.checklist ?? listChecklist)(version)]));
133
- const locations = new Map<string, string>();
134
- for (const [version, snapshot] of snapshots) for (const item of snapshot.items) {
135
- if (backlog.includes(version)) continue;
136
- if (locations.has(item.id)) throw new CliUserError(`여러 판의 같은 칸: ${item.id}`);
137
- locations.set(item.id, version);
138
- }
172
+ const { locations, conflict } = cellLocations([...snapshots].map(([version, snapshot]) => [version, snapshot.items] as [string, readonly { id: string }[]]), released, backlog);
173
+ if (conflict) throw new CliUserError(`여러 판의 같은 칸: ${conflict}`);
139
174
  // A backlog copy never outranks a scheduled cell with the same id.
140
175
  for (const version of backlog) for (const item of snapshots.get(version)?.items ?? []) if (!locations.has(item.id)) locations.set(item.id, version);
141
176
  const from = locations.get(input.id) ?? null;
@@ -171,7 +206,7 @@ export function placeCell(input: PlacementCell, deps: PlacementDeps = {}): Place
171
206
  const displaced: PlacementDecision['displaced'] = [];
172
207
  let chosen: ReleaseSchedule | undefined;
173
208
  let loadIssue: string | null = null;
174
- const feasible = candidates.filter((row) => !dependencyViolation(snapshots, locations, new Map([[input.id, row.version]]), input));
209
+ const feasible = candidates.filter((row) => !dependencyViolation(snapshots, locations, new Map([[input.id, row.version]]), input, released));
175
210
  // 판은 «마감 안 가장 이른 판»부터 본다(기존 불변식). 가속 등급은 그 판이 찼을 때 같은 우선순위 비가속 칸을 밀 수 있게만 한다.
176
211
  for (const row of feasible) {
177
212
  const placement = new Map([[input.id, row.version]]);
@@ -191,7 +226,7 @@ export function placeCell(input: PlacementCell, deps: PlacementDeps = {}): Place
191
226
  || (accelerator && priority === input.priority);
192
227
  const p2 = [...items].reverse().find((item) => {
193
228
  if (item.accelerator === true) return false;
194
- if (!victimOk(item.priority) || dependencyViolation(snapshots, locations, new Map([...placement, [item.id, next.version]]), input)) return false;
229
+ if (!victimOk(item.priority) || dependencyViolation(snapshots, locations, new Map([...placement, [item.id, next.version]]), input, released)) return false;
195
230
  if (item.deadlineVersion && versionOrder(next.version, item.deadlineVersion) > 0) return false;
196
231
  if (items.length < capacity(row) && !(seatItems.length >= seatLimit && ownerMatches(item.owner, seat))) return false;
197
232
  if (!item.owner) return false;
@@ -234,6 +269,7 @@ export function placeCell(input: PlacementCell, deps: PlacementDeps = {}): Place
234
269
  function moveConstraint(
235
270
  item: ChecklistItem, to: ReleaseSchedule, backlog: readonly string[],
236
271
  snapshots: Map<string, ChecklistItem[]>, now: number, rate: number, caps: Record<string, number>, rows: ReleaseSchedule[], ceoCap: number,
272
+ released: string | null | undefined,
237
273
  ): string | null {
238
274
  if (!to.landBy || !available(to, now, backlog)) return '착지 마감이 지났거나 백로그 판이라 이동 불가';
239
275
  if (item.deadlineVersion && versionOrder(to.version, item.deadlineVersion) > 0) return '마감 판 뒤로 이월 불가';
@@ -245,16 +281,15 @@ function moveConstraint(
245
281
  const loadIssue = ceoOverload(item, to, new Map([...snapshots].map(([version, items]) => [version, { items }])),
246
282
  rows, ceoCap);
247
283
  if (loadIssue) return loadIssue;
248
- const locations = new Map<string, string>();
249
- for (const [version, items] of snapshots) for (const cell of items) {
250
- if (locations.has(cell.id)) return `여러 판의 같은 칸: ${cell.id}`;
251
- locations.set(cell.id, version);
252
- }
284
+ const { locations, conflict } = cellLocations(snapshots, released);
285
+ if (conflict) return `여러 판의 같은 칸: ${conflict}`;
253
286
  for (const predecessor of item.predecessors ?? []) {
254
287
  const location = locations.get(predecessor);
255
288
  if (!location || versionOrder(to.version, location) <= 0) return `선행 칸 ${predecessor} 이후 판으로만 이동 가능`;
256
289
  }
257
290
  for (const [version, items] of snapshots) for (const dependent of items) {
291
+ // A released version is history — its cells never pin a move (same rule as placement).
292
+ if (released && versionOrder(version, released) <= 0) continue;
258
293
  if (dependent.id !== item.id && dependent.predecessors?.includes(item.id) && versionOrder(version, to.version) <= 0)
259
294
  return `의존 칸 ${dependent.id} 이전 판으로만 이동 가능`;
260
295
  }
@@ -271,20 +306,29 @@ export function rebalance(version: string, deps: PlacementDeps = {}): RebalanceR
271
306
  const backlog = deps.backlog ?? BACKLOG_VERSIONS;
272
307
  const next = rows.find((row) => versionOrder(row.version, version) > 0 && !backlog.includes(row.version));
273
308
  const snapshots = new Map(rows.map((row) => [row.version, [...(deps.checklist ?? listChecklist)(row.version).items]]));
274
- const unstarted = snapshots.get(version)!.filter((item) => item.status === 'yellow' && !item.evidence);
309
+ const unstarted = snapshots.get(version)!.filter((item) => item.status === 'yellow' && !item.evidence)
310
+ .map((item, index) => {
311
+ const rubric = parseRubric(`${item.title}\n${item.evidence ?? ''}`);
312
+ return { item, index, score: rubric ? rubricScore(rubric) : null };
313
+ })
314
+ .sort((a, b) => {
315
+ if (a.score === null) return b.score === null ? a.index - b.index : 1;
316
+ if (b.score === null) return -1;
317
+ return a.score - b.score || a.index - b.index;
318
+ });
275
319
  if (unstarted.length && !next) throw new CliUserError(`${version} 다음 판이 없다`);
276
320
  if (!unstarted.length) return { decisions: [], blocked: [] };
277
321
  const rate = deps.merged24h ?? mergedPrsLast24h(new Date(now));
278
322
  const caps = deps.seatCap ?? placementSeatCap();
279
323
  const ceoCap = validCeoCap(deps.ceoDailyCap ?? placementCeoDailyCap());
280
324
  const result: RebalanceResult = { decisions: [], blocked: [] };
281
- for (const item of unstarted) {
282
- const violation = moveConstraint(item, next!, backlog, snapshots, now, rate, caps, rows, ceoCap);
325
+ for (const { item, score } of unstarted) {
326
+ const violation = moveConstraint(item, next!, backlog, snapshots, now, rate, caps, rows, ceoCap, deps.released ?? releasedVersion());
283
327
  if (violation) {
284
328
  result.blocked.push({ id: item.id, from: version, to: next!.version, reason: violation });
285
329
  continue;
286
330
  }
287
- const reason = `${version} 착지 마감 2시간 전 미시작 칸 이월`;
331
+ const reason = `${version} 착지 마감 2시간 전 미시작 칸 이월${score === null ? '' : ` · 루브릭 ${score}`}`;
288
332
  result.decisions.push({ id: item.id, from: version, version: next!.version, reason, displaced: [] });
289
333
  snapshots.set(version, snapshots.get(version)!.filter((cell) => cell.id !== item.id));
290
334
  snapshots.get(next!.version)!.push(item);
@@ -305,7 +349,7 @@ export function seatMove(id: string, from: string, to: string, by: string, reaso
305
349
  if (!ownerMatches(item.owner, parseOwner(by).seat) || next?.version !== to) throw new CliUserError('남의 칸 당기기 거부 — COO 에 요청');
306
350
  const now = (deps.now ?? new Date()).getTime();
307
351
  const rate = deps.merged24h ?? mergedPrsLast24h(new Date(now));
308
- const violation = moveConstraint(item, next, backlog, snapshots, now, rate, deps.seatCap ?? placementSeatCap(), rows, validCeoCap(deps.ceoDailyCap ?? placementCeoDailyCap()));
352
+ const violation = moveConstraint(item, next, backlog, snapshots, now, rate, deps.seatCap ?? placementSeatCap(), rows, validCeoCap(deps.ceoDailyCap ?? placementCeoDailyCap()), deps.released ?? releasedVersion());
309
353
  if (violation) throw new CliUserError(`${id}: ${violation}`);
310
354
  if (!deps.dryRun) move(id, from, to, by, undefined, undefined, reason);
311
355
  }