elanous 0.2.18 → 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 (404) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → J5H7BvqgSI8DUjtRRcA_S}/_buildManifest.js +1 -1
  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 -1
  132. package/apps/pwa/out/decisions/index.txt +22 -22
  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 +1 -1
  207. package/scripts/coord-channel-watch.sh +2 -2
  208. package/scripts/discord-seat-probe.ts +3 -1
  209. package/scripts/lib/pch-regress-map.ts +23 -0
  210. package/scripts/morning-report.ts +18 -4
  211. package/scripts/pch-regress.ts +88 -0
  212. package/scripts/release-loop/gate-diff.ts +18 -0
  213. package/scripts/release-loop/gate-node.ts +338 -46
  214. package/scripts/release-loop/gate-partial.ts +106 -0
  215. package/scripts/release-loop/gate-timing.ts +120 -0
  216. package/scripts/release-loop/resume-release.ts +64 -5
  217. package/scripts/release-loop/shard-plan.ts +65 -0
  218. package/scripts/release-loop/unattended-release.ts +2 -0
  219. package/scripts/video/scene-table.ts +159 -0
  220. package/src/acp/core-turn-bridge.ts +39 -2
  221. package/src/away/presence.ts +40 -0
  222. package/src/away/release-watch.ts +255 -0
  223. package/src/chat/index.ts +2 -1
  224. package/src/chat/short-question.ts +3 -40
  225. package/src/cli/away-cli.ts +68 -0
  226. package/src/cli/guardian-cli.ts +49 -0
  227. package/src/cli/intake-cli.ts +13 -7
  228. package/src/cli/log-event-names.ts +1 -0
  229. package/src/cli/pod-cli.ts +5 -1
  230. package/src/cli/pty-drive-cli.ts +14 -3
  231. package/src/cli/release-cli.ts +32 -7
  232. package/src/cli/setup-cli.ts +31 -1
  233. package/src/cli/tasks-cli.ts +104 -11
  234. package/src/context-bus/context-now-public.ts +1 -1
  235. package/src/context-bus/context-now-surfaces.ts +16 -6
  236. package/src/context-bus/context-now.ts +97 -3
  237. package/src/core-turn/short-question.ts +100 -0
  238. package/src/dashboard/dashboard-status-lines.ts +24 -4
  239. package/src/dashboard/index.ts +50 -5
  240. package/src/dashboard/input/slash-executor.ts +6 -2
  241. package/src/dashboard/slash-runtime/dashboard-handlers.ts +63 -36
  242. package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
  243. package/src/dashboard/slash-runtime/help-from-registry.ts +21 -8
  244. package/src/dashboard/slash-runtime/help-groups.ts +44 -0
  245. package/src/dashboard/slash-runtime/model-choices.ts +125 -0
  246. package/src/domains/guardian-tick.ts +138 -0
  247. package/src/domains/outbound-alert.ts +44 -13
  248. package/src/domains/schedule-registry.ts +23 -0
  249. package/src/domains/telegram-kind-route.ts +39 -0
  250. package/src/domains/watch-default-scheduler.ts +95 -0
  251. package/src/esc-abort-gate.ts +44 -0
  252. package/src/graph-runner/runner.ts +25 -5
  253. package/src/harness/harness-cli-command.ts +2 -0
  254. package/src/harness/harness-salvage-cli.ts +78 -0
  255. package/src/harness/harness-worktree-add.ts +68 -0
  256. package/src/harness/ledger-write-guard.ts +6 -16
  257. package/src/hq/hq.ts +6 -1
  258. package/src/index.ts +52 -3
  259. package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
  260. package/src/instance/test-write-guard.ts +200 -0
  261. package/src/intake-plane/digest.ts +39 -15
  262. package/src/intake-plane/harness-target.ts +4 -3
  263. package/src/intake-plane/intake-sources.ts +1 -0
  264. package/src/intake-plane/items.ts +1 -0
  265. package/src/loops/orchestrator/degradation.ts +57 -0
  266. package/src/loops/orchestrator/finish-rate.ts +33 -3
  267. package/src/loops/orchestrator/tick.ts +166 -45
  268. package/src/maturity/feature-maturity.ts +6 -4
  269. package/src/maturity/tui-slash-maturity.ts +1 -0
  270. package/src/mss/logging/log-store.ts +3 -0
  271. package/src/nexus/api/http-server.ts +16 -5
  272. package/src/nexus/api/loop-edges.ts +290 -17
  273. package/src/nexus/api/ops-api.ts +96 -28
  274. package/src/nexus/index.ts +7 -0
  275. package/src/release-loop/checklist.ts +2 -0
  276. package/src/release-loop/feature-store.ts +54 -7
  277. package/src/release-loop/light-rc.ts +173 -0
  278. package/src/release-loop/placement.ts +51 -17
  279. package/src/release-loop/rubric.ts +57 -1
  280. package/src/self-dev/dev-cli.ts +36 -0
  281. package/src/self-dev/orchestrate.ts +1 -0
  282. package/src/self-dev/pr-preview.ts +179 -0
  283. package/src/self-dev/shard-run.ts +23 -7
  284. package/src/self-implement/gate-cli.ts +29 -8
  285. package/src/self-implement/goal-author-clarification.ts +88 -13
  286. package/src/self-implement/goal-clarification-escalation.ts +2 -0
  287. package/src/self-implement/orchestrator.ts +2 -1
  288. package/src/self-implement/salvage-branches.ts +254 -0
  289. package/src/task-agent/actions.ts +63 -2
  290. package/src/task-agent/board.ts +132 -0
  291. package/src/task-agent/card-evidence.ts +203 -0
  292. package/src/task-agent/completion-evidence.ts +126 -0
  293. package/src/task-agent/judge.ts +18 -0
  294. package/src/task-agent/live-moves.ts +176 -0
  295. package/src/task-agent/mission.ts +32 -6
  296. package/src/task-agent/shadow.ts +183 -15
  297. package/src/task-agent/task-hand.ts +117 -15
  298. package/src/task-orchestrator/surfaces/pod-command-job.ts +64 -4
  299. package/src/task-orchestrator/surfaces/pod-lease.ts +142 -13
  300. package/src/task-orchestrator/surfaces/pod-pool.ts +13 -5
  301. package/src/task-orchestrator/surfaces/self-implement-pod.ts +2 -2
  302. package/src/telegram-away-command.ts +38 -0
  303. package/src/telegram-commands.ts +11 -0
  304. package/src/telegram-report-log-probe.ts +21 -0
  305. package/src/telegram-report.ts +27 -3
  306. package/src/tui-client/dashboard-session.ts +1 -0
  307. package/src/user-config.ts +134 -2
  308. package/src/version/packed-revision.json +1 -1
  309. package/apps/pwa/out/_next/static/chunks/1307-30dff22c9e1f3073.js +0 -1
  310. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
  311. package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
  314. package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/6158.af2e93b7adff709b.js +0 -2
  335. package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
  344. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d92f7ee3994e84c7.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-4733c967b56a4277.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/board/page-d22fd849cc22f104.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/botlab/page-bb742f995c6769e7.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/app/bots/page-02b073a2ac793b83.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/app/control/page-308111abadf35670.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/app/design-check/page-5d97b2901e0d1ea6.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/app/editor/page-c39554c321576982.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/app/exec/page-8c3d8e63ae2e1c98.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/app/field/page-9b72a07d424701c6.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/app/intake/page-a572f58cd3ca5864.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/app/market/page-fa244bd146a00ae8.js +0 -1
  369. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-44d56d6391965a6a.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/app/morning/page-83a700d4c754d802.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/app/not-found-7b3170f374f77b9d.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a3c58f5c72a5f3f9.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/app/reflection/page-1467047104141297.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-44d5f767491692bf.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/app/share/page-3cc47dca0922ab9a.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/app/tasks/page-b17cd46cf0e9b8cf.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/app/trace/page-7bf54c9bb9274af8.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/app/workspace/page-134da468538baf48.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/webpack-c2782b330447ffb6.js +0 -1
  403. package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
  404. /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → 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,7 +34,7 @@ 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'],
