elanous 0.2.13 → 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 (445) 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/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/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-f14987d5850a31e0.js → page-1abfe3aec3be6bdc.js} +1 -1
  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-15374909d8f1aebd.js +1 -0
  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-66c27608f651b866.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-78b40a344ed22f24.js → page-e1e47d8d940aba3d.js} +1 -1
  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-63d14a4d8fe1e1c1.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/graphs/orchestrator/orchestrator.yaml +59 -0
  194. package/graphs/orchestrator/recipes.yaml +18 -0
  195. package/graphs/release/recipes.yaml +3 -0
  196. package/graphs/release/release-loop.yaml +4 -1
  197. package/graphs/rhythm/daily.yaml +38 -0
  198. package/graphs/rhythm/recipes.yaml +18 -0
  199. package/graphs/rhythm/weekly.yaml +39 -0
  200. package/graphs/video/recipes.yaml +3 -0
  201. package/graphs/video/youtube-summary.yaml +17 -0
  202. package/package.json +1 -1
  203. package/scripts/audit-test-state-writes.ts +72 -17
  204. package/scripts/brand/check.ts +186 -0
  205. package/scripts/coord-post.sh +11 -1
  206. package/scripts/docs-cli-check.ts +23 -6
  207. package/scripts/expose-rubric-check.ts +118 -0
  208. package/scripts/expose-rubric-suggest.ts +163 -0
  209. package/scripts/heal/run-role.ts +14 -0
  210. package/scripts/hq/standby-verify.ts +11 -7
  211. package/scripts/lessons/handbook-page.ts +85 -0
  212. package/scripts/release-loop/gate-node.ts +9 -0
  213. package/scripts/release-loop/publish-node.ts +14 -1
  214. package/scripts/release-loop/unattended-release.ts +23 -1
  215. package/scripts/release-loop/vault-note-node.ts +196 -0
  216. package/scripts/release-story/draft.ts +172 -0
  217. package/scripts/rhythm/daily.ts +383 -0
  218. package/scripts/rhythm/weekly.ts +294 -0
  219. package/scripts/skills/public-check.ts +169 -0
  220. package/scripts/test-deterministic.ts +4 -8
  221. package/scripts/test-diet/effectiveness.ts +195 -0
  222. package/scripts/test-diet/lib.ts +16 -8
  223. package/scripts/test-diet/node.ts +77 -9
  224. package/scripts/ux/demo-rehearsal.ts +2 -2
  225. package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
  226. package/scripts/video/youtube-summary.ts +133 -0
  227. package/skills/diagram-master/SKILL.md +42 -49
  228. package/skills/diagram-master/references/pyproject.toml +4 -1
  229. package/skills/diagram-master/references/routing-guide.md +4 -2
  230. package/skills/grill-me/SKILL.md +1 -0
  231. package/skills/youtube-master/SKILL.md +13 -13
  232. package/skills/youtube-master/package-lock.json +0 -545
  233. package/skills/youtube-master/package.json +2 -5
  234. package/skills/youtube-master/scripts/main.ts +2 -2
  235. package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
  236. package/skills/youtube-master/src/util.ts +30 -7
  237. package/src/agent-mission/driver.ts +3 -3
  238. package/src/autopilot/build/llm-conflict-merge.ts +45 -8
  239. package/src/autopilot/build/merge-intent.ts +53 -0
  240. package/src/chat/index.ts +2 -0
  241. package/src/claims/claims-ledger.ts +232 -0
  242. package/src/claims/claims-render.ts +157 -0
  243. package/src/cli/claims-cli.ts +94 -0
  244. package/src/cli/decisions-cli.ts +35 -3
  245. package/src/cli/docs-cli.ts +2 -0
  246. package/src/cli/flow-cli.ts +11 -0
  247. package/src/cli/freeze-cli.ts +45 -0
  248. package/src/cli/lesson-cli.ts +108 -0
  249. package/src/cli/logs-cli.ts +104 -2
  250. package/src/cli/logs-model-input.ts +131 -0
  251. package/src/cli/pr-cli.ts +29 -1
  252. package/src/cli/private-ledger-commands.ts +54 -4
  253. package/src/cli/pty-drive-verdict.ts +11 -2
  254. package/src/cli/release-cli.ts +144 -21
  255. package/src/cli/rfc-status.ts +91 -0
  256. package/src/connectors/linear.ts +5 -3
  257. package/src/context-bus/condense.ts +15 -20
  258. package/src/context-bus/context-now-public.ts +86 -0
  259. package/src/context-bus/context-now-surfaces.ts +55 -2
  260. package/src/context-bus/context-now.ts +4 -3
  261. package/src/context-bus/long-term-memory.ts +92 -11
  262. package/src/dashboard/index.ts +75 -22
  263. package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
  264. package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
  265. package/src/dashboard/slash-runtime/loops-table.ts +77 -0
  266. package/src/decisions/decision-ledger.ts +40 -1
  267. package/src/discord/sprint21-runtime.ts +16 -0
  268. package/src/discord-self-message.ts +8 -0
  269. package/src/discord-slash-wire.ts +31 -2
  270. package/src/domains/outbound-alert.ts +1 -1
  271. package/src/elanous-config-dir.ts +29 -4
  272. package/src/execution-loop/launch-gate.ts +29 -14
  273. package/src/flow/cards.ts +239 -0
  274. package/src/graph-runner/graph-tick.ts +4 -0
  275. package/src/graph-runner/runner.ts +30 -5
  276. package/src/harness/harness-cli-command.ts +152 -27
  277. package/src/harness/harness-incidents.ts +43 -12
  278. package/src/harness/harness-pod-dispatch.ts +2 -0
  279. package/src/harness/harness-queue-child.ts +3 -3
  280. package/src/harness/harness-queue.ts +216 -28
  281. package/src/harness/helper-scan.ts +139 -0
  282. package/src/harness/model-input-observation.ts +37 -0
  283. package/src/hq/hq.ts +1 -1
  284. package/src/hq/promote.ts +301 -0
  285. package/src/index.ts +83 -21
  286. package/src/intake-plane/linear-wish.ts +39 -11
  287. package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
  288. package/src/intake-plane/wish-card.ts +53 -2
  289. package/src/intake-plane/wish-folder.ts +5 -2
  290. package/src/intake-plane/wish-reply.ts +72 -0
  291. package/src/lessons/lesson-import.ts +106 -0
  292. package/src/lessons/lesson-ledger.ts +206 -0
  293. package/src/loops/activity.ts +203 -0
  294. package/src/loops/budget.ts +32 -0
  295. package/src/loops/checker.ts +5 -3
  296. package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
  297. package/src/loops/loop-cli.ts +36 -4
  298. package/src/loops/neighbors.ts +40 -0
  299. package/src/loops/observe.ts +10 -0
  300. package/src/loops/orchestrator/author-depth.ts +164 -0
  301. package/src/loops/orchestrator/author-input-check.ts +77 -0
  302. package/src/loops/orchestrator/author-ledger.ts +230 -0
  303. package/src/loops/orchestrator/finish-rate.ts +175 -0
  304. package/src/loops/orchestrator/overlap.ts +51 -12
  305. package/src/loops/orchestrator/resync-shadow.ts +227 -0
  306. package/src/loops/orchestrator/tick.ts +302 -0
  307. package/src/loops/orchestrator/traffic.ts +18 -3
  308. package/src/loops/registry.ts +454 -9
  309. package/src/loops/status-rows.ts +118 -0
  310. package/src/loops/verdict.ts +17 -5
  311. package/src/maturity/bot-slash-catalog.ts +66 -0
  312. package/src/maturity/feature-maturity.ts +7 -4
  313. package/src/nexus/api/dashboard.ts +6 -1
  314. package/src/nexus/api/http-server.ts +18 -2
  315. package/src/nexus/api/merge-approvals-gate.ts +1 -1
  316. package/src/nexus/api/schedules-read.ts +5 -1
  317. package/src/nexus/api/task-cards-api.ts +44 -5
  318. package/src/nexus/api/workflows.ts +2 -1
  319. package/src/release-loop/auto-start.ts +48 -0
  320. package/src/release-loop/checklist.ts +13 -4
  321. package/src/release-loop/feature-store.ts +28 -16
  322. package/src/release-loop/landing-freeze.ts +121 -0
  323. package/src/release-loop/placement.ts +242 -0
  324. package/src/release-loop/release-schedule.ts +3 -1
  325. package/src/seat-dispatch/ceo-commands.ts +2 -2
  326. package/src/seat-dispatch/seat-ask.ts +16 -3
  327. package/src/seat-dispatch/seat-questions.ts +58 -0
  328. package/src/seat-loop/seat-loop.ts +406 -31
  329. package/src/self-dev/dev-pipeline.ts +6 -2
  330. package/src/self-dev/draft-sweep.ts +76 -0
  331. package/src/self-dev/run-store.ts +73 -1
  332. package/src/self-dev/run-supervisor.ts +8 -0
  333. package/src/self-dev/self-orchestrate-runtime.ts +5 -1
  334. package/src/self-implement/budget-gate.ts +29 -0
  335. package/src/self-implement/expose-gate.ts +47 -0
  336. package/src/self-implement/frozen-merges.ts +294 -0
  337. package/src/self-implement/gate-cli.ts +10 -4
  338. package/src/self-implement/gate-scope.ts +46 -9
  339. package/src/self-implement/goal-archive.ts +222 -0
  340. package/src/self-implement/goal-lookup.ts +147 -0
  341. package/src/self-implement/heal-intake.ts +79 -0
  342. package/src/self-implement/host-regate.ts +25 -2
  343. package/src/self-implement/orchestrator.ts +114 -10
  344. package/src/self-implement/revert-guard.ts +62 -0
  345. package/src/self-implement/run-ledger.ts +1 -1
  346. package/src/self-implement/seams.ts +28 -8
  347. package/src/session/index.ts +2 -0
  348. package/src/task-cards/card-cli.ts +10 -2
  349. package/src/task-cards/card-store.ts +10 -6
  350. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  351. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
  352. package/src/task-orchestrator/surfaces/self-implement-pod.ts +80 -16
  353. package/src/telegram-commands.ts +27 -3
  354. package/src/telegram-loops-command.ts +44 -0
  355. package/src/user-config.ts +120 -16
  356. package/src/version/packed-revision.json +1 -1
  357. package/src/workflow-runtime/storage.ts +31 -13
  358. package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
  369. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
  373. package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
  444. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
  445. /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
