elanous 0.2.13 → 0.2.15

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 (524) 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/LZcstaO9_NXypPFmujolv/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1307-33ffcc49ab45123b.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
  10. package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
  31. package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
  42. package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/approvals/page-0e2c57fe6add4b2a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e05a77df7e005b04.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/bots/page-b8c49f6b4fa2df91.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/chat/page-976813c91d7394ea.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/design-check/page-72d3e7ff35a8c954.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/editor/page-60c7a8e1a4fe62ce.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/exec/page-2a52b316cb143d9c.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/field/page-c3ce483c8d5db370.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/intake/page-96f074f6538e8a2c.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/layout-e97be5334d83a9f5.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/morning/page-c1343f9499d86f3f.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/not-found-8739a2e0ae793130.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-d23b12bb92242558.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c7fea475488201a8.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/reflection/page-5efbf81124c474ae.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-06ee266953d9c879.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/settings/page-153c2b3bcc89e8de.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/share/page-0d6d23b40263a7ea.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-f5597e894c02cd33.js} +1 -1
  91. package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/trace/page-73735b64bfe1ae67.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f7e631e9b4daf66e.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9817c7f9007466cc.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
  106. package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +1 -0
  107. package/apps/pwa/out/approvals/index.html +1 -1
  108. package/apps/pwa/out/approvals/index.txt +23 -23
  109. package/apps/pwa/out/autopilot/index.html +1 -1
  110. package/apps/pwa/out/autopilot/index.txt +23 -23
  111. package/apps/pwa/out/board/index.html +1 -1
  112. package/apps/pwa/out/board/index.txt +23 -23
  113. package/apps/pwa/out/botlab/index.html +1 -1
  114. package/apps/pwa/out/botlab/index.txt +22 -22
  115. package/apps/pwa/out/bots/index.html +1 -1
  116. package/apps/pwa/out/bots/index.txt +22 -22
  117. package/apps/pwa/out/ceo/index.html +1 -0
  118. package/apps/pwa/out/ceo/index.txt +33 -0
  119. package/apps/pwa/out/chat/index.html +1 -1
  120. package/apps/pwa/out/chat/index.txt +22 -22
  121. package/apps/pwa/out/consult/index.html +1 -1
  122. package/apps/pwa/out/consult/index.txt +23 -23
  123. package/apps/pwa/out/control/index.html +1 -1
  124. package/apps/pwa/out/control/index.txt +23 -23
  125. package/apps/pwa/out/design-check/index.html +1 -1
  126. package/apps/pwa/out/design-check/index.txt +23 -23
  127. package/apps/pwa/out/editor/index.html +1 -1
  128. package/apps/pwa/out/editor/index.txt +23 -23
  129. package/apps/pwa/out/exec/index.html +1 -1
  130. package/apps/pwa/out/exec/index.txt +23 -23
  131. package/apps/pwa/out/field/index.html +1 -1
  132. package/apps/pwa/out/field/index.txt +23 -23
  133. package/apps/pwa/out/healthz +1 -1
  134. package/apps/pwa/out/index.html +1 -1
  135. package/apps/pwa/out/index.txt +22 -22
  136. package/apps/pwa/out/inside/index.html +1 -1
  137. package/apps/pwa/out/inside/index.txt +22 -22
  138. package/apps/pwa/out/intake/index.html +1 -1
  139. package/apps/pwa/out/intake/index.txt +23 -23
  140. package/apps/pwa/out/live/index.html +1 -1
  141. package/apps/pwa/out/live/index.txt +23 -23
  142. package/apps/pwa/out/loops/index.html +1 -0
  143. package/apps/pwa/out/loops/index.txt +30 -0
  144. package/apps/pwa/out/market/index.html +1 -1
  145. package/apps/pwa/out/market/index.txt +22 -22
  146. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  147. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  148. package/apps/pwa/out/morning/index.html +1 -1
  149. package/apps/pwa/out/morning/index.txt +23 -23
  150. package/apps/pwa/out/observatory/index.html +1 -1
  151. package/apps/pwa/out/observatory/index.txt +23 -23
  152. package/apps/pwa/out/ops/checklist/index.html +1 -1
  153. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  154. package/apps/pwa/out/ops/release/index.html +1 -1
  155. package/apps/pwa/out/ops/release/index.txt +22 -22
  156. package/apps/pwa/out/ops/seats/index.html +1 -1
  157. package/apps/pwa/out/ops/seats/index.txt +22 -22
  158. package/apps/pwa/out/outputs/index.html +1 -1
  159. package/apps/pwa/out/outputs/index.txt +22 -22
  160. package/apps/pwa/out/reflection/index.html +1 -1
  161. package/apps/pwa/out/reflection/index.txt +23 -23
  162. package/apps/pwa/out/scheduler/index.html +1 -1
  163. package/apps/pwa/out/scheduler/index.txt +23 -23
  164. package/apps/pwa/out/sessions/index.html +1 -1
  165. package/apps/pwa/out/sessions/index.txt +22 -22
  166. package/apps/pwa/out/settings/devices/index.html +1 -1
  167. package/apps/pwa/out/settings/devices/index.txt +23 -23
  168. package/apps/pwa/out/settings/index.html +1 -1
  169. package/apps/pwa/out/settings/index.txt +23 -23
  170. package/apps/pwa/out/setup/done/index.html +1 -1
  171. package/apps/pwa/out/setup/done/index.txt +23 -23
  172. package/apps/pwa/out/setup/index.html +1 -1
  173. package/apps/pwa/out/setup/index.txt +23 -23
  174. package/apps/pwa/out/share/index.html +1 -1
  175. package/apps/pwa/out/share/index.txt +23 -23
  176. package/apps/pwa/out/showroom/index.html +1 -1
  177. package/apps/pwa/out/showroom/index.txt +22 -22
  178. package/apps/pwa/out/tasks/index.html +1 -1
  179. package/apps/pwa/out/tasks/index.txt +23 -23
  180. package/apps/pwa/out/term/index.html +1 -1
  181. package/apps/pwa/out/term/index.txt +23 -23
  182. package/apps/pwa/out/today/index.html +1 -1
  183. package/apps/pwa/out/today/index.txt +23 -23
  184. package/apps/pwa/out/trace/index.html +1 -1
  185. package/apps/pwa/out/trace/index.txt +23 -23
  186. package/apps/pwa/out/vault/index.html +1 -1
  187. package/apps/pwa/out/vault/index.txt +23 -23
  188. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  189. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  190. package/apps/pwa/out/workflows/index.html +1 -1
  191. package/apps/pwa/out/workflows/index.txt +23 -23
  192. package/apps/pwa/out/workspace/index.html +1 -1
  193. package/apps/pwa/out/workspace/index.txt +23 -23
  194. package/apps/pwa/out/worktrees/index.html +1 -1
  195. package/apps/pwa/out/worktrees/index.txt +23 -23
  196. package/graphs/heal/heal-loop.yaml +1 -1
  197. package/graphs/implement-loop.yaml +1 -1
  198. package/graphs/orchestrator/orchestrator.yaml +59 -0
  199. package/graphs/orchestrator/recipes.yaml +18 -0
  200. package/graphs/release/recipes.yaml +6 -0
  201. package/graphs/release/release-loop.yaml +6 -1
  202. package/graphs/rhythm/daily.yaml +38 -0
  203. package/graphs/rhythm/recipes.yaml +18 -0
  204. package/graphs/rhythm/weekly.yaml +39 -0
  205. package/graphs/video/recipes.yaml +3 -0
  206. package/graphs/video/youtube-summary.yaml +17 -0
  207. package/package.json +1 -1
  208. package/scripts/audit-test-state-writes.ts +72 -17
  209. package/scripts/brand/check.ts +188 -0
  210. package/scripts/ci-typecheck-changed.ts +50 -18
  211. package/scripts/coord-post.sh +11 -1
  212. package/scripts/docs/generate-graph-anatomy.ts +315 -0
  213. package/scripts/docs-cli-check.ts +23 -6
  214. package/scripts/expose-rubric-check.ts +118 -0
  215. package/scripts/expose-rubric-suggest.ts +169 -0
  216. package/scripts/heal/run-role.ts +14 -0
  217. package/scripts/hq/standby-verify.ts +11 -7
  218. package/scripts/lecture-note/node.ts +31 -11
  219. package/scripts/lessons/handbook-page.ts +112 -0
  220. package/scripts/marketing/generate-ax-brief.ts +140 -0
  221. package/scripts/marketing/generate-feature-map.ts +118 -0
  222. package/scripts/release-loop/checklist-gate-node.ts +53 -14
  223. package/scripts/release-loop/gate-node.ts +89 -14
  224. package/scripts/release-loop/publish-node.ts +14 -1
  225. package/scripts/release-loop/unattended-release.ts +23 -1
  226. package/scripts/release-loop/vault-note-node.ts +428 -0
  227. package/scripts/release-story/draft.ts +220 -0
  228. package/scripts/rhythm/daily.ts +570 -0
  229. package/scripts/rhythm/weekly.ts +315 -0
  230. package/scripts/skills/public-check.ts +169 -0
  231. package/scripts/smart-merge/resync-shadow.ts +147 -0
  232. package/scripts/test-deterministic.ts +4 -8
  233. package/scripts/test-diet/effectiveness.ts +195 -0
  234. package/scripts/test-diet/lib.ts +16 -8
  235. package/scripts/test-diet/node.ts +77 -9
  236. package/scripts/ux/demo-rehearsal.ts +2 -2
  237. package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
  238. package/scripts/video/youtube-summary.ts +133 -0
  239. package/skills/diagram-master/SKILL.md +42 -49
  240. package/skills/diagram-master/references/pyproject.toml +4 -1
  241. package/skills/diagram-master/references/routing-guide.md +4 -2
  242. package/skills/grill-me/SKILL.md +1 -0
  243. package/skills/youtube-master/SKILL.md +13 -13
  244. package/skills/youtube-master/package-lock.json +0 -545
  245. package/skills/youtube-master/package.json +2 -5
  246. package/skills/youtube-master/scripts/main.ts +2 -2
  247. package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
  248. package/skills/youtube-master/src/util.ts +30 -7
  249. package/src/agent-env/profile.ts +161 -0
  250. package/src/agent-mission/driver.ts +3 -3
  251. package/src/agent-substrate/pr-reviewer.ts +2 -0
  252. package/src/ask-user-question/pending-questions.ts +5 -0
  253. package/src/auth/github-app-token.ts +112 -13
  254. package/src/autopilot/build/llm-conflict-merge.ts +73 -10
  255. package/src/autopilot/build/merge-intent.ts +53 -0
  256. package/src/briefing/brief-items.ts +121 -0
  257. package/src/chat/index.ts +2 -0
  258. package/src/claims/claims-ledger.ts +232 -0
  259. package/src/claims/claims-render.ts +166 -0
  260. package/src/cli/agent-env-cli.ts +21 -0
  261. package/src/cli/claims-cli.ts +94 -0
  262. package/src/cli/decisions-cli.ts +44 -3
  263. package/src/cli/docs-cli.ts +2 -0
  264. package/src/cli/doctor-cli.ts +23 -1
  265. package/src/cli/doctor-node-modules.ts +147 -0
  266. package/src/cli/flow-cli.ts +11 -0
  267. package/src/cli/freeze-cli.ts +45 -0
  268. package/src/cli/intake-cli.ts +46 -17
  269. package/src/cli/lesson-cli.ts +108 -0
  270. package/src/cli/logs-cli.ts +104 -2
  271. package/src/cli/logs-model-input.ts +131 -0
  272. package/src/cli/pod-cli.ts +2 -0
  273. package/src/cli/pr-cli.ts +29 -1
  274. package/src/cli/private-ledger-commands.ts +54 -4
  275. package/src/cli/pty-drive-verdict.ts +11 -2
  276. package/src/cli/release-cli.ts +190 -33
  277. package/src/cli/rfc-status.ts +91 -0
  278. package/src/cli/seat-requests-cli.ts +52 -0
  279. package/src/connectors/linear.ts +5 -3
  280. package/src/context-bus/condense.ts +15 -20
  281. package/src/context-bus/context-now-public.ts +96 -0
  282. package/src/context-bus/context-now-surfaces.ts +55 -2
  283. package/src/context-bus/context-now.ts +6 -3
  284. package/src/context-bus/coord-events.ts +6 -4
  285. package/src/context-bus/long-term-memory.ts +92 -11
  286. package/src/dashboard/index.ts +75 -22
  287. package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
  288. package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
  289. package/src/dashboard/slash-runtime/loops-table.ts +77 -0
  290. package/src/decisions/decision-cards.ts +33 -7
  291. package/src/decisions/decision-ledger.ts +40 -1
  292. package/src/decisions/proact-meter.ts +286 -0
  293. package/src/decisions/proact1-lite.ts +86 -0
  294. package/src/decisions/web-push-decision-cards.ts +1 -1
  295. package/src/discord/sprint21-runtime.ts +16 -0
  296. package/src/discord-self-message.ts +8 -0
  297. package/src/discord-slash-wire.ts +31 -2
  298. package/src/domains/alert-audit.ts +271 -0
  299. package/src/domains/core-tools.ts +4 -3
  300. package/src/domains/ops-facts-tool.ts +31 -1
  301. package/src/domains/outbound-alert.ts +1 -1
  302. package/src/elanous-config-dir.ts +29 -4
  303. package/src/execution-loop/launch-gate.ts +29 -14
  304. package/src/flow/cards.ts +239 -0
  305. package/src/graph-runner/graph-tick.ts +4 -0
  306. package/src/graph-runner/runner.ts +30 -5
  307. package/src/harness/harness-cli-command.ts +259 -38
  308. package/src/harness/harness-incidents.ts +45 -12
  309. package/src/harness/harness-pod-dispatch.ts +102 -11
  310. package/src/harness/harness-queue-child.ts +3 -3
  311. package/src/harness/harness-queue.ts +583 -65
  312. package/src/harness/helper-repair.ts +87 -0
  313. package/src/harness/helper-scan.ts +151 -0
  314. package/src/harness/launch-stamp.ts +236 -0
  315. package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
  316. package/src/harness/ledger-write-guard.ts +35 -0
  317. package/src/harness/model-input-observation.ts +37 -0
  318. package/src/harness/pod-memory-policy.ts +15 -0
  319. package/src/harness/repository-name.ts +55 -0
  320. package/src/hq/fence-audit.ts +90 -0
  321. package/src/hq/hq.ts +63 -5
  322. package/src/hq/lease.ts +4 -4
  323. package/src/hq/move-plan.ts +126 -0
  324. package/src/hq/promote.ts +332 -0
  325. package/src/index.ts +159 -27
  326. package/src/intake-plane/check.ts +63 -0
  327. package/src/intake-plane/digest.ts +37 -15
  328. package/src/intake-plane/front-classifier.ts +6 -0
  329. package/src/intake-plane/items.ts +3 -2
  330. package/src/intake-plane/lens.ts +177 -0
  331. package/src/intake-plane/linear-wish.ts +39 -11
  332. package/src/intake-plane/route.ts +145 -1
  333. package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
  334. package/src/intake-plane/wish-card.ts +53 -2
  335. package/src/intake-plane/wish-folder.ts +5 -2
  336. package/src/intake-plane/wish-reply.ts +72 -0
  337. package/src/launch-head/seat-goal-queue-lock.fixture.ts +15 -0
  338. package/src/launch-head/seat-goal-queue.ts +198 -0
  339. package/src/lessons/lesson-import.ts +106 -0
  340. package/src/lessons/lesson-ledger.ts +206 -0
  341. package/src/loops/activity.ts +203 -0
  342. package/src/loops/budget.ts +63 -0
  343. package/src/loops/checker.ts +5 -3
  344. package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
  345. package/src/loops/loop-cli.ts +36 -4
  346. package/src/loops/neighbors.ts +40 -0
  347. package/src/loops/observe.ts +10 -0
  348. package/src/loops/orchestrator/author-depth.ts +220 -0
  349. package/src/loops/orchestrator/author-input-check.ts +82 -0
  350. package/src/loops/orchestrator/author-ledger.ts +232 -0
  351. package/src/loops/orchestrator/finish-rate.ts +175 -0
  352. package/src/loops/orchestrator/overlap.ts +51 -12
  353. package/src/loops/orchestrator/resync-shadow.ts +227 -0
  354. package/src/loops/orchestrator/seat-share.ts +56 -0
  355. package/src/loops/orchestrator/tick.ts +309 -0
  356. package/src/loops/orchestrator/traffic.ts +134 -13
  357. package/src/loops/registry.ts +535 -9
  358. package/src/loops/status-rows.ts +118 -0
  359. package/src/loops/verdict.ts +17 -5
  360. package/src/maturity/bot-slash-catalog.ts +66 -0
  361. package/src/maturity/feature-maturity.ts +9 -5
  362. package/src/maturity/tui-slash-availability.ts +31 -0
  363. package/src/mss/logging/log-store.ts +35 -4
  364. package/src/nexus/api/dashboard.ts +6 -1
  365. package/src/nexus/api/http-server.ts +33 -3
  366. package/src/nexus/api/loop-edges.ts +173 -0
  367. package/src/nexus/api/merge-approvals-gate.ts +1 -1
  368. package/src/nexus/api/schedules-read.ts +18 -1
  369. package/src/nexus/api/seat-requests.ts +79 -56
  370. package/src/nexus/api/task-cards-api.ts +44 -5
  371. package/src/nexus/api/workflows.ts +2 -1
  372. package/src/prompt-enhance/enhance.ts +6 -1
  373. package/src/release-loop/auto-start.ts +51 -0
  374. package/src/release-loop/checklist.ts +22 -4
  375. package/src/release-loop/feature-store.ts +71 -32
  376. package/src/release-loop/landing-freeze.ts +121 -0
  377. package/src/release-loop/merged-pr-checklist.ts +125 -0
  378. package/src/release-loop/placement.ts +302 -0
  379. package/src/release-loop/release-schedule.ts +3 -1
  380. package/src/seat-dispatch/ceo-commands.ts +2 -2
  381. package/src/seat-dispatch/seat-ask.ts +16 -3
  382. package/src/seat-dispatch/seat-questions.ts +58 -0
  383. package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
  384. package/src/seat-dispatch/seat-request-ledger.ts +133 -0
  385. package/src/seat-loop/seat-loop.ts +531 -40
  386. package/src/self-dev/dev-pipeline.ts +14 -6
  387. package/src/self-dev/draft-sweep.ts +162 -6
  388. package/src/self-dev/draft-triage-rules.ts +25 -8
  389. package/src/self-dev/orchestrate.ts +15 -1
  390. package/src/self-dev/review-provider-fallback.ts +6 -1
  391. package/src/self-dev/run-store.ts +73 -1
  392. package/src/self-dev/run-supervisor.ts +57 -2
  393. package/src/self-dev/self-orchestrate-runtime.ts +5 -1
  394. package/src/self-implement/budget-gate.ts +29 -0
  395. package/src/self-implement/expose-gate.ts +47 -0
  396. package/src/self-implement/frozen-merges.ts +295 -0
  397. package/src/self-implement/gate-cli.ts +10 -4
  398. package/src/self-implement/gate-scope.ts +46 -9
  399. package/src/self-implement/goal-archive.ts +222 -0
  400. package/src/self-implement/goal-author.ts +43 -5
  401. package/src/self-implement/goal-clarification-escalation.ts +5 -0
  402. package/src/self-implement/goal-lookup.ts +147 -0
  403. package/src/self-implement/heal-intake.ts +197 -0
  404. package/src/self-implement/host-regate.ts +32 -2
  405. package/src/self-implement/l8-merge-queue.ts +132 -0
  406. package/src/self-implement/orchestrator.ts +299 -48
  407. package/src/self-implement/pipeline-shape.ts +1 -1
  408. package/src/self-implement/revert-guard.ts +97 -0
  409. package/src/self-implement/run-ledger.ts +3 -1
  410. package/src/self-implement/run-status-mapping.ts +2 -0
  411. package/src/self-implement/seams.ts +29 -9
  412. package/src/self-implement/self-implement-cli.ts +1 -0
  413. package/src/self-implement/speed-claims.ts +338 -0
  414. package/src/session/index.ts +2 -0
  415. package/src/steward/triage.ts +74 -3
  416. package/src/steward/working-backwards.ts +4 -1
  417. package/src/task-cards/card-cli.ts +10 -2
  418. package/src/task-cards/card-store.ts +10 -6
  419. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
  420. package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
  421. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
  422. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  423. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
  424. package/src/task-orchestrator/surfaces/pod-pool.ts +60 -8
  425. package/src/task-orchestrator/surfaces/self-implement-pod.ts +224 -49
  426. package/src/task-orchestrator/surfaces/self-implement.ts +5 -1
  427. package/src/telegram-commands.ts +27 -3
  428. package/src/telegram-loops-command.ts +44 -0
  429. package/src/telegram.ts +25 -4
  430. package/src/user-config.ts +146 -18
  431. package/src/version/packed-revision.json +1 -1
  432. package/src/workflow-runtime/nodes/bash.ts +9 -1
  433. package/src/workflow-runtime/storage.ts +31 -13
  434. package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
  445. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
  449. package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-78b40a344ed22f24.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
  517. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
  518. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
  520. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
  521. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
  523. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
  524. /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → LZcstaO9_NXypPFmujolv}/_ssgManifest.js +0 -0
