elanous 0.2.13 → 0.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (524) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/LZcstaO9_NXypPFmujolv/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1307-33ffcc49ab45123b.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
  10. package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
  31. package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
  42. package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/approvals/page-0e2c57fe6add4b2a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e05a77df7e005b04.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/bots/page-b8c49f6b4fa2df91.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/chat/page-976813c91d7394ea.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/design-check/page-72d3e7ff35a8c954.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/editor/page-60c7a8e1a4fe62ce.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/exec/page-2a52b316cb143d9c.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/field/page-c3ce483c8d5db370.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/intake/page-96f074f6538e8a2c.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/layout-e97be5334d83a9f5.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/morning/page-c1343f9499d86f3f.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/not-found-8739a2e0ae793130.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-d23b12bb92242558.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c7fea475488201a8.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/reflection/page-5efbf81124c474ae.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-06ee266953d9c879.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/settings/page-153c2b3bcc89e8de.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/share/page-0d6d23b40263a7ea.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-f5597e894c02cd33.js} +1 -1
  91. package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/trace/page-73735b64bfe1ae67.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f7e631e9b4daf66e.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9817c7f9007466cc.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
  106. package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +1 -0
  107. package/apps/pwa/out/approvals/index.html +1 -1
  108. package/apps/pwa/out/approvals/index.txt +23 -23
  109. package/apps/pwa/out/autopilot/index.html +1 -1
  110. package/apps/pwa/out/autopilot/index.txt +23 -23
  111. package/apps/pwa/out/board/index.html +1 -1
  112. package/apps/pwa/out/board/index.txt +23 -23
  113. package/apps/pwa/out/botlab/index.html +1 -1
  114. package/apps/pwa/out/botlab/index.txt +22 -22
  115. package/apps/pwa/out/bots/index.html +1 -1
  116. package/apps/pwa/out/bots/index.txt +22 -22
  117. package/apps/pwa/out/ceo/index.html +1 -0
  118. package/apps/pwa/out/ceo/index.txt +33 -0
  119. package/apps/pwa/out/chat/index.html +1 -1
  120. package/apps/pwa/out/chat/index.txt +22 -22
  121. package/apps/pwa/out/consult/index.html +1 -1
  122. package/apps/pwa/out/consult/index.txt +23 -23
  123. package/apps/pwa/out/control/index.html +1 -1
  124. package/apps/pwa/out/control/index.txt +23 -23
  125. package/apps/pwa/out/design-check/index.html +1 -1
  126. package/apps/pwa/out/design-check/index.txt +23 -23
  127. package/apps/pwa/out/editor/index.html +1 -1
  128. package/apps/pwa/out/editor/index.txt +23 -23
  129. package/apps/pwa/out/exec/index.html +1 -1
  130. package/apps/pwa/out/exec/index.txt +23 -23
  131. package/apps/pwa/out/field/index.html +1 -1
  132. package/apps/pwa/out/field/index.txt +23 -23
  133. package/apps/pwa/out/healthz +1 -1
  134. package/apps/pwa/out/index.html +1 -1
  135. package/apps/pwa/out/index.txt +22 -22
  136. package/apps/pwa/out/inside/index.html +1 -1
  137. package/apps/pwa/out/inside/index.txt +22 -22
  138. package/apps/pwa/out/intake/index.html +1 -1
  139. package/apps/pwa/out/intake/index.txt +23 -23
  140. package/apps/pwa/out/live/index.html +1 -1
  141. package/apps/pwa/out/live/index.txt +23 -23
  142. package/apps/pwa/out/loops/index.html +1 -0
  143. package/apps/pwa/out/loops/index.txt +30 -0
  144. package/apps/pwa/out/market/index.html +1 -1
  145. package/apps/pwa/out/market/index.txt +22 -22
  146. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  147. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  148. package/apps/pwa/out/morning/index.html +1 -1
  149. package/apps/pwa/out/morning/index.txt +23 -23
  150. package/apps/pwa/out/observatory/index.html +1 -1
  151. package/apps/pwa/out/observatory/index.txt +23 -23
  152. package/apps/pwa/out/ops/checklist/index.html +1 -1
  153. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  154. package/apps/pwa/out/ops/release/index.html +1 -1
  155. package/apps/pwa/out/ops/release/index.txt +22 -22
  156. package/apps/pwa/out/ops/seats/index.html +1 -1
  157. package/apps/pwa/out/ops/seats/index.txt +22 -22
  158. package/apps/pwa/out/outputs/index.html +1 -1
  159. package/apps/pwa/out/outputs/index.txt +22 -22
  160. package/apps/pwa/out/reflection/index.html +1 -1
  161. package/apps/pwa/out/reflection/index.txt +23 -23
  162. package/apps/pwa/out/scheduler/index.html +1 -1
  163. package/apps/pwa/out/scheduler/index.txt +23 -23
  164. package/apps/pwa/out/sessions/index.html +1 -1
  165. package/apps/pwa/out/sessions/index.txt +22 -22
  166. package/apps/pwa/out/settings/devices/index.html +1 -1
  167. package/apps/pwa/out/settings/devices/index.txt +23 -23
  168. package/apps/pwa/out/settings/index.html +1 -1
  169. package/apps/pwa/out/settings/index.txt +23 -23
  170. package/apps/pwa/out/setup/done/index.html +1 -1
  171. package/apps/pwa/out/setup/done/index.txt +23 -23
  172. package/apps/pwa/out/setup/index.html +1 -1
  173. package/apps/pwa/out/setup/index.txt +23 -23
  174. package/apps/pwa/out/share/index.html +1 -1
  175. package/apps/pwa/out/share/index.txt +23 -23
  176. package/apps/pwa/out/showroom/index.html +1 -1
  177. package/apps/pwa/out/showroom/index.txt +22 -22
  178. package/apps/pwa/out/tasks/index.html +1 -1
  179. package/apps/pwa/out/tasks/index.txt +23 -23
  180. package/apps/pwa/out/term/index.html +1 -1
  181. package/apps/pwa/out/term/index.txt +23 -23
  182. package/apps/pwa/out/today/index.html +1 -1
  183. package/apps/pwa/out/today/index.txt +23 -23
  184. package/apps/pwa/out/trace/index.html +1 -1
  185. package/apps/pwa/out/trace/index.txt +23 -23
  186. package/apps/pwa/out/vault/index.html +1 -1
  187. package/apps/pwa/out/vault/index.txt +23 -23
  188. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  189. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  190. package/apps/pwa/out/workflows/index.html +1 -1
  191. package/apps/pwa/out/workflows/index.txt +23 -23
  192. package/apps/pwa/out/workspace/index.html +1 -1
  193. package/apps/pwa/out/workspace/index.txt +23 -23
  194. package/apps/pwa/out/worktrees/index.html +1 -1
  195. package/apps/pwa/out/worktrees/index.txt +23 -23
  196. package/graphs/heal/heal-loop.yaml +1 -1
  197. package/graphs/implement-loop.yaml +1 -1
  198. package/graphs/orchestrator/orchestrator.yaml +59 -0
  199. package/graphs/orchestrator/recipes.yaml +18 -0
  200. package/graphs/release/recipes.yaml +6 -0
  201. package/graphs/release/release-loop.yaml +6 -1
  202. package/graphs/rhythm/daily.yaml +38 -0
  203. package/graphs/rhythm/recipes.yaml +18 -0
  204. package/graphs/rhythm/weekly.yaml +39 -0
  205. package/graphs/video/recipes.yaml +3 -0
  206. package/graphs/video/youtube-summary.yaml +17 -0
  207. package/package.json +1 -1
  208. package/scripts/audit-test-state-writes.ts +72 -17
  209. package/scripts/brand/check.ts +188 -0
  210. package/scripts/ci-typecheck-changed.ts +50 -18
  211. package/scripts/coord-post.sh +11 -1
  212. package/scripts/docs/generate-graph-anatomy.ts +315 -0
  213. package/scripts/docs-cli-check.ts +23 -6
  214. package/scripts/expose-rubric-check.ts +118 -0
  215. package/scripts/expose-rubric-suggest.ts +169 -0
  216. package/scripts/heal/run-role.ts +14 -0
  217. package/scripts/hq/standby-verify.ts +11 -7
  218. package/scripts/lecture-note/node.ts +31 -11
  219. package/scripts/lessons/handbook-page.ts +112 -0
  220. package/scripts/marketing/generate-ax-brief.ts +140 -0
  221. package/scripts/marketing/generate-feature-map.ts +118 -0
  222. package/scripts/release-loop/checklist-gate-node.ts +53 -14
  223. package/scripts/release-loop/gate-node.ts +89 -14
  224. package/scripts/release-loop/publish-node.ts +14 -1
  225. package/scripts/release-loop/unattended-release.ts +23 -1
  226. package/scripts/release-loop/vault-note-node.ts +428 -0
  227. package/scripts/release-story/draft.ts +220 -0
  228. package/scripts/rhythm/daily.ts +570 -0
  229. package/scripts/rhythm/weekly.ts +315 -0
  230. package/scripts/skills/public-check.ts +169 -0
  231. package/scripts/smart-merge/resync-shadow.ts +147 -0
  232. package/scripts/test-deterministic.ts +4 -8
  233. package/scripts/test-diet/effectiveness.ts +195 -0
  234. package/scripts/test-diet/lib.ts +16 -8
  235. package/scripts/test-diet/node.ts +77 -9
  236. package/scripts/ux/demo-rehearsal.ts +2 -2
  237. package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
  238. package/scripts/video/youtube-summary.ts +133 -0
  239. package/skills/diagram-master/SKILL.md +42 -49
  240. package/skills/diagram-master/references/pyproject.toml +4 -1
  241. package/skills/diagram-master/references/routing-guide.md +4 -2
  242. package/skills/grill-me/SKILL.md +1 -0
  243. package/skills/youtube-master/SKILL.md +13 -13
  244. package/skills/youtube-master/package-lock.json +0 -545
  245. package/skills/youtube-master/package.json +2 -5
  246. package/skills/youtube-master/scripts/main.ts +2 -2
  247. package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
  248. package/skills/youtube-master/src/util.ts +30 -7
  249. package/src/agent-env/profile.ts +161 -0
  250. package/src/agent-mission/driver.ts +3 -3
  251. package/src/agent-substrate/pr-reviewer.ts +2 -0
  252. package/src/ask-user-question/pending-questions.ts +5 -0
  253. package/src/auth/github-app-token.ts +112 -13
  254. package/src/autopilot/build/llm-conflict-merge.ts +73 -10
  255. package/src/autopilot/build/merge-intent.ts +53 -0
  256. package/src/briefing/brief-items.ts +121 -0
  257. package/src/chat/index.ts +2 -0
  258. package/src/claims/claims-ledger.ts +232 -0
  259. package/src/claims/claims-render.ts +166 -0
  260. package/src/cli/agent-env-cli.ts +21 -0
  261. package/src/cli/claims-cli.ts +94 -0
  262. package/src/cli/decisions-cli.ts +44 -3
  263. package/src/cli/docs-cli.ts +2 -0
  264. package/src/cli/doctor-cli.ts +23 -1
  265. package/src/cli/doctor-node-modules.ts +147 -0
  266. package/src/cli/flow-cli.ts +11 -0
  267. package/src/cli/freeze-cli.ts +45 -0
  268. package/src/cli/intake-cli.ts +46 -17
  269. package/src/cli/lesson-cli.ts +108 -0
  270. package/src/cli/logs-cli.ts +104 -2
  271. package/src/cli/logs-model-input.ts +131 -0
  272. package/src/cli/pod-cli.ts +2 -0
  273. package/src/cli/pr-cli.ts +29 -1
  274. package/src/cli/private-ledger-commands.ts +54 -4
  275. package/src/cli/pty-drive-verdict.ts +11 -2
  276. package/src/cli/release-cli.ts +190 -33
  277. package/src/cli/rfc-status.ts +91 -0
  278. package/src/cli/seat-requests-cli.ts +52 -0
  279. package/src/connectors/linear.ts +5 -3
  280. package/src/context-bus/condense.ts +15 -20
  281. package/src/context-bus/context-now-public.ts +96 -0
  282. package/src/context-bus/context-now-surfaces.ts +55 -2
  283. package/src/context-bus/context-now.ts +6 -3
  284. package/src/context-bus/coord-events.ts +6 -4
  285. package/src/context-bus/long-term-memory.ts +92 -11
  286. package/src/dashboard/index.ts +75 -22
  287. package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
  288. package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
  289. package/src/dashboard/slash-runtime/loops-table.ts +77 -0
  290. package/src/decisions/decision-cards.ts +33 -7
  291. package/src/decisions/decision-ledger.ts +40 -1
  292. package/src/decisions/proact-meter.ts +286 -0
  293. package/src/decisions/proact1-lite.ts +86 -0
  294. package/src/decisions/web-push-decision-cards.ts +1 -1
  295. package/src/discord/sprint21-runtime.ts +16 -0
  296. package/src/discord-self-message.ts +8 -0
  297. package/src/discord-slash-wire.ts +31 -2
  298. package/src/domains/alert-audit.ts +271 -0
  299. package/src/domains/core-tools.ts +4 -3
  300. package/src/domains/ops-facts-tool.ts +31 -1
  301. package/src/domains/outbound-alert.ts +1 -1
  302. package/src/elanous-config-dir.ts +29 -4
  303. package/src/execution-loop/launch-gate.ts +29 -14
  304. package/src/flow/cards.ts +239 -0
  305. package/src/graph-runner/graph-tick.ts +4 -0
  306. package/src/graph-runner/runner.ts +30 -5
  307. package/src/harness/harness-cli-command.ts +259 -38
  308. package/src/harness/harness-incidents.ts +45 -12
  309. package/src/harness/harness-pod-dispatch.ts +102 -11
  310. package/src/harness/harness-queue-child.ts +3 -3
  311. package/src/harness/harness-queue.ts +583 -65
  312. package/src/harness/helper-repair.ts +87 -0
  313. package/src/harness/helper-scan.ts +151 -0
  314. package/src/harness/launch-stamp.ts +236 -0
  315. package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
  316. package/src/harness/ledger-write-guard.ts +35 -0
  317. package/src/harness/model-input-observation.ts +37 -0
  318. package/src/harness/pod-memory-policy.ts +15 -0
  319. package/src/harness/repository-name.ts +55 -0
  320. package/src/hq/fence-audit.ts +90 -0
  321. package/src/hq/hq.ts +63 -5
  322. package/src/hq/lease.ts +4 -4
  323. package/src/hq/move-plan.ts +126 -0
  324. package/src/hq/promote.ts +332 -0
  325. package/src/index.ts +159 -27
  326. package/src/intake-plane/check.ts +63 -0
  327. package/src/intake-plane/digest.ts +37 -15
  328. package/src/intake-plane/front-classifier.ts +6 -0
  329. package/src/intake-plane/items.ts +3 -2
  330. package/src/intake-plane/lens.ts +177 -0
  331. package/src/intake-plane/linear-wish.ts +39 -11
  332. package/src/intake-plane/route.ts +145 -1
  333. package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
  334. package/src/intake-plane/wish-card.ts +53 -2
  335. package/src/intake-plane/wish-folder.ts +5 -2
  336. package/src/intake-plane/wish-reply.ts +72 -0
  337. package/src/launch-head/seat-goal-queue-lock.fixture.ts +15 -0
  338. package/src/launch-head/seat-goal-queue.ts +198 -0
  339. package/src/lessons/lesson-import.ts +106 -0
  340. package/src/lessons/lesson-ledger.ts +206 -0
  341. package/src/loops/activity.ts +203 -0
  342. package/src/loops/budget.ts +63 -0
  343. package/src/loops/checker.ts +5 -3
  344. package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
  345. package/src/loops/loop-cli.ts +36 -4
  346. package/src/loops/neighbors.ts +40 -0
  347. package/src/loops/observe.ts +10 -0
  348. package/src/loops/orchestrator/author-depth.ts +220 -0
  349. package/src/loops/orchestrator/author-input-check.ts +82 -0
  350. package/src/loops/orchestrator/author-ledger.ts +232 -0
  351. package/src/loops/orchestrator/finish-rate.ts +175 -0
  352. package/src/loops/orchestrator/overlap.ts +51 -12
  353. package/src/loops/orchestrator/resync-shadow.ts +227 -0
  354. package/src/loops/orchestrator/seat-share.ts +56 -0
  355. package/src/loops/orchestrator/tick.ts +309 -0
  356. package/src/loops/orchestrator/traffic.ts +134 -13
  357. package/src/loops/registry.ts +535 -9
  358. package/src/loops/status-rows.ts +118 -0
  359. package/src/loops/verdict.ts +17 -5
  360. package/src/maturity/bot-slash-catalog.ts +66 -0
  361. package/src/maturity/feature-maturity.ts +9 -5
  362. package/src/maturity/tui-slash-availability.ts +31 -0
  363. package/src/mss/logging/log-store.ts +35 -4
  364. package/src/nexus/api/dashboard.ts +6 -1
  365. package/src/nexus/api/http-server.ts +33 -3
  366. package/src/nexus/api/loop-edges.ts +173 -0
  367. package/src/nexus/api/merge-approvals-gate.ts +1 -1
  368. package/src/nexus/api/schedules-read.ts +18 -1
  369. package/src/nexus/api/seat-requests.ts +79 -56
  370. package/src/nexus/api/task-cards-api.ts +44 -5
  371. package/src/nexus/api/workflows.ts +2 -1
  372. package/src/prompt-enhance/enhance.ts +6 -1
  373. package/src/release-loop/auto-start.ts +51 -0
  374. package/src/release-loop/checklist.ts +22 -4
  375. package/src/release-loop/feature-store.ts +71 -32
  376. package/src/release-loop/landing-freeze.ts +121 -0
  377. package/src/release-loop/merged-pr-checklist.ts +125 -0
  378. package/src/release-loop/placement.ts +302 -0
  379. package/src/release-loop/release-schedule.ts +3 -1
  380. package/src/seat-dispatch/ceo-commands.ts +2 -2
  381. package/src/seat-dispatch/seat-ask.ts +16 -3
  382. package/src/seat-dispatch/seat-questions.ts +58 -0
  383. package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
  384. package/src/seat-dispatch/seat-request-ledger.ts +133 -0
  385. package/src/seat-loop/seat-loop.ts +531 -40
  386. package/src/self-dev/dev-pipeline.ts +14 -6
  387. package/src/self-dev/draft-sweep.ts +162 -6
  388. package/src/self-dev/draft-triage-rules.ts +25 -8
  389. package/src/self-dev/orchestrate.ts +15 -1
  390. package/src/self-dev/review-provider-fallback.ts +6 -1
  391. package/src/self-dev/run-store.ts +73 -1
  392. package/src/self-dev/run-supervisor.ts +57 -2
  393. package/src/self-dev/self-orchestrate-runtime.ts +5 -1
  394. package/src/self-implement/budget-gate.ts +29 -0
  395. package/src/self-implement/expose-gate.ts +47 -0
  396. package/src/self-implement/frozen-merges.ts +295 -0
  397. package/src/self-implement/gate-cli.ts +10 -4
  398. package/src/self-implement/gate-scope.ts +46 -9
  399. package/src/self-implement/goal-archive.ts +222 -0
  400. package/src/self-implement/goal-author.ts +43 -5
  401. package/src/self-implement/goal-clarification-escalation.ts +5 -0
  402. package/src/self-implement/goal-lookup.ts +147 -0
  403. package/src/self-implement/heal-intake.ts +197 -0
  404. package/src/self-implement/host-regate.ts +32 -2
  405. package/src/self-implement/l8-merge-queue.ts +132 -0
  406. package/src/self-implement/orchestrator.ts +299 -48
  407. package/src/self-implement/pipeline-shape.ts +1 -1
  408. package/src/self-implement/revert-guard.ts +97 -0
  409. package/src/self-implement/run-ledger.ts +3 -1
  410. package/src/self-implement/run-status-mapping.ts +2 -0
  411. package/src/self-implement/seams.ts +29 -9
  412. package/src/self-implement/self-implement-cli.ts +1 -0
  413. package/src/self-implement/speed-claims.ts +338 -0
  414. package/src/session/index.ts +2 -0
  415. package/src/steward/triage.ts +74 -3
  416. package/src/steward/working-backwards.ts +4 -1
  417. package/src/task-cards/card-cli.ts +10 -2
  418. package/src/task-cards/card-store.ts +10 -6
  419. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
  420. package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
  421. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
  422. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  423. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
  424. package/src/task-orchestrator/surfaces/pod-pool.ts +60 -8
  425. package/src/task-orchestrator/surfaces/self-implement-pod.ts +224 -49
  426. package/src/task-orchestrator/surfaces/self-implement.ts +5 -1
  427. package/src/telegram-commands.ts +27 -3
  428. package/src/telegram-loops-command.ts +44 -0
  429. package/src/telegram.ts +25 -4
  430. package/src/user-config.ts +146 -18
  431. package/src/version/packed-revision.json +1 -1
  432. package/src/workflow-runtime/nodes/bash.ts +9 -1
  433. package/src/workflow-runtime/storage.ts +31 -13
  434. package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
  445. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
  449. package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-78b40a344ed22f24.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
  517. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
  518. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
  520. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
  521. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
  523. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
  524. /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → LZcstaO9_NXypPFmujolv}/_ssgManifest.js +0 -0