@@ -1,12 +1,15 @@
1
1
  import { Option, type Command } from 'commander';
2
2
  import { hqCliWriteAllowed, type HqDeps } from '../hq/hq.js';
3
3
  import { fileLeaseStore } from '../hq/lease.js';
4
+ import { isIsolatedLedgerWriteRoot } from '../hq/ledger-write-target.js';
5
+ import { debug } from '../debug/log.js';
6
+ import { elanousStateRoot } from '../autopilot/state-paths.js';
4
7
  import { join } from 'node:path';
5
8
  import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
6
9
  import { effectiveInstanceRoot } from '../instance/resolve.js';
7
10
  import { getUserConfig } from '../user-config.js';
8
11
  import { projectDecisionsToLinear } from '../decisions/decision-linear-projection.js';
9
- import { DecisionLedger, importDecisionMarkdown, type DecisionCategory, type DecisionLedgerOptions, type DecisionOption, type DecisionTrack, type DecisionEntry } from '../decisions/decision-ledger.js';
12
+ import { DecisionLedger, importDecisionMarkdown, type DecisionCategory, type DecisionLedgerOptions, type DecisionOption, type DecisionTrack, type DecisionEntry, type Seat, type SeatDecisionRecord } from '../decisions/decision-ledger.js';
10
13
 
11
14
  const repeat = (value: string, values: string[]) => [...values, value];
12
15
  function date(raw?: string): string | undefined {
@@ -52,13 +55,24 @@ export function formatDecisionDetail(e: DecisionEntry): string {
52
55
  ...(e.refs?.length ? [`참조: ${e.refs.join(' · ')}`] : [])].join('\n');
53
56
  }
54
57
 