@@ -6,6 +6,7 @@
6
6
  // elanous release verify [--version <x.y.z>]
7
7
  // elanous release notes --from <ref> [--to <ref>]
8
8
  // elanous release run --version <x.y.z> [--cut-commit <sha>] [--dry-run] [--json] [--if-ready]
9
+ // elanous release auto-start [--window-minutes <n>] [--apply] [--json]
9
10
  // elanous release cut-branch --version <x.y.z> --base <sha> --pick <sha>... [--dry-run]
10
11
  //
11
12
  // 🩸 계기(2026-09-25 v0.1.0 첫 공개): 손으로 밟은 절차에서 둘을 빠뜨릴 뻔했다 — 공개본 git 커밋(판 커밋이 공개본 커밋이 된다) ·
@@ -28,14 +29,18 @@ import { addItem, claimItem, decodeClaimHistoryEntry, devVersion, listChecklist,
28
29
  import * as features from '../release-loop/feature-store.js';
29
30
  import { evidencePlan, landedButYellow, type MergedChecklistPr } from '../release-loop/landed-but-yellow.js';
30
31
  import { formatSchedule, getSchedule, listSchedules, setSchedule } from '../release-loop/release-schedule.js';
32
+ import { placeCell, rebalance, seatMove, type PlacementPriority } from '../release-loop/placement.js';
31
33
  import { writeStdoutJson } from './stdout-json.js';
32
34
  import { CliUserError } from './cli-user-error.js';
33
35
  import { hqCliWriteAllowed, type HqDeps } from '../hq/hq.js';
34
36
  import { fileLeaseStore } from '../hq/lease.js';
37
+ import { isIsolatedLedgerWriteRoot } from '../hq/ledger-write-target.js';
35
38
  import { runUnattendedRelease, type UnattendedReleaseDeps } from '../../scripts/release-loop/unattended-release.js';
36
39
  import { cutReleaseBranch } from '../../scripts/release-loop/cut-branch.js';
37
40
  import { releaseReadiness } from '../../scripts/release-loop/release-readiness.js';
38
41
  import { runReleaseIfReady } from './release-run-if-ready.js';
42
+ import { autoStartScheduledRelease, type AutoStartDeferral } from '../release-loop/auto-start.js';
43
+ import { landingFreezeMessage, LandingFrozenError, readLandingFreeze } from '../release-loop/landing-freeze.js';
39
44
 
40
45
  export const DEFAULT_PUBLIC_REPO = 'ElanvitalAI/elanous';
41
46
  const SEMVER = /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/;
@@ -459,7 +464,8 @@ async function jsonAction<T>(json: boolean | undefined, body: (log: (l: string)
459
464
  if (json) process.stdout.write(`${JSON.stringify({ ok: ok(r), ...(r as object) })}\n`);
460
465
  if (!ok(r) && !process.exitCode) process.exitCode = 1;
461
466
  } catch (e) {
462
- if (json) process.stdout.write(`${JSON.stringify({ ok: false, error: (e as Error).message })}\n`);
467
+ const frozen = e instanceof LandingFrozenError ? { reason: 'frozen' as const } : {};
468
+ if (json) process.stdout.write(`${JSON.stringify({ ok: false, ...frozen, error: (e as Error).message })}\n`);
463
469
  else console.error(`⛔ ${(e as Error).message}`);
464
470
  process.exitCode = 1;
465
471
  }
@@ -494,7 +500,7 @@ async function checklistOutput(v: string, codenames: Record<string, string>, mod
494
500
  console.log(`⚠ 짝 경고 ${parity.length}: ${parity.map(({ id }) => id).join(', ') || '없음'}`);
495
501
  console.log(`${v}${result.codename ? ` (${result.codename})` : ''} · 공개 ${data.released || '없음'} · 개발 ${data.dev}`);
496
502
  if (mode === 'list') {
497
- for (const item of data.items) console.log(`${({ green: '🟢', yellow: '🟡', red: '🔴', done: '✅' } as const)[item.status]} ${item.id} ${item.title}${item.owner ? ` · ${item.owner}` : ''}`);
503
+ for (const item of data.items) console.log(`${({ green: '🟢', yellow: '🟡', red: '🔴', done: '✅' } as const)[item.status]} ${item.id} ${item.title}${item.owner ? ` · ${item.owner}` : ''}${item.ceoMinutes !== undefined ? ` · 대표 손 ${item.ceoMinutes}분${item.ceoDate ? ` (${item.ceoDate})` : ''}` : ''}`);
498
504
  } else {
499
505
  console.log(`🟢 ${summary.green} · 🟡 ${summary.yellow} · 🔴 ${summary.red} · ✅ ${summary.done}`);
500
506
  console.log(`🔴 칸: ${summary.blocked.join(', ') || '없음'}`);
@@ -511,6 +517,46 @@ export interface LandedButYellowDeps {
511
517
  released?: typeof features.releasedVersion;
512
518
  }
513
519
 
520
+ const MERGED_PR_LIMIT = 400;
521
+
522
+ function mergedChecklistPrs(since: string, now: Date, run: Runner): { prs: MergedChecklistPr[]; truncated: boolean } {
523
+ const cwd = process.cwd();
524
+ const fetch = (lower: string, upper?: string, limit = MERGED_PR_LIMIT): MergedChecklistPr[] => {
525
+ const search = `merged:>=${lower}${upper ? ` merged:<${upper}` : ''}`;
526
+ const raw = must(run('gh', ['pr', 'list', '--state', 'merged', '--search', search, '--limit', String(limit), '--json', 'number,title,body,mergedAt'], cwd), '병합 PR 조회(gh)');
527
+ const page = JSON.parse(raw) as MergedChecklistPr[];
528
+ if (!Array.isArray(page)) throw new Error('병합 PR 조회(gh): 배열이 아니다');
529
+ return page;
530
+ };
531
+ const unique = new Map<number, MergedChecklistPr>();
532
+ let truncated = false;
533
+ const collect = (page: MergedChecklistPr[]) => {
534
+ for (const pr of page) if (!unique.has(pr.number)) unique.set(pr.number, pr);
535
+ };
536
+ const first = fetch(since);
537
+ if (first.length < MERGED_PR_LIMIT) return { prs: first, truncated: false };
538
+ const lower = Date.parse(`${since}T00:00:00Z`);
539
+ const upper = Math.max(lower + 1000, Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1));
540
+ const visit = (start: number, end: number): void => {
541
+ const from = new Date(start).toISOString();
542
+ const to = new Date(end).toISOString();
543
+ const page = fetch(from, to);
544
+ if (page.length < MERGED_PR_LIMIT) { collect(page); return; }
545
+ if (end - start <= 1000) {
546
+ // The second-precision search cannot be subdivided; probe one extra row to distinguish exactly 400 from a real truncation.
547
+ const probe = fetch(from, to, MERGED_PR_LIMIT + 1);
548
+ collect(probe.slice(0, MERGED_PR_LIMIT));
549
+ if (probe.length > MERGED_PR_LIMIT) truncated = true;
550
+ return;
551
+ }
552
+ const middle = start + Math.floor((end - start) / 2000) * 1000;
553
+ visit(start, middle);
554
+ visit(middle, end);
555
+ };
556
+ visit(lower, upper);
557
+ return { prs: [...unique.values()], truncated };
558
+ }
559
+
514
560
  function printLandedRows(rows: ReturnType<typeof landedButYellow>, items: ReturnType<typeof listChecklist>['items']): void {
515
561
  const updatedAtById = new Map(items.map((item) => [item.id, item.updatedAt]));
516
562
  const date = (iso: string) => {
@@ -544,6 +590,13 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
544
590
  const ledgerFenceDeps = (): HqDeps => getElanousConfigDirOverride() && !hqDeps.store && !getUserConfig().hq?.arbiter
545
591
  ? { ...hqDeps, store: fileLeaseStore(join(effectiveInstanceRoot(), 'hq', 'lease.json')), seenPath: join(effectiveInstanceRoot(), 'hq', 'seen-generation'), localPath: join(effectiveInstanceRoot(), 'hq', 'local.json') }
546
592
  : hqDeps;
593
+ const mayWriteLedger = (command: string, override: boolean, ledgerRoot: string) => {
594
+ if (isIsolatedLedgerWriteRoot(ledgerRoot)) {
595
+ try { debug.log('hq.fence', 'skipped-isolated', { root: ledgerRoot, command }); } catch { /* observation is fail-soft */ }
596
+ return true;
597
+ }
598
+ return hqCliWriteAllowed(command, override, ledgerFenceDeps());
599
+ };
547
600
  schedule.command('list').option('--json', '결과 JSON').action((o: { json?: boolean }) => {
548
601
  const rows = listSchedules(scheduleLedgerRoot());
549
602
  if (o.json) console.log(JSON.stringify(rows));
@@ -556,18 +609,50 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
556
609
  if (o.json) console.log(JSON.stringify(row)); else console.log(formatSchedule(row));
557
610
  });
558
611
  schedule.command('set').requiredOption('--version <v>', '갱신할 판')
559
- .option('--cut-at <iso>', '오프셋 포함 컷 시각').option('--land-by <iso>', '오프셋 포함 착지 마감').option('--json', '결과 JSON').option('--hq-override')
560
- .action((o: { version: string; cutAt?: string; landBy?: string; json?: boolean; hqOverride?: boolean }) => {
561
- if (o.cutAt === undefined && o.landBy === undefined) throw new CliUserError('갱신할 시각을 지정하라', '--cut-at <iso> 또는 --land-by <iso>');
562
- if (!hqCliWriteAllowed('release schedule set', Boolean(o.hqOverride), ledgerFenceDeps())) return;
563
- const row = setSchedule(o.version, { cutAt: o.cutAt, landBy: o.landBy }, process.env.ELANOUS_TRACK || 'cli', scheduleLedgerRoot());
612
+ .option('--cut-at <iso>', '오프셋 포함 컷 시각').option('--land-by <iso>', '오프셋 포함 착지 마감')
613
+ .option('--freeze-from <iso>', '동결 시작').option('--freeze-until <iso>', '동결 종료').option('--json', '결과 JSON').option('--hq-override')
614
+ .action((o: { version: string; cutAt?: string; landBy?: string; freezeFrom?: string; freezeUntil?: string; json?: boolean; hqOverride?: boolean }) => {
615
+ if (o.cutAt === undefined && o.landBy === undefined && o.freezeFrom === undefined && o.freezeUntil === undefined) throw new CliUserError('갱신할 시각을 지정하라', '--cut-at <iso> 또는 --land-by <iso>');
616
+ if (!mayWriteLedger('release schedule set', Boolean(o.hqOverride), scheduleLedgerRoot())) return;
617
+ const row = setSchedule(o.version, { cutAt: o.cutAt, landBy: o.landBy, freezeFrom: o.freezeFrom, freezeUntil: o.freezeUntil }, process.env.ELANOUS_TRACK || 'cli', scheduleLedgerRoot());
564
618
  if (o.json) console.log(JSON.stringify(row)); else console.log(formatSchedule(row));
565
619
  });
620
+ release.command('place <id>').description('칸의 우선순위·마감·용량으로 판 배치')
621
+ .option('--priority <priority>', 'P0|P1|P2').option('--dry-run', '이동 없이 배치 보기')
622
+ .action((id: string, opts: { priority?: string; dryRun?: boolean }) => {
623
+ const rows = listSchedules(scheduleLedgerRoot());
624
+ const found = rows.flatMap(({ version }) => listChecklist(version).items.filter((item) => item.id === id));
625
+ if (found.length !== 1) throw new CliUserError(found.length ? `여러 판의 같은 칸: ${id}` : `없는 칸: ${id}`, 'release checklist add 로 칸을 먼저 만든다');
626
+ const item = found[0]!;
627
+ if (!item.owner) throw new CliUserError(`담당 없는 칸: ${id}`);
628
+ const who = process.env.ELANOUS_TRACK;
629
+ if (who && who !== 'OP' && !ownerMatches(item.owner, parseOwner(who).seat)) throw new CliUserError('남의 칸 당기기 거부 — COO 에 요청');
630
+ if (!opts.dryRun && who !== 'OP') throw new CliUserError('판 배치는 COO 에 요청 — 자리는 이유와 함께 release checklist move 로 다음 판에만 이월');
631
+ if (!opts.dryRun && !mayWriteLedger('release place', false, scheduleLedgerRoot())) return;
632
+ const priority = opts.priority ?? item.priority;
633
+ if (!priority) throw new CliUserError(`우선순위가 없는 칸: ${id} — --priority P0|P1|P2 를 지정하라`);
634
+ const result = placeCell({ id, title: item.title, owner: item.owner, priority: priority as PlacementPriority, predecessors: item.predecessors ?? [], deadlineVersion: item.deadlineVersion, ceoMinutes: item.ceoMinutes, ceoDate: item.ceoDate }, { schedules: rows, dryRun: opts.dryRun, by: who ?? 'cli' });
635
+ console.log(`${opts.dryRun ? '· 드라이런' : '✅'} ${id} ${result.from ?? '-'} → ${result.version} · ${result.reason}${result.displaced.length ? ` · P2 이월 ${result.displaced.map((row) => row.id).join(', ')}` : ''}`);
636
+ });
637
+ release.command('rebalance').description('마감 2시간 전 미시작 칸을 다음 판으로')
638
+ .requiredOption('--version <v>', '대상 판').option('--dry-run', '이동 없이 보기')
639
+ .action((opts: { version: string; dryRun?: boolean }) => {
640
+ if (process.env.ELANOUS_TRACK !== 'OP' && !opts.dryRun) throw new CliUserError('판 이월은 COO 에 요청');
641
+ if (!opts.dryRun && !mayWriteLedger('release rebalance', false, scheduleLedgerRoot())) return;
642
+ const { decisions, blocked } = rebalance(opts.version, { schedules: listSchedules(scheduleLedgerRoot()), dryRun: opts.dryRun });
643
+ for (const row of decisions) console.log(`${opts.dryRun ? '· 드라이런' : '✅'} ${row.id} ${row.from} → ${row.version} · ${row.reason}`);
644
+ for (const row of blocked) console.log(`⛔ ${row.id} ${row.from} → ${row.to} 이월 거부 · ${row.reason}`);
645
+ if (!decisions.length && !blocked.length) console.log('이월할 미시작 칸 없음');
646
+ });
647
+ const ceoMinutes = (value: string): number => {
648
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value))) throw new CliUserError('대표 손 분량은 0 이상의 정수 분이어야 한다');
649
+ return Number(value);
650
+ };
566
651
  const checklist = release.command('checklist').description('판별 확인표 조회·갱신')
567
652
  .option('--version <v>', '판 또는 별칭(기본: package.json 의 개발판에서 -dev.N 제거)')
568
653
  .option('--json', '결과 JSON')
569
654
  .option('--hq-override', '본부 임대 거부를 관측하며 수동 우회');
570
- const mayWrite = (cmd: Command) => hqCliWriteAllowed(`release checklist ${cmd.name()}`, Boolean(cmd.optsWithGlobals().hqOverride), ledgerFenceDeps());
655
+ const mayWrite = (cmd: Command) => mayWriteLedger(`release checklist ${cmd.name()}`, Boolean(cmd.optsWithGlobals().hqOverride), releaseLedgerRoot());
571
656
  const context = (cmd: Command) => {
572
657
  const opts = { ...(cmd.parent?.parent?.opts() as { version?: string; json?: boolean }), ...(cmd.parent?.opts() as { version?: string; json?: boolean }), ...(cmd.opts() as { version?: string; json?: boolean }) };
573
658
  const codenames = checklistCodenames();
@@ -607,11 +692,14 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
607
692
  return;
608
693
  }
609
694
  if (opts.applyEvidence && !opts.dryRun && !mayWrite(cmd)) return;
610
- const since = new Date((landedDeps.now ?? (() => new Date()))().getTime() - 7 * 86_400_000).toISOString().slice(0, 10);
611
- const raw = must((landedDeps.run ?? defaultRunner)('gh', ['pr', 'list', '--state', 'merged', '--search', `merged:>=${since}`, '--limit', '400', '--json', 'number,title,body,mergedAt'], process.cwd()), '병합 PR 조회(gh)');
612
- const prs = JSON.parse(raw) as MergedChecklistPr[];
613
- if (!Array.isArray(prs)) throw new Error('병합 PR 조회(gh): 배열이 아니다');
614
695
  const snapshots = opts.open ? openSnapshots : [(landedDeps.checklist ?? listChecklist)(context(cmd).version)];
696
+ const now = (landedDeps.now ?? (() => new Date()))();
697
+ const updated = snapshots.flatMap(({ items }) => items
698
+ .filter((item) => (item.status === 'yellow' || item.status === 'red') && (opts.owner === undefined || ownerMatches(item.owner, opts.owner)))
699
+ .map((item) => Date.parse(item.updatedAt)).filter((at) => Number.isFinite(at)));
700
+ const since = updated.length ? new Date(updated.reduce((earliest, at) => Math.min(earliest, at))).toISOString().slice(0, 10)
701
+ : new Date(now.getTime() - 7 * 86_400_000).toISOString().slice(0, 10);
702
+ const { prs, truncated } = mergedChecklistPrs(since, now, landedDeps.run ?? defaultRunner);
615
703
  const results: Array<{ version: string; rows?: ReturnType<typeof landedButYellow>; plan?: ReturnType<typeof evidencePlan> }> = [];
616
704
  let totalAdded = 0, totalSkipped = 0;
617
705
  for (const snapshot of snapshots) {
@@ -643,30 +731,34 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
643
731
  if (!json) console.log(`판 ${snapshots.length} · 붙임 합 ${totalAdded} · 건너뜀(언급만) 합 ${totalSkipped}`);
644
732
  else await writeStdoutJson(`${JSON.stringify(results)}\n`);
645
733
  } else if (json) await writeStdoutJson(`${JSON.stringify(opts.applyEvidence || opts.dryRun ? results[0]!.plan : results[0]!.rows)}\n`);
646
- if (prs.length >= 400) console.error('⚠ 병합 PR 400개 상한: 목록이 잘렸을 수 있다');
734
+ if (truncated) console.error('⚠ 병합 PR 400개 상한: 목록이 잘렸을 수 있다');
647
735
  } catch (error) {
648
736
  console.error(`⛔ ${error instanceof Error ? error.message : String(error)}`);
649
737
  process.exitCode = 1;
650
738
  }
651
739
  });
652
740
  withContext(checklist.command('add <id> <title>').description('칸 추가')).option('--owner <owner>', '담당').option('--kind <kind>', 'screen = 다섯 화면 짝 칸 · 근거에 짝: PWA … · 데스크톱 … · 폴드 … · 아이폰 … · 아이패드 … 한 줄')
741
+ .option('--priority <priority>', 'P0|P1|P2').option('--deadline-version <v>', '마감 판').option('--predecessor <id...>', '선행 칸 id')
742
+ .option('--ceo-minutes <minutes>', '대표 손 분량(분)').option('--ceo-date <date>', '대표 손 날짜 YYYY-MM-DD (없으면 판 착지일 KST)')
653
743
  .option('--allow-duplicate-id', '다른 판에 같은 id 가 있어도 경고 후 추가')
654
- .action((id: string, title: string, opts: { owner?: string; kind?: string; allowDuplicateId?: boolean }, cmd: Command) => {
744
+ .action((id: string, title: string, opts: { owner?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[]; ceoMinutes?: string; ceoDate?: string; allowDuplicateId?: boolean }, cmd: Command) => {
655
745
  const { version, json } = context(cmd);
656
746
  if (!mayWrite(cmd)) return;
657
- const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}) }, { allowDuplicateId: opts.allowDuplicateId });
747
+ const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}), ...(opts.priority !== undefined ? { priority: opts.priority as PlacementPriority } : {}), ...(opts.deadlineVersion !== undefined ? { deadlineVersion: opts.deadlineVersion } : {}), ...(opts.predecessor !== undefined ? { predecessors: opts.predecessor } : {}), ...(opts.ceoMinutes !== undefined ? { ceoMinutes: ceoMinutes(opts.ceoMinutes) } : {}), ...(opts.ceoDate !== undefined ? { ceoDate: opts.ceoDate } : {}) }, { allowDuplicateId: opts.allowDuplicateId });
658
748
  if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 추가`);
659
749
  });
660
750
  withContext(checklist.command('set <id>').description('칸 상태·근거·담당·처분 갱신'))
661
751
  .option('--status <status>', 'green|yellow|red|done').option('--evidence <evidence>', '근거').option('--owner <owner>', '담당')
662
752
  .option('--disposition <disposition>', 'move|known-issue|block').option('--kind <kind>', 'screen = 다섯 화면 짝 칸 · 근거에 짝: PWA … · 데스크톱 … · 폴드 … · 아이폰 … · 아이패드 … 한 줄')
663
- .action((id: string, opts: { status?: string; evidence?: string; owner?: string; disposition?: string; kind?: string }, cmd: Command) => {
753
+ .option('--priority <priority>', 'P0|P1|P2').option('--deadline-version <v>', '마감 판').option('--predecessor <id...>', '선행 칸 id')
754
+ .option('--ceo-minutes <minutes>', '대표 손 분량(분)').option('--ceo-date <date>', '대표 손 날짜 YYYY-MM-DD')
755
+ .action((id: string, opts: { status?: string; evidence?: string; owner?: string; disposition?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[]; ceoMinutes?: string; ceoDate?: string }, cmd: Command) => {
664
756
  const { version, json } = context(cmd);
665
757
  if (opts.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(opts.status)) throw new CliUserError(`잘못된 상태: ${opts.status}`, 'green|yellow|red|done');
666
758
  if (opts.disposition !== undefined && !['move', 'known-issue', 'block'].includes(opts.disposition)) throw new CliUserError(`잘못된 처분: ${opts.disposition}`, 'move|known-issue|block');
667
- if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined && opts.disposition === undefined && opts.kind === undefined) throw new CliUserError('갱신할 칸을 지정하라', '--status · --evidence · --owner · --disposition · --kind 중 하나');
759
+ if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined && opts.disposition === undefined && opts.kind === undefined && opts.priority === undefined && opts.deadlineVersion === undefined && opts.predecessor === undefined && opts.ceoMinutes === undefined && opts.ceoDate === undefined) throw new CliUserError('갱신할 칸을 지정하라', '--status · --evidence · --owner · --disposition · --kind · --priority · --deadline-version · --predecessor · --ceo-minutes · --ceo-date 중 하나');
668
760
  if (!mayWrite(cmd)) return;
669
- const data = setItem(version, id, { ...(opts.status !== undefined ? { status: opts.status as ChecklistStatus } : {}), ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.disposition !== undefined ? { disposition: opts.disposition as ChecklistDisposition } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}) }, process.env.ELANOUS_TRACK || 'cli');
761
+ const data = setItem(version, id, { ...(opts.status !== undefined ? { status: opts.status as ChecklistStatus } : {}), ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.disposition !== undefined ? { disposition: opts.disposition as ChecklistDisposition } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}), ...(opts.priority !== undefined ? { priority: opts.priority as PlacementPriority } : {}), ...(opts.deadlineVersion !== undefined ? { deadlineVersion: opts.deadlineVersion } : {}), ...(opts.predecessor !== undefined ? { predecessors: opts.predecessor } : {}), ...(opts.ceoMinutes !== undefined ? { ceoMinutes: ceoMinutes(opts.ceoMinutes) } : {}), ...(opts.ceoDate !== undefined ? { ceoDate: opts.ceoDate } : {}) }, process.env.ELANOUS_TRACK || 'cli');
670
762
  const item = data.items.find((entry) => entry.id === id);
671
763
  const why = item?.kind === 'screen' && item.status === 'green' ? parityGap(item.evidence) : null;
672
764
  if (why) {
@@ -701,8 +793,15 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
701
793
  .action((id: string, opts: { from: string; to: string; reason?: string }, cmd: Command) => {
702
794
  const { codenames, json } = context(cmd);
703
795
  const from = checklistVersion(opts.from, codenames), to = checklistVersion(opts.to, codenames);
796
+ if (!opts.reason?.trim()) throw new CliUserError('이동 이유가 필요하다');
704
797
  if (!mayWrite(cmd)) return;
705
- features.move(id, from, to, actor(), undefined, undefined, opts.reason);
798
+ if (actor() === 'cli') {
799
+ // No seat identity (ELANOUS_TRACK unset): keep the plain move so seat sessions without the variable still work, but say so.
800
+ console.error('⚠ 자리 신원 없음(ELANOUS_TRACK 미설정) — 자리 규칙(자기 칸 · 다음 판 · 용량) 검사 없이 옮긴다');
801
+ debug.log('release.placement', 'anonymous-move', { id, from, to });
802
+ features.move(id, from, to, actor(), undefined, undefined, opts.reason);
803
+ } else if (actor() !== 'OP') seatMove(id, from, to, actor(), opts.reason, { schedules: listSchedules(scheduleLedgerRoot()) });
804
+ else features.move(id, from, to, actor(), undefined, undefined, opts.reason);
706
805
  if (json) console.log(JSON.stringify(listChecklist(to))); else console.log(`✅ ${id} ${from} → ${to}`);
707
806
  });
708
807
  withContext(checklist.command('retitle <id> <title>').description('칸 제목 수정')).action((id: string, title: string, _opts: unknown, cmd: Command) => {
@@ -807,42 +906,100 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
807
906
  .action(async (o: { version: string; base: string; pick: string[]; dryRun?: boolean; json?: boolean }) => {
808
907
  await jsonAction(o.json, async (log) => cutReleaseBranch({ ...o, log }), () => true);
809
908
  });
810
- release.command('run')
811
- .description('릴리스 루프 실행 — 원장 직전 판과 release.loop 설정으로 그래프 입력 구성 · --dry-run 은 입력만 출력')
812
- .requiredOption('--version <v>', '공개할 판(x.y.z)')
813
- .option('--cut-commit <sha>', 'origin/main HEAD 대신 컷으로 쓸 커밋')
814
- .option('--dry-run', '체크리스트·그래프 실행 없이 전체 입력 보기')
815
- .option('--if-ready', '준비되지 않았으면 사유를 출력하고 성공으로 건너뛴다')
816
- .option('--json', '결과 한 줄 JSON(stdout) · 사람 줄은 stderr')
817
- .action(async (o: { version: string; cutCommit?: string; dryRun?: boolean; ifReady?: boolean; json?: boolean }) => {
909
+ const runAction = async (o: { version: string; cutCommit?: string; dryRun?: boolean; ifReady?: boolean; forceFreeze?: boolean; json?: boolean }, logRun = console.log): Promise<boolean | AutoStartDeferral | undefined> => {
910
+ if (!o.dryRun) {
911
+ try {
912
+ const frozen = readLandingFreeze(releaseRunDeps.freezeRoot);
913
+ if (frozen) {
914
+ debug.log('release.run', o.forceFreeze ? 'freeze-forced' : 'frozen', { version: o.version, reason: frozen.reason, until: frozen.until });
915
+ if (!o.forceFreeze) {
916
+ // `--if-ready` (the scheduled path) defers quietly; a direct run fails — the release run is run again after `freeze off`.
917
+ if (o.ifReady) {
918
+ if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: true, skipped: true, reason: 'frozen', detail: landingFreezeMessage(frozen) })}\n`);
919
+ else logRun(`· ${landingFreezeMessage(frozen)} · 릴리스 루프 연기`);
920
+ } else {
921
+ if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, reason: 'frozen', error: landingFreezeMessage(frozen) })}\n`);
922
+ else console.error(`⛔ ${landingFreezeMessage(frozen)} · 릴리스 루프 거부(--force-freeze 로만 강행)`);
923
+ process.exitCode = 1;
924
+ }
925
+ return o.ifReady ? { deferred: true, reason: 'frozen' } : false;
926
+ }
927
+ }
928
+ } catch (e) {
929
+ if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, error: (e as Error).message })}\n`);
930
+ else console.error(`⛔ ${(e as Error).message}`);
931
+ process.exitCode = 1;
932
+ return false;
933
+ }
934
+ }
818
935
  if (o.ifReady && !o.dryRun) {
819
936
  try {
820
937
  const outcome = await runReleaseIfReady(o.version, {
821
938
  ledgerRoot: releaseRunDeps.ledgerRoot,
822
939
  readiness: (version, options) => releaseReadiness(version, { ledgerRoot: releaseRunDeps.ledgerRoot, checklist: releaseRunDeps.checklist, ...options }),
823
- run: () => runUnattendedRelease({ version: o.version, cutCommit: o.cutCommit }, releaseRunDeps),
940
+ run: () => runUnattendedRelease({ version: o.version, cutCommit: o.cutCommit, forceFreeze: o.forceFreeze }, releaseRunDeps),
824
941
  });
825
942
  if (outcome.skipped) {
826
943
  if (o.json) await writeStdoutJson(`${JSON.stringify(outcome)}\n`);
827
- else console.log(`· 준비 안 됨 ${o.version} · ${outcome.reason} · ${outcome.detail}`);
828
- return;
944
+ else logRun(`· 준비 안 됨 ${o.version} · ${outcome.reason} · ${outcome.detail}`);
945
+ return { deferred: true, reason: outcome.reason };
829
946
  }
830
947
  const result = outcome.result;
831
948
  const ok = result.dryRun || result.state?.status === 'done' || result.state?.status === 'awaiting-approval';
832
- (o.json ? console.error : console.log)(`${result.dryRun ? '· 드라이런' : '▶ 릴리스 루프'} ${result.input.version} · 입력 ${JSON.stringify(result.input)}`);
949
+ (o.json ? console.error : logRun)(`${result.dryRun ? '· 드라이런' : '▶ 릴리스 루프'} ${result.input.version} · 입력 ${JSON.stringify(result.input)}`);
833
950
  if (o.json) await writeStdoutJson(`${JSON.stringify({ ok, ...result })}\n`);
834
951
  if (!ok && !process.exitCode) process.exitCode = 1;
952
+ return ok;
835
953
  } catch (e) {
954
+ if (e instanceof LandingFrozenError) {
955
+ // A freeze switched on after the entry check still defers the scheduled run rather than failing it.
956
+ debug.log('release.run', 'frozen', { version: o.version, reason: e.freeze.reason, until: e.freeze.until, at: 'run-boundary' });
957
+ if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: true, skipped: true, reason: 'frozen', detail: e.message })}\n`);
958
+ else logRun(`· ${e.message} · 릴리스 루프 연기`);
959
+ return { deferred: true, reason: 'frozen' };
960
+ }
836
961
  if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, error: (e as Error).message })}\n`);
