elanous 0.2.17 → 0.2.18

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 (309) 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/P6YTVgzuZz5oTZhY4Din_/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{1307-aac7332576c4d6fc.js → 1307-30dff22c9e1f3073.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{6158.9c0449a9a8d0064b.js → 6158.af2e93b7adff709b.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-3143b07a817d1da9.js → page-d92f7ee3994e84c7.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-17ba086ef82d339b.js → page-4733c967b56a4277.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/board/{page-7eb661ff23af7da2.js → page-d22fd849cc22f104.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/botlab/page-bb742f995c6769e7.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/bots/{page-6e5c3a867557298b.js → page-02b073a2ac793b83.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/consult/{page-5e77cd1802567c89.js → page-a0eb02638b1e6f31.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/control/page-308111abadf35670.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-4187666132fc98c4.js → page-5d97b2901e0d1ea6.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/app/editor/{page-09328d499162bae4.js → page-c39554c321576982.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/exec/{page-29ff3a8f84f03b6a.js → page-8c3d8e63ae2e1c98.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/field/{page-80ec7401bc83f511.js → page-9b72a07d424701c6.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/intake/{page-fc7d86467291ae38.js → page-a572f58cd3ca5864.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/loops/{page-0e2d6985fb42cd2f.js → page-c130b63862437814.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/market/{page-13e4108999f58d13.js → page-fa244bd146a00ae8.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-e8296ee284aa1293.js → page-44d56d6391965a6a.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/morning/{page-043a9111e74f6b24.js → page-83a700d4c754d802.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/{not-found-23a4601ceb7f452e.js → not-found-7b3170f374f77b9d.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-10d958605a77b3bd.js → page-a3c58f5c72a5f3f9.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-7a1d85e121065cc8.js → page-e8e1ecaea7cd71d2.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-12916918988ac97e.js → page-1467047104141297.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d8a3347a9d0c3f4a.js → page-2da6f9b8a38c6978.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-1d824510df025d18.js → page-44d5f767491692bf.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/share/{page-7f49fe645d5132f7.js → page-3cc47dca0922ab9a.js} +1 -1
  55. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-27d71f131acd9320.js → page-b17cd46cf0e9b8cf.js} +1 -1
  57. package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/trace/{page-424659bacfb8cdd7.js → page-7bf54c9bb9274af8.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-5d4fef9ccd7d406b.js → page-134da468538baf48.js} +1 -1
  64. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/{webpack-539cde99554be20f.js → webpack-c2782b330447ffb6.js} +1 -1
  66. package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +1 -0
  67. package/apps/pwa/out/approvals/index.html +1 -1
  68. package/apps/pwa/out/approvals/index.txt +14 -14
  69. package/apps/pwa/out/autopilot/index.html +1 -1
  70. package/apps/pwa/out/autopilot/index.txt +14 -14
  71. package/apps/pwa/out/board/index.html +1 -1
  72. package/apps/pwa/out/board/index.txt +14 -14
  73. package/apps/pwa/out/botlab/index.html +1 -1
  74. package/apps/pwa/out/botlab/index.txt +14 -14
  75. package/apps/pwa/out/bots/index.html +1 -1
  76. package/apps/pwa/out/bots/index.txt +14 -14
  77. package/apps/pwa/out/ceo/index.html +1 -1
  78. package/apps/pwa/out/ceo/index.txt +14 -14
  79. package/apps/pwa/out/chat/index.html +1 -1
  80. package/apps/pwa/out/chat/index.txt +14 -14
  81. package/apps/pwa/out/consult/index.html +1 -1
  82. package/apps/pwa/out/consult/index.txt +14 -14
  83. package/apps/pwa/out/control/index.html +1 -1
  84. package/apps/pwa/out/control/index.txt +14 -14
  85. package/apps/pwa/out/decisions/index.html +1 -0
  86. package/apps/pwa/out/decisions/index.txt +30 -0
  87. package/apps/pwa/out/design-check/index.html +1 -1
  88. package/apps/pwa/out/design-check/index.txt +14 -14
  89. package/apps/pwa/out/editor/index.html +1 -1
  90. package/apps/pwa/out/editor/index.txt +14 -14
  91. package/apps/pwa/out/exec/index.html +1 -1
  92. package/apps/pwa/out/exec/index.txt +14 -14
  93. package/apps/pwa/out/field/index.html +1 -1
  94. package/apps/pwa/out/field/index.txt +14 -14
  95. package/apps/pwa/out/healthz +1 -1
  96. package/apps/pwa/out/index.html +1 -1
  97. package/apps/pwa/out/index.txt +14 -14
  98. package/apps/pwa/out/inside/index.html +1 -1
  99. package/apps/pwa/out/inside/index.txt +14 -14
  100. package/apps/pwa/out/intake/index.html +1 -1
  101. package/apps/pwa/out/intake/index.txt +14 -14
  102. package/apps/pwa/out/live/index.html +1 -1
  103. package/apps/pwa/out/live/index.txt +14 -14
  104. package/apps/pwa/out/loops/index.html +1 -1
  105. package/apps/pwa/out/loops/index.txt +14 -14
  106. package/apps/pwa/out/market/index.html +1 -1
  107. package/apps/pwa/out/market/index.txt +14 -14
  108. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  109. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  110. package/apps/pwa/out/morning/index.html +1 -1
  111. package/apps/pwa/out/morning/index.txt +14 -14
  112. package/apps/pwa/out/observatory/index.html +1 -1
  113. package/apps/pwa/out/observatory/index.txt +14 -14
  114. package/apps/pwa/out/ops/checklist/index.html +1 -1
  115. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  116. package/apps/pwa/out/ops/release/index.html +1 -1
  117. package/apps/pwa/out/ops/release/index.txt +14 -14
  118. package/apps/pwa/out/ops/seats/index.html +1 -1
  119. package/apps/pwa/out/ops/seats/index.txt +14 -14
  120. package/apps/pwa/out/outputs/index.html +1 -1
  121. package/apps/pwa/out/outputs/index.txt +14 -14
  122. package/apps/pwa/out/reflection/index.html +1 -1
  123. package/apps/pwa/out/reflection/index.txt +15 -15
  124. package/apps/pwa/out/scheduler/index.html +1 -1
  125. package/apps/pwa/out/scheduler/index.txt +14 -14
  126. package/apps/pwa/out/sessions/index.html +1 -1
  127. package/apps/pwa/out/sessions/index.txt +14 -14
  128. package/apps/pwa/out/settings/devices/index.html +1 -1
  129. package/apps/pwa/out/settings/devices/index.txt +14 -14
  130. package/apps/pwa/out/settings/index.html +1 -1
  131. package/apps/pwa/out/settings/index.txt +14 -14
  132. package/apps/pwa/out/setup/done/index.html +1 -1
  133. package/apps/pwa/out/setup/done/index.txt +14 -14
  134. package/apps/pwa/out/setup/index.html +1 -1
  135. package/apps/pwa/out/setup/index.txt +14 -14
  136. package/apps/pwa/out/share/index.html +1 -1
  137. package/apps/pwa/out/share/index.txt +14 -14
  138. package/apps/pwa/out/showroom/index.html +1 -1
  139. package/apps/pwa/out/showroom/index.txt +14 -14
  140. package/apps/pwa/out/tasks/index.html +1 -1
  141. package/apps/pwa/out/tasks/index.txt +14 -14
  142. package/apps/pwa/out/term/index.html +1 -1
  143. package/apps/pwa/out/term/index.txt +14 -14
  144. package/apps/pwa/out/today/index.html +1 -1
  145. package/apps/pwa/out/today/index.txt +14 -14
  146. package/apps/pwa/out/trace/index.html +1 -1
  147. package/apps/pwa/out/trace/index.txt +14 -14
  148. package/apps/pwa/out/vault/index.html +1 -1
  149. package/apps/pwa/out/vault/index.txt +14 -14
  150. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  151. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  152. package/apps/pwa/out/workflows/index.html +1 -1
  153. package/apps/pwa/out/workflows/index.txt +14 -14
  154. package/apps/pwa/out/workspace/index.html +1 -1
  155. package/apps/pwa/out/workspace/index.txt +14 -14
  156. package/apps/pwa/out/worktrees/index.html +1 -1
  157. package/apps/pwa/out/worktrees/index.txt +14 -14
  158. package/package.json +2 -1
  159. package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
  160. package/samples/workflows/templates/chat-driven-research.yaml +35 -0
  161. package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
  162. package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
  163. package/samples/workflows/templates/manual-quickstart.yaml +21 -0
  164. package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
  165. package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
  166. package/scripts/ci-public-export-import-gate.ts +7 -2
  167. package/scripts/ci-typecheck-changed.ts +21 -6
  168. package/scripts/discord-seat-probe.ts +3 -2
  169. package/scripts/docs/generate-cli-anatomy.ts +162 -0
  170. package/scripts/goals/authoring-metrics.ts +73 -0
  171. package/scripts/landing-heal/must-fix-match.ts +7 -7
  172. package/scripts/landing-heal/run-mission.ts +3 -2
  173. package/scripts/public-export-test-run.ts +37 -2
  174. package/scripts/release-loop/checklist-gate-node.ts +13 -11
  175. package/scripts/release-loop/cut-branch.ts +2 -1
  176. package/scripts/release-loop/cutoff.ts +4 -2
  177. package/scripts/release-loop/docs-land-node.ts +8 -1
  178. package/scripts/release-loop/docs-node.ts +1 -1
  179. package/scripts/release-loop/gate-node.ts +99 -14
  180. package/scripts/release-loop/node-verdict.ts +2 -1
  181. package/scripts/release-loop/npm-publish-node.ts +11 -7
  182. package/scripts/release-loop/ops-upgrade-node.ts +8 -1
  183. package/scripts/release-loop/preflight.ts +246 -0
  184. package/scripts/release-loop/publish-node.ts +8 -1
  185. package/scripts/release-loop/release-readiness.ts +11 -6
  186. package/scripts/release-loop/release-version.ts +48 -0
  187. package/scripts/release-loop/resume-release.ts +115 -0
  188. package/scripts/release-loop/unattended-release.ts +43 -7
  189. package/scripts/release-loop/version-node.ts +145 -40
  190. package/scripts/rhythm/daily.ts +25 -4
  191. package/scripts/skills/public-check.ts +62 -12
  192. package/scripts/test-deterministic.ts +51 -33
  193. package/scripts/test-diet/effectiveness.ts +62 -1
  194. package/scripts/test-diet/lib.ts +1 -1
  195. package/scripts/test-diet/node.ts +8 -1
  196. package/src/agent-mission/review-loop.ts +4 -3
  197. package/src/agent-substrate/pr-comment-meta.ts +20 -0
  198. package/src/autopilot/build/llm-conflict-merge.ts +28 -8
  199. package/src/briefing/brief-items.ts +86 -17
  200. package/src/cli/autopilot-cli.ts +30 -11
  201. package/src/cli/brief-cli.ts +36 -2
  202. package/src/cli/freeze-cli.ts +3 -2
  203. package/src/cli/intake-cli.ts +73 -1
  204. package/src/cli/notify-cli.ts +75 -0
  205. package/src/cli/provider-cli.ts +12 -2
  206. package/src/cli/release-cli.ts +101 -17
  207. package/src/cli/tasks-cli.ts +309 -3
  208. package/src/context-bus/condense.ts +19 -3
  209. package/src/context-bus/context-day.ts +77 -24
  210. package/src/context-bus/context-first.ts +33 -0
  211. package/src/discord-self-message.ts +20 -2
  212. package/src/discord-slash-wire.ts +1 -0
  213. package/src/domains/outbound-alert.ts +200 -27
  214. package/src/domains/outbound-log-probe.ts +16 -1
  215. package/src/domains/release-tool.ts +2 -2
  216. package/src/execution-loop/gates.ts +11 -2
  217. package/src/flow-loop/split.ts +8 -4
  218. package/src/git-fs/worktree.ts +23 -3
  219. package/src/harness/harness-cli-command.ts +10 -2
  220. package/src/harness/harness-queue.ts +388 -33
  221. package/src/harness/helper-scan.ts +10 -3
  222. package/src/hq/seats.ts +369 -0
  223. package/src/index.ts +133 -15
  224. package/src/intake-plane/adopt-gate.ts +49 -0
  225. package/src/intake-plane/check.ts +3 -0
  226. package/src/intake-plane/collect-github.ts +57 -25
  227. package/src/intake-plane/discord-seat-work.ts +8 -2
  228. package/src/intake-plane/requirement-funnel.ts +316 -0
  229. package/src/knowledge/know-find.ts +10 -1
  230. package/src/loops/budget.ts +36 -0
  231. package/src/loops/orchestrator/finish-rate.ts +73 -1
  232. package/src/loops/orchestrator/grid-place.ts +11 -0
  233. package/src/loops/orchestrator/tick.ts +295 -20
  234. package/src/maturity/feature-maturity.ts +3 -2
  235. package/src/nexus/api/decisions-route.ts +11 -1
  236. package/src/nexus/api/loop-edges.ts +69 -13
  237. package/src/nexus/api/ops-seats-sources.ts +2 -2
  238. package/src/nexus/api/workflow-templates.ts +3 -0
  239. package/src/oauth/codex.ts +38 -0
  240. package/src/onboarding/first-chat-gate.ts +29 -0
  241. package/src/release-loop/checklist.ts +104 -6
  242. package/src/release-loop/feature-store.ts +10 -9
  243. package/src/release-loop/landing-freeze.ts +6 -4
  244. package/src/release-loop/placement.ts +13 -3
  245. package/src/release-loop/rubric.ts +80 -0
  246. package/src/seat-dispatch/seat-ask.ts +79 -16
  247. package/src/seat-loop/seat-loop.ts +12 -6
  248. package/src/self-dev/ask-launch-flow.ts +24 -3
  249. package/src/self-dev/ask-path-candidates.ts +68 -0
  250. package/src/self-dev/dev-pipeline.ts +4 -0
  251. package/src/self-dev/draft-triage-rules.ts +1 -1
  252. package/src/self-dev/orchestrate-cli.ts +22 -0
  253. package/src/self-dev/round-pr-supersede.ts +244 -0
  254. package/src/self-dev/run-supervisor.ts +14 -0
  255. package/src/self-implement/follow-up-goals.ts +2 -2
  256. package/src/self-implement/gate-remote.ts +427 -0
  257. package/src/self-implement/goal-author.ts +8 -3
  258. package/src/self-implement/l8-merge-queue.ts +29 -5
  259. package/src/self-implement/orchestrator.ts +59 -12
  260. package/src/self-implement/prior-draft-findings.ts +4 -3
  261. package/src/skills/skill-feedback.ts +76 -0
  262. package/src/task-agent/actions.ts +39 -6
  263. package/src/task-agent/judge.ts +31 -5
  264. package/src/task-agent/mission.ts +423 -0
  265. package/src/task-agent/shadow.ts +90 -0
  266. package/src/task-agent/task-hand.ts +235 -0
  267. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +10 -0
  268. package/src/task-orchestrator/surfaces/self-implement-pod.ts +86 -18
  269. package/src/telegram-loops-command.ts +1 -1
  270. package/src/telegram.ts +5 -20
  271. package/src/user-config.ts +63 -3
  272. package/src/version/packed-revision.json +1 -1
  273. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
  274. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
  275. package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
  276. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
  277. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
  278. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
  279. package/apps/pwa/out/_next/static/chunks/6877-fe94e76a5841ee9b.js +0 -1
  280. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
  281. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
  282. package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +0 -1
  283. package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
  284. package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +0 -1
  285. package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +0 -1
  286. package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +0 -1
  287. package/apps/pwa/out/_next/static/chunks/app/chat/page-9a41d65355fc25b9.js +0 -1
  288. package/apps/pwa/out/_next/static/chunks/app/control/page-41afec0b255ed2bb.js +0 -1
  289. package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +0 -1
  290. package/apps/pwa/out/_next/static/chunks/app/layout-de43a186e5ba5683.js +0 -1
  291. package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +0 -1
  292. package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +0 -1
  293. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +0 -1
  294. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a04260b8ccc968d4.js +0 -1
  295. package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +0 -1
  296. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +0 -1
  297. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-5a64cb5208c6a8fd.js +0 -1
  298. package/apps/pwa/out/_next/static/chunks/app/settings/page-1407ec83ea64c1f9.js +0 -1
  299. package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +0 -1
  300. package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +0 -1
  301. package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +0 -1
  302. package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +0 -1
  303. package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +0 -1
  304. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-fc2df261f8a2c7df.js +0 -1
  305. package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +0 -1
  306. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +0 -1
  307. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
  308. package/apps/pwa/out/_next/static/yRA5ur6DcPl0YHSRheH2O/_buildManifest.js +0 -1
  309. /package/apps/pwa/out/_next/static/{yRA5ur6DcPl0YHSRheH2O → P6YTVgzuZz5oTZhY4Din_}/_ssgManifest.js +0 -0
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env bun
2
- /** Static readiness inventory for the public (core) skills; never executes a skill. */
3
- import { existsSync, lstatSync, readdirSync, readFileSync } from 'node:fs';
4
- import { join, relative } from 'node:path';
2
+ /** Public (core) skill readiness inventory; optional builds never execute a skill. */
3
+ import { cpSync, existsSync, lstatSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { basename, isAbsolute, join, relative, sep, win32 } from 'node:path';
5
6
  import { computeSkillBoundaries, type SkillBoundaryReport } from '../skill-boundary.js';
6
7
 
7
8
  export type PublicCheckKey = 'header' | 'entry' | 'free' | 'portable' | 'install';
8
9
  export interface PublicSkillRow {
9
10
  readonly skill: string;
10
- readonly checks: Readonly<Record<PublicCheckKey, boolean | 'N/A'>>;
11
+ readonly checks: Readonly<Record<PublicCheckKey, boolean | 'N/A'> & { build?: SkillBuildResult }>;
11
12
  readonly violations: readonly string[];
12
13
  }
13
14
  export interface PublicCheckReport {
@@ -68,6 +69,44 @@ function executableEntries(files: readonly string[], dir: string, docs: string):
68
69
  && (commanded.has(file) || file.startsWith('bin/') || ENTRY_NAME.test(file)));
69
70
  }
70
71
 
72
+ export type SkillBuildResult = 'OK' | 'FAIL' | 'N/A';
73
+
74
+ /** Build only selected TS/JS entries from an isolated skill copy, never from the repository. */
75
+ export function buildSkillEntries(
76
+ dir: string,
77
+ entries: readonly string[],
78
+ run: (args: string[], cwd: string) => boolean = (args, cwd) => Bun.spawnSync(args, {
79
+ cwd, stdout: 'ignore', stderr: 'ignore',
80
+ }).exitCode === 0,
81
+ ): SkillBuildResult {
82
+ const buildable = entries.filter((entry) => /\.(?:ts|tsx|js|mjs|cjs)$/iu.test(entry));
83
+ if (buildable.length === 0) return 'N/A';
84
+ if (buildable.some((entry) => isAbsolute(entry) || win32.isAbsolute(entry) || entry.split(/[\\/]/u).includes('..'))) return 'FAIL';
85
+ const temp = mkdtempSync(join(tmpdir(), 'skill-public-build-'));
86
+ try {
87
+ const skill = join(temp, 'skill');
88
+ cpSync(dir, skill, {
89
+ recursive: true,
90
+ filter: (source) => source === dir || !['.git', 'node_modules', '__pycache__'].includes(basename(source)) && !lstatSync(source).isSymbolicLink(),
91
+ });
92
+ const copiedRoot = realpathSync(skill);
93
+ for (const entry of buildable) {
94
+ const target = realpathSync(join(skill, entry));
95
+ const inside = relative(copiedRoot, target);
96
+ if (inside === '..' || inside.startsWith(`..${sep}`) || isAbsolute(inside) || !lstatSync(target).isFile()) return 'FAIL';
97
+ }
98
+ if (existsSync(join(skill, 'package.json')) && !run(['bun', 'install', '--ignore-scripts'], skill)) return 'FAIL';
99
+ for (const [index, entry] of buildable.entries()) {
100
+ if (!run(['bun', 'build', entry, '--target=bun', '--outfile', join(temp, `entry-${index}.js`)], skill)) return 'FAIL';
101
+ }
102
+ return 'OK';
103
+ } catch {
104
+ return 'FAIL';
105
+ } finally {
106
+ rmSync(temp, { recursive: true, force: true });
107
+ }
108
+ }
109
+
71
110
  function freeValid(doc: string, entries: ReadonlySet<string>): boolean {
72
111
  const section = /(?:^|\n)#{1,4}\s*[^\n]*(?:무료|free)[^\n]*\n([\s\S]*?)(?=\n#{1,4}\s|$)/giu;
73
112
  for (const match of doc.matchAll(section)) {
@@ -97,7 +136,7 @@ function installValid(doc: string, dir: string): boolean {
97
136
  }
98
137
 
99
138
  /** `boundary` is injectable for pure fixture tests; production always derives it from the repository. */
100
- export function checkPublicSkills(root: string, boundary: SkillBoundaryReport = computeSkillBoundaries(root)): PublicCheckReport {
139
+ export function checkPublicSkills(root: string, boundary: SkillBoundaryReport = computeSkillBoundaries(root), build = false): PublicCheckReport {
101
140
  const errors = [...boundary.errors];
102
141
  const skills: PublicSkillRow[] = [];
103
142
  const skillsDir = join(root, 'skills');
@@ -133,6 +172,7 @@ export function checkPublicSkills(root: string, boundary: SkillBoundaryReport =
133
172
  free: freeValid(docs, entries),
134
173
  portable: matches.length === 0,
135
174
  install: installValid(docs, dir),
175
+ ...(build ? { build: buildSkillEntries(dir, [...entries]) } : {}),
136
176
  };
137
177
  const violations = [
138
178
  ...(!checks.header ? ['SKILL.md 머리(name·description) 없음/불일치'] : []),
@@ -140,6 +180,7 @@ export function checkPublicSkills(root: string, boundary: SkillBoundaryReport =
140
180
  ...(!checks.free ? ['키/.env 없이 쓰는 무료 경로가 문서에 없음'] : []),
141
181
  ...matches.map((file) => `${file}: 개인 경로/내부 표지 문자열`),
142
182
  ...(!checks.install ? ['의존 manifest와 문서의 한 줄 설치 명령이 없음'] : []),
183
+ ...(checks.build === 'FAIL' ? ['스킬 실행 파일 빌드 실패'] : []),
143
184
  ];
144
185
  skills.push({ skill, checks, violations });
145
186
  }
@@ -147,23 +188,32 @@ export function checkPublicSkills(root: string, boundary: SkillBoundaryReport =
147
188
  }
148
189
 
149
190
  export function formatPublicCheck(report: PublicCheckReport): string {
150
- const columns: PublicCheckKey[] = ['header', 'entry', 'free', 'portable', 'install'];
191
+ const columns: (PublicCheckKey | 'build')[] = ['header', 'entry', 'free', 'portable', 'install'];
192
+ if (report.skills.some((row) => row.checks.build !== undefined)) columns.push('build');
151
193
  return [
152
194
  `skill ${columns.map((key) => key.padEnd(9)).join(' ')} result`,
153
- ...report.skills.map((row) => `${row.skill.padEnd(34)} ${columns.map((key) => (row.checks[key] === 'N/A' ? 'N/A' : row.checks[key] ? 'OK' : 'FAIL').padEnd(9)).join(' ')} ${row.violations.length ? row.violations.join('; ') : 'OK'}`),
195
+ ...report.skills.map((row) => `${row.skill.padEnd(34)} ${columns.map((key) => { const value = row.checks[key]; return (typeof value === 'string' ? value : value ? 'OK' : 'FAIL').padEnd(9); }).join(' ')} ${row.violations.length ? row.violations.join('; ') : 'OK'}`),
154
196
  ...report.errors.map((error) => `ERROR ${error}`),
155
197
  `PUBLIC-CHECK ${report.ok ? 'PASS' : 'FAIL'} (${report.skills.length} public skills)`,
156
198
  ].join('\n');
157
199
  }
158
200
 
201
+ export function runPublicCheck(root: string, args: readonly string[], boundary?: SkillBoundaryReport): { output: string; exitCode: number } {
202
+ const report = checkPublicSkills(root, boundary, args.includes('--build'));
203
+ return {
204
+ output: args.includes('--json') ? JSON.stringify(report, null, 2) : formatPublicCheck(report),
205
+ exitCode: report.ok ? 0 : 1,
206
+ };
207
+ }
208
+
159
209
  if (import.meta.main) {
160
210
  const args = process.argv.slice(2);
161
- if (args.some((arg) => arg !== '--json')) {
162
- console.error('Usage: bun scripts/skills/public-check.ts [--json]');
211
+ if (args.some((arg) => arg !== '--json' && arg !== '--build')) {
212
+ console.error('Usage: bun scripts/skills/public-check.ts [--json] [--build]');
163
213
  process.exitCode = 2;
164
214
  } else {
165
- const report = checkPublicSkills(join(import.meta.dir, '..', '..'));
166
- console.log(args.includes('--json') ? JSON.stringify(report, null, 2) : formatPublicCheck(report));
167
- if (!report.ok) process.exitCode = 1;
215
+ const result = runPublicCheck(join(import.meta.dir, '..', '..'), args);
216
+ console.log(result.output);
217
+ process.exitCode = result.exitCode;
168
218
  }
169
219
  }
@@ -57,7 +57,10 @@ const HARNESS_TEST_ENV_KEYS = [
57
57
  // Discovery: rg -n "process\.env\.ELANOUS_(HOST|RUN|ORIGIN|SUPERVISOR|PARENT)" src
58
58
  // These are ambient execution identities, not inputs to a deterministic test.
59
59
  // debug.log also attributes SUBSTRATE and ARM_ID to every plain-object record.
60
- const GIT_LOCATION_ENV_KEYS = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR'] as const;
60
+ export const GIT_LOCATION_ENV_KEYS = [
61
+ 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR', 'GIT_INDEX_FILE',
62
+ 'GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
63
+ ] as const;
61
64
 
62
65
  const EXECUTION_ORIGIN_ENV_KEYS = [
63
66
  'ELANOUS_HOST_ID',
@@ -123,7 +126,7 @@ export type DeterministicRunnerDeps = {
123
126
  childGraceMs?: number;
124
127
  killProcessGroup?: KillProcessGroup;
125
128
  getProcessGroupId?: GetProcessGroupId;
126
- gitConfig?: (cwd: string, args: string[]) => string;
129
+ gitConfig?: (cwd: string, args: string[]) => string | undefined;
127
130
  };
128
131
 
129
132
  export function isLivePid(pid: number): boolean {
@@ -273,14 +276,17 @@ export async function terminateDirectChild(
273
276
  reportVisible(`failed to terminate child pid=${pid}`, report);
274
277
  }
275
278
 
276
- function gitConfig(cwd: string, args: string[]): string {
279
+ function gitConfig(cwd: string, args: string[]): string | undefined {
277
280
  const env = { ...process.env };
278
281
  for (const key of GIT_LOCATION_ENV_KEYS) delete env[key];
279
282
  const result = spawnSync('git', args, { cwd, env, encoding: 'utf8' });
280
283
  if (result.error || (result.status !== 0 && !(result.status === 1 && args.includes('--get-all') && !result.stderr))) {
281
284
  throw new Error(`git ${args.join(' ')} failed: ${result.error?.message ?? result.stderr}`);
282
285
  }
283
- return result.stdout.trim();
286
+ if (result.status === 1) return undefined;
287
+ // --null --get-all terminates each complete value with NUL, even when
288
+ // a value itself contains newlines or is empty.
289
+ return args.includes('--get-all') ? result.stdout : result.stdout.trim();
284
290
  }
285
291
 
286
292
  export function prepareIsolatedTestEnv(sourceEnv: NodeJS.ProcessEnv, testRoot: string): NodeJS.ProcessEnv {
@@ -406,33 +412,45 @@ export async function runDeterministicTests(deps: DeterministicRunnerDeps = {}):
406
412
  const cwd = deps.cwd ?? process.cwd();
407
413
  const config = deps.gitConfig ?? gitConfig;
408
414
  const commonDir = config(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
415
+ if (commonDir === undefined) throw new Error('git common directory is unset');
409
416
  const configFile = join(resolve(cwd, commonDir), 'config');
410
- const bareArgs = ['config', '--file', configFile];
411
- const readBare = () => config(cwd, [...bareArgs, '--get-all', 'core.bare']);
412
- const bareBefore = readBare();
413
- let bareChanged = false;
414
- let bareChecked = false;
415
- const checkBare = (): void => {
416
- bareChecked = true;
417
- let bareAfter = '<unreadable>';
418
- const visible = (values: string) => values ? values.replaceAll('\n', ',') : '<unset>';
419
- try {
420
- bareAfter = readBare();
421
- if (bareAfter === bareBefore) return;
422
- bareChanged = true;
423
- const originalValues = bareBefore ? bareBefore.split('\n') : [];
424
- if (originalValues.length > 0) {
425
- config(cwd, [...bareArgs, '--replace-all', 'core.bare', originalValues[0]!]);
426
- for (const value of originalValues.slice(1)) config(cwd, [...bareArgs, '--add', 'core.bare', value]);
427
- } else if (bareAfter) {
428
- config(cwd, [...bareArgs, '--unset-all', 'core.bare']);
417
+ const configArgs = ['config', '--file', configFile];
418
+ const watchedKeys = ['core.bare', 'user.name', 'user.email'] as const;
419
+ // Legacy injected gitConfig fixtures supply newline-delimited results and
420
+ // return '' for an absent key. Real Git uses NUL-delimited complete values.
421
+ const readConfig = (key: string): string[] => {
422
+ const value = config(cwd, [...configArgs, '--null', '--get-all', key]);
423
+ if (value === undefined || (deps.gitConfig && value === '')) return [];
424
+ if (deps.gitConfig && !value.includes('\0')) return value.split('\n');
425
+ if (!value.endsWith('\0')) throw new Error(`unterminated git config value for ${key}`);
426
+ return value.slice(0, -1).split('\0');
427
+ };
428
+ const before = new Map(watchedKeys.map((key) => [key, readConfig(key)]));
429
+ let configChanged = false;
430
+ let configChecked = false;
431
+ const checkConfig = (): void => {
432
+ configChecked = true;
433
+ const visible = (values: string[]) => values.length === 0 ? '<unset>' : values.map((value) => value === '' ? '<empty>' : value.replaceAll('\n', ',')).join(',');
434
+ for (const key of watchedKeys) {
435
+ const original = before.get(key)!;
436
+ let after: string[] = ['<unreadable>'];
437
+ try {
438
+ after = readConfig(key);
439
+ if (JSON.stringify(after) === JSON.stringify(original)) continue;
440
+ configChanged = true;
441
+ if (original.length > 0) {
442
+ config(cwd, [...configArgs, '--replace-all', key, original[0]!]);
443
+ for (const value of original.slice(1)) config(cwd, [...configArgs, '--add', key, value]);
444
+ } else {
445
+ config(cwd, [...configArgs, '--unset-all', key]);
446
+ }
447
+ const restored = readConfig(key);
448
+ if (JSON.stringify(restored) !== JSON.stringify(original)) throw new Error(`expected ${visible(original)}, got ${visible(restored)}`);
449
+ report(`[test-deterministic] ${key} changed ${visible(original)} -> ${visible(after)}; restored ${visible(original)}; failing run`);
450
+ } catch (error) {
451
+ configChanged = true;
452
+ report(`[test-deterministic] ${key} changed ${visible(original)} -> ${visible(after)}; restore failed: ${formatError(error)}`);
429
453
  }
430
- const restored = readBare();
431
- if (restored !== bareBefore) throw new Error(`expected ${visible(bareBefore)}, got ${visible(restored)}`);
432
- report(`[test-deterministic] core.bare changed ${visible(bareBefore)} -> ${visible(bareAfter)}; restored ${visible(bareBefore)}; failing run`);
433
- } catch (error) {
434
- bareChanged = true;
435
- report(`[test-deterministic] core.bare changed ${visible(bareBefore)} -> ${visible(bareAfter)}; restore failed: ${formatError(error)}`);
436
454
  }
437
455
  };
438
456
  // ⛔ 사람이 «경로»를 직접 준 창에서는 아무것도 빼지 않는다 — 그 창은 「이것만 돌려라」다.
@@ -494,7 +512,7 @@ export async function runDeterministicTests(deps: DeterministicRunnerDeps = {}):
494
512
  });
495
513
  cleanupTemporaryRoot(testRoot, { rmSync: deps.rmSync, report });
496
514
  rootCleanupAttempted = true;
497
- checkBare();
515
+ checkConfig();
498
516
  gate?.dispose();
499
517
  gate = undefined;
500
518
  if (deps.killSelf) {
@@ -509,8 +527,8 @@ export async function runDeterministicTests(deps: DeterministicRunnerDeps = {}):
509
527
  }
510
528
 
511
529
  cleanupRoot();
512
- checkBare();
513
- return bareChanged ? 1 : (outcome.code ?? 1);
530
+ checkConfig();
531
+ return configChanged ? 1 : (outcome.code ?? 1);
514
532
  } catch (error) {
515
533
  if (child) {
516
534
  try {
@@ -529,7 +547,7 @@ export async function runDeterministicTests(deps: DeterministicRunnerDeps = {}):
529
547
  } finally {
530
548
  gate?.dispose();
531
549
  cleanupRoot();
532
- if (!bareChecked) checkBare();
550
+ if (!configChecked) checkConfig();
533
551
  }
534
552
  }
535
553
 
@@ -1,7 +1,7 @@
1
1
  import { spawnSync } from 'node:child_process';
2
2
  import { cpSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, existsSync, statSync, writeFileSync } from 'node:fs';
3
3
  import { tmpdir } from 'node:os';
4
- import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
4
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
5
5
  import ts from 'typescript';
6
6
  import { pickRange } from './lib.js';
7
7
 
@@ -119,6 +119,67 @@ export function fixedCountAssertions(testPath: string, root: string): Array<{ li
119
119
  return assertions.sort((a, b) => a.line - b.line);
120
120
  }
121
121
 
122
+ // Read module specifiers from syntax, not from comments or strings; re-exports count for non-test importers.
123
+ function directImports(file: string, includeReexports = false): string[] {
124
+ const source = ts.createSourceFile(file, readFileSync(file, 'utf8'), ts.ScriptTarget.Latest, true);
125
+ const imports: string[] = [];
126
+ const visit = (node: ts.Node): void => {
127
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) imports.push(node.moduleSpecifier.text);
128
+ if (includeReexports && ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier))
129
+ imports.push(node.moduleSpecifier.text);
130
+ if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword
131
+ && node.arguments.length === 1 && ts.isStringLiteral(node.arguments[0]!)) imports.push(node.arguments[0]!.text);
132
+ ts.forEachChild(node, visit);
133
+ };
134
+ visit(source);
135
+ return imports;
136
+ }
137
+
138
+ /** Directly imported repository modules with no direct, tracked, non-test importer. */
139
+ export function unreachableImports(testPath: string, root: string, deps: {
140
+ importers?: (modulePath: string, root: string) => string[];
141
+ } = {}): string[] {
142
+ const repo = realpathSync(resolve(root));
143
+ const inside = (path: string) => path !== repo && !relative(repo, path).startsWith(`..${sep}`)
144
+ && relative(repo, path) !== '..' && !isAbsolute(relative(repo, path));
145
+ const validFile = (path: string) => inside(path) && existsSync(path) && realpathSync(path) === path && statSync(path).isFile();
146
+ const isTest = (path: string) => path.endsWith('.test.ts') || path === 'test' || path.startsWith(`test${sep}`);
147
+ const testFile = resolve(repo, testPath);
148
+ if (!validFile(testFile)) throw new Error('test path outside repository or symlinked');
149
+ const resolveImport = (from: string, specifier: string): string | undefined => {
150
+ if (!/^\.{1,2}\//.test(specifier)) return undefined;
151
+ const base = resolve(dirname(from), specifier);
152
+ const candidates = base.endsWith('.js')
153
+ ? [base.replace(/\.js$/, '.ts'), base, `${base}.ts`, join(base, 'index.ts')]
154
+ : [base, `${base}.ts`, join(base, 'index.ts')];
155
+ return candidates.find(validFile);
156
+ };
157
+ const importers = deps.importers ?? ((modulePath: string, rootPath: string): string[] => {
158
+ const stem = basename(modulePath).replace(/\.[^.]+$/, '');
159
+ const needles = stem === 'index' ? [stem, basename(dirname(modulePath))] : [stem];
160
+ const matches = new Set<string>();
161
+ for (const needle of needles) {
162
+ const found = spawnSync('git', ['-C', rootPath, 'grep', '-l', '-F', '--', needle], { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
163
+ if (found.error || (found.status !== 0 && found.status !== 1)) throw new Error(`importer lookup failed: ${found.error?.message ?? found.stderr}`);
164
+ for (const file of found.stdout.split('\n').filter(Boolean)) matches.add(file);
165
+ }
166
+ return [...matches];
167
+ });
168
+ const sources = new Set(directImports(testFile).map((specifier) => resolveImport(testFile, specifier))
169
+ .filter((path): path is string => path !== undefined && !isTest(relative(repo, path))));
170
+ const unreachable: string[] = [];
171
+ for (const source of sources) {
172
+ const modulePath = relative(repo, source);
173
+ const used = importers(modulePath, repo).some((candidate) => {
174
+ const path = resolve(repo, candidate);
175
+ if (!validFile(path) || isTest(relative(repo, path)) || path === source) return false;
176
+ return directImports(path, true).some((specifier) => resolveImport(path, specifier) === source);
177
+ });
178
+ if (!used) unreachable.push(modulePath.split(sep).join('/'));
179
+ }
180
+ return unreachable.sort();
181
+ }
182
+
122
183
  export function mutationProbe(file: string, deps: { root: string; run?: (root: string, file: string) => number | null; baselineStable?: boolean }): Mutation {
123
184
  if (deps.baselineStable === false) return 'n/a';
124
185
  const root = realpathSync(resolve(deps.root));
@@ -7,7 +7,7 @@ export const DEFAULT_COST_SECS = 10;
7
7
  export const SLOW_SECS = 60;
8
8
  export const HEAVY_MB = 2048;
9
9
 
10
- export interface Effectiveness { flake: 'stable' | 'flaky' | 'failing'; mutation: 'caught' | 'survived' | 'n/a'; fixedCounts?: number }
10
+ export interface Effectiveness { flake: 'stable' | 'flaky' | 'failing'; mutation: 'caught' | 'survived' | 'n/a'; fixedCounts?: number; unreachable?: number }
11
11
  export interface Measurement { file: string; secs: number; rssMb: number | null; rc: number | null; pass: number | null; fail: number | null; reason?: string; effectiveness?: Effectiveness }
12
12
  export type Verdict = 'keep' | 'failing' | 'review';
13
13
  export type Proposal = 'delete' | 'shrink' | 'rewrite-cheap' | 'move-out-of-gate' | 'investigate';
@@ -10,7 +10,7 @@ import { effectiveInstanceRoot } from '../../src/instance/resolve.js';
10
10
  import { CardStore } from '../../src/task-cards/card-store.js';
11
11
  import { GATE_NIGHTLY_AUDITS } from '../release-loop/gate-node.js';
12
12
  import { appendLedger, costTable, judge, lastLedgerLine, ledgerPath, nightlyAuditLedgerPath, pickRange, propose, td1Dispositions, writeCardDraft, type LedgerLine, type Measurement, type Td1Disposition } from './lib.js';
13
- import { fixedCountAssertions, flakeVerdict, sweepSlice, type Mutation, type TestRun } from './effectiveness.js';
13
+ import { fixedCountAssertions, flakeVerdict, sweepSlice, unreachableImports, type Mutation, type TestRun } from './effectiveness.js';
14
14
 
15
15
  type Context = { graphId?: string; input: Record<string, unknown>; outputs: Record<string, Record<string, unknown> | null> };
16
16
  function context(): Context {
@@ -123,6 +123,13 @@ function measure(ctx: Context, audit = false, effectiveness = false): number {
123
123
  file: measurement.file, reason: error instanceof Error ? error.message : String(error),
124
124
  });
125
125
  }
126
+ try {
127
+ measurement.effectiveness.unreachable = unreachableImports(measurement.file, process.cwd()).length;
128
+ } catch (error) {
129
+ debug.log('test-diet.effectiveness', 'unreachable-unreadable', {
130
+ file: measurement.file, reason: error instanceof Error ? error.message : String(error),
131
+ });
132
+ }
126
133
  debug.log('test-diet.effectiveness', 'judged', { file: measurement.file, ...measurement.effectiveness, secs: effect.secs });
127
134
  }
128
135
  }
@@ -22,7 +22,7 @@ import { budgetReviewerContext } from '../agent-substrate/pr-reviewer.js';
22
22
  import { DEFAULT_REVIEW_BACKEND } from '../agent-substrate/acp-reviewer.js';
23
23
  import { loadReviewerContext } from '../agent-substrate/self-review-cli.js';
24
24
  import { createRepositoryReferencedFileReader, type ReferencedFileReader } from '../self-implement/goal-file-reader.js';
25
- import { parse as parsePrCommentMeta } from '../agent-substrate/pr-comment-meta.js';
25
+ import { expandRecords, parse as parsePrCommentMeta } from '../agent-substrate/pr-comment-meta.js';
26
26
 
27
27
  export type ReviewVerdict = 'ok' | 'reinforce' | 'reject' | 'ambiguous';
28
28
  export type ReviewClassificationSource = 'llm' | 'llm-call-failure' | 'empty-review' | 'injected';
@@ -310,9 +310,10 @@ function analyzeAppliedReviewItems(comments: readonly NonNullable<LatestReviewSo
310
310
  const items: string[] = [];
311
311
  const seen = new Set<string>();
312
312
  let headlineComments = 0;
313
- const newestFirst = [...comments].sort((a, b) => (b.createdAt ?? '').localeCompare(a.createdAt ?? ''));
313
+ const newestFirst = comments.flatMap((comment) => expandRecords(comment.body ?? '').map((body) => ({ ...comment, body })))
314
+ .sort((a, b) => (b.createdAt ?? '').localeCompare(a.createdAt ?? ''));
314
315
  for (const comment of newestFirst) {
315
- const lines = (comment.body ?? '').split('\n');
316
+ const lines = comment.body.split('\n');
316
317
  const headlineIndex = parsePrCommentMeta(lines[0] ?? '') ? 1 : 0;
317
318
  const headline = lines[headlineIndex]?.trim() ?? '';
318
319
  if (!headline.startsWith(REVIEW_REINFORCEMENT_HEADLINE) && !headline.startsWith(ACP_REWORK_HEADLINE)) continue;
@@ -97,3 +97,23 @@ export function parse(body: string): PrCommentMeta | null {
97
97
  }
98
98
  return parsed;
99
99
  }
100
+
101
+ /** 상태 댓글의 details 에 쌓인 라운드 기록을 개별 댓글 본문으로 펼친다. */
102
+ export function expandRecords(body: string): string[] {
103
+ if (!/^<!-- elanous:run-status -->(?:\r?\n|$)/.test(body)) return [body];
104
+
105
+ const records: string[] = [];
106
+ for (const details of body.matchAll(/<details(?:\s[^>]*)?>([\s\S]*?)<\/details>/g)) {
107
+ const content = details[1];
108
+ const headers: Array<{ index: number; round?: number }> = [];
109
+ for (const header of content.matchAll(/^<!-- elanous-pr-comment v1 [^\r\n]* -->$/gm)) {
110
+ const meta = parse(header[0]);
111
+ if (meta) headers.push({ index: header.index, round: meta.round });
112
+ }
113
+ for (let i = 0; i < headers.length; i++) {
114
+ if (headers[i].round === undefined) continue;
115
+ records.push(content.slice(headers[i].index, headers[i + 1]?.index ?? content.length).replace(/(?:\r?\n)+$/, ''));
116
+ }
117
+ }
118
+ return records.length ? records : [body];
119
+ }
@@ -67,8 +67,10 @@ export interface LlmMergeOutcome {
67
67
  testDeclarationUnmeasured?: string[];
68
68
  /** 해결기 응답이 LLM 공급자 오류 문구였다 — 파일 내용이 아니다(🅢 2026-09-27 #21086: 세 파일이 통째로 그 문구가 됐다). */
69
69
  providerFailure?: string[];
70
- /** LLM 입력 상한 초과: 사람 수확으로 넘긴다. 원래 브랜치는 abort 후 보존된다. */
71
- reason?: 'conflict-input-too-large' | 'conflict-resolver-interrupted';
70
+ /** 충돌 미해결의 원인. 기존 두 원인에 마커 잔존·파일 읽기 실패를 추가한다. */
71
+ reason?: 'conflict-input-too-large' | 'conflict-resolver-interrupted' | 'conflict-markers-remain' | 'conflict-file-unreadable';
72
+ /** 해결이 멎은 충돌 파일(worktree 상대 경로). */
73
+ failedFile?: string;
72
74
  inputChars?: number;
73
75
  /** 병합 결과가 양쪽 판 중 작은 쪽의 절반 아래로 줄었다 — 통째로 날린 것으로 보고 해결 실패로 친다. */
74
76
  sizeCollapse?: Array<{ file: string; ours: number; theirs: number; merged: number }>;
@@ -107,6 +109,20 @@ function addSizeChange(size: LlmMergeSizeChange, file: string, before: string, a
107
109
 
108
110
  export function formatLlmMergeOutcome(outcome: LlmMergeOutcome): string {
109
111
  const files = outcome.resolvedFiles?.length ? ` (LLM 종합 ${outcome.resolvedFiles.length}파일: ${outcome.resolvedFiles.join(', ')})` : '';
112
+ if (outcome.status === 'conflict-unresolved') {
113
+ // ⛔ reason·failedFile 은 «선택» 칸이다 — 공급자 오류·시험 선언 유실·크기 붕괴 출구는 계약상 reason 이 없다.
114
+ // 있는 칸만 싣고, 그 출구들은 자기 실제 결과(어느 판정이 멈췄나)를 싣는다 — 「사유 undefined」 금지.
115
+ const parts = [
116
+ ...(outcome.reason ? [`사유 ${outcome.reason}`] : []),
117
+ ...(outcome.failedFile ? [`파일 ${outcome.failedFile}`] : []),
118
+ ...(outcome.providerFailure?.length ? [`공급자 오류 문구 ${outcome.providerFailure.join(', ')}`] : []),
119
+ ...(outcome.testDeclarationLoss?.length
120
+ ? [`시험 선언 유실 ${outcome.testDeclarationLoss.map((l) => `${l.file} ${l.ours}/${l.theirs}→${l.merged}`).join(', ')}`] : []),
121
+ ...(outcome.sizeCollapse?.length
122
+ ? [`크기 붕괴 ${outcome.sizeCollapse.map((c) => `${c.file} ${c.ours}/${c.theirs}→${c.merged}줄`).join(', ')}`] : []),
123
+ ];
124
+ return `${outcome.status}${files}${parts.length ? `; ${parts.join(' · ')}` : ''}`;
125
+ }
110
126
  if (outcome.status !== 'llm-resolved') return `${outcome.status}${files}`;
111
127
  const size = outcome.sizeChange;
112
128
  if (size === undefined) return `${outcome.status}${files}; 규모 변화: 못 쟀다`;
@@ -211,6 +227,10 @@ export async function mergeMainWithLlmResolve(
211
227
  ...outcome,
212
228
  ...(testDeclarationUnmeasured.length ? { testDeclarationUnmeasured } : {}),
213
229
  });
230
+ const unresolved = (outcome: LlmMergeOutcome & { status: 'conflict-unresolved'; failedFile: string }): LlmMergeOutcome => {
231
+ debug.log('self-dev.merge', 'conflict-unresolved', { reason: outcome.reason, failedFile: outcome.failedFile, resolvedCount: resolvedFiles.length });
232
+ return measuredOutcome(outcome);
233
+ };
214
234
  for (const f of files) {
215
235
  const abs = join(worktreePath, f);
216
236
  let conflicted: string;
@@ -219,7 +239,7 @@ export async function mergeMainWithLlmResolve(
219
239
  conflicted = git.readFile(abs);
220
240
  } catch {
221
241
  git.abort(worktreePath);
222
- return measuredOutcome({ status: 'conflict-unresolved', resolvedFiles });
242
+ return unresolved({ status: 'conflict-unresolved', reason: 'conflict-file-unreadable', failedFile: f, resolvedFiles });
223
243
  }
224
244
  try {
225
245
  // release/next.md: concurrent landings each append a line — keep both without asking the LLM (REL7c).
@@ -227,7 +247,7 @@ export async function mergeMainWithLlmResolve(
227
247
  merged = deterministic ?? await resolve(f, conflicted);
228
248
  } catch (error) {
229
249
  git.abort(worktreePath); // LLM 예외 → base 유지
230
- return measuredOutcome({ status: 'conflict-unresolved', resolvedFiles,
250
+ return unresolved({ status: 'conflict-unresolved', resolvedFiles, failedFile: f,
231
251
  ...(error instanceof MergeConflictInputTooLarge
232
252
  ? { reason: 'conflict-input-too-large' as const, inputChars: error.inputChars }
233
253
  : { reason: 'conflict-resolver-interrupted' as const }),
@@ -235,11 +255,11 @@ export async function mergeMainWithLlmResolve(
235
255
  }
236
256
  if (PROVIDER_FAILURE_TEXT.test(merged)) {
237
257
  git.abort(worktreePath); // 해결기가 공급자 오류 문구를 «내용»으로 돌려줬다 → base 유지
238
- return measuredOutcome({ status: 'conflict-unresolved', resolvedFiles, providerFailure: [f] });
258
+ return unresolved({ status: 'conflict-unresolved', resolvedFiles, failedFile: f, providerFailure: [f] });
239
259
  }
240
260
  if (hasConflictMarkers(merged)) {
241
261
  git.abort(worktreePath); // LLM 이 종합 못 함(마커 잔존) → base 유지
242
- return measuredOutcome({ status: 'conflict-unresolved', resolvedFiles });
262
+ return unresolved({ status: 'conflict-unresolved', reason: 'conflict-markers-remain', failedFile: f, resolvedFiles });
243
263
  }
244
264
  try {
245
265
  const ours = countTestDeclarations(git.readIndexStage(worktreePath, 2, f));
@@ -248,7 +268,7 @@ export async function mergeMainWithLlmResolve(
248
268
  if (mergedDeclarations < Math.min(ours, theirs)) {
249
269
  const testDeclarationLoss = [{ file: f, ours, theirs, merged: mergedDeclarations }];
250
270
  git.abort(worktreePath);
251
- return measuredOutcome({ status: 'conflict-unresolved', resolvedFiles, testDeclarationLoss });
271
+ return unresolved({ status: 'conflict-unresolved', resolvedFiles, failedFile: f, testDeclarationLoss });
252
272
  }
253
273
  } catch {
254
274
  testDeclarationUnmeasured.push(f);
@@ -260,7 +280,7 @@ export async function mergeMainWithLlmResolve(
260
280
  const smaller = Math.min(ours, theirs);
261
281
  if (smaller >= SIZE_COLLAPSE_MIN_LINES && mergedLines * 2 < smaller) {
262
282
  git.abort(worktreePath); // 양쪽 판 모두보다 절반 넘게 작다 → 통째로 날렸다 → base 유지
263
- return measuredOutcome({ status: 'conflict-unresolved', resolvedFiles, sizeCollapse: [{ file: f, ours, theirs, merged: mergedLines }] });
283
+ return unresolved({ status: 'conflict-unresolved', resolvedFiles, failedFile: f, sizeCollapse: [{ file: f, ours, theirs, merged: mergedLines }] });
264
284
  }
265
285
  } catch {
266
286
  /* 한쪽 판이 없는 충돌(삭제·추가) — 크기 비교 대상이 아니다 */