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
@@ -47,6 +47,7 @@
47
47
  # 환경 노브: CH_PR · CH_REPO · CH_INTERVAL(기본 60) · CH_BODY_CHARS(기본 1500) · CH_WATCH_BODY(기본 1)
48
48
 
49
49
  set -u
50
+ set -o pipefail
50
51
 
51
52
  PR_NUM="${CH_PR:-23032}" # ⛔ 2026-10-03: #20798 가 2,500 상한 → 채널 «6»(OP) # ⛔ 2026-09-27: #16815 가 2,500 상한에 닿아 채널 «5» 로 옮김(🅞 · 벽에 «닿은 뒤» — 미리 못 옮겼다)
52
53
  # 📏 이력: #5730(07-28~08-12) → #8328(08-11~08-25) → #12577(08-25~09-10) → #16815(09-10~09-27) → #20798(09-27~10-03) → #23032.
@@ -220,7 +220,17 @@ fi
220
220
  echo "[coord-post] 발신 성공$VIA · 채널 #$PR · 신원 [$ID] · 시각 $TS" >&2
221
221
  # Only a successfully sent post is recorded; never pass the body or later lines to the ledger.
222
222
  POST_URL=$(printf '%s\n' "$POST_OUTPUT" | grep -oE 'https://github\.com/[^[:space:]]+/issues/[0-9]+#issuecomment-[0-9]+|https://github\.com/[^[:space:]]+/pull/[0-9]+#issuecomment-[0-9]+' | head -1 || true)
223
- if ! bun bin/elanous.mjs coord event record --seat "$ID" --header "$(head -1 "$TMP")" --url "$POST_URL" >/dev/null 2>&1; then
223
+ # The channel is a production surface, so its record belongs in the production ledger, not this tree's
224
+ # test universe (OP 10-04 18:00). An explicit ELANOUS_STATE_DIR is the caller's chosen universe and wins;
225
+ # otherwise prefer the installed CLI (resolves prod from any tree), else pin the source CLI to prod.
226
+ if [ -n "${ELANOUS_STATE_DIR:-}" ]; then
227
+ RECORD=(bun bin/elanous.mjs coord event record)
228
+ elif command -v eln >/dev/null 2>&1; then
229
+ RECORD=(eln coord event record)
230
+ else
231
+ RECORD=(env ELANOUS_STATE_DIR="$HOME/.elanous" bun bin/elanous.mjs --config-dir "$HOME/.elanous" coord event record)
232
+ fi
233
+ if ! "${RECORD[@]}" --seat "$ID" --header "$(head -1 "$TMP")" --url "$POST_URL" >/dev/null 2>&1; then
224
234
  echo '⚠️ 맥락 원장 기록 실패(발신은 됐다)' >&2
225
235
  fi
226
236
  exit 0