837
962
  else console.error(`⛔ ${(e as Error).message}`);
838
963
  process.exitCode = 1;
964
+ return false;
839
965
  }
840
- return;
841
966
  }
842
967
  await jsonAction(o.json, async (log) => {
843
- const result = await runUnattendedRelease({ version: o.version, dryRun: o.dryRun, cutCommit: o.cutCommit }, releaseRunDeps);
968
+ const result = await runUnattendedRelease({ version: o.version, dryRun: o.dryRun, cutCommit: o.cutCommit, forceFreeze: o.forceFreeze }, releaseRunDeps);
844
969
  log(`${result.dryRun ? '· 드라이런' : '▶ 릴리스 루프'} ${result.input.version} · 입력 ${JSON.stringify(result.input)}`);
845
970
  return result;
846
971
  }, (r) => r.dryRun || r.state?.status === 'done' || r.state?.status === 'awaiting-approval');
972
+ };
973
+ release.command('run')
974
+ .description('릴리스 루프 실행 — 원장 직전 판과 release.loop 설정으로 그래프 입력 구성 · --dry-run 은 입력만 출력')
975
+ .requiredOption('--version <v>', '공개할 판(x.y.z)')
976
+ .option('--cut-commit <sha>', 'origin/main HEAD 대신 컷으로 쓸 커밋')
977
+ .option('--dry-run', '체크리스트·그래프 실행 없이 전체 입력 보기')
978
+ .option('--if-ready', '준비되지 않았으면 사유를 출력하고 성공으로 건너뛴다')
979
+ .option('--force-freeze', '동결 중 게이트·발행 강행(관측 기록)')
980
+ .option('--json', '결과 한 줄 JSON(stdout) · 사람 줄은 stderr')
981
+ .action(async (o: { version: string; cutCommit?: string; dryRun?: boolean; ifReady?: boolean; forceFreeze?: boolean; json?: boolean }) => { await runAction(o); });
982
+ release.command('auto-start')
983
+ .description('판 일정의 컷 창에서 릴리스 시작을 미리 본다 · --apply 로 release run --if-ready 실행')
984
+ .option('--window-minutes <n>', '컷 뒤 선택 창(분)', '15')
985
+ .option('--apply', '선택한 판을 release run --if-ready 로 시작')
986
+ .option('--json', '결과 한 줄 JSON(stdout)')
987
+ .action(async (o: { windowMinutes: string; apply?: boolean; json?: boolean }) => {
988
+ await jsonAction(o.json, async (log) => {
989
+ const result = await autoStartScheduledRelease({
990
+ windowMinutes: Number(o.windowMinutes), apply: o.apply, ledgerRoot: scheduleLedgerRoot(),
991
+ readiness: (version, ledgerRoot, now) => releaseReadiness(version, { ledgerRoot, now: () => now, checklist: releaseRunDeps.checklist }),
992
+ launch: async (version) => {
993
+ const ran = await runAction({ version, ifReady: true, json: false }, log);
994
+ if (ran && typeof ran === 'object' && ran.deferred) return ran;
995
+ if (ran !== true) throw new Error(`release run 미시작 또는 실패: ${version}`);
996
+ },
997
+ });
998
+ if (result.status === 'planned') log(`· 미리 보기 ${result.version} · 컷 ${result.cutAt} · 실행하려면 --apply`);
999
+ else if (result.status === 'started') log(`▶ 릴리스 시작 ${result.version} · 컷 ${result.cutAt}`);
1000
+ else if (result.status === 'skipped') log(`· 릴리스 연기 ${result.version} · ${result.reason}`);
1001
+ else log('· 선택할 컷 없음');
1002
+ return result;
1003
+ }, () => true);
847
1004
  });