37
+ events: ['reason', 'generation'],
36
38
  assignments: ['priority', 'predecessors', 'deadline_version', 'ceo_minutes', 'ceo_date', 'accelerator', 'refs'],
37
39
  release_schedules: ['freeze_from', 'freeze_until'],
38
40
  };
@@ -72,7 +74,9 @@ 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
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) {
@@ -155,15 +159,15 @@ 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);
@@ -334,6 +338,30 @@ export interface ChecklistCollision { version: string; owner?: string; title: st
334
338
 
335
339
  export function mutate(version: string, released: string, dev: string, apply: (data: Checklist, otherItems: (id: string) => ChecklistCollision[]) => boolean): Checklist {
336
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
+ }
337
365
  const db = open();
338
366
  try {
339
367
  let imported = false;
@@ -352,6 +380,22 @@ export function mutate(version: string, released: string, dev: string, apply: (d
352
380
  return { version: item.version, ...(item.owner !== null ? { owner: item.owner } : {}), title: item.title };
353
381
  });
354
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
+ }
355
399
  for (const item of data.items) if (before.get(item.id) !== JSON.stringify(item)) {
356
400
  const previous = before.get(item.id);
357
401
  const evidenceChanged = previous && (JSON.parse(previous) as ChecklistItem).evidence !== item.evidence;
@@ -365,7 +409,10 @@ export function mutate(version: string, released: string, dev: string, apply: (d
365
409
  db.query('DELETE FROM evidence WHERE feature_id = ? AND version = ?').run(id, version);
366
410
  db.query('DELETE FROM assignments WHERE feature_id = ? AND version = ?').run(id, version);
367
411
  }
368
- 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
+ }
369
416
  }
370
417
  return data;
371
418
  });