@@ -56,6 +56,12 @@ docs-land:
56
56
  verify:
57
57
  command: 'bun scripts/release-loop/verify-node.ts'
58
58
  timeout_ms: 1800000
59
+ vault-note:
60
+ command: 'bun scripts/release-loop/vault-note-node.ts'
61
+ timeout_ms: 60000
62
+ release-story:
63
+ command: 'bun scripts/release-story/draft.ts --graph'
64
+ timeout_ms: 600000
59
65
  ops-upgrade:
60
66
  command: 'bun scripts/release-loop/ops-upgrade-node.ts'
61
67
  timeout_ms: 1200000
@@ -30,6 +30,8 @@ nodes:
30
30
  - { node_id: npm-publish, kind: git, recipe: 'cmd:npm-publish', max_visits: 1 }
31
31
  - { node_id: docs-land, kind: git, recipe: 'cmd:docs-land', max_visits: 1 }
32
32
  - { node_id: verify, kind: gate, recipe: 'cmd:verify', max_visits: 1 }
33
+ - { node_id: vault-note, kind: gate, recipe: 'cmd:vault-note', max_visits: 1 }
34
+ - { node_id: release-story, kind: gate, recipe: 'cmd:release-story', max_visits: 1 }
33
35
  - { node_id: ops-upgrade, kind: agent, recipe: 'cmd:ops-upgrade', max_visits: 1 }
