elanous 0.2.12 → 0.2.14

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 (534) 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/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1307-6e7e0377c1db0bdc.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
  9. package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.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-2df489622a52a33a.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/{9906-482c6619b3fdda20.js → 8060-cc008cc6fedfa818.js} +1 -1
  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/9070.067c106528ec1ae6.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-7b2b4f378354869a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/bots/page-1512d91c7491ed62.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/chat/page-a627db94ec6904a0.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/control/page-e6b84ab07d3567d5.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/editor/page-fed69f9759d248d0.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/exec/{page-a67e11ba962337b5.js → page-15374909d8f1aebd.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/app/field/page-012a742f81218b01.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/intake/page-99effd9d893a43dc.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/layout-938de57ae0315e46.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/loops/page-160089123a40dd91.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/market/page-629e1ede482ec6eb.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-793fa536b3a6ef37.js → page-2ce5471926761fdd.js} +1 -1
  69. package/apps/pwa/out/_next/static/chunks/app/morning/page-27e99b4e54a849d4.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-32cde713be47cddb.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e7e10cd3386222e5.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e64caff084f7ce21.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-db07eaffba0df7e3.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/settings/page-86f91034bd5cabe5.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/share/page-ce4d8a1215ce53a3.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/showroom/page-58043db78ba519ae.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/term/{page-e297866051595d81.js → page-274703bc41b71333.js} +1 -1
  89. package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/vault/page-3b7e821dc492bdfd.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2f810b18d89ecba6.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
  97. package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  98. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
  104. package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +1 -0
  105. package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +1 -0
  106. package/apps/pwa/out/approvals/index.html +1 -1
  107. package/apps/pwa/out/approvals/index.txt +23 -23
  108. package/apps/pwa/out/autopilot/index.html +1 -1
  109. package/apps/pwa/out/autopilot/index.txt +23 -23
  110. package/apps/pwa/out/board/index.html +1 -1
  111. package/apps/pwa/out/board/index.txt +23 -23
  112. package/apps/pwa/out/botlab/index.html +1 -1
  113. package/apps/pwa/out/botlab/index.txt +22 -22
  114. package/apps/pwa/out/bots/index.html +1 -1
  115. package/apps/pwa/out/bots/index.txt +22 -22
  116. package/apps/pwa/out/chat/index.html +1 -1
  117. package/apps/pwa/out/chat/index.txt +22 -22
  118. package/apps/pwa/out/consult/index.html +1 -1
  119. package/apps/pwa/out/consult/index.txt +23 -23
  120. package/apps/pwa/out/control/index.html +1 -1
  121. package/apps/pwa/out/control/index.txt +23 -23
  122. package/apps/pwa/out/design-check/index.html +1 -1
  123. package/apps/pwa/out/design-check/index.txt +23 -23
  124. package/apps/pwa/out/editor/index.html +1 -1
  125. package/apps/pwa/out/editor/index.txt +23 -23
  126. package/apps/pwa/out/exec/index.html +1 -1
  127. package/apps/pwa/out/exec/index.txt +23 -23
  128. package/apps/pwa/out/field/index.html +1 -1
  129. package/apps/pwa/out/field/index.txt +23 -23
  130. package/apps/pwa/out/healthz +1 -1
  131. package/apps/pwa/out/index.html +1 -1
  132. package/apps/pwa/out/index.txt +22 -22
  133. package/apps/pwa/out/inside/index.html +1 -1
  134. package/apps/pwa/out/inside/index.txt +22 -22
  135. package/apps/pwa/out/intake/index.html +1 -1
  136. package/apps/pwa/out/intake/index.txt +23 -23
  137. package/apps/pwa/out/live/index.html +1 -1
  138. package/apps/pwa/out/live/index.txt +23 -23
  139. package/apps/pwa/out/loops/index.html +1 -0
  140. package/apps/pwa/out/loops/index.txt +30 -0
  141. package/apps/pwa/out/market/index.html +1 -1
  142. package/apps/pwa/out/market/index.txt +22 -22
  143. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  144. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  145. package/apps/pwa/out/morning/index.html +1 -1
  146. package/apps/pwa/out/morning/index.txt +23 -23
  147. package/apps/pwa/out/observatory/index.html +1 -1
  148. package/apps/pwa/out/observatory/index.txt +23 -23
  149. package/apps/pwa/out/ops/checklist/index.html +1 -1
  150. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  151. package/apps/pwa/out/ops/release/index.html +1 -1
  152. package/apps/pwa/out/ops/release/index.txt +22 -22
  153. package/apps/pwa/out/ops/seats/index.html +1 -1
  154. package/apps/pwa/out/ops/seats/index.txt +22 -22
  155. package/apps/pwa/out/outputs/index.html +1 -1
  156. package/apps/pwa/out/outputs/index.txt +22 -22
  157. package/apps/pwa/out/reflection/index.html +1 -1
  158. package/apps/pwa/out/reflection/index.txt +23 -23
  159. package/apps/pwa/out/scheduler/index.html +1 -1
  160. package/apps/pwa/out/scheduler/index.txt +23 -23
  161. package/apps/pwa/out/sessions/index.html +1 -1
  162. package/apps/pwa/out/sessions/index.txt +22 -22
  163. package/apps/pwa/out/settings/devices/index.html +1 -1
  164. package/apps/pwa/out/settings/devices/index.txt +23 -23
  165. package/apps/pwa/out/settings/index.html +1 -1
  166. package/apps/pwa/out/settings/index.txt +23 -23
  167. package/apps/pwa/out/setup/done/index.html +1 -1
  168. package/apps/pwa/out/setup/done/index.txt +23 -23
  169. package/apps/pwa/out/setup/index.html +1 -1
  170. package/apps/pwa/out/setup/index.txt +23 -23
  171. package/apps/pwa/out/share/index.html +1 -1
  172. package/apps/pwa/out/share/index.txt +23 -23
  173. package/apps/pwa/out/showroom/index.html +1 -1
  174. package/apps/pwa/out/showroom/index.txt +22 -22
  175. package/apps/pwa/out/tasks/index.html +1 -1
  176. package/apps/pwa/out/tasks/index.txt +23 -23
  177. package/apps/pwa/out/term/index.html +1 -1
  178. package/apps/pwa/out/term/index.txt +23 -23
  179. package/apps/pwa/out/today/index.html +1 -1
  180. package/apps/pwa/out/today/index.txt +23 -23
  181. package/apps/pwa/out/trace/index.html +1 -1
  182. package/apps/pwa/out/trace/index.txt +23 -23
  183. package/apps/pwa/out/vault/index.html +1 -1
  184. package/apps/pwa/out/vault/index.txt +23 -23
  185. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  186. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  187. package/apps/pwa/out/workflows/index.html +1 -1
  188. package/apps/pwa/out/workflows/index.txt +23 -23
  189. package/apps/pwa/out/workspace/index.html +1 -1
  190. package/apps/pwa/out/workspace/index.txt +23 -23
  191. package/apps/pwa/out/worktrees/index.html +1 -1
  192. package/apps/pwa/out/worktrees/index.txt +23 -23
  193. package/examples/teams/personas/backend.yaml +4 -0
  194. package/examples/teams/personas/pm.yaml +4 -0
  195. package/examples/teams/personas/qa.yaml +4 -0
  196. package/examples/teams/small-product.yaml +20 -0
  197. package/graphs/lecture/lecture-note.yaml +28 -0
  198. package/graphs/lecture/recipes.yaml +17 -0
  199. package/graphs/orchestrator/orchestrator.yaml +59 -0
  200. package/graphs/orchestrator/recipes.yaml +18 -0
  201. package/graphs/release/recipes.yaml +3 -0
  202. package/graphs/release/release-loop.yaml +4 -1
  203. package/graphs/rhythm/daily.yaml +38 -0
  204. package/graphs/rhythm/recipes.yaml +18 -0
  205. package/graphs/rhythm/weekly.yaml +39 -0
  206. package/graphs/video/recipes.yaml +3 -0
  207. package/graphs/video/youtube-summary.yaml +17 -0
  208. package/package.json +2 -1
  209. package/scripts/audit-test-state-writes.ts +72 -17
  210. package/scripts/brand/check.ts +186 -0
  211. package/scripts/context-hooks/emit.ts +36 -14
  212. package/scripts/coord-channel-watch.sh +1 -0
  213. package/scripts/coord-post.sh +11 -1
  214. package/scripts/discord-seat-probe.ts +144 -0
  215. package/scripts/docs-cli-check.ts +23 -6
  216. package/scripts/expose-rubric-check.ts +118 -0
  217. package/scripts/expose-rubric-suggest.ts +163 -0
  218. package/scripts/heal/run-role.ts +14 -0
  219. package/scripts/hq/hq-standby.sh +65 -0
  220. package/scripts/hq/standby-promote.ts +173 -0
  221. package/scripts/hq/standby-role.ts +59 -0
  222. package/scripts/hq/standby-snapshot.ts +226 -0
  223. package/scripts/hq/standby-verify.ts +223 -0
  224. package/scripts/lecture-note/node.ts +256 -0
  225. package/scripts/lessons/handbook-page.ts +85 -0
  226. package/scripts/release-loop/gate-node.ts +62 -15
  227. package/scripts/release-loop/ops-upgrade-node.ts +17 -5
  228. package/scripts/release-loop/publish-node.ts +14 -1
  229. package/scripts/release-loop/unattended-release.ts +23 -1
  230. package/scripts/release-loop/vault-note-node.ts +196 -0
  231. package/scripts/release-story/draft.ts +172 -0
  232. package/scripts/rhythm/daily.ts +383 -0
  233. package/scripts/rhythm/weekly.ts +294 -0
  234. package/scripts/skills/public-check.ts +169 -0
  235. package/scripts/test-deterministic.ts +57 -11
  236. package/scripts/test-diet/effectiveness.ts +195 -0
  237. package/scripts/test-diet/lib.ts +16 -8
  238. package/scripts/test-diet/node.ts +77 -9
  239. package/scripts/ux/demo-rehearsal.ts +2 -2
  240. package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
  241. package/scripts/video/youtube-summary.ts +133 -0
  242. package/skills/diagram-master/SKILL.md +42 -49
  243. package/skills/diagram-master/references/pyproject.toml +4 -1
  244. package/skills/diagram-master/references/routing-guide.md +4 -2
  245. package/skills/grill-me/SKILL.md +1 -0
  246. package/skills/omni-crawl/SKILL.md +16 -16
  247. package/skills/omni-crawl/package-lock.json +1 -544
  248. package/skills/omni-crawl/package.json +3 -4
  249. package/skills/omni-crawl/scripts/main.ts +13 -13
  250. package/skills/omni-crawl/scripts/monitor.ts +8 -8
  251. package/skills/omni-crawl/src/apify.ts +13 -2
  252. package/skills/omni-crawl/src/capture.ts +6 -0
  253. package/skills/omni-crawl/src/registry.ts +2 -2
  254. package/skills/omni-crawl/src/stdout-json.ts +4 -0
  255. package/skills/omni-digest/SKILL.md +10 -11
  256. package/skills/omni-digest/package-lock.json +0 -545
  257. package/skills/omni-digest/package.json +2 -5
  258. package/skills/omni-digest/scripts/main.ts +3 -3
  259. package/skills/omni-digest/src/delegate.ts +5 -5
  260. package/skills/omni-digest/src/fetch.ts +2 -2
  261. package/skills/omni-digest/src/media.ts +25 -11
  262. package/skills/omni-digest/src/visual.ts +3 -2
  263. package/skills/omni-market/scripts/main.ts +1 -1
  264. package/skills/omni-market/scripts/stdout-json.ts +4 -0
  265. package/skills/youtube-master/SKILL.md +13 -13
  266. package/skills/youtube-master/package-lock.json +0 -545
  267. package/skills/youtube-master/package.json +2 -5
  268. package/skills/youtube-master/scripts/main.ts +2 -2
  269. package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
  270. package/skills/youtube-master/src/util.ts +30 -7
  271. package/src/agent/identity-env.ts +11 -0
  272. package/src/agent-mission/driver.ts +15 -10
  273. package/src/agent-mission/duo.ts +94 -0
  274. package/src/agent-mission/pty-decision.ts +1 -1
  275. package/src/autopilot/build/llm-conflict-merge.ts +45 -8
  276. package/src/autopilot/build/merge-intent.ts +53 -0
  277. package/src/autopilot/track-agent-shadow.ts +2 -1
  278. package/src/autopilot/track-agent.ts +4 -1
  279. package/src/boot/daemon-prompt-request.ts +3 -0
  280. package/src/boot/daemon-prompt-turn.ts +6 -2
  281. package/src/boot/daemon-runtime.ts +48 -16
  282. package/src/chat/index.ts +2 -0
  283. package/src/claims/claims-ledger.ts +232 -0
  284. package/src/claims/claims-render.ts +157 -0
  285. package/src/cli/claims-cli.ts +94 -0
  286. package/src/cli/decisions-cli.ts +84 -20
  287. package/src/cli/docs-cli.ts +2 -0
  288. package/src/cli/flow-cli.ts +11 -0
  289. package/src/cli/freeze-cli.ts +45 -0
  290. package/src/cli/lesson-cli.ts +108 -0
  291. package/src/cli/logs-cli.ts +104 -2
  292. package/src/cli/logs-model-input.ts +131 -0
  293. package/src/cli/outputs-cli.ts +92 -0
  294. package/src/cli/persona-cli.ts +29 -0
  295. package/src/cli/pod-cli.ts +10 -3
  296. package/src/cli/pr-cli.ts +44 -2
  297. package/src/cli/private-ledger-commands.ts +54 -4
  298. package/src/cli/pty-drive-verdict.ts +11 -2
  299. package/src/cli/release-cli.ts +248 -45
  300. package/src/cli/rfc-status.ts +91 -0
  301. package/src/connectors/linear.ts +5 -3
  302. package/src/context-bus/condense.ts +189 -0
  303. package/src/context-bus/context-day.ts +82 -0
  304. package/src/context-bus/context-now-public.ts +86 -0
  305. package/src/context-bus/context-now-surfaces.ts +70 -4
  306. package/src/context-bus/context-now.ts +4 -3
  307. package/src/context-bus/long-term-memory.ts +201 -24
  308. package/src/context-bus/nightly-condense.ts +1 -1
  309. package/src/dashboard/index.ts +75 -22
  310. package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
  311. package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
  312. package/src/dashboard/slash-runtime/loops-table.ts +77 -0
  313. package/src/decisions/decision-cards.ts +13 -7
  314. package/src/decisions/decision-ledger.ts +83 -18
  315. package/src/discord/sprint21-runtime.ts +16 -0
  316. package/src/discord-self-message.ts +8 -0
  317. package/src/discord-slash-wire.ts +31 -2
  318. package/src/domains/outbound-alert.ts +1 -1
  319. package/src/elanous-config-dir.ts +29 -4
  320. package/src/execution-loop/launch-gate.ts +29 -14
  321. package/src/flow/cards.ts +239 -0
  322. package/src/graph-kinds/registry.ts +2 -2
  323. package/src/graph-runner/graph-approval-card.ts +2 -2
  324. package/src/graph-runner/graph-grow.ts +64 -11
  325. package/src/graph-runner/graph-tick.ts +13 -2
  326. package/src/graph-runner/runner.ts +111 -22
  327. package/src/harness/harness-cli-command.ts +178 -27
  328. package/src/harness/harness-incidents.ts +43 -12
  329. package/src/harness/harness-pod-dispatch.ts +2 -0
  330. package/src/harness/harness-queue-child.ts +37 -0
  331. package/src/harness/harness-queue.ts +494 -0
  332. package/src/harness/helper-scan.ts +139 -0
  333. package/src/harness/model-input-observation.ts +37 -0
  334. package/src/hooks/github-shadow.ts +72 -2
  335. package/src/hooks/receiver.ts +4 -2
  336. package/src/hq/hq.ts +318 -0
  337. package/src/hq/lease.ts +210 -0
  338. package/src/hq/ledger-write-target.ts +41 -0
  339. package/src/hq/promote.ts +301 -0
  340. package/src/index.ts +281 -27
  341. package/src/intake-plane/discord-seat-work.ts +13 -6
  342. package/src/intake-plane/linear-wish.ts +72 -0
  343. package/src/intake-plane/persona-answer.ts +5 -0
  344. package/src/intake-plane/telegram-seat-work.ts +13 -6
  345. package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
  346. package/src/intake-plane/wish-card.ts +53 -2
  347. package/src/intake-plane/wish-folder.ts +5 -2
  348. package/src/intake-plane/wish-reply.ts +72 -0
  349. package/src/lessons/lesson-import.ts +106 -0
  350. package/src/lessons/lesson-ledger.ts +206 -0
  351. package/src/loops/activity.ts +203 -0
  352. package/src/loops/budget.ts +32 -0
  353. package/src/loops/checker.ts +180 -0
  354. package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
  355. package/src/loops/loop-cli.ts +72 -6
  356. package/src/loops/neighbors.ts +40 -0
  357. package/src/loops/observe.ts +59 -0
  358. package/src/loops/orchestrator/author-depth.ts +164 -0
  359. package/src/loops/orchestrator/author-input-check.ts +77 -0
  360. package/src/loops/orchestrator/author-ledger.ts +230 -0
  361. package/src/loops/orchestrator/finish-rate.ts +175 -0
  362. package/src/loops/orchestrator/overlap.ts +180 -0
  363. package/src/loops/orchestrator/resync-shadow.ts +227 -0
  364. package/src/loops/orchestrator/tick.ts +302 -0
  365. package/src/loops/orchestrator/traffic.ts +165 -0
  366. package/src/loops/package/verify.ts +125 -0
  367. package/src/loops/registry.ts +454 -9
  368. package/src/loops/status-rows.ts +118 -0
  369. package/src/loops/verdict.ts +34 -0
  370. package/src/maturity/bot-slash-catalog.ts +66 -0
  371. package/src/maturity/feature-maturity.ts +8 -5
  372. package/src/memory.ts +37 -4
  373. package/src/nexus/api/card-followup-route.ts +118 -0
  374. package/src/nexus/api/dashboard.ts +6 -1
  375. package/src/nexus/api/decisions-route.ts +68 -0
  376. package/src/nexus/api/http-server.ts +65 -2
  377. package/src/nexus/api/merge-approvals-gate.ts +1 -1
  378. package/src/nexus/api/personas.ts +2 -1
  379. package/src/nexus/api/schedules-read.ts +5 -1
  380. package/src/nexus/api/skill-exec-route.ts +97 -0
  381. package/src/nexus/api/task-cards-api.ts +44 -5
  382. package/src/nexus/api/workflows.ts +2 -1
  383. package/src/persona/loader.ts +18 -0
  384. package/src/persona/persona-todo.ts +14 -9
  385. package/src/persona/presets.ts +2 -1
  386. package/src/persona/team.ts +105 -0
  387. package/src/persona/types.ts +4 -0
  388. package/src/pod-lease/host-lease.ts +130 -20
  389. package/src/release-loop/auto-start.ts +48 -0
  390. package/src/release-loop/checklist.ts +13 -4
  391. package/src/release-loop/feature-store.ts +28 -16
  392. package/src/release-loop/landed-but-yellow.ts +12 -0
  393. package/src/release-loop/landing-freeze.ts +121 -0
  394. package/src/release-loop/placement.ts +242 -0
  395. package/src/release-loop/release-schedule.ts +3 -1
  396. package/src/seat-dispatch/ceo-commands.ts +2 -2
  397. package/src/seat-dispatch/seat-ask.ts +73 -15
  398. package/src/seat-dispatch/seat-questions.ts +58 -0
  399. package/src/seat-loop/seat-loop.ts +495 -46
  400. package/src/seat-loop/seat-pack.ts +127 -0
  401. package/src/self-dev/ask-launch-flow.ts +3 -0
  402. package/src/self-dev/dev-pipeline.ts +6 -2
  403. package/src/self-dev/draft-sweep.ts +76 -0
  404. package/src/self-dev/run-store.ts +73 -1
  405. package/src/self-dev/run-supervisor.ts +8 -0
  406. package/src/self-dev/self-orchestrate-runtime.ts +5 -1
  407. package/src/self-implement/budget-gate.ts +29 -0
  408. package/src/self-implement/expose-gate.ts +47 -0
  409. package/src/self-implement/frozen-merges.ts +294 -0
  410. package/src/self-implement/gate-cli.ts +10 -4
  411. package/src/self-implement/gate-scope.ts +46 -9
  412. package/src/self-implement/goal-archive.ts +222 -0
  413. package/src/self-implement/goal-lookup.ts +147 -0
  414. package/src/self-implement/heal-intake.ts +79 -0
  415. package/src/self-implement/host-regate.ts +25 -2
  416. package/src/self-implement/l8-merge-queue.ts +157 -1
  417. package/src/self-implement/orchestrator.ts +114 -10
  418. package/src/self-implement/revert-guard.ts +62 -0
  419. package/src/self-implement/run-ledger.ts +1 -1
  420. package/src/self-implement/seams.ts +28 -8
  421. package/src/session/index.ts +2 -0
  422. package/src/steward/launch.ts +5 -1
  423. package/src/steward/readiness.ts +125 -0
  424. package/src/steward/rescue.ts +75 -0
  425. package/src/steward/triage.ts +48 -9
  426. package/src/task-cards/card-cli.ts +86 -0
  427. package/src/task-cards/card-store.ts +10 -6
  428. package/src/task-orchestrator/surfaces/pod-lease.ts +23 -8
  429. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
  430. package/src/task-orchestrator/surfaces/pod-pool.ts +27 -10
  431. package/src/task-orchestrator/surfaces/self-implement-pod.ts +112 -18
  432. package/src/telegram-commands.ts +27 -3
  433. package/src/telegram-dispatch-forward.ts +1 -1
  434. package/src/telegram-fabric-plan.ts +90 -0
  435. package/src/telegram-loops-command.ts +44 -0
  436. package/src/telegram-project-command.ts +10 -4
  437. package/src/telegram.ts +5 -0
  438. package/src/tool-runtime/elanous-control-runtimes.ts +2 -1
  439. package/src/user-config.ts +204 -16
  440. package/src/version/packed-revision.json +1 -1
  441. package/src/workflow-runtime/executor.ts +4 -0
  442. package/src/workflow-runtime/node-catalog.ts +13 -0
  443. package/src/workflow-runtime/nodes/knowledge.ts +38 -0
  444. package/src/workflow-runtime/schema.ts +17 -1
  445. package/src/workflow-runtime/storage.ts +31 -13
  446. package/src/workflow-runtime/types.ts +13 -1
  447. package/apps/pwa/out/_next/static/-6XrAoD3eU7NyMHAE4xPS/_buildManifest.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/1147-466ec15afacc1a65.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/1394-ee3795b112907122.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/3992-2e522a8ac5f2afde.js +0 -25
  459. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
  463. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/7277-0b2fc7c16ba2d64b.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/7758-0b018a9e4fc56200.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/8605-0259994816604c49.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/9920-5cfdc5e8fd209825.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/approvals/page-cb169620945d1952.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-cefe8169e36cf62a.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/board/page-d2e35228bc497119.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5c05b0da4bc1392c.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/bots/page-e0ccdb576812b523.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/chat/page-813c4a52c9d05ca8.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/consult/page-4731f2c215358937.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/control/page-a022b257950f39e6.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/design-check/page-a2ffc717e04b8b0a.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/editor/page-b59ee4e70fc18fcf.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/field/page-c172dabd796eb7a3.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/inside/page-1efd0cb9e0ed9d13.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/intake/page-d5dc7ff69a37b4cf.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/layout-e71c84189e8c683e.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/live/page-1d6a533ce5a6efcb.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/market/page-dfca9b7d2de0b135.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/app/morning/page-6dd3bfbbde2a5c2c.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/app/not-found-490653e8d4cc8852.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/app/observatory/page-77447356baad75d2.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-29e961191f6057df.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-c822f55b884d9104.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fa4ccfc432424e52.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/app/outputs/page-6d604227e1664b71.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/app/page-9c7e8f744a765daf.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/app/reflection/page-4048543b4b7ad5e0.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4c3a3126abed551a.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/app/sessions/page-64b0aecb1ca75e75.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4292d0149020e8a7.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/app/settings/page-db14d0be7f1c460c.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-e5d8072cd7cfef73.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/app/setup/page-9547b5cec7cf76bf.js +0 -1
  517. package/apps/pwa/out/_next/static/chunks/app/share/page-e6dcb83265530636.js +0 -1
  518. package/apps/pwa/out/_next/static/chunks/app/showroom/page-45248f08d6aecae5.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/app/tasks/page-2cddeb9e2aeedc55.js +0 -1
  520. package/apps/pwa/out/_next/static/chunks/app/today/page-baba2e047872fb0f.js +0 -1
  521. package/apps/pwa/out/_next/static/chunks/app/trace/page-e895da4ec9cac345.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/app/vault/page-4344faf20216233b.js +0 -1
  523. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-698bb29f11864b89.js +0 -1
  524. package/apps/pwa/out/_next/static/chunks/app/workflows/page-bd6a6dc1ddc273f2.js +0 -1
  525. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2e9e2fb01c9da1c9.js +0 -1
  526. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d62ceb13411ebba4.js +0 -1
  527. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
  528. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
  529. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
  530. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
  531. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
  532. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
  533. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
  534. /package/apps/pwa/out/_next/static/{-6XrAoD3eU7NyMHAE4xPS → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
@@ -23,17 +23,22 @@ import { debug } from '../debug/log.js';
23
23
  import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js';
24
24
  import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
25
25
  import { envLiteral } from '../platform/env-literal.js';
26
- import { userConfigPath } from '../user-config.js';
26
+ import { getUserConfig, userConfigPath } from '../user-config.js';
27
27
  import { addItem, claimItem, decodeClaimHistoryEntry, devVersion, listChecklist, ownerMatches, parseOwner, parityGap, removeItem, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition, type ChecklistKind } from '../release-loop/checklist.js';
28
28
  import * as features from '../release-loop/feature-store.js';
29
- import { landedButYellow, type MergedChecklistPr } from '../release-loop/landed-but-yellow.js';
29
+ import { evidencePlan, landedButYellow, type MergedChecklistPr } from '../release-loop/landed-but-yellow.js';
30
30
  import { formatSchedule, getSchedule, listSchedules, setSchedule } from '../release-loop/release-schedule.js';
31
+ import { placeCell, rebalance, seatMove, type PlacementPriority } from '../release-loop/placement.js';
31
32
  import { writeStdoutJson } from './stdout-json.js';
32
33
  import { CliUserError } from './cli-user-error.js';
34
+ import { hqCliWriteAllowed, type HqDeps } from '../hq/hq.js';
35
+ import { fileLeaseStore } from '../hq/lease.js';
36
+ import { isIsolatedLedgerWriteRoot } from '../hq/ledger-write-target.js';
33
37
  import { runUnattendedRelease, type UnattendedReleaseDeps } from '../../scripts/release-loop/unattended-release.js';
34
38
  import { cutReleaseBranch } from '../../scripts/release-loop/cut-branch.js';
35
39
  import { releaseReadiness } from '../../scripts/release-loop/release-readiness.js';
36
40
  import { runReleaseIfReady } from './release-run-if-ready.js';
41
+ import { landingFreezeMessage, LandingFrozenError, readLandingFreeze } from '../release-loop/landing-freeze.js';
37
42
 
38
43
  export const DEFAULT_PUBLIC_REPO = 'ElanvitalAI/elanous';
39
44
  const SEMVER = /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/;
@@ -457,7 +462,8 @@ async function jsonAction<T>(json: boolean | undefined, body: (log: (l: string)
457
462
  if (json) process.stdout.write(`${JSON.stringify({ ok: ok(r), ...(r as object) })}\n`);
458
463
  if (!ok(r) && !process.exitCode) process.exitCode = 1;
459
464
  } catch (e) {
460
- if (json) process.stdout.write(`${JSON.stringify({ ok: false, error: (e as Error).message })}\n`);
465
+ const frozen = e instanceof LandingFrozenError ? { reason: 'frozen' as const } : {};
466
+ if (json) process.stdout.write(`${JSON.stringify({ ok: false, ...frozen, error: (e as Error).message })}\n`);
461
467
  else console.error(`⛔ ${(e as Error).message}`);
462
468
  process.exitCode = 1;
463
469
  }
@@ -504,12 +510,91 @@ export interface LandedButYellowDeps {
504
510
  run?: Runner;
505
511
  checklist?: typeof listChecklist;
506
512
  now?: () => Date;
513
+ evidenceAdd?: typeof features.evidenceAdd;
514
+ schedules?: typeof features.readSchedules;
515
+ released?: typeof features.releasedVersion;
507
516
  }
508
517
 
509
- export function registerReleaseCommands(program: Command, releaseRunDeps: UnattendedReleaseDeps = {}, landedDeps: LandedButYellowDeps = {}): void {
518
+ const MERGED_PR_LIMIT = 400;
519
+
520
+ function mergedChecklistPrs(since: string, now: Date, run: Runner): { prs: MergedChecklistPr[]; truncated: boolean } {
521
+ const cwd = process.cwd();
522
+ const fetch = (lower: string, upper?: string, limit = MERGED_PR_LIMIT): MergedChecklistPr[] => {
523
+ const search = `merged:>=${lower}${upper ? ` merged:<${upper}` : ''}`;
524
+ const raw = must(run('gh', ['pr', 'list', '--state', 'merged', '--search', search, '--limit', String(limit), '--json', 'number,title,body,mergedAt'], cwd), '병합 PR 조회(gh)');
525
+ const page = JSON.parse(raw) as MergedChecklistPr[];
526
+ if (!Array.isArray(page)) throw new Error('병합 PR 조회(gh): 배열이 아니다');
527
+ return page;
528
+ };
529
+ const unique = new Map<number, MergedChecklistPr>();
530
+ let truncated = false;
531
+ const collect = (page: MergedChecklistPr[]) => {
532
+ for (const pr of page) if (!unique.has(pr.number)) unique.set(pr.number, pr);
533
+ };
534
+ const first = fetch(since);
535
+ if (first.length < MERGED_PR_LIMIT) return { prs: first, truncated: false };
536
+ const lower = Date.parse(`${since}T00:00:00Z`);
537
+ const upper = Math.max(lower + 1000, Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1));
538
+ const visit = (start: number, end: number): void => {
539
+ const from = new Date(start).toISOString();
540
+ const to = new Date(end).toISOString();
541
+ const page = fetch(from, to);
542
+ if (page.length < MERGED_PR_LIMIT) { collect(page); return; }
543
+ if (end - start <= 1000) {
544
+ // The second-precision search cannot be subdivided; probe one extra row to distinguish exactly 400 from a real truncation.
545
+ const probe = fetch(from, to, MERGED_PR_LIMIT + 1);
546
+ collect(probe.slice(0, MERGED_PR_LIMIT));
547
+ if (probe.length > MERGED_PR_LIMIT) truncated = true;
548
+ return;
549
+ }
550
+ const middle = start + Math.floor((end - start) / 2000) * 1000;
551
+ visit(start, middle);
552
+ visit(middle, end);
553
+ };
554
+ visit(lower, upper);
555
+ return { prs: [...unique.values()], truncated };
556
+ }
557
+
558
+ function printLandedRows(rows: ReturnType<typeof landedButYellow>, items: ReturnType<typeof listChecklist>['items']): void {
559
+ const updatedAtById = new Map(items.map((item) => [item.id, item.updatedAt]));
560
+ const date = (iso: string) => {
561
+ const parts = new Intl.DateTimeFormat('en-US', { timeZone: 'Asia/Seoul', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }).formatToParts(new Date(iso));
562
+ const part = (name: string) => parts.find((entry) => entry.type === name)?.value;
563
+ return `${part('month')}-${part('day')} ${part('hour')}:${part('minute')}`;
564
+ };
565
+ for (const row of rows) {
566
+ const icon = row.status === 'yellow' ? '🟡' : '🔴';
567
+ console.log(`${row.id} ${row.owner ?? '-'} ${icon} ← ${row.prs.map((pr, i) => `#${pr.number} «${pr.title}» (${date(pr.mergedAt)} · 근거에 ${row.alreadyInEvidence[i] ? '있음' : '없음'}${pr.mergedAt > (updatedAtById.get(row.id) ?? '') ? ' · 칸보다 새것' : ''})`).join(' · ')}`);
568
+ }
569
+ console.log(`${rows.length}칸 · 그중 근거에 없는 PR 이 있는 칸 ${rows.filter((row) => row.alreadyInEvidence.includes(false)).length}`);
570
+ }
571
+
572
+ function compareReleaseVersions(a: string, b: string): number {
573
+ const parse = (version: string) => {
574
+ const [core, pre] = version.split('-');
575
+ const [major, minor, patch] = core!.split('.').map(Number);
576
+ const [stage, sequence] = pre?.split('.') ?? [];
577
+ return [major!, minor!, patch!, stage === undefined ? 3 : ({ alpha: 0, beta: 1, rc: 2 } as Record<string, number>)[stage]!, Number(sequence ?? 0)];
578
+ };
579
+ const left = parse(a), right = parse(b);
580
+ for (let i = 0; i < left.length; i++) if (left[i] !== right[i]) return left[i]! - right[i]!;
581
+ return 0;
582
+ }
583
+
584
+ export function registerReleaseCommands(program: Command, releaseRunDeps: UnattendedReleaseDeps = {}, landedDeps: LandedButYellowDeps = {}, hqDeps: HqDeps = {}): void {
510
585
  const release = program.command('release').description('공개 배포 한 판 — prepare(로컬) → publish(--yes) → verify (docs/manual/MANUAL-versioning-and-release-2026-09-25.md)');
511
586
  const schedule = release.command('schedule').description('판별 컷·착지 마감 조회·갱신');
512
587
  const scheduleLedgerRoot = () => releaseRunDeps.ledgerRoot ?? releaseLedgerRoot();
588
+ const ledgerFenceDeps = (): HqDeps => getElanousConfigDirOverride() && !hqDeps.store && !getUserConfig().hq?.arbiter
589
+ ? { ...hqDeps, store: fileLeaseStore(join(effectiveInstanceRoot(), 'hq', 'lease.json')), seenPath: join(effectiveInstanceRoot(), 'hq', 'seen-generation'), localPath: join(effectiveInstanceRoot(), 'hq', 'local.json') }
590
+ : hqDeps;
591
+ const mayWriteLedger = (command: string, override: boolean, ledgerRoot: string) => {
592
+ if (isIsolatedLedgerWriteRoot(ledgerRoot)) {
593
+ try { debug.log('hq.fence', 'skipped-isolated', { root: ledgerRoot, command }); } catch { /* observation is fail-soft */ }
594
+ return true;
595
+ }
596
+ return hqCliWriteAllowed(command, override, ledgerFenceDeps());
597
+ };
513
598
  schedule.command('list').option('--json', '결과 JSON').action((o: { json?: boolean }) => {
514
599
  const rows = listSchedules(scheduleLedgerRoot());
515
600
  if (o.json) console.log(JSON.stringify(rows));
@@ -522,21 +607,52 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
522
607
  if (o.json) console.log(JSON.stringify(row)); else console.log(formatSchedule(row));
523
608
  });
524
609
  schedule.command('set').requiredOption('--version <v>', '갱신할 판')
525
- .option('--cut-at <iso>', '오프셋 포함 컷 시각').option('--land-by <iso>', '오프셋 포함 착지 마감').option('--json', '결과 JSON')
526
- .action((o: { version: string; cutAt?: string; landBy?: string; json?: boolean }) => {
527
- if (o.cutAt === undefined && o.landBy === undefined) throw new CliUserError('갱신할 시각을 지정하라', '--cut-at <iso> 또는 --land-by <iso>');
528
- const row = setSchedule(o.version, { cutAt: o.cutAt, landBy: o.landBy }, process.env.ELANOUS_TRACK || 'cli', scheduleLedgerRoot());
610
+ .option('--cut-at <iso>', '오프셋 포함 컷 시각').option('--land-by <iso>', '오프셋 포함 착지 마감')
611
+ .option('--freeze-from <iso>', '동결 시작').option('--freeze-until <iso>', '동결 종료').option('--json', '결과 JSON').option('--hq-override')
612
+ .action((o: { version: string; cutAt?: string; landBy?: string; freezeFrom?: string; freezeUntil?: string; json?: boolean; hqOverride?: boolean }) => {
613
+ if (o.cutAt === undefined && o.landBy === undefined && o.freezeFrom === undefined && o.freezeUntil === undefined) throw new CliUserError('갱신할 시각을 지정하라', '--cut-at <iso> 또는 --land-by <iso>');
614
+ if (!mayWriteLedger('release schedule set', Boolean(o.hqOverride), scheduleLedgerRoot())) return;
615
+ const row = setSchedule(o.version, { cutAt: o.cutAt, landBy: o.landBy, freezeFrom: o.freezeFrom, freezeUntil: o.freezeUntil }, process.env.ELANOUS_TRACK || 'cli', scheduleLedgerRoot());
529
616
  if (o.json) console.log(JSON.stringify(row)); else console.log(formatSchedule(row));
530
617
  });
618
+ release.command('place <id>').description('칸의 우선순위·마감·용량으로 판 배치')
619
+ .option('--priority <priority>', 'P0|P1|P2').option('--dry-run', '이동 없이 배치 보기')
620
+ .action((id: string, opts: { priority?: string; dryRun?: boolean }) => {
621
+ const rows = listSchedules(scheduleLedgerRoot());
622
+ const found = rows.flatMap(({ version }) => listChecklist(version).items.filter((item) => item.id === id));
623
+ if (found.length !== 1) throw new CliUserError(found.length ? `여러 판의 같은 칸: ${id}` : `없는 칸: ${id}`, 'release checklist add 로 칸을 먼저 만든다');
624
+ const item = found[0]!;
625
+ if (!item.owner) throw new CliUserError(`담당 없는 칸: ${id}`);
626
+ const who = process.env.ELANOUS_TRACK;
627
+ if (who && who !== 'OP' && !ownerMatches(item.owner, parseOwner(who).seat)) throw new CliUserError('남의 칸 당기기 거부 — COO 에 요청');
628
+ if (!opts.dryRun && who !== 'OP') throw new CliUserError('판 배치는 COO 에 요청 — 자리는 이유와 함께 release checklist move 로 다음 판에만 이월');
629
+ if (!opts.dryRun && !mayWriteLedger('release place', false, scheduleLedgerRoot())) return;
630
+ const priority = opts.priority ?? item.priority;
631
+ if (!priority) throw new CliUserError(`우선순위가 없는 칸: ${id} — --priority P0|P1|P2 를 지정하라`);
632
+ const result = placeCell({ id, title: item.title, owner: item.owner, priority: priority as PlacementPriority, predecessors: item.predecessors ?? [], deadlineVersion: item.deadlineVersion }, { schedules: rows, dryRun: opts.dryRun, by: who ?? 'cli' });
633
+ console.log(`${opts.dryRun ? '· 드라이런' : '✅'} ${id} ${result.from ?? '-'} → ${result.version} · ${result.reason}${result.displaced.length ? ` · P2 이월 ${result.displaced.map((row) => row.id).join(', ')}` : ''}`);
634
+ });
635
+ release.command('rebalance').description('마감 2시간 전 미시작 칸을 다음 판으로')
636
+ .requiredOption('--version <v>', '대상 판').option('--dry-run', '이동 없이 보기')
637
+ .action((opts: { version: string; dryRun?: boolean }) => {
638
+ if (process.env.ELANOUS_TRACK !== 'OP' && !opts.dryRun) throw new CliUserError('판 이월은 COO 에 요청');
639
+ if (!opts.dryRun && !mayWriteLedger('release rebalance', false, scheduleLedgerRoot())) return;
640
+ const decisions = rebalance(opts.version, { schedules: listSchedules(scheduleLedgerRoot()), dryRun: opts.dryRun });
641
+ for (const row of decisions) console.log(`${opts.dryRun ? '· 드라이런' : '✅'} ${row.id} ${row.from} → ${row.version} · ${row.reason}`);
642
+ if (!decisions.length) console.log('이월할 미시작 칸 없음');
643
+ });
531
644
  const checklist = release.command('checklist').description('판별 확인표 조회·갱신')
532
645
  .option('--version <v>', '판 또는 별칭(기본: package.json 의 개발판에서 -dev.N 제거)')
533
- .option('--json', '결과 JSON');
646
+ .option('--json', '결과 JSON')
647
+ .option('--hq-override', '본부 임대 거부를 관측하며 수동 우회');
648
+ const mayWrite = (cmd: Command) => mayWriteLedger(`release checklist ${cmd.name()}`, Boolean(cmd.optsWithGlobals().hqOverride), releaseLedgerRoot());
534
649
  const context = (cmd: Command) => {
535
650
  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 }) };
536
651
  const codenames = checklistCodenames();
537
652
  return { version: checklistVersion(opts.version, codenames), codenames, json: opts.json };
538
653
  };
539
- const withContext = (cmd: Command) => cmd.option('--version <v>', '판 또는 별칭').option('--json', '결과 JSON');
654
+ const withContext = (cmd: Command) => cmd.option('--version <v>', '판 또는 별칭').option('--json', '결과 JSON').option('--hq-override', '본부 임대 거부를 관측하며 수동 우회');
655
+ const actor = () => process.env.ELANOUS_TRACK || 'cli';
540
656
  withContext(checklist.command('list').description('모든 칸')).option('--owner <owner>', '자리 또는 하위 자리로 거르기').action(async (opts: { owner?: string }, cmd: Command) => {
541
657
  const { version, codenames, json } = context(cmd);
542
658
  await checklistOutput(version, codenames, 'list', json, opts.owner);
@@ -545,54 +661,95 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
545
661
  const { version, codenames, json } = context(cmd);
546
662
  await checklistOutput(version, codenames, 'status', json, opts.owner);
547
663
  });
548
- withContext(checklist.command('landed-but-yellow').description('병합 PR 이 있지만 노랑·빨강인 칸(읽기 전용)'))
664
+ withContext(checklist.command('landed-but-yellow').description('병합 PR 이 있지만 노랑·빨강인 칸(기본 읽기 전용)'))
549
665
  .option('--owner <owner>', '자리 또는 하위 자리로 거르기')
550
- .action(async (opts: { owner?: string }, cmd: Command) => {
551
- const { version, json } = context(cmd);
666
+ .option('--open', '미발행 일정 판 중 칸이 있는 판 전부')
667
+ .option('--apply-evidence', '확실한 PR 짝의 근거를 붙인다(상태는 그대로)')
668
+ .option('--dry-run', '근거 추가 없이 계획만 출력')
669
+ .action(async (opts: { owner?: string; open?: boolean; applyEvidence?: boolean; dryRun?: boolean }, cmd: Command) => {
552
670
  try {
671
+ const inherited = cmd.parent?.opts() as { version?: string; json?: boolean } | undefined;
672
+ const local = cmd.opts() as { version?: string; json?: boolean };
673
+ if (opts.open && (local.version !== undefined || inherited?.version !== undefined)) throw new CliUserError('--open 과 --version 을 함께 쓸 수 없다');
674
+ const json = local.json ?? inherited?.json;
553
675
  if (opts.owner !== undefined) parseOwner(opts.owner);
554
- const since = new Date((landedDeps.now ?? (() => new Date()))().getTime() - 7 * 86_400_000).toISOString().slice(0, 10);
555
- 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)');
556
- const prs = JSON.parse(raw) as MergedChecklistPr[];
557
- if (!Array.isArray(prs)) throw new Error('병합 PR 조회(gh): 배열이 아니다');
558
- const items = (landedDeps.checklist ?? listChecklist)(version).items;
559
- const rows = landedButYellow(items, prs, { owner: opts.owner });
560
- if (json) await writeStdoutJson(`${JSON.stringify(rows)}\n`);
561
- else {
562
- const updatedAtById = new Map(items.map((item) => [item.id, item.updatedAt]));
563
- const date = (iso: string) => {
564
- const parts = new Intl.DateTimeFormat('en-US', { timeZone: 'Asia/Seoul', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }).formatToParts(new Date(iso));
565
- const part = (name: string) => parts.find((entry) => entry.type === name)?.value;
566
- return `${part('month')}-${part('day')} ${part('hour')}:${part('minute')}`;
567
- };
568
- for (const row of rows) {
569
- const icon = row.status === 'yellow' ? '🟡' : '🔴';
570
- console.log(`${row.id} ${row.owner ?? '-'} ${icon} ← ${row.prs.map((pr, i) => `#${pr.number} «${pr.title}» (${date(pr.mergedAt)} · 근거에 ${row.alreadyInEvidence[i] ? '있음' : '없음'}${pr.mergedAt > (updatedAtById.get(row.id) ?? '') ? ' · 칸보다 새것' : ''})`).join(' · ')}`);
676
+ const released = opts.open ? (landedDeps.released ?? features.releasedVersion)() : '';
677
+ const openSnapshots = opts.open
678
+ ? (landedDeps.schedules ?? features.readSchedules)()
679
+ .filter(({ version }) => isReleaseVersion(version) && (!released || compareReleaseVersions(version, released) > 0))
680
+ .sort((a, b) => compareReleaseVersions(a.version, b.version))
681
+ .map(({ version }) => (landedDeps.checklist ?? listChecklist)(version)).filter((snapshot) => snapshot.items.length > 0)
682
+ : [];
683
+ if (opts.open && !openSnapshots.length) {
684
+ if (json) await writeStdoutJson('[]\n'); else console.log('열린 판 없음');
685
+ return;
686
+ }
687
+ if (opts.applyEvidence && !opts.dryRun && !mayWrite(cmd)) return;
688
+ const snapshots = opts.open ? openSnapshots : [(landedDeps.checklist ?? listChecklist)(context(cmd).version)];
689
+ const now = (landedDeps.now ?? (() => new Date()))();
690
+ const updated = snapshots.flatMap(({ items }) => items
691
+ .filter((item) => (item.status === 'yellow' || item.status === 'red') && (opts.owner === undefined || ownerMatches(item.owner, opts.owner)))
692
+ .map((item) => Date.parse(item.updatedAt)).filter((at) => Number.isFinite(at)));
693
+ const since = updated.length ? new Date(updated.reduce((earliest, at) => Math.min(earliest, at))).toISOString().slice(0, 10)
694
+ : new Date(now.getTime() - 7 * 86_400_000).toISOString().slice(0, 10);
695
+ const { prs, truncated } = mergedChecklistPrs(since, now, landedDeps.run ?? defaultRunner);
696
+ const results: Array<{ version: string; rows?: ReturnType<typeof landedButYellow>; plan?: ReturnType<typeof evidencePlan> }> = [];
697
+ let totalAdded = 0, totalSkipped = 0;
698
+ for (const snapshot of snapshots) {
699
+ const { version, items } = snapshot;
700
+ const rows = landedButYellow(items, prs, { owner: opts.owner });
701
+ const skippedMention = new Set(rows.flatMap((row) => row.prs.flatMap((pr) =>
702
+ pr.basis === 'mention' ? [JSON.stringify([row.id, pr.number])] : []))).size;
703
+ if (opts.open) totalSkipped += skippedMention;
704
+ if (opts.open && !json) console.log(`— ${version} —`);
705
+ if (opts.applyEvidence || opts.dryRun) {
706
+ const plan = evidencePlan(rows);
707
+ const log = json ? console.error : console.log;
708
+ for (const { id, ref } of plan) {
709
+ if (!opts.dryRun) (landedDeps.evidenceAdd ?? features.evidenceAdd)(id, version, ref, actor(), snapshot.released, snapshot.dev);
710
+ log(`${opts.dryRun ? '·' : '✅'} ${id} 근거 ${ref}`);
711
+ }
712
+ const added = opts.dryRun ? 0 : plan.length;
713
+ totalAdded += added;
714
+ log(`붙임 ${added} · 건너뜀(언급만) ${skippedMention}`);
715
+ if (!opts.open && !opts.dryRun) debug.log('release.checklist', 'evidence-synced', { added, skippedMention });
716
+ results.push({ version, plan });
717
+ } else {
718
+ if (!json) printLandedRows(rows, items);
719
+ results.push({ version, rows });
571
720
  }
572
- console.log(`${rows.length}칸 · 그중 근거에 없는 PR 이 있는 칸 ${rows.filter((row) => row.alreadyInEvidence.includes(false)).length}`);
573
721
  }
574
- if (prs.length >= 400) console.error('⚠ 병합 PR 400개 상한: 목록이 잘렸을 수 있다');
722
+ if (opts.open) {
723
+ if (opts.applyEvidence && !opts.dryRun) debug.log('release.checklist', 'evidence-synced', { versions: snapshots.map(({ version }) => version), added: totalAdded, skippedMention: totalSkipped });
724
+ if (!json) console.log(`판 ${snapshots.length} · 붙임 합 ${totalAdded} · 건너뜀(언급만) 합 ${totalSkipped}`);
725
+ else await writeStdoutJson(`${JSON.stringify(results)}\n`);
726
+ } else if (json) await writeStdoutJson(`${JSON.stringify(opts.applyEvidence || opts.dryRun ? results[0]!.plan : results[0]!.rows)}\n`);
727
+ if (truncated) console.error('⚠ 병합 PR 400개 상한: 목록이 잘렸을 수 있다');
575
728
  } catch (error) {
576
729
  console.error(`⛔ ${error instanceof Error ? error.message : String(error)}`);
577
730
  process.exitCode = 1;
578
731
  }
579
732
  });
580
733
  withContext(checklist.command('add <id> <title>').description('칸 추가')).option('--owner <owner>', '담당').option('--kind <kind>', 'screen = 다섯 화면 짝 칸 · 근거에 짝: PWA … · 데스크톱 … · 폴드 … · 아이폰 … · 아이패드 … 한 줄')
734
+ .option('--priority <priority>', 'P0|P1|P2').option('--deadline-version <v>', '마감 판').option('--predecessor <id...>', '선행 칸 id')
581
735
  .option('--allow-duplicate-id', '다른 판에 같은 id 가 있어도 경고 후 추가')
582
- .action((id: string, title: string, opts: { owner?: string; kind?: string; allowDuplicateId?: boolean }, cmd: Command) => {
736
+ .action((id: string, title: string, opts: { owner?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[]; allowDuplicateId?: boolean }, cmd: Command) => {
583
737
  const { version, json } = context(cmd);
584
- const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}) }, { allowDuplicateId: opts.allowDuplicateId });
738
+ if (!mayWrite(cmd)) return;
739
+ 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 } : {}) }, { allowDuplicateId: opts.allowDuplicateId });
585
740
  if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 추가`);
586
741
  });
587
742
  withContext(checklist.command('set <id>').description('칸 상태·근거·담당·처분 갱신'))
588
743
  .option('--status <status>', 'green|yellow|red|done').option('--evidence <evidence>', '근거').option('--owner <owner>', '담당')
589
744
  .option('--disposition <disposition>', 'move|known-issue|block').option('--kind <kind>', 'screen = 다섯 화면 짝 칸 · 근거에 짝: PWA … · 데스크톱 … · 폴드 … · 아이폰 … · 아이패드 … 한 줄')
590
- .action((id: string, opts: { status?: string; evidence?: string; owner?: string; disposition?: string; kind?: string }, cmd: Command) => {
745
+ .option('--priority <priority>', 'P0|P1|P2').option('--deadline-version <v>', '마감 판').option('--predecessor <id...>', '선행 칸 id')
746
+ .action((id: string, opts: { status?: string; evidence?: string; owner?: string; disposition?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[] }, cmd: Command) => {
591
747
  const { version, json } = context(cmd);
592
748
  if (opts.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(opts.status)) throw new CliUserError(`잘못된 상태: ${opts.status}`, 'green|yellow|red|done');
593
749
  if (opts.disposition !== undefined && !['move', 'known-issue', 'block'].includes(opts.disposition)) throw new CliUserError(`잘못된 처분: ${opts.disposition}`, 'move|known-issue|block');
594
- if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined && opts.disposition === undefined && opts.kind === undefined) throw new CliUserError('갱신할 칸을 지정하라', '--status · --evidence · --owner · --disposition · --kind 중 하나');
595
- 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');
750
+ 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) throw new CliUserError('갱신할 칸을 지정하라', '--status · --evidence · --owner · --disposition · --kind · --priority · --deadline-version · --predecessor 중 하나');
751
+ if (!mayWrite(cmd)) return;
752
+ 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 } : {}) }, process.env.ELANOUS_TRACK || 'cli');
596
753
  const item = data.items.find((entry) => entry.id === id);
597
754
  const why = item?.kind === 'screen' && item.status === 'green' ? parityGap(item.evidence) : null;
598
755
  if (why) {
@@ -605,40 +762,52 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
605
762
  .requiredOption('--by <owner>', '잡을 자리 또는 하위 자리').option('--force', '기존 주인에서 강제로 바꾼다')
606
763
  .action((id: string, opts: { by: string; force?: boolean }, cmd: Command) => {
607
764
  const { version, json } = context(cmd);
765
+ if (!mayWrite(cmd)) return;
608
766
  const data = claimItem(version, id, opts.by, { force: opts.force });
609
767
  if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 주인 ${opts.by}`);
610
768
  });