@@ -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,6 +2,7 @@ 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';
@@ -40,10 +41,41 @@ function versionOrder(a: string, b: string): number {
40
41
  return 0;
41
42
  }
42
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
+
43
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`).
44
71
  export function mergedPrsLast24h(now = new Date()): number {
45
72
  const since = new Date(now.getTime() - 86_400_000).toISOString();
46
- 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' });
47
79
  if (r.status !== 0) throw new CliUserError(`병합 PR 속도 조회 실패: ${(r.stderr || r.error || '').toString().trim()}`);
48
80
  let rows: Array<{ number: number; mergedAt: string }>;
49
81
  try { rows = JSON.parse(r.stdout) as typeof rows; }
@@ -92,10 +124,16 @@ function dependencyViolation(
92
124
  locations: Map<string, string>,
93
125
  overrides: Map<string, string>,
94
126
  input: PlacementCell,
127
+ released?: string | null,
95
128
  ): string | null {
96
129
  for (const [version, snapshot] of snapshots) for (const item of snapshot.items) {
97
- 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;
98
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;
99
137
  for (const predecessor of predecessors) {
100
138
  const predecessorVersion = overrides.get(predecessor) ?? locations.get(predecessor);
101
139
  if (!predecessorVersion || versionOrder(dependentVersion, predecessorVersion) <= 0)
@@ -131,12 +169,8 @@ export function placeCell(input: PlacementCell, deps: PlacementDeps = {}): Place
131
169
  const open = schedules.filter((row) => (!released || versionOrder(row.version, released) > 0) && row.landBy && available(row, now, backlog));
132
170
  const sources = [...new Set([...schedules.map((row) => row.version), ...backlog])];
133
171
  const snapshots = new Map(sources.map((version) => [version, (deps.checklist ?? listChecklist)(version)]));
134
- const locations = new Map<string, string>();
135
- for (const [version, snapshot] of snapshots) for (const item of snapshot.items) {
136
- if (backlog.includes(version)) continue;
137
- if (locations.has(item.id)) throw new CliUserError(`여러 판의 같은 칸: ${item.id}`);
138
- locations.set(item.id, version);
139
- }
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}`);
140
174
  // A backlog copy never outranks a scheduled cell with the same id.
141
175
  for (const version of backlog) for (const item of snapshots.get(version)?.items ?? []) if (!locations.has(item.id)) locations.set(item.id, version);
142
176
  const from = locations.get(input.id) ?? null;
@@ -172,7 +206,7 @@ export function placeCell(input: PlacementCell, deps: PlacementDeps = {}): Place
172
206
  const displaced: PlacementDecision['displaced'] = [];
173
207
  let chosen: ReleaseSchedule | undefined;
174
208
  let loadIssue: string | null = null;
175
- 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));
176
210
  // 판은 «마감 안 가장 이른 판»부터 본다(기존 불변식). 가속 등급은 그 판이 찼을 때 같은 우선순위 비가속 칸을 밀 수 있게만 한다.