58
+ export function formatSeatDecisionRow(e: SeatDecisionRecord): string {
59
+ return `${e.id} · 결정: ${e.decidedAt ? kst(e.decidedAt) : '시각 미상'} · 기록: ${e.recordedAt ? kst(e.recordedAt) : '시각 미상'} · ${e.seat} 결정 · 사후 보고 · ${e.title} → ${e.decision} · 위임: ${e.delegation}${e.refs?.length ? ` · 참조: ${e.refs.join(' · ')}` : ''}`;
60
+ }
61
+
55
62
  export function registerDecisionsCommands(program: Command, config: DecisionLedgerOptions = {}, out: Pick<Console, 'log'> = console, hqDeps: HqDeps = {}): void {
56
63
  const noXcheck = new Option('--no-xcheck <이유>', '교차 확인 생략 이유');
57
64
  // This flag takes a reason, not Commander's negated boolean default.
58
65
  noXcheck.negate = false;
59
66
  const root = program.command('decisions').description('대표 결정 원장 · 로컬 전용').option('--hq-override', '본부 임대 거부를 관측하며 수동 우회');
60
- const mayWrite = (command: string, override?: boolean) => hqCliWriteAllowed(`decisions ${command}`, Boolean(override || root.opts().hqOverride),
61
- (getElanousConfigDirOverride() || config.stateDir) && !hqDeps.store && !getUserConfig().hq?.arbiter ? { ...hqDeps, store: fileLeaseStore(join(config.stateDir ?? effectiveInstanceRoot(), 'hq', 'lease.json')), seenPath: join(config.stateDir ?? effectiveInstanceRoot(), 'hq', 'seen-generation'), localPath: join(config.stateDir ?? effectiveInstanceRoot(), 'hq', 'local.json') } : hqDeps);
67
+ const mayWrite = (command: string, override?: boolean) => {
68
+ const ledgerRoot = config.stateDir ?? elanousStateRoot();
69
+ if (command !== 'linear-sync' && isIsolatedLedgerWriteRoot(ledgerRoot)) {
70
+ try { debug.log('hq.fence', 'skipped-isolated', { root: ledgerRoot, command: `decisions ${command}` }); } catch { /* observation is fail-soft */ }
71
+ return true;
72
+ }
73
+ return hqCliWriteAllowed(`decisions ${command}`, Boolean(override || root.opts().hqOverride),
74
+ (getElanousConfigDirOverride() || config.stateDir) && !hqDeps.store && !getUserConfig().hq?.arbiter ? { ...hqDeps, store: fileLeaseStore(join(config.stateDir ?? effectiveInstanceRoot(), 'hq', 'lease.json')), seenPath: join(config.stateDir ?? effectiveInstanceRoot(), 'hq', 'seen-generation'), localPath: join(config.stateDir ?? effectiveInstanceRoot(), 'hq', 'local.json') } : hqDeps);
75
+ };
62
76
  const emit = (value: unknown, json?: boolean, text?: string) => out.log(json ? JSON.stringify(value) : text ?? JSON.stringify(value));
63
77
  const fail = (action: () => void) => { try { action(); } catch (e) { throw new Error(`decisions: ${e instanceof Error ? e.message : String(e)}`); } };
64
78
  root.command('linear-sync').description('열린 결정을 COO Linear 프로젝트에 투영하고 닫힌 결정을 동기화한다')
@@ -105,6 +119,24 @@ export function registerDecisionsCommands(program: Command, config: DecisionLedg
105
119
  if (entry.crossCheckSkipped === 'missing') console.error('교차 확인 없음 — --xcheck SEAT:메모 또는 --no-xcheck 이유');
106
120
  emit(entry, o.json, `올림: ${formatDecisionRow(entry)}`);
107
121
  }));
122
+ root.command('record-seat').description('위임 범위에서 이미 내린 자리 결정을 사후 보고로 원장에 기록 (대표 카드 아님)')
123
+ .requiredOption('--seat <OP|MK|TC|UX>').requiredOption('--title <text>')
124
+ .requiredOption('--decision <text>').requiredOption('--delegation <scope>')
125
+ .option('--at <UTC>', '알고 있는 결정 시각 (UTC ISO; 생략 시 결정 시각 미상)')
126
+ .option('--ref <source>', '조율 글 등의 출처 (반복)', repeat, [] as string[])
127
+ .option('--json').option('--hq-override')
128
+ .action((o: { seat: Seat; title: string; decision: string; delegation: string; at?: string; ref: string[]; json?: boolean; hqOverride?: boolean }) => fail(() => {
129
+ if (!mayWrite('record-seat', o.hqOverride)) return;
130
+ const entry = new DecisionLedger(config).recordSeatDecision({ seat: o.seat, title: o.title, decision: o.decision,
131
+ delegation: o.delegation, ...(o.at ? { decidedAt: o.at } : {}), ...(o.ref.length ? { refs: o.ref } : {}) });
132
+ emit(entry, o.json, formatSeatDecisionRow(entry));
133
+ }));
134
+ root.command('seat-report').description('밤사이 자리들의 사후 결정을 한 번에 조회 (기본 최근 24시간)')
135
+ .option('--since <date>', '조회 시작 UTC ISO 또는 Nd', '1d').option('--seat <OP|MK|TC|UX>').option('--json')
136
+ .action((o: { since: string; seat?: Seat; json?: boolean }) => fail(() => {
137
+ const rows = new DecisionLedger(config).seatReport({ since: date(o.since), seat: o.seat });
138
+ emit(rows, o.json, rows.length ? rows.map(formatSeatDecisionRow).join('\n') : '자리 결정 0건');
139
+ }));
108
140
  root.command('list').description('결정 목록 (기본 열린 것)')
109
141
  .option('--status <open|decided|all>', '기본 open', 'open').option('--since <date>').option('--version <version>')
110
142
  .option('--decided-by <human|auto>').option('--category <category>').option('--json')
@@ -6,6 +6,7 @@
6
6
  // 양쪽에서 잡는다. 임베딩 다운 시 키워드 단독으로 강등(fail-soft).
7
7
 
8
8
  import type { Command } from 'commander';
9
+ import { registerRfcStatusCommand } from './rfc-status.js';
9
10
  import { existsSync, readFileSync } from 'node:fs';
10
11
  import { relative, resolve } from 'node:path';
11
12
  import { knowledgeDbPath, openKnowledgeDb, hybridQueryKnowledge } from '../domains/knowledge.js';
