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
@@ -7,13 +7,15 @@ import { effectiveInstanceRoot, prodInstanceRoot } from '../../src/instance/reso
7
7
  import { getUserConfig } from '../../src/user-config.js';
8
8
  import { loopEvent } from '../../src/loops/observe.js';
9
9
  import { sendOutbound } from '../../src/domains/outbound-alert.js';
10
- import { collectLandings, collectRelease, collectDecisions, type Landing, type Release, type Pending, type Section } from './daily.js';
10
+ import { listSchedules } from '../../src/release-loop/release-schedule.js';
11
+ import { listChecklist } from '../../src/release-loop/checklist.js';
12
+ import { collectLandings, collectDecisions, type Landing, type Release, type Pending, type Section } from './daily.js';
11
13
 
12
14
  type Issue = { id: string; title: string; owner: string; due: string; status: 'open' | 'done'; at: string; by: string };
13
15
  type WeeklyParts = { landings: Section<Landing[]>; release: Section<Release>; issues: Section<Issue[]>;
14
16
  followThrough: Section<{ issue: Issue; outcome: 'done' | '기한 뒤 완료' | '기한 넘김' | '진행'; doneAt?: string }[]>;
15
17
  decisions: Section<Pending[]>; news: Section<{ title: string; url: string }[]> };
