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
@@ -31,8 +31,32 @@
31
31
  import { importerTestsNotInRunSet, type ImporterTestIndex } from './importer-test-index.js';
32
32
  import type { RouteConsumerTestIndex } from './route-consumer-test-index.js';
33
33
  import { rankGateCallerCandidates } from './gate-candidate-ranking.js';
34
+ import { debug } from '../debug/log.js';
34
35
 
35
36
  export const GATE_CALLER_TEST_LIMIT = 30;
37
+
38
+ /** Command registries and their non-import-linked gate companions. Kept outside the caller cap. */
39
+ const PAIRED_REGISTRY_TESTS = {
40
+ registries: [
41
+ 'src/chat/index.ts',
42
+ 'src/telegram-commands.ts',
43
+ 'src/discord-slash-wire.ts',
44
+ 'src/index.ts',
45
+ ],
46
+ tests: [
47
+ 'src/maturity/feature-maturity.test.ts',
48
+ 'src/maturity/tui-slash-maturity.test.ts',
49
+ 'src/maturity/bot-command-maturity.test.ts',
50
+ 'src/maturity/cli-maturity.test.ts',
51
+ 'test/slash-filter.test.ts',
52
+ 'test/bot-slash-parity.test.ts',
53
+ ],
54
+ } as const;
55
+
56
+ /** 게이트 로그와 PR Gate 절에서 공통으로 쓰는 미검증 경고(파일 목록은 자르지 않는다). */
57
+ export function formatNoRelatedTestsWarning(files: readonly string[]): string {
58
+ return `⚠️ 동작 검증 0: ${files.join(', ')} — 사람 확인 필요`;
59
+ }
36
60
  export type GateCallerReason = 'import' | 'route';
37
61
 
38
62
  /** 테스트 파일 판정(기존 `seams.ts` 정규식과 동일 — 어휘 분기 금지). */
