elanous 0.2.13 → 0.2.15

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 (524) 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/LZcstaO9_NXypPFmujolv/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1307-33ffcc49ab45123b.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
  10. package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
  31. package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
  42. package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/approvals/page-0e2c57fe6add4b2a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e05a77df7e005b04.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/bots/page-b8c49f6b4fa2df91.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/chat/page-976813c91d7394ea.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/design-check/page-72d3e7ff35a8c954.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/editor/page-60c7a8e1a4fe62ce.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/exec/page-2a52b316cb143d9c.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/field/page-c3ce483c8d5db370.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/intake/page-96f074f6538e8a2c.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/layout-e97be5334d83a9f5.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/morning/page-c1343f9499d86f3f.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/not-found-8739a2e0ae793130.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-d23b12bb92242558.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c7fea475488201a8.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/reflection/page-5efbf81124c474ae.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-06ee266953d9c879.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/settings/page-153c2b3bcc89e8de.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/share/page-0d6d23b40263a7ea.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-f5597e894c02cd33.js} +1 -1
  91. package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/trace/page-73735b64bfe1ae67.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f7e631e9b4daf66e.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9817c7f9007466cc.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
  106. package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +1 -0
  107. package/apps/pwa/out/approvals/index.html +1 -1
  108. package/apps/pwa/out/approvals/index.txt +23 -23
  109. package/apps/pwa/out/autopilot/index.html +1 -1
  110. package/apps/pwa/out/autopilot/index.txt +23 -23
  111. package/apps/pwa/out/board/index.html +1 -1
  112. package/apps/pwa/out/board/index.txt +23 -23
  113. package/apps/pwa/out/botlab/index.html +1 -1
  114. package/apps/pwa/out/botlab/index.txt +22 -22
  115. package/apps/pwa/out/bots/index.html +1 -1
  116. package/apps/pwa/out/bots/index.txt +22 -22
  117. package/apps/pwa/out/ceo/index.html +1 -0
  118. package/apps/pwa/out/ceo/index.txt +33 -0
  119. package/apps/pwa/out/chat/index.html +1 -1
  120. package/apps/pwa/out/chat/index.txt +22 -22
  121. package/apps/pwa/out/consult/index.html +1 -1
  122. package/apps/pwa/out/consult/index.txt +23 -23
  123. package/apps/pwa/out/control/index.html +1 -1
  124. package/apps/pwa/out/control/index.txt +23 -23
  125. package/apps/pwa/out/design-check/index.html +1 -1
  126. package/apps/pwa/out/design-check/index.txt +23 -23
  127. package/apps/pwa/out/editor/index.html +1 -1
  128. package/apps/pwa/out/editor/index.txt +23 -23
  129. package/apps/pwa/out/exec/index.html +1 -1
  130. package/apps/pwa/out/exec/index.txt +23 -23
  131. package/apps/pwa/out/field/index.html +1 -1
  132. package/apps/pwa/out/field/index.txt +23 -23
  133. package/apps/pwa/out/healthz +1 -1
  134. package/apps/pwa/out/index.html +1 -1
  135. package/apps/pwa/out/index.txt +22 -22
  136. package/apps/pwa/out/inside/index.html +1 -1
  137. package/apps/pwa/out/inside/index.txt +22 -22
  138. package/apps/pwa/out/intake/index.html +1 -1
  139. package/apps/pwa/out/intake/index.txt +23 -23
  140. package/apps/pwa/out/live/index.html +1 -1
  141. package/apps/pwa/out/live/index.txt +23 -23
  142. package/apps/pwa/out/loops/index.html +1 -0
  143. package/apps/pwa/out/loops/index.txt +30 -0
  144. package/apps/pwa/out/market/index.html +1 -1
  145. package/apps/pwa/out/market/index.txt +22 -22
  146. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  147. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  148. package/apps/pwa/out/morning/index.html +1 -1
  149. package/apps/pwa/out/morning/index.txt +23 -23
  150. package/apps/pwa/out/observatory/index.html +1 -1
  151. package/apps/pwa/out/observatory/index.txt +23 -23
  152. package/apps/pwa/out/ops/checklist/index.html +1 -1
  153. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  154. package/apps/pwa/out/ops/release/index.html +1 -1
  155. package/apps/pwa/out/ops/release/index.txt +22 -22
  156. package/apps/pwa/out/ops/seats/index.html +1 -1
  157. package/apps/pwa/out/ops/seats/index.txt +22 -22
  158. package/apps/pwa/out/outputs/index.html +1 -1
  159. package/apps/pwa/out/outputs/index.txt +22 -22
  160. package/apps/pwa/out/reflection/index.html +1 -1
  161. package/apps/pwa/out/reflection/index.txt +23 -23
  162. package/apps/pwa/out/scheduler/index.html +1 -1
  163. package/apps/pwa/out/scheduler/index.txt +23 -23
  164. package/apps/pwa/out/sessions/index.html +1 -1
  165. package/apps/pwa/out/sessions/index.txt +22 -22
  166. package/apps/pwa/out/settings/devices/index.html +1 -1
  167. package/apps/pwa/out/settings/devices/index.txt +23 -23
  168. package/apps/pwa/out/settings/index.html +1 -1
  169. package/apps/pwa/out/settings/index.txt +23 -23
  170. package/apps/pwa/out/setup/done/index.html +1 -1
  171. package/apps/pwa/out/setup/done/index.txt +23 -23
  172. package/apps/pwa/out/setup/index.html +1 -1
  173. package/apps/pwa/out/setup/index.txt +23 -23
  174. package/apps/pwa/out/share/index.html +1 -1
  175. package/apps/pwa/out/share/index.txt +23 -23
  176. package/apps/pwa/out/showroom/index.html +1 -1
  177. package/apps/pwa/out/showroom/index.txt +22 -22
  178. package/apps/pwa/out/tasks/index.html +1 -1
  179. package/apps/pwa/out/tasks/index.txt +23 -23
  180. package/apps/pwa/out/term/index.html +1 -1
  181. package/apps/pwa/out/term/index.txt +23 -23
  182. package/apps/pwa/out/today/index.html +1 -1
  183. package/apps/pwa/out/today/index.txt +23 -23
  184. package/apps/pwa/out/trace/index.html +1 -1
  185. package/apps/pwa/out/trace/index.txt +23 -23
  186. package/apps/pwa/out/vault/index.html +1 -1
  187. package/apps/pwa/out/vault/index.txt +23 -23
  188. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  189. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  190. package/apps/pwa/out/workflows/index.html +1 -1
  191. package/apps/pwa/out/workflows/index.txt +23 -23
  192. package/apps/pwa/out/workspace/index.html +1 -1
  193. package/apps/pwa/out/workspace/index.txt +23 -23
  194. package/apps/pwa/out/worktrees/index.html +1 -1
  195. package/apps/pwa/out/worktrees/index.txt +23 -23
  196. package/graphs/heal/heal-loop.yaml +1 -1
  197. package/graphs/implement-loop.yaml +1 -1
  198. package/graphs/orchestrator/orchestrator.yaml +59 -0
  199. package/graphs/orchestrator/recipes.yaml +18 -0
  200. package/graphs/release/recipes.yaml +6 -0
  201. package/graphs/release/release-loop.yaml +6 -1
  202. package/graphs/rhythm/daily.yaml +38 -0
  203. package/graphs/rhythm/recipes.yaml +18 -0
  204. package/graphs/rhythm/weekly.yaml +39 -0
  205. package/graphs/video/recipes.yaml +3 -0
  206. package/graphs/video/youtube-summary.yaml +17 -0
  207. package/package.json +1 -1
  208. package/scripts/audit-test-state-writes.ts +72 -17
  209. package/scripts/brand/check.ts +188 -0
  210. package/scripts/ci-typecheck-changed.ts +50 -18
  211. package/scripts/coord-post.sh +11 -1
  212. package/scripts/docs/generate-graph-anatomy.ts +315 -0
  213. package/scripts/docs-cli-check.ts +23 -6
  214. package/scripts/expose-rubric-check.ts +118 -0
  215. package/scripts/expose-rubric-suggest.ts +169 -0
  216. package/scripts/heal/run-role.ts +14 -0
  217. package/scripts/hq/standby-verify.ts +11 -7
  218. package/scripts/lecture-note/node.ts +31 -11
  219. package/scripts/lessons/handbook-page.ts +112 -0
  220. package/scripts/marketing/generate-ax-brief.ts +140 -0
  221. package/scripts/marketing/generate-feature-map.ts +118 -0
  222. package/scripts/release-loop/checklist-gate-node.ts +53 -14
  223. package/scripts/release-loop/gate-node.ts +89 -14
  224. package/scripts/release-loop/publish-node.ts +14 -1
  225. package/scripts/release-loop/unattended-release.ts +23 -1
  226. package/scripts/release-loop/vault-note-node.ts +428 -0
  227. package/scripts/release-story/draft.ts +220 -0
  228. package/scripts/rhythm/daily.ts +570 -0
  229. package/scripts/rhythm/weekly.ts +315 -0
  230. package/scripts/skills/public-check.ts +169 -0
  231. package/scripts/smart-merge/resync-shadow.ts +147 -0
  232. package/scripts/test-deterministic.ts +4 -8
  233. package/scripts/test-diet/effectiveness.ts +195 -0
  234. package/scripts/test-diet/lib.ts +16 -8
  235. package/scripts/test-diet/node.ts +77 -9
  236. package/scripts/ux/demo-rehearsal.ts +2 -2
  237. package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
  238. package/scripts/video/youtube-summary.ts +133 -0
  239. package/skills/diagram-master/SKILL.md +42 -49
  240. package/skills/diagram-master/references/pyproject.toml +4 -1
  241. package/skills/diagram-master/references/routing-guide.md +4 -2
  242. package/skills/grill-me/SKILL.md +1 -0
  243. package/skills/youtube-master/SKILL.md +13 -13
  244. package/skills/youtube-master/package-lock.json +0 -545
  245. package/skills/youtube-master/package.json +2 -5
  246. package/skills/youtube-master/scripts/main.ts +2 -2
  247. package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
  248. package/skills/youtube-master/src/util.ts +30 -7
  249. package/src/agent-env/profile.ts +161 -0
  250. package/src/agent-mission/driver.ts +3 -3
  251. package/src/agent-substrate/pr-reviewer.ts +2 -0
  252. package/src/ask-user-question/pending-questions.ts +5 -0
  253. package/src/auth/github-app-token.ts +112 -13
  254. package/src/autopilot/build/llm-conflict-merge.ts +73 -10
  255. package/src/autopilot/build/merge-intent.ts +53 -0
  256. package/src/briefing/brief-items.ts +121 -0
  257. package/src/chat/index.ts +2 -0
  258. package/src/claims/claims-ledger.ts +232 -0
  259. package/src/claims/claims-render.ts +166 -0
  260. package/src/cli/agent-env-cli.ts +21 -0
  261. package/src/cli/claims-cli.ts +94 -0
  262. package/src/cli/decisions-cli.ts +44 -3
  263. package/src/cli/docs-cli.ts +2 -0
  264. package/src/cli/doctor-cli.ts +23 -1
  265. package/src/cli/doctor-node-modules.ts +147 -0
  266. package/src/cli/flow-cli.ts +11 -0
  267. package/src/cli/freeze-cli.ts +45 -0
  268. package/src/cli/intake-cli.ts +46 -17
  269. package/src/cli/lesson-cli.ts +108 -0
  270. package/src/cli/logs-cli.ts +104 -2
  271. package/src/cli/logs-model-input.ts +131 -0
  272. package/src/cli/pod-cli.ts +2 -0
  273. package/src/cli/pr-cli.ts +29 -1
  274. package/src/cli/private-ledger-commands.ts +54 -4
  275. package/src/cli/pty-drive-verdict.ts +11 -2
  276. package/src/cli/release-cli.ts +190 -33
  277. package/src/cli/rfc-status.ts +91 -0
  278. package/src/cli/seat-requests-cli.ts +52 -0
  279. package/src/connectors/linear.ts +5 -3
  280. package/src/context-bus/condense.ts +15 -20
  281. package/src/context-bus/context-now-public.ts +96 -0
  282. package/src/context-bus/context-now-surfaces.ts +55 -2
  283. package/src/context-bus/context-now.ts +6 -3
  284. package/src/context-bus/coord-events.ts +6 -4
  285. package/src/context-bus/long-term-memory.ts +92 -11
  286. package/src/dashboard/index.ts +75 -22
  287. package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
  288. package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
  289. package/src/dashboard/slash-runtime/loops-table.ts +77 -0
  290. package/src/decisions/decision-cards.ts +33 -7
  291. package/src/decisions/decision-ledger.ts +40 -1
  292. package/src/decisions/proact-meter.ts +286 -0
  293. package/src/decisions/proact1-lite.ts +86 -0
  294. package/src/decisions/web-push-decision-cards.ts +1 -1
  295. package/src/discord/sprint21-runtime.ts +16 -0
  296. package/src/discord-self-message.ts +8 -0
  297. package/src/discord-slash-wire.ts +31 -2
  298. package/src/domains/alert-audit.ts +271 -0
  299. package/src/domains/core-tools.ts +4 -3
  300. package/src/domains/ops-facts-tool.ts +31 -1
  301. package/src/domains/outbound-alert.ts +1 -1
  302. package/src/elanous-config-dir.ts +29 -4
  303. package/src/execution-loop/launch-gate.ts +29 -14
  304. package/src/flow/cards.ts +239 -0
  305. package/src/graph-runner/graph-tick.ts +4 -0
  306. package/src/graph-runner/runner.ts +30 -5
  307. package/src/harness/harness-cli-command.ts +259 -38
  308. package/src/harness/harness-incidents.ts +45 -12
  309. package/src/harness/harness-pod-dispatch.ts +102 -11
  310. package/src/harness/harness-queue-child.ts +3 -3
  311. package/src/harness/harness-queue.ts +583 -65
  312. package/src/harness/helper-repair.ts +87 -0
  313. package/src/harness/helper-scan.ts +151 -0
  314. package/src/harness/launch-stamp.ts +236 -0
  315. package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
  316. package/src/harness/ledger-write-guard.ts +35 -0
  317. package/src/harness/model-input-observation.ts +37 -0
  318. package/src/harness/pod-memory-policy.ts +15 -0
  319. package/src/harness/repository-name.ts +55 -0
  320. package/src/hq/fence-audit.ts +90 -0
  321. package/src/hq/hq.ts +63 -5
  322. package/src/hq/lease.ts +4 -4
  323. package/src/hq/move-plan.ts +126 -0
  324. package/src/hq/promote.ts +332 -0
  325. package/src/index.ts +159 -27
  326. package/src/intake-plane/check.ts +63 -0
  327. package/src/intake-plane/digest.ts +37 -15
  328. package/src/intake-plane/front-classifier.ts +6 -0
  329. package/src/intake-plane/items.ts +3 -2
  330. package/src/intake-plane/lens.ts +177 -0
  331. package/src/intake-plane/linear-wish.ts +39 -11
  332. package/src/intake-plane/route.ts +145 -1
  333. package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
  334. package/src/intake-plane/wish-card.ts +53 -2
  335. package/src/intake-plane/wish-folder.ts +5 -2
  336. package/src/intake-plane/wish-reply.ts +72 -0
  337. package/src/launch-head/seat-goal-queue-lock.fixture.ts +15 -0
  338. package/src/launch-head/seat-goal-queue.ts +198 -0
  339. package/src/lessons/lesson-import.ts +106 -0
  340. package/src/lessons/lesson-ledger.ts +206 -0
  341. package/src/loops/activity.ts +203 -0
  342. package/src/loops/budget.ts +63 -0
  343. package/src/loops/checker.ts +5 -3
  344. package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
  345. package/src/loops/loop-cli.ts +36 -4
  346. package/src/loops/neighbors.ts +40 -0
  347. package/src/loops/observe.ts +10 -0
  348. package/src/loops/orchestrator/author-depth.ts +220 -0
  349. package/src/loops/orchestrator/author-input-check.ts +82 -0
  350. package/src/loops/orchestrator/author-ledger.ts +232 -0
  351. package/src/loops/orchestrator/finish-rate.ts +175 -0
  352. package/src/loops/orchestrator/overlap.ts +51 -12
  353. package/src/loops/orchestrator/resync-shadow.ts +227 -0
  354. package/src/loops/orchestrator/seat-share.ts +56 -0
  355. package/src/loops/orchestrator/tick.ts +309 -0
  356. package/src/loops/orchestrator/traffic.ts +134 -13
  357. package/src/loops/registry.ts +535 -9
  358. package/src/loops/status-rows.ts +118 -0
  359. package/src/loops/verdict.ts +17 -5
  360. package/src/maturity/bot-slash-catalog.ts +66 -0
  361. package/src/maturity/feature-maturity.ts +9 -5
  362. package/src/maturity/tui-slash-availability.ts +31 -0
  363. package/src/mss/logging/log-store.ts +35 -4
  364. package/src/nexus/api/dashboard.ts +6 -1
  365. package/src/nexus/api/http-server.ts +33 -3
  366. package/src/nexus/api/loop-edges.ts +173 -0
  367. package/src/nexus/api/merge-approvals-gate.ts +1 -1
  368. package/src/nexus/api/schedules-read.ts +18 -1
  369. package/src/nexus/api/seat-requests.ts +79 -56
  370. package/src/nexus/api/task-cards-api.ts +44 -5
  371. package/src/nexus/api/workflows.ts +2 -1
  372. package/src/prompt-enhance/enhance.ts +6 -1
  373. package/src/release-loop/auto-start.ts +51 -0
  374. package/src/release-loop/checklist.ts +22 -4
  375. package/src/release-loop/feature-store.ts +71 -32
  376. package/src/release-loop/landing-freeze.ts +121 -0
  377. package/src/release-loop/merged-pr-checklist.ts +125 -0
  378. package/src/release-loop/placement.ts +302 -0
  379. package/src/release-loop/release-schedule.ts +3 -1
  380. package/src/seat-dispatch/ceo-commands.ts +2 -2
  381. package/src/seat-dispatch/seat-ask.ts +16 -3
  382. package/src/seat-dispatch/seat-questions.ts +58 -0
  383. package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
  384. package/src/seat-dispatch/seat-request-ledger.ts +133 -0
  385. package/src/seat-loop/seat-loop.ts +531 -40
  386. package/src/self-dev/dev-pipeline.ts +14 -6
  387. package/src/self-dev/draft-sweep.ts +162 -6
  388. package/src/self-dev/draft-triage-rules.ts +25 -8
  389. package/src/self-dev/orchestrate.ts +15 -1
  390. package/src/self-dev/review-provider-fallback.ts +6 -1
  391. package/src/self-dev/run-store.ts +73 -1
  392. package/src/self-dev/run-supervisor.ts +57 -2
  393. package/src/self-dev/self-orchestrate-runtime.ts +5 -1
  394. package/src/self-implement/budget-gate.ts +29 -0
  395. package/src/self-implement/expose-gate.ts +47 -0
  396. package/src/self-implement/frozen-merges.ts +295 -0
  397. package/src/self-implement/gate-cli.ts +10 -4
  398. package/src/self-implement/gate-scope.ts +46 -9
  399. package/src/self-implement/goal-archive.ts +222 -0
  400. package/src/self-implement/goal-author.ts +43 -5
  401. package/src/self-implement/goal-clarification-escalation.ts +5 -0
  402. package/src/self-implement/goal-lookup.ts +147 -0
  403. package/src/self-implement/heal-intake.ts +197 -0
  404. package/src/self-implement/host-regate.ts +32 -2
  405. package/src/self-implement/l8-merge-queue.ts +132 -0
  406. package/src/self-implement/orchestrator.ts +299 -48
  407. package/src/self-implement/pipeline-shape.ts +1 -1
  408. package/src/self-implement/revert-guard.ts +97 -0
  409. package/src/self-implement/run-ledger.ts +3 -1
  410. package/src/self-implement/run-status-mapping.ts +2 -0
  411. package/src/self-implement/seams.ts +29 -9
  412. package/src/self-implement/self-implement-cli.ts +1 -0
  413. package/src/self-implement/speed-claims.ts +338 -0
  414. package/src/session/index.ts +2 -0
  415. package/src/steward/triage.ts +74 -3
  416. package/src/steward/working-backwards.ts +4 -1
  417. package/src/task-cards/card-cli.ts +10 -2
  418. package/src/task-cards/card-store.ts +10 -6
  419. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
  420. package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
  421. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
  422. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  423. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
  424. package/src/task-orchestrator/surfaces/pod-pool.ts +60 -8
  425. package/src/task-orchestrator/surfaces/self-implement-pod.ts +224 -49
  426. package/src/task-orchestrator/surfaces/self-implement.ts +5 -1
  427. package/src/telegram-commands.ts +27 -3
  428. package/src/telegram-loops-command.ts +44 -0
  429. package/src/telegram.ts +25 -4
  430. package/src/user-config.ts +146 -18
  431. package/src/version/packed-revision.json +1 -1
  432. package/src/workflow-runtime/nodes/bash.ts +9 -1
  433. package/src/workflow-runtime/storage.ts +31 -13
  434. package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
  445. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
  449. package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-78b40a344ed22f24.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
  517. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
  518. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
  520. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
  521. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
  523. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
  524. /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → LZcstaO9_NXypPFmujolv}/_ssgManifest.js +0 -0