177
211
  for (const row of feasible) {
178
212
  const placement = new Map([[input.id, row.version]]);
@@ -192,7 +226,7 @@ export function placeCell(input: PlacementCell, deps: PlacementDeps = {}): Place
192
226
  || (accelerator && priority === input.priority);
193
227
  const p2 = [...items].reverse().find((item) => {
194
228
  if (item.accelerator === true) return false;
195
- 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;
196
230
  if (item.deadlineVersion && versionOrder(next.version, item.deadlineVersion) > 0) return false;
197
231
  if (items.length < capacity(row) && !(seatItems.length >= seatLimit && ownerMatches(item.owner, seat))) return false;
198
232
  if (!item.owner) return false;
@@ -235,6 +269,7 @@ export function placeCell(input: PlacementCell, deps: PlacementDeps = {}): Place
235
269
  function moveConstraint(
236
270
  item: ChecklistItem, to: ReleaseSchedule, backlog: readonly string[],
237
271
  snapshots: Map<string, ChecklistItem[]>, now: number, rate: number, caps: Record<string, number>, rows: ReleaseSchedule[], ceoCap: number,
272
+ released: string | null | undefined,
238
273
  ): string | null {
239
274
  if (!to.landBy || !available(to, now, backlog)) return '착지 마감이 지났거나 백로그 판이라 이동 불가';
240
275
  if (item.deadlineVersion && versionOrder(to.version, item.deadlineVersion) > 0) return '마감 판 뒤로 이월 불가';
@@ -246,16 +281,15 @@ function moveConstraint(
246
281
  const loadIssue = ceoOverload(item, to, new Map([...snapshots].map(([version, items]) => [version, { items }])),
247
282
  rows, ceoCap);
248
283
  if (loadIssue) return loadIssue;
249
- const locations = new Map<string, string>();
250
- for (const [version, items] of snapshots) for (const cell of items) {
251
- if (locations.has(cell.id)) return `여러 판의 같은 칸: ${cell.id}`;
252
- locations.set(cell.id, version);
253
- }
284
+ const { locations, conflict } = cellLocations(snapshots, released);
285
+ if (conflict) return `여러 판의 같은 칸: ${conflict}`;
254
286
  for (const predecessor of item.predecessors ?? []) {
255
287
  const location = locations.get(predecessor);
256
288
  if (!location || versionOrder(to.version, location) <= 0) return `선행 칸 ${predecessor} 이후 판으로만 이동 가능`;
257
289
  }
258
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;
259
293
  if (dependent.id !== item.id && dependent.predecessors?.includes(item.id) && versionOrder(version, to.version) <= 0)
260
294
  return `의존 칸 ${dependent.id} 이전 판으로만 이동 가능`;
261
295
  }
@@ -289,7 +323,7 @@ export function rebalance(version: string, deps: PlacementDeps = {}): RebalanceR
289
323
  const ceoCap = validCeoCap(deps.ceoDailyCap ?? placementCeoDailyCap());
290
324
  const result: RebalanceResult = { decisions: [], blocked: [] };
291
325
  for (const { item, score } of unstarted) {
292
- const violation = moveConstraint(item, next!, backlog, snapshots, now, rate, caps, rows, ceoCap);
326
+ const violation = moveConstraint(item, next!, backlog, snapshots, now, rate, caps, rows, ceoCap, deps.released ?? releasedVersion());
293
327
  if (violation) {
294
328
  result.blocked.push({ id: item.id, from: version, to: next!.version, reason: violation });
295
329
  continue;
@@ -315,7 +349,7 @@ export function seatMove(id: string, from: string, to: string, by: string, reaso
315
349
  if (!ownerMatches(item.owner, parseOwner(by).seat) || next?.version !== to) throw new CliUserError('남의 칸 당기기 거부 — COO 에 요청');
316
350
  const now = (deps.now ?? new Date()).getTime();
317
351
  const rate = deps.merged24h ?? mergedPrsLast24h(new Date(now));
318
- 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());
319
353
  if (violation) throw new CliUserError(`${id}: ${violation}`);
320
354
  if (!deps.dryRun) move(id, from, to, by, undefined, undefined, reason);
321
355
  }
@@ -2,6 +2,56 @@ import { Database } from 'bun:sqlite';
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import type { ChecklistItem } from './checklist.js';
5
+ import { debug } from '../debug/log.js';
6
+ import { getUserConfig } from '../user-config.js';
7
+
8
+ export type RubricAxis = { key: string; weight: number };
9
+
10
+ export const DEFAULT_PROJECT_AXES: readonly RubricAxis[] = [
11
+ { key: 'V', weight: 2 }, { key: 'U', weight: 2 },
12
+ { key: 'R', weight: 2 }, { key: 'S', weight: -0.5 },
13
+ ];
14
+
15
+ const INTERNAL_AXES: readonly RubricAxis[] = [
16
+ { key: 'A', weight: 2 }, { key: 'E', weight: 2 },
17
+ { key: 'R', weight: 2 }, { key: 'D', weight: 1.5 },
18
+ { key: 'M', weight: 1 }, { key: 'B', weight: 1 },
19
+ { key: 'S', weight: -0.5 }, { key: 'X', weight: -0.5 },
20
+ ];
21
+
22
+ export function rubricScoreWith(axes: readonly RubricAxis[], values: Readonly<{ [key: string]: number } | Rubric>): number {
23
+ const scores: Readonly<Record<string, number>> = values as Readonly<Record<string, number>>;
24
+ return axes.reduce((score, { key, weight }) => {
25
+ const value = Object.hasOwn(scores, key) ? scores[key] : undefined;
26
+ if (typeof value !== 'number' || !Number.isFinite(value)) throw new Error(`루브릭 축 ${key} 값이 없다`);
27
+ if (typeof weight !== 'number' || !Number.isFinite(weight)) throw new Error(`루브릭 축 ${key} 가중이 유한수가 아니다`);
28
+ return score + weight * value;
29
+ }, 0);
30
+ }
31
+
32
+ /**
33
+ * RFC §A4b⑤ — unconfigured projects (and the internal pack `elanous`) keep the internal eight axes;
34
+ * a project with `projects.<id>.rubric.axes` starts from V/U/R/S and replaces matching keys or appends new ones.
35
+ * An invalid axes entry raises its config error text here (loading the config itself stays non-fatal).
36
+ */
37
+ export function resolveRubricAxes(projectId?: string): RubricAxis[] {
38
+ const projects = projectId && projectId !== 'elanous' ? getUserConfig().projects : undefined;
39
+ const rubric = projects && Object.hasOwn(projects, projectId!) ? projects[projectId!]?.rubric : undefined;
40
+ if (rubric?.error) {
41
+ debug.log('release.rubric', 'axes-invalid', { project: projectId, error: rubric.error });
42
+ throw new Error(rubric.error);
43
+ }
44
+ const configured = rubric?.axes;
45
+ const axes = (configured ? DEFAULT_PROJECT_AXES : INTERNAL_AXES).map((axis) => ({ ...axis }));
46
+ for (const axis of configured ?? []) {
47
+ const index = axes.findIndex((current) => current.key === axis.key);
48
+ if (index < 0) axes.push({ ...axis });
49
+ else axes[index] = { ...axis };
50
+ }
51
+ // An omitted project id is logged as null (unspecified) — not as `elanous` — even though it resolves to the internal axes.
52
+ debug.log('release.rubric', 'axes', { project: projectId ?? null, keys: axes.map(({ key }) => key) });
53
+ return axes;
54
+ }
5
55
 
6
56
  export interface Rubric {
7
57
  A: number;
@@ -33,7 +83,7 @@ export function parseRubric(text: string): Rubric | null {
33
83
  }
34
84
 
35
85
  export function rubricScore(r: Rubric): number {
36
- return 2 * r.A + 2 * r.E + 2 * r.R + 1.5 * r.D + r.M + r.B - 0.5 * r.S - 0.5 * r.X;
86
+ return rubricScoreWith(INTERNAL_AXES, r);
37
87
  }
38
88
 
39
89
  export function rubricGrade(score: number): 'P1' | 'P2' | 'P3' | 'P4' {
@@ -43,6 +93,12 @@ export function rubricGrade(score: number): 'P1' | 'P2' | 'P3' | 'P4' {
43
93
  return 'P4';
44
94
  }
45
95
 
96
+ /** The rule-only split uses the same numeric rubric as the release checklist. */
97
+ export function rubricPriority(text: string): ReturnType<typeof rubricGrade> {
98
+ const rubric = parseRubric(text);
99
+ return rubric ? rubricGrade(rubricScore(rubric)) : 'P2';
100
+ }
101
+
46
102
  /** Inspect an existing checklist without initializing, importing, or migrating its ledger. */
47
103
  export function readRubricItems(version: string, ledgerRoot: string): Pick<ChecklistItem, 'id' | 'title' | 'evidence' | 'priority'>[] {
48
104
  const dbPath = join(ledgerRoot, 'release', 'features.sqlite');
@@ -77,6 +77,42 @@ export function shouldSuppressDevJsonWrapper(opts: Pick<DevCliOpts, 'elanous' |
77
77
  return opts.elanous === true && opts.hold === true && opts.json === true;
78
78
  }
79
79
 
80
+ /** 격리 TUI 기동의 작업 디렉토리 갈래(TUI-ONE-WORD-LAUNCH · 대표 *"cwd 를 넣어야 한다는 것 자체가 프릭션"*).
81
+ *
82
+ * - `explicit-cwd` — 사람이 `--cwd` 를 줬다. ⭐ 명시가 이긴다(그대로 쓴다).
83
+ * - `requested` — `--worktree` 를 줬다(종전 경로).
84
+ * - `auto` — 명시 격리 우주 ⊕ `--elanous` ⊕ 둘 다 없음 ⇒ 시스템이 `prepareDevWorktree` 로 스스로 만든다.
85
+ * ⚠️ `hold` 여부와 무관하다 — `--elanous --goal` 도 같은 우주에서 같은 거부를 받았기 때문이다.
86
+ * - `none` — 격리가 아닌 우주(운영·트리 파생) 또는 elanous 가 아닌 경로 — 종전 동작 그대로.
87
+ *
88
+ * ⛔ `explicitlyIsolated` 는 `isExplicitlyIsolated`(src/cli/pty-drive-cli.ts) 한 벌에서 온다. */
89
+ export type AutoWorktreeDecision = 'explicit-cwd' | 'requested' | 'auto' | 'none';
90
+
91
+ export function decideAutoWorktree(input: {
92
+ readonly explicitlyIsolated: boolean;
93
+ readonly elanous?: boolean;
94
+ readonly hold?: boolean;
95
+ readonly cwd?: string;
96
+ readonly worktree?: boolean;
97
+ }): AutoWorktreeDecision {
98
+ if (input.cwd?.trim()) return 'explicit-cwd';
99
+ if (input.worktree === true) return 'requested';
100
+ if (input.explicitlyIsolated && input.elanous === true) return 'auto';
101
+ return 'none';
102
+ }
103
+
104
+ /** git 저장소 밖 등으로 자동 워크트리를 못 만들었을 때의 사람 문면(스택 없이 한 줄). */
105
+ export function formatAutoWorktreeFailure(cause: unknown): string {
106
+ const raw = cause instanceof Error ? cause.message : String(cause);
107
+ const reason = /requires a Git repository/i.test(raw) ? '지금 디렉토리가 git 저장소가 아니다' : raw.split('\n')[0]!.trim();
108
+ return `작업 디렉토리를 스스로 정하지 못했다 — git 저장소 안에서 치거나 --cwd <경로> 를 주십시오 (${reason})`;
109
+ }
110
+
111
+ /** 자동 워크트리를 만들었을 때 사람에게 알리는 한 줄. */
112
+ export function formatAutoWorktreeNotice(path: string): string {
113
+ return `작업 디렉토리: ${path} (격리 우주라 자동으로 만들었다 · 직접 정하려면 --cwd)`;
114
+ }
115
+
80
116
  export interface DevCliOpts {
81
117
  base?: string;
82
118
  enhance?: boolean;
@@ -1494,6 +1494,7 @@ export function orchestrateSelfDev(opts: OrchestrateSelfDevOptions): Promise<Sel
1494
1494
  ...(disp?.branch ? { branch: disp.branch } : {}),
1495
1495
  ...(disp?.harvestable === true && disp.branch ? { harvestable: true } : {}),
1496
1496
  ...(disp?.worktreePath ? { worktreePath: disp.worktreePath } : {}),
1497
+ ...(disp?.ok !== undefined ? { ok: disp.ok } : {}),
1497
1498
  ...(disp?.prUrl ? { prUrl: disp.prUrl } : {}),
1498
1499
  ...(disp?.prNumber !== undefined ? { prNumber: disp.prNumber } : {}),
1499
1500
  ...(disp?.merged !== undefined ? { merged: disp.merged } : {}),