34
36
  - { node_id: version-dev-bump, kind: git, recipe: 'cmd:version-dev-bump', max_visits: 1 }
35
37
  - { node_id: done, kind: gate, max_visits: 1 }
@@ -54,6 +56,9 @@ edges:
54
56
  - { from: publish, on: outcome, map: { ok: npm-publish, fail: failed, error: failed } }
55
57
  - { from: npm-publish, on: outcome, map: { ok: docs-land, fail: docs-land, error: docs-land } }
56
58
  - { from: docs-land, on: outcome, map: { ok: verify, fail: failed, error: failed } }
57
- - { from: verify, on: outcome, map: { ok: ops-upgrade, fail: failed, error: failed } }
59
+ - { from: verify, on: outcome, map: { ok: vault-note, fail: failed, error: failed } }
60
+ # Warning-only: vault note and release story failures after publication never block operational upgrades.
61
+ - { from: vault-note, on: outcome, map: { ok: release-story, fail: release-story, error: release-story } }
62
+ - { from: release-story, on: outcome, map: { ok: ops-upgrade, fail: ops-upgrade, error: ops-upgrade } }
58
63
  - { from: ops-upgrade, on: outcome, map: { ok: version-dev-bump, fail: version-dev-bump, error: version-dev-bump } }
59
64
  - { from: version-dev-bump, on: outcome, map: { ok: done, fail: failed, error: failed } }
