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
@@ -39,6 +39,7 @@ import { formatDoctorLocalLlm, probeDoctorLocalLlm, type DoctorLocalLlm } from '
39
39
  import type { LlmInventory } from '../llm/local-manager/types.js';
40
40
  import { buildDoctorBundle } from './doctor-bundle.js';
41
41
  import { uploadDoctorBundle } from './doctor-bundle-upload.js';
42
+ import { checkNodeModules, formatNodeModulesCheck, nodeModulesTrees } from './doctor-node-modules.js';
42
43
 
43
44
  function defaultCheckPythonEnv(): { status: 'ok' | 'fixable' | 'manual'; evidence: string; remedy?: string } {
44
45
  const c = checkPythonEnv(false);
@@ -1216,9 +1217,30 @@ export function registerDoctorCommand(program: Command, deps: DoctorCliDeps = {}
1216
1217
  const out = deps.out ?? { log: (value: string) => console.log(value) };
1217
1218
  const err = deps.err ?? { error: (value: string) => console.error(value) };
1218
1219
  const setExitCode = deps.setExitCode ?? ((code: number) => { process.exitCode = code; });
1219
- program.command('doctor')
1220
+ const doctor = program.command('doctor')
1220
1221
  .description('Reports whether credentials resolve and where each resolution comes from')
1222
+ .option('--json', 'structured output');
1223
+ doctor.command('node-modules')
1224
+ .description('Check installed top-level packages against bun.lock (read-only)')
1225
+ .option('--dir <tree>', 'tree to check', process.cwd())
1226
+ .option('--all', 'also check nested package.json trees')
1221
1227
  .option('--json', 'structured output')
1228
+ .action((opts: { dir: string; all?: boolean; json?: boolean }, command: Command) => {
1229
+ const json = opts.json || command.parent?.opts().json === true;
1230
+ let dirs: string[];
1231
+ try { dirs = opts.all ? nodeModulesTrees(opts.dir) : [resolve(opts.dir)]; }
1232
+ catch (error) {
1233
+ const reason = error instanceof Error ? error.message : String(error);
1234
+ if (json) out.log(JSON.stringify({ status: 'unreadable', reason }));
1235
+ else out.log(`unreadable: ${reason}`);
1236
+ setExitCode(1);
1237
+ return;
1238
+ }
1239
+ const checks = dirs.map(checkNodeModules);
1240
+ out.log(json ? JSON.stringify(opts.all ? checks : checks[0], null, 2) : checks.map(formatNodeModulesCheck).join('\n'));
1241
+ if (checks.some((check) => check.status !== 'ok')) setExitCode(1);
1242
+ });
1243
+ doctor
1222
1244
  .option('--credentials', 'show per-credential details in human-readable output')
1223
1245
  .option('--bundle', 'write a redacted diagnostics archive for support')
1224
1246
  .option('--upload', 'send the bundle to diagnostics.uploadUrl after consent (requires --bundle)')
@@ -0,0 +1,147 @@
1
+ import { lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
2
+ import { dirname, join, relative, resolve, sep } from 'node:path';
3
+ import { debug } from '../debug/log.js';
4
+
5
+ export interface NodeModulesCheck {
6
+ dir: string;
7
+ status: 'ok' | 'issues' | 'unreadable';
8
+ missing: string[];
9
+ mismatch: { name: string; expected: string; actual: string }[];
10
+ borrowed: { target: string; tree: string } | null;
11
+ reason?: string;
12
+ }
13
+
14
+ function record(value: unknown): value is Record<string, unknown> {
15
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
16
+ }
17
+
18
+ function loadJson(path: string): unknown {
19
+ return JSON.parse(readFileSync(path, 'utf8')) as unknown;
20
+ }
21
+
22
+ function dependencyNames(pkg: Record<string, unknown>): string[] {
23
+ if (!record(pkg.dependencies ?? {}) || !record(pkg.devDependencies ?? {})) throw new Error('invalid package.json dependencies');
24
+ return [...new Set([...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})])].sort();
25
+ }
26
+
27
+ interface LockContext { lockRoot: string; workspace: Record<string, unknown>; packages: Record<string, unknown>; scope: string | null }
28
+
29
+ /** Own bun.lock first; otherwise the nearest ancestor bun.lock whose workspaces list this folder (root-lock Bun workspaces). */
30
+ function findLock(root: string, packageName: string | null): LockContext {
31
+ let current = root;
32
+ for (;;) {
33
+ let text: string | null = null;
34
+ try { text = readFileSync(join(current, 'bun.lock'), 'utf8'); }
35
+ catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
36
+ if (text !== null) {
37
+ const lock = Bun.JSONC.parse(text) as unknown;
38
+ if (!record(lock) || !record(lock.packages) || !record(lock.workspaces)) throw new Error('invalid bun.lock');
39
+ const key = relative(current, root).split(sep).join('/');
40
+ const workspace = lock.workspaces[key];
41
+ if (record(workspace)) return { lockRoot: current, workspace, packages: lock.packages, scope: key === '' ? null : (packageName ?? key) };
42
+ if (key === '') throw new Error('invalid bun.lock');
43
+ throw new Error(`bun.lock at ${current} does not list workspace ${key}`);
44
+ }
45
+ const parent = dirname(current);
46
+ if (parent === current) throw new Error('no bun.lock in this folder or any parent workspace root');
47
+ current = parent;
48
+ }
49
+ }
50
+
51
+ function lockedVersion(packages: Record<string, unknown>, name: string, scope: string | null = null): string {
52
+ // Bun keys a workspace-specific resolution as «<workspace>/<dep>» when it differs from the hoisted one.
53
+ const scoped = scope ? packages[`${scope}/${name}`] : undefined;
54
+ const entry = Array.isArray(scoped) ? scoped : packages[name];
55
+ if (!Array.isArray(entry) || typeof entry[0] !== 'string' || !entry[0].startsWith(`${name}@`) || entry[0].length <= name.length + 1) {
56
+ throw new Error(`bun.lock has no resolved version for ${name}`);
57
+ }
58
+ return entry[0].slice(name.length + 1);
59
+ }
60
+
61
+ export function checkNodeModules(dir: string): NodeModulesCheck {
62
+ const root = resolve(dir);
63
+ const result: NodeModulesCheck = { dir: root, status: 'ok', missing: [], mismatch: [], borrowed: null };
64
+ try {
65
+ const pkg = loadJson(join(root, 'package.json'));
66
+ if (!record(pkg)) throw new Error('invalid package.json');
67
+ const names = dependencyNames(pkg);
68
+ const lock = findLock(root, typeof pkg.name === 'string' ? pkg.name : null);
69
+ const declared = dependencyNames(lock.workspace);
70
+ for (const name of names) {
71
+ if (!declared.includes(name)) throw new Error(`bun.lock does not declare ${name}`);
72
+ }
73
+ const versions = new Map(names.map((name) => [name, lockedVersion(lock.packages, name, lock.scope)]));
74
+ // A workspace without its own node_modules resolves hoisted packages from the lock root.
75
+ const own = join(root, 'node_modules');
76
+ const modules = lock.lockRoot !== root && !lstatSync(own, { throwIfNoEntry: false }) ? join(lock.lockRoot, 'node_modules') : own;
77
+ try {
78
+ const entry = lstatSync(modules);
79
+ if (entry.isSymbolicLink()) {
80
+ const target = realpathSync(modules);
81
+ if (!lstatSync(target).isDirectory()) throw new Error('node_modules symlink target is not a directory');
82
+ result.borrowed = { target, tree: dirname(target) };
83
+ } else if (!entry.isDirectory()) {
84
+ throw new Error('node_modules is not a directory');
85
+ }
86
+ } catch (error) {
87
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
88
+ // A dangling symlink has a different cause from an absent node_modules directory.
89
+ if (lstatSync(modules, { throwIfNoEntry: false })?.isSymbolicLink()) throw new Error('node_modules symlink target is missing');
90
+ result.missing.push(...names);
91
+ if (names.length) result.status = 'issues';
92
+ return result;
93
+ }
94
+ for (const name of names) {
95
+ let installed: unknown;
96
+ const candidates = [...new Set([modules, join(lock.lockRoot, 'node_modules')])];
97
+ for (const base of candidates) {
98
+ try {
99
+ installed = loadJson(join(base, name, 'package.json'));
100
+ break;
101
+ } catch (error) {
102
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
103
+ }
104
+ }
105
+ if (installed === undefined) {
106
+ result.missing.push(name);
107
+ continue;
108
+ }
109
+ if (!record(installed) || typeof installed.version !== 'string') throw new Error(`invalid node_modules/${name}/package.json`);
110
+ const expected = versions.get(name)!;
111
+ if (installed.version !== expected) result.mismatch.push({ name, expected, actual: installed.version });
112
+ }
113
+ if (result.missing.length || result.mismatch.length) result.status = 'issues';
114
+ } catch (error) {
115
+ result.status = 'unreadable';
116
+ result.reason = error instanceof Error ? error.message : String(error);
117
+ } finally {
118
+ debug.log('doctor.node-modules', 'checked', { dir: root, missing: result.missing, mismatch: result.mismatch, borrowed: result.borrowed });
119
+ }
120
+ return result;
121
+ }
122
+
123
+ export function nodeModulesTrees(dir: string): string[] {
124
+ const root = resolve(dir);
125
+ const found = [root];
126
+ const skip = new Set(['node_modules', '.git', '.next', 'dist', 'build', 'out', '.elanous-test']);
127
+ function visit(current: string): void {
128
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
129
+ if (!entry.isDirectory() || skip.has(entry.name) || entry.name.startsWith('.')) continue;
130
+ const child = join(current, entry.name);
131
+ const children = readdirSync(child);
132
+ if (children.includes('package.json')) found.push(child);
133
+ visit(child);
134
+ }
135
+ }
136
+ visit(root);
137
+ return found;
138
+ }
139
+
140
+ export function formatNodeModulesCheck(result: NodeModulesCheck): string {
141
+ const lines = [`${result.dir}: ${result.status === 'ok' ? '정상' : result.status === 'unreadable' ? '못 읽음' : '결손'}`];
142
+ if (result.borrowed) lines.push(` 빌린 트리: ${result.borrowed.tree} (node_modules → ${result.borrowed.target})`);
143
+ if (result.missing.length) lines.push(` 빠짐 ${result.missing.length}: ${result.missing.join(', ')}`);
144
+ if (result.mismatch.length) lines.push(` 버전 다름 ${result.mismatch.length}: ${result.mismatch.map(({ name, expected, actual }) => `${name} (${actual} != ${expected})`).join(', ')}`);
145
+ if (result.reason) lines.push(` 이유: ${result.reason}`);
146
+ return lines.join('\n');
147
+ }
@@ -21,6 +21,13 @@ export interface IntakeToTasksCliDeps {
21
21
  llm?: (input: IntakeTaskInput) => Promise<string>;
22
22
  }
