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
@@ -18,13 +18,35 @@ let seatsCache: ReturnType<typeof createSeatsCache> | null = null;
18
18
  export function setSeatsCacheForTest(cache: ReturnType<typeof createSeatsCache> | null): void { seatsCache = cache; }
19
19
  const LOG_PATH = /^\/v1\/ops\/release\/runs\/([^/]+)\/nodes\/([^/]+)\/log$/;
20
20
 
21
- interface RunNode { nodeId: string; ok?: unknown; output?: unknown }
21
+ interface RunNode { nodeId: string; ok?: unknown; output?: unknown; startedAt?: unknown; endedAt?: unknown }
22
22
  interface RunRecord {
23
23
  status: string;
24
24
  path: string[];
25
25
  startedAt: string;
26
26
  input?: unknown;
27
27
  nodes: RunNode[];
28
+ currentNode?: { nodeId?: unknown; startedAt?: unknown };
29
+ }
30
+
31
+ /** Node times come only from the ledger (GRAPH-NODE-TIMES); older ledgers have none and the screen says so. */
32
+ const ISO_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
33
+ function isoTime(value: unknown): string | undefined {
34
+ return typeof value === 'string' && ISO_TIME.test(value) && Number.isFinite(Date.parse(value)) ? value : undefined;
35
+ }
36
+
37
+ function nodeTimes(node: RunNode): { startedAt?: string; endedAt?: string } {
38
+ const startedAt = isoTime(node.startedAt);
39
+ const endedAt = isoTime(node.endedAt);
40
+ return { ...(startedAt ? { startedAt } : {}), ...(endedAt ? { endedAt } : {}) };
41
+ }
42
+
43
+ /** The running node has no record yet — show it with its start time when the ledger names it as the path tail. */
44
+ function runningNode(run: RunRecord, recorded: number): Array<{ nodeId: string; ok: null; summary: string; startedAt: string }> {
45
+ const current = run.currentNode;
46
+ const startedAt = isoTime(current?.startedAt);
47
+ if (run.status !== 'running' || !startedAt || typeof current?.nodeId !== 'string' ||
48
+ run.path.length !== recorded + 1 || run.path.at(-1) !== current.nodeId) return [];
49
+ return [{ nodeId: redactSecretText(current.nodeId), ok: null, summary: '', startedAt }];
28
50
  }
29
51
 