@@ -0,0 +1,38 @@
1
+ graph_id: rhythm-daily
2
+ loop:
3
+ title: 데일리 리뷰
4
+ description: 어제 착지·판·루프·그리드·결정·위험·외부 동향을 한 장으로 읽는다
5
+ trigger:
6
+ cron: '30 8 * * *'
7
+ version: 1
8
+ entry_node: collect
9
+ terminal_nodes: [done, failed]
10
+ nodes:
11
+ - node_id: collect
12
+ kind: agent
13
+ recipe: cmd:collect
14
+ max_visits: 1
15
+ - node_id: compose
16
+ kind: agent
17
+ recipe: cmd:compose
18
+ max_visits: 1
19
+ - node_id: deliver
20
+ kind: agent
21
+ recipe: cmd:deliver
22
+ max_visits: 1
23
+ - node_id: done
24
+ kind: gate
25
+ max_visits: 1
26
+ - node_id: failed
27
+ kind: gate
28
+ max_visits: 1
29
+ edges:
30
+ - from: collect
31
+ on: outcome
32
+ map: { ok: compose, fail: compose }
33
+ - from: compose
34
+ on: outcome
35
+ map: { ok: deliver, fail: failed }
36
+ - from: deliver
37
+ on: outcome
38
+ map: { ok: done, fail: failed }
@@ -0,0 +1,18 @@
1
+ collect:
2
+ command: 'bun "$ELANOUS_GRAPH_DIR/../../scripts/rhythm/daily.ts" collect --json'
3
+ timeout_ms: 400000
4
+ compose:
5
+ command: 'bun "$ELANOUS_GRAPH_DIR/../../scripts/rhythm/daily.ts" compose --json'
6
+ timeout_ms: 30000
7
+ deliver:
8
+ command: 'bun "$ELANOUS_GRAPH_DIR/../../scripts/rhythm/daily.ts" deliver --json'
9
+ timeout_ms: 30000
10
+ weekly-collect:
11
+ command: 'bun "$ELANOUS_GRAPH_DIR/../../scripts/rhythm/weekly.ts" collect --json'
12
+ timeout_ms: 400000
13
+ weekly-compose:
14
+ command: 'bun "$ELANOUS_GRAPH_DIR/../../scripts/rhythm/weekly.ts" compose --json'
15
+ timeout_ms: 30000
16
+ weekly-deliver:
17
+ command: 'bun "$ELANOUS_GRAPH_DIR/../../scripts/rhythm/weekly.ts" deliver --json'
18
+ timeout_ms: 30000
@@ -0,0 +1,39 @@
1
+ graph_id: rhythm-weekly
2
+ loop:
3
+ title: 주간 미팅 자료
4
+ description: 지난주 성과·이번 주 판·현안·이행 대조·결정·외부 동향을 한 장으로 읽는다
5
+ trigger:
6
+ cron: '0 9 * * 1'
7
+ version: 1
8
+ entry_node: collect
9
+ terminal_nodes: [done, failed]
10
+ nodes:
11
+ - node_id: collect
12
+ kind: agent
13
+ recipe: cmd:weekly-collect
14
+ max_visits: 1
15
+ - node_id: compose
16
+ kind: agent
17
+ recipe: cmd:weekly-compose
18
+ max_visits: 1
19
+ - node_id: deliver
20
+ kind: agent
21
+ recipe: cmd:weekly-deliver
22
+ max_visits: 1
23
+ - node_id: done
24
+ kind: gate
25
+ max_visits: 1
26
+ - node_id: failed
27
+ kind: gate
28
+ max_visits: 1
29
+ edges:
30
+ # A failed collect must not let compose deliver an older file of the same week (review round 3).
31
+ - from: collect
32
+ on: outcome
33
+ map: { ok: compose, fail: failed }
34
+ - from: compose
35
+ on: outcome
36
+ map: { ok: deliver, fail: failed }
37
+ - from: deliver
38
+ on: outcome
39
+ map: { ok: done, fail: failed }
@@ -0,0 +1,3 @@
1
+ summary:
2
+ command: 'bun "$ELANOUS_GRAPH_DIR/../../scripts/video/youtube-summary.ts"'
3
+ timeout_ms: 120000
@@ -0,0 +1,17 @@
1
+ graph_id: youtube-summary
2
+ version: 1
3
+ loop:
4
+ title: Built-in YouTube summary
5
+ description: Turn video captions (or STT) into a local summary or study note; no publishing or external skill.
6
+ exec_request: true
7
+ inputs: [url, format]
8
+ trigger:
9
+ events: [manual]
10
+ entry_node: summary
11
+ terminal_nodes: [done, failed]
12
+ nodes:
13
+ - { node_id: summary, kind: agent, recipe: 'cmd:summary', max_visits: 1 }
14
+ - { node_id: done, kind: gate, max_visits: 1 }
15
+ - { node_id: failed, kind: gate, max_visits: 1 }
16
+ edges:
17
+ - { from: summary, on: outcome, map: { ok: done, no-transcript: done, invalid-video-id: failed, fail: failed } }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "elanous",
3
3
  "mcpName": "io.github.ElanvitalAI/elanous",
4
- "version": "0.2.13",
4
+ "version": "0.2.15",
5
5
  "description": "Self-healing coding harness where eyes, hands, and memory all turn on one sentence — it authors its own goals, implements them in isolated worktrees, and reviews its own work.",
6
6
  "license": "Apache-2.0",
7
7
  "author": "Elanvital AI",