@@ -280,6 +281,7 @@ export async function runDocsStale(
280
281
  export function registerDocsCommands(program: Command): void {
281
282
  const docsCmd = program.command('docs')
282
283
  .description('문서 지식 검색/관리 — knowledge.db 벡터+BM25 하이브리드 (DocOps)');
284
+ registerRfcStatusCommand(docsCmd);
283
285
  docsCmd.command('search <query>')
284
286
  .description('하이브리드 검색(RRF) — 의미(임베딩)+키워드(FTS5) 융합. 임베딩 다운 시 키워드 단독')
285
287
  .option('--limit <n>', '최대 결과 (기본 8·최대 20)')
@@ -0,0 +1,11 @@
1
+ import type { Command } from 'commander';
2
+ import { flowCard, flowTick } from '../flow/cards.js';
3
+
4
+ export function registerFlowCommands(program: Command): void {
5
+ const flow = program.command('flow').description('Wish 카드 → 칸 → 릴리스 판');
6
+ flow.command('tick').description('새 Wish 카드 한 장 처리 (크론용)')
7
+ .action(async () => { const result = await flowTick(); console.log(result ? result.text : '새 카드 없음'); });
8
+ flow.command('plan <cardId>').description('카드 분할·배치 미리보기 (쓰기 없음)')
9
+ .requiredOption('--dry-run', '판·결정·카드 원장에 쓰지 않는다')
10
+ .action(async (cardId: string) => { console.log(JSON.stringify(await flowCard(cardId, {}, true))); });
11
+ }
@@ -0,0 +1,45 @@
1
+ import type { Command } from 'commander';
2
+ import { debug } from '../debug/log.js';
3
+ import { CliUserError } from './cli-user-error.js';
4
+ import { awaitLandingMergesDrained, disableLandingFreeze, enableLandingFreeze, readLandingFreeze, landingFreezeMessage } from '../release-loop/landing-freeze.js';
5
+ import { pendingFrozenMerges, sweepFrozenMerges } from '../self-implement/frozen-merges.js';
6
+
7
+ export function registerFreezeCommands(program: Command): void {
8
+ const freeze = program.command('freeze').description('착지 동결 상태 on/off/status');
9
+ freeze.command('on').option('--reason <text>', '동결 이유').option('--until <timestamp>', '끝 시각(타임존 포함 ISO)')
10
+ .option('--wait-seconds <n>', '이미 동결 검사를 지난 병합이 끝나기를 기다릴 최대 초', '600')
11
+ .action(async (opts: { reason?: string; until?: string; waitSeconds: string }) => {
12
+ const waitSeconds = Number(opts.waitSeconds);
13
+ if (!/^\d+$/.test(opts.waitSeconds.trim()) || !Number.isSafeInteger(waitSeconds) || waitSeconds > 86_400) {
14
+ throw new CliUserError(`잘못된 --wait-seconds: ${opts.waitSeconds}`, '0 이상 86400 이하의 정수 초');
15
+ }
16
+ const state = enableLandingFreeze(opts);
17
+ debug.log('harness.merge', 'freeze-on', state);
18
+ const drained = await awaitLandingMergesDrained(undefined, { timeoutMs: waitSeconds * 1000 });
19
+ debug.log('harness.merge', 'freeze-drain', drained);
20
+ console.log(landingFreezeMessage(state));
21
+ if (!drained.drained) {
22
+ console.error(`⚠ 동결 전에 시작한 병합 ${drained.pending}건이 아직 진행 중 — 끝나면 더 이상 병합이 시작되지 않는다`);
23
+ process.exitCode = 2;
24
+ }
25
+ });
26
+ freeze.command('off').description('동결을 풀고 보류된 병합을 바로 이어 한다').action(async () => {
27
+ disableLandingFreeze();
28
+ debug.log('harness.merge', 'freeze-off', {});
29
+ console.log('동결 해제');
30
+ await resume();
31
+ });
32
+ freeze.command('resume').description('동결이 풀렸으면 보류된 병합을 지금 이어 한다').action(async () => { await resume(); });
33
+ freeze.command('status').option('--json', '상태 JSON').action((opts: { json?: boolean }) => {
34
+ const state = readLandingFreeze();
35
+ const pending = pendingFrozenMerges();
36
+ if (opts.json) console.log(JSON.stringify({ frozen: state !== null, freeze: state, pendingMerges: pending }));
37
+ else console.log(`${state ? landingFreezeMessage(state) : '동결 없음'} · 보류 병합 ${pending}건`);
38
+ });
39
+ }
40
+
41
+ async function resume(): Promise<void> {
42
+ const result = await sweepFrozenMerges();
43
+ debug.log('harness.merge', 'freeze-resume', result);
44
+ console.log(`보류 병합 이어 하기: 병합 ${result.merged}건 · 남음 ${result.pending}건`);
45
+ }
@@ -0,0 +1,108 @@
1
+ import type { Command } from 'commander';
2
+ import { resolve } from 'node:path';
3
+ import { debug } from '../debug/log.js';
4
+ import { importLessons, scanLessonDocs } from '../lessons/lesson-import.js';
5
+ import { LessonInputError, LessonLedger, type LessonLedgerOptions, type LessonRow, type LessonDetail } from '../lessons/lesson-ledger.js';
6
+
7
+ function actor(explicit?: string): string {
8
+ const by = explicit?.trim() || process.env.AI_AGENT?.trim();
9
+ if (!by) throw new LessonInputError('--by or AI_AGENT is required');
10
+ return by;
11
+ }
12
+
13
+ function reportError(command: string, message: string): void {
14
+ const escaped = message.replace(/\r/g, '\\r').replace(/\n/g, '\\n')
15
+ .replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
16
+ process.stderr.write(`lesson ${command}: ${escaped}\n`);
17
+ process.exitCode = 1;
18
+ }
19
+
20
+ function handle<Args extends unknown[]>(command: string, action: (...args: Args) => void): (...args: Args) => void {
21
+ return (...args) => {
22
+ try { action(...args); }
23
+ catch (error) {
24
+ if (!(error instanceof LessonInputError)) throw error;
25
+ reportError(command, error.message);
26
+ }
27
+ };
28
+ }
29
+
30
+ function format(row: LessonRow): string {
31
+ return `${row.id} · ${row.status} · ${row.occurrence_count}회 · ${row.incident} · ${row.owner}`;
32
+ }
33
+
34
+ function detail(row: LessonDetail): string {
35
+ return [format(row), `원인: ${row.cause}`, `처방: ${row.remedy}`, `강제: ${row.enforced_by || '없음'}`,
36
+ ...(row.disproof ? [`반증: ${row.disproof}`] : []),
37
+ '발생:', ...row.occurrences.map(o => ` ${o.at} · ${o.source}${o.note ? ` · ${o.note}` : ''}`),
38
+ '이력:', ...row.history.map(h => ` ${h.at} · ${h.event} · ${h.by} · ${h.detail}`)].join('\n');
39
+ }
40
+
41
+ export function registerLessonCommands(program: Command, options: LessonLedgerOptions = {}, out: Pick<Console, 'log'> = console): void {
42
+ const root = program.command('lesson').description('사고 · 원인 · 처방 · 재발 교훈 원장');
43
+ const ledger = () => new LessonLedger(options);
44
+ const emit = (value: unknown, json: boolean | undefined, text: string) => out.log(json ? JSON.stringify(value) : text);
45
+ root.command('import').description('INCIDENT·FINDING 문서 교훈 미리 보기 (기본값); --apply 로 적재')
46
+ .option('--apply', '원장에 적재').option('--json', 'JSON 출력').option('--by <name>', '기록자 (기본 AI_AGENT)')
47
+ .action(handle('import', (o: { apply?: boolean; json?: boolean; by?: string }) => {
48
+ const by = actor(o.by);
49
+ const scan = scanLessonDocs(resolve(process.cwd()));
50
+ const imported = importLessons(ledger(), scan.items, { apply: !!o.apply, by });
51
+ const result = { files: scan.files, items: imported.items, skipped: [...scan.skipped, ...imported.skipped],
52
+ emptyCause: imported.items.filter(item => !item.cause).length,
53
+ emptyRemedy: imported.items.filter(item => !item.remedy).length, written: imported.written };
54
+ if (o.apply) debug.log('lessons.import', 'applied', { files: result.files, items: result.items.length,
55
+ skipped: result.skipped.length, written: result.written });
56
+ emit(result, o.json, `파일 ${result.files} · 넣을 교훈 ${result.items.length} · 건너뜀 ${result.skipped.length} · 빈 원인 ${result.emptyCause} · 빈 처방 ${result.emptyRemedy} · 기록 ${result.written}`);
57
+ }));
58
+ root.command('page').description('교훈 원장에서 핸드북 페이지 생성')
59
+ .option('--out <file>', '출력 파일').option('--json', 'JSON 출력')
60
+ .action(async (o: { out?: string; json?: boolean }) => {
61
+ try {
62
+ const { generateHandbookPage } = await import('../../scripts/lessons/handbook-page.js');
63
+ const result = generateHandbookPage({ stateDir: options.stateDir, out: o.out });
64
+ emit(result, o.json, 'skipped' in result
65
+ ? '교훈 페이지: 건너뜀(원장 비어 있음)'
66
+ : `교훈 페이지: ${result.out} · 총 ${result.total} · 강제 자리 없음 ${result.unenforced}`);
67
+ } catch (error) {
68
+ reportError('page', error instanceof Error ? error.message : String(error));
69
+ }
70
+ });
71
+ root.command('add <id>').requiredOption('--incident <text>').requiredOption('--cause <text>')
72
+ .requiredOption('--remedy <text>').requiredOption('--owner <name>').requiredOption('--source <path>')
73
+ .option('--enforced-by <paths>').option('--disproof <command>').option('--by <name>')
74
+ .action(handle('add', (id: string, o: { incident: string; cause: string; remedy: string; owner: string; source: string; enforcedBy?: string; disproof?: string; by?: string }) => {
75
+ const by = actor(o.by);
76
+ const row = ledger().add({ id, incident: o.incident, cause: o.cause, remedy: o.remedy, owner: o.owner,
77
+ source: o.source, by, ...(o.enforcedBy !== undefined ? { enforcedBy: o.enforcedBy } : {}),
78
+ ...(o.disproof !== undefined ? { disproof: o.disproof } : {}) });
79
+ emit(row, false, format(row));
80
+ }));
81
+ root.command('recur <id>').requiredOption('--source <path>').option('--note <text>').option('--by <name>')
82
+ .action(handle('recur', (id: string, o: { source: string; note?: string; by?: string }) => {
83
+ const row = ledger().recur(id, { source: o.source, note: o.note ?? '', by: actor(o.by) });
84
+ emit(row, false, format(row));
85
+ }));
86
+ root.command('enforce <id>').requiredOption('--enforced-by <paths>').option('--by <name>')
87
+ .action(handle('enforce', (id: string, o: { enforcedBy: string; by?: string }) => {
88
+ const row = ledger().enforce(id, { enforcedBy: o.enforcedBy, by: actor(o.by) });
89
+ emit(row, false, format(row));
90
+ }));
91
+ root.command('promote <id>').requiredOption('--rule <path>').option('--by <name>')
92
+ .action(handle('promote', (id: string, o: { rule: string; by?: string }) => {
93
+ const row = ledger().promote(id, { rulePath: o.rule, by: actor(o.by) });
94
+ emit(row, false, format(row));
95
+ }));
96
+ root.command('find <query>').option('--json').action(handle('find', (query: string, o: { json?: boolean }) => {
97
+ const rows = ledger().find(query);
98
+ emit(rows, o.json, rows.length ? rows.map(format).join('\n') : '교훈 0건');
99
+ }));
100
+ root.command('show <id>').option('--json').action(handle('show', (id: string, o: { json?: boolean }) => {
101
+ const row = ledger().get(id);
102
+ emit(row, o.json, detail(row));
103
+ }));
104
+ root.command('candidates').option('--json').action(handle('candidates', (o: { json?: boolean }) => {
105
+ const rows = ledger().candidates();
106
+ emit(rows, o.json, rows.length ? rows.map(format).join('\n') : '승격 후보 0건');
107
+ }));
108
+ }
@@ -14,7 +14,7 @@
14
14
  // 같은 케이던스). 레벨 제어만 데몬 REST(/v1/logs/level — 런타임 상태라 프로세스
15
15
  // 경유 필수). 설계: 내부 문서 `PLAN-unified-log-fabric-2026-07-13` §LF3.
16
16
 
17
- import { existsSync, readFileSync } from 'node:fs';
17
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
18
18
  import type { Command } from 'commander';
19
19
  import { homedir } from 'node:os';
20
20
  import { basename, dirname, join } from 'node:path';
@@ -37,7 +37,7 @@ import { isRenderCategory, RENDER_ORIENTED_PREFIXES } from '../mss/logging/rende
37
37
  import { readScopedRenderLogs } from '../mss/logging/scoped-level.js';
38
38
  import { formatClock } from '../time/format.js';
39
39
  import { HARNESS_SPACE_KINDS } from '../harness/harness-space.js';
40
- import { LogCursorNotFoundError, STORE_SAFETY_MAX } from '../mss/logging/log-store.js';
40
+ import { LOG_RETENTION_DEFAULTS, LogCursorNotFoundError, STORE_SAFETY_MAX } from '../mss/logging/log-store.js';
41
41
  import { resolveDaemonEndpoint } from '../nexus/daemon-endpoint.js';
42
42
  import { getElanousConfigDir } from '../elanous-config-dir.js';
43
43
  import { debug } from '../debug/log.js';
@@ -705,6 +705,74 @@ export function categoryCandidateWarning(
705
705
  return `안내: 지정한 카테고리 이름은 이 로그 스토어에서 관측된 적 없습니다: ${shown}${rest > 0 ? ` 외 ${rest}개` : ''}.`;
706
706
  }
707
707
 
708
+ /** A child-observation query can miss Pod-only logs even when the local store was read successfully. */
709
+ export function podObservationGapNotice(
710
+ query: Pick<LogQuery, 'categories' | 'exactCategories' | 'events'>,
711
+ podRuns: number,
712
+ ): string | null {
713
+ if (podRuns <= 0) return null;
714
+ const childCategory = (category: string): boolean => ['self-implement', 'self-dev', 'harness']
715
+ .some((prefix) => category.startsWith(prefix));
716
+ const categories = [...(query.categories ?? []), ...(query.exactCategories ?? [])];
717
+ if (!categories.some(childCategory) && !(categories.length === 0 && (query.events?.length ?? 0) > 0)) return null;
718
+ return `안내: 이 창에 Pod 에서 돈 런 ${podRuns}개 — Pod 자식의 관측은 이 logs.db 에 수집되지 않는다(POD-OBS). 원장으로 본다: elanous self run-ledger <runId>`;
719
+ }
720
+
721
+ /** Select ledger roots independently of DB existence; --all includes registered roots with no logs.db. */
722
+ function podLedgerDirectories(opts: LogsCliOpts, targets: readonly LogTarget[], instances: readonly LogInstanceView[]): string[] {
723
+ const roots = targets.map(({ dbPath }) => dirname(dirname(dbPath)));
724
+ if (opts.all && !opts.instance) {
725
+ roots.push(dirname(dirname(prodTarget().dbPath)));
726
+ for (const instance of instances) {
727
+ if (instance.kind !== 'test' || opts.includeTest) roots.push(instance.stateDir);
728
+ }
729
+ }
730
+ return [...new Set(roots.map((root) => join(root, 'run-ledger')))];
731
+ }
732
+
733
+ /** Read-only run-ledger scan over the independently selected instance roots. */
734
+ async function countPodRunLedgers(
735
+ directories: readonly string[],
736
+ query: Pick<LogQuery, 'sinceMs' | 'untilMs'>,
737
+ ): Promise<{ podRuns: number; unreadableLedgers: number }> {
738
+ const { loadRunLedger } = await import('../self-implement/run-ledger.js');
739
+ const untilMs = query.untilMs ?? Date.now();
740
+ const sinceMs = query.sinceMs ?? untilMs - LOG_RETENTION_DEFAULTS.maxAgeDays * 86_400_000;
741
+ let podRuns = 0;
742
+ let unreadableLedgers = 0;
743
+ for (const directory of directories) {
744
+ let files: string[];
745
+ try { files = readdirSync(directory); }
746
+ catch (error) {
747
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') unreadableLedgers += 1;
748
+ continue;
749
+ }
750
+ for (const file of files) {
751
+ if (!/^run-[a-zA-Z0-9-]+\.jsonl$/.test(file)) continue;
752
+ try {
753
+ const stamp = statSync(join(directory, file));
754
+ if (!stamp.isFile() || stamp.mtimeMs < sinceMs) continue;
755
+ const ledger = loadRunLedger(file.slice(0, -'.jsonl'.length), directory);
756
+ if (!ledger) { unreadableLedgers += 1; continue; }
757
+ const origin = ledger.find((entry) => entry.event === 'run-origin');
758
+ if (!origin || typeof origin.data.hostname !== 'string' || !origin.data.hostname.startsWith('si-task-')) continue;
759
+ let start = Number.POSITIVE_INFINITY;
760
+ let end = Number.NEGATIVE_INFINITY;
761
+ for (const entry of ledger) {
762
+ const time = entry.timestamp === undefined ? NaN : Date.parse(entry.timestamp);
763
+ if (!Number.isFinite(time)) continue;
764
+ start = Math.min(start, time);
765
+ end = Math.max(end, time);
766
+ }
767
+ // Older ledgers may have no timestamp at all; their mtime is the only available window evidence.
768
+ if (start === Number.POSITIVE_INFINITY) { start = stamp.mtimeMs; end = stamp.mtimeMs; }
769
+ if (start <= untilMs && end >= sinceMs) podRuns += 1;
770
+ } catch { unreadableLedgers += 1; }
771
+ }
772
+ }
773
+ return { podRuns, unreadableLedgers };
774
+ }
775
+
708
776
  // ── 0건 --event 안내: 이미 찍힌 기록에서만 답한다 ────────────────────────
709
777
  //
710
778
  // 갈림 ① 어느 범위에서 모으나 — **고정 24시간 관측 창**.
@@ -1480,9 +1548,25 @@ export async function runLogsCli(opts: LogsCliOpts, deps: RunLogsCliDeps = {}):
1480
1548
  : {};
1481
1549
  const queryStatus = registryScope.queryStatus;
1482
1550
  const unopenedStoreBanner = unopenedStores === undefined ? '안 본 스토어 수 미측정' : `안 본 스토어 ${unopenedStores}개`;
1551
+ const podGap = !opts.topFailures && podObservationGapNotice(query, 1) !== null
1552
+ ? await countPodRunLedgers(podLedgerDirectories(opts, resolved.targets, registryScope.instances), query)
1553
+ : null;
1554
+ const podNotice = podGap ? podObservationGapNotice(query, podGap.podRuns) : null;
1555
+ const podGapMeta = podGap && (podNotice || podGap.unreadableLedgers > 0)
1556
+ ? { _meta: { type: 'log-query-pod-observation-gap', ...podGap } }
1557
+ : null;
1558
+ const emitPodGap = (): void => {
1559
+ if (!opts.json && podGap) {
1560
+ if (podNotice) console.error(`${podNotice}${podGap.unreadableLedgers > 0 ? ` (못 읽은 원장 ${podGap.unreadableLedgers})` : ''}`);
1561
+ else if (podGap.unreadableLedgers > 0) console.error(`안내: Pod 원장 조회 중 못 읽은 원장 ${podGap.unreadableLedgers}개 — Pod 런 수를 확정할 수 없다(POD-OBS).`);
1562
+ }
1563
+ if (podGapMeta && podGap) debug.log('logs.query', 'pod-observation-gap', podGap);
1564
+ };
1483
1565
  if (opened.length === 0) {
1484
1566
  console.error(`elanous logs: 열 수 있는 로그 스토어 없음 — ${missing.join(' · ') || '타겟 0'} · scope=${JSON.stringify(scope)} · queryStatus=${JSON.stringify(queryStatus)}`);
1485
1567
  console.error(' 스토어는 해당 인스턴스 데몬(LF0 이후)이 한 번은 떠야 생성됩니다.');
1568
+ if (opts.json && podGapMeta) console.log(JSON.stringify(podGapMeta));
1569
+ emitPodGap();
1486
1570
  return 1;
1487
1571
  }
1488
1572
  if (missing.length > 0) console.error(`elanous logs: 스킵 — ${missing.join(' · ')}`);
@@ -1666,6 +1750,7 @@ export async function runLogsCli(opts: LogsCliOpts, deps: RunLogsCliDeps = {}):
1666
1750
  // JSONL 소비자는 stdout만 읽는다. 결손·중복 성격·상한 메타도 별도 행으로 내어 행별 JSON 파싱을 보존한다.
1667
1751
  if (opts.json && multiSurfaceDuplicateMeta) outputLines.unshift(JSON.stringify(multiSurfaceDuplicateMeta));
1668
1752
  if (opts.json && unreadableMeta) outputLines.unshift(JSON.stringify(unreadableMeta));
1753
+ if (opts.json && podGapMeta) outputLines.unshift(JSON.stringify(podGapMeta));
1669
1754
  if (unreadableInstances.length > 0 && !opts.json) {
1670
1755
  outputLines.unshift(`⚠️ 못 읽은 인스턴스 ${unreadableInstances.length}개: ${unreadableInstances.join(', ')}`);
1671
1756
  }
@@ -1676,6 +1761,7 @@ export async function runLogsCli(opts: LogsCliOpts, deps: RunLogsCliDeps = {}):
1676
1761
  const output = `${outputLines.join('\n')}\n`;
1677
1762
  await new Promise<void>((resolve, reject) => process.stdout.write(output, (error) => error ? reject(error) : resolve()));
1678
1763
  }
1764
+ emitPodGap();
1679
1765
  const eventWarning = eventCategoryWarning(opts.event);
1680
1766
  if (eventWarning) {
1681
1767
  console.error(eventWarning);
@@ -1744,6 +1830,8 @@ export async function runLogsCli(opts: LogsCliOpts, deps: RunLogsCliDeps = {}):
1744
1830
  }
1745
1831
  seed.sort((a, b) => a.row.ts_ms - b.row.ts_ms || a.row.id - b.row.id);
1746
1832
  for (const m of seed.slice(-20)) emit(m.row, m.name, m.path);
1833
+ if (opts.json && podGapMeta) console.log(JSON.stringify(podGapMeta));
1834
+ emitPodGap();
1747
1835
  const cursors = new Map<string, number>();
1748
1836
  for (const { name, store } of opened) {
1749
1837
  try { cursors.set(name, store.maxId()); } catch { cursors.set(name, 0); }
@@ -1993,6 +2081,20 @@ logsCmd.command('durations')
1993
2081
  process.exit(runLogsToolDurations(merged));
1994
2082
  });