611
769
  withContext(checklist.command('rm <id>').description('칸 삭제')).action((id: string, _opts: unknown, cmd: Command) => {
612
770
  const { version, json } = context(cmd);
771
+ if (!mayWrite(cmd)) return;
613
772
  const data = removeItem(version, id, process.env.ELANOUS_TRACK || 'cli');
614
773
  if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 삭제`);
615
774
  });
616
775
  withContext(checklist.command('seed').description('마크다운 로드맵에서 빈 칸 들이기')).requiredOption('--from <file>', '로드맵 파일')
617
776
  .action((opts: { from: string }, cmd: Command) => {
618
777
  const { version, json } = context(cmd);
778
+ if (!mayWrite(cmd)) return;
619
779
  const data = seedFromRoadmap(version, readFileSync(opts.from, 'utf8'));
620
780
  if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${version} ${data.items.length}칸`);
621
781
  });
622
- const actor = () => process.env.ELANOUS_TRACK || 'cli';
623
782
  withContext(checklist.command('move <id>').description('칸을 한 트랜잭션으로 다른 판에 옮긴다'))
624
783
  .requiredOption('--from <v>', '현재 판').requiredOption('--to <v>', '새 판').option('--reason <reason>', '이동 사유')
625
784
  .action((id: string, opts: { from: string; to: string; reason?: string }, cmd: Command) => {
626
785
  const { codenames, json } = context(cmd);
627
786
  const from = checklistVersion(opts.from, codenames), to = checklistVersion(opts.to, codenames);
628
- features.move(id, from, to, actor(), undefined, undefined, opts.reason);
787
+ if (!opts.reason?.trim()) throw new CliUserError('이동 이유가 필요하다');
788
+ if (!mayWrite(cmd)) return;
789
+ if (actor() === 'cli') {
790
+ // No seat identity (ELANOUS_TRACK unset): keep the plain move so seat sessions without the variable still work, but say so.
791
+ console.error('⚠ 자리 신원 없음(ELANOUS_TRACK 미설정) — 자리 규칙(자기 칸 · 다음 판 · 용량) 검사 없이 옮긴다');
792
+ debug.log('release.placement', 'anonymous-move', { id, from, to });
793
+ features.move(id, from, to, actor(), undefined, undefined, opts.reason);
794
+ } else if (actor() !== 'OP') seatMove(id, from, to, actor(), opts.reason, { schedules: listSchedules(scheduleLedgerRoot()) });
795
+ else features.move(id, from, to, actor(), undefined, undefined, opts.reason);
629
796
  if (json) console.log(JSON.stringify(listChecklist(to))); else console.log(`✅ ${id} ${from} → ${to}`);
630
797
  });