16
- export type WeeklyDeps = { now?: () => Date; root?: string; vaultRoot?: string | null; sendEnabled?: boolean;
18
+ export type WeeklyDeps = { now?: () => Date; root?: string; vaultRoot?: string | null; sendEnabled?: boolean; repoRoot?: string; repoName?: string;
17
19
  landings?: (now: Date, window: { from: Date; to: Date }) => Promise<Landing[]>; release?: () => Promise<Release>;
18
20
  decisions?: () => Promise<Pending[]>; send?: (text: string, kind: string) => boolean;
19
21
  receipt?: (key: string) => 'sent' | 'not-sent' | 'unknown';
@@ -60,6 +62,20 @@ function appendIssue(root: string, row: Issue) {
60
62
  mkdirSync(dirname(file), { recursive: true });
61
63
  appendFileSync(file, `${JSON.stringify(row)}\n`);
62
64
  }
65
+ async function collectWeeklyRelease(now: Date, root: string): Promise<Release> {
66
+ if (!existsSync(join(root, 'release', 'features.sqlite'))) throw new Error('판 원장 없음');
67
+ const schedules = listSchedules(root).sort((a, b) => a.cutAt.localeCompare(b.cutAt));
68
+ const index = schedules.findIndex(schedule => Date.parse(schedule.cutAt) >= now.getTime());
69
+ if (index < 0) throw new Error('남은 판 일정 없음');
70
+ const current = schedules[index]!;
71
+ const items = listChecklist(current.version, root).items;
72
+ const next = schedules[index + 1];
73
+ const nextItems = next ? listChecklist(next.version, root).items : null;
74
+ return { version: current.version, green: items.filter(i => i.status === 'green').length, total: items.length,
75
+ nextVersion: next?.version ?? null, nextGreen: nextItems ? nextItems.filter(i => i.status === 'green').length : null,
76
+ nextTotal: nextItems?.length ?? null, cutAt: current.cutAt,
77
+ red: items.filter(i => i.status === 'red').map(i => ({ name: `${i.id} ${i.title}` })) };
78
+ }
63
79
  function readDailyNews(root: string, from: Date, to: Date): Section<{ title: string; url: string }[]> {
64
80
  const found = new Map<string, { title: string; url: string }>();
65
81
  const failures: string[] = [];
@@ -67,7 +83,7 @@ function readDailyNews(root: string, from: Date, to: Date): Section<{ title: str
67
83
  for (let at = from.getTime(); at < to.getTime(); at += DAY) {
68
84
  const day = kstDate(new Date(at));
69
85
  const file = join(root, 'rhythm', 'daily', `${day}.md`);
70
- if (!existsSync(file)) { failures.push(`${day} 데일리 없음`); continue; }
86
+ if (!existsSync(file)) continue;
71
87
  files++;
72
88
  try {
73
89
  const text = readFileSync(file, 'utf8');
@@ -83,7 +99,8 @@ function readDailyNews(root: string, from: Date, to: Date): Section<{ title: str
83
99
  } catch (e) { failures.push(`${day} ${reasonOf(e)}`); }
84
100
  }
85
101
  const news = [...found.values()].slice(0, 10);
86
- if (failures.length) return { status: 'unreadable', reason: `${files ? failures.join('; ') : '데일리 없음'}${news.length ? `\n부분 수집 (완전한 주간 집계 아님):\n${news.map(n => `- ${n.title} — ${n.url}`).join('\n')}` : ''}` };
102
+ if (!files) return { status: 'unreadable', reason: '데일리 없음' };
103
+ if (failures.length) return { status: 'unreadable', reason: `${failures.join('; ')}${news.length ? `\n부분 수집 (완전한 주간 집계 아님):\n${news.map(n => `- ${n.title} — ${n.url}`).join('\n')}` : ''}` };
87
104
  return { status: 'ok', value: news };
88
105
  }
89
106
  const labels: Record<keyof WeeklyParts, string> = {
@@ -133,8 +150,10 @@ function vaultCopy(markdown: string, key: string, deps: WeeklyDeps) {
133
150
  let root = deps.vaultRoot;
134
151
  if (root === undefined) {
135
152
  try {
136
- root = String(object(getUserConfig().raw.obsidian).vaultRoot || '') || null;
137
- if (root && effectiveInstanceRoot() !== prodInstanceRoot()) return { vaultError: '격리 인스턴스 — 운영 볼트 사본 건너뜀', vaultFatal: false };
153
+ const config = getUserConfig();
154
+ const obsidian = object(config.raw.obsidian);
155
+ root = (typeof obsidian.vault === 'string' && obsidian.vault) || (typeof obsidian.vaultRoot === 'string' && obsidian.vaultRoot) || config.obsidian.vault || null;
156
+ if (root && effectiveInstanceRoot() !== prodInstanceRoot() && !root.startsWith(`${effectiveInstanceRoot()}/`)) return { vaultError: '격리 인스턴스 — 운영 볼트 사본 건너뜀', vaultFatal: false };
138
157
  } catch (e) { return { vaultError: reasonOf(e), vaultFatal: true }; }
139
158
  }
140
159
  if (!root) return { vaultError: null, vaultFatal: false };
@@ -181,6 +200,8 @@ export async function runWeekly(options: { dryRun?: boolean; stage?: 'collect' |
181
200
  const now = (deps.now ?? (() => new Date()))();
182
201
  const root = deps.root ?? effectiveInstanceRoot();
183
202
  const { from, to, key } = weekOf(now);
203
+ const newsTo = new Date(`${kstDate(now)}T00:00:00+09:00`);
204
+ const newsFrom = new Date(newsTo.getTime() - 7 * DAY);
184
205
  const file = join(root, 'rhythm', 'weekly', `${key}.md`);
185
206
  let markdown: string;
186
207
  let header: string;
@@ -205,8 +226,8 @@ export async function runWeekly(options: { dryRun?: boolean; stage?: 'collect' |
205
226
  };
206
227
  const issues = await safe(async () => issueHistory(root));
207
228
  const parts: WeeklyParts = {
208
- landings: await safe(() => (deps.landings ?? collectLandings)(now, { from, to })),
209
- release: await safe(deps.release ?? collectRelease),
229
+ landings: await safe(() => deps.landings ? deps.landings(now, { from, to }) : collectLandings(now, { from, to }, { repoRoot: deps.repoRoot, repoName: deps.repoName })),
230
+ release: await safe(deps.release ?? (() => collectWeeklyRelease(now, root))),
210
231
  issues: issues.status === 'ok' ? { status: 'ok', value: [...latest(issues.value).values()].filter(i => i.status === 'open') } : issues,
211
232
  followThrough: issues.status === 'ok' ? { status: 'ok', value: [...new Set([
212
233
  ...[...latest(issues.value, from.getTime()).values()].filter(i => i.status === 'open').map(i => i.id),
@@ -219,7 +240,7 @@ export async function runWeekly(options: { dryRun?: boolean; stage?: 'collect' |
219
240
  return { issue, outcome: Date.parse(issue.due) < now.getTime() ? '기한 넘김' as const : '진행' as const };
220
241
  }) } : issues,
221
242
  decisions: await safe(deps.decisions ?? collectDecisions),
222
- news: readDailyNews(root, from, to),
243
+ news: readDailyNews(root, newsFrom, newsTo),
223
244
  };
224
245
  const versions = await safe(async () => publishedVersions(root, from, to));
225
246
  if (versions.status === 'unreadable') {
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env bun
2
+ import { spawnSync } from 'node:child_process';
3
+ import { appendFileSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
4
+ import { homedir, tmpdir } from 'node:os';
5
+ import { dirname, join } from 'node:path';
6
+ import { debug } from '../../src/debug/log.js';
7
+ import {
8
+ defaultGitMergeSeam, defaultLlmResolve, mergeMainWithLlmResolve,
9
+ type MergeGitSeam,
10
+ } from '../../src/autopilot/build/llm-conflict-merge.js';
11
+ import { NEXT_MD_PATH } from '../../src/release-loop/next-md-merge.js';
12
+
13
+ type Command = (cwd: string, program: 'git' | 'gh', args: readonly string[]) => { status: number; stdout: string; stderr: string };
14
+ export type ShadowOutcome = 'clean' | 'resolved-deterministic' | 'resolved-llm' | 'unresolved' | 'unmeasured';
15
+ export interface ShadowRow { pr: number; outcome: ShadowOutcome; files: string[]; durationMs: number }
16
+ interface Draft { number: number; headRefName: string; isDraft: boolean; mergeable: string | boolean | null }
17
+
18
+ export interface ShadowOptions {
19
+ repo: string;
20
+ stateRoot: string;
21
+ limit?: number;
22
+ run?: Command;
23
+ git?: MergeGitSeam;
24
+ resolve?: (file: string, conflicted: string) => Promise<string>;
25
+ log?: (line: string) => void;
26
+ now?: () => number;
27
+ }
28
+
29
+ export const runCommand: Command = (cwd, program, args) => {
30
+ const result = spawnSync(program, [...args], { cwd, encoding: 'utf8' });
31
+ return { status: result.status ?? 1, stdout: result.stdout ?? '', stderr: result.stderr ?? String(result.error ?? '') };
32
+ };
33
+
34
+ function mustRun(run: Command, cwd: string, program: 'git' | 'gh', args: readonly string[]): string {
35
+ const result = run(cwd, program, args);
36
+ if (result.status !== 0) throw new Error(`${program} ${args.slice(0, 3).join(' ')}: ${result.stderr || 'failed'}`);
37
+ return result.stdout;
38
+ }
39
+
40
+ /** Use the same harness ownership boundary as the draft sweep; never touch a non-harness PR. */
41
+ export function conflictingHarnessDrafts(json: string): Draft[] {
42
+ const rows: unknown = JSON.parse(json);
43
+ if (!Array.isArray(rows)) throw new Error('gh PR inventory is not an array');
44
+ if (rows.some((row) => !row || typeof row !== 'object' || !Number.isSafeInteger(row.number)
45
+ || row.number <= 0 || typeof row.headRefName !== 'string' || typeof row.isDraft !== 'boolean'
46
+ || !('mergeable' in row) || !['string', 'boolean'].includes(typeof row.mergeable) && row.mergeable !== null)) {
47
+ throw new Error('gh PR inventory is incomplete');
48
+ }
49
+ return (rows as Draft[]).filter((row) => row.isDraft && row.headRefName.startsWith('self-impl/')
50
+ && (row.mergeable === 'CONFLICTING' || row.mergeable === false));
51
+ }
52
+
53
+ /** GitHub 는 병합 가능 여부를 늦게 계산한다 — 첫 조회에 UNKNOWN 이면 «충돌 0»이 아니라 «아직 못 잼»이다(10-05 첫 운영 실행이 조용히 0). */
54
+ export function unknownMergeableDrafts(json: string): number {
55
+ const rows = JSON.parse(json) as Draft[];
56
+ return rows.filter((row) => row.isDraft && row.headRefName.startsWith('self-impl/') && (row.mergeable === 'UNKNOWN' || row.mergeable === null)).length;
57
+ }
58
+
59
+ export function shadowSummary(rows: readonly ShadowRow[]): string {
60
+ const measured = rows.filter((row) => row.outcome !== 'unmeasured');
61
+ const recoverable = measured.filter((row) => row.outcome !== 'unresolved').length;
62
+ return `충돌 draft ${measured.length} 중 살릴 수 있음 ${recoverable} = ${measured.length ? Math.round(recoverable / measured.length * 100) : 0}%`;
63
+ }
64
+
65
+ /** Shadow only: fetch a PR snapshot, detach it outside the repo, simulate merging fresh origin/main, then discard it. */
66
+ export async function runResyncShadow(options: ShadowOptions): Promise<ShadowRow[]> {
67
+ const { repo, stateRoot, limit = 10, run = runCommand, log = console.log, now = Date.now } = options;
68
+ if (!Number.isSafeInteger(limit) || limit < 1) throw new Error('--limit must be a positive integer');
69
+ const rows: ShadowRow[] = [];
70
+ const output = join(stateRoot, 'smart-merge', 'resync-shadow.jsonl');
71
+ const record = (row: ShadowRow): void => {
72
+ mkdirSync(dirname(output), { recursive: true });
73
+ appendFileSync(output, `${JSON.stringify(row)}\n`);
74
+ rows.push(row);
75
+ debug.log('smart-merge.resync', 'shadow-result', { pr: row.pr, outcome: row.outcome, files: row.files });
76
+ };
77
+ let drafts: Draft[];
78
+ try {
79
+ // gh's mergeable GraphQL field is CONFLICTING/MERGEABLE/UNKNOWN (some adapters return false/true).
80
+ const inventory = mustRun(run, repo, 'gh', [
81
+ 'pr', 'list', '--state', 'open', '--limit', '1000', '--json', 'number,isDraft,headRefName,mergeable',
82
+ ]);
83
+ drafts = conflictingHarnessDrafts(inventory).slice(0, limit);
84
+ const unknown = unknownMergeableDrafts(inventory);
85
+ if (unknown) log(`병합 가능 여부 미계산 draft ${unknown} — GitHub 계산 뒤 다시 돌려라(이번 수에 안 들어감)`);
86
+ } catch (error) {
87
+ log(`못 잼: GitHub draft 조회 실패 (${String(error)})`);
88
+ log(shadowSummary(rows));
89
+ return rows;
90
+ }
91
+ for (const draft of drafts) {
92
+ const start = now();
93
+ let temp: string | undefined;
94
+ let attached = false;
95
+ let outcome: ShadowOutcome = 'unmeasured';
96
+ let files: string[] = [];
97
+ try {
98
+ // refs/pull/N/head supports fork heads too; FETCH_HEAD is consumed before the next fetch.
99
+ mustRun(run, repo, 'git', ['fetch', 'origin', `refs/pull/${draft.number}/head`]);
100
+ temp = mkdtempSync(join(tmpdir(), 'elanous-resync-shadow-'));
101
+ mustRun(run, repo, 'git', ['worktree', 'add', '--detach', temp, 'FETCH_HEAD']);
102
+ attached = true;
103
+ const seam = options.git ?? defaultGitMergeSeam();
104
+ const observedGit: MergeGitSeam = {
105
+ ...seam,
106
+ conflictedFiles: (wt) => { files = seam.conflictedFiles(wt); return files; },
107
+ };
108
+ const result = await mergeMainWithLlmResolve(temp, 'origin/main',
109
+ options.resolve ?? ((file, conflicted) => defaultLlmResolve(file, conflicted, 'origin/main', { worktreePath: temp })), observedGit);
110
+ if (result.status === 'merged' || result.status === 'up-to-date') outcome = 'clean';
111
+ else if (result.status === 'llm-resolved') {
112
+ files = result.resolvedFiles ?? files;
113
+ outcome = files.length > 0 && files.every((file) => file === NEXT_MD_PATH) ? 'resolved-deterministic' : 'resolved-llm';
114
+ } else if (result.status === 'conflict-unresolved') outcome = 'unresolved';
115
+ // A git error is a measurement failure, not evidence of an unresolvable conflict.
116
+ } catch {
117
+ outcome = 'unmeasured';
118
+ } finally {
119
+ if (attached && temp) {
120
+ try { mustRun(run, repo, 'git', ['worktree', 'remove', '--force', temp]); }
121
+ catch { outcome = 'unmeasured'; }
122
+ }
123
+ if (temp) {
124
+ try { rmSync(temp, { recursive: true, force: true }); }
125
+ catch { outcome = 'unmeasured'; }
126
+ }
127
+ record({ pr: draft.number, outcome, files, durationMs: Math.max(0, now() - start) });
128
+ }
129
+ }
130
+ log(shadowSummary(rows));
131
+ return rows;
132
+ }
133
+
134
+ if (import.meta.main) {
135
+ const args = process.argv.slice(2);
136
+ const valid = args.length === 0 || args.length === 2 && args[0] === '--limit' && /^[1-9]\d*$/.test(args[1]!);
137
+ if (!valid) {
138
+ console.error('usage: bun scripts/smart-merge/resync-shadow.ts [--limit N]');
139
+ process.exitCode = 2;
140
+ } else {
141
+ await runResyncShadow({
142
+ repo: mustRun(runCommand, process.cwd(), 'git', ['rev-parse', '--show-toplevel']).trim(),
143
+ stateRoot: process.env.ELANOUS_STATE_DIR || join(homedir(), '.elanous'),
144
+ ...(args.length ? { limit: Number(args[1]) } : {}),
145
+ });
146
+ }
147
+ }
package/src/acp/client.ts CHANGED
@@ -302,7 +302,7 @@ const ACP_FORCED_BILLING_ENV_BY_BACKEND: Readonly<Record<string, Readonly<Record
302
302
  };
303
303
 
304
304
  function scrubAcpBillingEnv(spec: AcpBackendSpec, env: NodeJS.ProcessEnv): AcpBillingEnvScrubObservation {
305
- const billingEnvScrubEnabled = getUserConfig().acp.scrubBillingEnv !== false;
305
+ const billingEnvScrubEnabled = spec.id === 'claude' || getUserConfig().acp.scrubBillingEnv !== false;
306
306
  const scrubbedBillingEnv: string[] = [];
307
307
  const forcedBillingEnv: string[] = [];
308
308
  if (billingEnvScrubEnabled) {
@@ -378,6 +378,8 @@ export interface AcpAgentOpts {
378
378
  /** Extra env vars to pass to the subprocess. Inherits from
379
379
  * process.env unless overridden. */
380
380
  env?: Record<string, string>;
381
+ /** Test seam: replace the child process without launching Claude. */
382
+ spawnChild?: typeof spawn;
381
383
  /** Session-scoped Codex app-server configuration arguments. Ignored by
382
384
  * non-Codex ACP backends. */
383
385
  codexArgs?: readonly string[];
@@ -419,6 +421,7 @@ export class AcpAgent {
419
421
  private readonly spec: AcpBackendSpec;
420
422
  private readonly cwd: string;
421
423
  private readonly env: NodeJS.ProcessEnv;
424
+ private readonly spawnChild: typeof spawn;
422
425
  private readonly billingEnvScrub: AcpBillingEnvScrubObservation;
423
426
  private readonly log: (msg: string) => void;
424
427
  private readonly onCapabilities: ((capabilities: ElanousCapabilities) => void) | undefined;
@@ -461,6 +464,7 @@ export class AcpAgent {
461
464
  }
462
465
  this.billingEnvScrub = scrubAcpBillingEnv(this.spec, cleanEnv);
463
466
  this.env = cleanEnv;
467
+ this.spawnChild = opts.spawnChild ?? spawn;
464
468
  this.log = opts.log ?? ((m) => console.error(`[acp:${this.spec.id}] ${m}`));
465
469
  this.onCapabilities = opts.onCapabilities;
466
470
  this.permissionApprover = opts.permissionApprover ?? null;
@@ -501,7 +505,18 @@ export class AcpAgent {
501
505
  throw new Error(`acp spawn: cwd does not exist: ${this.cwd}`);
502
506
  }
503
507
 
504
- const child = spawn(resolvedBin.path, this.spec.args, {
508
+ if (this.spec.id === 'claude') {
509
+ const remaining = (claudeBackend.scrubEnv ?? []).filter(key => key in this.env);
510
+ debug.log('acp.claude', 'auth-source', {
511
+ apiKeyPresent: remaining.length > 0,
512
+ source: remaining.length > 0 ? 'billing-env' : 'subscription',
513
+ });
514
+ if (remaining.length > 0 && process.env.NODE_ENV !== 'test' && !process.env.ELANOUS_TEST_HOME) {
515
+ throw new Error('ACP Claude refused: billing authentication environment remains after scrub');
516
+ }
517
+ }
518
+
519
+ const child = this.spawnChild(resolvedBin.path, this.spec.args, {
505
520
  cwd: this.cwd,
506
521
  env: this.env,
507
522
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -0,0 +1,161 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+
5
+ type Agent = 'claude' | 'codex' | 'grok';
6
+ type Presence = 'present' | '«없음»';
7
+
8
+ function present(path: string): Presence {
9
+ return existsSync(path) ? 'present' : '«없음»';
10
+ }
11
+
12
+ function record(value: unknown): Record<string, unknown> {
13
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
14
+ ? value as Record<string, unknown> : {};
15
+ }
16
+
17
+ function json(path: string): Record<string, unknown> | null {
18
+ if (!existsSync(path)) return null;
19
+ return record(JSON.parse(readFileSync(path, 'utf8')));
20
+ }
21
+
22
+ // Command strings can contain tokens even without a shell; export only known executable names.
23
+ function safeCommand(value: unknown): string | undefined {
24
+ if (typeof value !== 'string') return undefined;
25
+ return /^(?:npx|npm|node|bun|uv|uvx|python|python3|docker|deno|pnpm|yarn|bash|sh|claude|codex|grok|cct)$/.test(value)
26
+ ? value : '<redacted>';
27
+ }
28
+
29
+ // Key-shaped values (sk-…, ghp_…, AKIA…) pass the character check, so shape alone cannot prove a value is not a secret (10-05 post-review).
30
+ const SECRET_PREFIX = /^(?:sk-|sk_|pk_|rk_|xai-|ghp_|gho_|ghu_|ghs_|ghr_|github_pat_|glpat-|AKIA|ASIA|AIza|ya29\.|xox[abprs]-|eyJ)/;
31
+
32
+ export function safeIdentifier(value: unknown): string | null {
33
+ if (typeof value !== 'string') return null;
34
+ const digits = (value.match(/[0-9]/g) ?? []).length;
35
+ const keyShaped = SECRET_PREFIX.test(value) || value.length > 40 || (value.length >= 20 && digits >= 8);
36
+ return /^[a-zA-Z0-9_.:-]{1,90}$/.test(value) && !keyShaped && !/(?:token|secret|password|api.?key|credential)/i.test(value)
37
+ ? value : '<redacted>';
38
+ }
39
+
40
+ type McpSource = 'settings' | 'user' | 'project';
41
+
42
+ function mcpEntry(server: unknown, source: McpSource): Record<string, unknown> {
43
+ const body = record(server);
44
+ return {
45
+ source,
46
+ command: safeCommand(body.command) ?? null,
47
+ env: Object.fromEntries(Object.keys(record(body.env)).map(key => [key, '<redacted>'])),
48
+ headers: Object.fromEntries(Object.keys(record(body.headers)).map(key => [key, '<redacted>'])),
49
+ };
50
+ }
51
+
52
+ function collectMcp(
53
+ into: Record<string, unknown>,
54
+ servers: unknown,
55
+ source: McpSource,
56
+ ): void {
57
+ for (const [name, server] of Object.entries(record(servers))) into[name] = mcpEntry(server, source);
58
+ }
59
+
60
+ function hookGroups(hooks: Record<string, unknown>): Record<string, unknown[]> {
61
+ return Object.fromEntries(Object.entries(hooks).map(([event, groups]) => [event,
62
+ Array.isArray(groups) ? groups.map(group => {
63
+ const body = record(group);
64
+ const entries = body.hooks;
65
+ return {
66
+ matcher: safeIdentifier(body.matcher),
67
+ hooks: Array.isArray(entries)
68
+ ? entries.map(entry => ({ command: safeCommand(record(entry).command) ?? null }))
69
+ : [],
70
+ };
71
+ }) : [],
72
+ ]));
73
+ }
74
+
75
+ function claude(home: string): Record<string, unknown> {
76
+ const dir = join(home, '.claude');
77
+ const settingsPath = join(dir, 'settings.json');
78
+ const userPath = join(home, '.claude.json');
79
+ const settings = json(settingsPath);
80
+ const user = json(userPath);
81
+ const data = record(settings);
82
+ const userData = record(user);
83
+ const plugins = record(data.enabledPlugins);
84
+ const hooks = record(data.hooks);
85
+ const statusLine = record(data.statusLine);
86
+ const skillsDir = join(dir, 'skills');
87
+ const team = join(home, '.teamclaude');
88
+ const teamManifest = json(join(team, 'package.json'));
89
+ const teamVersion = typeof teamManifest?.version === 'string' && /^\d+\.\d+\.\d+(?:[-+.][a-zA-Z0-9.-]+)?$/.test(teamManifest.version)
90
+ ? teamManifest.version : undefined;
91
+ const mcp: Record<string, unknown> = {};
92
+ if (settings !== null) collectMcp(mcp, data.mcpServers, 'settings');
93
+ if (user !== null) {
94
+ collectMcp(mcp, userData.mcpServers, 'user');
95
+ for (const project of Object.values(record(userData.projects))) {
96
+ collectMcp(mcp, record(project).mcpServers, 'project');
97
+ }
98
+ }
99
+ const mcpFilesMissing = settings === null && user === null;
100
+ return {
101
+ settings: settings === null ? '«없음»' : 'present',
102
+ userConfig: user === null ? '«없음»' : 'present',
103
+ permissionMode: safeIdentifier(record(data.permissions).defaultMode),
104
+ statusLine: settings === null ? '«없음»' : data.statusLine === undefined ? '«없음»' : {
105
+ type: safeIdentifier(statusLine.type),
106
+ command: safeCommand(statusLine.command) ?? null,
107
+ },
108
+ hooks: settings === null ? '«없음»' : hookGroups(hooks),
109
+ enabledPlugins: settings === null ? '«없음»' : Object.fromEntries(Object.entries(plugins).map(([name, enabled]) => [name, enabled === true])),
110
+ mcpServers: mcpFilesMissing ? '«없음»' : mcp,
111
+ skills: !existsSync(skillsDir) ? '«없음»' : readdirSync(skillsDir, { withFileTypes: true })
112
+ .filter(entry => entry.isDirectory()).map(entry => entry.name).sort(),
113
+ teamclaude: { installed: present(team), version: teamVersion ?? null },
114
+ auth: present(join(dir, 'auth.json')),
115
+ credentials: present(join(dir, '.credentials.json')),
116
+ };
117
+ }
118
+
119
+ function codex(home: string): Record<string, unknown> {
120
+ const dir = join(home, '.codex');
121
+ const file = join(dir, 'config.toml');
122
+ if (!existsSync(file)) return { config: '«없음»', model: null, profile: null, mcpServers: '«없음»', auth: present(join(dir, 'auth.json')) };
123
+ const data = record(Bun.TOML.parse(readFileSync(file, 'utf8')));
124
+ const mcp = record(data.mcp_servers);
125
+ return {
126
+ config: 'present',
127
+ model: safeIdentifier(data.model),
128
+ profile: safeIdentifier(data.profile),
129
+ mcpServers: Object.keys(mcp).sort(),
130
+ auth: present(join(dir, 'auth.json')),
131
+ };
132
+ }
133
+
134
+ function grok(home: string): Record<string, unknown> {
135
+ const dir = join(home, '.grok');
136
+ const jsonPath = join(dir, 'config.json');
137
+ const tomlPath = join(dir, 'config.toml');
138
+ const file = existsSync(jsonPath) ? jsonPath : tomlPath;
139
+ const data = !existsSync(file) ? null : file === jsonPath ? json(file) : record(Bun.TOML.parse(readFileSync(file, 'utf8')));
140
+ return {
141
+ config: data === null ? '«없음»' : 'present',
142
+ keys: data === null ? '«없음»' : Object.keys(data).sort(),
143
+ auth: present(join(dir, 'auth.json')),
144
+ credentials: present(join(dir, 'credentials.json')),
145
+ };
146
+ }
147
+
148
+ export function captureAgentEnv(options: { agent?: Agent; home?: string; now?: Date } = {}): {
149
+ version: number; capturedAt: string; agents: Record<string, Record<string, unknown>>;
150
+ } {
151
+ const home = options.home ?? homedir();
152
+ const selected = options.agent ? [options.agent] : ['claude', 'codex', 'grok'] as const;
153
+ const agents: Record<string, Record<string, unknown>> = {};
154
+ for (const agent of selected) {
155
+ if (agent === 'claude') agents.claude = claude(home);
156
+ else if (agent === 'codex') agents.codex = codex(home);
157
+ else if (agent === 'grok') agents.grok = grok(home);
158
+ else throw new Error('Unknown agent');
159
+ }
160
+ return { version: 1, capturedAt: (options.now ?? new Date()).toISOString(), agents };
161
+ }
@@ -15,6 +15,8 @@ import { join, relative, resolve, isAbsolute, sep } from 'node:path';
15
15
  import { startPty, mintPtyId, onPtyEvent, type PtyHandle, type StartOpts } from '../pty-shell/registry.js';
16
16
  import { withChildPtyIdentity } from '../agent/pty-identity.js';
17
17
  import { NESTED_AGENT_ENV_BLOCKLIST } from '../agent/nested-agent-env.js';
18
+ import { getNestDepth } from '../agent/nest-depth.js';
19
+ import { gateNestedElanousLaunch, nestedDepthCodexArgs, readNestedElanousDepth } from '../harness/nested-elanous-policy.js';
18
20
  import { createWorktree, resolveMainRepoRoot, type CreateWorktreeOpts } from '../git-fs/worktree.js';
19
21
  import { configuredWorktreeRoot } from '../user-config.js';
20
22
  import { worktreeHasChanges, commitWorktree, changedFiles } from '../self-implement/seams.js';
@@ -321,7 +323,36 @@ export function resolveBackendSpawn(
321
323
  // 지운 키 이름 — PTY 합성이 로그인 셸 캡처본에서 되살리지 않게 그대로 넘긴다(강제 env 로 다시 넣은 키는 뺀다).
322
324
  // ⚠️ 과금 스크럽 키만 — 중첩 표지는 정체성 allowlist 가 다시 싣는 키가 있어 이 PR 의 범위 밖이다.
323
325
  const unsetEnv = [...new Set(backend.scrubEnv ?? [])].filter((k) => !(k in env));
324
- return { cmd: backend.cmd, args: [...backend.args], env, unsetEnv, nestedEnvRemovedCount, forcedEnv };
326
+ // Codex drops parent env under shell_environment_policy inherit=core, so the nested-depth
327
+ // marker rides on argv (`-c shell_environment_policy.set.ELANOUS_NESTED_DEPTH=<child>`).
328
+ // Backends that cannot carry it are refused only when a nested elanous launch was requested.
329
+ const args = [...backend.args, ...nestedDepthCodexArgs(backend.name, getNestDepth() + 1)];
330
+ return { cmd: backend.cmd, args, env, unsetEnv, nestedEnvRemovedCount, forcedEnv };
331
+ }
332
+
333
+ /**
334
+ * Refuse a requested nested elanous launch when this backend cannot carry the depth marker.
335
+ * Unset depth is 0. A normal mission spawn (`nestedLaunchRequested: false`) is never refused here.
336
+ */
337
+ export function refuseNestedElanousOnBackend(input: {
338
+ readonly backendName: string;
339
+ readonly nestedLaunchRequested: boolean;
340
+ readonly allow?: boolean;
341
+ readonly configAllow?: boolean;
342
+ readonly env?: Record<string, string | undefined>;
343
+ }): { readonly refused: boolean; readonly reason?: string; readonly depth: number } {
344
+ const depth = readNestedElanousDepth(input.env);
345
+ const decision = gateNestedElanousLaunch({
346
+ backendName: input.backendName,
347
+ depth,
348
+ nestedLaunchRequested: input.nestedLaunchRequested,
349
+ ...(input.allow === true ? { allow: true } : {}),
350
+ ...(input.configAllow === true ? { configAllow: true } : {}),
351
+ });
352
+ if (input.nestedLaunchRequested && !decision.allowed) {
353
+ return { refused: true, reason: decision.reason, depth };
354
+ }
355
+ return { refused: false, depth };
325
356
  }
326
357
 
327
358
  /** Build the mission PTY options after identity is preallocated for child env propagation. */
@@ -491,6 +522,12 @@ export interface AgentMissionSpec {
491
522
  memory?: boolean;
492
523
  /** PTY 닉네임(휴먼 리더블·goto 로 나중 접근). 기본 branch. */
493
524
  nickname?: string;
525
+ /**
526
+ * True only when this mission is itself a nested elanous launch.
527
+ * A backend that cannot carry the depth marker is then refused.
528
+ * Absent means an ordinary mission spawn — never refused for a missing marker.
529
+ */
530
+ nestedElanousLaunch?: boolean;
494
531
  /** Concurrent missions may share one decision stream alias without sharing a PTY or worktree. */
495
532
  decisionMissionId?: string;
496
533
  /** ⭐⭐ 그 브랜치를 이미 쥔 «소유» 워크트리가 있으면 지우지 말고 그대로 재사용하라고 요청한다
@@ -1534,6 +1571,19 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
1534
1571
  // 구독 모드 — backend 지정 env 키 스크럽 후 강제 env까지 적용해 cmd·args·env를 순수 seam으로 구성한다.
1535
1572
  // forcedEnv 이름의 후속 로깅은 이 착지의 의도적 경계이며, spawn에는 같은 seam의 env가 그대로 흐른다.
1536
1573
  const spawnParams = resolveBackendSpawn(backend, process.env);
1574
+ const nestedRefusal = refuseNestedElanousOnBackend({
1575
+ backendName: backend.name,
1576
+ nestedLaunchRequested: spec.nestedElanousLaunch === true,
1577
+ env: process.env,
1578
+ });
1579
+ if (nestedRefusal.refused) {
1580
+ debug.log('agent-mission.nested', 'refused', { agent: backend.name, depth: nestedRefusal.depth, reason: nestedRefusal.reason });
1581
+ return {
1582
+ ok: false, worktree: spec.workdir ?? '', branch: spec.branch ?? null, rounds: 0,
1583
+ evidencePath: null, committed: false, usedOmniCrawl: false,
1584
+ detail: `nested elanous refused (${nestedRefusal.reason})`,
1585
+ };
1586
+ }
1537
1587
  const env = spawnParams.env;
1538
1588
  if (backend.name === 'claude') {
1539
1589
  const status = (deps.checkClaudeSubscription ?? checkClaudeSubscription)({ env });
@@ -0,0 +1,40 @@
1
+ import type { Command } from 'commander';
2
+ import { parseSince } from '../cli/logs-cli.js';
3
+ import {
4
+ SUPERVISOR_METRICS_DEFAULT_SINCE,
5
+ formatSupervisorMetrics,
6
+ loadSupervisorMetrics,
7
+ type ReadSupervisorLogs,
8
+ } from './supervisor-metrics.js';
9
+
10
+ export interface SupervisorMetricsCliDeps {
11
+ readonly read?: ReadSupervisorLogs;
12
+ readonly write?: (text: string) => void;
13
+ readonly nowMs?: number;
14
+ }
15
+
16
+ export function registerSupervisorMetricsCommand(parent: Command, deps: SupervisorMetricsCliDeps = {}): Command {
17
+ return parent.command('metrics')
18
+ .description('외부 에이전트 미션 슈퍼바이저 품질 지표 — 이미 남은 미션 기록만 읽는다')
19
+ .option('--since <window>', '조회 창 (30s/15m/2h/7d · 기본 7d)', SUPERVISOR_METRICS_DEFAULT_SINCE)
20
+ .option('--json', 'JSON 출력')
21
+ .action((opts: { since?: string; json?: boolean }) => {
22
+ const since = opts.since?.trim() || SUPERVISOR_METRICS_DEFAULT_SINCE;
23
+ const nowMs = deps.nowMs ?? Date.now();
24
+ const relative = /^(\d+)(s|m|h|d)$/.exec(since);
25
+ const unitMs = { s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 } as const;
26
+ // parseSince returns an absolute epoch (including for relative inputs); anchor relative windows to this invocation's clock.
27
+ const sinceMs = relative
28
+ ? nowMs - Number(relative[1]) * unitMs[relative[2] as keyof typeof unitMs]
29
+ : parseSince(since);
30
+ const write = deps.write ?? ((text: string) => { process.stdout.write(text); });
31
+ if (sinceMs === null || !Number.isFinite(sinceMs)) {
32
+ write(`--since 를 읽지 못함: ${since}\n`);
33
+ process.exitCode = 2;
34
+ return;
35
+ }
36
+ const report = loadSupervisorMetrics(since, sinceMs, nowMs, deps.read);
37
+ if (opts.json) write(`${JSON.stringify(report)}\n`);
38
+ else write(`${formatSupervisorMetrics(report)}\n`);
39
+ });
40
+ }