23
23
 
24
+ export interface IntakeDigestCliDeps {
25
+ root?: string;
26
+ annotateLens?: (root: string, day: string) => Promise<unknown>;
27
+ buildDigest?: typeof import('../intake-plane/digest.js').buildIntakeDigest;
28
+ sendTelegram?: (text: string) => Promise<boolean>;
29
+ }
30
+
24
31
  export function intakeToTasksMessages(content: string) {
25
32
  return [
26
33
  { role: 'system' as const, content: 'Interpret the supplied goal line or idea note into up to three actionable tasks. Respond only with JSON: {"tasks":[{"type":"implement|research|document|operate","title":"...","description":"...","priority":"low|medium|high","acceptanceCriteria":["..."]}],"questions":["..."]}. Each task needs a verifiable acceptance criterion; put unresolved requests in questions instead. Do not invent facts. Limit each title to 80 characters and description to 4000 characters.' },
@@ -76,6 +83,44 @@ export async function runIntakeToTasksCli(
76
83
  return runIntakeToTasks(deps.root, opts, { llm, post });
77
84
  }
78
85
 
86
+ /** `--telegram` 다이제스트. 렌즈는 digest 를 만들기 전에 돈다. 렌즈 실패는 보고를 막지 않는다. */
87
+ export async function runIntakeDigestCli(
88
+ opts: { day?: string; json?: boolean; telegram?: boolean; vault?: string; note?: string },
89
+ deps: IntakeDigestCliDeps = {},
90
+ ): Promise<{ sent?: boolean; empty?: boolean }> {
91
+ const { buildIntakeDigest, renderDigestMarkdown, renderDigestTelegram } = await import('../intake-plane/digest.js');
92
+ const { effectiveInstanceRoot } = await import('../instance/resolve.js');
93
+ const root = deps.root ?? effectiveInstanceRoot();
94
+ const day = opts.day ?? new Date(Date.now() + 9 * 3600_000).toISOString().slice(0, 10);
95
+ if (opts.telegram) {
96
+ const annotate = deps.annotateLens ?? (async (lensRoot, lensDay) => {
97
+ const { annotateIntakeLens } = await import('../intake-plane/lens.js');
98
+ return annotateIntakeLens(lensRoot, lensDay);
99
+ });
100
+ try { await annotate(root, day); }
101
+ catch (error) { debug.log('intake.lens', 'annotate-failed', { day, reason: error instanceof Error ? error.message : String(error) }); }
102
+ }
103
+ const d = (deps.buildDigest ?? buildIntakeDigest)(root, day);
104
+ if (opts.telegram) {
105
+ if (!d.absorbed.length) { console.log(`텔레그램: ${day} 흡수 0 — 보내지 않음`); return { empty: true }; }
106
+ const text = renderDigestTelegram(d, { ...(opts.vault ? { vaultRoot: opts.vault } : {}), ...(opts.note ? { notePath: opts.note } : {}) });
107
+ const sent = deps.sendTelegram
108
+ ? await deps.sendTelegram(text)
109
+ : await (async () => {
110
+ const { sendTelegramReport } = await import('../telegram-report.js');
111
+ const { getUserConfig } = await import('../user-config.js');
112
+ return sendTelegramReport(getUserConfig(), text, { markdown: true, kind: 'intake' });
113
+ })();
114
+ debug.log('intake.digest', 'telegram', { day, absorbed: d.absorbed.length, goals: d.goals.length, sent });
115
+ console.log(sent ? `텔레그램 보고 채널로 보냈다 (${day} · 흡수 ${d.absorbed.length} · 골 후보 ${d.goals.length})` : '텔레그램 보고 채널 설정이 없다(telegram.reportChannel) — 보내지 않음');
116
+ if (!sent) process.exitCode = 3;
117
+ return { sent };
118
+ }
119
+ if (opts.json) { await writeStdoutFully(JSON.stringify(d, null, 2)); return {}; }
120
+ await writeStdoutFully(renderDigestMarkdown(d));
121
+ return {};
122
+ }
123
+
79
124
  export function resolveIntakeCheckRoot(
80
125
  cwd: string,
81
126
  git: typeof runGitCommand = runGitCommand,
@@ -283,23 +328,7 @@ export function registerIntakeCommands(program: Command): void {
283
328
  .option('--vault <root>', '옵시디언 볼트 뿌리 — 텔레그램 판에 노트 열기 주소를 싣는다')
284
329
  .option('--note <path>', '열기 주소가 가리킬 노트(그날 트렌드 노트)')
285
330
  .action(async (opts: { day?: string; json?: boolean; telegram?: boolean; vault?: string; note?: string }) => {
286
- const { buildIntakeDigest, renderDigestMarkdown, renderDigestTelegram } = await import('../intake-plane/digest.js');
287
- const { effectiveInstanceRoot } = await import('../instance/resolve.js');
288
- const day = opts.day ?? new Date(Date.now() + 9 * 3600_000).toISOString().slice(0, 10);
289
- const d = buildIntakeDigest(effectiveInstanceRoot(), day);
290
- if (opts.telegram) {
291
- if (!d.absorbed.length) { console.log(`텔레그램: ${day} 흡수 0 — 보내지 않음`); return; }
292
- const { sendTelegramReport } = await import('../telegram-report.js');
293
- const { getUserConfig } = await import('../user-config.js');
294
- const text = renderDigestTelegram(d, { ...(opts.vault ? { vaultRoot: opts.vault } : {}), ...(opts.note ? { notePath: opts.note } : {}) });
295
- const sent = await sendTelegramReport(getUserConfig(), text, { markdown: true, kind: 'intake' });
296
- debug.log('intake.digest', 'telegram', { day, absorbed: d.absorbed.length, goals: d.goals.length, sent });
297
- console.log(sent ? `텔레그램 보고 채널로 보냈다 (${day} · 흡수 ${d.absorbed.length} · 골 후보 ${d.goals.length})` : '텔레그램 보고 채널 설정이 없다(telegram.reportChannel) — 보내지 않음');
298
- if (!sent) process.exitCode = 3;
299
- return;
300
- }
301
- if (opts.json) { await writeStdoutFully(JSON.stringify(d, null, 2)); return; }
302
- await writeStdoutFully(renderDigestMarkdown(d));
331
+ await runIntakeDigestCli(opts);
303
332
  });
304
333
 
305
334
  intakeCmd
@@ -0,0 +1,20 @@
1
+ import type { Command } from 'commander';
2
+ import { knowFind, type KnowFindDeps } from '../knowledge/know-find.js';
3
+ import { writeStdoutJson } from './stdout-json.js';
4
+
5
+ export function registerKnowCommand(program: Command, deps: Partial<KnowFindDeps> = {}, out: Pick<Console, 'log' | 'error'> = console): void {
6
+ program.command('know <words...>').description('대표 지시 · 결정 · 판 칸 · 교훈을 한 질문으로 검색')
7
+ .option('--json', 'JSON 출력').option('--limit <N>', '최대 결과 수', '20')
8
+ .action(async (words: string[], opts: { json?: boolean; limit: string }) => {
9
+ const limit = Number(opts.limit);
10
+ if (!Number.isSafeInteger(limit) || limit < 1) throw new Error('--limit must be a positive integer');
11
+ const result = knowFind(words.join(' '), deps);
12
+ const rows = result.rows.slice(0, limit);
13
+ if (opts.json) {
14
+ await writeStdoutJson(JSON.stringify({ rows, unavailable: result.unavailable }) + '\n');
15
+ return;
16
+ }
17
+ for (const row of rows) out.log(`[${row.source}] ${row.id} · ${row.title} · ${row.status}${row.current ? ' · (지금 유효)' : ''}`);
18
+ for (const item of result.unavailable) out.error(`[${item.source}] 못 읽음 · ${item.reason}`);
19
+ });
20
+ }
@@ -715,7 +715,7 @@ export function podObservationGapNotice(
715
715
  .some((prefix) => category.startsWith(prefix));
716
716
  const categories = [...(query.categories ?? []), ...(query.exactCategories ?? [])];
717
717
  if (!categories.some(childCategory) && !(categories.length === 0 && (query.events?.length ?? 0) > 0)) return null;
718
- return `안내: 이 창에 Pod 에서 돈 런 ${podRuns}개 — Pod 자식의 관측은 이 logs.db 에 수집되지 않는다(POD-OBS). 원장으로 본다: elanous self run-ledger <runId>`;
718
+ return `안내: 이 창에 Pod 에서 돈 런 ${podRuns}개 — Pod 자식의 관측은 «발사한 트리의 파생 우주» logs.db 로 회수된다(POD-OBS) — 전체: elanous logs --all --include-test · 런 하나: elanous self run-ledger <runId>`;
719
719
  }
720
720
 
721
721
  /** Select ledger roots independently of DB existence; --all includes registered roots with no logs.db. */
@@ -1038,6 +1038,24 @@ export function otherInstanceHint(matches: readonly { name: string; count: numbe
1038
1038
  return ` ↳ 다른 인스턴스에는 있다 — ${found}\n 전체를 보려면: elanous logs --all --include-test${filters ? ` ${filters}` : ''}`;
1039
1039
  }
1040
1040
 
1041
+ /** POD-OBS(10-06): 결과가 «0이 아닌데 적을 때»도 조용하지 않게 — 다른 우주에 더 많으면 경고한다.
1042
+ * 실측: 24h `pre-pr-sync` 운영 1건 vs 발사 트리 파생 우주 276건(Pod 자식 재방출은 부모 하니스의 우주에 쌓인다). */
1043
+ export function otherInstanceMajorityWarning(
1044
+ shown: number,
1045
+ matches: readonly { name: string; count: number }[],
1046
+ opts: LogsCliOpts,
1047
+ ): string | null {
1048
+ const elsewhere = matches.reduce((sum, match) => sum + match.count, 0);
1049
+ if (shown <= 0 || elsewhere <= shown) return null;
1050
+ const top = [...matches].sort((a, b) => b.count - a.count);
1051
+ const named = top.slice(0, 3).map((match) => `${match.name} ${match.count}건`).join(' · ');
1052
+ const more = top.length > 3 ? ` 외 ${top.length - 3}곳` : '';
1053
+ const filters = requeryFilterArgs(opts);
1054
+ return `⚠ 여기 ${shown}건 · 다른 우주에 ${elsewhere}건이 더 있다 — ${named}${more}\n`
1055
+ + ' (Pod 자식 관측은 발사한 트리의 파생 우주에 쌓인다 · 이 수만 보고 «적다»로 읽지 말 것)\n'
1056
+ + ` 전체를 보려면: elanous logs --all --include-test${filters ? ` ${filters}` : ''}`;
1057
+ }
1058
+
1041
1059
  export function buildQuery(opts: LogsCliOpts): { query: LogQuery; error?: string } {
1042
1060
  const q: LogQuery = {};
1043
1061
  if (opts.level) {
@@ -1789,6 +1807,30 @@ export async function runLogsCli(opts: LogsCliOpts, deps: RunLogsCliDeps = {}):
1789
1807
  }
1790
1808
  const coverageHint = runCoverageHint(out.map(({ row }) => row));
1791
1809
  if (coverageHint) console.error(coverageHint);
1810
+ // 좁힌 조회(이벤트·카테고리)만 — 넓은 조회까지 매번 전 우주를 열지 않는다.
1811
+ if (out.length > 0 && ((query.events?.length ?? 0) > 0 || (query.categories?.length ?? 0) > 0
1812
+ || (query.exactCategories?.length ?? 0) > 0)) {
1813
+ const matches = probeOtherInstanceMatches(opts, query, resolved.targets.map((target) => target.dbPath));
1814
+ // 표시 상한(--limit)이 아니라 «여기» 실제 일치 수와 비교한다(상한 100 을 «100건»으로 말하지 않는다).
1815
+ let here: number | null = null;
1816
+ try {
1817
+ here = Math.max(out.length, (deps.countMatching ?? ((candidate: LogQuery) => opened.reduce(
1818
+ (count, { store }) => count + store.countMatching(candidate), 0)))(query));
1819
+ } catch (error) {
1820
+ // 셈을 못 했으면 경고하지 않는다 — 잘린 표시 수를 «여기 N건»으로 말하지 않는다.
1821
+ debug.log('logs.cli', 'other-instance-count-failed', { shown: out.length, error: error instanceof Error ? error.message : String(error) });
1822
+ }
1823
+ const majority = here === null ? null : otherInstanceMajorityWarning(here, matches, opts);
1824
+ if (majority) {
1825
+ console.error(majority);
1826
+ debug.log('logs.cli', 'other-instance-majority', {
1827
+ shown: out.length,
1828
+ here,
1829
+ elsewhere: matches.reduce((sum, match) => sum + match.count, 0),
1830
+ instances: matches.length,
1831
+ });
1832
+ }
1833
+ }
1792
1834
  if (out.length === 0) {
1793
1835
  console.error('(일치하는 로그 없음)');
1794
1836
  const instanceHint = otherInstanceHint(
@@ -94,6 +94,7 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
94
94
  const mem = (v: number | null) => v === null ? '?' : `${(v / 1024 ** 3).toFixed(1)}Gi`;
95
95
  io.log(`${m.context} | ${m.capacity} | ${m.running ?? '?'} | ${m.pending ?? '?'} | ${mem(m.memoryLimitBytes)}/${mem(m.allocatableMemoryBytes)} | ${m.allocatableCpuMillicores === null ? '?' : m.allocatableCpuMillicores / 1000}${m.reason ? ` (${m.reason})` : ''}`);
96
96
  }
97
+ io.log(`실측 점유: ${measure.members.map((m) => `${m.context} ${m.running === null || m.pending === null ? '못 쟀다' : m.running + m.pending}/${m.capacity}`).join(' · ') || '?'}`);
97
98
  io.log(`권장 지금 ${decision.recommended ?? '?'} 개 더 (limitedBy=${decision.limitedBy ?? 'unknown'})${decision.reason ? ` · ${decision.reason}` : ''}`);
98
99
  io.log(`예약(저작 중) ${host.reserved} · 대기 Job ${waitingJobs ?? '?'} · 실행 ${decision.running ?? '?'}`);
99
100
  io.log(`임대 없는 실행 ${decision.unleasedRunning ?? '?'}`);
@@ -155,6 +156,7 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
155
156
  });
156
157
  if (result.image) io.error(`[pod] image ${result.image} · job ${result.job}`);
157
158
  io.log(result.artifactsDir);
159
+ if (result.artifacts?.error) io.error(`산출 회수 못 함: ${result.artifacts.error}`);
158
160
  io.exit(result.exitCode);
159
161
  } catch (err) {
160
162
  io.error(err instanceof Error ? err.message : String(err));
package/src/cli/pr-cli.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync } from 'node:fs';
1
+ import { existsSync, readFileSync, statSync } from 'node:fs';
2
2
  import { basename, join, relative, resolve } from 'node:path';
3
3
  import { createInterface } from 'node:readline';
4
4
  import type { Command } from 'commander';
@@ -19,6 +19,7 @@ import { runModelHardcodeGate } from '../../scripts/ci-model-hardcode-gate.js';
19
19
  import { runDaemonPortGate } from '../../scripts/ci-daemon-port-gate.js';
20
20
  import { runPublicLeakGate } from '../../scripts/ci-public-leak-gate.js';
21
21
  import { exportLeakCheck, publicExportChangedFiles, type ExportLeakCheck } from '../../scripts/ci-public-export-leak-gate.js';
22
+ import { exportImportCheck, type ExportImportCheck } from '../../scripts/ci-public-export-import-gate.js';
22
23
  import { checkCommands, extractElanousCommands, type Finding as DocsCliFinding } from '../../scripts/docs-cli-check.js';
23
24
  import { runTestInterferenceGate } from '../../scripts/ci-test-interference-gate.js';
24
25
  import { isGoalDocumentFileName } from '../self-implement/goal-document.js';
@@ -111,6 +112,8 @@ export interface PrLandDeps {
111
112
  runPublicLeakGate?: (changedFiles: readonly string[], out: { log: (message: string) => void; error: (message: string) => void }) => number;
112
113
  /** Test seam — the post-transform export leak check on changed files. */
113
114
  runExportLeakCheck?: (changedFiles: readonly string[], cwd: string) => ExportLeakCheck;
115
+ /** Test seam — included files must not import a module the public export excludes. */
116
+ runExportImportCheck?: (changedFiles: readonly string[], cwd: string) => ExportImportCheck | Promise<ExportImportCheck>;
114
117
  /** 공개 문서의 `elanous …` 호출 ↔ 실제 `--help` 대조(경고 전용) — 바뀐 공개 문서 경로를 받아 어긋남 목록을 돌려준다. */
115
118
  runDocsCliCheck?: (docFiles: readonly string[]) => DocsCliFinding[];
116
119
  /** 변경 시험 파일 간 간섭 검사 심(시험 주입용). */
@@ -231,14 +234,14 @@ export async function decideOverlapLanding(input: {
231
234
 
232
235
  const liveOut = { log: (message: string) => console.log(message), error: (message: string) => console.error(message) };
233
236
 
234
- function runPrLandTypecheckGate(out: { log: (message: string) => void; error: (message: string) => void }): boolean {
237
+ function runPrLandTypecheckGate(out: { log: (message: string) => void; error: (message: string) => void }, cwd: string = process.cwd()): boolean {
235
238
  let failed = false;
236
239
  const exit = ((code: number): never => {
237
240
  failed = code !== 0;
238
241
  throw new Error(`ci-typecheck-changed exited ${code}`);
239
242
  });
240
243
  try {
241
- runChangedTypecheckGate({ log: out.log, warn: out.error, error: out.error, exit });
244
+ runChangedTypecheckGate({ log: out.log, warn: out.error, error: out.error, exit, cwd });
242
245
  } catch (error) {
243
246
  if (!failed) throw error;
244
247
  }
@@ -290,7 +293,67 @@ export function runPrLandExportLeakCheck(changedFiles: readonly string[], cwd: s
290
293
  return exportLeakCheck(changedFiles, top.status === 0 && String(top.stdout ?? '').trim() ? String(top.stdout).trim() : cwd);
291
294
  }
292
295
 
296
+ /** Included files importing an excluded module — scripts/ci-public-export-import-gate.ts. cwd is the tree being landed. */
297
+ export function runPrLandExportImportCheck(changedFiles: readonly string[], cwd: string): Promise<ExportImportCheck> {
298
+ const top = runGitCommand(cwd, ['rev-parse', '--show-toplevel'], { encoding: 'utf-8' });
299
+ return exportImportCheck(changedFiles, top.status === 0 && String(top.stdout ?? '').trim() ? String(top.stdout).trim() : cwd);
300
+ }
301
+
302
+ /** Blocks the landing when a changed public file imports a module the public export excludes. */
303
+ export async function exportImportBlock(
304
+ changedFiles: readonly string[] | undefined,
305
+ check: () => ExportImportCheck | Promise<ExportImportCheck>,
306
+ out: { log: (message: string) => void; error: (message: string) => void },
307
+ ): Promise<boolean> {
308
+ if (!changedFiles || changedFiles.length === 0) return true;
309
+ let result: ExportImportCheck;
310
+ try { result = await check(); } catch { result = { measured: false, hits: [], unseen: 0, detail: '검사 실행 실패' }; }
311
+ const blocked = !result.measured || result.hits.length > 0;
312
+ record('public-export-import', !blocked, { measured: result.measured, hits: result.hits.length, unseen: result.unseen });
313
+ debug.log('pr.land', 'import-gate', { blocked, hits: result.hits.map(({ file, target }) => ({ file, target })), measured: result.measured, unseen: result.unseen });
314
+ if (!result.measured) {
315
+ out.error(`✗ public-export-import: 못 쟀다 — 막는다(${result.detail ?? '?'})`);
316
+ return false;
317
+ }
318
+ if (result.unseen > 0) out.log(`⚠ public-export-import: 비리터럴 지정자 ${result.unseen}곳은 못 봄`);
319
+ if (result.hits.length === 0) {
320
+ out.log('✓ public-export-import: 바뀐 공개 파일이 공개 내보내기에서 제외된 모듈을 import 하지 않는다.');
321
+ return true;
322
+ }
323
+ out.error(`✗ public-export-import: 공개 파일이 공개 내보내기에서 제외된 모듈을 import 한다 (${result.hits.length}곳) — 공개본 빌드가 여기서 깨진다:`);
324
+ for (const hit of result.hits.slice(0, 20)) out.error(` ${hit.file}:${hit.line} → ${hit.target}`);
325
+ if (result.hits.length > 20) out.error(` … 외 ${result.hits.length - 20}곳`);
326
+ return false;
327
+ }
328
+
293
329
  /** Blocks changed public files that leak or cannot be measured. */
330
+ /**
331
+ * MARKER-GUARD — a changed file that still holds merge-conflict markers is refused before landing.
332
+ * A file counts only when it has both a `<<<<<<< ` and a `>>>>>>> ` line (`=======` alone is a Markdown underline).
333
+ * 10-06 02:4x: a hand landing put three marker lines into release/next.md on main (hotfix #24333).
334
+ */
335
+ export function conflictMarkerFiles(changedFiles: readonly string[] | undefined, cwd: string, read: (path: string) => string | undefined = (path) => {
336
+ // No size cutoff: a large file with markers must still be refused (ACP must-fix). A deleted file reads as undefined.
337
+ try { const full = resolve(cwd, path); return existsSync(full) && statSync(full).isFile() ? readFileSync(full, 'utf8') : undefined; }
338
+ catch { return undefined; }
339
+ }): string[] {
340
+ return (changedFiles ?? []).filter((path) => {
341
+ const text = read(path);
342
+ return text !== undefined && /^<{7} /m.test(text) && /^>{7} /m.test(text);
343
+ });
344
+ }
345
+
346
+ export function conflictMarkerBlock(changedFiles: readonly string[] | undefined, cwd: string,
347
+ out: { log: (message: string) => void; error: (message: string) => void }, read?: (path: string) => string | undefined): boolean {
348
+ const hits = read ? conflictMarkerFiles(changedFiles, cwd, read) : conflictMarkerFiles(changedFiles, cwd);
349
+ if (hits.length) {
350
+ debug.log('pr.land', 'conflict-markers-blocked', { files: hits });
351
+ out.error(`✗ conflict-markers: 충돌 표시(<<<<<<< · >>>>>>>)가 남은 파일 ${hits.length}개 — ${hits.join(', ')} · 표시를 풀고 다시 착지하라`);
352
+ return false;
353
+ }
354
+ return true;
355
+ }
356
+
294
357
  export function exportLeakBlock(
295
358
  changedFiles: readonly string[] | undefined,
296
359
  check: () => ExportLeakCheck,
@@ -401,8 +464,10 @@ function runPrLandDocsCliCheck(root: string): (docFiles: readonly string[]) => D
401
464
  return (docFiles) => checkCommands(docFiles.flatMap((f) => extractElanousCommands(f, readFileSync(join(root, f), 'utf8'))));
402
465
  }
403
466
 
404
- function runPrLandMockModuleRestoreGate(out: { log: (message: string) => void; error: (message: string) => void }): boolean {
405
- return runMockModuleRestoreGate({ args: [], log: out.log, error: out.error }) === 0;
467
+ function runPrLandMockModuleRestoreGate(out: { log: (message: string) => void; error: (message: string) => void }, cwd: string = process.cwd()): boolean {
468
+ const top = runGitCommand(cwd, ['rev-parse', '--show-toplevel'], { encoding: 'utf-8', env: { ...process.env, GIT_DIR: undefined, GIT_WORK_TREE: undefined, GIT_COMMON_DIR: undefined } as NodeJS.ProcessEnv });
469
+ const root = top.status === 0 && String(top.stdout ?? '').trim() ? String(top.stdout).trim() : cwd;
470
+ return runMockModuleRestoreGate({ args: [], cwd: root, log: out.log, error: out.error }) === 0;
406
471
  }
407
472
 
408
473
  // 🚨 안드로이드 축 — `bun test` 우주 «밖»이라 다른 게이트가 원리상 못 본다.
@@ -415,8 +480,9 @@ function runPrLandMockModuleRestoreGate(out: { log: (message: string) => void; e
415
480
  function runPrLandAndroidGate(
416
481
  out: { log: (message: string) => void; error: (message: string) => void },
417
482
  changedFiles: readonly string[] = [],
483
+ cwd: string = process.cwd(),
418
484
  ): boolean {
419
- return runAndroidUnitTestGate({ args: ['--changed-files', ...changedFiles], log: out.log, error: out.error }) === 0;
485
+ return runAndroidUnitTestGate({ args: ['--changed-files', ...changedFiles], cwd, log: out.log, error: out.error }) === 0;
420
486
  }
421
487
 
422
488
  /** ⛔ 보호는 «호출부»에 둔다 — 주입된 게이트가 던져도 같은 계약이 서야 한다.
@@ -1320,7 +1386,7 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
1320
1386
  }
1321
1387
  }
1322
1388
 
1323
- const typecheckOk = (deps.runTypecheckGate ?? runPrLandTypecheckGate)(out);
1389
+ const typecheckOk = (deps.runTypecheckGate ?? ((o) => runPrLandTypecheckGate(o, cwd)))(out);
1324
1390
  record('typecheck', typecheckOk);
1325
1391
  if (!typecheckOk) {
1326
1392
  // ⛔⭐ 「왜 막혔나」를 «단정하지 않는다» — 이 게이트는 불리언만 돌려주므로 여기서는 이유를 모른다.
@@ -1347,7 +1413,7 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
1347
1413
  out.log('✓ isolation-gate: scripts/ci-isolation-hardcode-gate.ts PASS — no new homedir+.elanous hardcoding.');
1348
1414
  }
1349
1415
 
1350
- const mockModuleRestore = gateVerdict('mock-module-restore-gate', deps.runMockModuleRestoreGate ?? runPrLandMockModuleRestoreGate, out);
1416
+ const mockModuleRestore = gateVerdict('mock-module-restore-gate', deps.runMockModuleRestoreGate ?? ((o) => runPrLandMockModuleRestoreGate(o, cwd)), out);
1351
1417
  record('mock-module-restore-gate', mockModuleRestore.ok, { measured: mockModuleRestore.measured });
1352
1418
  if (!mockModuleRestore.ok) {
1353
1419
  out.error('✗ mock-module-restore-gate: scripts/ci-mock-module-restore-gate.ts blocked pr land because changed tests introduce mock.module without R-TST23 restoration.');
@@ -1361,7 +1427,7 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
1361
1427
  // 근본은 훈계가 아니라 ***분포***였다: 스크립트에 박힌 이름 중 «가장 흔한» 것이 ***낡은 것***이라
1362
1428
  // 옆 파일을 베끼면 낡은 이름을 물려받는다. ⇒ 이 자가 «신규»만 막는다(ratchet).
1363
1429
  // ⚠️ `.github/workflows` 가 없어 `pr land` 가 «유일한» 강제점이다.
1364
- const modelHardcode = gateVerdict('model-hardcode-gate', deps.runModelHardcodeGate ?? ((o) => runModelHardcodeGate({ log: o.log, error: o.error, args: [] }) === 0), out);
1430
+ const modelHardcode = gateVerdict('model-hardcode-gate', deps.runModelHardcodeGate ?? ((o) => runModelHardcodeGate({ log: o.log, error: o.error, args: [], cwd }) === 0), out);
1365
1431
  record('model-hardcode-gate', modelHardcode.ok, { measured: modelHardcode.measured });
1366
1432
  if (!modelHardcode.ok) {
1367
1433
  out.error('✗ model-hardcode-gate: scripts/ci-model-hardcode-gate.ts blocked pr land because changed files hardcode a model id — derive it from tierModel()/config instead.');
@@ -1382,15 +1448,17 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
1382
1448
 
1383
1449
  const changedPaths = currentChangePaths(run, cwd, staged, base, out);
1384
1450
  nextMdWarning(body ?? '', changedPaths, out);
1451
+ if (!conflictMarkerBlock(changedPaths, cwd, out)) return 1;
1385
1452
  publicLeakWarning(changedPaths, deps.runPublicLeakGate ?? ((files, sink) => runPrLandPublicLeakGate(files, sink, cwd, base)), out);
1386
1453
  if (!exportLeakBlock(changedPaths, () => (deps.runExportLeakCheck ?? runPrLandExportLeakCheck)(changedPaths ?? [], cwd), opts.allowPublicLeak === true, out, cwd)) return 1;
1454
+ if (!await exportImportBlock(changedPaths, () => (deps.runExportImportCheck ?? runPrLandExportImportCheck)(changedPaths ?? [], cwd), out)) return 1;
1387
1455
  if (publicDocPaths(changedPaths, () => true).length > 0) {
1388
1456
  const top = run('git', ['rev-parse', '--show-toplevel'], { cwd });
1389
1457
  const docsRoot = top.ok && top.out.trim() ? top.out.trim() : cwd;
1390
1458
  docsCliWarning(publicDocPaths(changedPaths, (f) => existsSync(join(docsRoot, f))), deps.runDocsCliCheck ?? runPrLandDocsCliCheck(docsRoot), out);
1391
1459
  }
1392
1460
  const testInterference = await testInterferenceGateVerdict(
1393
- deps.runTestInterferenceGate ?? ((o, files) => runTestInterferenceGate({ args: ['--changed-files', ...files], log: o.log })),
1461
+ deps.runTestInterferenceGate ?? ((o, files) => runTestInterferenceGate({ args: ['--changed-files', ...files], log: o.log, cwd })),
1394
1462
  changedPaths,
1395
1463
  out,
1396
1464
  );
@@ -1400,7 +1468,7 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
1400
1468
  'android-gate',
1401
1469
  deps.runAndroidGate
1402
1470
  ? (o) => deps.runAndroidGate!(o, changedPaths)
1403
- : (o) => runPrLandAndroidGate(o, changedPaths),
1471
+ : (o) => runPrLandAndroidGate(o, changedPaths, cwd),
1404
1472
  out,
1405
1473
  );
1406
1474
  record('android-gate', androidGate.ok, { measured: androidGate.measured });
@@ -1418,7 +1486,7 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
1418
1486
  'ios-gate',
1419
1487
  deps.runIosGate
1420
1488
  ? (o) => deps.runIosGate!(o, changedPaths)
1421
- : (o) => runIosUnitTestGate({ args: ['--changed-files', ...changedPaths], log: o.log, error: o.error }) === 0,
1489
+ : (o) => runIosUnitTestGate({ args: ['--changed-files', ...changedPaths], cwd, log: o.log, error: o.error }) === 0,
1422
1490
  out,
1423
1491
  );
1424
1492
  record('ios-gate', iosGate.ok, { measured: iosGate.measured });