@@ -0,0 +1,144 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { readFileSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ import { discordDecisionOwner } from '../src/decisions/discord-decision-cards.js';
5
+ import { handleDiscordSeatWork, type DiscordSeatWorkDeps } from '../src/intake-plane/discord-seat-work.js';
6
+ import { submitIntakeWork } from '../src/intake-plane/submit-intake-work.js';
7
+ import type { MsgStore } from '../src/msg/msg-store.js';
8
+ import { dispatchCeoTask } from '../src/seat-dispatch/ceo-commands.js';
9
+ import { resolveSeat } from '../src/seat-address/seat-address.js';
10
+ import { getUserConfig, type UserConfig } from '../src/user-config.js';
11
+
12
+ export type ProbeResult = {
13
+ text: string;
14
+ ms: number;
15
+ route: 'dispatch' | 'ask' | 'submit' | 'none';
16
+ seat: string | null;
17
+ body: string | null;
18
+ reply: string | null;
19
+ };
20
+
21
+ type ProbeOptions = { config?: UserConfig; dm?: boolean; live?: boolean; liveDeps?: DiscordSeatWorkDeps; messageId?: string };
22
+
23
+ /** Execute the real Discord seat router; dry dependencies never open a live store or delivery channel. */
24
+ export async function probeDiscordSeatWork(text: string, options: ProbeOptions = {}): Promise<ProbeResult> {
25
+ const config = options.config ?? getUserConfig();
26
+ const ownerId = discordDecisionOwner(config);
27
+ if (options.dm && !ownerId) throw new Error('Discord 소유자 ID가 없어 DM을 재현할 수 없습니다.');
28
+ const messageId = options.messageId ?? 'probe-1';
29
+ const msg = { channelId: options.dm ? `probe-dm-${ownerId}` : 'probe-channel', messageId,
30
+ ...(ownerId ? { userId: ownerId } : {}), isDm: options.dm ?? false };
31
+ let route: ProbeResult['route'] = 'none';
32
+ let seat: string | null = null;
33
+ let body: string | null = null;
34
+ const capture = (path: ProbeResult['route'], seatId: string | null, forwarded: string) => {
35
+ route = path;
36
+ seat = seatId;
37
+ body = Array.from(forwarded).slice(0, 80).join('');
38
+ };
39
+ const dry: DiscordSeatWorkDeps = {
40
+ config,
41
+ // An unknown address must not cause registry I/O or a persona/LLM answer in a dry run.
42
+ personaSource: { list: () => [], get: () => undefined },
43
+ personaAnswer: async () => { throw new Error('dry persona answer unavailable'); },
44
+ answer: async () => null,
45
+ commandDeps: { ownerId, replyTarget: null, runGh: async () => { throw new Error('dry channel delivery unavailable'); },
46
+ append: () => { throw new Error('dry seat delivery unavailable'); } },
47
+ dispatch: async (seatId, forwarded) => {
48
+ capture('dispatch', seatId, forwarded);
49
+ return { channel: 'unknown', reply: `마른 실행 — ${seatId}에 맡길 예정 (실제 맡김 없음).` };
50
+ },
51
+ submit: async (input) => {
52
+ capture('submit', resolveSeat(input.text.split(/\s+/)[0] ?? '')?.id ?? null, input.text);
53
+ return { ok: true, track: 'graph', acceptanceId: 'DRY-RUN (모의 · 실제 접수 없음)' };
54
+ },
55
+ askDeps: {
56
+ open: () => { const db = new Database(':memory:'); return { db, close: () => db.close() } as unknown as MsgStore; },
57
+ dispatch: async (seatId, forwarded) => {
58
+ capture('ask', seatId, forwarded);
59
+ return { channel: 'unknown', reply: '마른 실행 — 질문을 맡기지 않았습니다.' };
60
+ },
61
+ send: async () => { throw new Error('dry Discord delivery unavailable'); },
62
+ },
63
+ };
64
+ const live: DiscordSeatWorkDeps = {
65
+ ...options.liveDeps,
66
+ config,
67
+ dispatch: async (seatId, forwarded, deps, extra) => {
68
+ capture('dispatch', seatId, forwarded);
69
+ return (options.liveDeps?.dispatch ?? dispatchCeoTask)(seatId, forwarded, deps, extra);
70
+ },
71
+ submit: async (input, deps) => {
72
+ capture('submit', resolveSeat(input.text.split(/\s+/)[0] ?? '')?.id ?? null, input.text);
73
+ return (options.liveDeps?.submit ?? submitIntakeWork)(input, deps);
74
+ },
75
+ askDeps: {
76
+ ...options.liveDeps?.askDeps,
77
+ dispatch: async (seatId, forwarded, deps, extra) => {
78
+ capture('ask', seatId, forwarded);
79
+ return (options.liveDeps?.askDeps?.dispatch ?? dispatchCeoTask)(seatId, forwarded, deps, extra);
80
+ },
81
+ // This CLI has no Discord transport: even live mode must not post to Discord.
82
+ send: async () => { throw new Error('probe does not send Discord messages'); },
83
+ },
84
+ };
85
+ const start = performance.now();
86
+ const reply = await handleDiscordSeatWork(text, msg, options.live ? live : dry);
87
+ return { text: Array.from(text).slice(0, 80).join(''), ms: Math.round((performance.now() - start) * 100) / 100,
88
+ route, seat, body, reply: reply === null ? null : Array.from(reply).slice(0, 120).join('') };
89
+ }
90
+
91
+ /** Extract only the '치는 문장' column of a Markdown runbook table, in row order. */
92
+ export function runbookTexts(markdown: string): string[] {
93
+ const lines = markdown.split(/\r?\n/);
94
+ const header = lines.findIndex((line) => /^\|.*\|\s*치는 문장(?:\s*\([^|]*\))?\s*\|/.test(line));
95
+ if (header < 0) throw new Error('대본의 «치는 문장» 표를 찾지 못했습니다.');
96
+ const columns = lines[header]!.split('|').slice(1, -1).map((cell) => cell.trim());
97
+ const index = columns.findIndex((cell) => cell.startsWith('치는 문장'));
98
+ const texts: string[] = [];
99
+ for (const line of lines.slice(header + 2)) {
100
+ if (!line.startsWith('|')) break;
101
+ const cells = line.split('|').slice(1, -1);
102
+ if (cells.length !== columns.length) throw new Error('대본 표의 열 수가 맞지 않습니다.');
103
+ const cell = cells[index]?.trim() ?? '';
104
+ if (!cell.startsWith('`') || !cell.endsWith('`')) throw new Error('치는 문장 칸이 백틱으로 둘러싸여 있지 않습니다.');
105
+ texts.push(cell.slice(1, -1));
106
+ }
107
+ if (!texts.length) throw new Error('대본에 치는 문장이 없습니다.');
108
+ return texts;
109
+ }
110
+
111
+ export async function runProbeCli(argv: string[], config: UserConfig = getUserConfig()): Promise<ProbeResult[]> {
112
+ let text: string | undefined;
113
+ let runbook: string | undefined;
114
+ let dm = false;
115
+ let live = false;
116
+ let json = false;
117
+ for (let i = 0; i < argv.length; i++) {
118
+ const flag = argv[i];
119
+ if (flag === '--text' || flag === '--runbook') {
120
+ const value = argv[++i];
121
+ if (!value || value.startsWith('--')) throw new Error(`${flag} 값이 필요합니다.`);
122
+ if (flag === '--text') text = value;
123
+ else runbook = value;
124
+ } else if (flag === '--dm') dm = true;
125
+ else if (flag === '--live') live = true;
126
+ else if (flag === '--json') json = true;
127
+ else throw new Error(`알 수 없는 옵션: ${flag}`);
128
+ }
129
+ if (Boolean(text) === Boolean(runbook)) throw new Error('--text 또는 --runbook 중 하나만 지정하세요.');
130
+ const texts = runbook ? runbookTexts(readFileSync(resolve(runbook), 'utf8')) : [text!];
131
+ const results: ProbeResult[] = [];
132
+ for (const [index, line] of texts.entries()) {
133
+ const result = await probeDiscordSeatWork(line, { config, dm, live, messageId: `probe-${index + 1}` });
134
+ results.push(result);
135
+ if (!json) console.log(`글: ${result.text} | ${result.ms}ms | 길: ${result.route} | 자리: ${result.seat ?? '-'} | 본문: ${result.body ?? '-'} | 봇 답: ${result.reply ?? '-'}`);
136
+ }
137
+ if (json) console.log(JSON.stringify(results));
138
+ return results;
139
+ }
140
+
141
+ if (import.meta.main) {
142
+ try { await runProbeCli(process.argv.slice(2)); }
143
+ catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; }
144
+ }
@@ -2,7 +2,7 @@
2
2
  // 공개 문서에 적힌 `elanous …` 명령·하위 명령·플래그가 «실제 CLI» 에 있나 — 문서가 실제와 어긋나는 1순위 원인을 잡는다.