1995
2083
 
2084
+ logsCmd.command('model-input')
2085
+ .description('최근 노드 종류별 모델 입력 토큰 기준선 (중앙값·p90·못 잼 별도 집계, 기본 3일)')
2086
+ .option('--since <t>', '조회 시작 (기본 3d · 30s|15m|2h|7d 또는 ISO)')
2087
+ .option('--json', '구조화 JSON 출력')
2088
+ .option('--test', 'cwd 레포의 격리 테스트 인스턴스(.elanous-test/)')
2089
+ .option('--instance <name>', '레지스트리 등록 인스턴스')
2090
+ .option('--all', '등록된 모든 로그 인스턴스 연합 조회')
2091
+ .option('--include-test', '--all 연합에 격리 test 인스턴스 포함')
2092
+ .action(async (o: import('./logs-model-input.js').LogsModelInputOpts, cmd: { optsWithGlobals(): Record<string, unknown> }) => {
2093
+ const merged = { ...cmd.optsWithGlobals(), ...o } as import('./logs-model-input.js').LogsModelInputOpts;
2094
+ const { runLogsModelInput } = await import('./logs-model-input.js');
2095
+ process.exitCode = runLogsModelInput(merged);
2096
+ });
2097
+
1996
2098
  logsCmd.command('degenerate')