@@ -225,7 +249,7 @@ export function deriveRelatedTests(
225
249
  /** 왜 이 범위로 돌았나 — 로그만으로 답할 수 있게 하는 관측 필드(제1원칙).
226
250
  * ⚠️ 비-export 유지(리뷰 must-fix) — 소비처가 이 모듈 안뿐이다. 구조적 사용(호출부의 `scope.reason`)은
227
251
  * export 없이도 성립하므로 불필요한 공개 surface 를 만들지 않는다. */
228
- type GateScopeReason = 'changed-tests' | 'derived' | 'no-changes' | 'docs-only' | 'no-related-tests' | 'unmeasured';
252
+ type GateScopeReason = 'changed-tests' | 'derived' | 'paired-registry' | 'no-changes' | 'docs-only' | 'no-related-tests' | 'unmeasured';
229
253
 
230
254
  export type GateScopeMode = 'worktree' | 'postsync';
231
255
 
@@ -279,7 +303,7 @@ export function resolveGateScope(
279
303
  changed: readonly string[],
280
304
  exists: (path: string) => boolean,
281
305
  importerTestIndex?: ImporterTestIndex,
282
- opts?: { mode?: GateScopeMode; isDeleted?: (path: string) => boolean; routeConsumerTestIndex?: RouteConsumerTestIndex },
306
+ opts?: { mode?: GateScopeMode; isDeleted?: (path: string) => boolean; routeConsumerTestIndex?: RouteConsumerTestIndex; runChanged?: readonly string[]; round?: number },
283
307
  ): GateScopeDecision {
284
308
  // ⚠️ **실존 검증(리뷰 must-fix 4R)** — `gitChangedFiles` 는 `git diff --name-only HEAD` 라
285
309
  // **삭제·rename 前 경로도 포함**한다. 그걸 그대로 `testArgs` 로 넘기면 필터가 아무것도 매치하지
@@ -292,9 +316,9 @@ export function resolveGateScope(
292
316
  const missingTestFiles = changedTests.length - testFiles.length;
293
317
  const sourceFiles = userChanged.filter((f) => SOURCE_RE.test(f));
294
318
  const ignoredOutsideSrc = sourceFiles.filter((f) => !isTestFile(f) && !f.startsWith('src/')).length;
295
- /** ⭐ **문서가 아닌 사용자 변경** — 이게 비어야 docs-only 다(소스 부재가 아니라 **문서 전부**여야 한다). */
296
- const documentPaths = userChanged.filter(isDocPath);
297
- const nonDoc = userChanged.filter((f) => !isDocPath(f));
319
+ /** ⭐ **문서가 아닌 사용자 변경** — 시험 파일도 명시적으로 docs-only 대상에서 제외한다. */
320
+ const documentPaths = userChanged.filter((f) => isDocPath(f) && !isTestFile(f));
321
+ const nonDoc = userChanged.filter((f) => isTestFile(f) || !isDocPath(f));
298
322
  /**
299
323
  * ⭐ **이 변경 중 무엇이 검증되지 않나** — 분기와 **무관하게** 계산한다(리뷰 must-fix 2026-07-27).
300
324
  *
@@ -323,6 +347,8 @@ export function resolveGateScope(
323
347
  if (runSet.includes(f)) return true;
324
348
  if (opts?.isDeleted?.(f)) return true;
325
349
  if (importerTestIndex && (importerTestIndex.testsBySource.get(f) ?? []).some((t) => runSet.includes(t))) return true;
350
+ if (PAIRED_REGISTRY_TESTS.registries.some((registry) => registry === f)
351
+ && PAIRED_REGISTRY_TESTS.tests.some((test) => runSet.includes(test))) return true;
326
352
  const related = relatedFor(f);
327
353
  return related.length > 0 && related.every((t) => runSet.includes(t));
328
354
  };
@@ -354,7 +380,9 @@ export function resolveGateScope(
354
380
  importerTestIndex?.testsBySource ?? new Map());
355
381
  const callerTests = candidates.slice(0, GATE_CALLER_TEST_LIMIT);
356
382
  const callerTestsOverflow = candidates.slice(GATE_CALLER_TEST_LIMIT);
357
- const runWithCallers = (tests: readonly string[]) => [...new Set([...tests, ...callerTests.map(({ file }) => file)])];
383
+ const pairedRegistryTests = userChanged.some((file) => PAIRED_REGISTRY_TESTS.registries.some((registry) => registry === file))
384
+ ? PAIRED_REGISTRY_TESTS.tests.filter(exists) : [];
385
+ const runWithCallers = (tests: readonly string[]) => [...new Set([...tests, ...callerTests.map(({ file }) => file), ...pairedRegistryTests])];
358
386
  const base = { sourceFiles, documentPaths, ignoredOutsideSrc, missingTestFiles, elanousRuntimeArtifacts, callerTests, callerTestsOverflow,
359
387
  unverified: [] as readonly string[], derived: [] as readonly string[], pulledInRelatedTests: [] as readonly string[] };
360
388
  const withDocumentNonContribution = (runSet: readonly string[]) => ({
@@ -381,8 +409,17 @@ export function resolveGateScope(
381
409
  const derived = conventional;
382
410
  const runSet = runWithCallers(derived);
383
411
  if (runSet.length > 0) {
384
- return { ...base, ...withDocumentNonContribution(runSet), derived, testArgs: runSet, unverified: unverifiedFor(runSet), importerTestsNotRun: observationFor(runSet), skipTestStep: false, reason: 'derived' };
412
+ return { ...base, ...withDocumentNonContribution(runSet), derived, testArgs: runSet, unverified: unverifiedFor(runSet), importerTestsNotRun: observationFor(runSet), skipTestStep: false, reason: derived.length === 0 && callerTests.length === 0 ? 'paired-registry' : 'derived' };
413
+ }
414
+ // 마지막 라운드에 짝이 없어도 원 PR의 앞 라운드에서 편집한 소스·시험을 같은 규칙으로 한 번 더 잰다.
415
+ if (opts?.runChanged?.some((file) => !changed.includes(file))) {
416
+ const wholeRun = resolveGateScope([...new Set([...changed, ...opts.runChanged])], exists, importerTestIndex,
417
+ { ...opts, runChanged: undefined });
418
+ return wholeRun;
385
419
  }
386
- // 실행 집합이 비었으므로 비문서 전부가 미검증이다.
387
- return { ...base, ...withDocumentNonContribution([]), unverified: unverifiedFor([]), importerTestsNotRun: observationFor([]), skipTestStep: true, reason: 'no-related-tests' };
420
+ const unverified = unverifiedFor([]);
421
+ try {
422
+ debug.log('self-implement.gate-scope', 'no-related-tests', { files: unverified, round: opts?.round ?? null }, { level: 'warn' });
423
+ } catch { /* 관측 장애는 게이트 판정을 바꾸지 않는다. */ }
424
+ return { ...base, ...withDocumentNonContribution([]), unverified, importerTestsNotRun: observationFor([]), skipTestStep: true, reason: 'no-related-tests' };
388
425
  }
@@ -0,0 +1,222 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { appendFileSync, closeSync, copyFileSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, rmdirSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { basename, dirname, join, relative, resolve, sep } from 'node:path';
4
+ import { elanousStateRoot } from '../autopilot/state-paths.js';
5
+ import { debug } from '../debug/log.js';
6
+ import { isGoalAuthorFileName } from './goal-document.js';
7
+ import { hasTerminalRunStatus, loadRunLedger, queryUnfinishedRunLedgers, runLedgerDir } from './run-ledger.js';
8
+
9
+ export interface GoalArchiveOptions {
10
+ repoRoot?: string;
11
+ stateRoot?: string;
12
+ before?: string;
13
+ apply?: boolean;
14
+ trustDocRecord?: boolean;
15
+ /** Failure-injection seam for the two write stages (copy · index); defaults use the real filesystem.
16
+ * The repository is never written: removing the originals is a human PR from `filesForPr`. */
17
+ writes?: {
18
+ copy?: (source: string, destination: string) => void;
19
+ appendIndex?: (path: string, line: string) => void;
20
+ };
21
+ }
22
+
23
+ export interface GoalArchiveEntry {
24
+ goalId: string;
25
+ runId: string;
26
+ pr: number | null;
27
+ outcome: 'merged' | 'abandoned' | 'superseded';
28
+ source: 'ledger' | 'doc-record';
29
+ originalPath: string;
30
+ archivePath: string;
31
+ }
32
+
33
+ export interface GoalArchiveResult {
34
+ candidates: GoalArchiveEntry[];
35
+ applied: GoalArchiveEntry[];
36
+ skipped: Record<string, number>;
37
+ trustDocRecordAdds: number;
38
+ filesForPr: string[];
39
+ }
40
+
41
+ function gitRead(root: string, ...args: string[]): string {
42
+ return execFileSync('git', args, {
43
+ cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
44
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' },
45
+ });
46
+ }
47
+
48
+ function applyArchiveEntry(index: string, source: string,
49
+ entry: GoalArchiveEntry, document: string, writes: GoalArchiveOptions['writes']): void {
50
+ const directory = dirname(entry.archivePath);
51
+ const hadDirectory = existsSync(directory);
52
+ const priorIndex = existsSync(index) ? readFileSync(index) : null;
53
+ let ownedCopy = false;
54
+ let indexAttempted = false;
55
+ try {
56
+ mkdirSync(directory, { recursive: true });
57
+ const reservation = openSync(entry.archivePath, 'wx');
58
+ ownedCopy = true;
59
+ closeSync(reservation);
60
+ (writes?.copy ?? copyFileSync)(source, entry.archivePath);
61
+ if (readFileSync(entry.archivePath, 'utf8') !== document) throw new Error(`archive copy mismatch: ${entry.originalPath}`);
62
+ indexAttempted = true;
63
+ (writes?.appendIndex ?? ((path, line) => appendFileSync(path, line, 'utf8')))(index, `${JSON.stringify(entry)}\n`);
64
+ } catch (error) {
65
+ try {
66
+ if (indexAttempted) {
67
+ if (priorIndex === null) rmSync(index, { force: true });
68
+ else if (!readFileSync(index).equals(priorIndex)) writeFileSync(index, priorIndex);
69
+ }
70
+ if (ownedCopy) rmSync(entry.archivePath, { force: true });
71
+ if (ownedCopy && !hadDirectory && existsSync(directory)) rmdirSync(directory);
72
+ } catch (rollbackError) {
73
+ throw new Error(`archive failed for ${entry.originalPath}: ${String(error)}; recovery required (archive rollback: ${String(rollbackError)}). Preserve ${entry.archivePath} and ${index}; reconcile the index before retry.`);
74
+ }
75
+ throw error;
76
+ }
77
+ }
78
+
79
+ function beforeDate(value: string | undefined): string | undefined {
80
+ if (value === undefined) return undefined;
81
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || Number.isNaN(Date.parse(`${value}T00:00:00Z`))
82
+ || new Date(`${value}T00:00:00Z`).toISOString().slice(0, 10) !== value) {
83
+ throw new Error(`--before requires a valid YYYY-MM-DD date: ${value}`);
84
+ }
85
+ return value;
86
+ }
87
+
88
+ function ledgerGoalFileContradicts(goalFile: unknown, originalPath: string): boolean {
89
+ if (typeof goalFile !== 'string' || !goalFile.trim()) return false;
90
+ const normalized = goalFile.replaceAll('\\', '/');
91
+ return normalized !== originalPath && !normalized.endsWith(`/${originalPath}`);
92
+ }
93
+
94
+ function lastExecution(document: string): Record<string, string> | null {
95
+ const sections = document.split(/^## 실행 기록\s*$/m).slice(1);
96
+ const block = sections.at(-1)?.split(/^#{1,2} /m, 1)[0];
97
+ const run = block?.split(/^- runId: /m).slice(1).at(-1);
98
+ if (!run) return null;
99
+ const id = run.split(/\r?\n/, 1)[0]?.trim();
100
+ if (!id) return null;
101
+ const fields: Record<string, string> = { runId: id };
102
+ for (const [, key, value] of run.matchAll(/^ (stage|outcome|prNumber|completedAt): ([^\r\n]*)$/gm)) fields[key!] = value!.trim();
103
+ return fields;
104
+ }
105
+
106
+ /** Preview by default. --apply only copies to the state archive and indexes; it never removes or stages anything in the repository —
107
+ * `filesForPr` is the list a human removes in one PR. A missing ledger requires explicit trust in the document's finished-run record. */
108
+ export function archiveGoals(options: GoalArchiveOptions = {}): GoalArchiveResult {
109
+ const root = resolve(options.repoRoot ?? gitRead(process.cwd(), 'rev-parse', '--show-toplevel').trim());
110
+ const state = resolve(options.stateRoot ?? elanousStateRoot());
111
+ const cutoff = beforeDate(options.before);
112
+ const goals = join(root, 'docs', 'goals');
113
+ const archive = join(state, 'goal-archive');
114
+ const index = join(archive, 'index.jsonl');
115
+ const result: GoalArchiveResult = { candidates: [], applied: [], skipped: {}, trustDocRecordAdds: 0, filesForPr: [] };
116
+ const skip = (reason: string): void => { result.skipped[reason] = (result.skipped[reason] ?? 0) + 1; };
117
+ const lock = join(archive, '.apply.lock');
118
+ if (options.apply) {
119
+ mkdirSync(archive, { recursive: true });
120
+ try { mkdirSync(lock); }
121
+ catch (error) {
122
+ if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
123
+ throw new Error(`EEXIST: goal archive --apply is already running or a previous run stopped mid-way (${lock}). `
124
+ + `If no other archive is running: compare ${index} with ${archive}/<YYYY-MM>/ copies, drop index lines whose copy is missing, then remove ${lock} and retry.`);
125
+ }
126
+ }
127
+ try {
128
+ const tracked = new Set(gitRead(root, 'ls-files', '-z', '--', 'docs/goals').split('\0').filter(Boolean));
129
+ const pendingRemovals = new Set(gitRead(root, 'diff', '--cached', '--name-only', '--diff-filter=D', '-z', '--', 'docs/goals').split('\0').filter(Boolean));
130
+ const unfinished = queryUnfinishedRunLedgers({ dir: runLedgerDir(state), goalsDir: goals, noCache: true });
131
+ if (unfinished.unreadableLedgerCount > 0) throw new Error('run ledger contains unreadable runs; archive refused');
132
+ const activePaths = new Set(unfinished.entries.filter((entry) => entry.goalDocumentPath)
133
+ .map((entry) => resolve(root, entry.goalDocumentPath!)));
134
+ const activeRunIds = new Set(unfinished.entries.map((entry) => entry.runId));
135
+ const indexed = new Set<string>();
136
+ if (existsSync(index)) {
137
+ for (const line of readFileSync(index, 'utf8').split('\n').filter(Boolean)) {
138
+ const entry = JSON.parse(line) as { originalPath: string };
139
+ indexed.add(entry.originalPath);
140
+ }
141
+ }
142
+ let names: string[];
143
+ try { names = readdirSync(goals).sort(); }
144
+ catch (error) {
145
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
146
+ names = [];
147
+ }
148
+ for (const name of names) {
149
+ if (!isGoalAuthorFileName(name)) continue;
150
+ const path = join(goals, name);
151
+ const originalPath = relative(root, path).split(sep).join('/');
152
+ if (!tracked.has(originalPath) || pendingRemovals.has(originalPath)) { skip('untracked'); continue; }
153
+ const fileStat = lstatSync(path, { throwIfNoEntry: false });
154
+ if (!fileStat?.isFile() || fileStat.isSymbolicLink()) { skip('not-file'); continue; }
155
+ if (indexed.has(originalPath)) { skip('already-indexed'); continue; }
156
+ if (activePaths.has(path)) { skip('active-goal-run'); continue; }
157
+ const document = readFileSync(path, 'utf8');
158
+ const fields = lastExecution(document);
159
+ if (!fields) { skip('no-execution-record'); continue; }
160
+ const runId = fields.runId!;
161
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(runId)) { skip('invalid-run-id'); continue; }
162
+ if (activeRunIds.has(runId)) { skip('active-goal-run'); continue; }
163
+ const outcome = fields.stage === 'merged' && (fields.outcome === 'completed' || fields.outcome === 'merged') ? 'merged'
164
+ : fields.outcome === 'abandoned' ? 'abandoned'
165
+ : fields.outcome === 'superseded' ? 'superseded' : null;
166
+ if (!outcome) { skip('unfinished'); continue; }
167
+ let ledger;
168
+ try { ledger = loadRunLedger(runId, runLedgerDir(state)); }
169
+ catch (error) {
170
+ skip(error instanceof Error && /runId does not match /.test(error.message) ? 'ledger-run-id-mismatch' : 'unreadable-ledger');
171
+ continue;
172
+ }
173
+ const missingLedger = !ledger?.length;
174
+ if (missingLedger && (!fields.stage || !fields.outcome)) { skip('unfinished'); continue; }
175
+ const documentGoalId = /^- GoalId:\s*([0-9a-f]{16})\s*$/m.exec(document.split(/^## 실행 기록\s*$/m, 1)[0] ?? '')?.[1];
176
+ if (!documentGoalId) { skip('missing-goal-id'); continue; }
177
+ if (!missingLedger) {
178
+ const entries = ledger!;
179
+ const ledgerGoalIds = new Set(entries.map((entry) => entry.goalId).filter((id): id is string => typeof id === 'string'));
180
+ if (ledgerGoalIds.size !== 1 || !ledgerGoalIds.has(documentGoalId)) { skip('ledger-goal-id-mismatch'); continue; }
181
+ const starts = entries.filter((entry) => entry.event === 'start');
182
+ if (starts.length === 0 || starts.some((entry) => ledgerGoalFileContradicts(entry.data.goalFile, originalPath))) {
183
+ skip('ledger-goal-mismatch'); continue;
184
+ }
185
+ }
186
+ const latest = ledger?.filter((entry) => entry.event === 'run-status').at(-1);
187
+ if (!missingLedger) {
188
+ if (!latest || !hasTerminalRunStatus(ledger!) || !['completed', 'failed', 'cancelled'].includes(String(latest.data.runStatus))) {
189
+ skip('running-or-unconfirmed'); continue;
190
+ }
191
+ if (outcome === 'merged' && (latest.data.runStatus !== 'completed' || (latest.data.stage !== undefined && latest.data.stage !== 'merged'))
192
+ || outcome === 'abandoned' && latest.data.runStatus === 'completed'
193
+ || outcome === 'superseded' && latest.data.runStatus === 'failed') {
194
+ skip('status-mismatch'); continue;
195
+ }
196
+ }
197
+ const completedAt = missingLedger ? fields.completedAt : fields.completedAt ?? latest?.timestamp;
198
+ if (!completedAt || !Number.isFinite(Date.parse(completedAt))) { skip('no-completion-date'); continue; }
199
+ const date = new Date(completedAt).toISOString().slice(0, 10);
200
+ if (cutoff && date >= cutoff) { skip('after-before'); continue; }
201
+ const prRaw = fields.prNumber;
202
+ const pr = prRaw && /^[1-9]\d*$/.test(prRaw) && Number.isSafeInteger(Number(prRaw)) ? Number(prRaw) : null;
203
+ const archivePath = join(archive, date.slice(0, 7), basename(path));
204
+ if (existsSync(archivePath)) { skip('archive-collision'); continue; }
205
+ if (gitRead(root, 'status', '--porcelain', '--', originalPath).trim()) { skip('dirty'); continue; }
206
+ if (missingLedger && !options.trustDocRecord) { skip('missing-ledger'); result.trustDocRecordAdds += 1; continue; }
207
+ const entry: GoalArchiveEntry = { goalId: documentGoalId, runId, pr, outcome, source: missingLedger ? 'doc-record' : 'ledger', originalPath, archivePath };
208
+ result.candidates.push(entry);
209
+ if (options.apply) {
210
+ applyArchiveEntry(index, path, entry, document, options.writes);
211
+ result.applied.push(entry);
212
+ result.filesForPr.push(originalPath);
213
+ } else {
214
+ result.filesForPr.push(originalPath);
215
+ }
216
+ }
217
+ try { debug.log('harness.goal', 'archive', { candidates: result.candidates.length, applied: result.applied.length, skipped: result.skipped }); } catch { /* observation is fail-soft */ }
218
+ return result;
219
+ } finally {
220
+ if (options.apply) rmdirSync(lock);
221
+ }
222
+ }
@@ -6,6 +6,7 @@ import { dirname, isAbsolute, join, normalize, relative, resolve, sep } from 'no
6
6
  import { groundMissionInCodebase, isRepositoryImplementationCandidate, type CodebaseGrounding } from '../autopilot/mission-codebase-gate.js';
7
7
  import { extractSlugSource, generateMissionSlug, slugify } from '../autopilot/mission-registry.js';
8
8
  import { enhancePrompt, type EnhanceOpts, type EnhanceResult } from '../prompt-enhance/enhance.js';
9
+ import type { LLMUsage } from '../prompt-cache/types.js';
9
10
  import { debug } from '../debug/log.js';
10
11
 
11
12
  function observeGoalAuthor(
@@ -324,9 +325,12 @@ function startGoalAuthorPhase(phase: GoalAuthorTimedPhase, authorRunId: string,
324
325
  return Date.now();
325
326
  }
326
327
 
327
- function endGoalAuthorPhase(phase: GoalAuthorTimedPhase, startedAt: number, authorRunId: string, onProgress?: GoalAuthorDeps['onProgress']): void {
328
+ type GoalAuthorTokens = { inputTokens: number | null; outputTokens: number | null };
329
+ const UNKNOWN_GOAL_AUTHOR_TOKENS: GoalAuthorTokens = { inputTokens: null, outputTokens: null };
330
+
331
+ function endGoalAuthorPhase(phase: GoalAuthorTimedPhase, startedAt: number, authorRunId: string, onProgress?: GoalAuthorDeps['onProgress'], tokens: GoalAuthorTokens = UNKNOWN_GOAL_AUTHOR_TOKENS): void {
328
332
  const elapsedMs = Date.now() - startedAt;
329
- observeGoalAuthor('goal-author', 'phase-end', { phase, authorRunId, elapsedMs });
333
+ observeGoalAuthor('goal-author', 'phase-end', { phase, authorRunId, elapsedMs, ...tokens });
330
334
  if (GOAL_AUTHOR_PHASES.includes(phase as GoalAuthorPhase)) {
331
335
  if (onProgress) onProgress(phase as GoalAuthorPhase, 'end');
332
336
  else process.stderr.write(`[goal-author] ${phase} ended in ${elapsedMs}ms\n`);
@@ -1156,6 +1160,21 @@ function observeAuthoredGroundingPathSections(document: string, paths: readonly
1156
1160
  }
1157
1161
  }
1158
1162
 
1163
+ function authoredGoalSectionChars(document: string): Record<string, number> {
1164
+ const headings = linesOutsideFencedCode(document)
1165
+ .filter(({ text }) => /^## (?:\S)/.test(text))
1166
+ .map(({ text, start }) => ({ heading: text.slice(3).trim(), start }));
1167
+ const sections: Record<string, number> = { preamble: [...document.slice(0, headings[0]?.start ?? document.length)].length };
1168
+ for (let index = 0; index < headings.length; index += 1) {
1169
+ const { heading, start } = headings[index];
1170
+ sections[heading] = (sections[heading] ?? 0) + [...document.slice(start, headings[index + 1]?.start ?? document.length)].length;
1171
+ }
1172
+ for (const heading of ['PROBLEM', 'WHAT TO BUILD', 'ACCEPTANCE CRITERIA', 'TRACED PATHS', 'SCOPE BOUNDARY', 'STEPS', '답하지 못하는 것']) {
1173
+ sections[heading] ??= 0;
1174
+ }
1175
+ return sections;
1176
+ }
1177
+
1159
1178
  function canonicalBlocks(document: string): string[] {
1160
1179
  return linesOutsideFencedCode(document)
1161
1180
  .filter((line) => /^##(?:\s|$)/.test(line.text))
@@ -4906,6 +4925,13 @@ async function authorGoalWithSupersededRootIntent(
4906
4925
  }
4907
4926
  // ⭐ 이 저작 «한 번»의 id. goalId 보다 «먼저» 있어야 ground·enhance 의 소요를 그 골에 붙일 수 있다.
4908
4927
  const authorRunId = generateGoalId();
4928
+ const authorStartedAt = Date.now();
4929
+ const enhanceTokens: GoalAuthorTokens = { inputTokens: null, outputTokens: null };
4930
+ const onEnhanceUsage = (usage: LLMUsage): void => {
4931
+ if (usage.inputTokens !== undefined) enhanceTokens.inputTokens = (enhanceTokens.inputTokens ?? 0) + usage.inputTokens;
4932
+ if (usage.outputTokens !== undefined) enhanceTokens.outputTokens = (enhanceTokens.outputTokens ?? 0) + usage.outputTokens;
4933
+ try { deps.enhanceOpts?.onUsage?.(usage); } catch { /* usage observation is fail-soft */ }
4934
+ };
4909
4935
  let facts: CodebaseGrounding | null = null;
4910
4936
  let groundingError = false;
4911
4937
  const persistentGrounding = resolveGoalAuthorPersistentGrounding('persistentGrounding' in deps ? deps.persistentGrounding : undefined);
@@ -4937,8 +4963,8 @@ async function authorGoalWithSupersededRootIntent(
4937
4963
  ? await deps.enhance(
4938
4964
  enhanceAsk,
4939
4965
  persistentFacts.length > 0
4940
- ? { ...deps.enhanceOpts, groundedFacts: persistentFacts }
4941
- : deps.enhanceOpts,
4966
+ ? { ...deps.enhanceOpts, groundedFacts: persistentFacts, onUsage: onEnhanceUsage }
4967
+ : { ...deps.enhanceOpts, onUsage: onEnhanceUsage },
4942
4968
  )
4943
4969
  : { original: ask, checklist: [], verbatimPreserved: true };
4944
4970
  // ⭐ goal-context 규범이 «어디에도 안 실린다»는 것을 관측으로 남긴다.
@@ -4956,7 +4982,7 @@ async function authorGoalWithSupersededRootIntent(
4956
4982
  placement: 'omitted',
4957
4983
  });
4958
4984
  } finally {
4959
- endGoalAuthorPhase('enhance', enhanceStartedAt, authorRunId, deps.onProgress);
4985
+ endGoalAuthorPhase('enhance', enhanceStartedAt, authorRunId, deps.onProgress, enhanceTokens);
4960
4986
  }
4961
4987
  if (!enhancement.verbatimPreserved || (enhanceAsk && enhancement.original !== enhanceAsk)) {
4962
4988
  throw new Error('goal author refused an enhancement that does not preserve its input verbatim');
@@ -5431,6 +5457,18 @@ async function authorGoalWithSupersededRootIntent(
5431
5457
  } finally {
5432
5458
  endGoalAuthorPhase('lint', lintStartedAt, authorRunId, deps.onProgress);
5433
5459
  }
5460
+ try {
5461
+ observeGoalAuthor('goal-author', 'summary', {
5462
+ authorRunId,
5463
+ elapsedMs: Date.now() - authorStartedAt,
5464
+ inputTokens: enhanceTokens.inputTokens,
5465
+ outputTokens: enhanceTokens.outputTokens,
5466
+ totalTokens: enhanceTokens.inputTokens === null || enhanceTokens.outputTokens === null
5467
+ ? null : enhanceTokens.inputTokens + enhanceTokens.outputTokens,
5468
+ goalChars: [...document].length,
5469
+ sectionChars: authoredGoalSectionChars(document),
5470
+ });
5471
+ } catch { /* observation is fail-soft */ }
5434
5472
  return { document, facts, grounded: !groundingError && hasEvidence, authorRunId };
5435
5473
  } finally {
5436
5474
  endAssembleSubphase();
@@ -35,6 +35,8 @@ type GoalClarificationAnsweredBy = 'human' | 'agent' | 'none' | 'mixed';
35
35
 
36
36
  export interface GoalClarificationEscalationInput {
37
37
  goalFile: string;
38
+ /** Explicit provenance for isolated test fixtures; never inferred from goalFile. */
39
+ testOrigin?: true;
38
40
  delivery?: GoalClarificationDelivery;
39
41
  resolvedDelivery?: GoalClarificationObservedDelivery;
40
42
  dispatch?: (
@@ -577,6 +579,9 @@ export async function escalateGoalDocumentClarifications(
577
579
  }],
578
580
  },
579
581
  input.dispatchContext?.sessionId,
582
+ {},
583
+ undefined,
584
+ { testOrigin: input.testOrigin },
580
585
  );
581
586
  pendingQuestionPersistence.write(pending);
582
587
  pendingWritten += 1;
@@ -0,0 +1,147 @@
1
+ import { readFileSync, readdirSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ import { selfDevRunsDir, type SelfDevRunState } from '../self-dev/run-store.js';
4
+ import { isGoalAuthorFileName } from './goal-document.js';
5
+
6
+ export interface GoalLookupOptions {
7
+ /** Defaults to the current repository's docs/goals directory. */
8
+ goalsDir?: string;
9
+ /** Defaults to the current instance's self-dev-runs directory. */
10
+ runsDir?: string;
11
+ }
12
+
13
+ export interface GoalLookupRecord {
14
+ runId: string;
15
+ stage: string | null;
16
+ outcome: string | null;
17
+ prNumber: number | null;
18
+ }
19
+
20
+ /** The two sources are independent: null means that side did not match. */
21
+ export interface GoalLookupResult {
22
+ goalBody: string | null;
23
+ goalFile: string | null;
24
+ kanId: string | null;
25
+ runId: string | null;
26
+ record: GoalLookupRecord | null;
27
+ runState: SelfDevRunState | null;
28
+ }
29
+
30
+ function filesIn(directory: string): string[] {
31
+ try {
32
+ return readdirSync(directory, { withFileTypes: true })
33
+ .filter((entry) => entry.isFile())
34
+ .map((entry) => entry.name)
35
+ .sort();
36
+ } catch (error) {
37
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];
38
+ throw error;
39
+ }
40
+ }
41
+
42
+ type ValidResult = { runId: string; stage?: string | null; prNumber?: number | null };
43
+
44
+ function isValidResult(result: unknown): result is ValidResult {
45
+ if (result === null || typeof result !== 'object' || Array.isArray(result)) return false;
46
+ const r = result as { runId?: unknown; stage?: unknown; prNumber?: unknown };
47
+ return typeof r.runId === 'string' && r.runId.length > 0
48
+ && (r.stage == null || typeof r.stage === 'string')
49
+ && (r.prNumber == null || (typeof r.prNumber === 'number' && Number.isSafeInteger(r.prNumber) && r.prNumber > 0));
50
+ }
51
+
52
+ function prNumber(value: string | undefined): number | null {
53
+ if (!value || !/^[1-9]\d*$/.test(value)) return null;
54
+ const number = Number(value);
55
+ return Number.isSafeInteger(number) ? number : null;
56
+ }
57
+
58
+ function executionRecords(document: string): GoalLookupRecord[] {
59
+ const sections = document.split(/^## 실행 기록\s*$/m).slice(1);
60
+ return sections.flatMap((section) => {
61
+ const block = section.split(/^#{1,2} /m, 1)[0] ?? '';
62
+ const entries = block.split(/^- runId: /m).slice(1);
63
+ return entries.flatMap((entry) => {
64
+ const runId = entry.split(/\r?\n/, 1)[0]?.trim();
65
+ if (!runId) return [];
66
+ const field = (name: string): string | undefined =>
67
+ new RegExp(`^ ${name}: ([^\\r\\n]*)$`, 'm').exec(entry)?.[1]?.trim();
68
+ return [{ runId, stage: field('stage') ?? null, outcome: field('outcome') ?? null, prNumber: prNumber(field('prNumber')) }];
69
+ });
70
+ });
71
+ }
72
+
73
+ /** Local, read-only lookup. A bare positive integer (or #number) is a PR; all other input is an exact run ID. */
74
+ export function lookupGoal(reference: string, options: GoalLookupOptions = {}): GoalLookupResult {
75
+ const input = reference.trim();
76
+ const requestedPr = prNumber(input.replace(/^#/, ''));
77
+ const requestedRunId = requestedPr === null ? input : null;
78
+ const runsDir = options.runsDir ?? selfDevRunsDir();
79
+ const findRun = (matches: (result: ValidResult) => boolean, wantedRunId: string | null) => {
80
+ for (const name of filesIn(runsDir)) {
81
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]*\.json$/.test(name)) continue;
82
+ let candidate: SelfDevRunState;
83
+ try {
84
+ candidate = JSON.parse(readFileSync(join(runsDir, name), 'utf8')) as SelfDevRunState;
85
+ } catch { continue; }
86
+ if (!candidate || candidate.runId !== name.slice(0, -'.json'.length) || !Array.isArray(candidate.results)) continue;
87
+ const matchingResult = (candidate.results as unknown[]).find((result): result is ValidResult => isValidResult(result) && matches(result));
88
+ if (matchingResult || (wantedRunId !== null && candidate.runId === wantedRunId)) {
89
+ const runId = matchingResult?.runId ?? candidate.runId;
90
+ const record: GoalLookupRecord | null = matchingResult
91
+ ? { runId, stage: matchingResult.stage ?? null, outcome: null, prNumber: matchingResult.prNumber ?? null }
92
+ : null;
93
+ return { runState: candidate, runId, record };
94
+ }
95
+ }
96
+ return null;
97
+ };
98
+ const found = findRun((result) => requestedPr !== null ? result.prNumber === requestedPr : result.runId === requestedRunId, requestedRunId);
99
+ let runState: SelfDevRunState | null = found?.runState ?? null;
100
+ const resultRunId: string | null = found?.runId ?? null;
101
+ let runRecord: GoalLookupRecord | null = found?.record ?? null;
102
+
103
+ const goalsDir = options.goalsDir ?? resolve(process.cwd(), 'docs', 'goals');
104
+ let fallback: { goalFile: string; document: string; record: GoalLookupRecord } | null = null;
105
+ let selected: { goalFile: string; document: string; record: GoalLookupRecord } | null = null;
106
+ for (const name of filesIn(goalsDir)) {
107
+ if (!isGoalAuthorFileName(name)) continue;
108
+ const goalFile = join(goalsDir, name);
109
+ let document: string;
110
+ try { document = readFileSync(goalFile, 'utf8'); } catch { continue; }
111
+ const records = executionRecords(document);
112
+ const direct = records.find((candidate) => requestedPr !== null
113
+ ? candidate.prNumber === requestedPr
114
+ : candidate.runId === requestedRunId);
115
+ if (direct) { selected = { goalFile, document, record: direct }; break; }
116
+ if (requestedPr !== null && resultRunId !== null && fallback === null) {
117
+ const record = records.find((candidate) => candidate.prNumber === null && candidate.runId === resultRunId);
118
+ if (record) fallback = { goalFile, document, record };
119
+ }
120
+ }
121
+ const match = selected ?? fallback;
122
+ if (match) {
123
+ const { goalFile, document, record } = match;
124
+ // The returned run state must belong to the selected document record's run (review must-fix): re-resolve, or drop it.
125
+ if (resultRunId !== record.runId) {
126
+ const again = findRun((result) => result.runId === record.runId, record.runId);
127
+ runState = again?.runState ?? null;
128
+ runRecord = again?.record ?? null;
129
+ }
130
+ const body = document.split(/^## 실행 기록\s*$/m, 1)[0]!.replace(/\r?\n$/, '');
131
+ // A kan ID is only reported when the authored document explicitly names one.
132
+ const kanId = /^- Kan(?:Id|-ID):\s*(\S+)\s*$/im.exec(body)?.[1] ?? null;
133
+ return {
134
+ goalBody: body, goalFile, kanId, runId: record.runId,
135
+ record: {
136
+ ...record,
137
+ ...(runRecord?.runId === record.runId ? {
138
+ stage: record.stage ?? runRecord.stage,
139
+ outcome: record.outcome ?? runRecord.outcome,
140
+ prNumber: record.prNumber ?? runRecord.prNumber,
141
+ } : {}),
142
+ },
143
+ runState,
144
+ };
145
+ }
146
+ return { goalBody: null, goalFile: null, kanId: null, runId: resultRunId ?? requestedRunId, record: runRecord, runState };
147
+ }