30
52
  function recordAt(path: string): RunRecord | null {
@@ -66,6 +88,77 @@ function runVersion(input: unknown): string | null {
66
88
  return typeof version === 'string' && VERSION.test(version) ? version : null;
67
89
  }
68
90
 
91
+ /** Facts away mode prints for a node — only built when asked, so the HTTP shape stays as it was. */
92
+ export interface ReleaseNodeFacts { verdict?: string; introduced?: number; preexisting?: number; waiver?: string }
93
+ export interface ReleaseRunNodeView {
94
+ nodeId: string; ok: boolean | null; summary: string; startedAt?: string; endedAt?: string; facts?: ReleaseNodeFacts;
95
+ }
96
+
97
+ function parsedOutput(output: unknown): Record<string, unknown> | null {
98
+ let value = output;
99
+ if (typeof value === 'string') {
100
+ const text = value;
101
+ try { value = JSON.parse(text) as unknown; }
102
+ catch {
103
+ const last = text.split('\n').map((line) => line.trim()).filter(Boolean).at(-1);
104
+ try { value = JSON.parse(last ?? '') as unknown; } catch { return null; }
105
+ }
106
+ }
107
+ return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
108
+ }
109
+
110
+ export function releaseNodeFacts(output: unknown): ReleaseNodeFacts {
111
+ const value = parsedOutput(output);
112
+ if (!value) return {};
113
+ const verdict = typeof value.verdict === 'string' ? value.verdict : typeof value.outcome === 'string' ? value.outcome : undefined;
114
+ const introduced = Array.isArray(value.introduced) ? value.introduced.length : typeof value.introduced === 'number' ? value.introduced : undefined;
115
+ const preexisting = typeof value.preexisting === 'number' ? value.preexisting : undefined;
116
+ const waiver = typeof value.waiver === 'string' && value.waiver.trim() ? redactSecretText(value.waiver).slice(0, 200) : undefined;
117
+ return {
118
+ ...(verdict ? { verdict: redactSecretText(verdict).slice(0, 40) } : {}),
119
+ ...(introduced !== undefined ? { introduced } : {}),
120
+ ...(preexisting !== undefined ? { preexisting } : {}),
121
+ ...(waiver ? { waiver } : {}),
122
+ };
123
+ }
124
+ export interface ReleaseRunView {
125
+ runId: string; status: string; startedAt: string; version: string | null; path: string[]; nodes: ReleaseRunNodeView[];
126
+ }
127
+
128
+ /** The release run ledger as `/v1/ops/release/runs` serves it (newest 20) — shared with Telegram /release and away mode
129
+ * so every surface reads the same verdicts. `'unavailable'` = the directory exists but cannot be read. */
130
+ export function readReleaseRuns(version: string | null = null,
131
+ dir: string = join(effectiveInstanceRoot(), 'graph-runs', 'release-loop'),
132
+ opts: { facts?: boolean } = {}): ReleaseRunView[] | 'unavailable' {
133
+ let filenames: string[];
134
+ try {
135
+ filenames = readdirSync(dir, { withFileTypes: true })
136
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.json')
137
+ && IDENTIFIER.test(entry.name.slice(0, -5)))
138
+ .map((entry) => entry.name);
139
+ }
140
+ catch (error) {
141
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];
142
+ return 'unavailable';
143
+ }
144
+ return filenames.flatMap((filename) => {
145
+ const run = recordAt(join(dir, filename));
146
+ if (!run) return [];
147
+ const v = runVersion(run.input);
148
+ if (version !== null && v !== version) return [];
149
+ return [{
150
+ runId: redactSecretText(filename.slice(0, -5)), status: redactSecretText(run.status),
151
+ startedAt: redactSecretText(run.startedAt), version: v,
152
+ path: run.path.filter((step): step is string => typeof step === 'string').map(redactSecretText),
153
+ nodes: [...run.nodes.filter((node) => node && typeof node.nodeId === 'string').map((node) => ({
154
+ nodeId: redactSecretText(node.nodeId), ok: typeof node.ok === 'boolean' ? node.ok : null,
155
+ summary: nodeSummary(node.output), ...nodeTimes(node),
156
+ ...(opts.facts ? { facts: releaseNodeFacts(node.output) } : {}),
157
+ })), ...runningNode(run, run.nodes.length)],
158
+ }];
159
+ }).sort((a, b) => b.startedAt.localeCompare(a.startedAt)).slice(0, 20);
160
+ }
161
+
69
162
  /** The sole data gate for every read-only /v1/ops endpoint. */
70
163
  export function handleOpsApi(req: Request, metaApi: MetaApiOpts | undefined): Response | Promise<Response> {
71
164
  const url = new URL(req.url);
@@ -103,33 +196,8 @@ export function handleOpsApi(req: Request, metaApi: MetaApiOpts | undefined): Re
103
196
  }
104
197
  if (pathname === '/v1/ops/release/runs') {
105
198
  if (version !== null && !VERSION.test(version)) return jsonResponse({ error: 'invalid-version' }, 400);
106
- const dir = join(effectiveInstanceRoot(), 'graph-runs', 'release-loop');
107
- let filenames: string[];
108
- try {
109
- filenames = readdirSync(dir, { withFileTypes: true })
110
- .filter((entry) => entry.isFile() && entry.name.endsWith('.json')
111
- && IDENTIFIER.test(entry.name.slice(0, -5)))
112
- .map((entry) => entry.name);
113
- }
114
- catch (error) {
115
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') return served(jsonResponse([]));
116
- return jsonResponse({ error: 'runs-unavailable' }, 500);
117
- }
118
- const runs = filenames.flatMap((filename) => {
119
- const run = recordAt(join(dir, filename));
120
- if (!run) return [];
121
- const v = runVersion(run.input);
122
- if (version !== null && v !== version) return [];
123
- return [{
124
- runId: redactSecretText(filename.slice(0, -5)), status: redactSecretText(run.status),
125
- startedAt: redactSecretText(run.startedAt), version: v,
126
- path: run.path.filter((step): step is string => typeof step === 'string').map(redactSecretText),
127
- nodes: run.nodes.filter((node) => node && typeof node.nodeId === 'string').map((node) => ({
128
- nodeId: redactSecretText(node.nodeId), ok: typeof node.ok === 'boolean' ? node.ok : null,
129
- summary: nodeSummary(node.output),
130
- })),
131
- }];
132
- }).sort((a, b) => b.startedAt.localeCompare(a.startedAt)).slice(0, 20);
199
+ const runs = readReleaseRuns(version);
200
+ if (runs === 'unavailable') return jsonResponse({ error: 'runs-unavailable' }, 500);
133
201
  return served(jsonResponse(runs));
134
202
  }