848
1005
  }
@@ -0,0 +1,91 @@
1
+ import type { Command } from 'commander';
2
+ import { readdirSync, readFileSync } from 'node:fs';
3
+ import { join, relative, resolve } from 'node:path';
4
+ import YAML from 'yaml';
5
+
6
+ export const RFC_STATUSES = ['unknown', 'proposed', 'adopted', 'completed', 'superseded', 'rejected'] as const;
7
+ export type RfcStatus = typeof RFC_STATUSES[number];
8
+ export interface RfcRow {
9
+ path: string;
10
+ status: RfcStatus;
11
+ owner: string | null;
12
+ card: string | null;
13
+ supersededBy: string | null;
14
+ handbook: string | null;
15
+ issue: string | null;
16
+ /** Legacy top-level `status:` / `owner:` values, verbatim: evidence for a human migration, never the lifecycle status. */
17
+ legacyStatus: string | null;
18
+ legacyOwner: string | null;
19
+ }
20
+
21
+ function rfcPaths(dir: string): string[] {
22
+ return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
23
+ const path = join(dir, entry.name);
24
+ if (entry.isDirectory()) return rfcPaths(path);
25
+ return entry.isFile() && /^RFC-.*\.md$/.test(entry.name) ? [path] : [];
26
+ });
27
+ }
28
+
29
+ function field(value: unknown): string | null {
30
+ return typeof value === 'string' && value.trim() && value.trim().toLowerCase() !== 'unknown' ? value.trim() : null;
31
+ }
32
+
33
+ /** Explicit lifecycle fields only: prose, old `status: rfc`, and file location do not establish adoption. */
34
+ export function readRfcRow(text: string, path: string): RfcRow {
35
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text);
36
+ let meta: Record<string, unknown> = {};
37
+ let issue: string | null = null;
38
+ let legacyStatus: string | null = null;
39
+ let legacyOwner: string | null = null;
40
+ if (!match) issue = 'lifecycle frontmatter missing';
41
+ else {
42
+ // Legacy RFC frontmatter can contain non-YAML prose. Parse only the new, top-level rfc_* declarations.
43
+ legacyStatus = /^status:\s*(.+)$/m.exec(match[1]!)?.[1]?.trim().replace(/^["']|["']$/g, '') || null;
44
+ legacyOwner = /^owner:\s*(.+)$/m.exec(match[1]!)?.[1]?.trim().replace(/^["']|["']$/g, '') || null;
45
+ const declarations = [...match[1]!.matchAll(/^(rfc_status|rfc_owner|rfc_card|rfc_superseded_by|rfc_handbook):\s*(.*)$/gm)];
46
+ for (const [, key, raw] of declarations) {
47
+ try { meta[key!] = YAML.parse(raw!); }
48
+ catch { issue = 'invalid lifecycle frontmatter'; }
49
+ }
50
+ }
51
+ const rawStatus = meta.rfc_status;
52
+ const status: RfcStatus = typeof rawStatus === 'string' && RFC_STATUSES.includes(rawStatus as RfcStatus)
53
+ ? rawStatus as RfcStatus : 'unknown';
54
+ if (rawStatus !== undefined && status === 'unknown' && rawStatus !== 'unknown') issue = 'invalid rfc_status';
55
+ return {
56
+ path, status, owner: field(meta.rfc_owner), card: field(meta.rfc_card),
57
+ supersededBy: field(meta.rfc_superseded_by), handbook: field(meta.rfc_handbook), issue, legacyStatus, legacyOwner,
58
+ };
59
+ }
60
+
61
+ export function scanRfcs(repoRoot: string): RfcRow[] {
62
+ const root = resolve(repoRoot);
63
+ return rfcPaths(join(root, 'docs')).map((file) => readRfcRow(readFileSync(file, 'utf8'), relative(root, file).replaceAll('\\', '/')))
64
+ .sort((a, b) => a.path.localeCompare(b.path, 'en'));
65
+ }
66
+
67
+ /** A missing declaration is not proof that a card does not exist; an adopted RFC with an explicit null is. */
68
+ export function adoptedWithoutCard(rows: readonly RfcRow[]): RfcRow[] {
69
+ return rows.filter((row) => row.status === 'adopted' && row.card === null);
70
+ }
71
+
72
+ export function runRfcStatus(options: { json?: boolean; missingCards?: boolean }, root = process.cwd(),
73
+ write: (text: string) => void = (text) => process.stdout.write(text)): void {
74
+ const all = scanRfcs(root);
75
+ const rows = options.missingCards ? adoptedWithoutCard(all) : all;
76
+ if (options.json) {
77
+ write(`${JSON.stringify({ total: all.length, adoptedWithoutCard: adoptedWithoutCard(all).length, rows }, null, 2)}\n`);
78
+ return;
79
+ }
80
+ write(`RFC ${all.length} · 채택·칸 없음 ${adoptedWithoutCard(all).length} · 표시 ${rows.length}\n`);
81
+ write('상태\t담당\t칸\t대체 문서\t핸드북\t옛 상태\t옛 담당\t경로\t메타 오류\n');
82
+ for (const row of rows) write(`${row.status}\t${row.owner ?? '모름'}\t${row.card ?? '모름'}\t${row.supersededBy ?? '모름'}\t${row.handbook ?? '모름'}\t${row.legacyStatus ?? '-'}\t${row.legacyOwner ?? '-'}\t${row.path}\t${row.issue ?? '-'}\n`);
83
+ }
84
+
85
+ export function registerRfcStatusCommand(docs: Command): void {
86
+ docs.command('rfc-status')
87
+ .description('전체 RFC 생애주기 표 (RFC frontmatter만 읽음)')
88
+ .option('--json', '기계가 읽을 JSON')
89
+ .option('--missing-cards', '채택됐으나 칸이 기록되지 않은 RFC만')
90
+ .action((options: { json?: boolean; missingCards?: boolean }) => runRfcStatus(options));
91
+ }
@@ -0,0 +1,52 @@
1
+ import type { Command } from 'commander';
2
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
3
+ import { hqCliWriteAllowed, type HqDeps } from '../hq/hq.js';
4
+ import { isIsolatedLedgerWriteRoot } from '../hq/ledger-write-target.js';
5
+ import { closeSeatRequests, listSeatRequests, type SeatRequestStatus } from '../seat-dispatch/seat-request-ledger.js';
6
+
7
+ function duration(value: string): number {
8
+ const match = /^(\d+)(ms|s|m|h|d)$/.exec(value);
9
+ if (!match) throw new Error(`invalid --older-than: ${value} (use e.g. 24h)`);
10
+ const ms = Number(match[1]) * ({ ms: 1, s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 }[match[2]! as 'ms' | 's' | 'm' | 'h' | 'd']);
11
+ if (!Number.isSafeInteger(ms)) throw new Error(`invalid --older-than: ${value}`);
12
+ return ms;
13
+ }
14
+
15
+ export function registerSeatRequestsCommands(seat: Command, deps: { root?: () => string; hq?: HqDeps; now?: () => Date } = {}): void {
16
+ const requests = seat.command('requests').description('자리 요청 원장 조회·닫기');
17
+ requests.command('list').description('키별 최신 요청 상태 조회')
18
+ .option('--seat <seat>', '자리 ID')
19
+ .option('--status <status>', 'pending|queued|rejected|done')
20
+ .option('--json', 'JSON 출력')
21
+ .action((opts: { seat?: string; status?: string; json?: boolean }) => {
22
+ try {
23
+ if (opts.status && !['pending', 'queued', 'rejected', 'done'].includes(opts.status)) throw new Error(`invalid status: ${opts.status}`);
24
+ const rows = listSeatRequests((deps.root ?? effectiveInstanceRoot)(), { seat: opts.seat, status: opts.status as SeatRequestStatus | undefined });
25
+ if (opts.json) console.log(JSON.stringify(rows));
26
+ else for (const row of rows) console.log(`${row.key} ${row.seat} ${row.status} ${row.queuedAt} ${row.text}`);
27
+ } catch (error) { console.error(`seat requests list: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
28
+ });
29
+ requests.command('close <keys...>').description('열린 요청을 사유와 함께 종료 (원장에 덧붙임)')
30
+ .requiredOption('--reason <reason>', '닫는 이유')
31
+ .option('--status <status>', 'rejected|done', 'rejected')
32
+ .option('--older-than <duration>', '최소 대기 시간 (예: 24h)')
33
+ .option('--dry-run', '쓰기 없이 대상만 미리 보기')
34
+ .action((keys: string[], opts: { reason: string; status: string; olderThan?: string; dryRun?: boolean }) => {
35
+ try {
36
+ if (!opts.reason.trim()) throw new Error('reason must not be empty');
37
+ if (opts.status !== 'rejected' && opts.status !== 'done') throw new Error(`invalid close status: ${opts.status}`);
38
+ const olderThan = opts.olderThan === undefined ? undefined : duration(opts.olderThan);
39
+ const root = (deps.root ?? effectiveInstanceRoot)();
40
+ const known = new Set(listSeatRequests(root).map((row) => row.key));
41
+ const missing = keys.find((key) => !known.has(key));
42
+ if (missing) throw new Error(`unknown key: ${missing}`);
43
+ if (!opts.dryRun && !isIsolatedLedgerWriteRoot(root) && !hqCliWriteAllowed('seat requests close', false, deps.hq)) return;
44
+ const rows = closeSeatRequests(root, keys, {
45
+ reason: opts.reason, status: opts.status,
46
+ ...(olderThan === undefined ? {} : { olderThan }),
47
+ ...(deps.now ? { now: deps.now() } : {}), dryRun: opts.dryRun,
48
+ });
49
+ for (const row of rows) console.log(`${opts.dryRun ? 'would close' : 'closed'} ${row.key} ${row.status}: ${row.reason}`);
50
+ } catch (error) { console.error(`seat requests close: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
51
+ });
52
+ }
@@ -68,8 +68,8 @@ export function parseLinearWebhook(body: unknown, deliveryId: string, ledger?: E
68
68
  return isLinearProjectionEcho(event, ledger ?? new EventLedger()) ? null : event;
69
69
  }
70
70
 
71
- export async function fetchLinearIssues({ apiKey, teamKey, labelOrPrefix, since, fetch: fetchFn = fetch, ledger }: {
72
- apiKey: string; teamKey: string; labelOrPrefix?: string; since?: string; fetch?: typeof fetch; ledger?: EventLedger;
71
+ export async function fetchLinearIssues({ apiKey, teamKey, labelOrPrefix, excludeLabel, since, fetch: fetchFn = fetch, ledger }: {
72
+ apiKey: string; teamKey: string; labelOrPrefix?: string; excludeLabel?: string; since?: string; fetch?: typeof fetch; ledger?: EventLedger;
73
73
  }): Promise<ExternalTaskEvent[]> {
74
74
  const query = `query ConnectorIssues($teamKey: String!, $after: String) {
75
75
  issues(filter: { team: { key: { eq: $teamKey } } }, first: 100, after: $after) {
@@ -106,8 +106,10 @@ export async function fetchLinearIssues({ apiKey, teamKey, labelOrPrefix, since,
106
106
  const issue = asIssue(node);
107
107
  if (!issue || (since && issue.updatedAt < since) ||
108
108
  ['completed', 'canceled', 'duplicate'].includes((node.state as { type?: string } | undefined)?.type ?? '')) continue;
109
+ const labels = (node.labels as { nodes?: Array<{ name: string }> } | undefined)?.nodes;
110
+ if (excludeLabel && labels?.some(label => label.name.toLowerCase() === excludeLabel.toLowerCase())) continue;
109
111
  if (labelOrPrefix && !issue.title.startsWith(labelOrPrefix) &&
110
- !(node.labels as { nodes?: Array<{ name: string }> } | undefined)?.nodes?.some(label => label.name === labelOrPrefix)) continue;
112
+ !labels?.some(label => label.name === labelOrPrefix)) continue;
111
113
  const event: ExternalTaskEvent = { provider: 'linear', eventId: `${issue.id}:${issue.updatedAt}`, kind: 'updated',
112
114
  ref: issue.id, identifier: issue.identifier, title: issue.title, body: issue.description ?? '',
113
115
  url: issue.url ?? '', priority: priorityOf(issue.priority), occurredAt: issue.updatedAt };
@@ -1,32 +1,22 @@
1
- import { Database } from 'bun:sqlite';
2
- import { closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, openSync } from 'node:fs';
1
+ import { closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync } from 'node:fs';
3
2
  import { dlopen, FFIType } from 'bun:ffi';
4
3
  import { join } from 'node:path';
5
4
  import { listMemories, memoryRoot, saveMemory, type MemoryEntry } from '../memory.js';
6
- import { surfaceEventsDbPath } from '../domains/surface-events.js';
7
5
  import { DecisionLedger } from '../decisions/decision-ledger.js';
8
- import { listCoordEvents } from './coord-events.js';
9
- import { condenseContextWindowWithReport, type CondenseSkipped, type MemoryDeps, type MemoryItem, type MemorySource, type MemorySummary } from './long-term-memory.js';
6
+ import { condenseContextWindowWithReport, readCondenseEvents, type CondenseFunnel, type CondenseSkipped, type MemoryDeps, type MemoryItem, type MemorySource, type MemorySummary } from './long-term-memory.js';
10
7
 
11
8
  const summarize = async (source: MemorySource): Promise<MemorySummary> =>
12
9
  (await import('./nightly-condense.js')).summarize(source);
13
10
 
14
- /** Reuse the context-bus reader against a strictly read-only SQLite handle. */
15
- function readContextEvents(since: string) {
16
- const path = surfaceEventsDbPath();
17
- if (!existsSync(path)) return [];
18
- const db = new Database(path, { readonly: true, strict: true });
19
- try { return listCoordEvents({ since }, { db }); }
20
- finally { db.close(); }
21
- }
22
-
23
11
  export interface CondenseReport {
24
12
  since: string;
25
13
  until: string;
26
14
  cards: MemoryItem[];
27
15
  conflicts: number;
28
16
  skipped: CondenseSkipped;
17
+ funnel: CondenseFunnel;
29
18
  folded: number;
19
+ droppedHarnessChild: number;
30
20
  markdown: string;
31
21
  applied?: { written: number; retired: number; decisions: string[] };
32
22
  }
@@ -35,12 +25,17 @@ function mdCell(value: string): string {
35
25
  return value.replaceAll('|', '\\|').replaceAll('\n', ' ');
36
26
  }
37
27
 
38
- export function renderCondenseMarkdown(report: Pick<CondenseReport, 'since' | 'until' | 'cards' | 'conflicts' | 'skipped'>): string {
28
+ export function renderCondenseMarkdown(report: Pick<CondenseReport, 'since' | 'until' | 'cards' | 'conflicts' | 'skipped' | 'droppedHarnessChild' | 'funnel'>): string {
29
+ const seats = (counts: Record<string, number>) => ['OP', 'TC', 'MK', 'UX', ...Object.keys(counts).filter(seat => !['OP', 'TC', 'MK', 'UX'].includes(seat)).sort()]
30
+ .map(seat => `${seat} ${counts[seat] ?? 0}`).join(' · ');
31
+ const stage = (name: string, value: { total: number; bySeat: Record<string, number> }) =>
32
+ `${name} ${value.total} (${seats(value.bySeat)})`;
39
33
  const lines = [
40
34
  '# Long-term context memory candidates',
41
35
  '',
42
36
  `Window: ${report.since} → ${report.until} · Cards: ${report.cards.length} · Conflicts: ${report.conflicts}`,
43
- `Skipped: ${JSON.stringify(report.skipped)}`,
37
+ `Funnel: ${stage('read', report.funnel.read)} → ${stage('sources', report.funnel.sources)} → ${stage('summarized', report.funnel.summarized)} → ${stage('cards', report.funnel.cards)}`,
38
+ `Skipped: ${JSON.stringify(report.skipped)} · Dropped harness-child: ${report.droppedHarnessChild}`,
44
39
  '',
45
40
  ];
46
41
  for (const card of report.cards) {
@@ -180,15 +175,15 @@ export async function runContextCondense(options: {
180
175
  const now = options.now ?? new Date();
181
176
  const until = now.toISOString();
182
177
  const since = new Date(now.getTime() - options.hours * 3_600_000).toISOString();
183
- const { cards, skipped, folded } = await condenseContextWindowWithReport(since, until, [], {
178
+ const { cards, skipped, folded, droppedHarnessChild, funnel } = await condenseContextWindowWithReport(since, until, [], {
184
179
  summarize: deps.summarize,
185
- events: deps.events ?? readContextEvents,
180
+ events: deps.events ?? readCondenseEvents,
186
181
  decisions: deps.decisions ?? (() => new DecisionLedger(options.root ? { stateDir: options.root } : {}).list({ status: 'all' })
187
182
  .filter(decision => decision.raisedBy.agent !== 'context-condense')),
188
183
  }, now);
189
184
  const conflicts = cards.filter(card => card.conflict !== undefined).length;
190
- const report: CondenseReport = { since, until, cards, conflicts, skipped, folded,
191
- markdown: renderCondenseMarkdown({ since, until, cards, conflicts, skipped }) };
185
+ const report: CondenseReport = { since, until, cards, conflicts, skipped, folded, droppedHarnessChild, funnel,
186
+ markdown: renderCondenseMarkdown({ since, until, cards, conflicts, skipped, droppedHarnessChild, funnel }) };
192
187
  if (options.apply === true) report.applied = applyCondensedCards(cards, store ?? defaultStore(options.root));
193
188
  return report;
194
189
  }