631
798
  withContext(checklist.command('retitle <id> <title>').description('칸 제목 수정')).action((id: string, title: string, _opts: unknown, cmd: Command) => {
632
799
  const { version, json } = context(cmd);
800
+ if (!mayWrite(cmd)) return;
633
801
  const snapshot = listChecklist(version);
634
802
  features.retitle(id, title, actor(), snapshot.released, snapshot.dev);
635
803
  if (json) console.log(JSON.stringify(listChecklist(version))); else console.log(`✅ ${id} 제목 수정`);
636
804
  });
637
805
  const evidence = checklist.command('evidence').description('칸의 PR·커밋 근거');
638
806
  withContext(evidence.command('add <id> <ref>').description('근거 참조 추가')).action((id: string, ref: string, _opts: unknown, cmd: Command) => {
639
- const { version, json } = context(cmd);
640
- const snapshot = listChecklist(version);
641
- features.evidenceAdd(id, version, ref, actor(), snapshot.released, snapshot.dev);
807
+ const { version, json } = context(cmd);
808
+ if (!mayWrite(cmd)) return;
809
+ const snapshot = listChecklist(version);
810
+ features.evidenceAdd(id, version, ref, actor(), snapshot.released, snapshot.dev);
642
811
  if (json) console.log(JSON.stringify(features.history(id).map(decodeClaimHistoryEntry))); else console.log(`✅ ${id} 근거 ${ref}`);
643
812
  });