135
203
  // WHATWG URL normalizes a literal runs/../nodes/... before routing.
@@ -2,7 +2,7 @@
2
2
  // removed — same rule as decision replies), the release checklist, and the decision ledger. Each failure becomes null upstream.
3
3
  import { DecisionLedger } from '../../decisions/decision-ledger.js';
4
4
  import { listCoordEvents, type CoordEvent } from '../../context-bus/coord-events.js';
5
- import { devVersion, listChecklist, type ChecklistItem } from '../../release-loop/checklist.js';
5
+ import { checklistDevVersion, listChecklist, type ChecklistItem } from '../../release-loop/checklist.js';
6
6
  import { getUserConfig } from '../../user-config.js';
7
7
  import { SEATS, kstDayRange, type ChannelComment, type ChannelSource, type MergedPr, type SeatsSources } from './ops-seats.js';
8
8
 
@@ -82,7 +82,7 @@ export function liveSeatsSources(deps: { listEvents?: typeof listCoordEvents; gi
82
82
  return lines?.map((line) => JSON.parse(line) as MergedPr) ?? null;
83
83
  },
84
84
  checklist() {
85
- const dev = devVersion();
85
+ const dev = checklistDevVersion();
86
86
  const next = listChecklist(nextPatch(dev)).items;
87
87
  const current = listChecklist(dev).items;
88
88
  const all: ChecklistItem[] = [...current, ...next];
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { readdirSync, readFileSync } from 'fs';
6
6
  import { join } from 'path';
7
+ import { debug } from '../../debug/log.js';
7
8
  import { jsonResponse } from './http-server.js';
8
9
  import { checkAuth, type MetaApiOpts } from './meta-api.js';
9
10
 
@@ -47,6 +48,8 @@ export function listTemplateCatalog(dir = findTemplateDir()): WorkflowTemplateEn
47
48
  try {
48
49
  files = readdirSync(dir).filter((f) => f.endsWith('.yaml'));
49
50
  } catch {
51
+ // An installed package without this folder used to answer «No templates available» silently (10-06 · files list).
52
+ debug.log('nexus.workflow-templates', 'template-dir-missing', { dir });
50
53
  return [];
51
54
  }
52
55
  const out: WorkflowTemplateEntry[] = [];
@@ -136,6 +136,7 @@ import {
136
136
  type DailyReflectionSchedulerHandle,
137
137
  } from '../notes/daily-reflection-scheduler.js';
138
138
  import { type ScheduleRunnerHandle } from '../domains/schedule-runner.js';
139
+ import { startWatchDefaultScheduler } from '../domains/watch-default-scheduler.js';
139
140
  import { resolvePwaStaticDir } from './static-dir-resolve.js';
140
141
  import {
141
142
  createHitlPendingCallbacks,
@@ -1310,6 +1311,7 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
1310
1311
  // the daily-reflection scheduler regardless of which branch booted
1311
1312
  // it. Tests with detachForTesting set leave this undefined.
1312
1313
  let dailyReflectionScheduler: DailyReflectionSchedulerHandle | undefined;
1314
+ let watchDefaultScheduler: ReturnType<typeof startWatchDefaultScheduler> | undefined;
1313
1315
  // RFC #2161 FU A8 (2026-05-11) — discovery cron via NEXUS. Dormant
1314
1316
  // by default (opt-in via `ELANOUS_DISCOVERY_CRON_INTERVAL_MS`); when
1315
1317
  // configured, fires runDiscovery every interval and pushes the
@@ -3198,6 +3200,8 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
3198
3200
  // schedule migrate 로 이관.) 롤백: 아래 한 줄 복원.
3199
3201
  // try { scheduleRunner = startScheduleRunner(); } catch { /* fail-soft */ }
3200
3202
  void scheduleRunner; // 은퇴(undefined 유지)
3203
+ try { watchDefaultScheduler = startWatchDefaultScheduler(); }
3204
+ catch (error) { debug.log('watch.default', 'failed', { stage: 'boot', reason: error instanceof Error ? error.message : String(error) }); }
3201
3205
  }
3202
3206
  }
3203
3207
 
@@ -3335,6 +3339,9 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
3335
3339
  }
3336
3340
  // S2 (2026-07-07) · stop the schedule runner so its node-cron tasks +
3337
3341
  // reconcile interval don't keep firing/holding the loop past shutdown.
3342
+ if (watchDefaultScheduler) {
3343
+ try { watchDefaultScheduler.stop(); } catch { /* swallow */ }
3344
+ }
3338
3345
  if (scheduleRunner) {
3339
3346
  try { scheduleRunner.stop(); } catch { /* swallow */ }
3340
3347
  }