@@ -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
+ }
@@ -2202,8 +2202,8 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
2202
2202
  rounds: round, evidencePath: null, committed: false, usedOmniCrawl: usedOmni, detail: 'aborted' };
2203
2203
  const finalEv = verifyEvidence(wt.path, spec.evidence);
2204
2204
  const finalScreen = await h.renderScreen();
2205
- const driveVerdict = verdictForFinalPtyScreen({ screen: finalScreen, inputHistory: submittedInputs, exitCode: h.exitCode ?? null });
2206
- debug.log('agent-mission', 'drive-verdict', { kind: driveVerdict.kind, reason: driveVerdict.reason.slice(0, 200) });
2205
+ const driveVerdict = verdictForFinalPtyScreen({ screen: finalScreen, inputHistory: submittedInputs, exitCode: h.exitCode ?? null, artifactEvidence: finalEv.ok });
2206
+ debug.log('agent-mission', 'drive-ok', { verdict: driveVerdict.kind, reason: driveVerdict.reason.slice(0, 200), evidence: finalEv.ok });
2207
2207
  const recoverExhausted = control.termination.kind === 'stuck' && control.termination.reason.startsWith('사람 필요');
2208
2208
  const accepted = finalEv.ok && !recoverExhausted && driveVerdict.kind !== 'done-but-failed';
