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,294 @@
1
+ #!/usr/bin/env bun
2
+ import { randomUUID } from 'node:crypto';
3
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, appendFileSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+ import { Database } from 'bun:sqlite';
6
+ import { effectiveInstanceRoot, prodInstanceRoot } from '../../src/instance/resolve.js';
7
+ import { getUserConfig } from '../../src/user-config.js';
8
+ import { loopEvent } from '../../src/loops/observe.js';
9
+ import { sendOutbound } from '../../src/domains/outbound-alert.js';
10
+ import { collectLandings, collectRelease, collectDecisions, type Landing, type Release, type Pending, type Section } from './daily.js';
11
+
12
+ type Issue = { id: string; title: string; owner: string; due: string; status: 'open' | 'done'; at: string; by: string };
13
+ type WeeklyParts = { landings: Section<Landing[]>; release: Section<Release>; issues: Section<Issue[]>;
14
+ followThrough: Section<{ issue: Issue; outcome: 'done' | '기한 뒤 완료' | '기한 넘김' | '진행'; doneAt?: string }[]>;
15
+ decisions: Section<Pending[]>; news: Section<{ title: string; url: string }[]> };
16
+ export type WeeklyDeps = { now?: () => Date; root?: string; vaultRoot?: string | null; sendEnabled?: boolean;
17
+ landings?: (now: Date, window: { from: Date; to: Date }) => Promise<Landing[]>; release?: () => Promise<Release>;
18
+ decisions?: () => Promise<Pending[]>; send?: (text: string, kind: string) => boolean;
19
+ receipt?: (key: string) => 'sent' | 'not-sent' | 'unknown';
20
+ log?: (category: string, event: string, data: Record<string, unknown>) => void; print?: (line: string) => void };
21
+ const reasonOf = (e: unknown) => e instanceof Error ? e.message : String(e);
22
+ const object = (v: unknown): Record<string, unknown> => v && typeof v === 'object' && !Array.isArray(v) ? v as Record<string, unknown> : {};
23
+ const sortNames = (a: string, b: string) => a < b ? -1 : a > b ? 1 : 0;
24
+ const DAY = 86_400_000;
25
+ const KST = 9 * 3_600_000;
26
+ const kstDate = (date: Date) => new Date(date.getTime() + KST).toISOString().slice(0, 10);
27
+ function weekOf(now: Date) {
28
+ const local = new Date(now.getTime() + KST);
29
+ const day = local.getUTCDay();
30
+ const monday = Date.UTC(local.getUTCFullYear(), local.getUTCMonth(), local.getUTCDate() - ((day + 6) % 7));
31
+ const current = new Date(monday - KST);
32
+ const from = new Date(current.getTime() - 7 * DAY);
33
+ const to = current;
34
+ const thursday = new Date(monday + 3 * DAY);
35
+ const year = thursday.getUTCFullYear();
36
+ const firstThursday = Date.UTC(year, 0, 4);
37
+ const firstMonday = firstThursday - ((new Date(firstThursday).getUTCDay() + 6) % 7) * DAY;
38
+ const week = Math.floor((monday - firstMonday) / (7 * DAY)) + 1;
39
+ return { from, to, key: `${year}-W${String(week).padStart(2, '0')}` };
40
+ }
41
+ const issuePath = (root: string) => join(root, 'rhythm', 'weekly', 'issues.jsonl');
42
+ function issueHistory(root: string): Issue[] {
43
+ const file = issuePath(root);
44
+ if (!existsSync(file)) return [];
45
+ return readFileSync(file, 'utf8').split('\n').filter(Boolean).map((line, index) => {
46
+ let row: Record<string, unknown>;
47
+ try { row = object(JSON.parse(line)); } catch { throw new Error(`현안 원장 ${index + 1}줄 JSON 오류`); }
48
+ if (!['id', 'title', 'owner', 'due', 'at', 'by'].every(k => typeof row[k] === 'string' && row[k]) || !['open', 'done'].includes(String(row.status)) ||
49
+ !Number.isFinite(Date.parse(String(row.at))) || !Number.isFinite(Date.parse(String(row.due)))) throw new Error(`현안 원장 ${index + 1}줄 형식 오류`);
50
+ return row as Issue;
51
+ });
52
+ }
53
+ function latest(rows: Issue[], before = Infinity): Map<string, Issue> {
54
+ const map = new Map<string, Issue>();
55
+ for (const row of rows) if (Date.parse(row.at) < before) map.set(row.id, row);
56
+ return map;
57
+ }
58
+ function appendIssue(root: string, row: Issue) {
59
+ const file = issuePath(root);
60
+ mkdirSync(dirname(file), { recursive: true });
61
+ appendFileSync(file, `${JSON.stringify(row)}\n`);
62
+ }
63
+ function readDailyNews(root: string, from: Date, to: Date): Section<{ title: string; url: string }[]> {
64
+ const found = new Map<string, { title: string; url: string }>();
65
+ const failures: string[] = [];
66
+ let files = 0;
67
+ for (let at = from.getTime(); at < to.getTime(); at += DAY) {
68
+ const day = kstDate(new Date(at));
69
+ const file = join(root, 'rhythm', 'daily', `${day}.md`);
70
+ if (!existsSync(file)) { failures.push(`${day} 데일리 없음`); continue; }
71
+ files++;
72
+ try {
73
+ const text = readFileSync(file, 'utf8');
74
+ const block = text.split('## ⑦ 외부 동향\n')[1]?.split(/\n\n## /)[0]?.trim();
75
+ if (!block) throw new Error('외부 동향 절 없음');
76
+ if (block.startsWith('못 읽음 ·')) throw new Error(block.split('\n')[0]!);
77
+ for (const line of block.split('\n')) {
78
+ if (line === '수집된 기사 0건' || /^ 시사점: /.test(line)) continue;
79
+ const match = /^- (.+) — (https?:\/\/\S+)$/.exec(line);
80
+ if (!match) throw new Error(`외부 동향 기사 형식 오류: ${line}`);
81
+ if (!found.has(match[2]!)) found.set(match[2]!, { title: match[1]!, url: match[2]! });
82
+ }
83
+ } catch (e) { failures.push(`${day} ${reasonOf(e)}`); }
84
+ }
85
+ const news = [...found.values()].slice(0, 10);
86
+ if (failures.length) return { status: 'unreadable', reason: `${files ? failures.join('; ') : '데일리 없음'}${news.length ? `\n부분 수집 (완전한 주간 집계 아님):\n${news.map(n => `- ${n.title} — ${n.url}`).join('\n')}` : ''}` };
87
+ return { status: 'ok', value: news };
88
+ }
89
+ const labels: Record<keyof WeeklyParts, string> = {
90
+ landings: '① 지난주 성과', release: '② 이번 주 판 계획', issues: '③ 주간 현안',
91
+ followThrough: '④ 지난주 현안 이행 대조', decisions: '⑤ 결정 필요', news: '⑥ 주간 외부 동향',
92
+ };
93
+ function publishedVersions(root: string, from: Date, to: Date): string[] {
94
+ const directory = join(root, 'release');
95
+ if (!existsSync(directory)) return [];
96
+ return readdirSync(directory).filter(version => /^\d+\.\d+\.\d+$/.test(version)).filter(version => {
97
+ const file = join(directory, version, 'release.json');
98
+ if (!existsSync(file)) return false;
99
+ const record = object(JSON.parse(readFileSync(file, 'utf8')));
100
+ const publishedAt = record.publishedAt;
101
+ return record.version === version && typeof publishedAt === 'string' && Number.isFinite(Date.parse(publishedAt)) && Date.parse(publishedAt) >= from.getTime() && Date.parse(publishedAt) < to.getTime();
102
+ }).sort(sortNames);
103
+ }
104
+ function compose(parts: WeeklyParts, key: string, now: Date, versions: Section<string[]>) {
105
+ const open = parts.issues.status === 'ok' ? parts.issues.value : [];
106
+ const overdue = open.filter(i => Date.parse(i.due) < now.getTime());
107
+ const red = parts.release.status === 'ok' ? parts.release.value.red : [];
108
+ const risk = overdue[0] ? `${overdue[0].title} 기한 넘김 (${overdue[0].owner})` : red[0] ? `${red[0].name} red 칸` : '확인된 위험 없음(못 읽은 절은 별도 확인)';
109
+ const old = parts.decisions.status === 'ok' ? parts.decisions.value.filter(d => now.getTime() - Date.parse(d.openedAt) > 3 * DAY) : [];
110
+ const header = [
111
+ `S: 지난주 착지 ${parts.landings.status === 'ok' ? `${parts.landings.value.length}건` : `못 읽음 · ${parts.landings.reason}`} · 판 ${parts.release.status === 'ok' ? `${parts.release.value.green}/${parts.release.value.total}` : `못 읽음 · ${parts.release.reason}`}`,
112
+ `C: ${risk}`, `Q: 이번 주 ${old.length ? old[0]!.name : '어떤 현안을'} 정할 것인가?`,
113
+ `A: ${overdue[0] ? `${overdue[0].owner}가 ${overdue[0].title} 이행 기한을 확인` : red[0] ? `${red[0].name} 해소를 결정` : '열린 현안의 담당과 기한을 확인'}`,
114
+ ].join('\n');
115
+ const section = <T>(name: keyof WeeklyParts, value: Section<T>, format: (v: T) => string) =>
116
+ `## ${labels[name]}\n${value.status === 'ok' ? format(value.value) : `못 읽음 · ${value.reason}`}`;
117
+ const markdown = [
118
+ `# 주간 미팅 — ${key} (KST)`, header,
119
+ section('landings', parts.landings, list => {
120
+ const seats = [...new Set(list.map(i => i.seat))].sort(sortNames).map(s => `${s} ${list.filter(i => i.seat === s).length}`).join(' · ');
121
+ return `총 ${list.length}건 · 자리별 ${seats || '없음'}\n발행된 판: ${versions.status === 'ok' ? versions.value.join(', ') || '없음' : `못 읽음 · ${versions.reason}`}`;
122
+ }),
123
+ section('release', parts.release, r => `${r.version}: green ${r.green}/${r.total} · 다음 판 ${r.nextVersion ?? '미정'} ${r.nextGreen ?? '못 읽음'}/${r.nextTotal ?? '못 읽음'} · 다음 컷 ${r.cutAt ?? '미정'}\nred 칸: ${r.red.map(i => i.name).join(', ') || '없음'}`),
124
+ section('issues', parts.issues, list => list.map(i => `- ${i.id} ${i.title} · ${i.owner} · 기한 ${i.due}`).join('\n') || '열린 현안 없음'),
125
+ section('followThrough', parts.followThrough, list => list.map(({ issue, outcome, doneAt }) => `- ${issue.id} ${issue.title} · ${issue.owner} · 기한 ${issue.due} — ${outcome}${doneAt ? ` (완료 ${doneAt})` : ''}`).join('\n') || '지난주 열린 현안 없음'),
126
+ section('decisions', parts.decisions, list => list.filter(d => now.getTime() - Date.parse(d.openedAt) > 3 * DAY).map(d => `- ${d.name} (${d.openedAt})`).join('\n') || '3일 넘은 결정 없음'),
127
+ section('news', parts.news, list => list.map(n => `- ${n.title} — ${n.url}`).join('\n') || '수집된 기사 0건'),
128
+ ].join('\n\n') + '\n';
129
+ const sections = Object.fromEntries(Object.keys(labels).map(k => [k, parts[k as keyof WeeklyParts].status])) as Record<keyof WeeklyParts, 'ok' | 'unreadable'>;
130
+ return { markdown, header, sections, issuesOpen: open.length, issuesOverdue: overdue.length };
131
+ }
132
+ function vaultCopy(markdown: string, key: string, deps: WeeklyDeps) {
133
+ let root = deps.vaultRoot;
134
+ if (root === undefined) {
135
+ try {
136
+ root = String(object(getUserConfig().raw.obsidian).vaultRoot || '') || null;
137
+ if (root && effectiveInstanceRoot() !== prodInstanceRoot()) return { vaultError: '격리 인스턴스 — 운영 볼트 사본 건너뜀', vaultFatal: false };
138
+ } catch (e) { return { vaultError: reasonOf(e), vaultFatal: true }; }
139
+ }
140
+ if (!root) return { vaultError: null, vaultFatal: false };
141
+ try {
142
+ const file = join(root, '00. Inbox', 'Weekly Review', `${key}.md`);
143
+ mkdirSync(dirname(file), { recursive: true }); writeFileSync(file, markdown);
144
+ return { vaultError: null, vaultFatal: false };
145
+ } catch (e) { return { vaultError: reasonOf(e), vaultFatal: true }; }
146
+ }
147
+ function sendReview(file: string, text: string, send: (text: string, kind: string) => boolean, receipt: (key: string) => 'sent' | 'not-sent' | 'unknown') {
148
+ const key = file.slice(file.lastIndexOf('/') + 1).replace(/\.md$/, '');
149
+ const db = new Database(join(dirname(file), 'delivery.sqlite'));
150
+ try {
151
+ db.run("CREATE TABLE IF NOT EXISTS deliveries (week TEXT PRIMARY KEY, state TEXT NOT NULL CHECK(state IN ('pending','unknown','sent')))");
152
+ db.run("INSERT OR IGNORE INTO deliveries(week, state) VALUES (?, 'pending')", [key]);
153
+ const state = (db.prepare('SELECT state FROM deliveries WHERE week = ?').get(key) as { state: string }).state;
154
+ if (state === 'sent') return { sent: false, sendError: null };
155
+ if (state === 'unknown') {
156
+ const verdict = receipt(key);
157
+ if (verdict === 'sent') { db.run("UPDATE deliveries SET state = 'sent' WHERE week = ?", [key]); return { sent: false, sendError: null }; }
158
+ if (verdict !== 'not-sent') return { sent: false, sendError: `발송 결과 불명 · RHYTHM-WEEKLY:${key} 영수증 확인 필요` };
159
+ db.run("UPDATE deliveries SET state = 'pending' WHERE week = ?", [key]);
160
+ }
161
+ if (!db.prepare("UPDATE deliveries SET state = 'unknown' WHERE week = ? AND state = 'pending'").run(key).changes)
162
+ return { sent: false, sendError: `발송 결과 불명 · RHYTHM-WEEKLY:${key} 영수증 확인 필요` };
163
+ try {
164
+ if (!send(`${text}\nRHYTHM-WEEKLY:${key}`, 'ops-report')) return { sent: false, sendError: '발송 실패 · 결과 불명 (영수증 확인 필요)' };
165
+ db.run("UPDATE deliveries SET state = 'sent' WHERE week = ?", [key]);
166
+ return { sent: true, sendError: null };
167
+ } catch (e) { return { sent: false, sendError: `발송 결과 불명 · ${reasonOf(e)}` }; }
168
+ } catch (e) { return { sent: false, sendError: `발송 상태 못 읽음 · ${reasonOf(e)}` }; }
169
+ finally { db.close(); }
170
+ }
171
+ function outboundReceipt(root: string, key: string): 'sent' | 'unknown' {
172
+ const file = join(root, ...(root === prodInstanceRoot() ? ['memory'] : []), 'surface_events.db');
173
+ if (!existsSync(file)) return 'unknown';
174
+ try {
175
+ const db = new Database(file, { readonly: true, create: false });
176
+ try { return db.prepare("SELECT 1 FROM events WHERE direction='outbound' AND kind='ops-report' AND instr(text, ?) > 0 LIMIT 1").get(`RHYTHM-WEEKLY:${key}`) ? 'sent' : 'unknown'; }
177
+ finally { db.close(); }
178
+ } catch { return 'unknown'; }
179
+ }
180
+ export async function runWeekly(options: { dryRun?: boolean; stage?: 'collect' | 'compose' | 'deliver' } = {}, deps: WeeklyDeps = {}) {
181
+ const now = (deps.now ?? (() => new Date()))();
182
+ const root = deps.root ?? effectiveInstanceRoot();
183
+ const { from, to, key } = weekOf(now);
184
+ const file = join(root, 'rhythm', 'weekly', `${key}.md`);
185
+ let markdown: string;
186
+ let header: string;
187
+ let sections: Record<keyof WeeklyParts, 'ok' | 'unreadable'>;
188
+ let issuesOpen: number;
189
+ let issuesOverdue: number;
190
+ if (options.stage === 'compose' || options.stage === 'deliver') {
191
+ if (!existsSync(file)) throw new Error(`수집 파일 없음: ${file}`);
192
+ markdown = readFileSync(file, 'utf8');
193
+ header = markdown.split('\n').slice(2, 6).join('\n');
194
+ if (!header.startsWith('S: ') || Object.values(labels).some(label => !markdown.includes(`## ${label}\n`))) throw new Error(`리뷰 파일 형식 오류: ${file}`);
195
+ sections = Object.fromEntries(Object.entries(labels).map(([k, label]) => [k, markdown.split(`## ${label}\n`)[1]?.startsWith('못 읽음 ·') ? 'unreadable' : 'ok'])) as typeof sections;
196
+ if (sections.issues === 'unreadable') { issuesOpen = 0; issuesOverdue = 0; }
197
+ else {
198
+ const open = [...latest(issueHistory(root)).values()].filter(i => i.status === 'open');
199
+ issuesOpen = open.length;
200
+ issuesOverdue = open.filter(i => Date.parse(i.due) < now.getTime()).length;
201
+ }
202
+ } else {
203
+ const safe = async <T>(fn: () => Promise<T>): Promise<Section<T>> => {
204
+ try { return { status: 'ok', value: await fn() }; } catch (e) { return { status: 'unreadable', reason: reasonOf(e) }; }
205
+ };
206
+ const issues = await safe(async () => issueHistory(root));
207
+ const parts: WeeklyParts = {
208
+ landings: await safe(() => (deps.landings ?? collectLandings)(now, { from, to })),
209
+ release: await safe(deps.release ?? collectRelease),
210
+ issues: issues.status === 'ok' ? { status: 'ok', value: [...latest(issues.value).values()].filter(i => i.status === 'open') } : issues,
211
+ followThrough: issues.status === 'ok' ? { status: 'ok', value: [...new Set([
212
+ ...[...latest(issues.value, from.getTime()).values()].filter(i => i.status === 'open').map(i => i.id),
213
+ ...issues.value.filter(i => Date.parse(i.at) >= from.getTime() && Date.parse(i.at) < to.getTime()).map(i => i.id),
214
+ ])].map(id => {
215
+ const issue = issues.value.filter(i => i.id === id && Date.parse(i.at) < to.getTime()).at(-1)!;
216
+ const current = latest(issues.value).get(id);
217
+ // A completion after the due date is a missed deadline, not a plain «done» (review round 3).
218
+ if (current?.status === 'done') return { issue, outcome: Date.parse(current.at) > Date.parse(issue.due) ? '기한 뒤 완료' as const : 'done' as const, doneAt: current.at };
219
+ return { issue, outcome: Date.parse(issue.due) < now.getTime() ? '기한 넘김' as const : '진행' as const };
220
+ }) } : issues,
221
+ decisions: await safe(deps.decisions ?? collectDecisions),
222
+ news: readDailyNews(root, from, to),
223
+ };
224
+ const versions = await safe(async () => publishedVersions(root, from, to));
225
+ if (versions.status === 'unreadable') {
226
+ parts.landings = { status: 'unreadable', reason: `${parts.landings.status === 'unreadable' ? `${parts.landings.reason}; ` : ''}발행된 판 목록 못 읽음 · ${versions.reason}` };
227
+ }
228
+ const review = compose(parts, key, now, versions);
229
+ ({ markdown, header, sections, issuesOpen, issuesOverdue } = review);
230
+ mkdirSync(dirname(file), { recursive: true }); writeFileSync(file, markdown);
231
+ }
232
+ const vault = options.stage === 'deliver' ? { vaultError: null, vaultFatal: false } : vaultCopy(markdown, key, deps);
233
+ let sent = false;
234
+ let sendError: string | null = null;
235
+ let enabled = deps.sendEnabled === true;
236
+ if (deps.sendEnabled === undefined && !options.dryRun && options.stage !== 'collect') {
237
+ try { enabled = object(object(object(getUserConfig().raw.loops).rhythm).weekly).send === true; }
238
+ catch (e) { sendError = `발송 설정 못 읽음 · ${reasonOf(e)}`; }
239
+ }
240
+ if (!vault.vaultFatal && !options.dryRun && options.stage !== 'collect' && options.stage !== 'compose' && enabled) {
241
+ const delivery = sendReview(file, `${header}\n\n${file}`, deps.send ?? sendOutbound, deps.receipt ?? (key => outboundReceipt(root, key)));
242
+ sent = delivery.sent; sendError = delivery.sendError;
243
+ }
244
+ if (options.stage !== 'collect' && options.stage !== 'compose') {
245
+ const tick = { sections, issuesOpen, issuesOverdue, sent };
246
+ if (deps.log) deps.log('loop.rhythm-weekly', 'tick', tick); else loopEvent('rhythm-weekly', 'tick', tick);
247
+ }
248
+ return { file, markdown, header, sections, issuesOpen, issuesOverdue, sent, ...vault, sendError };
249
+ }
250
+ export async function main(args: string[] = process.argv.slice(2), deps: WeeklyDeps = {}) {
251
+ const print = deps.print ?? console.log;
252
+ const root = deps.root ?? effectiveInstanceRoot();
253
+ if (args[0] === '--help') {
254
+ print('bun scripts/rhythm/weekly.ts [--dry-run] [--json]\nbun scripts/rhythm/weekly.ts issue add --title <t> --owner <자리> --due <ISO> [--by <이름>]\nbun scripts/rhythm/weekly.ts issue done <id> [--by <이름>]\nbun scripts/rhythm/weekly.ts issue list [--json]');
255
+ return;
256
+ }
257
+ if (args[0] === 'issue') {
258
+ const [action, ...rest] = args.slice(1);
259
+ const flag = (name: string) => { const index = rest.indexOf(name); return index < 0 ? undefined : rest[index + 1]; };
260
+ const now = (deps.now ?? (() => new Date()))().toISOString();
261
+ if (action === 'list') {
262
+ if (rest.some(arg => arg !== '--json')) throw new Error('알 수 없는 옵션');
263
+ const rows = [...latest(issueHistory(root)).values()];
264
+ print(rest.includes('--json') ? JSON.stringify(rows) : rows.map(i => `${i.id} · ${i.title} · ${i.owner} · ${i.due} · ${i.status}`).join('\n') || '현안 없음');
265
+ return rows;
266
+ }
267
+ const by = rest.includes('--by') ? flag('--by') : process.env.AI_AGENT;
268
+ if (!by) throw new Error('--by 또는 AI_AGENT 필요');
269
+ if (action === 'add') {
270
+ const title = flag('--title'), owner = flag('--owner'), due = flag('--due');
271
+ if (!title || !owner || !due || !/^\d{4}-\d\d-\d\dT\d\d:\d\d/.test(due) || !Number.isFinite(Date.parse(due)) || rest.length % 2 ||
272
+ rest.some((arg, i) => i % 2 === 0 ? !['--title', '--owner', '--due', '--by'].includes(arg) : !arg || arg.startsWith('--'))) throw new Error('issue add: --title --owner --due <ISO> 필요');
273
+ const row: Issue = { id: randomUUID(), title, owner, due, status: 'open', at: now, by };
274
+ appendIssue(root, row); print(JSON.stringify(row)); return row;
275
+ }
276
+ if (action === 'done') {
277
+ const id = rest[0];
278
+ if (!id || (rest.length !== 1 && (rest.length !== 3 || rest[1] !== '--by' || !rest[2]))) throw new Error('issue done <id> [--by <이름>]');
279
+ const issue = latest(issueHistory(root)).get(id);
280
+ if (!issue) throw new Error(`현안 없음: ${id}`);
281
+ const row: Issue = { ...issue, status: 'done', at: now, by };
282
+ appendIssue(root, row); print(JSON.stringify(row)); return row;
283
+ }
284
+ throw new Error(`알 수 없는 issue 명령: ${action}`);
285
+ }
286
+ const stage = process.env.ELANOUS_GRAPH_DIR && ['collect', 'compose', 'deliver'].includes(args[0] ?? '') ? args.shift() as 'collect' | 'compose' | 'deliver' : undefined;
287
+ for (const arg of args) if (!['--dry-run', '--json'].includes(arg)) throw new Error(`알 수 없는 옵션: ${arg}`);
288
+ const result = await runWeekly({ dryRun: args.includes('--dry-run'), stage }, deps);
289
+ print(args.includes('--json') ? JSON.stringify(result) : `${result.markdown}\n파일: ${result.file} · sent=${result.sent}`);
290
+ if ((stage === 'compose' || !stage) && result.vaultFatal) throw new Error(`볼트 사본 실패 · ${result.vaultError}`);
291
+ if ((stage === 'deliver' || !stage) && result.sendError) throw new Error(`발송 실패 · ${result.sendError}`);
292
+ return result;
293
+ }
294
+ if (import.meta.main) main().catch(e => { console.error(`rhythm-weekly: ${reasonOf(e)}`); process.exitCode = 1; });
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env bun
2
+ /** Static readiness inventory for the public (core) skills; never executes a skill. */
3
+ import { existsSync, lstatSync, readdirSync, readFileSync } from 'node:fs';
4
+ import { join, relative } from 'node:path';
5
+ import { computeSkillBoundaries, type SkillBoundaryReport } from '../skill-boundary.js';
6
+
7
+ export type PublicCheckKey = 'header' | 'entry' | 'free' | 'portable' | 'install';
8
+ export interface PublicSkillRow {
9
+ readonly skill: string;
10
+ readonly checks: Readonly<Record<PublicCheckKey, boolean | 'N/A'>>;
11
+ readonly violations: readonly string[];
12
+ }
13
+ export interface PublicCheckReport {
14
+ readonly skills: readonly PublicSkillRow[];
15
+ readonly errors: readonly string[];
16
+ readonly ok: boolean;
17
+ }
18
+
19
+ const PERSONAL_OR_INTERNAL = /\/Users\/|~\/source(?:\/|\b)|\b(?:INTERNAL_ONLY|ELANOUS_INTERNAL|PRIVATE_INTERNAL|internal-only)\b|@internal\b|내부\s*전용/iu;
20
+ const EXECUTABLE = /\.(?:ts|tsx|js|mjs|cjs|py|sh|bash)$/iu;
21
+ const ENTRY_DIR = /^(?:scripts|src|bin)\//u;
22
+ const NON_ENTRY = /(?:\.d\.ts|\.(?:test|spec)\.[^.]+)$/iu;
23
+ const RUN_COMMAND = /(?:^|[\s`$])(?:bun(?:\s+run)?|node|python3?|bash|sh|npx\s+tsx|uv\s+run\s+python3?)\s+((?:\.\/)?(?:scripts|src|bin)\/[\w./-]+\.(?:ts|tsx|js|mjs|cjs|py|sh|bash))\b/gimu;
24
+
25
+ function skillFiles(dir: string): string[] {
26
+ const files: string[] = [];
27
+ function visit(path: string): void {
28
+ for (const item of readdirSync(path, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
29
+ if (item.name === '.git' || item.name === 'node_modules' || item.name === '__pycache__') continue;
30
+ const file = join(path, item.name);
31
+ if (item.isDirectory()) visit(file);
32
+ else if (item.isFile()) files.push(file);
33
+ }
34
+ }
35
+ visit(dir);
36
+ return files;
37
+ }
38
+
39
+ function skillHeader(doc: string): string | undefined {
40
+ return /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(doc)?.[1];
41
+ }
42
+
43
+ function headerValid(doc: string, skill: string): boolean {
44
+ const header = skillHeader(doc);
45
+ if (!header) return false;
46
+ const name = /^name:\s*['"]?([^\s'"#]+)['"]?\s*$/mu.exec(header)?.[1];
47
+ const description = /^description:\s*(.*)$/mu.exec(header);
48
+ if (!description) return false;
49
+ const value = description[1]!.replace(/\s+#.*$/u, '').trim();
50
+ const following = header.slice(description.index + description[0].length).split(/\r?\n/u).slice(1);
51
+ const multiline = following.some((line) => /^\s+\S/u.test(line) && !/^\s*#/u.test(line));
52
+ const block = /^[>|][+-]?$/u.test(value);
53
+ // 리뷰 must-fix: `description: >-` 처럼 블록 표시만 있고 뒤 줄이 비면 설명이 없다.
54
+ return name === skill && (value !== '' && !block && !value.startsWith('#') || block && multiline);
55
+ }
56
+
57
+ function promptOnly(doc: string): boolean {
58
+ return /^prompt-only:\s*true\s*(?:#.*)?$/mu.test(skillHeader(doc) ?? '');
59
+ }
60
+
61
+ const ENTRY_NAME = /^(?:scripts|src)\/(?:main|cli|index|run)\.[^/]+$/u;
62
+
63
+ /** 리뷰 must-fix: 확장자·디렉터리만으로 «실행 진입점»이 아니다(`src/types.ts`).
64
+ * 인정 = 문서가 실제로 실행하는 파일 ∪ `bin/` 아래 ∪ scripts|src 바로 아래 main·cli·index·run. */
65
+ function executableEntries(files: readonly string[], dir: string, docs: string): string[] {
66
+ const commanded = new Set([...docs.matchAll(RUN_COMMAND)].map((command) => command[1]!.replace(/^\.\//u, '')));
67
+ return files.map((file) => relative(dir, file)).filter((file) => ENTRY_DIR.test(file) && EXECUTABLE.test(file) && !NON_ENTRY.test(file)
68
+ && (commanded.has(file) || file.startsWith('bin/') || ENTRY_NAME.test(file)));
69
+ }
70
+
71
+ function freeValid(doc: string, entries: ReadonlySet<string>): boolean {
72
+ const section = /(?:^|\n)#{1,4}\s*[^\n]*(?:무료|free)[^\n]*\n([\s\S]*?)(?=\n#{1,4}\s|$)/giu;
73
+ for (const match of doc.matchAll(section)) {
74
+ const body = match[1]!;
75
+ const keyless = /(?:키\s*없이|키\s*불필요|키\s*없는|API\s*키가\s*필요\s*없|no\s+(?:api\s+)?key|without\s+(?:an?\s+)?(?:api\s+)?key|keyless)/iu;
76
+ const envRequired = body.split(/\r?\n/u).some((line) => /\.env\b/iu.test(line) &&
77
+ !/(?:without|no|not\s+required|optional)\s+(?:an?\s+)?\.env\b|\.env\b[^\n]*(?:없이|불필요|선택|optional|not\s+required)/iu.test(line) &&
78
+ /(?:\b(?:require|need|configure|create|copy|source|set\s*up|must)\b[^\n]*\.env\b|\.env\b[^\n]*(?:필요|설정|생성|복사|로드|\b(?:required|needed|must)\b))/iu.test(line));
79
+ const runnable = [...body.matchAll(RUN_COMMAND)].some((command) => entries.has(command[1]!.replace(/^\.\//u, '')));
80
+ // 리뷰 must-fix: 제목이 «키 없이»여도 본문이 키를 요구하면 무료 경로가 아니다.
81
+ const keyRequired = /(?:\brequires?\s+(?:an?\s+)?(?:api\s+)?key\b|\bneeds?\s+(?:an?\s+)?(?:api\s+)?key\b|(?:API\s*)?키가?\s*필요(?!\s*없))/iu.test(body);
82
+ if (keyless.test(match[0]) && runnable && !envRequired && !keyRequired &&
83
+ !/(?:키\s*없는?\s+(?:자동|실행)\s*(?:경로|을)\s*(?:보장|지원)하지\s*않|without\s+(?:an?\s+)?(?:api\s+)?key\s+(?:is\s+)?not\s+supported)/iu.test(body)) return true;
84
+ }
85
+ return false;
86
+ }
87
+
88
+ /** 리뷰 must-fix: manifest 와 설치 명령의 «종류»가 맞아야 한다(requirements.txt 만 있는데 `bun install` 은 통과 아님). */
89
+ function installValid(doc: string, dir: string): boolean {
90
+ const has = (name: string) => existsSync(join(dir, name)) && lstatSync(join(dir, name)).isFile();
91
+ if (has('package.json') && /\b(?:bun|npm|pnpm|yarn)\s+(?:install|i)\b/imu.test(doc)) return true;
92
+ for (const req of ['requirements.txt', 'requirements-python.txt']) {
93
+ if (has(req) && new RegExp(`\\b(?:pip|pip3|uv\\s+pip)\\s+install\\s+-r\\s+\\S*${req.replace('.', '\\.')}\\b`, 'imu').test(doc)) return true;
94
+ }
95
+ if (has('pyproject.toml') && /\b(?:(?:pip|pip3|uv\s+pip)\s+install\s+(?:-e\s+)?\.(?:\s|$|\[)|uv\s+sync\b)/imu.test(doc)) return true;
96
+ return false;
97
+ }
98
+
99
+ /** `boundary` is injectable for pure fixture tests; production always derives it from the repository. */
100
+ export function checkPublicSkills(root: string, boundary: SkillBoundaryReport = computeSkillBoundaries(root)): PublicCheckReport {
101
+ const errors = [...boundary.errors];
102
+ const skills: PublicSkillRow[] = [];
103
+ const skillsDir = join(root, 'skills');
104
+ const addon = new Set(boundary.skills.filter((row) => row.verdict === 'addon').map((row) => row.skill));
105
+ const names = existsSync(skillsDir) ? readdirSync(skillsDir).filter((name) => {
106
+ const dir = join(skillsDir, name);
107
+ return lstatSync(dir).isDirectory() && !addon.has(name);
108
+ }).sort() : [];
109
+ if (!existsSync(skillsDir)) errors.push('skills/ 디렉터리가 없다');
110
+ for (const row of boundary.skills) {
111
+ if (row.verdict === 'core' && !names.includes(row.skill)) errors.push(`${row.skill}: skills/ 디렉터리가 없다`);
112
+ }
113
+ for (const skill of names) {
114
+ const dir = join(skillsDir, skill);
115
+ const files = skillFiles(dir);
116
+ const docPath = join(dir, 'SKILL.md');
117
+ const doc = files.includes(docPath) ? readFileSync(docPath, 'utf8') : '';
118
+ const docs = [doc, ...files.filter((f) => f !== docPath && /(?:^|\/)(?:README|INSTALL)\.md$/iu.test(f)).map((f) => readFileSync(f, 'utf8'))].join('\n');
119
+ const matches: string[] = [];
120
+ for (const file of files) {
121
+ const bytes = readFileSync(file);
122
+ if (bytes.includes(0)) continue;
123
+ try {
124
+ if (PERSONAL_OR_INTERNAL.test(new TextDecoder('utf-8', { fatal: true }).decode(bytes))) matches.push(relative(root, file));
125
+ } catch {
126
+ // Binary assets are not text; extensionless and dotfiles are checked when decodable.
127
+ }
128
+ }
129
+ const entries = new Set(executableEntries(files, dir, docs));
130
+ const checks: PublicSkillRow['checks'] = {
131
+ header: headerValid(doc, skill),
132
+ entry: entries.size > 0 ? true : promptOnly(doc) ? 'N/A' : false,
133
+ free: freeValid(docs, entries),
134
+ portable: matches.length === 0,
135
+ install: installValid(docs, dir),
136
+ };
137
+ const violations = [
138
+ ...(!checks.header ? ['SKILL.md 머리(name·description) 없음/불일치'] : []),
139
+ ...(checks.entry === false ? ['스킬 내부 실행 파일(scripts/src/bin)이 없음'] : []),
140
+ ...(!checks.free ? ['키/.env 없이 쓰는 무료 경로가 문서에 없음'] : []),
141
+ ...matches.map((file) => `${file}: 개인 경로/내부 표지 문자열`),
142
+ ...(!checks.install ? ['의존 manifest와 문서의 한 줄 설치 명령이 없음'] : []),
143
+ ];
144
+ skills.push({ skill, checks, violations });
145
+ }
146
+ return { skills, errors, ok: errors.length === 0 && skills.every((row) => row.violations.length === 0) };
147
+ }
148
+
149
+ export function formatPublicCheck(report: PublicCheckReport): string {
150
+ const columns: PublicCheckKey[] = ['header', 'entry', 'free', 'portable', 'install'];
151
+ return [
152
+ `skill ${columns.map((key) => key.padEnd(9)).join(' ')} result`,
153
+ ...report.skills.map((row) => `${row.skill.padEnd(34)} ${columns.map((key) => (row.checks[key] === 'N/A' ? 'N/A' : row.checks[key] ? 'OK' : 'FAIL').padEnd(9)).join(' ')} ${row.violations.length ? row.violations.join('; ') : 'OK'}`),
154
+ ...report.errors.map((error) => `ERROR ${error}`),
155
+ `PUBLIC-CHECK ${report.ok ? 'PASS' : 'FAIL'} (${report.skills.length} public skills)`,
156
+ ].join('\n');
157
+ }
158
+
159
+ if (import.meta.main) {
160
+ const args = process.argv.slice(2);
161
+ if (args.some((arg) => arg !== '--json')) {
162
+ console.error('Usage: bun scripts/skills/public-check.ts [--json]');
163
+ process.exitCode = 2;
164
+ } else {
165
+ const report = checkPublicSkills(join(import.meta.dir, '..', '..'));
166
+ console.log(args.includes('--json') ? JSON.stringify(report, null, 2) : formatPublicCheck(report));
167
+ if (!report.ok) process.exitCode = 1;
168
+ }
169
+ }
@@ -31,8 +31,9 @@
31
31
  // reports the failure.
32
32
 
33
33
  import { mkdtempSync, rmSync } from 'node:fs';
34
+ import { spawnSync } from 'node:child_process';
34
35
  import { tmpdir } from 'node:os';
35
- import { join } from 'node:path';
36
+ import { join, resolve } from 'node:path';
36
37
  // The predicate lives in its own side-effect-free module so it can be tested:
37
38
  // importing THIS file used to spawn the suite at load time. The runner is now
38
39
  // behind `import.meta.main` / `runDeterministicTests()`, but the filter stays
@@ -56,6 +57,8 @@ const HARNESS_TEST_ENV_KEYS = [
56
57
  // Discovery: rg -n "process\.env\.ELANOUS_(HOST|RUN|ORIGIN|SUPERVISOR|PARENT)" src
57
58
  // These are ambient execution identities, not inputs to a deterministic test.
58
59
  // debug.log also attributes SUBSTRATE and ARM_ID to every plain-object record.
60
+ const GIT_LOCATION_ENV_KEYS = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR'] as const;
61
+
59
62
  const EXECUTION_ORIGIN_ENV_KEYS = [
60
63
  'ELANOUS_HOST_ID',
61
64
  'ELANOUS_RUN_ID',
@@ -120,6 +123,7 @@ export type DeterministicRunnerDeps = {
120
123
  childGraceMs?: number;
121
124
  killProcessGroup?: KillProcessGroup;
122
125
  getProcessGroupId?: GetProcessGroupId;
126
+ gitConfig?: (cwd: string, args: string[]) => string;
123
127
  };
124
128
 
125
129
  export function isLivePid(pid: number): boolean {
@@ -269,11 +273,22 @@ export async function terminateDirectChild(
269
273
  reportVisible(`failed to terminate child pid=${pid}`, report);
270
274
  }
271
275
 
276
+ function gitConfig(cwd: string, args: string[]): string {
277
+ const env = { ...process.env };
278
+ for (const key of GIT_LOCATION_ENV_KEYS) delete env[key];
279
+ const result = spawnSync('git', args, { cwd, env, encoding: 'utf8' });
280
+ if (result.error || (result.status !== 0 && !(result.status === 1 && args.includes('--get-all') && !result.stderr))) {
281
+ throw new Error(`git ${args.join(' ')} failed: ${result.error?.message ?? result.stderr}`);
282
+ }
283
+ return result.stdout.trim();
284
+ }
285
+
272
286
  export function prepareIsolatedTestEnv(sourceEnv: NodeJS.ProcessEnv, testRoot: string): NodeJS.ProcessEnv {
273
287
  const env = Object.fromEntries(
274
288
  Object.entries(sourceEnv).filter(([key]) => !isCredentialKey(key)
275
289
  && !HARNESS_TEST_ENV_KEYS.includes(key as typeof HARNESS_TEST_ENV_KEYS[number])
276
- && !EXECUTION_ORIGIN_ENV_KEYS.includes(key as typeof EXECUTION_ORIGIN_ENV_KEYS[number])),
290
+ && !EXECUTION_ORIGIN_ENV_KEYS.includes(key as typeof EXECUTION_ORIGIN_ENV_KEYS[number])
291
+ && !GIT_LOCATION_ENV_KEYS.includes(key as typeof GIT_LOCATION_ENV_KEYS[number])),
277
292
  );
278
293
  env.HOME = testRoot;
279
294
  // userConfigPath() warns whenever XDG_CONFIG_HOME is set, including when
@@ -281,15 +296,11 @@ export function prepareIsolatedTestEnv(sourceEnv: NodeJS.ProcessEnv, testRoot: s
281
296
  // resolves the old path through ELANOUS_TEST_HOME rather than the real home.
282
297
  delete env.XDG_CONFIG_HOME;
283
298
  env.ELANOUS_TEST_HOME = testRoot;
284
- // ⚠️ Redirect elanous's OWN roots explicitly — do not merely inherit them.
285
- // `ELANOUS_STATE_DIR` / `ELANOUS_CONFIG_DIR` are absolute paths that win over
286
- // HOME, so a developer who exports either one keeps pointing the "isolated"
287
- // run straight back at their real state while every other signal says the run
288
- // is contained. Deleting them is not enough either: absence resolves to the
289
- // default under HOME, which is fine here, but pinning them makes the
290
- // isolation legible in the child's own environment rather than implied.
299
+ // HOME and ELANOUS_STATE_DIR isolate both the state and default config roots.
300
+ // ELANOUS_CONFIG_DIR is not a config-dir override; discard an inherited value
301
+ // rather than passing an ignored variable that appears to provide isolation.
291
302
  env.ELANOUS_STATE_DIR = join(testRoot, 'state');
292
- env.ELANOUS_CONFIG_DIR = join(testRoot, 'config');
303
+ delete env.ELANOUS_CONFIG_DIR;
293
304
  // All ambient execution identities are removed by EXECUTION_ORIGIN_ENV_KEYS
294
305
  // above, before the child is spawned; logger API-shape tests need their absence.
295
306
  // ⚠️ No `ELANOUS_TEST_DETERMINISTIC` marker is exported. Nothing reads it yet,
@@ -392,6 +403,38 @@ export async function runDeterministicTests(deps: DeterministicRunnerDeps = {}):
392
403
  const report = deps.report ?? console.error;
393
404
  const spawn = deps.spawn ?? defaultSpawn;
394
405
  const argv = deps.argv ?? process.argv.slice(2);
406
+ const cwd = deps.cwd ?? process.cwd();
407
+ const config = deps.gitConfig ?? gitConfig;
408
+ const commonDir = config(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
409
+ const configFile = join(resolve(cwd, commonDir), 'config');
410
+ const bareArgs = ['config', '--file', configFile];
411
+ const readBare = () => config(cwd, [...bareArgs, '--get-all', 'core.bare']);
412
+ const bareBefore = readBare();
413
+ let bareChanged = false;
414
+ let bareChecked = false;
415
+ const checkBare = (): void => {
416
+ bareChecked = true;
417
+ let bareAfter = '<unreadable>';
418
+ const visible = (values: string) => values ? values.replaceAll('\n', ',') : '<unset>';
419
+ try {
420
+ bareAfter = readBare();
421
+ if (bareAfter === bareBefore) return;
422
+ bareChanged = true;
423
+ const originalValues = bareBefore ? bareBefore.split('\n') : [];
424
+ if (originalValues.length > 0) {
425
+ config(cwd, [...bareArgs, '--replace-all', 'core.bare', originalValues[0]!]);
426
+ for (const value of originalValues.slice(1)) config(cwd, [...bareArgs, '--add', 'core.bare', value]);
427
+ } else if (bareAfter) {
428
+ config(cwd, [...bareArgs, '--unset-all', 'core.bare']);
429
+ }
430
+ const restored = readBare();
431
+ if (restored !== bareBefore) throw new Error(`expected ${visible(bareBefore)}, got ${visible(restored)}`);
432
+ report(`[test-deterministic] core.bare changed ${visible(bareBefore)} -> ${visible(bareAfter)}; restored ${visible(bareBefore)}; failing run`);
433
+ } catch (error) {
434
+ bareChanged = true;
435
+ report(`[test-deterministic] core.bare changed ${visible(bareBefore)} -> ${visible(bareAfter)}; restore failed: ${formatError(error)}`);
436
+ }
437
+ };
395
438
  // ⛔ 사람이 «경로»를 직접 준 창에서는 아무것도 빼지 않는다 — 그 창은 「이것만 돌려라」다.
396
439
  const explicitPaths = argv.some((arg) => !arg.startsWith('-'));
397
440
  const cdpPatterns = explicitPaths ? [] : (deps.deriveCdpPatterns ?? deriveCdpTestPatterns)();
@@ -451,6 +494,7 @@ export async function runDeterministicTests(deps: DeterministicRunnerDeps = {}):
451
494
  });
452
495
  cleanupTemporaryRoot(testRoot, { rmSync: deps.rmSync, report });
453
496
  rootCleanupAttempted = true;
497
+ checkBare();
454
498
  gate?.dispose();
455
499
  gate = undefined;
456
500
  if (deps.killSelf) {
@@ -465,7 +509,8 @@ export async function runDeterministicTests(deps: DeterministicRunnerDeps = {}):
465
509
  }
466
510
 
467
511
  cleanupRoot();
468
- return outcome.code ?? 1;
512
+ checkBare();
513
+ return bareChanged ? 1 : (outcome.code ?? 1);
469
514
  } catch (error) {
470
515
  if (child) {
471
516
  try {
@@ -484,6 +529,7 @@ export async function runDeterministicTests(deps: DeterministicRunnerDeps = {}):
484
529
  } finally {
485
530
  gate?.dispose();
486
531
  cleanupRoot();
532
+ if (!bareChecked) checkBare();
487
533
  }
488
534
  }
489
535