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,343 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * GOAL-LIFE① — docs/goals 전수 분류(이관 전 측정).
4
+ *
5
+ * GitHub API·gh 를 부르지 않는다. git 이력과 파일 시스템만 읽고,
6
+ * docs/goals 아래 어떤 파일도 쓰지 않는다.
7
+ *
8
+ * bun scripts/goals/goal-census.ts [--json] [--out <path>]
9
+ */
10
+ import { spawnSync } from 'node:child_process';
11
+ import { readdirSync, statSync, writeFileSync } from 'node:fs';
12
+ import { join, relative, resolve } from 'node:path';
13
+ import { debug } from '../../src/debug/log.js';
14
+
15
+ export type GoalKind = 'GOAL' | 'ASK' | 'other';
16
+ export type GoalClass = 'landed' | 'untracked' | 'tracked-no-pr';
17
+
18
+ export interface GoalCensusEntry {
19
+ readonly path: string;
20
+ readonly classification: GoalClass;
21
+ readonly kind: GoalKind;
22
+ readonly bytes: number;
23
+ /** 추가 날짜 YYYY-MM-DD (KST). 미추적이면 파일 mtime. */
24
+ readonly addedDateKst: string;
25
+ readonly prNumber?: number;
26
+ readonly addCommit?: string;
27
+ }
28
+
29
+ export interface GoalCensusSummary {
30
+ readonly total: number;
31
+ readonly byClass: Record<GoalClass, number>;
32
+ readonly byKind: Record<GoalKind, number>;
33
+ readonly matrix: Record<GoalClass, Record<GoalKind, number>>;
34
+ readonly bytes: number;
35
+ readonly bytesByClass: Record<GoalClass, number>;
36
+ /** 월별 추가 수 — YYYY-MM (KST). */
37
+ readonly addedByMonth: Record<string, number>;
38
+ }
39
+
40
+ export interface GoalCensus {
41
+ readonly entries: readonly GoalCensusEntry[];
42
+ readonly summary: GoalCensusSummary;
43
+ }
44
+
45
+ export interface AddedCommit {
46
+ readonly hash: string;
47
+ readonly subject: string;
48
+ /** git %aI — ISO 8601 with offset. */
49
+ readonly authoredAt: string;
50
+ }
51
+
52
+ export interface CensusGoalsInput {
53
+ readonly repoRoot: string;
54
+ /** 경로(repo 상대, posix) → 그 파일을 처음 추가한 커밋. 없으면 미추적. */
55
+ readonly gitLog: (repoRoot: string) => ReadonlyMap<string, AddedCommit>;
56
+ /** repo 상대 미추적 경로. */
57
+ readonly listUntracked: (repoRoot: string) => readonly string[];
58
+ /** 테스트·대체 루트. 기본 docs/goals. */
59
+ readonly goalsDir?: string;
60
+ }
61
+
62
+ const CLASSES: readonly GoalClass[] = ['landed', 'untracked', 'tracked-no-pr'];
63
+ const KINDS: readonly GoalKind[] = ['GOAL', 'ASK', 'other'];
64
+
65
+ const PR_AT_END = /\(#(\d+)\)\s*$/;
66
+
67
+ export function classifyKind(relPath: string): GoalKind {
68
+ const base = relPath.split('/').pop() ?? relPath;
69
+ if (base.startsWith('GOAL')) return 'GOAL';
70
+ if (base.startsWith('ASK')) return 'ASK';
71
+ return 'other';
72
+ }
73
+
74
+ export function prNumberFromSubject(subject: string): number | undefined {
75
+ const m = subject.match(PR_AT_END);
76
+ return m ? Number(m[1]) : undefined;
77
+ }
78
+
79
+ /** UTC ISO(또는 offset 포함) → KST YYYY-MM-DD. */
80
+ export function kstDate(iso: string): string {
81
+ const t = Date.parse(iso);
82
+ if (Number.isNaN(t)) return iso.slice(0, 10);
83
+ const kst = new Date(t + 9 * 60 * 60 * 1000);
84
+ const y = kst.getUTCFullYear();
85
+ const m = String(kst.getUTCMonth() + 1).padStart(2, '0');
86
+ const d = String(kst.getUTCDate()).padStart(2, '0');
87
+ return `${y}-${m}-${d}`;
88
+ }
89
+
90
+ function emptyMatrix(): Record<GoalClass, Record<GoalKind, number>> {
91
+ const matrix = {} as Record<GoalClass, Record<GoalKind, number>>;
92
+ for (const c of CLASSES) {
93
+ matrix[c] = { GOAL: 0, ASK: 0, other: 0 };
94
+ }
95
+ return matrix;
96
+ }
97
+
98
+ function measureEntry(repoRoot: string, rel: string): { bytes: number; mtimeIso: string; files: string[] } {
99
+ const abs = resolve(repoRoot, rel);
100
+ const root = resolve(repoRoot);
101
+ try {
102
+ const st = statSync(abs);
103
+ if (!st.isDirectory()) return { bytes: st.size, mtimeIso: st.mtime.toISOString(), files: [rel] };
104
+ const files: string[] = [];
105
+ walkFiles(abs, root, files);
106
+ let bytes = 0;
107
+ for (const f of files) {
108
+ try { bytes += statSync(resolve(repoRoot, f)).size; } catch { /* skip missing */ }
109
+ }
110
+ return { bytes, mtimeIso: st.mtime.toISOString(), files };
111
+ } catch {
112
+ return { bytes: 0, mtimeIso: new Date(0).toISOString(), files: [] };
113
+ }
114
+ }
115
+
116
+ function earliestAddition(added: ReadonlyMap<string, AddedCommit>, prefix: string): AddedCommit | undefined {
117
+ let best: AddedCommit | undefined;
118
+ for (const [p, c] of added) {
119
+ if (!p.startsWith(prefix)) continue;
120
+ if (!best || c.authoredAt < best.authoredAt) best = c;
121
+ }
122
+ return best;
123
+ }
124
+
125
+ function walkFiles(dir: string, root: string, out: string[]): void {
126
+ let names: string[];
127
+ try {
128
+ names = readdirSync(dir);
129
+ } catch {
130
+ return;
131
+ }
132
+ for (const name of names) {
133
+ if (name === '.git') continue;
134
+ const abs = join(dir, name);
135
+ let st;
136
+ try {
137
+ st = statSync(abs);
138
+ } catch {
139
+ continue;
140
+ }
141
+ if (st.isDirectory()) walkFiles(abs, root, out);
142
+ else if (st.isFile()) out.push(relative(root, abs).split('\\').join('/'));
143
+ }
144
+ }
145
+
146
+ /**
147
+ * `ls docs/goals` 와 같은 최상위 이름(파일·디렉터리 각 1).
148
+ * 디렉터리의 크기·착지 근거는 그 아래 파일을 모아 정한다.
149
+ */
150
+ export function listGoalFiles(repoRoot: string, goalsDir = 'docs/goals'): string[] {
151
+ const abs = resolve(repoRoot, goalsDir);
152
+ let names: string[];
153
+ try {
154
+ names = readdirSync(abs);
155
+ } catch {
156
+ return [];
157
+ }
158
+ return names.filter((n) => n !== '.' && n !== '..').map((n) => `${goalsDir}/${n}`).sort();
159
+ }
160
+
161
+ export function summarize(entries: readonly GoalCensusEntry[]): GoalCensusSummary {
162
+ const byClass = { landed: 0, untracked: 0, 'tracked-no-pr': 0 } as Record<GoalClass, number>;
163
+ const byKind = { GOAL: 0, ASK: 0, other: 0 } as Record<GoalKind, number>;
164
+ const matrix = emptyMatrix();
165
+ const bytesByClass = { landed: 0, untracked: 0, 'tracked-no-pr': 0 } as Record<GoalClass, number>;
166
+ const addedByMonth: Record<string, number> = {};
167
+ let bytes = 0;
168
+ for (const e of entries) {
169
+ byClass[e.classification] += 1;
170
+ byKind[e.kind] += 1;
171
+ matrix[e.classification][e.kind] += 1;
172
+ bytesByClass[e.classification] += e.bytes;
173
+ bytes += e.bytes;
174
+ const month = e.addedDateKst.slice(0, 7);
175
+ addedByMonth[month] = (addedByMonth[month] ?? 0) + 1;
176
+ }
177
+ return { total: entries.length, byClass, byKind, matrix, bytes, bytesByClass, addedByMonth };
178
+ }
179
+
180
+ export function censusGoals(input: CensusGoalsInput): GoalCensus {
181
+ const goalsDir = input.goalsDir ?? 'docs/goals';
182
+ const prefix = goalsDir.endsWith('/') ? goalsDir : `${goalsDir}/`;
183
+ const files = listGoalFiles(input.repoRoot, goalsDir);
184
+ const added = input.gitLog(input.repoRoot);
185
+ const untracked = new Set(input.listUntracked(input.repoRoot).map((p) => p.split('\\').join('/')));
186
+
187
+ const entries: GoalCensusEntry[] = [];
188
+ for (const path of files) {
189
+ if (!path.startsWith(prefix)) continue;
190
+ const kind = classifyKind(path);
191
+ const measured = measureEntry(input.repoRoot, path);
192
+ const { bytes, mtimeIso } = measured;
193
+ const commit = added.get(path) ?? earliestAddition(added, `${path}/`);
194
+ const nestedUntracked = measured.files.length > 0
195
+ && measured.files.every((f) => untracked.has(f) || !added.has(f));
196
+ if (!commit || untracked.has(path) || nestedUntracked) {
197
+ entries.push({
198
+ path,
199
+ classification: 'untracked',
200
+ kind,
201
+ bytes,
202
+ addedDateKst: kstDate(mtimeIso),
203
+ });
204
+ continue;
205
+ }
206
+ const prNumber = prNumberFromSubject(commit.subject);
207
+ if (prNumber !== undefined) {
208
+ entries.push({
209
+ path,
210
+ classification: 'landed',
211
+ kind,
212
+ bytes,
213
+ addedDateKst: kstDate(commit.authoredAt),
214
+ prNumber,
215
+ addCommit: commit.hash,
216
+ });
217
+ } else {
218
+ entries.push({
219
+ path,
220
+ classification: 'tracked-no-pr',
221
+ kind,
222
+ bytes,
223
+ addedDateKst: kstDate(commit.authoredAt),
224
+ addCommit: commit.hash,
225
+ });
226
+ }
227
+ }
228
+ return { entries, summary: summarize(entries) };
229
+ }
230
+
231
+ /**
232
+ * 파일을 처음 추가한 커밋만. `git log --diff-filter=A` 한 번.
233
+ * 최신→과거 순이므로 같은 경로가 다시 나오면 나중 값(최초 추가)이 이긴다.
234
+ */
235
+ export function gitLogAdditions(repoRoot: string, goalsDir = 'docs/goals'): Map<string, AddedCommit> {
236
+ const r = spawnSync(
237
+ 'git',
238
+ [
239
+ '-C', repoRoot,
240
+ 'log',
241
+ '--diff-filter=A',
242
+ '--name-only',
243
+ '--format=@@%H%x09%s%x09%aI',
244
+ '--',
245
+ goalsDir,
246
+ ],
247
+ { encoding: 'utf8', maxBuffer: 512 * 1024 * 1024 },
248
+ );
249
+ if (r.status !== 0) {
250
+ throw new Error(`git log failed: ${(r.stderr || '').trim()}`);
251
+ }
252
+ const out = new Map<string, AddedCommit>();
253
+ let current: AddedCommit | undefined;
254
+ for (const line of r.stdout.split('\n')) {
255
+ if (!line) continue;
256
+ if (line.startsWith('@@')) {
257
+ const body = line.slice(2);
258
+ const tab = body.indexOf('\t');
259
+ const tab2 = body.lastIndexOf('\t');
260
+ if (tab > 0 && tab2 > tab) {
261
+ current = {
262
+ hash: body.slice(0, tab),
263
+ subject: body.slice(tab + 1, tab2),
264
+ authoredAt: body.slice(tab2 + 1),
265
+ };
266
+ }
267
+ continue;
268
+ }
269
+ if (current) out.set(line, current);
270
+ }
271
+ return out;
272
+ }
273
+
274
+ /** 작업 트리의 미추적 파일. `git ls-files --others --exclude-standard`. */
275
+ export function gitListUntracked(repoRoot: string, goalsDir = 'docs/goals'): string[] {
276
+ const r = spawnSync(
277
+ 'git',
278
+ ['-C', repoRoot, 'ls-files', '--others', '--exclude-standard', '--', goalsDir],
279
+ { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 },
280
+ );
281
+ if (r.status !== 0) {
282
+ throw new Error(`git ls-files failed: ${(r.stderr || '').trim()}`);
283
+ }
284
+ return r.stdout.split('\n').filter((p) => p.length > 0);
285
+ }
286
+
287
+ export function formatSummary(census: GoalCensus): string {
288
+ const { summary } = census;
289
+ const lines: string[] = [];
290
+ lines.push(`total ${summary.total} bytes ${summary.bytes}`);
291
+ lines.push('class × kind');
292
+ const header = ['class', ...KINDS, 'bytes'].join('\t');
293
+ lines.push(header);
294
+ for (const c of CLASSES) {
295
+ const row = [
296
+ c,
297
+ ...KINDS.map((k) => String(summary.matrix[c][k])),
298
+ String(summary.bytesByClass[c]),
299
+ ];
300
+ lines.push(row.join('\t'));
301
+ }
302
+ lines.push('added by month (KST)');
303
+ const months = Object.keys(summary.addedByMonth).sort();
304
+ for (const month of months) lines.push(`${month}\t${summary.addedByMonth[month]}`);
305
+ return lines.join('\n') + '\n';
306
+ }
307
+
308
+ function parseArgs(argv: readonly string[]): { json: boolean; out?: string } {
309
+ let json = false;
310
+ let out: string | undefined;
311
+ for (let i = 0; i < argv.length; i++) {
312
+ const a = argv[i];
313
+ if (a === '--json') json = true;
314
+ else if (a === '--out') {
315
+ out = argv[i + 1];
316
+ i += 1;
317
+ } else if (a.startsWith('--out=')) out = a.slice('--out='.length);
318
+ }
319
+ return { json, out };
320
+ }
321
+
322
+ function main(argv: readonly string[]): void {
323
+ const repoRoot = resolve(import.meta.dir, '../..');
324
+ const args = parseArgs(argv);
325
+ const census = censusGoals({
326
+ repoRoot,
327
+ gitLog: (root) => gitLogAdditions(root),
328
+ listUntracked: (root) => gitListUntracked(root),
329
+ });
330
+ const { summary } = census;
331
+ debug.log('goals.census', 'scanned', {
332
+ total: summary.total,
333
+ landed: summary.byClass.landed,
334
+ untracked: summary.byClass.untracked,
335
+ trackedNoPr: summary.byClass['tracked-no-pr'],
336
+ bytes: summary.bytes,
337
+ });
338
+ const body = args.json ? JSON.stringify(census, null, 2) + '\n' : formatSummary(census);
339
+ if (args.out) writeFileSync(args.out, body);
340
+ process.stdout.write(body);
341
+ }
342
+
343
+ if (import.meta.main) main(process.argv.slice(2));
@@ -5,6 +5,7 @@ import { dirname, extname, isAbsolute, join, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { homedir } from 'node:os';
7
7
  import { debug } from '../../src/debug/log.js';
8
+ import { runOcr } from '../../skills/omni-digest/src/ocr.js';
8
9
  import { streamLLM } from '../../src/llm.js';
9
10
  import { unified } from 'unified';
10
11
  import remarkParse from 'remark-parse';
@@ -19,6 +20,7 @@ export interface Counts { inputs: number; diagrams: number; rendered: number; ht
19
20
  export interface StageResult extends Counts { outcome: 'ok' | 'skipped' }
20
21
  export interface StageOptions {
21
22
  llm?: (prompt: string) => Promise<string>;
23
+ ocr?: (filePath: string) => Promise<string>;
22
24
  toolsPath?: string;
23
25
  referenceRoot?: string;
24
26
  }
@@ -62,9 +64,9 @@ function binary(name: string, path = process.env.PATH ?? ''): string | undefined
62
64
  return undefined;
63
65
  }
64
66
 
65
- function run(bin: string, args: string[], dir: string, timeout: number): ReturnType<typeof spawnSync> {
67
+ function run(bin: string, args: string[], dir: string, timeout: number, cacheDir = dir): ReturnType<typeof spawnSync> {
66
68
  return spawnSync(bin, args, { cwd: dir, encoding: 'utf8', timeout, maxBuffer: 20 * 1024 * 1024,
67
- env: { ...process.env, HOME: dir, XDG_CACHE_HOME: dir, XDG_CONFIG_HOME: dir, UV_CACHE_DIR: dir, PYTHONDONTWRITEBYTECODE: '1' } });
69
+ env: { ...process.env, HOME: dir, XDG_CACHE_HOME: dir, XDG_CONFIG_HOME: dir, UV_CACHE_DIR: cacheDir, PYTHONDONTWRITEBYTECODE: '1' } });
68
70
  }
69
71
 
70
72
  function readInputs(dir: string): Input[] {
@@ -83,7 +85,7 @@ function counts(dir: string): Counts {
83
85
  return { inputs, diagrams, rendered, html: existsSync(join(dir, 'notes.html')) ? 'yes' : 'no', pdf: existsSync(join(dir, 'notes.pdf')) ? 'yes' : 'skipped' };
84
86
  }
85
87
 
86
- export function collect(folder: string, options: StageOptions = {}): StageResult {
88
+ export async function collect(folder: string, options: StageOptions = {}): Promise<StageResult> {
87
89
  const dir = outputDir(folder);
88
90
  const files: Input[] = [];
89
91
  const pdfText = binary('pdftotext', options.toolsPath);
@@ -94,11 +96,22 @@ export function collect(folder: string, options: StageOptions = {}): StageResult
94
96
  const kind = kinds[extname(entry.name).toLowerCase()];
95
97
  if (!entry.isFile() || !kind) continue;
96
98
  const at = join(base, entry.name);
97
- const text = kind === 'markdown' ? readFileSync(at, 'utf8') : kind === 'image' ? '이미지 — 시각 자료 (텍스트 추출 없음)' :
98
- pdfText ? (() => {
99
- const result = run(pdfText, ['-layout', at, '-'], dir, 60000);
100
- return result.status === 0 ? String(result.stdout) : 'PDF 텍스트 없음 — pdftotext 추출 실패';
101
- })() : 'PDF 텍스트 없음 — pdftotext 없음';
99
+ let text: string;
100
+ if (kind === 'markdown') text = readFileSync(at, 'utf8');
101
+ else if (kind === 'image') {
102
+ try {
103
+ const extracted = await (options.ocr ?? runOcr)(at);
104
+ if (!extracted.trim()) throw new Error('Upstage/OCR.space 폴백 결과가 비었다');
105
+ text = extracted;
106
+ } catch (error) {
107
+ const reason = error instanceof Error ? error.message : String(error);
108
+ text = `이미지 텍스트 못 읽음 · ${reason}`;
109
+ debug.log('lecture-note.collect', 'ocr-failed', { name: prefix + entry.name, reason });
110
+ }
111
+ } else if (pdfText) {
112
+ const result = run(pdfText, ['-layout', at, '-'], dir, 60000);
113
+ text = result.status === 0 ? String(result.stdout) : 'PDF 텍스트 없음 — pdftotext 추출 실패';
114
+ } else text = 'PDF 텍스트 없음 — pdftotext 없음';
102
115
  files.push({ name: prefix + entry.name, kind, text });
103
116
  }
104
117
  writeFileSync(artifact(dir, 'inputs.json'), JSON.stringify(files, null, 2) + '\n');
@@ -131,19 +144,26 @@ export function render(folder: string, options: StageOptions = {}): StageResult
131
144
  const renderer = join(options.referenceRoot ?? ownRoot, 'diagram-master/references/engines/mermaid/render_mermaid.py');
132
145
  const uv = binary('uv', options.toolsPath);
133
146
  let unavailable = 0;
147
+ let failed = 0;
134
148
  for (const [i, match] of [...notes.matchAll(mermaid)].entries()) {
135
149
  const png = artifact(dir, `diagram-${i + 1}.png`);
136
150
  if (existsSync(png)) rmSync(png);
137
151
  if (!uv || !existsSync(renderer)) { unavailable++; continue; }
138
152
  const mmd = artifact(dir, `diagram-${i + 1}.mmd`);
139
153
  writeFileSync(mmd, match[1]!);
140
- const result = run(uv, ['run', 'python', renderer, mmd, '--output', png], dir, 120000);
154
+ const cacheDir = join(process.env.ELANOUS_STATE_DIR ?? join(homedir(), '.elanous'), 'lecture-note', 'uv-cache');
155
+ const result = run(uv, ['run', 'python', renderer, mmd, '--output', png], dir, 120000, cacheDir);
141
156
  if (result.status !== 0 || !existsSync(png)) {
142
157
  if (existsSync(png)) rmSync(png);
143
- throw new Error(`Mermaid 렌더 실패 (diagram-${i + 1}, exit=${result.status}): ${result.error?.message ?? result.stderr ?? result.stdout ?? 'PNG 없음'}`);
158
+ failed++;
159
+ const detail = String(result.stderr ?? '').split(/\r?\n/)[0]?.trim() || result.error?.message ||
160
+ String(result.stdout ?? '').split(/\r?\n/)[0]?.trim() || 'PNG 없음';
161
+ const reason = `Mermaid 렌더 실패 (diagram-${i + 1}, exit=${result.status}): ${detail}`;
162
+ debug.log('lecture-note.render', 'diagram-failed', { name: `diagram-${i + 1}`, reason });
144
163
  }
145
164
  }
146
165
  if (unavailable) console.log(`렌더러 없음: ${unavailable}`);
166
+ if (failed) console.log(`못 그림: ${failed}`);
147
167
  return { ...counts(dir), outcome: 'ok' };
148
168
  }
149
169
 
@@ -216,7 +236,7 @@ export function pdf(folder: string, options: StageOptions = {}): StageResult {
216
236
  }
217
237
 
218
238
  export async function runStage(stage: Stage, folder: string, options: StageOptions = {}): Promise<StageResult> {
219
- const result = stage === 'collect' ? collect(folder, options) : stage === 'draft' ? await draft(folder, options) :
239
+ const result = stage === 'collect' ? await collect(folder, options) : stage === 'draft' ? await draft(folder, options) :
220
240
  stage === 'render' ? render(folder, options) : stage === 'html' ? html(folder, options) : pdf(folder, options);
221
241
  debug.log('lecture.note', stage, { inputs: result.inputs, diagrams: result.diagrams, rendered: result.rendered, pdf: result.pdf });
222
242
  return result;
@@ -29,14 +29,41 @@ function inlineText(value: string): string {
29
29
  www ? 'w\u200bww.' : `${scheme ?? at}\u200b${slashes ?? ''}`);
30
30
  }
31
31
 
32
+ function displayId(id: string): string {
33
+ return id.replace(/(-(?:\d{4}-\d{2}-\d{2}|undated))-[a-f\d]{64}$/i, '$1');
34
+ }
35
+
36
+ function summaryText(value: string): string {
37
+ // 원장 적재 때 줄바꿈이 사라져 «| a | b | |---|---| | 1 | 2 |» 처럼 한 줄로 뭉개진 표는 행 경계 «| |»에서 다시 줄로 편다.
38
+ const lines = value.split(/\r?\n/)
39
+ .flatMap(line => /\|\s*:?-{3,}:?\s*\|/.test(line) && /\|\s+\|/.test(line) ? line.replace(/\|\s+\|/g, '|\n|').split('\n') : [line])
40
+ .map(line => line.trim()).filter(Boolean);
41
+ const cells = (line: string) => line.replace(/^\||\|$/g, '').split('|').map(cell => cell.trim());
42
+ const findTable = () => lines.findIndex((line, index) => {
43
+ if (!line.includes('|') || !lines[index + 1]) return false;
44
+ const separator = cells(lines[index + 1]);
45
+ return separator.length > 1 && separator.every(cell => /^:?-{3,}:?$/.test(cell));
46
+ });
47
+ // 표가 여럿이면 모두 «첫 데이터 행 한 줄»로 접는다 — 뒤 표의 구분선·이스케이프가 남지 않게.
48
+ for (let tableAt = findTable(); tableAt >= 0; tableAt = findTable()) {
49
+ const row = lines[tableAt + 2]?.includes('|') ? lines[tableAt + 2] : lines[tableAt];
50
+ const summary = cells(row).join(' · ');
51
+ let after = tableAt + 2;
52
+ while (after < lines.length && lines[after].includes('|')) after++;
53
+ lines.splice(tableAt, after - tableAt, summary);
54
+ }
55
+ const text = lines.join(' ').replace(/\*\*|__/g, '').replace(/\s+/g, ' ').trim();
56
+ return inlineText(text.length > 200 ? `${text.slice(0, 200)}…` : text);
57
+ }
58
+
32
59
  function section(title: string, rows: LessonRow[], ledger: LessonLedger, kind: LessonStatus): string[] {
33
60
  const lines = [`## ${title}`, ''];
34
61
  if (!rows.length) return [...lines, '없음', ''];
35
62
  for (const row of rows) {
36
63
  const last = ledger.get(row.id).occurrences.at(-1);
37
- lines.push(`### ${inlineText(row.id)} — ${inlineText(row.incident)}`,
38
- `- 원인: ${inlineText(row.cause)}`,
39
- `- 처방: ${inlineText(row.remedy)}`,
64
+ lines.push(`### ${inlineText(displayId(row.id))} — ${inlineText(row.incident)}`,
65
+ `- 원인: ${summaryText(row.cause)}`,
66
+ `- 처방: ${summaryText(row.remedy)}`,
40
67
  `- 재발: ${row.occurrence_count}회`,
41
68
  `- 마지막 발생: ${inlineText(last?.at ?? '없음')} · 출처: ${inlineText(last?.source ?? '없음')}`);
42
69
  if (kind === 'candidate') lines.push(`- 반증: ${row.disproof?.trim() ? inlineText(row.disproof) : '반증 없음 — 승격 불가'}`);
@@ -0,0 +1,140 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { FEATURE_MATURITY } from '../../src/maturity/feature-maturity.js';
4
+ import type { ChecklistItem } from '../../src/release-loop/checklist.js';
5
+ import type { ReleaseSchedule } from '../../src/release-loop/release-schedule.js';
6
+ import type { MergedRunLedgerQuery } from '../../src/self-implement/run-ledger.js';
7
+ import { generateFeatureMap } from './generate-feature-map.js';
8
+
9
+ const MAP = resolve(import.meta.dir, '../../docs/marketing/MAP-value-props-to-features.md');
10
+ const AUTONOMY = new Set(['ORCH1', 'ORCH2', 'LOOP-LIVE1', 'AUTOQ-LOOP', 'AUTHOR-PAR', 'RUN-HELPER', 'FINISH-RATE']);
11
+ const ROADMAP = /^(?:ENT-|KPACK|GRID)/;
12
+
13
+ export interface AxBriefSources {
14
+ featureMap: () => string;
15
+ checklist: (version: string) => readonly ChecklistItem[];
16
+ schedules: () => readonly ReleaseSchedule[];
17
+ merges: () => Pick<MergedRunLedgerQuery, 'entries' | 'ledgerDirectoryMissing' | 'unreadableLedgerCount' | 'excludedLedgerCount'>;
18
+ now?: () => Date;
19
+ }
20
+
21
+ type Source<T> = { ok: true; value: T } | { ok: false; reason: string };
22
+
23
+ function readSource<T>(read: () => T): Source<T> {
24
+ try { return { ok: true, value: read() }; }
25
+ catch (error) { return { ok: false, reason: error instanceof Error ? error.message : String(error) }; }
26
+ }
27
+
28
+ function cell(text: string): string {
29
+ return text.replace(/\|/g, '\\|').replace(/[\r\n]+/g, ' ').trim();
30
+ }
31
+
32
+ function reason(source: Source<unknown>): string {
33
+ return source.ok ? '' : `못 읽음 · 사유: ${cell(source.reason)}`;
34
+ }
35
+
36
+ function versionNumbers(version: string): number[] | null {
37
+ return /^\d+\.\d+\.\d+$/.test(version) ? version.split('.').map(Number) : null;
38
+ }
39
+
40
+ function after03(version: string): boolean {
41
+ const parts = versionNumbers(version);
42
+ return !!parts && (parts[0]! > 0 || parts[1]! >= 3);
43
+ }
44
+
45
+ function compareVersions(left: string, right: string): number {
46
+ const a = versionNumbers(left)!, b = versionNumbers(right)!;
47
+ return a[0]! - b[0]! || a[1]! - b[1]! || a[2]! - b[2]!;
48
+ }
49
+
50
+ function firstLine(evidence: string | undefined): string {
51
+ return evidence?.split(/\r?\n/, 1)[0]?.trim() || '근거 없음';
52
+ }
53
+
54
+ /** Read-only report: failures in one ledger do not turn other sections into claims of success. */
55
+ export function generateAxBrief(version: string, sources: AxBriefSources): string {
56
+ const now = (sources.now ?? (() => new Date()))().toISOString();
57
+ const feature = readSource(sources.featureMap);
58
+ const current = readSource(() => sources.checklist(version));
59
+ const schedules = readSource(sources.schedules);
60
+ const merges = readSource(sources.merges);
61
+ const mergeCount = merges.ok && !merges.value.ledgerDirectoryMissing
62
+ ? new Set(merges.value.entries.filter((entry) => entry.merged).map((entry) => entry.prNumber)).size : null;
63
+ const mergeValue = merges.ok && !merges.value.ledgerDirectoryMissing && merges.value.unreadableLedgerCount > 0
64
+ ? `못 읽음 · 사유: 원장 ${merges.value.unreadableLedgerCount}건 판독 실패 · 확인된 병합 ${mergeCount}건 (집계 불완전)`
65
+ : mergeCount ?? (merges.ok ? '못 읽음 · 사유: 원장 디렉터리 없음' : reason(merges));
66
+ const mergeCaveat = merges.ok && !merges.value.ledgerDirectoryMissing && (merges.value.unreadableLedgerCount || merges.value.excludedLedgerCount)
67
+ ? ` · 미반영 원장: 못 읽음 ${merges.value.unreadableLedgerCount}건 · 중복 병합 제외 ${merges.value.excludedLedgerCount}건` : '';
68
+ const featureRows = feature.ok ? feature.value.split(/\r?\n/).filter((line) => /^\| .* \| (?:된다|베타) \|/.test(line)) : [];
69
+ const autonomous = current.ok ? current.value.filter((item) => AUTONOMY.has(item.id)) : [];
70
+ const roadmap = schedules.ok ? schedules.value.filter((schedule) => after03(schedule.version)).sort((a, b) => compareVersions(a.version, b.version)) : [];
71
+ const roadmapSources = roadmap.map((schedule) => ({ version: schedule.version, result: schedule.version === version ? current : readSource(() => sources.checklist(schedule.version)) }));
72
+ const roadmapFailures = roadmapSources.filter(({ result }) => !result.ok).map(({ version: v, result }) => `${v}: ${reason(result)}`);
73
+ const roadmapRows = roadmapSources.flatMap(({ version: v, result }) => result.ok
74
+ ? result.value.filter((item) => ROADMAP.test(item.id)).map((item) => `| ${cell(v)} | ${cell(item.id)} | ${cell(item.title)} |`)
75
+ : []);
76
+ const owners = [...new Set(autonomous.map((item) => item.owner).filter((owner): owner is string => !!owner))];
77
+ return [
78
+ '# AX-BRIEF — 내부·파트너용 AX 브리핑',
79
+ '',
80
+ '> 공개 문서가 아닙니다. 내부 수치·자리 이름을 포함하며 외부 공개 판정으로 쓰지 않습니다.',
81
+ `> 생성: ${cell(version)} · ${now} · 원천: FEATURE-MAP 생성 출력, 릴리스 체크리스트, 릴리스 일정(release schedules), self-implement run-ledger`,
82
+ '',
83
+ '## 1. 지금 되는 것',
84
+ '',
85
+ feature.ok ? '| 기능 | 성숙도 | 칸 id | 근거 PR |' : reason(feature),
86
+ ...(feature.ok ? ['|---|---|---|---|', ...featureRows] : []),
87
+ '',
88
+ '## 2. 자율 단계표',
89
+ '',
90
+ current.ok ? '| 칸 | 판 | 상태 | 근거 첫 줄 |' : reason(current),
91
+ ...(current.ok ? ['|---|---|---|---|', ...autonomous.map((item) => `| ${cell(item.id)} | ${cell(version)} | ${cell(item.status)} | ${cell(firstLine(item.evidence))} |`)] : []),
92
+ ...(current.ok ? [`자리 이름: ${owners.length ? owners.map(cell).join(' · ') : '원장에 없음'}`] : []),
93
+ '',
94
+ `실제 병합 수 (self-implement run-ledger 전체 기록 · 중복 PR 제외 · 외부/수동 병합 제외): ${mergeValue}${mergeCaveat}`,
95
+ '',
96
+ '## 3. 로드맵 계단',
97
+ '',
98
+ '> 계획 · 바뀔 수 있습니다',
99
+ schedules.ok ? '| 판 | 칸 | 제목 |' : reason(schedules),
100
+ ...(schedules.ok ? ['|---|---|---|', ...roadmapRows, ...roadmapFailures.map((failure) => `못 읽음 · 사유: ${cell(failure)}`)] : []),
101
+ '',
102
+ '## 4. 도입 경로 (파일럿 기준 · 한 자리부터)',
103
+ '',
104
+ '1. **설치 한 줄 → 웹 앱 첫 화면** — 쓰던 구독을 그대로 알아본다.',
105
+ '2. **한 자리(예: 마케팅 또는 기술)만** — L1(한 줄 → 병합)을 그 팀 저장소에서. 측정: 발사 대비 병합 · 사람 수확 비율.',
106
+ '3. **결정 카드** — 비가역·공개만 사람에게. 나머지는 자리에 위임 범위를 적는다.',
107
+ '4. **자리 넷 ⊕ 그림자 루프** — L2 를 «기록만»으로 일주일 대조한 뒤 live.',
108
+ '5. **지식 팩** — 사내 문서·절차를 팩으로(로드맵 §3 · 유료 판매는 결정 뒤).',
109
+ '',
110
+ ].join('\n');
111
+ }
112
+
113
+ if (import.meta.main) {
114
+ const args = process.argv.slice(2);
115
+ const option = (name: string) => {
116
+ const index = args.indexOf(name);
117
+ if (index < 0) return undefined;
118
+ const value = args[index + 1];
119
+ if (!value || value.startsWith('--')) throw new Error(`${name} 값이 필요하다`);
120
+ args.splice(index, 2);
121
+ return value;
122
+ };
123
+ const version = option('--version');
124
+ const out = option('--out');
125
+ if (!version || args.length) throw new Error('Usage: bun scripts/marketing/generate-ax-brief.ts --version <v> [--out <file>]');
126
+ const { listChecklist } = await import('../../src/release-loop/checklist.js');
127
+ const { listSchedules } = await import('../../src/release-loop/release-schedule.js');
128
+ const { queryMergedRunLedgers } = await import('../../src/self-implement/run-ledger.js');
129
+ const { releaseLedgerRoot } = await import('../../src/instance/resolve.js');
130
+ const root = releaseLedgerRoot();
131
+ const checklist = (v: string) => listChecklist(v, root).items;
132
+ const body = generateAxBrief(version, {
133
+ featureMap: () => generateFeatureMap({ map: readFileSync(MAP, 'utf8'), items: checklist(version), registry: FEATURE_MATURITY, version }),
134
+ checklist,
135
+ schedules: () => listSchedules(root),
136
+ merges: () => queryMergedRunLedgers(),
137
+ });
138
+ if (out) writeFileSync(out, body);
139
+ else process.stdout.write(body);
140
+ }