2209
2209
  const evidencePath = accepted ? finalEv.path : null;
@@ -2223,7 +2223,7 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
2223
2223
  ok: accepted, worktree: wt.path, branch: wt.branch, rounds: round,
2224
2224
  evidencePath, evidenceSatisfied: finalEv.ok, committed, usedOmniCrawl: usedOmni,
2225
2225
  ptyId: h.id, webUrl: webAddress.webUrl, driveVerdict: driveVerdict.kind,
2226
- detail: recoverExhausted ? control.termination.reason : driveVerdict.kind === 'done-but-failed' ? `미완(DRIVE-OK: ${driveVerdict.reason})` : done ? '완료(증거 충족)' : (finalEv.ok ? '증거 충족(루프 종료)' : '미완(증거 부족)'),
2226
+ detail: recoverExhausted ? control.termination.reason : driveVerdict.kind === 'done-but-failed' ? `미완(DRIVE-OK: ${driveVerdict.reason})` : driveVerdict.kind === 'success-unverified' && finalEv.ok ? '완료(증거)' : done ? '완료(증거 충족)' : (finalEv.ok ? '증거 충족(루프 종료)' : '미완(증거 부족)'),
2227
2227
  };
2228
2228
  } finally { stopLive(); finishContext(false); }
2229
2229
  }