644
813
  withContext(checklist.command('history <id>').description('칸의 판 이동·변경 이력')).action((id: string, _opts: unknown, cmd: Command) => {
@@ -654,6 +823,7 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
654
823
  });
655
824
  withContext(checklist.command('export').description('SQLite 에서 checklist.json 스냅샷 생성')).action((_opts: unknown, cmd: Command) => {
656
825
  const { version, json } = context(cmd);
826
+ if (!mayWrite(cmd)) return;
657
827
  const snapshot = listChecklist(version);
658
828
  features.exportJson(version, snapshot.released, snapshot.dev);
659
829
  if (json) console.log(JSON.stringify(listChecklist(version))); else console.log(`✅ ${version} checklist.json 내보냄`);
@@ -733,14 +903,40 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
733
903
  .option('--cut-commit <sha>', 'origin/main HEAD 대신 컷으로 쓸 커밋')
734
904
  .option('--dry-run', '체크리스트·그래프 실행 없이 전체 입력 보기')
735
905
  .option('--if-ready', '준비되지 않았으면 사유를 출력하고 성공으로 건너뛴다')
906
+ .option('--force-freeze', '동결 중 게이트·발행 강행(관측 기록)')
736
907
  .option('--json', '결과 한 줄 JSON(stdout) · 사람 줄은 stderr')
737
- .action(async (o: { version: string; cutCommit?: string; dryRun?: boolean; ifReady?: boolean; json?: boolean }) => {
908
+ .action(async (o: { version: string; cutCommit?: string; dryRun?: boolean; ifReady?: boolean; forceFreeze?: boolean; json?: boolean }) => {
909
+ if (!o.dryRun) {
910
+ try {
911
+ const frozen = readLandingFreeze(releaseRunDeps.freezeRoot);
912
+ if (frozen) {
913
+ debug.log('release.run', o.forceFreeze ? 'freeze-forced' : 'frozen', { version: o.version, reason: frozen.reason, until: frozen.until });
914
+ if (!o.forceFreeze) {
915
+ // `--if-ready` (the scheduled path) defers quietly; a direct run fails — the release run is run again after `freeze off`.
916
+ if (o.ifReady) {
917
+ if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: true, skipped: true, reason: 'frozen', detail: landingFreezeMessage(frozen) })}\n`);
918
+ else console.log(`· ${landingFreezeMessage(frozen)} · 릴리스 루프 연기`);
919
+ } else {
920
+ if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, reason: 'frozen', error: landingFreezeMessage(frozen) })}\n`);
921
+ else console.error(`⛔ ${landingFreezeMessage(frozen)} · 릴리스 루프 거부(--force-freeze 로만 강행)`);
922
+ process.exitCode = 1;
923
+ }
924
+ return;
925
+ }
926
+ }
927
+ } catch (e) {
928
+ if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, error: (e as Error).message })}\n`);
929
+ else console.error(`⛔ ${(e as Error).message}`);
930
+ process.exitCode = 1;
931
+ return;
932
+ }
933
+ }
738
934
  if (o.ifReady && !o.dryRun) {
739
935
  try {
740
936
  const outcome = await runReleaseIfReady(o.version, {
741
937
  ledgerRoot: releaseRunDeps.ledgerRoot,
742
938
  readiness: (version, options) => releaseReadiness(version, { ledgerRoot: releaseRunDeps.ledgerRoot, checklist: releaseRunDeps.checklist, ...options }),
743
- run: () => runUnattendedRelease({ version: o.version, cutCommit: o.cutCommit }, releaseRunDeps),
939
+ run: () => runUnattendedRelease({ version: o.version, cutCommit: o.cutCommit, forceFreeze: o.forceFreeze }, releaseRunDeps),
744
940
  });
745
941
  if (outcome.skipped) {
746
942
  if (o.json) await writeStdoutJson(`${JSON.stringify(outcome)}\n`);
@@ -753,6 +949,13 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
753
949
  if (o.json) await writeStdoutJson(`${JSON.stringify({ ok, ...result })}\n`);
754
950
  if (!ok && !process.exitCode) process.exitCode = 1;
755
951
  } catch (e) {
952
+ if (e instanceof LandingFrozenError) {
953
+ // A freeze switched on after the entry check still defers the scheduled run rather than failing it.
954
+ debug.log('release.run', 'frozen', { version: o.version, reason: e.freeze.reason, until: e.freeze.until, at: 'run-boundary' });
955
+ if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: true, skipped: true, reason: 'frozen', detail: e.message })}\n`);
956
+ else console.log(`· ${e.message} · 릴리스 루프 연기`);
957
+ return;
958
+ }
756
959
  if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, error: (e as Error).message })}\n`);
757
960
  else console.error(`⛔ ${(e as Error).message}`);
758
961
  process.exitCode = 1;
@@ -760,7 +963,7 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
760
963
  return;
761
964
  }
762
965
  await jsonAction(o.json, async (log) => {
763
- const result = await runUnattendedRelease({ version: o.version, dryRun: o.dryRun, cutCommit: o.cutCommit }, releaseRunDeps);
966
+ const result = await runUnattendedRelease({ version: o.version, dryRun: o.dryRun, cutCommit: o.cutCommit, forceFreeze: o.forceFreeze }, releaseRunDeps);
764
967
  log(`${result.dryRun ? '· 드라이런' : '▶ 릴리스 루프'} ${result.input.version} · 입력 ${JSON.stringify(result.input)}`);
765
968
  return result;
766
969
  }, (r) => r.dryRun || r.state?.status === 'done' || r.state?.status === 'awaiting-approval');
@@ -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
+ }
@@ -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 };