3
3
  // bun scripts/docs-cli-check.ts [--json] [files…] (기본 = release/public/내부 문서 `*` ⊕ README.md)
4
4
  // 🩸 계기(2026-09-25 🅢 RFC 피드백 ③): README·install.md 가 거짓이 된 원인이 전부 «설치기·doctor 가 바뀌었는데 문서가 모름»이었다.
5
- // 자 = `bun bin/elanous.mjs <cmd> [<sub>] --help` 산출(원천) — 문서의 코드 블록과 인라인 코드에서 `elanous ` 로 시작하는 것만 본다.
5
+ // 자 = `bun bin/elanous.mjs <cmd> [<sub>] [<subsub>] --help` 산출(원천) — 문서의 코드 블록과 인라인 코드에서 `elanous ` 로 시작하는 것만 본다.
6
6
  // ⛔ 못 본 것은 «없다»로 적지 않는다: `--help` 가 실패하면 그 명령은 `unmeasured` 로 따로 센다.
7
7
  import { readdirSync, readFileSync, statSync } from 'node:fs';
8
8
  import { join, relative, resolve } from 'node:path';
@@ -10,7 +10,7 @@ import { spawnSync } from 'node:child_process';
10
10
 
11
11
  const REPO = resolve(import.meta.dir, '..');
12
12
 
13
- export interface DocCommand { file: string; line: number; text: string; cmd: string; sub?: string; flags: string[] }
13
+ export interface DocCommand { file: string; line: number; text: string; cmd: string; sub?: string; subsub?: string; flags: string[] }
14
14
  export interface Finding { kind: 'unknown-command' | 'unknown-subcommand' | 'unknown-flag' | 'unmeasured'; ref: DocCommand; detail: string }
15
15
 
16
16
  /** 코드 블록(``` … ```) 줄과 인라인 코드(`elanous …`)에서 elanous 호출을 뽑는다. 자리표(`<x>`)·셸 변수는 인자로 보고 넘긴다. */