1997
2099
  .description('수치 로그 필드의 always-same/all-zero/표본 부족 퇴화를 NDJSON으로 판정 (기본 표본 50)')
1998
2100
  .option('--category <prefix>', '카테고리 접두 필터 (복수는 쉼표)')
@@ -0,0 +1,131 @@
1
+ import { existsSync } from 'node:fs';
2
+ import type { LogStoreRow } from '../mss/logging/log-store.js';
3
+ import { LogStore } from '../mss/logging/log-store.js';
4
+ import type { ModelInputObservation } from '../harness/model-input-observation.js';
5
+ import { collectDegenerateRows } from './logs-degenerate.js';
6
+ import { parseSince, resolveLogTargets } from './logs-cli.js';
7
+
8
+ const DAY_MS = 86_400_000;
9
+ const CATEGORY = 'harness.model-input';
10
+ const EVENT = 'recorded';
11
+
12
+ type Baseline = { scope: ModelInputObservation['scope']; nodeKind: string; measured: number; unmeasured: number; median: number | null; p90: number | null };
13
+ export interface ModelInputReport {
14
+ since: string;
15
+ until: string;
16
+ stores: string[];
17
+ records: number;
18
+ baselines: Baseline[];
19
+ }
20
+ export interface LogsModelInputOpts {
21
+ since?: string;
22
+ json?: boolean;
23
+ test?: boolean;
24
+ instance?: string;
25
+ all?: boolean;
26
+ includeTest?: boolean;
27
+ }
28
+ export interface LogsModelInputDeps {
29
+ exists: typeof existsSync;
30
+ openReadOnly: typeof LogStore.openReadOnly;
31
+ resolveTargets: typeof resolveLogTargets;
32
+ now: () => number;
33
+ write: (line: string) => void;
34
+ writeError: (line: string) => void;
35
+ }
36
+ const DEFAULT_DEPS: LogsModelInputDeps = {
37
+ exists: existsSync, openReadOnly: LogStore.openReadOnly, resolveTargets: resolveLogTargets,
38
+ now: Date.now, write: console.log, writeError: console.error,
39
+ };
40
+
41
+ /** Only observations with a known state enter the baseline; unavailable usage is not zero. */
42
+ export function summarizeModelInput(rows: readonly LogStoreRow[], since: string, until: string, stores: string[]): ModelInputReport {
43
+ const groups = new Map<string, { scope: Baseline['scope']; nodeKind: string; values: number[]; unmeasured: number }>();
44
+ let records = 0;
45
+ for (const row of rows) {
46
+ if (row.category !== CATEGORY || row.event !== EVENT || !row.data) continue;
47
+ let data: unknown;
48
+ try { data = JSON.parse(row.data); } catch { continue; }
49
+ if (!data || typeof data !== 'object' || Array.isArray(data)) continue;
50
+ const observation = data as Partial<ModelInputObservation>;
51
+ if ((observation.scope !== 'harness-node' && observation.scope !== 'loop-tick')
52
+ || typeof observation.nodeKind !== 'string' || !observation.nodeKind.trim()) continue;
53
+ const measured = observation.status === 'measured'
54
+ && typeof observation.inputTokens === 'number'
55
+ && Number.isSafeInteger(observation.inputTokens) && observation.inputTokens >= 0;
56
+ const unmeasured = observation.status === 'unmeasured' && observation.inputTokens === null;
57
+ if (!measured && !unmeasured) continue;
58
+ const key = JSON.stringify([observation.scope, observation.nodeKind]);
59
+ const group = groups.get(key) ?? { scope: observation.scope, nodeKind: observation.nodeKind, values: [], unmeasured: 0 };
60
+ if (measured) group.values.push(observation.inputTokens as number);
61
+ else group.unmeasured += 1;
62
+ groups.set(key, group);
63
+ records += 1;
64
+ }
65
+ const baselines = [...groups.values()].map(({ scope, nodeKind, values, unmeasured }): Baseline => {
66
+ values.sort((a, b) => a - b);
67
+ const n = values.length;
68
+ return {
69
+ scope, nodeKind, measured: n, unmeasured,
70
+ median: n === 0 ? null : n % 2 === 1 ? values[Math.floor(n / 2)]! : (values[n / 2 - 1]! + values[n / 2]!) / 2,
71
+ p90: n === 0 ? null : values[Math.ceil(n * 0.9) - 1]!,
72
+ };
73
+ }).sort((a, b) => a.scope.localeCompare(b.scope) || a.nodeKind.localeCompare(b.nodeKind));
74
+ return { since, until, stores, records, baselines };
75
+ }
76
+
77
+ export function renderModelInput(report: ModelInputReport): string {
78
+ return [
79
+ `model input tokens · ${report.since} ~ ${report.until} · stores=${report.stores.join(', ') || '(없음)'} · records=${report.records}`,
80
+ 'scope\tnodeKind\tmeasured\tmedian\tp90\t못 잼',
81
+ ...(report.baselines.length === 0 ? ['(관측 표본 없음)'] : report.baselines.map((item) =>
82
+ `${item.scope}\t${item.nodeKind}\t${item.measured}\t${item.median ?? '—'}\t${item.p90 ?? '—'}\t${item.unmeasured}`)),
83
+ ].join('\n');
84
+ }
85
+
86
+ /** Read the entire bounded time window rather than just the newest default page. */
87
+ export function runLogsModelInput(opts: LogsModelInputOpts, deps: LogsModelInputDeps = DEFAULT_DEPS): number {
88
+ const untilMs = deps.now();
89
+ const relative = opts.since === undefined ? null : /^(\d+)(s|m|h|d)$/.exec(opts.since.trim());
90
+ const unitMs = { s: 1_000, m: 60_000, h: 3_600_000, d: DAY_MS };
91
+ const sinceMs = opts.since === undefined ? untilMs - 3 * DAY_MS
92
+ : relative ? untilMs - Number(relative[1]) * unitMs[relative[2] as keyof typeof unitMs]
93
+ : parseSince(opts.since);
94
+ if (sinceMs === null || !Number.isFinite(sinceMs) || sinceMs > untilMs) {
95
+ deps.writeError('elanous logs model-input: --since 파싱 불가 또는 미래 시각 (예: 3d, ISO)');
96
+ return 1;
97
+ }
98
+ const resolved = deps.resolveTargets({ test: opts.test, instance: opts.instance, all: opts.all, includeTest: opts.includeTest });
99
+ if (resolved.error) { deps.writeError(`elanous logs model-input: ${resolved.error}`); return 1; }
100
+ const rows: LogStoreRow[] = [];
101
+ const stores: string[] = [];
102
+ // Without a stable observation ID, equal timestamps and payloads in different
103
+ // stores may belong to distinct executions. Count every stored record.
104
+ for (const target of resolved.targets) {
105
+ if (!deps.exists(target.dbPath)) {
106
+ deps.writeError(`elanous logs model-input: 로그 스토어 없음: ${target.name} (${target.dbPath})`);
107
+ return 1;
108
+ }
109
+ let store: LogStore;
110
+ try { store = deps.openReadOnly(target.dbPath); }
111
+ catch (error) {
112
+ deps.writeError(`elanous logs model-input: ${target.name} 열기 실패: ${String(error)}`);
113
+ return 1;
114
+ }
115
+ try {
116
+ const scan = collectDegenerateRows(store, { exactCategories: [CATEGORY], events: [EVENT], sinceMs, untilMs });
117
+ if (scan.truncated) {
118
+ deps.writeError(`elanous logs model-input: ${target.name} 스캔 상한 도달 — --since 창을 좁혀라`);
119
+ return 1;
120
+ }
121
+ stores.push(target.name);
122
+ rows.push(...scan.rows);
123
+ } catch (error) {
124
+ deps.writeError(`elanous logs model-input: ${target.name} 조회 실패: ${String(error)}`);
125
+ return 1;
126
+ } finally { store.close(); }
127
+ }
128
+ const report = summarizeModelInput(rows, new Date(sinceMs).toISOString(), new Date(untilMs).toISOString(), stores);
129
+ deps.write(opts.json ? JSON.stringify(report) : renderModelInput(report));
130
+ return 0;
131
+ }
package/src/cli/pr-cli.ts CHANGED
@@ -53,6 +53,8 @@ import {
53
53
  } from './pr-granularity.js';