@@ -34,18 +34,13 @@ const statePattern = /(?:\b(?:homedir|HOME|ELANOUS_(?:STATE|CONFIG|NEXUS)_DIR|EL
34
34
  export type StaticIsolationSignal = 'ELANOUS_STATE_DIR' | '--config-dir' | 'mkdtemp';
35
35
  export type StaticSafetyClassification = 'isolated' | 'manual-review';
36
36
 
37
- const inspectedManualReviews: Record<string, string> = {
38
- 'src/autopilot/build/config-isolation.test.ts': 'setElanousConfigDir redirects the tested config into an isolated worktree path; SHA assertions prove the real config is unchanged.',
39
- 'test/artifact-store.test.ts': 'the apparent writes target an in-memory ArtifactFs fake, not the host filesystem.',
40
- 'test/pty-drive-workdir-decision.test.ts': 'setElanousConfigDir receives only /tmp paths and the afterEach reset restores the override.',
41
- 'test/working-dir.test.ts': 'the fixed test root is derived from tmpdir() and removed by afterAll.',
42
- };
43
-
44
37
  export type WriterCall = {
45
38
  line: number;
46
39
  pattern: string;
47
40
  staticIsolationSignals: StaticIsolationSignal[];
48
41
  staticSafety: StaticSafetyClassification;
42
+ /** The expanded target names the real home (homedir(), $HOME, ~/) — the only writer calls that fail the audit. */
43
+ realHomeTarget: boolean;
49
44
  };
50
45
 
51
46
  type AuditFinding = {
@@ -123,8 +118,10 @@ export function classifyStaticIsolation(text: string): { signals: StaticIsolatio
123
118
  return { signals, safety: signals.length ? 'isolated' : 'manual-review' };
124
119
  }
125
120
 
126
- function writerIsolation(source: ts.SourceFile, writer: ts.CallExpression | ts.NewExpression, name: string): { signals: StaticIsolationSignal[]; safety: StaticSafetyClassification } {
127
- if (name === 'mkdtemp' || name === 'mkdtempSync') return { signals: ['mkdtemp'], safety: 'isolated' };
121
+ const REAL_HOME_TARGET = /\bhomedir\s*\(|process\.env\.HOME\b|process\.env\[\s*['"]HOME['"]\s*\]|['"`]~\//;
122
+
123
+ function writerIsolation(source: ts.SourceFile, writer: ts.CallExpression | ts.NewExpression, name: string): { signals: StaticIsolationSignal[]; safety: StaticSafetyClassification; realHomeTarget: boolean } {
124
+ if (name === 'mkdtemp' || name === 'mkdtempSync') return { signals: ['mkdtemp'], safety: 'isolated', realHomeTarget: false };
128
125
  const variables = new Map<string, string>();
129
126
  const visit = (node: ts.Node): void => {
130
127
  if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) variables.set(node.name.text, node.initializer.getText(source));
@@ -138,7 +135,40 @@ function writerIsolation(source: ts.SourceFile, writer: ts.CallExpression | ts.N
138
135
  return `(${expand(variables.get(name)!)})`;
139
136
  });
140
137
  const argumentsText = writer.arguments?.map((argument) => argument.getText(source)).join(' ') ?? '';
141
- return classifyStaticIsolation(expand(argumentsText));
138
+ return { ...classifyStaticIsolation(expand(argumentsText)), realHomeTarget: realHomeTarget(source, writer) };
139
+ }
140
+
141
+ /** Lexically nearest declaration of `name` visible before `at` (same file-wide name reused across tests must not leak). */
142
+ function scopedInitializer(source: ts.SourceFile, at: ts.Node, name: string): ts.Expression | undefined {
143
+ for (let scope: ts.Node | undefined = at.parent; scope; scope = scope.parent) {
144
+ if (!ts.isBlock(scope) && !ts.isSourceFile(scope) && !ts.isModuleBlock(scope)) continue;
145
+ let found: ts.Expression | undefined;
146
+ for (const statement of scope.statements) {
147
+ if (statement.pos >= at.pos) break;
148
+ if (!ts.isVariableStatement(statement)) continue;
149
+ for (const declaration of statement.declarationList.declarations) {
150
+ if (ts.isIdentifier(declaration.name) && declaration.name.text === name && declaration.initializer) found = declaration.initializer;
151
+ }
152
+ }
153
+ if (found) return found;
154
+ }
155
+ return undefined;
156
+ }
157
+
158
+ /** Only the target path (first argument) counts — written content may mention homedir() as fixture text. */
159
+ function realHomeTarget(source: ts.SourceFile, writer: ts.CallExpression | ts.NewExpression): boolean {
160
+ const target = writer.arguments?.[0];
161
+ if (!target) return false;
162
+ const seen = new Set<string>();
163
+ const expand = (node: ts.Node, text: string): string => text.replace(/\b[A-Za-z_$][\w$]*\b/g, (name) => {
164
+ if (seen.has(name)) return name;
165
+ const initializer = scopedInitializer(source, node, name);
166
+ if (!initializer) return name;
167
+ seen.add(name);
168
+ return `(${expand(initializer, initializer.getText(source))})`;
169
+ });
170
+ const expanded = expand(target, target.getText(source));
171
+ return REAL_HOME_TARGET.test(expanded) && classifyStaticIsolation(expanded).safety !== 'isolated';
142
172
  }
143
173
 
144
174
  export type CandidateClassification = {
@@ -158,18 +188,30 @@ export function addedCandidates(report: ReturnType<typeof auditTestStateWrites>,
158
188
  return new Set(report.findings.map(({ file }) => file).filter((file) => !previousInventory.has(file)));
159
189
  }
160
190
 
191
+ /**
192
+ * Fails only writer calls whose target reaches the real home without isolation evidence. A static scan cannot
193
+ * follow a temp directory through function returns or parameters, so "no evidence" alone stays visible as
194
+ * manual review in the rendered inventory instead of failing hundreds of mkdtemp-backed writes.
195
+ */
196
+ export function unclassifiedCandidates(report: ReturnType<typeof auditTestStateWrites>, previousInventory: ReadonlySet<string>): string[] {
197
+ const { isolated, manualReview } = classifyCandidates(report, addedCandidates(report, previousInventory));
198
+ // The file-wide isolation label can be borrowed from a same-named temp variable in another test, so the
199
+ // scoped real-HOME judgement decides on every call, including ones labelled isolated.
200
+ return [...isolated, ...manualReview].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line)
201
+ .filter(({ realHomeTarget }) => realHomeTarget)
202
+ .map(({ file, line, pattern }) => `${file}:${line} ${pattern} — writes a real HOME path without isolation evidence`);
203
+ }
204
+
161
205
  function classificationSummary(classification: CandidateClassification): string {
162
206
  const { findings, isolated, manualReview } = classification;
163
- const isolatedFiles = findings.filter(({ staticSafety }) => staticSafety === 'isolated');
164
207
  const manualReviewFiles = findings.filter(({ staticSafety }) => staticSafety === 'manual-review');
165
208
  return [
166
209
  `- August 8 candidate window: **${findings.length} files** (current inventory minus the preserved August 8 inventory).`,
167
- `- Candidate classification (files): **${isolatedFiles.length} ㉠ isolated** / **${manualReviewFiles.length} ㉡ manual review**.`,
168
- `- ㉡ candidate files: ${manualReviewFiles.length ? manualReviewFiles.map(({ file }) => `\`${file}\``).join(', ') : '0건'}.`,
210
+ `- Candidate classification (files): **${findings.length - manualReviewFiles.length} ㉠ isolated** / **${manualReviewFiles.length} ㉡ manual review**.`,
169
211
  `- Writer-call evidence in the August 8 window: **${isolated.length} isolated** / **${manualReview.length} manual review**; a file is ㉡ whenever any writer call lacks a direct approved signal.`,
170
212
  `- ㉡ writer calls: ${manualReview.length ? manualReview.map(({ file, line, pattern }) => `\`${file}:${line} ${pattern}\``).join(', ') : '0건'}.`,
171
- `- Manual inspection opened: **${manualReviewFiles.length}**; genuine HOME/state writers: **0건**.`,
172
- ...manualReviewFiles.map(({ file }) => `- Manual inspection — \`${file}\`: ${inspectedManualReviews[file] ?? 'opened; no genuine HOME/state write established.'}`),
213
+ `- Manual review required: **${manualReviewFiles.length}** candidate files. This static audit does not establish whether an unclassified write targets a real HOME/state path.`,
214
+ ...manualReviewFiles.map(({ file }) => `- Manual review pending — \`${file}\`: isolation is unverified.`),
173
215
  ].join('\n');
174
216
  }
175
217
 
@@ -198,7 +240,7 @@ export function auditTestStateWrites(root = repo): { total: number; findings: Au
198
240
  visit(source);
199
241
  const writerCalls = writerNodes.map(({ name, node }) => {
200
242
  const classification = writerIsolation(source, node, name);
201
- return { line: lineOf(source, node), pattern: formatWriterPattern(name, node, source), staticIsolationSignals: classification.signals, staticSafety: classification.safety };
243
+ return { line: lineOf(source, node), pattern: formatWriterPattern(name, node, source), staticIsolationSignals: classification.signals, staticSafety: classification.safety, realHomeTarget: classification.realHomeTarget };
202
244
  });
203
245
  const stateLines = text.split('\n').flatMap((line, index) => statePattern.test(line) ? [index + 1] : []);
204
246
  if (writerCalls.length && stateLines.length) {
@@ -221,8 +263,9 @@ export function auditTestStateWrites(root = repo): { total: number; findings: Au
221
263
  return { total: files.length, findings };
222
264
  }
223
265
 
224
- export function renderAudit(report: ReturnType<typeof auditTestStateWrites>, previous = august8Inventory(repo)): string {
266
+ export function renderAudit(report: ReturnType<typeof auditTestStateWrites>, previous = august8Inventory(repo), reviewed = new Set(readFileSync(baselinePath, 'utf8').split('\n').filter((line) => line && !line.startsWith('#')))): string {
225
267
  const candidateWindow = classifyCandidates(report, addedCandidates(report, previous));
268
+ const automatic = classifyCandidates(report, addedCandidates(report, reviewed));
226
269
  const rows = report.findings.map((finding) =>
227
270
  `| \`${finding.file}\` | ${finding.writerLines.join(', ')} | ${finding.writerTargets.map((target) => `\`${target}\``).join('<br>')} | ${finding.stateLines.join(', ')} | ${finding.risk} | ${finding.isolation} | ${finding.staticSafety} (${finding.staticIsolationSignals.join(', ') || 'none'}) |`,
228
271
  );
@@ -230,6 +273,7 @@ export function renderAudit(report: ReturnType<typeof auditTestStateWrites>, pre
230
273
  '# Test Home and State Write Audit',
231
274
  '',
232
275
  'This inventory is generated by `scripts/audit-test-state-writes.ts` using TypeScript AST call detection for filesystem/state writer calls and text detection for home/state-root evidence. It covers every `*.test.*` and `*.spec.*` file under `test/` and `src/`.',
276
+ `Rows for files outside the reviewed baseline are a snapshot from generation time; refresh with \`${INVENTORY_REFRESH_COMMAND}\`.`,
233
277
  '',
234
278
  `- Scanned test files: **${report.total}**`,
235
279
  `- Potential home/state writers: **${report.findings.length}**`,
@@ -238,6 +282,17 @@ export function renderAudit(report: ReturnType<typeof auditTestStateWrites>, pre
238
282
  '- Static safety classification is approval-gating: only `ELANOUS_STATE_DIR`, `--config-dir`, or `mkdtemp` counts as isolated; all other candidates require manual inspection.',
239
283
  classificationSummary(candidateWindow),
240
284
  '',
285
+ '## 분류기 통과(자동) — 사람 검토 기준선 밖',
286
+ '',
287
+ `Reviewed baseline: \`test/test-home-state-write-audit-baseline.txt\`. Regenerate with \`${INVENTORY_REFRESH_COMMAND}\`. Only candidates with every writer call statically isolated appear below; manual-review calls fail the audits rather than expanding the baseline.`,
288
+ '',
289
+ '| File | Writer lines | Approved isolation signals |',
290
+ '| --- | --- | --- |',
291
+ ...automatic.findings.filter(({ staticSafety }) => staticSafety === 'isolated').map(({ file, writerLines, staticIsolationSignals }) =>
292
+ `| \`${file}\` | ${writerLines.join(', ')} | ${staticIsolationSignals.join(', ')} |`),
293
+ '',
294
+ '## 전체 스캔 (사람 검토 + 자동 분류)',
295
+ '',
241
296
  '| File | Writer call lines (AST) | Write target or store argument | Home/state evidence lines (text) | Risk | Current isolation evidence | Static safety |',
242
297
  '| --- | --- | --- | --- | --- | --- | --- |',
243
298
  ...rows,
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env bun
2
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
+ import { extname, join, resolve } from 'node:path';
4
+ import { parse as parseYaml } from 'yaml';
5
+ import { debug } from '../../src/debug/log.js';
6
+
7
+ type Scope = 'public-docs' | 'release-notes' | 'site' | 'deck' | 'skill' | 'any';
8
+ type Kind = 'forbid' | 'require-form' | 'korean-in-english' | 'number-needs-source';
9
+ type Rule = { id: string; kind: Kind; pattern: string; flags?: string; scope: Scope[]; why: string; source: string; allowInCode?: boolean; allowPattern?: string; sourcePattern?: string };
10
+ type Finding = { file: string; line: number; id: string; match: string };
11
+ type GlossaryEntry = { internal: string; public: string; publicForbidden: boolean };
12
+ const scopes: Scope[] = ['public-docs', 'release-notes', 'site', 'deck', 'skill', 'any'];
13
+ const kinds: Kind[] = ['forbid', 'require-form', 'korean-in-english', 'number-needs-source'];
14
+ const defaultRules = join(import.meta.dir, '../../docs/brand/brand-rules.yaml');
15
+ export const BRAND_RULES_UNMEASURED = '규칙 없음 — 측정 불가';
16
+
17
+ function loadRules(path: string): Rule[] {
18
+ const document = parseYaml(readFileSync(path, 'utf8')) as unknown;
19
+ if (!document || typeof document !== 'object' || !('rules' in document) || !Array.isArray(document.rules)) throw new Error(`invalid brand rules: ${path}`);
20
+ const glossary = 'glossary' in document ? document.glossary : [];
21
+ if (!Array.isArray(glossary) || !glossary.every((entry: unknown) => entry && typeof entry === 'object'
22
+ && typeof (entry as GlossaryEntry).internal === 'string' && typeof (entry as GlossaryEntry).public === 'string'
23
+ && typeof (entry as GlossaryEntry).publicForbidden === 'boolean')) throw new Error(`invalid brand glossary: ${path}`);
24
+ const ids = new Set<string>();
25
+ const rules: Rule[] = document.rules.map((candidate: unknown) => {
26
+ if (!candidate || typeof candidate !== 'object') throw new Error(`invalid brand rule: ${path}`);
27
+ const rule = candidate as Partial<Rule>;
28
+ if (typeof rule.id !== 'string' || !rule.id || ids.has(rule.id) || !kinds.includes(rule.kind as Kind)
29
+ || typeof rule.pattern !== 'string' || !rule.pattern || typeof rule.why !== 'string' || !rule.why
30
+ || typeof rule.source !== 'string' || !rule.source || !Array.isArray(rule.scope) || !rule.scope.length
31
+ || !rule.scope.every((scope) => scopes.includes(scope)) || (rule.flags !== undefined && typeof rule.flags !== 'string')
32
+ || (rule.allowInCode !== undefined && typeof rule.allowInCode !== 'boolean')
33
+ || (rule.allowPattern !== undefined && typeof rule.allowPattern !== 'string')
34
+ || (rule.kind === 'number-needs-source' && (typeof rule.sourcePattern !== 'string' || !rule.sourcePattern))) throw new Error(`invalid brand rule: ${path}`);
35
+ new RegExp(rule.pattern, rule.flags);
36
+ if (rule.sourcePattern) new RegExp(rule.sourcePattern, rule.flags);
37
+ if (rule.allowPattern) new RegExp(rule.allowPattern, rule.flags);
38
+ ids.add(rule.id);
39
+ return rule as Rule;
40
+ });
41
+ for (const entry of glossary as GlossaryEntry[]) {
42
+ if (!entry.publicForbidden) continue;
43
+ const id = `GLOSSARY-${entry.internal}`;
44
+ if (!entry.internal || ids.has(id)) throw new Error(`invalid brand glossary: ${path}`);
45
+ ids.add(id);
46
+ const literal = entry.internal.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
47
+ const pattern = `(?<![가-힣])${literal}(?![가-힣])`;
48
+ rules.push({ id, kind: 'forbid', pattern, scope: ['any'], why: `Use ${entry.public} instead of the internal term.`, source: `${path}#glossary` });
49
+ }
50
+ return rules;
51
+ }
52
+
53
+ function filesAt(paths: string[]): string[] {
54
+ const files = new Set<string>();
55
+ const visit = (path: string): void => {
56
+ const stat = statSync(path);
57
+ if (stat.isDirectory()) {
58
+ for (const entry of readdirSync(path, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
59
+ if (entry.isDirectory() || entry.isFile()) visit(join(path, entry.name));
60
+ }
61
+ } else if (stat.isFile()) files.add(path);
62
+ };
63
+ for (const path of paths) visit(resolve(path));
64
+ return [...files].sort();
65
+ }
66
+
67
+ function maskUiStrings(line: string): string {
68
+ const ui = /^\s*(?:>\s*`?\s*)?(?:(?:const|let|var)\s+)?(?:label|title|button|placeholder|ariaLabel|aria-label|tooltip|caption|uiText|uiMessage)\s*[:=]\s*(["'`])([^"'`\n]*[가-힣][^"'`\n]*)\1/;
69
+ const chars = line.split('');
70
+ const match = ui.exec(line);
71
+ if (match) {
72
+ const value = match[2]!;
73
+ const start = match.index + match[0].indexOf(value);
74
+ for (let i = start; i < start + value.length; i++) chars[i] = ' ';
75
+ }
76
+ return chars.join('');
77
+ }
78
+
79
+ function htmlVisible(content: string): string {
80
+ const blank = (text: string) => text.replace(/[^\r\n]/g, ' ');
81
+ return content.replace(/<!--[\s\S]*?-->|<(?:script|style)\b[^>]*>[\s\S]*?<\/\s*(?:script|style)\s*>/gi, blank)
82
+ .replace(/<(?:[^>"']|"[^"]*"|'[^']*')*>/g, (tag) => {
83
+ if (!/^<img\b/i.test(tag)) return blank(tag);
84
+ const alt = /\balt\s*=\s*(["'])(.*?)\1/is.exec(tag);
85
+ if (!alt) return blank(tag);
86
+ const offset = alt.index + alt[0].indexOf(alt[2]!);
87
+ return blank(tag.slice(0, offset)) + alt[2] + blank(tag.slice(offset + alt[2].length));
88
+ });
89
+ }
90
+
91
+ function isEnglishDocument(content: string, file: string, scope: Scope): boolean {
92
+ const html = ['.html', '.htm'].includes(extname(file).toLowerCase());
93
+ const frontmatter = /^---\s*\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content)?.[1];
94
+ const language = html
95
+ ? /<html\b[^>]*\blang\s*=\s*["']?([a-z]{2,}(?:-[a-z]+)*)/i.exec(content)?.[1]
96
+ ?? /<meta\b[^>]*\b(?:http-equiv\s*=\s*["']?content-language|name\s*=\s*["']?language)[^>]*\bcontent\s*=\s*["']?([a-z]{2,})/i.exec(content)?.[1]
97
+ : frontmatter ? /^lang(?:uage)?\s*:\s*["']?([a-z]{2,}(?:-[a-z]+)*)/im.exec(frontmatter)?.[1] : undefined;
98
+ if (language) return /^en(?:-|$)/i.test(language);
99
+ // Language-suffixed filenames are an explicit declaration, even without frontmatter.
100
+ if (/(?:^|[._-])(?:en|ko)(?:-[a-z]+)?\.[^.]+$/i.test(file)) return /(?:^|[._-])en(?:-[a-z]+)?\.[^.]+$/i.test(file);
101
+ if (scope !== 'site') return false;
102
+ const body = (html ? htmlVisible(content) : content.replace(/^---\s*\r?\n[\s\S]*?\r?\n---/, ''))
103
+ .replace(/\bElanous\b/gi, '');
104
+ return /\b[A-Za-z]{2,}(?:\s+[A-Za-z]{2,}){2,}\b/.test(body);
105
+ }
106
+
107
+ function findInFile(file: string, rules: Rule[], scope: Scope): Finding[] {
108
+ const findings: Finding[] = [];
109
+ const content = readFileSync(file, 'utf8');
110
+ const html = ['.html', '.htm'].includes(extname(file).toLowerCase());
111
+ const englishDocument = isEnglishDocument(content, file, scope);
112
+ const markdown = ['.md', '.mdx'].includes(extname(file).toLowerCase());
113
+ const bodyLines = html ? htmlVisible(content).split(/\r?\n/) : [];
114
+ let fence: { marker: string; length: number } | undefined;
115
+ const allLines = content.split(/\r?\n/);
116
+ for (const [index, line] of allLines.entries()) {
117
+ const lineFindings: Finding[] = [];
118
+ const marker = markdown ? /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1] : undefined;
119
+ if (marker) {
120
+ if (!fence) fence = { marker: marker[0]!, length: marker.length };
121
+ else if (marker[0] === fence.marker && marker.length >= fence.length) fence = undefined;
122
+ }
123
+ const quoted = markdown && /^\s*>/.test(line);
124
+ const inCode = Boolean(fence || marker || quoted);
125
+ for (const rule of rules) {
126
+ if (rule.kind === 'korean-in-english' && !englishDocument) continue;
127
+ const text = rule.kind === 'korean-in-english' && html ? bodyLines[index]! : line;
128
+ const visible = rule.allowInCode && inCode ? maskUiStrings(text) : text;
129
+ const allowed = rule.allowPattern ? [...line.matchAll(new RegExp(rule.allowPattern, [...new Set(`${rule.flags ?? ''}g`)].join('')))] : [];
130
+ const matches = visible.matchAll(new RegExp(rule.pattern, [...new Set(`${rule.flags ?? ''}g`)].join('')));
131
+ // B4: a measured number must carry its source marker on the same line or a neighbouring one.
132
+ const sourced = rule.kind === 'number-needs-source'
133
+ && [index - 1, index, index + 1].some((i) => i >= 0 && i < allLines.length && new RegExp(rule.sourcePattern!, rule.flags).test(allLines[i]!));
134
+ for (const match of matches) {
135
+ if (sourced) break;
136
+ if (allowed.some((exception) => exception.index! <= match.index! && match.index! < exception.index! + exception[0].length)) continue;
137
+ lineFindings.push({ file, line: index + 1, id: rule.id, match: match[0] });
138
+ if (rule.kind === 'korean-in-english') break;
139
+ }
140
+ }
141
+ findings.push(...lineFindings.filter((finding) => finding.id !== 'B11'
142
+ || !lineFindings.some((other) => other.id === 'BRAND-AUTONOMY' && other.match.includes(finding.match))));
143
+ }
144
+ return findings;
145
+ }
146
+
147
+ export function checkBrand(scope: Scope, paths: string[], rulesPath = defaultRules): { files: number; findings: Finding[]; rules: number; missing: boolean; message?: string } {
148
+ if (!existsSync(rulesPath)) {
149
+ debug.log('brand.check', 'run', { scope, files: 0, findings: 0, rules: 0, missing: true, message: BRAND_RULES_UNMEASURED });
150
+ return { files: 0, findings: [], rules: 0, missing: true, message: BRAND_RULES_UNMEASURED };
151
+ }
152
+ const applicable = loadRules(rulesPath).filter((rule) => scope === 'any' || rule.scope.includes('any') || rule.scope.includes(scope));
153
+ const files = filesAt(paths);
154
+ const findings = files.flatMap((file) => findInFile(file, applicable, scope));
155
+ debug.log('brand.check', 'run', { scope, files: files.length, findings: findings.length, rules: applicable.length });
156
+ return { files: files.length, findings, rules: applicable.length, missing: false };
157
+ }
158
+
159
+ if (import.meta.main) {
160
+ try {
161
+ const args = process.argv.slice(2);
162
+ let scope: Scope | undefined;
163
+ let rulesPath = defaultRules;
164
+ let json = false;
165
+ const paths: string[] = [];
166
+ for (let i = 0; i < args.length; i++) {
167
+ const arg = args[i]!;
168
+ if (arg === '--scope') scope = args[++i] as Scope;
169
+ else if (arg === '--rules') rulesPath = args[++i] ?? '';
170
+ else if (arg === '--json') json = true;
171
+ else if (arg.startsWith('--')) throw new Error(`unknown option: ${arg}`);
172
+ else paths.push(arg);
173
+ }
174
+ if (!scope || !scopes.includes(scope) || !paths.length || !rulesPath) throw new Error('usage: bun scripts/brand/check.ts --scope <scope> <file|folder...> [--rules <path>] [--json]');
175
+ const result = checkBrand(scope, paths, rulesPath);
176
+ if (result.missing) console.log(json ? JSON.stringify(result) : result.message);
177
+ else if (json) console.log(JSON.stringify({ scope, ...result }));
178
+ else {
179
+ for (const finding of result.findings) console.log(`${finding.file}:${finding.line}:${finding.id}:${finding.match}`);
180
+ console.log(`brand-check ${scope} files=${result.files} findings=${result.findings.length} rules=${result.rules}`);
181
+ }
182
+ if (result.missing) process.exitCode = 2;
183
+ else if (result.findings.length) process.exitCode = 1;
184
+ } catch (error) {
185
+ console.error(error instanceof Error ? error.message : String(error));
186
+ process.exitCode = 2;
187
+ }
188
+ }
@@ -53,6 +53,9 @@ export type ChangedTsFiles = {
53
53
  files: Set<string>;
54
54
  base: string;
55
55
  baseSource: string;
56
+ head?: string;
57
+ diffFiles?: Set<string>;
58
+ warnings?: string[];
56
59
  failures?: { command: string; why: string }[];
57
60
  };
58
61
 
@@ -78,23 +81,41 @@ export function changedTsFiles(run = sh, env: NodeJS.ProcessEnv = process.env):
78
81
  }
79
82
  }
80
83
 
81
- const files = new Set<string>();
82
- const add = (out: string) => {
83
- for (const line of out.split('\n')) {
84
- const file = line.trim().replace(/^\.\//, '');
85
- if (/\.tsx?$/.test(file) && existsSync(file)) files.add(file);
86
- }
87
- };
88
84
  const failures: { command: string; why: string }[] = [];
89
- const addCommand = (command: string) => {
90
- try { add(run(command)); } catch (cause) {
85
+ // Revision reads are observational: an unreadable HEAD/base must not turn into a collection failure.
86
+ const quietRevision = (command: string): string => { try { return run(command).trim(); } catch { return ''; } };
87
+ const head = quietRevision('git rev-parse HEAD') || undefined;
88
+ const warnings: string[] = [];
89
+ if (head && quietRevision(`git rev-parse ${base}`) === head) {
90
+ const main = quietRevision('git rev-parse origin/main');
91
+ const merged = main && main !== head ? quietRevision('git merge-base HEAD origin/main') : '';
92
+ if (merged && merged !== head) {
93
+ warnings.push(`base=${base} 가 HEAD 와 같은 커밋 — origin/main (${main})과 달라 merge-base 로 재계산`);
94
+ base = merged;
95
+ baseSource = 'recomputed merge-base(origin/main)';
96
+ } else if (main && main !== head) {
97
+ failures.push({ command: `git rev-parse ${base}`, why: `base 가 HEAD(${head}) 와 같아 변경을 셀 수 없다 — TSC_BASE_REF 로 base 를 주라 (못 셌음 ≠ 0)` });
98
+ }
99
+ }
100
+
101
+ const files = new Set<string>();
102
+ const diffFiles = new Set<string>();
103
+ const commands = [`git diff --name-only ${base}...HEAD`, 'git diff --name-only HEAD', 'git ls-files --others --exclude-standard'];
104
+ for (const command of commands) {
105
+ try {
106
+ for (const line of run(command).split('\n')) {
107
+ const file = line.trim().replace(/^\.\//, '');
108
+ if (!file) continue;
109
+ const ts = /\.tsx?$/.test(file);
110
+ // A deleted .ts is not a compile candidate, so it must not count as «should have been collected».
111
+ if (!ts || existsSync(file)) diffFiles.add(file);
112
+ if (ts && existsSync(file)) files.add(file);
113
+ }
114
+ } catch (cause) {
91
115
  failures.push({ command, why: cause instanceof Error ? cause.message : String(cause) });
92
116
  }
93
- };
94
- addCommand(`git diff --name-only ${base}...HEAD`);
95
- addCommand('git diff --name-only HEAD');
96
- addCommand('git ls-files --others --exclude-standard');
97
- return { files, base, baseSource, failures };
117
+ }
118
+ return { files, base, baseSource, head, diffFiles, warnings, failures };
98
119
  }
99
120
 
100
121
  type RequiredExportField = { typeName: string; fieldName: string };
@@ -850,10 +871,13 @@ export function runGate(io: Partial<GateIo> = {}): void {
850
871
  ? { additions: [] }
851
872
  : exportedLiteralUnionMembersInChanges(changed, base));
852
873
  const changedResult = getChanged();
853
- const { files: changed, base, baseSource, failures = [] } = changedResult instanceof Set
854
- ? { files: changedResult, base: 'injected test seam', baseSource: 'injected test seam', failures: [] }
874
+ const { files: changed, base, baseSource, head, diffFiles, warnings = [], failures = [] } = changedResult instanceof Set
875
+ ? { files: changedResult, base: 'injected test seam', baseSource: 'injected test seam', head: 'injected test seam', diffFiles: new Set<string>(), warnings: [], failures: [] }
855
876
  : changedResult;
856
- log(`[tsc-gate] base=${base} (${baseSource}); 변경 .ts ${changed.size}개 관측.`);
877
+ for (const message of warnings) warn(`[tsc-gate] ⚠ ${message}`);
878
+ const diffCount = diffFiles?.size;
879
+ const diffTsCount = diffFiles && [...diffFiles].filter((file) => /\.tsx?$/.test(file)).length;
880
+ log(`[tsc-gate] base=${base} (${baseSource}); 변경 .ts ${changed.size}개 관측. (head=${head ?? 'unknown'} · 전체 diff 파일 ${diffCount ?? 'unknown'}개 · diff .ts ${diffTsCount ?? 'unknown'}개)`);
857
881
  if (failures.length > 0) {
858
882
  error('[tsc-gate] ⛔ 변경 파일 수집 실패:');
859
883
  for (const { command, why } of failures) error(` ${command}: ${why}`);
@@ -861,7 +885,15 @@ export function runGate(io: Partial<GateIo> = {}): void {
861
885
  exit(1);
862
886
  return;
863
887
  }
864
- if (changed.size === 0) { log('[tsc-gate] 변경된 .ts 파일 없음 — 위 base 기준 0개이며 tsc 실행 없이 통과.'); return; }
888
+ if (changed.size === 0) {
889
+ if (!(changedResult instanceof Set) && (!head || diffCount === undefined || diffTsCount === undefined || diffTsCount > 0)) {
890
+ error(`[tsc-gate] FAIL — 변경 파일 수집 불일치: 전체 diff 파일 ${diffCount ?? 'unknown'}개 · diff .ts ${diffTsCount ?? 'unknown'}개 · 수집 .ts 0개 (못 셌음 ≠ 0).`);
891
+ exit(1);
892
+ return;
893
+ }
894
+ log(`[tsc-gate] PASS — 변경된 .ts 파일 없음 · base=${base} · head=${head} · 전체 diff 파일 ${diffCount}개 · diff .ts ${diffTsCount}개 · 수집 .ts 0개; tsc 실행 없이 통과.`);
895
+ return;
896
+ }
865
897
  const fieldCheck = requiredExportFields(changed, base);
866
898
  const requiredFields = Array.isArray(fieldCheck) ? fieldCheck : fieldCheck.fields;
867
899
  if (!Array.isArray(fieldCheck) && fieldCheck.failure) {
@@ -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