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
@@ -0,0 +1,59 @@
1
+ graph_id: orchestrator
2
+ loop:
3
+ title: 오케스트레이터
4
+ trigger:
5
+ cron: '0 8,12,18 * * *'
6
+ events: [manual]
7
+ version: 1
8
+ entry_node: intake
9
+ terminal_nodes: [done, failed]
10
+ nodes:
11
+ - node_id: intake
12
+ kind: agent
13
+ recipe: cmd:orchestrator-intake
14
+ max_visits: 1
15
+ - node_id: split
16
+ kind: agent
17
+ recipe: cmd:orchestrator-split
18
+ max_visits: 1
19
+ - node_id: place
20
+ kind: agent
21
+ recipe: cmd:orchestrator-place
22
+ max_visits: 1
23
+ - node_id: delegate
24
+ kind: agent
25
+ recipe: cmd:orchestrator-delegate
26
+ max_visits: 1
27
+ - node_id: reconcile
28
+ kind: agent
29
+ recipe: cmd:orchestrator-reconcile
30
+ max_visits: 1
31
+ - node_id: report
32
+ kind: agent
33
+ recipe: cmd:orchestrator-report
34
+ max_visits: 1
35
+ - node_id: done
36
+ kind: gate
37
+ max_visits: 1
38
+ - node_id: failed
39
+ kind: gate
40
+ max_visits: 1
41
+ edges:
42
+ - from: intake
43
+ on: outcome
44
+ map: { ok: split, fail: failed }
45
+ - from: split
46
+ on: outcome
47
+ map: { ok: place, fail: failed }
48
+ - from: place
49
+ on: outcome
50
+ map: { ok: delegate, fail: failed }
51
+ - from: delegate
52
+ on: outcome
53
+ map: { ok: reconcile, fail: failed }
54
+ - from: reconcile
55
+ on: outcome
56
+ map: { ok: report, fail: failed }
57
+ - from: report
58
+ on: outcome
59
+ map: { ok: done, fail: failed }
@@ -0,0 +1,18 @@
1
+ orchestrator-intake:
2
+ command: 'bun "$ELANOUS_GRAPH_DIR/../../src/loops/orchestrator/tick.ts" intake'
3
+ timeout_ms: 120000
4
+ orchestrator-split:
5
+ command: 'bun "$ELANOUS_GRAPH_DIR/../../src/loops/orchestrator/tick.ts" split'
6
+ timeout_ms: 120000
7
+ orchestrator-place:
8
+ command: 'bun "$ELANOUS_GRAPH_DIR/../../src/loops/orchestrator/tick.ts" place'
9
+ timeout_ms: 120000
10
+ orchestrator-delegate:
11
+ command: 'bun "$ELANOUS_GRAPH_DIR/../../src/loops/orchestrator/tick.ts" delegate'
12
+ timeout_ms: 120000
13
+ orchestrator-reconcile:
14
+ command: 'bun "$ELANOUS_GRAPH_DIR/../../src/loops/orchestrator/tick.ts" reconcile'
15
+ timeout_ms: 120000
16
+ orchestrator-report:
17
+ command: 'bun "$ELANOUS_GRAPH_DIR/../../src/loops/orchestrator/tick.ts" report'
18
+ timeout_ms: 120000
@@ -56,6 +56,9 @@ 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
59
62
  ops-upgrade:
60
63
  command: 'bun scripts/release-loop/ops-upgrade-node.ts'
61
64
  timeout_ms: 1200000
@@ -30,6 +30,7 @@ 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 }
33
34
  - { node_id: ops-upgrade, kind: agent, recipe: 'cmd:ops-upgrade', max_visits: 1 }
34
35
  - { node_id: version-dev-bump, kind: git, recipe: 'cmd:version-dev-bump', max_visits: 1 }
35
36
  - { node_id: done, kind: gate, max_visits: 1 }
@@ -54,6 +55,8 @@ edges:
54
55
  - { from: publish, on: outcome, map: { ok: npm-publish, fail: failed, error: failed } }
55
56
  - { from: npm-publish, on: outcome, map: { ok: docs-land, fail: docs-land, error: docs-land } }
56
57
  - { 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 } }