@@ -554,6 +554,44 @@ export function codexAccessHoursLeft(accessToken: unknown, now: number = Date.no
554
554
  return typeof exp === 'number' ? Math.round(((exp * 1000 - now) / 3_600_000) * 10) / 10 : null;
555
555
  }
556
556
 
557
+ /** PREREFRESH-LOCK (10-06 · post-review of #24449) — the per-account host refresh lock path.
558
+ * 🩸 Two Pod launches on one host picked the same expiring account at once: both refreshed, the server
559
+ * rotated the refresh token under the second, its refresh failed and that run dropped the account.
560
+ * The lock file sits NEXT TO the account's credential state (`<codexHome>/.elanous-prerefresh.lock`) —
561
+ * ⛔ never inside a token file, and it carries only the holder's pid/nonce. null = no named home to lock. */
562
+ export function codexAccountRefreshLockPath(name: string, storePath: string = authStorePath()): string | null {
563
+ if (name === DEFAULT_CODEX_ACCOUNT || !isValidAccountName(name)) return null;
564
+ const codexHome = loadTokens(codexStoreKey(name), storePath)?.codexHome;
565
+ return codexHome ? join(codexHome, '.elanous-prerefresh.lock') : null;
566
+ }
567
+
568
+ /** A refresh (network + rotate + atomic write + import) can take tens of seconds — wait up to ~90 s,
569
+ * and treat a lock as stale only after 2 min (the async holder heartbeats its mtime meanwhile). */
570
+ export const CODEX_REFRESH_LOCK_OPTS = { staleMs: 120_000, retryBusyMs: 100, maxTries: 900 } as const;
571
+
572
+ /** The refresh lock could not be acquired (timeout · ENOENT · EACCES …) — `fn` never ran, so nothing was refreshed. */
573
+ export class CodexRefreshLockUnavailableError extends Error {
574
+ constructor(readonly reason: 'lock-timeout' | 'lock-error', cause: unknown) {
575
+ super(`codex refresh lock unavailable (${reason}): ${cause instanceof Error ? cause.message : String(cause)}`);
576
+ this.name = 'CodexRefreshLockUnavailableError';
577
+ }
578
+ }
579
+
580
+ /** Runs `fn` while holding the account's refresh lock (cross-process · same-process). null path = no lock.
581
+ * Any failure to acquire the lock throws `CodexRefreshLockUnavailableError` (fn not run); fn's own errors pass through. */
582
+ export async function withCodexAccountRefreshLock<T>(lockPath: string | null, fn: () => Promise<T>, opts: { staleMs?: number; retryBusyMs?: number; maxTries?: number } = CODEX_REFRESH_LOCK_OPTS): Promise<T> {
583
+ if (!lockPath) return fn();
584
+ const { acquireLockAsync } = await import('../storage/file-lock.js');
585
+ let handle: Awaited<ReturnType<typeof acquireLockAsync>>;
586
+ try { handle = await acquireLockAsync(lockPath, opts); }
587
+ catch (error) {
588
+ const timedOut = error instanceof Error && error.message.startsWith('acquireLockAsync: timed out');
589
+ throw new CodexRefreshLockUnavailableError(timedOut ? 'lock-timeout' : 'lock-error', error);
590
+ }
591
+ try { return await fn(); }
592
+ finally { handle.release(); }
593
+ }
594
+
557
595
  export async function refreshCodexAccountHome(
558
596
  name: string,
559
597
  opts: {
@@ -0,0 +1,29 @@
1
+ import { debug } from '../debug/log.js';
2
+ import { needsFirstRun, needsOnboarding } from '../onboarding.js';
3
+ import type { UserConfig } from '../user-config.js';
4
+ import type { FirstRunDeps, runFirstRun } from './first-run.js';
5
+ import { UNATTENDED_SETUP_COMMAND } from './entry-hints.js';
6
+
7
+ export interface FirstChatGateDeps {
8
+ isTTY: boolean;
9
+ runFirstRun: (deps: FirstRunDeps) => ReturnType<typeof runFirstRun>;
10
+ runInteractive: () => Promise<boolean>;
11
+ print: (line: string) => void;
12
+ }
13
+
14
+ export async function firstChatGate(cfg: UserConfig, deps: FirstChatGateDeps): Promise<'continue' | 'stop'> {
15
+ const decide = (decision: 'continue' | 'stop', outcome: string): 'continue' | 'stop' => {
16
+ debug.log('onboarding.first-chat', decision, { isTTY: deps.isTTY, outcome });
17
+ return decision;
18
+ };
19
+ if (!needsOnboarding(cfg)) return decide('continue', 'completed');
20
+ if (deps.isTTY) {
21
+ return decide(await deps.runInteractive() ? 'continue' : 'stop', 'interactive');
22
+ }
23
+ if (!needsFirstRun(cfg)) return decide('continue', 'already-ready');
24
+ const result = await deps.runFirstRun({ config: cfg, isTTY: false, print: () => {} });
25
+ if (result.outcome === 'ready') return decide('continue', result.outcome);
26
+ deps.print(`다음: elanous setup llm (또는 ${UNATTENDED_SETUP_COMMAND})`);
27
+ process.exitCode = 2;
28
+ return decide('stop', result.outcome);
29
+ }
@@ -1,5 +1,6 @@
1
- import { readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
1
+ import { spawnSync } from 'node:child_process';
2
+ import { readFileSync, statSync } from 'node:fs';
3
+ import { isAbsolute, join } from 'node:path';
3
4
  import * as store from './feature-store.js';
4
5
  import { debug } from '../debug/log.js';
5
6
  import { CliUserError } from '../cli/cli-user-error.js';
@@ -20,6 +21,8 @@ export interface ChecklistItem {
20
21
  ceoDate?: string;
21
22
  /** 다른 칸의 처리량을 올리는 칸(자율성·효율성·동시성). 없으면 가속 등급이 아니다. */
22
23
  accelerator?: true;
24
+ /** 이 칸이 근거로 삼는 문서(저장소 상대 경로, 선택적으로 `#절`). 없으면 칸에 실리지 않는다. */
25
+ refs?: string[];
23
26
  evidence?: string;
24
27
  disposition?: ChecklistDisposition;
25
28
  kind?: ChecklistKind;
@@ -37,6 +40,7 @@ export interface ChecklistHistory {
37
40
  dev: string;
38
41
  force?: true;
39
42
  reason?: string;
43
+ generation?: number;
40
44
  }
41
45
  export interface Checklist {
42
46
  version: string;
@@ -50,6 +54,12 @@ export function devVersion(): string {
50
54
  return (JSON.parse(readFileSync(join(import.meta.dir, '..', '..', 'package.json'), 'utf8')) as { version: string }).version;
51
55
  }
52
56
 
57
+ /** The checklist version of the development tree: package.json says `0.2.18-dev.0` once a release branch is cut,
58
+ * but the checklist only knows `0.2.18` (10-07: every server caller that passed devVersion() raw got null/throw). */
59
+ export function checklistDevVersion(version = devVersion()): string {
60
+ return version.replace(/-dev\.\d+$/, '');
61
+ }
62
+
53
63
  export function parseOwner(value: string): { seat: string; sub?: string } {
54
64
  const match = /^([A-Z]{2,8})(?:\/([a-z][a-z0-9-]{0,31}))?$/.exec(value);
55
65
  if (!match || match[0] !== value) throw new CliUserError(`잘못된 담당: ${value}`, 'TC 또는 TC/rel');
@@ -92,6 +102,7 @@ export function statusChangesSince(history: readonly ChecklistHistory[], sinceIs
92
102
  }
93
103
 
94
104
  function mutate(v: string, apply: (data: Checklist, otherItems: (id: string) => store.ChecklistCollision[]) => boolean): Checklist {
105
+ // addItem/setItem/claimItem/removeItem/seedFromRoadmap reach the generation-fenced ledger write here.
95
106
  return decodeClaimHistory(store.mutate(v, store.releasedVersion(), devVersion(), apply));
96
107
  }
97
108
 
@@ -100,12 +111,82 @@ function change(data: Checklist, id: string, field: string, from: unknown, to: u
100
111
  debug.log('release-loop.checklist', 'change', { version: data.version, id, field, from: from ?? null, to: to ?? null, by });
101
112
  }
102
113
 
114
+ const REPO_ROOT = join(import.meta.dir, '..', '..');
115
+
116
+ /** `--ref` 문서를 찾는 뿌리들 — 이 코드의 트리 ⊕ 지금 작업 디렉토리의 git 뿌리.
117
+ * ⭐ 설치본(`~/.local/share/elanous/current`)에는 `docs/` 가 없다 — 저장소 안에서 부른 설치본도 저장소 문서를 인용할 수 있게. */
118
+ export function refRoots(cwd = process.cwd()): string[] {
119
+ const roots = [REPO_ROOT];
120
+ try {
121
+ const top = spawnSync('git', ['rev-parse', '--show-toplevel'], { cwd, encoding: 'utf8', timeout: 5_000 });
122
+ const path = top.status === 0 ? top.stdout.trim() : '';
123
+ if (path && !roots.includes(path)) roots.push(path);
124
+ } catch { /* git 없음 — 코드 트리만 */ }
125
+ return roots;
126
+ }
127
+
128
+ /** `--ref` 값 검사: 저장소 상대 경로(⊕ `#절`)이고 그 파일이 뿌리들 중 하나에 있어야 한다. 중복은 순서를 지키며 지운다. */
129
+ export function normalizeRefs(refs: readonly string[], root: string | readonly string[] = refRoots()): string[] {
130
+ const roots = typeof root === 'string' ? [root] : root;
131
+ const out: string[] = [];
132
+ for (const raw of refs) {
133
+ const ref = raw.trim();
134
+ const path = ref.split('#')[0] ?? '';
135
+ if (!path || isAbsolute(path) || path.split(/[\\/]/).some((part) => part === '..')) throw new CliUserError(`잘못된 문서 참조: ${raw}`, '저장소 상대 경로[#절] — 예: docs/manual/MANUAL-x.md#§1');
136
+ let isFile = false;
137
+ for (const base of roots) {
138
+ try { if (statSync(join(base, path)).isFile()) { isFile = true; break; } } catch { /* 없음 */ }
139
+ }
140
+ if (!isFile) throw new CliUserError(`없는 문서: ${path}`, '저장소 상대 «파일» 경로로 준다(폴더는 안 된다)');
141
+ if (!out.includes(ref)) out.push(ref);
142
+ }
143
+ return out;
144
+ }
145
+
146
+ /** 한 문서를 인용하는 칸 한 줄 — `release checklist refs <doc>` 의 «구현 현황» 행. */
147
+ export interface DocRefRow { version: string; id: string; title: string; status: ChecklistStatus; owner: string | null; section: string | null }
148
+
149
+ function splitRef(ref: string): { path: string; section: string | null } {
150
+ const at = ref.indexOf('#');
151
+ const path = (at < 0 ? ref : ref.slice(0, at)).trim().replace(/^\.\//, '');
152
+ return { path, section: at < 0 ? null : ref.slice(at + 1) };
153
+ }
154
+
155
+ /** 읽기 전용: 칸의 refs 중 경로(`#` 앞)가 doc 의 경로와 같은 것을 행으로 낸다. doc 에 `#절`이 붙으면 그 절과 정확히 같은 ref 만. */
156
+ export function cellsReferencingDoc(doc: string, checklists: readonly Checklist[]): DocRefRow[] {
157
+ const want = splitRef(doc.trim());
158
+ const rows: DocRefRow[] = [];
159
+ for (const data of checklists) {
160
+ for (const item of data.items) {
161
+ // 칸마다 한 행 — 같은 문서의 절을 여럿 인용해도 절을 모아 한 줄로(절 없는 인용이 섞이면 문서 전체로 본다).
162
+ const sections: string[] = [];
163
+ let whole = false;
164
+ for (const ref of item.refs ?? []) {
165
+ const got = splitRef(ref);
166
+ if (got.path !== want.path) continue;
167
+ if (want.section !== null && got.section !== want.section) continue;
168
+ if (got.section === null) whole = true;
169
+ else if (!sections.includes(got.section)) sections.push(got.section);
170
+ }
171
+ if (!whole && sections.length === 0) continue;
172
+ rows.push({ version: data.version, id: item.id, title: item.title, status: item.status, owner: item.owner ?? null, section: whole ? null : sections.join(' · ') });
173
+ }
174
+ }
175
+ return rows;
176
+ }
177
+
178
+ /** `| 판 | 칸 | 상태 | 절 | 제목 |` 마크다운 표 — 행이 없으면 머리줄 둘만. */
179
+ export function renderRefsStatus(rows: readonly DocRefRow[]): string {
180
+ const cell = (value: string) => value.replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
181
+ return ['| 판 | 칸 | 상태 | 절 | 제목 |', '| --- | --- | --- | --- | --- |', ...rows.map((row) => `| ${cell(row.version)} | ${cell(row.id)} | ${row.status} | ${cell(row.section ?? '-')} | ${cell(row.title)} |`)].join('\n');
182
+ }
183
+
103
184
  export function validateCeoLoad(input: { ceoMinutes?: number; ceoDate?: string }): void {
104
185
  if (input.ceoMinutes !== undefined && (!Number.isSafeInteger(input.ceoMinutes) || input.ceoMinutes < 0)) throw new CliUserError('대표 손 분량은 0 이상의 정수 분이어야 한다');
105
186
  if (input.ceoDate !== undefined && (!/^\d{4}-\d{2}-\d{2}$/.test(input.ceoDate) || !Number.isFinite(Date.parse(`${input.ceoDate}T00:00:00Z`)) || new Date(`${input.ceoDate}T00:00:00Z`).toISOString().slice(0, 10) !== input.ceoDate)) throw new CliUserError('대표 손 날짜는 YYYY-MM-DD 이어야 한다');
106
187
  }
107
188
 
108
- export function addItem(v: string, input: { id: string; title: string; owner?: string; kind?: ChecklistKind; priority?: ChecklistItem['priority']; predecessors?: string[]; deadlineVersion?: string; ceoMinutes?: number; ceoDate?: string; accelerator?: boolean }, options: { allowDuplicateId?: boolean } = {}): Checklist {
189
+ export function addItem(v: string, input: { id: string; title: string; refs?: string[]; owner?: string; kind?: ChecklistKind; priority?: ChecklistItem['priority']; predecessors?: string[]; deadlineVersion?: string; ceoMinutes?: number; ceoDate?: string; accelerator?: boolean }, options: { allowDuplicateId?: boolean } = {}): Checklist {
109
190
  if (!input.id.trim()) throw new CliUserError('칸 id 가 비었다');
110
191
  if (!input.title.trim()) throw new CliUserError('칸 제목이 비었다');
111
192
  if (input.kind !== undefined && input.kind !== 'screen') throw new CliUserError(`잘못된 종류: ${input.kind}`, 'screen');
@@ -115,6 +196,7 @@ export function addItem(v: string, input: { id: string; title: string; owner?: s
115
196
  if (input.deadlineVersion !== undefined) store.validateVersion(input.deadlineVersion);
116
197
  if (input.accelerator !== undefined && input.accelerator !== true) throw new CliUserError('가속 등급은 true 이거나 생략한다');
117
198
  validateCeoLoad(input);
199
+ const refs = input.refs !== undefined && input.refs.length ? normalizeRefs(input.refs) : undefined;
118
200
  return mutate(v, (data, otherItems) => {
119
201
  if (data.items.some((item) => item.id === input.id)) throw new CliUserError(`이미 있는 칸: ${input.id}`, 'set <id> 로 고친다');
120
202
  const collisions = otherItems(input.id);
@@ -125,14 +207,14 @@ export function addItem(v: string, input: { id: string; title: string; owner?: s
125
207
  }
126
208
  const by = process.env.ELANOUS_TRACK || 'cli';
127
209
  const at = new Date().toISOString();
128
- const item: ChecklistItem = { id: input.id, title: input.title, status: 'yellow', ...(input.owner !== undefined ? { owner: input.owner } : {}), ...(input.kind !== undefined ? { kind: input.kind } : {}), ...(input.priority !== undefined ? { priority: input.priority } : {}), ...(input.predecessors !== undefined ? { predecessors: input.predecessors } : {}), ...(input.deadlineVersion !== undefined ? { deadlineVersion: input.deadlineVersion } : {}), ...(input.ceoMinutes !== undefined ? { ceoMinutes: input.ceoMinutes } : {}), ...(input.ceoDate !== undefined ? { ceoDate: input.ceoDate } : {}), ...(input.accelerator === true ? { accelerator: true as const } : {}), updatedAt: at, updatedBy: by };
210
+ const item: ChecklistItem = { id: input.id, title: input.title, status: 'yellow', ...(input.owner !== undefined ? { owner: input.owner } : {}), ...(input.kind !== undefined ? { kind: input.kind } : {}), ...(input.priority !== undefined ? { priority: input.priority } : {}), ...(input.predecessors !== undefined ? { predecessors: input.predecessors } : {}), ...(input.deadlineVersion !== undefined ? { deadlineVersion: input.deadlineVersion } : {}), ...(input.ceoMinutes !== undefined ? { ceoMinutes: input.ceoMinutes } : {}), ...(input.ceoDate !== undefined ? { ceoDate: input.ceoDate } : {}), ...(input.accelerator === true ? { accelerator: true as const } : {}), ...(refs ? { refs } : {}), updatedAt: at, updatedBy: by };
129
211
  data.items.push(item);
130
212
  change(data, item.id, 'add', null, item, by, at);
131
213
  return true;
132
214
  });
133
215
  }
134
216
 
135
- export function setItem(v: string, id: string, patch: { status?: ChecklistStatus; evidence?: string; owner?: string; disposition?: ChecklistDisposition; kind?: ChecklistKind; priority?: ChecklistItem['priority']; predecessors?: string[]; deadlineVersion?: string; ceoMinutes?: number; ceoDate?: string; accelerator?: boolean | null }, by: string): Checklist {
217
+ export function setItem(v: string, id: string, patch: { status?: ChecklistStatus; evidence?: string; refs?: string[]; owner?: string; disposition?: ChecklistDisposition; kind?: ChecklistKind; priority?: ChecklistItem['priority']; predecessors?: string[]; deadlineVersion?: string; ceoMinutes?: number; ceoDate?: string; accelerator?: boolean | null }, by: string): Checklist {
136
218
  return mutate(v, (data) => {
137
219
  const item = data.items.find((i) => i.id === id);
138
220
  if (!item) throw new CliUserError(`없는 칸: ${id}`, 'list 로 칸 목록을 본다');
@@ -144,13 +226,15 @@ export function setItem(v: string, id: string, patch: { status?: ChecklistStatus
144
226
  if (patch.deadlineVersion !== undefined) store.validateVersion(patch.deadlineVersion);
145
227
  if (patch.accelerator !== undefined && patch.accelerator !== true && patch.accelerator !== null) throw new CliUserError('가속 등급은 true(부여) 또는 null(해제)이다');
146
228
  validateCeoLoad(patch);
229
+ // --ref 는 «더하기»다 — 기존 참조 뒤에 붙이고 중복을 지운다.
230
+ if (patch.refs !== undefined) patch = { ...patch, refs: normalizeRefs([...(item.refs ?? []), ...patch.refs]) };
147
231
  if (patch.owner !== undefined) {
148
232
  parseOwner(patch.owner);
149
233
  if (item.owner && patch.owner !== item.owner) {
150
234
  throw new CliUserError(`지금 주인: ${item.owner} — --force 로만 바꾼다`, 'release checklist claim <id> --by <자리> --force');
151
235
  }
152
236
  }
153
- const fields = (['evidence', 'owner', 'status', 'disposition', 'kind', 'priority', 'predecessors', 'deadlineVersion', 'ceoMinutes', 'ceoDate', 'accelerator'] as const).filter((field) => patch[field] !== undefined && JSON.stringify(patch[field]) !== JSON.stringify(item[field] ?? null));
237
+ const fields = (['evidence', 'owner', 'status', 'disposition', 'kind', 'priority', 'predecessors', 'deadlineVersion', 'ceoMinutes', 'ceoDate', 'accelerator', 'refs'] as const).filter((field) => patch[field] !== undefined && JSON.stringify(patch[field]) !== JSON.stringify(item[field] ?? null));
154
238
  if (fields.length === 0) return false;
155
239
  const at = new Date().toISOString();
156
240
  for (const field of fields) {
@@ -269,6 +353,22 @@ export function checklistGate(v: string): ChecklistGate {
269
353
  return result;
270
354
  }
271
355
 
356
+ /** GATE-ENTRY-ALIGN: the cut-time judgement shared by `release run`'s entry check and the checklist-gate node —
357
+ * past the landing deadline a non-P0 yellow without a disposition is carried (moved), and a P0 yellow always blocks. */
358
+ export function cutChecklistGate(v: string, landBy: string | null | undefined, now: Date = new Date()): ChecklistGate & { autoMoved: string[] } {
359
+ const items = listChecklist(v).items;
360
+ const overdue = landBy !== null && landBy !== undefined && now.getTime() > Date.parse(landBy);
361
+ const autoMoved = overdue ? items.filter((item) => item.status === 'yellow' && item.priority !== 'P0' && item.disposition === undefined).map((item) => item.id) : [];
362
+ const p0 = items.filter((item) => item.status === 'yellow' && item.priority === 'P0').map((item) => item.id);
363
+ const gate = checklistGate(v);
364
+ gate.undecided = gate.undecided.filter((id) => !autoMoved.includes(id) && !p0.includes(id));
365
+ gate.moved = gate.moved.filter((id) => !p0.includes(id)).concat(autoMoved);
366
+ gate.knownIssues = gate.knownIssues.filter((item) => !p0.includes(item.id));
367
+ gate.blocked.push(...p0.filter((id) => !gate.blocked.includes(id)));
368
+ gate.ok = gate.red.length === 0 && gate.undecided.length === 0 && gate.blocked.length === 0;
369
+ return { ...gate, autoMoved };
370
+ }
371
+
272
372
  export function seedFromRoadmap(v: string, markdown: string): Checklist {
273
373
  return mutate(v, (data) => {
274
374
  let added = false;