@@ -825,6 +825,8 @@ export async function reviewPullRequest(
825
825
 
826
826
  /** 리뷰 결과를 PR 코멘트/알림/CLI 용 markdown 으로 렌더. */
827
827
  export function renderReview(r: ReviewResult): string {
828
+ // A review that never ran must not read as PASS anywhere it is rendered (PR body, comments, CLI) — 10-05 #23942·#24151.
829
+ if (r.reviewed === false) return `⚠️ 자율 PR 리뷰: 리뷰 안 돎 — 사유 ${r.failureReason?.trim() || '미상'} (통과 판정 아님)`;
828
830
  const icon = r.verdict === 'pass' ? '✅' : r.verdict === 'warn' ? '⚠️' : '⛔';
829
831
  const head = `${icon} 자율 PR 리뷰: ${r.verdict.toUpperCase()}`;
830
832
  const parts = [head];
@@ -9,6 +9,8 @@ export interface PendingQuestion {
9
9
  id: string;
10
10
  sessionId?: string;
11
11
  runId?: string;
12
+ /** Explicit provenance for isolated test questions; a temporary goal path alone is not proof. */
13
+ testOrigin?: true;
12
14
  questions: Array<Pick<Question, 'id' | 'question' | 'options' | 'impact' | 'recommendedIndex'>>;
13
15
  startedAt: string;
14
16
  expiresAt?: string;
@@ -84,6 +86,7 @@ function isPendingQuestion(value: unknown): value is PendingQuestion {
84
86
  return typeof pending.id === 'string'
85
87
  && (pending.sessionId === undefined || typeof pending.sessionId === 'string')
86
88
  && (pending.runId === undefined || typeof pending.runId === 'string')
89
+ && (pending.testOrigin === undefined || pending.testOrigin === true)
87
90
  && Array.isArray(pending.questions)
88
91
  && pending.questions.every((question) => {
89
92
  if (question === null || typeof question !== 'object' || Array.isArray(question)) return false;
@@ -175,6 +178,7 @@ export function createPendingQuestion(
175
178
  sessionId?: string,
176
179
  deps: Pick<PendingQuestionStoreDeps, 'now'> = {},
177
180
  presentation: PendingQuestionPresentation = { surface: 'tui', delivery: request.delivery ?? 'modal' },
181
+ provenance: { testOrigin?: true } = {},
178
182
  ): PendingQuestion {
179
183
  // File-surface waits must carry a concrete deadline so the CLI answer path can
180
184
  // reject an expired record (see isPendingQuestion). Fail loudly at creation
@@ -186,6 +190,7 @@ export function createPendingQuestion(
186
190
  id,
187
191
  ...(sessionId === undefined ? {} : { sessionId }),
188
192
  ...(request.runId === undefined ? {} : { runId: request.runId }),
193
+ ...(provenance.testOrigin ? { testOrigin: true as const } : {}),
189
194
  questions: request.questions.map(({ id, question, options, impact, recommendedIndex }) => ({
190
195
  id, question, options,
191
196
  ...(impact === undefined ? {} : { impact }),
@@ -1,6 +1,6 @@
1
1
  import { createHash, createSign } from 'node:crypto';
2
2
  import { spawnSync } from 'node:child_process';
3
- import { existsSync, readFileSync } from 'node:fs';
3
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
4
4
  import { dirname, isAbsolute, join } from 'node:path';
5
5
  import { getElanousConfigDir, getElanousConfigDirOverride } from '../elanous-config-dir.js';
6
6
  import { prodInstanceRoot } from '../instance/resolve.js';
@@ -45,21 +45,23 @@ export function githubAutomationToken(deps: GithubAutomationTokenDeps = {}): str
45
45
  return githubInstallationCredential(deps)?.token ?? null;
46
46
  }
47
47
 
48
+ /** The App credential is machine-level (like `gh auth` in ~/.config/gh), not per universe: a tree-derived test
49
+ * universe resolves its config dir to `<tree>/.elanous-test`, where the file never is — 09-29 #21879 was inert there.
50
+ * Order: explicit path → this universe's config dir → the prod root `~/.elanous`.
51
+ * An explicit config-dir override (tests · `--config-dir`) is a deliberate boundary — never reach past it.
52
+ * ELANOUS_GITHUB_APP_CONFIG_PATH pins the machine-level fallback (the bun test preload points it at nothing,
53
+ * so tests never mint); an explicit config-dir override still wins. */
54
+ function appConfigPath(explicit?: string): string {
55
+ const rel = join('secrets', 'github-app', 'app.json');
56
+ const own = join(getElanousConfigDir(), rel);
57
+ const pinned = process.env.ELANOUS_GITHUB_APP_CONFIG_PATH?.trim();
58
+ return explicit ?? (getElanousConfigDirOverride() ? own : pinned || (existsSync(own) ? own : join(prodInstanceRoot(), rel)));
59
+ }
60
+
48
61
  /** Scoped requests always mint anew; the returned expiry comes from GitHub's response. */
49
62
  export function githubInstallationCredential(deps: GithubAutomationTokenDeps = {}): { token: string; expires_at: string } | null {
50
63
  let path: string;
51
- // The App credential is machine-level (like `gh auth` in ~/.config/gh), not per universe: a tree-derived test
52
- // universe resolves its config dir to `<tree>/.elanous-test`, where the file never is — 09-29 #21879 was inert there.
53
- // Order: explicit path → this universe's config dir → the prod root `~/.elanous`.
54
- try {
55
- const rel = join('secrets', 'github-app', 'app.json');
56
- const own = join(getElanousConfigDir(), rel);
57
- // An explicit config-dir override (tests · `--config-dir`) is a deliberate boundary — never reach past it.
58
- // ELANOUS_GITHUB_APP_CONFIG_PATH pins the machine-level fallback (the bun test preload points it at nothing,
59
- // so tests never mint); an explicit config-dir override still wins.
60
- const pinned = process.env.ELANOUS_GITHUB_APP_CONFIG_PATH?.trim();
61
- path = deps.configPath ?? (getElanousConfigDirOverride() ? own : pinned || (existsSync(own) ? own : join(prodInstanceRoot(), rel)));
62
- }
64
+ try { path = appConfigPath(deps.configPath); }
63
65
  catch {
64
66
  try { debug.log('auth.github-app', 'token-failed', { reason: 'config-path-failed' }); } catch { /* fail open */ }
65
67
  return null;
@@ -113,3 +115,100 @@ export function githubInstallationCredential(deps: GithubAutomationTokenDeps = {
113
115
  return null;
114
116
  }
115
117
  }
118
+
119
+ /** PODCRED1 (10-05): simultaneous launches each minted a scoped token in the same second and the Pods' logins died
120
+ * together (12:49Z · 5 in 2 s). Concurrent issuers for one repository — across processes — share one mint: the first
121
+ * takes a lock and mints, the others wait and reuse that result while it is recent and long-lived. */
122
+ export interface CoalescedInstallationOptions {
123
+ cacheDir?: string;
124
+ /** Reuse a token minted at most this long ago. */
125
+ windowMs?: number;
126
+ /** …and only while it still has this much life left. */
127
+ minRemainingMs?: number;
128
+ /** Skip reuse (still mint under the lock and publish the result) — for a retry after the shared token failed. */
129
+ fresh?: boolean;
130
+ lockWaitMs?: number;
131
+ staleLockMs?: number;
132
+ now?: () => number;
133
+ sleepSync?: (ms: number) => void;
134
+ mint?: (scope: GithubInstallationTokenScope) => { token: string; expires_at: string } | null;
135
+ }
136
+
137
+ const COALESCE_WINDOW_MS = 120_000;
138
+ const COALESCE_MIN_REMAINING_MS = 50 * 60_000;
139
+
140
+ function blockingSleep(ms: number): void {
141
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
142
+ }
143
+
144
+ export function coalescedInstallationCredential(scope: GithubInstallationTokenScope, opts: CoalescedInstallationOptions = {}): { token: string; expires_at: string } | null {
145
+ const now = opts.now ?? Date.now;
146
+ const mint = opts.mint ?? ((s: GithubInstallationTokenScope) => githubInstallationCredential({ scope: s }));
147
+ const windowMs = opts.windowMs ?? COALESCE_WINDOW_MS;
148
+ const minRemainingMs = opts.minRemainingMs ?? COALESCE_MIN_REMAINING_MS;
149
+ const sleepSync = opts.sleepSync ?? blockingSleep;
150
+ // Same boundary as the App config: a test process (or config-dir override) never writes the operational root.
151
+ if (!opts.cacheDir && (process.env.NODE_ENV === 'test' || process.env.ELANOUS_TEST_HOME)) return mint(scope);
152
+ let dir: string;
153
+ let identity: string;
154
+ try {
155
+ const configPath = appConfigPath();
156
+ const config = JSON.parse(readFileSync(configPath, 'utf8')) as Partial<AppConfig>;
157
+ identity = JSON.stringify([configPath, config.id, config.installation_id]);
158
+ dir = opts.cacheDir ?? join(getElanousConfigDirOverride() ? getElanousConfigDir() : prodInstanceRoot(), 'cache', 'github-app-scoped');
159
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
160
+ } catch {
161
+ if (!opts.cacheDir) return mint(scope);
162
+ dir = opts.cacheDir;
163
+ identity = 'injected';
164
+ }
165
+ // Keyed by App installation and repository, so another App (another universe or owner) never reuses this token.
166
+ const key = createHash('sha256').update(`${identity}\0${scope.repository}`).digest('hex').slice(0, 32);
167
+ const file = join(dir, `${key}.json`);
168
+ const lock = join(dir, `${key}.lock`);
169
+ let waitStart: number | undefined;
170
+ const reuse = (): { token: string; expires_at: string } | null => {
171
+ try {
172
+ const entry = JSON.parse(readFileSync(file, 'utf8')) as { token?: unknown; expiresAt?: unknown; mintedAt?: unknown };
173
+ const t = now();
174
+ if (typeof entry.token === 'string' && entry.token && typeof entry.expiresAt === 'number' && typeof entry.mintedAt === 'number'
175
+ && t - entry.mintedAt <= windowMs && entry.expiresAt - t >= minRemainingMs) {
176
+ try { debug.log('auth.github-app', 'token-coalesced', { ageMs: t - entry.mintedAt, expiresAt: new Date(entry.expiresAt).toISOString(), ...(waitStart === undefined ? {} : { waitedMs: t - waitStart }) }); } catch { /* fail open */ }
177
+ return { token: entry.token, expires_at: new Date(entry.expiresAt).toISOString() };
178
+ }
179
+ } catch { /* no usable entry */ }
180
+ return null;
181
+ };
182
+ const hit = opts.fresh ? null : reuse();
183
+ if (hit) return hit;
184
+ waitStart = now();
185
+ const deadline = waitStart + (opts.lockWaitMs ?? 15_000);
186
+ let locked = false;
187
+ for (;;) {
188
+ try { mkdirSync(lock); locked = true; break; }
189
+ catch {
190
+ try { if (now() - statSync(lock).mtimeMs > (opts.staleLockMs ?? 30_000)) { rmSync(lock, { recursive: true, force: true }); continue; } } catch { continue; }
191
+ if (now() >= deadline) break;
192
+ sleepSync(200);
193
+ const waited = opts.fresh ? null : reuse();
194
+ if (waited) return waited;
195
+ }
196
+ }
197
+ if (!locked) { try { debug.log('auth.github-app', 'token-coalesce-lock-timeout', { waitedMs: now() - (waitStart ?? now()) }); } catch { /* fail open */ } }
198
+ try {
199
+ const again = opts.fresh ? null : reuse();
200
+ if (again) return again;
201
+ const minted = mint(scope);
202
+ const expiresAt = minted ? Date.parse(minted.expires_at) : NaN;
203
+ if (minted && Number.isFinite(expiresAt)) {
204
+ const temp = `${file}.${process.pid}.tmp`;
205
+ try {
206
+ writeFileSync(temp, JSON.stringify({ token: minted.token, expiresAt, mintedAt: now() }), { mode: 0o600 });
207
+ renameSync(temp, file);
208
+ } catch { try { rmSync(temp, { force: true }); } catch { /* best effort */ } }
209
+ }
210
+ return minted;
211
+ } finally {
212
+ if (locked) { try { rmSync(lock, { recursive: true, force: true }); } catch { /* stale-lock rule recovers */ } }
213
+ }
214
+ }
@@ -10,6 +10,9 @@ import { readFileSync, writeFileSync } from 'node:fs';
10
10
  import { runGitCommand } from '../../git-fs/runner.js';
11
11
  import { countTestDeclarations } from '../../self-implement/test-declarations.js';
12
12
  import { NEXT_MD_PATH, resolveNextMdConflict } from '../../release-loop/next-md-merge.js';
13
+ import { getUserConfig } from '../../user-config.js';
14
+ import { debug } from '../../debug/log.js';
15
+ import { collectMergeIntent, defaultIntentGit, type IntentGit } from './merge-intent.js';
13
16
 
14
17
  function deterministicNextMd(git: MergeGitSeam, worktreePath: string, file: string): string | null {
15
18
  try {
@@ -64,6 +67,9 @@ export interface LlmMergeOutcome {
64
67
  testDeclarationUnmeasured?: string[];
65
68
  /** 해결기 응답이 LLM 공급자 오류 문구였다 — 파일 내용이 아니다(🅢 2026-09-27 #21086: 세 파일이 통째로 그 문구가 됐다). */
66
69
  providerFailure?: string[];
70
+ /** LLM 입력 상한 초과: 사람 수확으로 넘긴다. 원래 브랜치는 abort 후 보존된다. */
71
+ reason?: 'conflict-input-too-large' | 'conflict-resolver-interrupted';
72
+ inputChars?: number;
67
73
  /** 병합 결과가 양쪽 판 중 작은 쪽의 절반 아래로 줄었다 — 통째로 날린 것으로 보고 해결 실패로 친다. */
68
74
  sizeCollapse?: Array<{ file: string; ours: number; theirs: number; merged: number }>;
69
75
  /** `status: 'error'` 일 때 어느 단계인지. 선택 — 옛 호출자는 이 칸 없이 돌아도 된다. */
@@ -76,6 +82,13 @@ export interface LlmMergeOutcome {
76
82
  export const PROVIDER_FAILURE_TEXT = /^\s*\[LLM PROVIDER (?:BLOCKED|STOPPED)\]/;
77
83
  /** 이 줄 수 미만 파일은 크기 붕괴 판정에서 뺀다(작은 파일은 정당하게 크게 줄 수 있다). */
78
84
  export const SIZE_COLLAPSE_MIN_LINES = 50;
85
+ export const DEFAULT_MERGE_CONFLICT_INPUT_MAX_CHARS = 60_000;
86
+
87
+ export class MergeConflictInputTooLarge extends Error {
88
+ constructor(readonly inputChars: number) {
89
+ super(`충돌 큼 — 사람 수확 (LLM 입력 ${inputChars}자)`);
90
+ }
91
+ }
79
92
 
80
93
  function lineCount(s: string): number {
81
94
  if (s.length === 0) return 0;
@@ -204,12 +217,21 @@ export async function mergeMainWithLlmResolve(
204
217
  let merged: string;
205
218
  try {
206
219
  conflicted = git.readFile(abs);
220
+ } catch {
221
+ git.abort(worktreePath);
222
+ return measuredOutcome({ status: 'conflict-unresolved', resolvedFiles });
223
+ }
224
+ try {
207
225
  // release/next.md: concurrent landings each append a line — keep both without asking the LLM (REL7c).
208
226
  const deterministic = f === NEXT_MD_PATH ? deterministicNextMd(git, worktreePath, f) : null;
209
227
  merged = deterministic ?? await resolve(f, conflicted);
210
- } catch {
228
+ } catch (error) {
211
229
  git.abort(worktreePath); // LLM 예외 → base 유지
212
- return measuredOutcome({ status: 'conflict-unresolved', resolvedFiles });
230
+ return measuredOutcome({ status: 'conflict-unresolved', resolvedFiles,
231
+ ...(error instanceof MergeConflictInputTooLarge
232
+ ? { reason: 'conflict-input-too-large' as const, inputChars: error.inputChars }
233
+ : { reason: 'conflict-resolver-interrupted' as const }),
234
+ });
213
235
  }
214
236
  if (PROVIDER_FAILURE_TEXT.test(merged)) {
215
237
  git.abort(worktreePath); // 해결기가 공급자 오류 문구를 «내용»으로 돌려줬다 → base 유지
@@ -328,27 +350,68 @@ export function defaultGitMergeSeam(): MergeGitSeam {
328
350
  }
329
351
 
330
352
  /** 실 LLM 충돌 해결 어댑터(streamLLM·sol). 코드펜스/설명 제거해 완결 파일만. */
331
- export async function defaultLlmResolve(filePath: string, conflicted: string, mergeTarget: string): Promise<string> {
332
- const { streamLLM } = await import('../../llm.js');
333
- const out = await streamLLM([{ role: 'user', content: conflictResolvePrompt(filePath, conflicted, mergeTarget) }], () => {}, { model: process.env.ELANOUS_CONFLICT_MODEL || tierModel('better'), reasoningEffort: 'medium' });
334
- // ⛔ 라우터는 공급자가 전부 막히면 던지지 않고 오류 문구를 돌려준다 — 그것은 파일 내용이 아니다. 던져서 «LLM 예외 → base 유지» 로 보낸다.
335
- if (PROVIDER_FAILURE_TEXT.test(out)) throw new Error(`conflict resolve: LLM provider failure for ${filePath}`);
336
- return `${out.replace(/^```[\w.-]*\n?/, '').replace(/\n?```\s*$/, '').trimEnd()}\n`;
353
+ export async function defaultLlmResolve(
354
+ filePath: string, conflicted: string, mergeTarget: string,
355
+ options: { worktreePath?: string; mode?: 'off' | 'shadow' | 'on'; git?: IntentGit; stream?: typeof import('../../llm.js')['streamLLM'] } = {},
356
+ ): Promise<string> {
357
+ let rawConfig: { mergeConflictInputMaxChars?: unknown; mergeIntent?: unknown } | undefined;
358
+ try { rawConfig = getUserConfig().raw.selfImplement as typeof rawConfig; }
359
+ catch { /* unreadable config keeps the old resolver and default input limit */ }
360
+ const configuredMax = rawConfig?.mergeConflictInputMaxChars;
361
+ const maxChars = typeof configuredMax === 'number' && Number.isSafeInteger(configuredMax) && configuredMax > 0
362
+ ? configuredMax : DEFAULT_MERGE_CONFLICT_INPUT_MAX_CHARS;
363
+ let rawMode: unknown = options.mode;
364
+ if (rawMode === undefined) {
365
+ rawMode = rawConfig?.mergeIntent;
366
+ }
367
+ const mode = rawMode === 'shadow' || rawMode === 'on' ? rawMode : 'off';
368
+ const resolve = async (intent?: { ours: string | null; theirs: string[] }): Promise<string> => {
369
+ const prompt = conflictResolvePrompt(filePath, conflicted, mergeTarget, intent);
370
+ if (prompt.length > maxChars) throw new MergeConflictInputTooLarge(prompt.length);
371
+ const streamLLM = options.stream ?? (await import('../../llm.js')).streamLLM;
372
+ const out = await streamLLM([{ role: 'user', content: prompt }], () => {}, { model: process.env.ELANOUS_CONFLICT_MODEL || tierModel('better'), reasoningEffort: 'medium' });
373
+ // ⛔ 라우터는 공급자가 전부 막히면 던지지 않고 오류 문구를 돌려준다 — 그것은 파일 내용이 아니다.
374
+ if (PROVIDER_FAILURE_TEXT.test(out)) throw new Error(`conflict resolve: LLM provider failure for ${filePath}`);
375
+ return `${out.replace(/^```[\w.-]*\n?/, '').replace(/\n?```\s*$/, '').trimEnd()}\n`;
376
+ };
377
+ if (mode === 'off') return resolve();
378
+ const collect = () => collectMergeIntent({ worktreePath: options.worktreePath ?? process.cwd(), filePath, mergeTarget, git: options.git ?? defaultIntentGit });
379
+ if (mode === 'on') return resolve(collect());
380
+ const plain = await resolve();
381
+ try {
382
+ const intent = collect();
383
+ const withIntent = await resolve(intent);
384
+ debug.log('self-dev.merge', 'intent-shadow', {
385
+ file: filePath, same: plain === withIntent,
386
+ plainMarkers: hasConflictMarkers(plain), intentMarkers: hasConflictMarkers(withIntent),
387
+ plainLines: lineCount(plain), intentLines: lineCount(withIntent),
388
+ oursIntent: intent.ours !== null, theirsCount: intent.theirs.length,
389
+ });
390
+ } catch (error) {
391
+ try { debug.log('self-dev.merge', 'intent-shadow-failed', { file: filePath, error: String(error) }); }
392
+ catch { /* shadow observation must not affect the adopted result */ }
393
+ }
394
+ return plain;
337
395
  }
338
396
 
339
397
  /** 편의 — 실 git+LLM 으로 호출부가 해석한 ref를 worktree 에 지능 정합한다. */
340
398
  export async function mergeMainIntoWorktreeWithLlm(worktreePath: string, mergeTarget: string): Promise<LlmMergeOutcome> {
341
- return mergeMainWithLlmResolve(worktreePath, mergeTarget, (filePath, conflicted) => defaultLlmResolve(filePath, conflicted, mergeTarget), defaultGitMergeSeam());
399
+ return mergeMainWithLlmResolve(worktreePath, mergeTarget, (filePath, conflicted) => defaultLlmResolve(filePath, conflicted, mergeTarget, { worktreePath }), defaultGitMergeSeam());
342
400
  }
343
401
 
344
402
  /** LLM 충돌 해결 프롬프트(순수·테스트) — ours(walker)·theirs(호출자가 전달한 정합 대상) 종합 지시. */
345
- export function conflictResolvePrompt(filePath: string, conflictedContent: string, mergeTarget: string): string {
403
+ export function conflictResolvePrompt(filePath: string, conflictedContent: string, mergeTarget: string, intent?: { ours: string | null; theirs: string[] }): string {
346
404
  return [
347
405
  '너는 git merge 충돌을 지능적으로 해결하는 엔지니어다. 아래 파일은 3-way merge 충돌 마커를 포함한다:',
348
406
  ' <<<<<<< ours = 현재 브랜치(walker 가 이 미션에서 만든 산출물)',
349
407
  ' ======= 사이 = 양쪽 버전',
350
408
  ` >>>>>>> theirs = ${mergeTarget}(호출자가 해석해 전달한 정합 대상)`,
351
409
  '',
410
+ ...(intent ? [
411
+ `ours 의 의도: ${intent.ours ?? '(확인 불가)'}`,
412
+ `theirs 에 먼저 착지한 변경: ${intent.theirs.length ? intent.theirs.join(' · ') : '(확인 불가)'}`,
413
+ '',
414
+ ] : []),
352
415
  '해결 원칙:',
353
416
  '- 양쪽의 의도를 **모두 보존**하며 종합한다(한쪽을 통째로 버리지 않는다).',
354
417
  `- 같은 목적의 중복(예: 같은 테스트·같은 함수)은 **theirs(${mergeTarget}) 버전을 채택**하고 ours 의 중복은 제거.`,
@@ -0,0 +1,53 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { runGitCommand } from '../../git-fs/runner.js';
4
+
5
+ export interface MergeIntent {
6
+ ours: string | null;
7
+ theirs: string[];
8
+ }
9
+
10
+ export type IntentGit = (worktreePath: string, args: string[]) => { status: number | null; stdout: string };
11
+
12
+ export const defaultIntentGit: IntentGit = (worktreePath, args) => {
13
+ const result = runGitCommand(worktreePath, args, { encoding: 'utf8' });
14
+ return { status: result.status, stdout: result.stdout ?? '' };
15
+ };
16
+
17
+ /** Read only commits introduced on each side of the merge base; a missing ref/document never blocks resolution. */
18
+ export function collectMergeIntent({ worktreePath, filePath, mergeTarget, git }: {
19
+ worktreePath: string;
20
+ filePath: string;
21
+ mergeTarget: string;
22
+ git: IntentGit;
23
+ }): MergeIntent {
24
+ const read = (...args: string[]): string | null => {
25
+ try {
26
+ const result = git(worktreePath, args);
27
+ return result.status === 0 ? result.stdout.trim() : null;
28
+ } catch { return null; }
29
+ };
30
+ const base = read('merge-base', 'HEAD', mergeTarget);
31
+ if (!base) return { ours: null, theirs: [] };
32
+
33
+ let ours: string | null = null;
34
+ const added = read('log', '--reverse', '--diff-filter=A', '--format=', '--name-only', '-z', `${base}..HEAD`, '--', 'docs/goals/');
35
+ for (const goal of added?.split('\0').map((file) => file.trim()).filter((file) => /^docs\/goals\/[^/]+$/.test(file)) ?? []) {
36
+ try {
37
+ const content = readFileSync(join(worktreePath, goal), 'utf8');
38
+ const lines = content.split(/\r?\n/);
39
+ const title = lines.find((line) => /^#\s+\S/.test(line))?.replace(/^#\s+/, '').trim()
40
+ ?? lines.find((line) => line.trim().length > 0)?.trim();
41
+ const situation = lines.find((line) => /^Situation:/.test(line))?.trim();
42
+ const complication = lines.find((line) => /^Complication:/.test(line))?.trim();
43
+ ours = [title, situation, complication].filter(Boolean).join('\n') || null;
44
+ if (ours) break;
45
+ } catch { /* missing goal: try the next added document */ }
46
+ }
47
+ if (!ours) {
48
+ const subjects = read('log', '-3', '--format=%s', `${base}..HEAD`);
49
+ ours = subjects || null;
50
+ }
51
+ const subjects = read('log', '-5', '--format=%s', `${base}..${mergeTarget}`, '--', filePath);
52
+ return { ours, theirs: subjects ? subjects.split('\n') : [] };
53
+ }