elanous 0.2.14 → 0.2.16

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 (350) 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/{1307-6e7e0377c1db0bdc.js → 1307-b46d45b705e8c9ae.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-7b2b4f378354869a.js → page-3cb52e5ea8fc2fa5.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/botlab/page-c1e20d7d338713f1.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/bots/{page-1512d91c7491ed62.js → page-482c5dbde699947c.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/chat/{page-a627db94ec6904a0.js → page-8c2d9526164f5d09.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/control/{page-e6b84ab07d3567d5.js → page-a0e2a6d1bfb52fc5.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c53598cfce6156f2.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/editor/{page-fed69f9759d248d0.js → page-13a63953f53c6a23.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/exec/{page-15374909d8f1aebd.js → page-5d3c8429d04f3831.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/field/{page-012a742f81218b01.js → page-3193bdc0bbab047a.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/intake/{page-99effd9d893a43dc.js → page-6107ca8ddd5e3bdd.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/{layout-938de57ae0315e46.js → layout-dce686e582c23937.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/loops/{page-160089123a40dd91.js → page-1a61c7ca1ceb3ae7.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/app/market/{page-629e1ede482ec6eb.js → page-51f811fa119308b6.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-27e99b4e54a849d4.js → page-4167348049166852.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/not-found-de52e8fdf9095f7a.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-32cde713be47cddb.js → page-ca3952cd78a83ca2.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-e7e10cd3386222e5.js → page-5b042736aa113756.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-e64caff084f7ce21.js → page-1ebfe049c640e171.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-db07eaffba0df7e3.js → page-57924393e3e4e539.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/settings/{page-86f91034bd5cabe5.js → page-0f5cd57ca928f42b.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-abe8e3b74bea1ffc.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/share/{page-ce4d8a1215ce53a3.js → page-d28573d6792b4275.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-58043db78ba519ae.js → page-59f03fda76c03d22.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-357750dfb91bf960.js → page-d751de5f8062ff5e.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/term/{page-274703bc41b71333.js → page-fe1759f94003b32b.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/today/page-d64c984e855c8c68.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/{page-3b7e821dc492bdfd.js → page-9afdee66b820c76e.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-f8da5dc6f3752276.js → page-1ea64acadd704ace.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-2f810b18d89ecba6.js → page-3544f154c87b43ee.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +1 -0
  53. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
  54. package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
  55. package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +1 -0
  56. package/apps/pwa/out/approvals/index.html +1 -1
  57. package/apps/pwa/out/approvals/index.txt +15 -15
  58. package/apps/pwa/out/autopilot/index.html +1 -1
  59. package/apps/pwa/out/autopilot/index.txt +15 -15
  60. package/apps/pwa/out/board/index.html +1 -1
  61. package/apps/pwa/out/board/index.txt +15 -15
  62. package/apps/pwa/out/botlab/index.html +1 -1
  63. package/apps/pwa/out/botlab/index.txt +15 -15
  64. package/apps/pwa/out/bots/index.html +1 -1
  65. package/apps/pwa/out/bots/index.txt +15 -15
  66. package/apps/pwa/out/ceo/index.html +1 -0
  67. package/apps/pwa/out/ceo/index.txt +33 -0
  68. package/apps/pwa/out/chat/index.html +1 -1
  69. package/apps/pwa/out/chat/index.txt +15 -15
  70. package/apps/pwa/out/consult/index.html +1 -1
  71. package/apps/pwa/out/consult/index.txt +15 -15
  72. package/apps/pwa/out/control/index.html +1 -1
  73. package/apps/pwa/out/control/index.txt +15 -15
  74. package/apps/pwa/out/design-check/index.html +1 -1
  75. package/apps/pwa/out/design-check/index.txt +15 -15
  76. package/apps/pwa/out/editor/index.html +1 -1
  77. package/apps/pwa/out/editor/index.txt +15 -15
  78. package/apps/pwa/out/exec/index.html +1 -1
  79. package/apps/pwa/out/exec/index.txt +15 -15
  80. package/apps/pwa/out/field/index.html +1 -1
  81. package/apps/pwa/out/field/index.txt +15 -15
  82. package/apps/pwa/out/healthz +1 -1
  83. package/apps/pwa/out/index.html +1 -1
  84. package/apps/pwa/out/index.txt +15 -15
  85. package/apps/pwa/out/inside/index.html +1 -1
  86. package/apps/pwa/out/inside/index.txt +15 -15
  87. package/apps/pwa/out/intake/index.html +1 -1
  88. package/apps/pwa/out/intake/index.txt +15 -15
  89. package/apps/pwa/out/live/index.html +1 -1
  90. package/apps/pwa/out/live/index.txt +15 -15
  91. package/apps/pwa/out/loops/index.html +1 -1
  92. package/apps/pwa/out/loops/index.txt +15 -15
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +15 -15
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +15 -15
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +15 -15
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +15 -15
  101. package/apps/pwa/out/ops/checklist/index.html +1 -1
  102. package/apps/pwa/out/ops/checklist/index.txt +15 -15
  103. package/apps/pwa/out/ops/release/index.html +1 -1
  104. package/apps/pwa/out/ops/release/index.txt +15 -15
  105. package/apps/pwa/out/ops/seats/index.html +1 -1
  106. package/apps/pwa/out/ops/seats/index.txt +15 -15
  107. package/apps/pwa/out/outputs/index.html +1 -1
  108. package/apps/pwa/out/outputs/index.txt +15 -15
  109. package/apps/pwa/out/reflection/index.html +1 -1
  110. package/apps/pwa/out/reflection/index.txt +16 -16
  111. package/apps/pwa/out/scheduler/index.html +1 -1
  112. package/apps/pwa/out/scheduler/index.txt +15 -15
  113. package/apps/pwa/out/sessions/index.html +1 -1
  114. package/apps/pwa/out/sessions/index.txt +15 -15
  115. package/apps/pwa/out/settings/devices/index.html +1 -1
  116. package/apps/pwa/out/settings/devices/index.txt +15 -15
  117. package/apps/pwa/out/settings/index.html +1 -1
  118. package/apps/pwa/out/settings/index.txt +15 -15
  119. package/apps/pwa/out/setup/done/index.html +1 -1
  120. package/apps/pwa/out/setup/done/index.txt +15 -15
  121. package/apps/pwa/out/setup/index.html +1 -1
  122. package/apps/pwa/out/setup/index.txt +15 -15
  123. package/apps/pwa/out/share/index.html +1 -1
  124. package/apps/pwa/out/share/index.txt +15 -15
  125. package/apps/pwa/out/showroom/index.html +1 -1
  126. package/apps/pwa/out/showroom/index.txt +15 -15
  127. package/apps/pwa/out/tasks/index.html +1 -1
  128. package/apps/pwa/out/tasks/index.txt +15 -15
  129. package/apps/pwa/out/term/index.html +1 -1
  130. package/apps/pwa/out/term/index.txt +15 -15
  131. package/apps/pwa/out/today/index.html +1 -1
  132. package/apps/pwa/out/today/index.txt +15 -15
  133. package/apps/pwa/out/trace/index.html +1 -1
  134. package/apps/pwa/out/trace/index.txt +15 -15
  135. package/apps/pwa/out/vault/index.html +1 -1
  136. package/apps/pwa/out/vault/index.txt +15 -15
  137. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  138. package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
  139. package/apps/pwa/out/workflows/index.html +1 -1
  140. package/apps/pwa/out/workflows/index.txt +15 -15
  141. package/apps/pwa/out/workspace/index.html +1 -1
  142. package/apps/pwa/out/workspace/index.txt +15 -15
  143. package/apps/pwa/out/worktrees/index.html +1 -1
  144. package/apps/pwa/out/worktrees/index.txt +15 -15
  145. package/graphs/doc-gen/doc-gen.yaml +24 -0
  146. package/graphs/doc-gen/recipes.yaml +3 -0
  147. package/graphs/heal/heal-loop.yaml +1 -1
  148. package/graphs/implement-loop.yaml +1 -1
  149. package/graphs/orchestrator/orchestrator.yaml +7 -0
  150. package/graphs/orchestrator/recipes.yaml +3 -0
  151. package/graphs/release/recipes.yaml +3 -0
  152. package/graphs/release/release-loop.yaml +4 -2
  153. package/graphs/steward/recipes.yaml +2 -1
  154. package/package.json +1 -1
  155. package/scripts/brand/check.ts +7 -5
  156. package/scripts/ci-public-export-import-gate.ts +154 -0
  157. package/scripts/ci-test-interference-gate.ts +5 -1
  158. package/scripts/ci-typecheck-changed.ts +82 -31
  159. package/scripts/docs/doc-rot.ts +164 -0
  160. package/scripts/docs/generate-graph-anatomy.ts +437 -0
  161. package/scripts/docs-cli-check.ts +4 -4
  162. package/scripts/expose-rubric-suggest.ts +18 -12
  163. package/scripts/goals/goal-census.ts +343 -0
  164. package/scripts/lecture-note/node.ts +31 -11
  165. package/scripts/lessons/handbook-page.ts +30 -3
  166. package/scripts/marketing/generate-ax-brief.ts +140 -0
  167. package/scripts/marketing/generate-feature-map.ts +118 -0
  168. package/scripts/measure/author-section-contribution.ts +84 -0
  169. package/scripts/release-loop/checklist-gate-node.ts +53 -14
  170. package/scripts/release-loop/cut-branch.ts +43 -13
  171. package/scripts/release-loop/gate-node.ts +82 -14
  172. package/scripts/release-loop/vault-note-node.ts +257 -8
  173. package/scripts/release-story/draft.ts +57 -9
  174. package/scripts/rhythm/daily.ts +263 -76
  175. package/scripts/rhythm/monthly.ts +97 -0
  176. package/scripts/rhythm/weekly.ts +30 -9
  177. package/scripts/smart-merge/resync-shadow.ts +147 -0
  178. package/src/acp/client.ts +17 -2
  179. package/src/agent-env/profile.ts +161 -0
  180. package/src/agent-mission/driver.ts +51 -1
  181. package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
  182. package/src/agent-mission/supervisor-metrics.ts +218 -0
  183. package/src/agent-substrate/pr-reviewer.ts +2 -0
  184. package/src/ask-user-question/pending-questions.ts +5 -0
  185. package/src/auth/github-app-token.ts +112 -13
  186. package/src/autopilot/build/llm-conflict-merge.ts +32 -6
  187. package/src/autopilot/track-agent-shadow.ts +2 -1
  188. package/src/briefing/brief-items.ts +276 -0
  189. package/src/budget/usage-outcomes.ts +204 -0
  190. package/src/claims/claims-render.ts +14 -5
  191. package/src/cli/agent-env-cli.ts +21 -0
  192. package/src/cli/brief-cli.ts +73 -0
  193. package/src/cli/decisions-cli.ts +9 -0
  194. package/src/cli/doctor-cli.ts +23 -1
  195. package/src/cli/doctor-node-modules.ts +147 -0
  196. package/src/cli/intake-cli.ts +46 -17
  197. package/src/cli/know-cli.ts +20 -0
  198. package/src/cli/logs-cli.ts +43 -1
  199. package/src/cli/pod-cli.ts +2 -0
  200. package/src/cli/pr-cli.ts +80 -12
  201. package/src/cli/release-cli.ts +66 -29
  202. package/src/cli/seat-requests-cli.ts +52 -0
  203. package/src/cli/self-update.ts +200 -1
  204. package/src/cli/usage-cli.ts +26 -0
  205. package/src/context-bus/context-now-public.ts +16 -6
  206. package/src/context-bus/context-now.ts +3 -1
  207. package/src/context-bus/coord-events.ts +6 -4
  208. package/src/decisions/decision-cards.ts +33 -7
  209. package/src/decisions/proact-meter.ts +286 -0
  210. package/src/decisions/proact1-lite.ts +86 -0
  211. package/src/decisions/web-push-decision-cards.ts +1 -1
  212. package/src/discord-self-message.ts +9 -0
  213. package/src/domains/alert-audit.ts +271 -0
  214. package/src/domains/core-tools.ts +4 -3
  215. package/src/domains/ops-facts-tool.ts +31 -1
  216. package/src/flow-loop/split.ts +24 -0
  217. package/src/harness/harness-cli-command.ts +147 -17
  218. package/src/harness/harness-incidents.ts +2 -0
  219. package/src/harness/harness-pod-dispatch.ts +67 -8
  220. package/src/harness/harness-queue.ts +380 -50
  221. package/src/harness/harness-seams.ts +13 -3
  222. package/src/harness/helper-repair.ts +218 -0
  223. package/src/harness/helper-scan.ts +27 -4
  224. package/src/harness/launch-stamp.ts +236 -0
  225. package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
  226. package/src/harness/ledger-write-guard.ts +35 -0
  227. package/src/harness/nested-elanous-policy.ts +114 -0
  228. package/src/harness/pod-memory-policy.ts +15 -0
  229. package/src/harness/repository-name.ts +55 -0
  230. package/src/hq/fence-audit.ts +90 -0
  231. package/src/hq/hq.ts +70 -6
  232. package/src/hq/lease.ts +9 -7
  233. package/src/hq/move-plan.ts +126 -0
  234. package/src/hq/promote.ts +31 -0
  235. package/src/hq/transfer.ts +185 -0
  236. package/src/index.ts +189 -13
  237. package/src/intake-plane/check.ts +63 -0
  238. package/src/intake-plane/digest.ts +65 -21
  239. package/src/intake-plane/front-classifier.ts +6 -0
  240. package/src/intake-plane/items.ts +3 -2
  241. package/src/intake-plane/lens.ts +177 -0
  242. package/src/intake-plane/route.ts +145 -1
  243. package/src/knowledge/know-find.ts +105 -0
  244. package/src/llm.ts +75 -19
  245. package/src/loops/budget.ts +36 -5
  246. package/src/loops/checker.ts +2 -2
  247. package/src/loops/orchestrator/author-depth.ts +65 -9
  248. package/src/loops/orchestrator/author-input-check.ts +8 -3
  249. package/src/loops/orchestrator/author-ledger.ts +3 -1
  250. package/src/loops/orchestrator/finish-rate.ts +14 -3
  251. package/src/loops/orchestrator/seat-share.ts +56 -0
  252. package/src/loops/orchestrator/tick.ts +107 -29
  253. package/src/loops/orchestrator/traffic.ts +117 -11
  254. package/src/loops/registry.ts +142 -61
  255. package/src/maturity/bot-slash-catalog.ts +11 -2
  256. package/src/maturity/feature-maturity.ts +4 -3
  257. package/src/maturity/tui-slash-availability.ts +40 -0
  258. package/src/mss/logging/log-store.ts +35 -4
  259. package/src/nexus/api/http-server.ts +28 -14
  260. package/src/nexus/api/loop-edges.ts +173 -0
  261. package/src/nexus/api/schedules-read.ts +13 -0
  262. package/src/nexus/api/seat-requests.ts +79 -56
  263. package/src/oauth/overload-failover-launch.ts +75 -0
  264. package/src/oauth/overload-failover.ts +137 -0
  265. package/src/prompt-enhance/enhance.ts +6 -1
  266. package/src/release-loop/auto-start.ts +6 -3
  267. package/src/release-loop/checklist.ts +19 -5
  268. package/src/release-loop/feature-store.ts +59 -31
  269. package/src/release-loop/merged-pr-checklist.ts +125 -0
  270. package/src/release-loop/placement.ts +98 -29
  271. package/src/seat-dispatch/seat-ask.ts +33 -6
  272. package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
  273. package/src/seat-dispatch/seat-request-ledger.ts +133 -0
  274. package/src/seat-loop/seat-loop.ts +215 -12
  275. package/src/self-dev/ask-launch-flow.ts +22 -1
  276. package/src/self-dev/authored-goal-trace.ts +64 -0
  277. package/src/self-dev/dev-cli.ts +19 -1
  278. package/src/self-dev/dev-pipeline.ts +14 -9
  279. package/src/self-dev/draft-sweep.ts +90 -10
  280. package/src/self-dev/draft-triage-rules.ts +25 -8
  281. package/src/self-dev/entrance-registry.ts +10 -6
  282. package/src/self-dev/old-door.ts +73 -0
  283. package/src/self-dev/orchestrate.ts +15 -1
  284. package/src/self-dev/review-provider-fallback.ts +6 -1
  285. package/src/self-dev/run-supervisor.ts +49 -2
  286. package/src/self-implement/app-api-budget.ts +166 -0
  287. package/src/self-implement/budget-gate.ts +7 -5
  288. package/src/self-implement/follow-up-goals.ts +289 -0
  289. package/src/self-implement/frozen-merges.ts +3 -2
  290. package/src/self-implement/goal-author.ts +50 -6
  291. package/src/self-implement/goal-clarification-escalation.ts +5 -0
  292. package/src/self-implement/heal-intake.ts +120 -2
  293. package/src/self-implement/heal-triage.ts +44 -0
  294. package/src/self-implement/host-regate.ts +9 -2
  295. package/src/self-implement/l8-merge-queue.ts +132 -0
  296. package/src/self-implement/orchestrator.ts +325 -47
  297. package/src/self-implement/pipeline-shape.ts +1 -1
  298. package/src/self-implement/revert-guard.ts +42 -7
  299. package/src/self-implement/run-ledger.ts +6 -0
  300. package/src/self-implement/run-status-mapping.ts +2 -0
  301. package/src/self-implement/seams.ts +45 -16
  302. package/src/self-implement/self-implement-cli.ts +1 -0
  303. package/src/self-implement/speed-claims.ts +338 -0
  304. package/src/session-runtime/index.ts +2 -0
  305. package/src/session-runtime/retry-api.ts +87 -0
  306. package/src/session-runtime/retry-policy.ts +107 -6
  307. package/src/steward/triage.ts +74 -3
  308. package/src/steward/working-backwards.ts +20 -2
  309. package/src/task-cards/card-cli.ts +25 -0
  310. package/src/task-cards/card-store.ts +5 -3
  311. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
  312. package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
  313. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
  314. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
  315. package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
  316. package/src/task-orchestrator/surfaces/pod-pool.ts +70 -9
  317. package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
  318. package/src/task-orchestrator/surfaces/self-implement-pod.ts +166 -46
  319. package/src/task-orchestrator/surfaces/self-implement.ts +7 -1
  320. package/src/telegram-commands.ts +5 -0
  321. package/src/telegram.ts +72 -4
  322. package/src/user-config.ts +64 -7
  323. package/src/version/packed-revision.json +1 -1
  324. package/src/workflow-runtime/nodes/bash.ts +9 -1
  325. package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2ce5471926761fdd.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +0 -1
  348. package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +0 -1
  349. package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +0 -1
  350. /package/apps/pwa/out/_next/static/{x6mjI6vqOkVVu0jFuDkz1 → rv9cierV0jSOvDBm1g0XB}/_ssgManifest.js +0 -0
@@ -0,0 +1,437 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * DOC-GEN① — 그래프 아나토미를 코드에서 생성한다.
4
+ *
5
+ * 원천은 둘뿐이다.
6
+ * 노드 종류 = src/graph-kinds/registry.ts (HARNESS_CORE_KINDS · WORKFLOW_CORE_KINDS · listNodeKinds)
7
+ * 그래프 = graphs/ YAML 을 src/self-implement/graph-yaml.ts 의 loadGraphTemplates · edgeMapOf 가 읽는다
8
+ *
9
+ * renderGraphAnatomy · diffAnatomy 는 순수하다 — 파일을 쓰지 않고 process.cwd() 를 읽지 않는다.
10
+ * 뿌리·커밋·시각은 인자로만 받는다. 쓰기는 main 만 한다.
11
+ * main: `--out <path>` (기본 내부 문서 `graph-anatomy`) 와 같은 이름 `.json` 옆 파일.
12
+ * git 트리가 아니면 죽지 않고 `commit: (설치본 v<package.json version>)`.
13
+ */
14
+
15
+ import { execFileSync } from 'node:child_process';
16
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
17
+ import { dirname, join, relative } from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+
20
+ import { listNodeKinds, type GraphKind, type NodeKindEntry } from '../../src/graph-kinds/registry.js';
21
+ import {
22
+ edgeMapOf,
23
+ loadGraphTemplates,
24
+ parseGraphTemplateYaml,
25
+ type GraphLoadResult,
26
+ type GraphParseIssue,
27
+ type GraphTemplateSpec,
28
+ } from '../../src/self-implement/graph-yaml.js';
29
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
30
+
31
+ /** drafts/ · overlays/ 는 스캔에서 뺀다(실측 10-05). 그 이름인 디렉터리와 그 아래는 안 읽는다. */
32
+ const EXCLUDED_DIR_NAMES = new Set(['drafts', 'overlays']);
33
+
34
+ export interface RenderGraphAnatomyInput {
35
+ readonly graphsRoot: string;
36
+ readonly commit: string;
37
+ readonly generatedAt: string;
38
+ }
39
+
40
+ /** `<out>.json` 옆 파일. graphs 값은 graph_id → nodeId 목록. */
41
+ export interface AnatomySnapshot {
42
+ readonly generatedAt: string;
43
+ readonly commit: string;
44
+ readonly graphs: Readonly<Record<string, readonly string[]>>;
45
+ }
46
+
47
+ export interface AnatomyDiff {
48
+ readonly addedGraphs: readonly string[];
49
+ readonly removedGraphs: readonly string[];
50
+ /** `graph_id/nodeId` */
51
+ readonly addedNodes: readonly string[];
52
+ /** `graph_id/nodeId` */
53
+ readonly removedNodes: readonly string[];
54
+ }
55
+
56
+ export interface ScannedGraphDir {
57
+ /** graphsRoot 기준 상대 경로. 뿌리 자신은 '.' */
58
+ readonly dir: string;
59
+ readonly result: GraphLoadResult;
60
+ }
61
+
62
+ function cell(value: string): string {
63
+ return value.replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
64
+ }
65
+
66
+ function edgeCountOf(template: GraphTemplateSpec): number {
67
+ return Object.values(edgeMapOf(template)).reduce((sum, targets) => sum + targets.length, 0);
68
+ }
69
+
70
+ function edgeLinesOf(template: GraphTemplateSpec): string[] {
71
+ const lines: string[] = [];
72
+ for (const edge of template.edges) {
73
+ if (edge.to !== undefined) lines.push(`${edge.from} → ${edge.to}`);
74
+ for (const [key, target] of Object.entries(edge.map ?? {})) lines.push(`${edge.from} → ${target} (${key})`);
75
+ for (const fallback of edge.fallback ?? []) lines.push(`${edge.from} → ${fallback.node} (fallback)`);
76
+ }
77
+ return lines;
78
+ }
79
+
80
+ /**
81
+ * A directory with recipes.yaml holds workflow graphs run by `elanous graph run` (src/graph-runner/runner.ts), not
82
+ * harness templates. The runner gives a recipe-less node (done · failed) `recipe: 'none'` before the shared parser
83
+ * (runner.ts «command-less node … represented internally as `none`»); read those graphs the same way, and do not
84
+ * read recipes.yaml itself as a graph.
85
+ */
86
+ function isRunnerDir(abs: string): boolean { return existsSync(join(abs, 'recipes.yaml')); }
87
+
88
+ function parseLikeRunner(source: string, label: string): ReturnType<typeof parseGraphTemplateYaml> {
89
+ let raw: unknown;
90
+ try { raw = parseYaml(source); } catch { return parseGraphTemplateYaml(source, label); }
91
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return parseGraphTemplateYaml(source, label);
92
+ const document = raw as Record<string, unknown>;
93
+ if (Array.isArray(document.nodes)) {
94
+ document.nodes = document.nodes.map((node: unknown) => node && typeof node === 'object' && !Array.isArray(node) && !('recipe' in node)
95
+ ? { ...node, recipe: 'none' } : node);
96
+ }
97
+ return parseGraphTemplateYaml(stringifyYaml(document), label);
98
+ }
99
+
100
+ function graphFileNames(abs: string): string[] {
101
+ const runner = isRunnerDir(abs);
102
+ return readdirSync(abs).filter((name) => (name.endsWith('.yaml') || name.endsWith('.yml')) && !(runner && name === 'recipes.yaml')).sort();
103
+ }
104
+
105
+ /** loadGraphTemplates for harness dirs; the runner's reading for workflow-graph dirs (same result shape). */
106
+ function loadDir(abs: string): GraphLoadResult {
107
+ if (!isRunnerDir(abs)) return loadGraphTemplates(abs);
108
+ const errors: GraphParseIssue[] = [];
109
+ const warnings: GraphParseIssue[] = [];
110
+ const templates: Record<string, GraphTemplateSpec> = {};
111
+ let files: string[];
112
+ try { files = graphFileNames(abs); }
113
+ catch (error) { return { templates, errors: [{ path: abs, message: `그래프 디렉토리를 못 읽었다: ${String(error)}` }], warnings, scannedFiles: 0 }; }
114
+ for (const file of files) {
115
+ let source: string;
116
+ try { source = readFileSync(join(abs, file), 'utf8'); }
117
+ catch (error) { errors.push({ path: file, message: `읽기 실패: ${String(error)}` }); continue; }
118
+ const result = parseLikeRunner(source, file);
119
+ errors.push(...result.errors);
120
+ warnings.push(...result.warnings);
121
+ if (!result.template) continue;
122
+ if (templates[result.template.graphId]) { errors.push({ path: file, message: `graph_id '${result.template.graphId}' 가 중복이다` }); continue; }
123
+ templates[result.template.graphId] = result.template;
124
+ }
125
+ return { templates, errors, warnings, scannedFiles: files.length };
126
+ }
127
+
128
+ /** 뿌리와 하위 디렉터리를 돈다. drafts/ · overlays/ 와 그 아래는 제외. 디렉터리마다 loadGraphTemplates 를 그대로 부른다. */
129
+ export function scanGraphDirs(graphsRoot: string): ScannedGraphDir[] {
130
+ const scanned: ScannedGraphDir[] = [];
131
+ const visit = (dir: string): void => {
132
+ scanned.push({ dir: relative(graphsRoot, dir) || '.', result: loadDir(dir) });
133
+ let names: string[];
134
+ try {
135
+ names = readdirSync(dir, { withFileTypes: true })
136
+ .filter((entry) => entry.isDirectory() && !EXCLUDED_DIR_NAMES.has(entry.name))
137
+ .map((entry) => entry.name)
138
+ .sort();
139
+ } catch {
140
+ return;
141
+ }
142
+ for (const name of names) visit(join(dir, name));
143
+ };
144
+ visit(graphsRoot);
145
+ return scanned;
146
+ }
147
+
148
+ /**
149
+ * loadGraphTemplates 는 파일명을 템플릿에 안 남긴다.
150
+ * 같은 디렉터리 yaml 을 parseGraphTemplateYaml 로 한 번씩 읽어 graph_id → 상대 경로를 붙인다.
151
+ * 파싱에 실패한 파일은 표에 안 들어가므로 맵에도 안 넣는다.
152
+ */
153
+ /** 그 디렉터리에서 loadGraphTemplates 가 센 yaml 이름. 못 읽으면 빈 목록. */
154
+ function yamlNames(graphsRoot: string, dir: string): readonly string[] {
155
+ const abs = dir === '.' ? graphsRoot : join(graphsRoot, dir);
156
+ try {
157
+ return graphFileNames(abs);
158
+ } catch {
159
+ return [];
160
+ }
161
+ }
162
+
163
+ function graphIdPaths(graphsRoot: string, dir: string): ReadonlyMap<string, string> {
164
+ const abs = dir === '.' ? graphsRoot : join(graphsRoot, dir);
165
+ const prefix = dir === '.' ? '' : `${dir}/`;
166
+ const paths = new Map<string, string>();
167
+ let names: string[];
168
+ try {
169
+ names = graphFileNames(abs);
170
+ } catch {
171
+ return paths;
172
+ }
173
+ for (const name of names) {
174
+ let source: string;
175
+ try { source = readFileSync(join(abs, name), 'utf8'); }
176
+ catch { continue; }
177
+ const parsed = isRunnerDir(abs) ? parseLikeRunner(source, name) : parseGraphTemplateYaml(source, name);
178
+ if (!parsed.template) continue;
179
+ const rel = `${prefix}${name}`;
180
+ const prev = paths.get(parsed.template.graphId);
181
+ paths.set(parsed.template.graphId, prev === undefined ? rel : `${prev}, ${rel}`);
182
+ }
183
+ return paths;
184
+ }
185
+
186
+ function sortedIds(ids: Iterable<string>): string[] {
187
+ return [...ids].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
188
+ }
189
+
190
+ /** 순수. 어제 판과 오늘 판의 그래프·노드 차이. 파일을 읽거나 쓰지 않는다. */
191
+ export function diffAnatomy(prev: AnatomySnapshot, next: AnatomySnapshot): AnatomyDiff {
192
+ const prevIds = new Set(Object.keys(prev.graphs));
193
+ const nextIds = new Set(Object.keys(next.graphs));
194
+ const addedGraphs = sortedIds([...nextIds].filter((id) => !prevIds.has(id)));
195
+ const removedGraphs = sortedIds([...prevIds].filter((id) => !nextIds.has(id)));
196
+ const addedNodes: string[] = [];
197
+ const removedNodes: string[] = [];
198
+ // A graph that appears or disappears contributes all of its nodes, so the node list never hides them (ACP must-fix).
199
+ for (const id of sortedIds([...new Set([...prevIds, ...nextIds])])) {
200
+ const before = new Set(prev.graphs[id] ?? []);
201
+ const after = new Set(next.graphs[id] ?? []);
202
+ for (const nodeId of sortedIds([...after].filter((node) => !before.has(node)))) addedNodes.push(`${id}/${nodeId}`);
203
+ for (const nodeId of sortedIds([...before].filter((node) => !after.has(node)))) removedNodes.push(`${id}/${nodeId}`);
204
+ }
205
+ return { addedGraphs, removedGraphs, addedNodes, removedNodes };
206
+ }
207
+
208
+ /** 머리(`# Graph anatomy` 와 생성물 경고) 바로 아래. 파일을 쓰지 않는다. */
209
+ export function renderYesterdaySection(prev: AnatomySnapshot | null, next: AnatomySnapshot): string {
210
+ if (prev === null) return ['## 어제 대비', '', '이전 판 없음(첫 생성)', ''].join('\n');
211
+ const diff = diffAnatomy(prev, next);
212
+ const lines = ['## 어제 대비', ''];
213
+ const changed = diff.addedGraphs.length + diff.removedGraphs.length + diff.addedNodes.length + diff.removedNodes.length;
214
+ if (changed === 0) {
215
+ lines.push('변화 없음', '');
216
+ return lines.join('\n');
217
+ }
218
+ for (const id of diff.addedGraphs) lines.push(`+ ${id}`);
219
+ for (const id of diff.removedGraphs) lines.push(`- ${id}`);
220
+ for (const node of diff.addedNodes) lines.push(`+ ${node}`);
221
+ for (const node of diff.removedNodes) lines.push(`- ${node}`);
222
+ lines.push('');
223
+ return lines.join('\n');
224
+ }
225
+
226
+ function snapshotOf(scanned: readonly ScannedGraphDir[], commit: string, generatedAt: string): AnatomySnapshot {
227
+ const graphs: Record<string, string[]> = {};
228
+ for (const { result } of scanned) {
229
+ for (const template of Object.values(result.templates)) {
230
+ const ids = template.nodes.map((node) => node.nodeId);
231
+ const prev = graphs[template.graphId];
232
+ graphs[template.graphId] = prev === undefined ? ids : [...prev, ...ids.filter((id) => !prev.includes(id))];
233
+ }
234
+ }
235
+ return { generatedAt, commit, graphs };
236
+ }
237
+
238
+ function kindRows(entries: readonly NodeKindEntry[]): string[] {
239
+ return [...entries]
240
+ .sort((a, b) => (a.graph < b.graph ? -1 : a.graph > b.graph ? 1 : a.kind < b.kind ? -1 : a.kind > b.kind ? 1 : 0))
241
+ .map((entry) => `| ${cell(entry.graph)} | ${cell(entry.kind)} | ${entry.core ? 'core' : 'plugin'} |`);
242
+ }
243
+
244
+ function issueLines(label: string, issues: readonly GraphParseIssue[]): string[] {
245
+ if (issues.length === 0) return [`- ${label}: 0`];
246
+ return [`- ${label}: ${issues.length}`, ...issues.map((issue) => ` - ${cell(issue.path)} — ${issue.message}`)];
247
+ }
248
+
249
+ /**
250
+ * 마크다운 한 장. 파일을 쓰지 않는다. graphsRoot · commit · generatedAt 만 읽는다.
251
+ */
252
+ export function renderGraphAnatomy(input: RenderGraphAnatomyInput, yesterday?: string): string {
253
+ const scanned = scanGraphDirs(input.graphsRoot);
254
+ const templates: { dir: string; fileLabel: string; template: GraphTemplateSpec }[] = [];
255
+ for (const { dir, result } of scanned) {
256
+ for (const template of Object.values(result.templates)) {
257
+ templates.push({ dir, fileLabel: dir === '.' ? `${template.graphId}` : `${dir}/${template.graphId}`, template });
258
+ }
259
+ }
260
+ templates.sort((a, b) => (a.template.graphId < b.template.graphId ? -1 : a.template.graphId > b.template.graphId ? 1 : a.dir < b.dir ? -1 : 1));
261
+
262
+ const scannedFiles = scanned.reduce((sum, entry) => sum + entry.result.scannedFiles, 0);
263
+ const missingDirs = scanned.filter((entry) =>
264
+ entry.result.scannedFiles === 0
265
+ && entry.result.errors.some((issue) => issue.message.startsWith('그래프 디렉토리를 못 읽었다')),
266
+ );
267
+
268
+ const lines: string[] = [
269
+ '# Graph anatomy',
270
+ '',
271
+ '> 이 문서는 생성물이다 — 손으로 고치지 마라',
272
+ '',
273
+ `- generatedAt: ${input.generatedAt}`,
274
+ `- commit: ${input.commit}`,
275
+ `- graphsRoot: (인자)`,
276
+ `- scannedFiles: ${scannedFiles}`,
277
+ `- scannedDirs: ${scanned.length}`,
278
+ `- templates: ${templates.length}`,
279
+ '',
280
+ ...(yesterday === undefined ? [] : [yesterday.trimEnd(), '']),
281
+ '## 표 1 노드 종류',
282
+ '',
283
+ '원천: `src/graph-kinds/registry.ts` — `HARNESS_CORE_KINDS` · `WORKFLOW_CORE_KINDS` · `listNodeKinds()`.',
284
+ '',
285
+ '| graph | kind | core/plugin |',
286
+ '| --- | --- | --- |',
287
+ ...kindRows(listNodeKinds()),
288
+ '',
289
+ '## 표 2 그래프',
290
+ '',
291
+ '원천: `loadGraphTemplates(dir)` ⊕ `edgeMapOf(template)`. 간선 수는 edgeMapOf 합(to · map · fallback, 중복 목적지는 한 번). `drafts/` · `overlays/` 는 제외.',
292
+ '',
293
+ '| 파일 경로 | graph_id | 노드 수 | 간선 수 | entryNode | terminalNodes |',
294
+ '| --- | --- | --- | --- | --- | --- |',
295
+ ];
296
+
297
+ if (templates.length === 0) {
298
+ lines.push('| (없음) | — | 0 | 0 | — | — |');
299
+ }
300
+ const pathCache = new Map<string, ReadonlyMap<string, string>>();
301
+ for (const { dir, template } of templates) {
302
+ let paths = pathCache.get(dir);
303
+ if (paths === undefined) {
304
+ paths = graphIdPaths(input.graphsRoot, dir);
305
+ pathCache.set(dir, paths);
306
+ }
307
+ const where = paths.get(template.graphId) ?? (dir === '.' ? `${template.graphId}.yaml` : `${dir}/`);
308
+ lines.push(`| ${cell(where)} | ${cell(template.graphId)} | ${template.nodes.length} | ${edgeCountOf(template)} | ${cell(template.entryNode)} | ${cell(template.terminalNodes.join(', '))} |`);
309
+ }
310
+
311
+ lines.push('', '## 그래프별 노드 · 간선', '');
312
+ if (templates.length === 0) lines.push('(읽힌 그래프 없음)', '');
313
+ for (const { dir, template } of templates) {
314
+ const where = dir === '.' ? '(graphs root)' : dir;
315
+ lines.push(`### ${template.graphId}`, '', `- path: ${where}`, '', '#### 노드', '', '| nodeId | kind |', '| --- | --- |');
316
+ for (const node of template.nodes) lines.push(`| ${cell(node.nodeId)} | ${cell(node.kind)} |`);
317
+ lines.push('', '#### 간선', '');
318
+ const edges = edgeLinesOf(template);
319
+ if (edges.length === 0) lines.push('(간선 없음)');
320
+ else for (const edge of edges) lines.push(`- ${edge}`);
321
+ lines.push('');
322
+ }
323
+
324
+ lines.push('## 스캔한 파일', '');
325
+ lines.push('loadGraphTemplates 가 센 yaml. 파싱에 실패한 파일은 표 2에 없고 여기와 오류 절에만 있다.', '');
326
+ lines.push('| 디렉터리 | 파일 |', '| --- | --- |');
327
+ for (const { dir } of scanned) {
328
+ const names = yamlNames(input.graphsRoot, dir);
329
+ const prefix = dir === '.' ? '' : `${dir}/`;
330
+ if (names.length === 0) {
331
+ lines.push(`| ${cell(dir === '.' ? '(graphs root)' : dir)} | (yaml 0) |`);
332
+ continue;
333
+ }
334
+ for (const name of names) lines.push(`| ${cell(dir === '.' ? '(graphs root)' : dir)} | ${cell(`${prefix}${name}`)} |`);
335
+ }
336
+ lines.push('', '## 로드 오류 · 경고 · scannedFiles', '');
337
+ lines.push(`scannedFiles 합: ${scannedFiles}`, '');
338
+ if (missingDirs.length > 0) {
339
+ lines.push('디렉토리 없음 (0개 읽음과 구분):', '');
340
+ for (const entry of missingDirs) {
341
+ for (const issue of entry.result.errors) lines.push(`- ${cell(issue.path)} — ${issue.message}`);
342
+ }
343
+ lines.push('');
344
+ }
345
+ for (const { dir, result } of scanned) {
346
+ const label = dir === '.' ? '(graphs root)' : dir;
347
+ const unreadable = result.scannedFiles === 0 && result.errors.some((issue) => issue.message.startsWith('그래프 디렉토리를 못 읽었다'));
348
+ lines.push(`### ${label}`, '');
349
+ lines.push(`- scannedFiles: ${result.scannedFiles}${unreadable ? ' (디렉토리 없음)' : result.scannedFiles === 0 ? ' (0개 읽음)' : ''}`);
350
+ lines.push(...issueLines('errors', result.errors));
351
+ lines.push(...issueLines('warnings', result.warnings));
352
+ lines.push('');
353
+ }
354
+
355
+ const axes: GraphKind[] = ['harness', 'workflow'];
356
+ lines.push('## 축별 종류 수', '');
357
+ for (const axis of axes) lines.push(`- ${axis}: ${listNodeKinds(axis).length}`);
358
+ lines.push('');
359
+ return `${lines.join('\n').replace(/\n{3,}/g, '\n\n')}\n`;
360
+ }
361
+
362
+ function repoRootFromHere(): string {
363
+ return join(dirname(fileURLToPath(import.meta.url)), '..', '..');
364
+ }
365
+
366
+ /** git 트리가 아니면 죽지 않는다. 설치본은 package.json version 으로 적는다. */
367
+ export function readCommit(repoRoot: string): string {
368
+ try {
369
+ return execFileSync('git', ['rev-parse', 'HEAD'], { cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
370
+ } catch {
371
+ let version = 'unknown';
372
+ try {
373
+ const raw = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')) as { version?: unknown };
374
+ if (typeof raw.version === 'string' && raw.version.length > 0) version = raw.version;
375
+ } catch { /* 설치본인데 package.json 도 없으면 unknown */ }
376
+ return `(설치본 v${version})`;
377
+ }
378
+ }
379
+
380
+ export function defaultOutputPath(repoRoot: string): string {
381
+ return join(repoRoot, 'docs', 'generated', 'graph-anatomy.md');
382
+ }
383
+
384
+ /** 옆 파일 이름. `a.md` → `a.md.json`. */
385
+ export function snapshotPathOf(out: string): string {
386
+ return `${out}.json`;
387
+ }
388
+
389
+ function isSnapshot(value: unknown): value is AnatomySnapshot {
390
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
391
+ const record = value as Record<string, unknown>;
392
+ if (typeof record.generatedAt !== 'string' || typeof record.commit !== 'string') return false;
393
+ if (!record.graphs || typeof record.graphs !== 'object' || Array.isArray(record.graphs)) return false;
394
+ return Object.values(record.graphs as Record<string, unknown>).every((nodes) => Array.isArray(nodes) && nodes.every((node) => typeof node === 'string'));
395
+ }
396
+
397
+ /** 쓰기 전의 옆 파일. 없거나 깨졌으면 null(첫 생성). 파일을 쓰지 않는다. */
398
+ export function readPreviousSnapshot(out: string): AnatomySnapshot | null {
399
+ const path = snapshotPathOf(out);
400
+ if (!existsSync(path)) return null;
401
+ try {
402
+ const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));
403
+ return isSnapshot(parsed) ? parsed : null;
404
+ } catch {
405
+ return null;
406
+ }
407
+ }
408
+
409
+ function parseFlag(argv: readonly string[], name: string): string | undefined {
410
+ const index = argv.indexOf(name);
411
+ const value = index >= 0 ? argv[index + 1] : undefined;
412
+ if (index >= 0 && (value === undefined || value.startsWith('--'))) throw new Error(`${name} 에는 경로가 필요하다`);
413
+ return value;
414
+ }
415
+
416
+ export function parseOutArg(argv: readonly string[], fallback: string): string {
417
+ return parseFlag(argv, '--out') ?? fallback;
418
+ }
419
+
420
+ function main(): void {
421
+ const repoRoot = repoRootFromHere();
422
+ const argv = process.argv.slice(2);
423
+ const graphsRoot = parseFlag(argv, '--graphs') ?? join(repoRoot, 'graphs');
424
+ const generatedAt = new Date().toISOString();
425
+ const commit = readCommit(parseFlag(argv, '--repo') ?? repoRoot);
426
+ const out = parseOutArg(argv, defaultOutputPath(repoRoot));
427
+ const scanned = scanGraphDirs(graphsRoot);
428
+ const next = snapshotOf(scanned, commit, generatedAt);
429
+ const prev = readPreviousSnapshot(out);
430
+ const markdown = renderGraphAnatomy({ graphsRoot, commit, generatedAt }, renderYesterdaySection(prev, next));
431
+ mkdirSync(dirname(out), { recursive: true });
432
+ writeFileSync(out, markdown);
433
+ writeFileSync(snapshotPathOf(out), `${JSON.stringify(next, null, 2)}\n`);
434
+ process.stdout.write(`${out}\n`);
435
+ }
436
+
437
+ if (import.meta.main) main();
@@ -2,7 +2,7 @@
2
2
  // 공개 문서에 적힌 `elanous …` 명령·하위 명령·플래그가 «실제 CLI» 에 있나 — 문서가 실제와 어긋나는 1순위 원인을 잡는다.
3
3
  // bun scripts/docs-cli-check.ts [--json] [files…] (기본 = release/public/내부 문서 `*` ⊕ README.md)
4
4
  // 🩸 계기(2026-09-25 🅢 RFC 피드백 ③): README·install.md 가 거짓이 된 원인이 전부 «설치기·doctor 가 바뀌었는데 문서가 모름»이었다.
5
- // 자 = `bun bin/elanous.mjs <cmd> [<sub>] [<subsub>] --help` 산출(원천) — 문서의 코드 블록과 인라인 코드에서 `elanous ` 로 시작하는 것만 본다.
5
+ // 자 = `bun bin/elanous.mjs <cmd> [<sub>] [<subsub>] --help` 산출(원천) — 문서의 코드 블록과 인라인 코드에서 `elanous ` 또는 `bun bin/elanous.mjs ` 로 시작하는 것만 본다.
6
6
  // ⛔ 못 본 것은 «없다»로 적지 않는다: `--help` 가 실패하면 그 명령은 `unmeasured` 로 따로 센다.
7
7
  import { readdirSync, readFileSync, statSync } from 'node:fs';
8
8
  import { join, relative, resolve } from 'node:path';
@@ -21,10 +21,10 @@ export function extractElanousCommands(file: string, text: string): DocCommand[]
21
21
  if (/^\s*(```|~~~)/.test(raw)) { inFence = !inFence; return; }
22
22
  const candidates: string[] = [];
23
23
  if (inFence) candidates.push(raw.replace(/#.*$/, ''));
24
- for (const m of raw.matchAll(/`(elanous [^`]+)`/g)) candidates.push(m[1]!);
24
+ for (const m of raw.matchAll(/`((?:bun bin\/elanous\.mjs |elanous )[^`]+)`/g)) candidates.push(m[1]!);
25
25
  for (const c of candidates) {
26
26
  for (const seg of c.split(/&&|\|\||;|\|/)) {
27
- const m = /^(?:(?:\$\s+|>\s+|sudo\s+|[A-Za-z_][A-Za-z0-9_]*=\S+\s+))*elanous\s+(.+)$/.exec(seg.trim());
27
+ const m = /^(?:(?:\$\s+|>\s+|sudo\s+|[A-Za-z_][A-Za-z0-9_]*=\S+\s+))*(?:bun bin\/elanous\.mjs |elanous\s+)(.+)$/.exec(seg.trim());
28
28
  if (!m) continue;
29
29
  const tokens = m[1]!.trim().split(/\s+/).filter(Boolean);
30
30
  const cmd = tokens[0];
@@ -86,7 +86,7 @@ export function checkCommands(refs: readonly DocCommand[], help: HelpRunner = de
86
86
  }
87
87
  }
88
88
  for (const f of ref.flags) {
89
- if (!new RegExp(`(^|[\\s,])${f}(\\b|[\\s,=<\\[])`, 'm').test(flagsHelp)) findings.push({ kind: 'unknown-flag', ref, detail: `${f} (${flagsPath})` });
89
+ if (!new RegExp(`(^|[\\s,])${f.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(\\b|[\\s,=<\\[])`, 'm').test(flagsHelp)) findings.push({ kind: 'unknown-flag', ref, detail: `${f} (${flagsPath})` });
90
90
  }
91
91
  }
92
92
  return findings;
@@ -5,7 +5,7 @@ import { spawnSync } from 'node:child_process';
5
5
  import { join, relative, resolve, sep } from 'node:path';
6
6
  import { stringify } from 'yaml';
7
7
  import { debug } from '../src/debug/log.js';
8
- import { checkBrand } from './brand/check.js';
8
+ import { BRAND_RULES_UNMEASURED, checkBrand } from './brand/check.js';
9
9
  import { checkCommands, extractElanousCommands, type HelpRunner } from './docs-cli-check.js';
10
10
  import { scanLeaks } from './public-export.js';
11
11
 
@@ -25,7 +25,7 @@ function measure(run: () => Criterion): Criterion {
25
25
  }
26
26
 
27
27
  /** Files are repository-relative paths (or absolute paths within root); the ledger is never read or written. */
28
- export function suggestExposeRubric(root: string, files?: readonly string[], help?: HelpRunner): Suggestion[] {
28
+ export function suggestExposeRubric(root: string, files?: readonly string[], help?: HelpRunner, rulesPath?: string): Suggestion[] {
29
29
  const names = files ?? readdirSync(join(root, DOCS)).filter((name) => name.endsWith('.md')).map((name) => `${DOCS}/${name}`);
30
30
  const paths = [...new Set(names.map((name) => relative(root, resolve(root, name)).split(sep).join('/')))];
31
31
  if (paths.some((path) => !/^release\/public\/docs\/[^/]+\.md$/.test(path))) throw new Error('문서 경로는 release/public/docs/*.md 이어야 합니다');
@@ -77,6 +77,15 @@ export function suggestExposeRubric(root: string, files?: readonly string[], hel
77
77
  const failed = unmeasured(error);
78
78
  return { path, criteria: { reproducible: failed, confidential: failed, brand: failed } };
79
79
  }
80
+ const brand = measure(() => {
81
+ const result = checkBrand('public-docs', [absolute], rulesPath);
82
+ if (result.missing) return { status: 'n/a', reason: result.message ?? BRAND_RULES_UNMEASURED };
83
+ if (result.files !== 1 || result.rules === 0) return unmeasured('브랜드 규칙 없음 또는 파일 검사 실패');
84
+ const ids = [...new Set(result.findings.map((finding) => finding.id))].slice(0, 3);
85
+ return ids.length
86
+ ? { status: 'fail', reason: `브랜드 규칙: ${ids.join(', ')}` }
87
+ : { status: 'pass', reason: '브랜드 규칙 위반 없음' };
88
+ });
80
89
  const reproducible = measure(() => {
81
90
  const refs = extractElanousCommands(path, text);
82
91
  if (!refs.length) return { status: 'n/a', reason: '문서에 elanous 명령 없음' };
@@ -95,14 +104,7 @@ export function suggestExposeRubric(root: string, files?: readonly string[], hel
95
104
  ? { status: 'fail', reason: `누출 표식: ${markers.join(', ')}` }
96
105
  : { status: 'pass', reason: '누출 표식 없음' };
97
106
  });
98
- const brand = measure(() => {
99
- const result = checkBrand('public-docs', [absolute]);
100
- if (result.missing || result.files !== 1 || result.rules === 0) return unmeasured('브랜드 규칙 없음 또는 파일 검사 실패');
101
- const ids = [...new Set(result.findings.map((finding) => finding.id))].slice(0, 3);
102
- return ids.length
103
- ? { status: 'fail', reason: `브랜드 규칙: ${ids.join(', ')}` }
104
- : { status: 'pass', reason: '브랜드 규칙 위반 없음' };
105
- });
107
+ // 브랜드 규칙이 없으면 브랜드만 «측정 불가» — 따로 잰 재현성·누출 결과는 덮어쓰지 않는다.
106
108
  return { path, criteria: { reproducible, confidential, brand } };
107
109
  });
108
110
  const statuses = results.flatMap((item) => KEYS.map((key) => item.criteria[key].status));
@@ -119,19 +121,23 @@ export function run(argv: readonly string[], root = process.cwd(), help?: HelpRu
119
121
  const json = argv.includes('--json');
120
122
  let files: string[] | undefined;
121
123
  let out: string | undefined;
124
+ let rulesPath: string | undefined;
122
125
  for (let i = 0; i < argv.length; i++) {
123
126
  const arg = argv[i]!;
124
127
  if (arg === '--json') continue;
125
128
  if (arg === '--yaml-patch') {
126
129
  out = argv[++i];
127
130
  if (!out || out.startsWith('--')) throw new Error('--yaml-patch 에 출력 경로가 필요합니다');
131
+ } else if (arg === '--rules') {
132
+ rulesPath = argv[++i];
133
+ if (!rulesPath || rulesPath.startsWith('--')) throw new Error('--rules 에 규칙 경로가 필요합니다');
128
134
  } else if (arg === '--files') {
129
135
  files = [];
130
136
  while (argv[i + 1] && !argv[i + 1]!.startsWith('--')) files.push(argv[++i]!);
131
137
  if (!files.length) throw new Error('--files 에 문서 경로가 필요합니다');
132
138
  } else throw new Error(`알 수 없는 옵션: ${arg}`);
133
139
  }
134
- const results = suggestExposeRubric(root, files, help);
140
+ const results = suggestExposeRubric(root, files, help, rulesPath);
135
141
  if (out) {
136
142
  const target = resolve(root, out);
137
143
  const ledger = resolve(root, LEDGER);
@@ -140,7 +146,7 @@ export function run(argv: readonly string[], root = process.cwd(), help?: HelpRu
140
146
  }
141
147
  if (json) console.log(JSON.stringify(results));
142
148
  else {
143
- for (const { path, criteria } of results) console.log(`${path} · ${KEYS.map((key) => `${key} ${criteria[key].status}${criteria[key].status === 'fail' ? `(${criteria[key].reason.split(': ').slice(1).join(': ') || criteria[key].reason})` : ''}`).join(' · ')}`);
149
+ for (const { path, criteria } of results) console.log(`${path} · ${KEYS.map((key) => `${key} ${criteria[key].status}${criteria[key].status === 'fail' ? `(${criteria[key].reason.split(': ').slice(1).join(': ') || criteria[key].reason})` : criteria[key].reason === BRAND_RULES_UNMEASURED ? `(${criteria[key].reason})` : ''}`).join(' · ')}`);
144
150
  const statuses = results.flatMap((item) => KEYS.map((key) => item.criteria[key].status));
145
151
  console.log(`합계 문서 ${results.length} · pass ${statuses.filter((status) => status === 'pass').length} · fail ${statuses.filter((status) => status === 'fail').length} · n/a ${statuses.filter((status) => status === 'n/a').length} · maturity/value/evidence/support 사람 판정 (MK)`);
146
152
  }