@@ -24,15 +24,17 @@ export function extractElanousCommands(file: string, text: string): DocCommand[]
24
24
  for (const m of raw.matchAll(/`(elanous [^`]+)`/g)) candidates.push(m[1]!);
25
25
  for (const c of candidates) {
26
26
  for (const seg of c.split(/&&|\|\||;|\|/)) {
27
- const m = /(?:^|\s)elanous\s+(.+)$/.exec(seg.trim());
27
+ const m = /^(?:(?:\$\s+|>\s+|sudo\s+|[A-Za-z_][A-Za-z0-9_]*=\S+\s+))*elanous\s+(.+)$/.exec(seg.trim());
28
28
  if (!m) continue;
29
29
  const tokens = m[1]!.trim().split(/\s+/).filter(Boolean);
30
30
  const cmd = tokens[0];
31
31
  if (!cmd || !/^[a-z][a-z0-9:-]*$/.test(cmd)) continue;
32
32
  const second = tokens[1];
33
33
  const sub = second && /^[a-z][a-z0-9-]*$/.test(second) ? second : undefined;
34
+ const third = tokens[2];
35
+ const subsub = sub && third && /^[a-z][a-z0-9-]*$/.test(third) ? third : undefined;
34
36
  const flags = tokens.filter((t) => /^--[a-z]/.test(t)).map((t) => t.replace(/[=,.)].*$/, ''));
35
- out.push({ file, line: i + 1, text: seg.trim(), cmd, ...(sub ? { sub } : {}), flags });
37
+ out.push({ file, line: i + 1, text: seg.trim(), cmd, ...(sub ? { sub } : {}), ...(subsub ? { subsub } : {}), flags });
36
38
  }
37
39
  }
38
40
  });
@@ -63,13 +65,28 @@ export function checkCommands(refs: readonly DocCommand[], help: HelpRunner = de
63
65
  const cmdHelp = get([ref.cmd]);
64
66
  if (!cmdHelp.ok) { findings.push({ kind: 'unmeasured', ref, detail: `elanous ${ref.cmd} --help 실패` }); continue; }
65
67
  let flagsHelp = cmdHelp.out;
68
+ let flagsPath = `elanous ${ref.cmd}`;
66
69
  if (ref.sub && /\nCommands:\n/.test(cmdHelp.out)) {
67
70
  if (!listed(cmdHelp.out, ref.sub)) { findings.push({ kind: 'unknown-subcommand', ref, detail: `elanous ${ref.cmd} ${ref.sub}` }); continue; }
71
+ flagsPath = `elanous ${ref.cmd} ${ref.sub}`;
68
72
  const subHelp = get([ref.cmd, ref.sub]);
69
- if (subHelp.ok) flagsHelp = subHelp.out;
73
+ if (!subHelp.ok) {
74
+ findings.push({ kind: 'unmeasured', ref, detail: `${flagsPath} --help 실패` });
75
+ continue;
76
+ }
77
+ flagsHelp = subHelp.out;
78
+ if (ref.subsub && listed(subHelp.out, ref.subsub)) {
79
+ flagsPath += ` ${ref.subsub}`;
80
+ const subsubHelp = get([ref.cmd, ref.sub, ref.subsub]);
81
+ if (!subsubHelp.ok) {
82
+ findings.push({ kind: 'unmeasured', ref, detail: `${flagsPath} --help 실패` });
83
+ continue;
84
+ }
85
+ flagsHelp = subsubHelp.out;
86
+ }
70
87
  }
71
88
  for (const f of ref.flags) {
72
- if (!new RegExp(`(^|[\\s,])${f}(\\b|[\\s,=<\\[])`, 'm').test(flagsHelp)) findings.push({ kind: 'unknown-flag', ref, detail: `${f} (elanous ${ref.cmd}${ref.sub ? ` ${ref.sub}` : ''})` });
89
+ if (!new RegExp(`(^|[\\s,])${f}(\\b|[\\s,=<\\[])`, 'm').test(flagsHelp)) findings.push({ kind: 'unknown-flag', ref, detail: `${f} (${flagsPath})` });
73
90
  }
74
91
  }
75
92
  return findings;
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env bun
2
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
3
+ import { join, relative, resolve, sep } from 'node:path';
4
+ import { parse } from 'yaml';
5
+ import { debug } from '../src/debug/log.js';
6
+
7
+ const DOCS = 'release/public/docs';
8
+ const LEDGER = 'release/public/expose-rubric.yaml';
9
+ const CRITERIA = ['maturity', 'value', 'reproducible', 'evidence', 'confidential', 'brand', 'support'] as const;
10
+ type Criterion = { status: 'pass' | 'fail' | 'n/a'; reason: string };
11
+ type Entry = {
12
+ path: string;
13
+ verdict: 'public' | 'beta' | 'internal';
14
+ criteria: Record<(typeof CRITERIA)[number], Criterion>;
15
+ judge: string;
16
+ techReview?: string;
17
+ at: string;
18
+ };
19
+ type RubricResult = { docs: number; missing: string[]; orphan: string[]; failing: Array<{ path: string; criteria: string[]; verdict?: 'beta' }>; internal: string[]; unreviewed: string[]; unjudged: string[] };
20
+
21
+ function validEntry(value: unknown): value is Entry {
22
+ if (!value || typeof value !== 'object') return false;
23
+ const entry = value as Partial<Entry>;
24
+ if (typeof entry.path !== 'string' || !/^release\/public\/docs\/[^/]+\.md$/.test(entry.path)
25
+ || !['public', 'beta', 'internal'].includes(entry.verdict ?? '')
26
+ // The exposure judge is the CMO seat (EXPOSE-RUBRIC); any other non-empty name is not a judgement.
27
+ || entry.judge !== 'MK'
28
+ || (entry.techReview !== undefined && (typeof entry.techReview !== 'string' || !entry.techReview.trim()))
29
+ || typeof entry.at !== 'string' || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(entry.at)
30
+ || !Number.isFinite(Date.parse(entry.at)) || !entry.criteria || typeof entry.criteria !== 'object') return false;
31
+ const criteria = entry.criteria as Record<string, unknown>;
32
+ if (Object.keys(criteria).length !== CRITERIA.length) return false;
33
+ return CRITERIA.every((key) => {
34
+ const item = criteria[key] as Partial<Criterion> | undefined;
35
+ return item && ['pass', 'fail', 'n/a'].includes(item.status ?? '')
36
+ && typeof item.reason === 'string' && !!item.reason.trim() && !item.reason.includes('\n');
37
+ });
38
+ }
39
+
40
+ function selectedPath(input: string, root: string): string | undefined {
41
+ const path = relative(root, resolve(root, input)).split(sep).join('/');
42
+ return /^release\/public\/docs\/[^/]+\.md$/.test(path) ? path : undefined;
43
+ }
44
+
45
+ export function checkExposeRubric(root: string, files?: readonly string[]): RubricResult {
46
+ const data = parse(readFileSync(join(root, LEDGER), 'utf8')) as unknown;
47
+ if (!data || typeof data !== 'object' || !('entries' in data) || !Array.isArray(data.entries)) throw new Error(`invalid ledger: ${LEDGER}`);
48
+ const entries = new Map<string, Entry>();
49
+ for (const value of data.entries as unknown[]) {
50
+ if (!validEntry(value) || entries.has(value.path)) throw new Error(`invalid or duplicate ledger entry: ${JSON.stringify(value)}`);
51
+ entries.set(value.path, value);
52
+ }
53
+ const scope = files === undefined ? undefined : new Set(files.map((file) => selectedPath(file, root)).filter((path): path is string => path !== undefined));
54
+ const docs = readdirSync(join(root, DOCS)).filter((name) => name.endsWith('.md'))
55
+ .map((name) => `${DOCS}/${name}`).filter((path) => !scope || scope.has(path)).sort();
56
+ const missing = docs.filter((path) => !entries.has(path));
57
+ const orphan: string[] = [];
58
+ const failing: RubricResult['failing'] = [];
59
+ const internal: string[] = [];
60
+ const unreviewed: string[] = [];
61
+ const unjudged: string[] = [];
62
+ for (const entry of entries.values()) {
63
+ if (scope && !scope.has(entry.path)) continue;
64
+ if (!existsSync(join(root, entry.path))) {
65
+ orphan.push(entry.path);
66
+ continue;
67
+ }
68
+ if (entry.verdict === 'internal') internal.push(entry.path);
69
+ if (entry.verdict === 'public' || entry.verdict === 'beta') {
70
+ const failed = entry.verdict === 'public'
71
+ ? CRITERIA.filter((key) => entry.criteria[key].status === 'fail')
72
+ : (entry.criteria.confidential.status === 'fail' ? ['confidential'] : []);
73
+ if (failed.length) failing.push({ path: entry.path, criteria: failed, ...(entry.verdict === 'beta' ? { verdict: 'beta' as const } : {}) });
74
+ if (entry.verdict === 'public' && entry.techReview !== 'TC') unreviewed.push(entry.path);
75
+ // An outward verdict with no criterion actually passed is a placeholder, not a judgement (all n/a = never checked).
76
+ if (!CRITERIA.some((key) => entry.criteria[key].status === 'pass')) unjudged.push(entry.path);
77
+ }
78
+ }
79
+ orphan.sort();
80
+ failing.sort((a, b) => a.path.localeCompare(b.path));
81
+ internal.sort();
82
+ unreviewed.sort();
83
+ unjudged.sort();
84
+ const result = { docs: docs.length, missing, orphan, failing, internal, unreviewed, unjudged };
85
+ debug.log('expose.rubric', 'checked', { docs: result.docs, missing: missing.length, orphan: orphan.length, failing: failing.length, unjudged: unjudged.length });
86
+ return result;
87
+ }
88
+
89
+ if (import.meta.main) {
90
+ try {
91
+ const args = process.argv.slice(2);
92
+ if (args.includes('--help')) {
93
+ console.log('Usage: bun scripts/expose-rubric-check.ts [--strict] [--json] [--files <path...>]');
94
+ } else {
95
+ const options = new Set(['--strict', '--json', '--files']);
96
+ const index = args.indexOf('--files');
97
+ const beforeFiles = args.slice(0, index < 0 ? args.length : index);
98
+ const files = index < 0 ? undefined : args.slice(index + 1);
99
+ if (beforeFiles.some((arg) => !options.has(arg) || arg === '--files')
100
+ || (files && (!files.length || files.some((file) => file.startsWith('--'))))) throw new Error('invalid arguments: use --files <path...> last');
101
+ const result = checkExposeRubric(process.cwd(), files);
102
+ if (args.includes('--json')) console.log(JSON.stringify(result));
103
+ else {
104
+ for (const path of result.missing) console.error(`경고: 원장 없음: ${path}`);
105
+ for (const path of result.orphan) console.error(`경고: 파일 없음: ${path}`);
106
+ for (const item of result.failing) console.error(`경고: ${item.verdict ?? 'public'} 인데 fail (${item.criteria.join(', ')}): ${item.path}`);
107
+ for (const path of result.internal) console.error(`경고: internal 인데 공개 경로: ${path}`);
108
+ for (const path of result.unreviewed) console.error(`경고: public 기술 정확성 TC 미검토: ${path}`);
109
+ for (const path of result.unjudged) console.error(`경고: 미판정(일곱 기준 중 pass 0): ${path}`);
110
+ console.error(`expose-rubric — docs=${result.docs} missing=${result.missing.length} orphan=${result.orphan.length} failing=${result.failing.length} internal=${result.internal.length} unreviewed=${result.unreviewed.length} unjudged=${result.unjudged.length}`);
111
+ }
112
+ if (args.includes('--strict') && (result.missing.length || result.orphan.length || result.failing.length || result.internal.length || result.unjudged.length)) process.exitCode = 1;
113
+ }
114
+ } catch (error) {
115
+ console.error(error instanceof Error ? error.message : String(error));
116
+ process.exitCode = 2;
117
+ }
118
+ }
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env bun
2
+ /** Machine-measured suggestions only; MK judges and merges the exposure ledger. */
3
+ import { readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from 'node:fs';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { join, relative, resolve, sep } from 'node:path';
6
+ import { stringify } from 'yaml';
7
+ import { debug } from '../src/debug/log.js';
8
+ import { checkBrand } from './brand/check.js';
9
+ import { checkCommands, extractElanousCommands, type HelpRunner } from './docs-cli-check.js';
10
+ import { scanLeaks } from './public-export.js';
11
+
12
+ const DOCS = 'release/public/docs';
13
+ const LEDGER = 'release/public/expose-rubric.yaml';
14
+ const KEYS = ['reproducible', 'confidential', 'brand'] as const;
15
+ type Criterion = { status: 'pass' | 'fail' | 'n/a'; reason: string };
16
+ type Suggestion = { path: string; criteria: Record<(typeof KEYS)[number], Criterion> };
17
+
18
+ function unmeasured(error: unknown): Criterion {
19
+ const detail = error instanceof Error ? error.message : String(error);
20
+ return { status: 'n/a', reason: `측정 불가: ${detail.split(/\r?\n/)[0] || '알 수 없는 오류'}` };
21
+ }
22
+
23
+ function measure(run: () => Criterion): Criterion {
24
+ try { return run(); } catch (error) { return unmeasured(error); }
25
+ }
26
+
27
+ /** Files are repository-relative paths (or absolute paths within root); the ledger is never read or written. */
28
+ export function suggestExposeRubric(root: string, files?: readonly string[], help?: HelpRunner): Suggestion[] {
29
+ const names = files ?? readdirSync(join(root, DOCS)).filter((name) => name.endsWith('.md')).map((name) => `${DOCS}/${name}`);
30
+ const paths = [...new Set(names.map((name) => relative(root, resolve(root, name)).split(sep).join('/')))];
31
+ if (paths.some((path) => !/^release\/public\/docs\/[^/]+\.md$/.test(path))) throw new Error('문서 경로는 release/public/docs/*.md 이어야 합니다');
32
+ type HelpResult = ReturnType<HelpRunner> & { failure?: string };
33
+ let helpRuns = 0;
34
+ const rootHelp: (args: string[]) => HelpResult = help ?? ((args) => {
35
+ helpRuns++;
36
+ const result = spawnSync('bun', ['bin/elanous.mjs', '--test', ...args, '--help'], {
37
+ cwd: resolve(import.meta.dir, '..'), encoding: 'utf8', timeout: 60_000, env: { ...process.env, NO_COLOR: '1' },
38
+ });
39
+ const error = result.error as NodeJS.ErrnoException | undefined;
40
+ return {
41
+ ok: result.status === 0 && !error,
42
+ out: `${result.stdout ?? ''}\n${result.stderr ?? ''}`,
43
+ failure: error?.code === 'ETIMEDOUT' ? 'timeout'
44
+ : error ? error.message
45
+ : result.status !== 0 ? `exit ${result.status ?? 'unknown'}` : undefined,
46
+ };
47
+ });
48
+ const helpCache = new Map<string, { ok: true; out: string } | { ok: false; reason: string }>();
49
+ let helpFailures = 0;
50
+ const checkedHelp: HelpRunner = (args) => {
51
+ const key = args.join(' ');
52
+ let cached = helpCache.get(key);
53
+ if (!cached) {
54
+ const runOnce = (): { ok: true; out: string } | { ok: false; reason: string } => {
55
+ try {
56
+ const result = rootHelp(args);
57
+ if (result.ok && result.out.trim()) return { ok: true, out: result.out };
58
+ helpFailures++;
59
+ return { ok: false, reason: result.failure ?? (result.out.trim() ? '실행 실패' : '빈 출력') };
60
+ } catch (error) {
61
+ helpFailures++;
62
+ return { ok: false, reason: error instanceof Error ? error.message : String(error) };
63
+ }
64
+ };
65
+ cached = runOnce();
66
+ if (!cached.ok) cached = runOnce();
67
+ helpCache.set(key, cached);
68
+ }
69
+ if (!cached.ok) throw new Error(`elanous ${key ? `${key} ` : ''}--help 실패(${cached.reason.split(/\r?\n/)[0] || '알 수 없는 오류'})`);
70
+ return cached;
71
+ };
72
+ const results = paths.sort().map((path): Suggestion => {
73
+ const absolute = join(root, path);
74
+ let text: string;
75
+ try { text = readFileSync(absolute, 'utf8'); }
76
+ catch (error) {
77
+ const failed = unmeasured(error);
78
+ return { path, criteria: { reproducible: failed, confidential: failed, brand: failed } };
79
+ }
80
+ const reproducible = measure(() => {
81
+ const refs = extractElanousCommands(path, text);
82
+ if (!refs.length) return { status: 'n/a', reason: '문서에 elanous 명령 없음' };
83
+ const findings = checkCommands(refs, checkedHelp);
84
+ const unable = findings.find((finding) => finding.kind === 'unmeasured');
85
+ if (unable) return unmeasured(unable.detail);
86
+ const missing = [...new Set(findings.map((finding) => finding.detail))].slice(0, 3);
87
+ return missing.length
88
+ ? { status: 'fail', reason: `없는 명령: ${missing.join(', ')}` }
89
+ : { status: 'pass', reason: `명령 ${refs.length}개 모두 --help 에 있음` };
90
+ });
91
+ const confidential = measure(() => {
92
+ const hits = scanLeaks(root, [path], undefined, new Map([[path, text]]));
93
+ const markers = [...new Set(hits.map((hit) => hit.marker))].slice(0, 3);
94
+ return markers.length
95
+ ? { status: 'fail', reason: `누출 표식: ${markers.join(', ')}` }
96
+ : { status: 'pass', reason: '누출 표식 없음' };
97
+ });
98
+ const brand = measure(() => {
99
+ const result = checkBrand('public-docs', [absolute]);
100
+ if (result.missing || result.files !== 1 || result.rules === 0) return unmeasured('브랜드 규칙 없음 또는 파일 검사 실패');
101
+ const ids = [...new Set(result.findings.map((finding) => finding.id))].slice(0, 3);
102
+ return ids.length
103
+ ? { status: 'fail', reason: `브랜드 규칙: ${ids.join(', ')}` }
104
+ : { status: 'pass', reason: '브랜드 규칙 위반 없음' };
105
+ });
106
+ return { path, criteria: { reproducible, confidential, brand } };
107
+ });
108
+ const statuses = results.flatMap((item) => KEYS.map((key) => item.criteria[key].status));
109
+ debug.log('expose.rubric', 'suggested', {
110
+ docs: results.length, pass: statuses.filter((status) => status === 'pass').length,
111
+ fail: statuses.filter((status) => status === 'fail').length,
112
+ unmeasured: statuses.filter((status) => status === 'n/a').length,
113
+ helpRuns, helpFailures,
114
+ });
115
+ return results;
116
+ }
117
+
118
+ export function run(argv: readonly string[], root = process.cwd(), help?: HelpRunner): number {
119
+ const json = argv.includes('--json');
120
+ let files: string[] | undefined;
121
+ let out: string | undefined;
122
+ for (let i = 0; i < argv.length; i++) {
123
+ const arg = argv[i]!;
124
+ if (arg === '--json') continue;
125
+ if (arg === '--yaml-patch') {
126
+ out = argv[++i];
127
+ if (!out || out.startsWith('--')) throw new Error('--yaml-patch 에 출력 경로가 필요합니다');
128
+ } else if (arg === '--files') {
129
+ files = [];
130
+ while (argv[i + 1] && !argv[i + 1]!.startsWith('--')) files.push(argv[++i]!);
131
+ if (!files.length) throw new Error('--files 에 문서 경로가 필요합니다');
132
+ } else throw new Error(`알 수 없는 옵션: ${arg}`);
133
+ }
134
+ const results = suggestExposeRubric(root, files, help);
135
+ if (out) {
136
+ const target = resolve(root, out);
137
+ const ledger = resolve(root, LEDGER);
138
+ if (target === ledger || realpathOrSelf(target) === realpathOrSelf(ledger) || sameFile(target, ledger)) throw new Error('원장 파일은 덮어쓸 수 없습니다');
139
+ writeFileSync(target, stringify({ entries: results }));
140
+ }
141
+ if (json) console.log(JSON.stringify(results));
142
+ else {
143
+ for (const { path, criteria } of results) console.log(`${path} · ${KEYS.map((key) => `${key} ${criteria[key].status}${criteria[key].status === 'fail' ? `(${criteria[key].reason.split(': ').slice(1).join(': ') || criteria[key].reason})` : ''}`).join(' · ')}`);
144
+ const statuses = results.flatMap((item) => KEYS.map((key) => item.criteria[key].status));
145
+ console.log(`합계 문서 ${results.length} · pass ${statuses.filter((status) => status === 'pass').length} · fail ${statuses.filter((status) => status === 'fail').length} · n/a ${statuses.filter((status) => status === 'n/a').length} · maturity/value/evidence/support 사람 판정 (MK)`);
146
+ }
147
+ return 0;
148
+ }
149
+
150
+ function realpathOrSelf(path: string): string {
151
+ try { return realpathSync(path); } catch { return path; }
152
+ }
153
+
154
+ function sameFile(target: string, ledger: string): boolean {
155
+ const a = statSync(target, { throwIfNoEntry: false });
156
+ const b = statSync(ledger, { throwIfNoEntry: false });
157
+ return !!a && !!b && a.dev === b.dev && a.ino === b.ino;
158
+ }
159
+
160
+ if (import.meta.main) {
161
+ try { process.exitCode = run(process.argv.slice(2)); }
162
+ catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 2; }
163
+ }
@@ -5,6 +5,7 @@ import { readFileSync } from 'node:fs';
5
5
  import { homedir } from 'node:os';
6
6
  import { join } from 'node:path';
7
7
  import { debug } from '../../src/debug/log.js';
8
+ import { readFailureInbox } from '../../src/self-implement/heal-intake.js';
8
9
  import {
9
10
  fillMissingGateFacts,
10
11
  markDeletedUnverified,
@@ -101,6 +102,7 @@ export interface HealRoleDeps {
101
102
  loadLedger?: (runId: string) => RunLedgerEntry[] | null;
102
103
  ledgerDir?: string;
103
104
  search?: (query: string) => unknown;
105
+ stateRoot?: string;
104
106
  }
105
107
 
106
108
  function gitHeadExists(worktree: string, path: string): PathExists {
@@ -243,6 +245,18 @@ export function roleResult(role: string, ctx: GraphContext, deps: HealRoleDeps =
243
245
  const signature = failureOf(ctx);
244
246
  switch (role) {
245
247
  case 'observe-ledger': {
248
+ const selector = asRecord(input.failureEvent);
249
+ if (selector) {
250
+ if (typeof selector.source !== 'string' || typeof selector.ref !== 'string' || !selector.source || !selector.ref) {
251
+ return withAddedFacts({ outcome: 'fail', reason: 'invalid-failure-event-selector' }, []);
252
+ }
253
+ const since = typeof input.since === 'string' ? input.since : undefined;
254
+ const event = readFailureInbox({ since }, deps.stateRoot)
255
+ .filter(row => row.source === selector.source && row.ref === selector.ref)
256
+ .sort((a, b) => Date.parse(b.at) - Date.parse(a.at)).at(0);
257
+ if (!event) return withAddedFacts({ outcome: 'empty', reason: 'inbox-event-not-found' }, []);
258
+ return withAddedFacts({ outcome: 'ok', entries: [{ errorText: event.summary }], event }, ['errorText']);
259
+ }
246
260
  if (!asRecord(input.failure) && Object.keys(failure).length === 0) return withAddedFacts({ outcome: 'empty' }, []);
247
261
  const worktree = typeof input.worktree === 'string' ? input.worktree : '';
248
262
  const paths = signature.unverified ?? [];
@@ -0,0 +1,65 @@
1
+ #!/bin/sh
2
+ # HQ-REP replication body — the same file runs on both HQ hosts (OP 10-04 10:09 · «되돌리기 방향»).
3
+ # The lease holder is the source, the other host is the standby: mbp → node-b today, node-b → mbp once node-b holds the lease.
4
+ # Called by each host's short cron shim (~/.elanous/bin/hq-standby.sh: lock ⊕ fetch ⊕ detach origin/main) with:
5
+ # HQ_REP_PEER=<the other host's ssh name> HQ_REP_DEFAULT_SOURCE=<lease names of the pre-lease source, comma list>
6
+ # [HQ_REP_SURVIVAL=<host>,s3://bucket/prefix] [HQ_REP_STATE_DIR=<canonical ledgers · default ~/.elanous>]
7
+ # sh scripts/hq/hq-standby.sh core|big|obs|vault
8
+ # HQ_REP_STATE_DIR (OP 10-04 11:27): on node-b the promoted HQ state lives in ~/.elanous-hqstate — its live ~/.elanous is the
9
+ # Pod host's own state and must never be copied as HQ ledgers. The copy refuses a state dir without release/features.sqlite
10
+ # (an empty or wrong root would otherwise become the standby's newest generation).
11
+ # Direction comes from scripts/hq/standby-role.ts: `fenced` → the copy runs under `elanous hq fence --role cron`
12
+ # (TC HQ-FENCE decides · skip exits 0), `direct` → no lease yet and this is the default source, `skip` → no lease, not it.
13
+ MODE="$1"
14
+ ts() { date '+%Y-%m-%dT%H:%M:%S%z'; }
15
+ # Production ledgers on every host — a source-tree command resolves the test universe otherwise.
16
+ # HQ_REP_CONFIG_DIR (TC drill step 0 · 10-04 11:5x): node-b keeps its hq settings (hq.hostName=node-b) in ~/.elanous-hqcfg
17
+ # because its live ~/.elanous is the Pod host's; the role check and `hq fence` must read the same lease name as HQ-HB.
18
+ # ⚠️ The config dir moves only by the `--config-dir` flag (src/elanous-config-dir.ts) — an ELANOUS_CONFIG_DIR env is ignored.
19
+ export ELANOUS_STATE_DIR="$HOME/.elanous"
20
+ CFG="${HQ_REP_CONFIG_DIR:-$HOME/.elanous}"
21
+ STATE="${HQ_REP_STATE_DIR:-$HOME/.elanous}"
22
+ case "$MODE" in core|big|obs|vault) ;; *) echo "$(ts) hq-standby skip: unknown mode $MODE"; exit 2 ;; esac
23
+ [ -n "$HQ_REP_PEER" ] || { echo "$(ts) hq-standby skip($MODE): HQ_REP_PEER not set"; exit 2; }
24
+ [ -n "$HQ_REP_DEFAULT_SOURCE" ] || { echo "$(ts) hq-standby skip($MODE): HQ_REP_DEFAULT_SOURCE not set"; exit 2; }
25
+ verify_received() {
26
+ # Verification is observational: a broken or not-yet-received copy must not fail replication's cron exit status.
27
+ bun scripts/hq/standby-verify.ts --root "$HOME/.elanous-standby" --tier core,big --max-age-min core=30,big=400 || :
28
+ }
29
+ ROLE=$(bun scripts/hq/standby-role.ts --config-dir "$CFG" --default-source "$HQ_REP_DEFAULT_SOURCE" 2>/dev/null) || ROLE=fenced
30
+ case "$ROLE" in
31
+ skip) if [ "$MODE" = core ]; then verify_received; else echo "$(ts) hq-standby skip($MODE): standby — no lease yet and this host is not the default source"; fi; exit 0 ;;
32
+ direct) set -- ;;
33
+ *) ROLE=fenced; set -- bun bin/elanous.mjs --config-dir "$CFG" hq fence --role cron -- ;;
34
+ esac
35
+ echo "$(ts) hq-standby run($MODE) role=$ROLE state=$STATE → $HQ_REP_PEER at $(git rev-parse --short HEAD 2>/dev/null)"
36
+ if [ "$MODE" = vault ]; then
37
+ # Obsidian vault: one-way copy to the standby (no --delete; workspace state and trash stay local; --update keeps newer files there).
38
+ V="$HOME/Obsidian/ElanvitalAI/"
39
+ "$@" rsync -a --update --exclude '.obsidian/workspace*' --exclude '.trash/' "$V" "$HQ_REP_PEER:$V" 2>&1 \
40
+ && echo "$(ts) hq-standby vault done rc=0 (a fence skip line above means nothing was copied)" || { echo "$(ts) hq-standby vault FAILED"; exit 1; }
41
+ exit 0
42
+ fi
43
+ # In core fenced mode the guard runs inside the fence: a non-holder need not have HQ source ledgers.
44
+ if [ "$MODE" != core ] || [ "$ROLE" = direct ]; then
45
+ [ -f "$STATE/release/features.sqlite" ] || { echo "$(ts) hq-standby skip($MODE): $STATE has no release/features.sqlite — not an HQ state dir, nothing copied"; exit 1; }
46
+ fi
47
+ SURV=""
48
+ [ "$MODE" = core ] && [ -n "$HQ_REP_SURVIVAL" ] && SURV="--survival $HQ_REP_SURVIVAL"
49
+ # The fence exits 0 both after copying and after skipping. Mark execution inside the fence,
50
+ # not by parsing its diagnostic text: only the skipped host owns a received copy to verify.
51
+ if [ "$MODE" = core ] && [ "$ROLE" = fenced ]; then
52
+ FENCE_RAN=$(mktemp) || { echo "$(ts) hq-standby FAILED: cannot track fence execution"; exit 1; }
53
+ # shellcheck disable=SC2086 — SURV is either empty or two words.
54
+ COPY_OUT=$("$@" sh -c '
55
+ [ -f "$1/release/features.sqlite" ] || { echo "hq-standby skip(core): $1 has no release/features.sqlite — not an HQ state dir, nothing copied"; exit 1; }
56
+ printf "ran\n" > "$2"; shift 2; exec "$@"
57
+ ' sh "$STATE" "$FENCE_RAN" env ELANOUS_STATE_DIR="$STATE" bun scripts/hq/standby-snapshot.ts --tier "$MODE" --push "$HQ_REP_PEER" $SURV 2>&1)
58
+ COPY_RC=$?
59
+ [ -z "$COPY_OUT" ] || printf '%s\n' "$COPY_OUT"
60
+ if [ "$COPY_RC" -eq 0 ] && [ ! -s "$FENCE_RAN" ]; then verify_received; fi
61
+ rm -f "$FENCE_RAN"
62
+ exit "$COPY_RC"
63
+ fi
64
+ # shellcheck disable=SC2086 — SURV is either empty or two words.
65
+ "$@" env ELANOUS_STATE_DIR="$STATE" bun scripts/hq/standby-snapshot.ts --tier "$MODE" --push "$HQ_REP_PEER" $SURV 2>&1