58
+ - { from: verify, on: outcome, map: { ok: vault-note, fail: failed, error: failed } }
59
+ # Warning-only: vault note failures after publication never block operational upgrades.
60
+ - { from: vault-note, on: outcome, map: { ok: ops-upgrade, fail: ops-upgrade, error: ops-upgrade } }
58
61
  - { from: ops-upgrade, on: outcome, map: { ok: version-dev-bump, fail: version-dev-bump, error: version-dev-bump } }
59
62
  - { 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.12",
4
+ "version": "0.2.14",
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",
@@ -31,6 +31,7 @@
31
31
  "catalog/",
32
32
  "personas/",
33
33
  "persona-presets/",
34
+ "examples/teams/",
34
35
  "graphs/",
35
36
  "apps/pwa/out/",
36
37
  ".python-version",
@@ -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,186 @@
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
+
16
+ function loadRules(path: string): Rule[] {
17
+ const document = parseYaml(readFileSync(path, 'utf8')) as unknown;
18
+ if (!document || typeof document !== 'object' || !('rules' in document) || !Array.isArray(document.rules)) throw new Error(`invalid brand rules: ${path}`);
19
+ const glossary = 'glossary' in document ? document.glossary : [];
20
+ if (!Array.isArray(glossary) || !glossary.every((entry: unknown) => entry && typeof entry === 'object'
21
+ && typeof (entry as GlossaryEntry).internal === 'string' && typeof (entry as GlossaryEntry).public === 'string'
22
+ && typeof (entry as GlossaryEntry).publicForbidden === 'boolean')) throw new Error(`invalid brand glossary: ${path}`);
23
+ const ids = new Set<string>();
24
+ const rules: Rule[] = document.rules.map((candidate: unknown) => {
25
+ if (!candidate || typeof candidate !== 'object') throw new Error(`invalid brand rule: ${path}`);
26
+ const rule = candidate as Partial<Rule>;
27
+ if (typeof rule.id !== 'string' || !rule.id || ids.has(rule.id) || !kinds.includes(rule.kind as Kind)
28
+ || typeof rule.pattern !== 'string' || !rule.pattern || typeof rule.why !== 'string' || !rule.why
29
+ || typeof rule.source !== 'string' || !rule.source || !Array.isArray(rule.scope) || !rule.scope.length
30
+ || !rule.scope.every((scope) => scopes.includes(scope)) || (rule.flags !== undefined && typeof rule.flags !== 'string')
31
+ || (rule.allowInCode !== undefined && typeof rule.allowInCode !== 'boolean')
32
+ || (rule.allowPattern !== undefined && typeof rule.allowPattern !== 'string')
33
+ || (rule.kind === 'number-needs-source' && (typeof rule.sourcePattern !== 'string' || !rule.sourcePattern))) throw new Error(`invalid brand rule: ${path}`);
34
+ new RegExp(rule.pattern, rule.flags);
35
+ if (rule.sourcePattern) new RegExp(rule.sourcePattern, rule.flags);
36
+ if (rule.allowPattern) new RegExp(rule.allowPattern, rule.flags);
37
+ ids.add(rule.id);
38
+ return rule as Rule;
39
+ });
40
+ for (const entry of glossary as GlossaryEntry[]) {
41
+ if (!entry.publicForbidden) continue;
42
+ const id = `GLOSSARY-${entry.internal}`;
43
+ if (!entry.internal || ids.has(id)) throw new Error(`invalid brand glossary: ${path}`);
44
+ ids.add(id);
45
+ const literal = entry.internal.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
46
+ const pattern = `(?<![가-힣])${literal}(?![가-힣])`;
47
+ rules.push({ id, kind: 'forbid', pattern, scope: ['any'], why: `Use ${entry.public} instead of the internal term.`, source: `${path}#glossary` });
48
+ }
49
+ return rules;
50
+ }
51
+
52
+ function filesAt(paths: string[]): string[] {
53
+ const files = new Set<string>();
54
+ const visit = (path: string): void => {
55
+ const stat = statSync(path);
56
+ if (stat.isDirectory()) {
57
+ for (const entry of readdirSync(path, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
58
+ if (entry.isDirectory() || entry.isFile()) visit(join(path, entry.name));
59
+ }
60
+ } else if (stat.isFile()) files.add(path);
61
+ };
62
+ for (const path of paths) visit(resolve(path));
63
+ return [...files].sort();
64
+ }
65
+
66
+ function maskUiStrings(line: string): string {
67
+ 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/;
68
+ const chars = line.split('');
69
+ const match = ui.exec(line);
70
+ if (match) {
71
+ const value = match[2]!;
72
+ const start = match.index + match[0].indexOf(value);
73
+ for (let i = start; i < start + value.length; i++) chars[i] = ' ';
74
+ }
75
+ return chars.join('');
76
+ }
77
+
78
+ function htmlVisible(content: string): string {
79
+ const blank = (text: string) => text.replace(/[^\r\n]/g, ' ');
80
+ return content.replace(/<!--[\s\S]*?-->|<(?:script|style)\b[^>]*>[\s\S]*?<\/\s*(?:script|style)\s*>/gi, blank)
81
+ .replace(/<(?:[^>"']|"[^"]*"|'[^']*')*>/g, (tag) => {
82
+ if (!/^<img\b/i.test(tag)) return blank(tag);
83
+ const alt = /\balt\s*=\s*(["'])(.*?)\1/is.exec(tag);
84
+ if (!alt) return blank(tag);
85
+ const offset = alt.index + alt[0].indexOf(alt[2]!);
86
+ return blank(tag.slice(0, offset)) + alt[2] + blank(tag.slice(offset + alt[2].length));
87
+ });
88
+ }
89
+
90
+ function isEnglishDocument(content: string, file: string, scope: Scope): boolean {
91
+ const html = ['.html', '.htm'].includes(extname(file).toLowerCase());
92
+ const frontmatter = /^---\s*\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content)?.[1];
93
+ const language = html
94
+ ? /<html\b[^>]*\blang\s*=\s*["']?([a-z]{2,}(?:-[a-z]+)*)/i.exec(content)?.[1]
95
+ ?? /<meta\b[^>]*\b(?:http-equiv\s*=\s*["']?content-language|name\s*=\s*["']?language)[^>]*\bcontent\s*=\s*["']?([a-z]{2,})/i.exec(content)?.[1]
96
+ : frontmatter ? /^lang(?:uage)?\s*:\s*["']?([a-z]{2,}(?:-[a-z]+)*)/im.exec(frontmatter)?.[1] : undefined;
97
+ if (language) return /^en(?:-|$)/i.test(language);
98
+ // Language-suffixed filenames are an explicit declaration, even without frontmatter.
99
+ if (/(?:^|[._-])(?:en|ko)(?:-[a-z]+)?\.[^.]+$/i.test(file)) return /(?:^|[._-])en(?:-[a-z]+)?\.[^.]+$/i.test(file);
100
+ if (scope !== 'site') return false;
101
+ const body = (html ? htmlVisible(content) : content.replace(/^---\s*\r?\n[\s\S]*?\r?\n---/, ''))
102
+ .replace(/\bElanous\b/gi, '');
103
+ return /\b[A-Za-z]{2,}(?:\s+[A-Za-z]{2,}){2,}\b/.test(body);
104
+ }
105
+
106
+ function findInFile(file: string, rules: Rule[], scope: Scope): Finding[] {
107
+ const findings: Finding[] = [];
108
+ const content = readFileSync(file, 'utf8');
109
+ const html = ['.html', '.htm'].includes(extname(file).toLowerCase());
110
+ const englishDocument = isEnglishDocument(content, file, scope);
111
+ const markdown = ['.md', '.mdx'].includes(extname(file).toLowerCase());
112
+ const bodyLines = html ? htmlVisible(content).split(/\r?\n/) : [];
113
+ let fence: { marker: string; length: number } | undefined;
114
+ const allLines = content.split(/\r?\n/);
115
+ for (const [index, line] of allLines.entries()) {
116
+ const lineFindings: Finding[] = [];
117
+ const marker = markdown ? /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1] : undefined;
118
+ if (marker) {
119
+ if (!fence) fence = { marker: marker[0]!, length: marker.length };
120
+ else if (marker[0] === fence.marker && marker.length >= fence.length) fence = undefined;
121
+ }
122
+ const quoted = markdown && /^\s*>/.test(line);
123
+ const inCode = Boolean(fence || marker || quoted);
124
+ for (const rule of rules) {
125
+ if (rule.kind === 'korean-in-english' && !englishDocument) continue;
126
+ const text = rule.kind === 'korean-in-english' && html ? bodyLines[index]! : line;
127
+ const visible = rule.allowInCode && inCode ? maskUiStrings(text) : text;
128
+ const allowed = rule.allowPattern ? [...line.matchAll(new RegExp(rule.allowPattern, [...new Set(`${rule.flags ?? ''}g`)].join('')))] : [];
129
+ const matches = visible.matchAll(new RegExp(rule.pattern, [...new Set(`${rule.flags ?? ''}g`)].join('')));
130
+ // B4: a measured number must carry its source marker on the same line or a neighbouring one.
131
+ const sourced = rule.kind === 'number-needs-source'
132
+ && [index - 1, index, index + 1].some((i) => i >= 0 && i < allLines.length && new RegExp(rule.sourcePattern!, rule.flags).test(allLines[i]!));
133
+ for (const match of matches) {
134
+ if (sourced) break;
135
+ if (allowed.some((exception) => exception.index! <= match.index! && match.index! < exception.index! + exception[0].length)) continue;
136
+ lineFindings.push({ file, line: index + 1, id: rule.id, match: match[0] });
137
+ if (rule.kind === 'korean-in-english') break;
138
+ }
139
+ }
140
+ findings.push(...lineFindings.filter((finding) => finding.id !== 'B11'
141
+ || !lineFindings.some((other) => other.id === 'BRAND-AUTONOMY' && other.match.includes(finding.match))));
142
+ }
143
+ return findings;
144
+ }
145
+
146
+ export function checkBrand(scope: Scope, paths: string[], rulesPath = defaultRules): { files: number; findings: Finding[]; rules: number; missing: boolean } {
147
+ if (!existsSync(rulesPath)) {
148
+ debug.log('brand.check', 'run', { scope, files: 0, findings: 0, rules: 0 });
149
+ return { files: 0, findings: [], rules: 0, missing: true };
150
+ }
151
+ const applicable = loadRules(rulesPath).filter((rule) => scope === 'any' || rule.scope.includes('any') || rule.scope.includes(scope));
152
+ const files = filesAt(paths);
153
+ const findings = files.flatMap((file) => findInFile(file, applicable, scope));
154
+ debug.log('brand.check', 'run', { scope, files: files.length, findings: findings.length, rules: applicable.length });
155
+ return { files: files.length, findings, rules: applicable.length, missing: false };
156
+ }
157
+
158
+ if (import.meta.main) {
159
+ try {
160
+ const args = process.argv.slice(2);
161
+ let scope: Scope | undefined;
162
+ let rulesPath = defaultRules;
163
+ let json = false;
164
+ const paths: string[] = [];
165
+ for (let i = 0; i < args.length; i++) {
166
+ const arg = args[i]!;
167
+ if (arg === '--scope') scope = args[++i] as Scope;
168
+ else if (arg === '--rules') rulesPath = args[++i] ?? '';
169
+ else if (arg === '--json') json = true;
170
+ else if (arg.startsWith('--')) throw new Error(`unknown option: ${arg}`);
171
+ else paths.push(arg);
172
+ }
173
+ if (!scope || !scopes.includes(scope) || !paths.length || !rulesPath) throw new Error('usage: bun scripts/brand/check.ts --scope <scope> <file|folder...> [--rules <path>] [--json]');
174
+ const result = checkBrand(scope, paths, rulesPath);
175
+ if (result.missing) console.log(json ? JSON.stringify({ ...result, message: '규칙 없음' }) : '규칙 없음');
176
+ else if (json) console.log(JSON.stringify({ scope, ...result }));
177
+ else {
178
+ for (const finding of result.findings) console.log(`${finding.file}:${finding.line}:${finding.id}:${finding.match}`);
179
+ console.log(`brand-check ${scope} files=${result.files} findings=${result.findings.length} rules=${result.rules}`);
180
+ }
181
+ if (result.findings.length) process.exitCode = 1;
182
+ } catch (error) {
183
+ console.error(error instanceof Error ? error.message : String(error));
184
+ process.exitCode = 2;
185
+ }
186
+ }
@@ -1,24 +1,46 @@
1
+ // Bounded Claude Code hook extractor: only seat folders (marked by `.claude/seat`) emit, and only fixed metadata.
1
2
  import { spawnSync } from 'node:child_process';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { dirname, isAbsolute, join } from 'node:path';
5
+
6
+ const SEATS = new Set(['OP', 'TC', 'MK', 'UX']);
7
+ const MAX_LEVELS = 6;
8
+
9
+ // Walks up from the session cwd to the git root (or MAX_LEVELS); no or invalid marker → null.
10
+ function findSeat(start: string): string | null {
11
+ let dir = start;
12
+ for (let level = 0; level <= MAX_LEVELS; level++) {
13
+ const marker = join(dir, '.claude', 'seat');
14
+ if (existsSync(marker)) {
15
+ const seat = readFileSync(marker, 'utf8').trim();
16
+ return SEATS.has(seat) ? seat : null;
17
+ }
18
+ if (existsSync(join(dir, '.git'))) return null;
19
+ const parent = dirname(dir);
20
+ if (parent === dir) return null;
21
+ dir = parent;
22
+ }
23
+ return null;
24
+ }
2
25
 
3
26
  const event = process.argv[2];
4
27
  if (event !== 'stop' && event !== 'post-tool-use') process.exit(0);
5
-
6
28
  try {
7
29
  const payload = JSON.parse(await Bun.stdin.text()) as Record<string, unknown>;
8
- // A locator is useful only when it is a session ID, not arbitrary hook input.
9
30
  const session = payload.session_id;
10
31
  if (typeof session !== 'string' || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(session)) process.exit(0);
32
+ const cwd = typeof payload.cwd === 'string' && isAbsolute(payload.cwd) ? payload.cwd : process.cwd();
33
+ const seat = findSeat(cwd);
34
+ if (seat === null) process.exit(0);
35
+ if (event === 'stop') {
36
+ spawnSync('elanous', ['context', 'emit', 'task-done', '--seat', seat, '--text', 'Claude Code session stopped', '--ref', session],
37
+ { stdio: 'ignore', timeout: 3000 });
38
+ process.exit(0);
39
+ }
11
40
  const tool = payload.tool_name;
12
- if (event === 'post-tool-use' && (typeof tool !== 'string' || !/^[a-z][a-z0-9_.:-]{0,63}$/i.test(tool))) process.exit(0);
13
- // Only known tool identifiers can appear in the generated summary. Never copy hook free text.
14
- const knownTools = new Set(['Read', 'Write', 'Edit', 'MultiEdit', 'Bash', 'Glob', 'Grep', 'LS',
15
- 'Task', 'WebFetch', 'WebSearch', 'NotebookEdit', 'TodoWrite', 'AskUserQuestion']);
16
- const summary = event === 'stop' ? 'Claude Code session stopped'
17
- : `Claude Code tool ${knownTools.has(tool as string) ? tool : 'other'} completed`;
41
+ if (typeof tool !== 'string' || !/^[a-z][a-z0-9_.:-]{0,63}$/i.test(tool)) process.exit(0);
42
+ const knownTools = new Set(['Read', 'Write', 'Edit', 'MultiEdit', 'Bash', 'Glob', 'Grep', 'LS', 'Task', 'WebFetch', 'WebSearch', 'NotebookEdit', 'TodoWrite', 'AskUserQuestion']);
43
+ const summary = `Claude Code tool ${knownTools.has(tool) ? tool : 'other'} completed`;
18
44
  const source = `elanous://context/claude-code/${session}`;
19
- spawnSync('elanous', ['context', 'emit', '--kind', 'done', '--summary', summary, '--source', source], {
20
- stdio: 'ignore', timeout: 3000,
21
- });
22
- } catch {
23
- // Hooks must not interrupt the seat session on malformed input or unavailable CLI.
24
- }
45
+ spawnSync('elanous', ['context', 'emit', '--kind', 'done', '--summary', summary, '--source', source], { stdio: 'ignore', timeout: 3000 });
46
+ } catch { /* never interrupt the seat session */ }