54
54
  import { branchLineageSlug, findSiblingPrs } from './pr-lineage.js';
55
55
  import { parseReleaseNoteSection } from '../release-loop/release-note.js';
56
+ import { landingFreezeMessage } from '../release-loop/landing-freeze.js';
57
+ import { admitLandingMerge } from '../self-implement/frozen-merges.js';
56
58
  import { enqueueL8ShadowQueue, runL8ShadowQueue, shadowPrNumber, statusL8ShadowQueue, type L8ShadowQueueDeps } from '../self-implement/l8-merge-queue.js';
57
59
 
58
60
  export { branchLineageSlug, findSiblingPrs };
@@ -1574,7 +1576,32 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
1574
1576
  return 0;
1575
1577
  }
1576
1578
 
1577
- let merged = manager.mergePrOutcome(upsert.url);
1579
+ const prNumber = Number(/\/(\d+)$/.exec(upsert.url)?.[1]);
1580
+ const landedHead = run('git', ['rev-parse', 'HEAD'], { cwd });
1581
+ const headCommit = landedHead.ok && /^[a-f0-9]{40}$/i.test(landedHead.out.trim()) ? landedHead.out.trim() : undefined;
1582
+ const validPr = Number.isSafeInteger(prNumber) && prNumber > 0;
1583
+ const heldEntry = validPr && headCommit ? { prNumber, headCommit, repoRoot: cwd } : null;
1584
+ const admission = admitLandingMerge(heldEntry, undefined, {}, validPr ? { prNumber, repoRoot: cwd, ...(headCommit ? { headCommit } : {}) } : undefined);
1585
+ if (admission.kind === 'held' && !heldEntry) {
1586
+ out.error(`✗ freeze: ready PR의 검증 HEAD/번호를 보존하지 못했습니다: ${upsert.url}`);
1587
+ return 1;
1588
+ }
1589
+ if (admission.kind === 'held') {
1590
+ const frozen = admission.freeze;
1591
+ debug.log('harness.merge', 'frozen', { prUrl: upsert.url, reason: frozen.reason, until: frozen.until });
1592
+ record('freeze', true, { url: upsert.url, reason: frozen.reason, until: frozen.until });
1593
+ out.log(`✓ ready: ${upsert.url} — ${landingFreezeMessage(frozen)} · 병합 연기`);
1594
+ return 0;
1595
+ }
1596
+ if (admission.kind === 'taken') {
1597
+ record('freeze', true, { url: upsert.url, resumedElsewhere: true });
1598
+ out.log(`✓ ready: ${upsert.url} — 동결이 막 풀려 보류 병합 재개가 이 PR 을 맡았습니다`);
1599
+ return 0;
1600
+ }
1601
+ const landing = admission;
1602
+ let merged: ReturnType<typeof manager.mergePrOutcome>;
1603
+ try {
1604
+ merged = manager.mergePrOutcome(upsert.url);
1578
1605
  // `GIT-S14` — 병합이 «미완료(OPEN)»로 끝났고 GitHub 가 아직 `mergeable=UNKNOWN`(병합 가능 여부 계산 중)이면
1579
1606
  // 짧게 기다렸다 다시 병합한다. 표본 셋(08-25 · 09-24 🅢 #20233 등)이 모두 «같은 명령 재시도에 병합»이었다.
1580
1607
  // ⛔ UNKNOWN 일 때만 재시도한다 — CONFLICTING 등 다른 값은 기다려도 안 풀린다(그대로 실패를 낸다).
@@ -1588,6 +1615,7 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
1588
1615
  await sleep(PR_LAND_MERGEABLE_UNKNOWN_WAIT_MS);
1589
1616
  merged = manager.mergePrOutcome(upsert.url);
1590
1617
  }
1618
+ } finally { landing.end(merged!?.ok === true); }
1591
1619
  if (!merged.ok) {
1592
1620
  // `GIT-S76` — 「미완료(OPEN)」만으로는 «충돌»과 «아직 계산 중»이 안 갈린다. GitHub 의 병합 가능 상태를 같이 싣는다.
1593
1621
  // 되읽기에 실패하면 종전 문면 그대로다(못 읽은 것을 「충돌 아님」으로 적지 않는다).