elanous 0.2.19 → 0.2.21

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 (509) 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/MObU8ujkMciAKr6ljAV05/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/088d8e8a.8069543d082bec12.js +160 -0
  5. package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +148 -0
  6. package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +32 -0
  7. package/apps/pwa/out/_next/static/chunks/119fc678.6f591cdb84ca5791.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +309 -0
  9. package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 1268.0840ff91e083fcbd.js} +1 -1
  10. package/apps/pwa/out/_next/static/chunks/1307-c97eba9fb72a9a5e.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +122 -0
  12. package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +149 -0
  13. package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +79 -0
  14. package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +24 -0
  16. package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +27 -0
  21. package/apps/pwa/out/_next/static/chunks/1948.5f35b39eef222a94.js +5 -0
  22. package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{2977-e38442e86289f1d3.js → 211-9c9090c59e2c5907.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +36 -0
  31. package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +286 -0
  32. package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
  45. package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
  49. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
  54. package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
  59. package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
  61. package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
  63. package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
  66. package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
  70. package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/5977-59fedea50d13c762.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
  75. package/apps/pwa/out/_next/static/chunks/{4847.56f5bc454fbcc827.js → 6110.1297d4093fdfbb0d.js} +2 -2
  76. package/apps/pwa/out/_next/static/chunks/6158.566c890adc06a459.js +2 -0
  77. package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
  78. package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
  80. package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
  81. package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
  83. package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
  91. package/apps/pwa/out/_next/static/chunks/{1470.98a6ab61ccb4131c.js → 726.83532d69452ee24b.js} +1 -1
  92. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 7327-2d69fb321ba73389.js} +1 -1
  94. package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
  95. package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
  96. package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
  105. package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
  109. package/apps/pwa/out/_next/static/chunks/8857-f1762140ef9a96b1.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
  111. package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
  112. package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
  113. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +1 -0
  115. package/apps/pwa/out/_next/static/chunks/9247-7581825d511a64c0.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/{7647.2fcd30d894d5ee30.js → 935.4ddaa99fc1d1126c.js} +1 -1
  117. package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
  118. package/apps/pwa/out/_next/static/chunks/9497-b0e758d1cd4ae8d7.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
  120. package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  123. package/apps/pwa/out/_next/static/chunks/app/approvals/page-937c92292a4ab4f0.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-d781be3e3a9cfd74.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/board/page-b0a80997903852d8.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/botlab/page-2b1263165630d9c8.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/bots/page-ea998f7f5d156bea.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/ceo/page-33c328b64ca508f8.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/chat/page-e2d6bed48ac20120.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/consult/page-111cabe4d1fd69ae.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/control/page-544dd4bd4847eeb7.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/decisions/page-3ee191319bf97d87.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e4bcb2c503dab04b.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/editor/page-300897dcbbd5ed76.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/exec/page-a449f1ed472b81c2.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/field/page-9045094d6d3b5cd0.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/inside/page-c06673596849ef45.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/intake/page-103fa3191c1b2e08.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/layout-9f81233f29dcd409.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/live/{page-b70cd005d9169e25.js → page-be8b4615c919e36c.js} +1 -1
  142. package/apps/pwa/out/_next/static/chunks/app/live-run/page-c685b104e0dfa609.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/loops/page-8d693331cfbb6add.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/market/page-cef4f30a4c22fae1.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66ea1b54fc83fa15.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/morning/page-7a12f61be4a579d1.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/not-found-c85b0589591a99c9.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/observatory/page-725a4da02dd129eb.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8cc707bd09a497d9.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-90b55be1f15ce0e8.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cb16606e08be65db.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/outputs/page-16dc64b74ed6a40a.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/page-21bec4afeb2f0917.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/reflection/page-dc39ee7a36d26385.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-84ae759f76053672.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/sessions/page-3db58ddb61fe2be9.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c5315e30f3528dc4.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/settings/page-2fe368efb6de3b6a.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8d04132dd0ac5e10.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/page-f23484de467c9ea3.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/share/page-c69762987b751174.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5456692bda39aafb.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/tasks/page-d75f7cd65845ce98.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/term/page-5603f5e568b25ce3.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/today/page-d2cc0f4e89f42028.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/trace/page-a6eb73b8a4840f1c.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/vault/page-c0a3f4d10edb83fa.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6c0b88068c143f15.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workflows/page-7f8d7fcdf058a845.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workspace/page-22163556cd38bc88.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-141436fd26dd56fc.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
  174. package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → f6231284-45726550aba08fca.js} +1 -1
  175. package/apps/pwa/out/_next/static/chunks/{2ea96958.e8c7e112de50b067.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  176. package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
  177. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  178. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
  183. package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
  184. package/apps/pwa/out/_next/static/css/e81df11490b4413b.css +1 -0
  185. package/apps/pwa/out/approvals/index.html +1 -1
  186. package/apps/pwa/out/approvals/index.txt +23 -23
  187. package/apps/pwa/out/autopilot/index.html +1 -1
  188. package/apps/pwa/out/autopilot/index.txt +23 -23
  189. package/apps/pwa/out/board/index.html +1 -1
  190. package/apps/pwa/out/board/index.txt +23 -23
  191. package/apps/pwa/out/botlab/index.html +1 -1
  192. package/apps/pwa/out/botlab/index.txt +22 -22
  193. package/apps/pwa/out/bots/index.html +1 -1
  194. package/apps/pwa/out/bots/index.txt +22 -22
  195. package/apps/pwa/out/ceo/index.html +1 -1
  196. package/apps/pwa/out/ceo/index.txt +23 -23
  197. package/apps/pwa/out/chat/index.html +1 -1
  198. package/apps/pwa/out/chat/index.txt +22 -22
  199. package/apps/pwa/out/consult/index.html +1 -1
  200. package/apps/pwa/out/consult/index.txt +23 -23
  201. package/apps/pwa/out/control/index.html +1 -1
  202. package/apps/pwa/out/control/index.txt +23 -23
  203. package/apps/pwa/out/decisions/index.html +1 -1
  204. package/apps/pwa/out/decisions/index.txt +22 -22
  205. package/apps/pwa/out/design-check/index.html +1 -1
  206. package/apps/pwa/out/design-check/index.txt +23 -23
  207. package/apps/pwa/out/editor/index.html +1 -1
  208. package/apps/pwa/out/editor/index.txt +24 -24
  209. package/apps/pwa/out/exec/index.html +1 -1
  210. package/apps/pwa/out/exec/index.txt +23 -23
  211. package/apps/pwa/out/field/index.html +1 -1
  212. package/apps/pwa/out/field/index.txt +23 -23
  213. package/apps/pwa/out/healthz +1 -1
  214. package/apps/pwa/out/index.html +1 -1
  215. package/apps/pwa/out/index.txt +22 -22
  216. package/apps/pwa/out/inside/index.html +1 -1
  217. package/apps/pwa/out/inside/index.txt +23 -23
  218. package/apps/pwa/out/intake/index.html +1 -1
  219. package/apps/pwa/out/intake/index.txt +23 -23
  220. package/apps/pwa/out/live/index.html +1 -1
  221. package/apps/pwa/out/live/index.txt +23 -23
  222. package/apps/pwa/out/live-run/index.html +1 -0
  223. package/apps/pwa/out/live-run/index.txt +35 -0
  224. package/apps/pwa/out/loops/index.html +1 -1
  225. package/apps/pwa/out/loops/index.txt +22 -22
  226. package/apps/pwa/out/market/index.html +1 -1
  227. package/apps/pwa/out/market/index.txt +22 -22
  228. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  229. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  230. package/apps/pwa/out/morning/index.html +1 -1
  231. package/apps/pwa/out/morning/index.txt +23 -23
  232. package/apps/pwa/out/observatory/index.html +1 -1
  233. package/apps/pwa/out/observatory/index.txt +23 -23
  234. package/apps/pwa/out/ops/checklist/index.html +1 -1
  235. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  236. package/apps/pwa/out/ops/release/index.html +1 -1
  237. package/apps/pwa/out/ops/release/index.txt +22 -22
  238. package/apps/pwa/out/ops/seats/index.html +1 -1
  239. package/apps/pwa/out/ops/seats/index.txt +22 -22
  240. package/apps/pwa/out/outputs/index.html +1 -1
  241. package/apps/pwa/out/outputs/index.txt +22 -22
  242. package/apps/pwa/out/reflection/index.html +1 -1
  243. package/apps/pwa/out/reflection/index.txt +23 -23
  244. package/apps/pwa/out/scheduler/index.html +1 -1
  245. package/apps/pwa/out/scheduler/index.txt +23 -23
  246. package/apps/pwa/out/sessions/index.html +1 -1
  247. package/apps/pwa/out/sessions/index.txt +22 -22
  248. package/apps/pwa/out/settings/devices/index.html +1 -1
  249. package/apps/pwa/out/settings/devices/index.txt +23 -23
  250. package/apps/pwa/out/settings/index.html +1 -1
  251. package/apps/pwa/out/settings/index.txt +23 -23
  252. package/apps/pwa/out/setup/done/index.html +1 -1
  253. package/apps/pwa/out/setup/done/index.txt +23 -23
  254. package/apps/pwa/out/setup/index.html +1 -1
  255. package/apps/pwa/out/setup/index.txt +23 -23
  256. package/apps/pwa/out/share/index.html +1 -1
  257. package/apps/pwa/out/share/index.txt +23 -23
  258. package/apps/pwa/out/showroom/index.html +1 -1
  259. package/apps/pwa/out/showroom/index.txt +22 -22
  260. package/apps/pwa/out/tasks/index.html +1 -1
  261. package/apps/pwa/out/tasks/index.txt +23 -23
  262. package/apps/pwa/out/term/index.html +1 -1
  263. package/apps/pwa/out/term/index.txt +23 -23
  264. package/apps/pwa/out/today/index.html +1 -1
  265. package/apps/pwa/out/today/index.txt +23 -23
  266. package/apps/pwa/out/trace/index.html +1 -1
  267. package/apps/pwa/out/trace/index.txt +23 -23
  268. package/apps/pwa/out/vault/index.html +1 -1
  269. package/apps/pwa/out/vault/index.txt +23 -23
  270. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  271. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  272. package/apps/pwa/out/workflows/index.html +1 -1
  273. package/apps/pwa/out/workflows/index.txt +23 -23
  274. package/apps/pwa/out/workspace/index.html +1 -1
  275. package/apps/pwa/out/workspace/index.txt +23 -23
  276. package/apps/pwa/out/worktrees/index.html +1 -1
  277. package/apps/pwa/out/worktrees/index.txt +23 -23
  278. package/graphs/demo/editor-recipes.yaml +22 -0
  279. package/graphs/drafts/recipes.yaml +4 -4
  280. package/graphs/implement-loop.yaml +277 -5
  281. package/graphs/retro/daily.yaml +25 -0
  282. package/graphs/retro/recipes.yaml +24 -0
  283. package/package.json +1 -1
  284. package/scripts/ask-marker-check.ts +3 -1
  285. package/scripts/coord-channel-watch.sh +31 -4
  286. package/scripts/docs-lint.ts +4 -1
  287. package/scripts/land-global-checks.ts +89 -0
  288. package/scripts/lib/pch-regress-map.ts +2 -1
  289. package/scripts/marketing/generate-ax-brief.ts +18 -1
  290. package/scripts/measure-nl-routing-corpus.ts +11 -3
  291. package/scripts/release-loop/gate-node.ts +296 -25
  292. package/scripts/release-loop/gate-timing.ts +116 -7
  293. package/scripts/release-loop/preflight.ts +6 -4
  294. package/scripts/release-loop/publish-window.ts +141 -0
  295. package/scripts/release-loop/release-docs.ts +1 -4
  296. package/scripts/release-loop/release-universe.ts +38 -0
  297. package/scripts/release-loop/resume-release.ts +4 -1
  298. package/scripts/release-loop/shard-plan.ts +50 -5
  299. package/scripts/release-loop/unattended-release.ts +10 -2
  300. package/scripts/release-story/draft.ts +5 -1
  301. package/scripts/retro/shadow-compare.ts +71 -0
  302. package/scripts/rhythm/daily.ts +8 -2
  303. package/src/agent-mission/driver.ts +34 -6
  304. package/src/autopilot/build/llm-conflict-merge.ts +132 -7
  305. package/src/autopilot/build/merge-intent.ts +135 -7
  306. package/src/away/release-watch.ts +20 -3
  307. package/src/browser-cdp/client.ts +10 -5
  308. package/src/cli/config-drift.ts +513 -0
  309. package/src/cli/log-event-names.ts +4 -0
  310. package/src/cli/ops-cli.ts +11 -0
  311. package/src/cli/ops-overview-cli.ts +50 -0
  312. package/src/cli/pod-cli.ts +1 -0
  313. package/src/cli/pr-cli.ts +20 -0
  314. package/src/cli/release-cli.ts +20 -5
  315. package/src/cli/retro-cli.ts +78 -0
  316. package/src/cli/tasks-cli.ts +200 -8
  317. package/src/dashboard/index.ts +40 -4
  318. package/src/dashboard/slash-runtime/dashboard-handlers.ts +5 -4
  319. package/src/dashboard/slash-runtime/help-from-registry.ts +57 -31
  320. package/src/domains/outbound-log-probe.ts +6 -1
  321. package/src/esc-abort-gate.ts +3 -1
  322. package/src/graph-runner/graph-cli.ts +57 -0
  323. package/src/graph-runner/graph-run-api.ts +218 -0
  324. package/src/graph-runner/runner.ts +11 -1
  325. package/src/graph-wizard/archetypes.ts +88 -0
  326. package/src/graph-wizard/generate.ts +545 -0
  327. package/src/graph-wizard/saved-steps.ts +120 -0
  328. package/src/graph-wizard/steps.ts +193 -0
  329. package/src/harness/harness-cli-command.ts +482 -59
  330. package/src/harness/harness-queue.ts +315 -25
  331. package/src/harness/harness-salvage-cli.ts +62 -0
  332. package/src/hq/fence-outcomes.ts +63 -0
  333. package/src/hq/fence-wrapper.ts +33 -0
  334. package/src/hq/hq.ts +11 -1
  335. package/src/index.ts +76 -10
  336. package/src/intake-plane/digest.ts +18 -11
  337. package/src/maturity/feature-maturity.ts +4 -3
  338. package/src/nexus/api/draft-metrics.ts +114 -0
  339. package/src/nexus/api/graph-kinds.ts +10 -4
  340. package/src/nexus/api/graph-wizard-api.ts +53 -0
  341. package/src/nexus/api/graphs-api.ts +220 -10
  342. package/src/nexus/api/harness-traversal.ts +205 -0
  343. package/src/nexus/api/http-server.ts +69 -3
  344. package/src/nexus/api/loop-edges.ts +4 -2
  345. package/src/nexus/api/ops-api.ts +22 -7
  346. package/src/nexus/api/ops-board-route.ts +48 -0
  347. package/src/nexus/outbound/router.ts +28 -8
  348. package/src/onboarding/entry-hints.ts +5 -0
  349. package/src/onboarding/first-run.ts +3 -0
  350. package/src/onboarding.ts +24 -6
  351. package/src/ops/overview-collect.ts +345 -0
  352. package/src/ops/overview-seat-era.ts +90 -0
  353. package/src/ops/overview.ts +223 -0
  354. package/src/release-loop/feature-store.ts +14 -11
  355. package/src/release-loop/gate-shards.ts +163 -0
  356. package/src/release-loop/release-schedule.ts +6 -2
  357. package/src/retro/daily.ts +372 -0
  358. package/src/seat-loop/op-decide.ts +173 -0
  359. package/src/seat-loop/seat-loop.ts +60 -3
  360. package/src/seat-loop/seat-report.ts +58 -3
  361. package/src/self-dev/ask-launch-flow.ts +9 -6
  362. package/src/self-dev/ask-launch-io.ts +52 -2
  363. package/src/self-dev/ask-path-candidates.ts +40 -2
  364. package/src/self-dev/draft-sweep.ts +371 -16
  365. package/src/self-dev/draft-triage-rules.ts +19 -4
  366. package/src/self-dev/graph-journey-nodes.ts +157 -0
  367. package/src/self-dev/graph-journey-route.ts +105 -0
  368. package/src/self-dev/launch-preflight.ts +43 -3
  369. package/src/self-dev/pr-preview.ts +84 -16
  370. package/src/self-dev/release-path-guard.ts +7 -4
  371. package/src/self-implement/blocked-draft-policy.ts +101 -0
  372. package/src/self-implement/execution-clarification.ts +11 -0
  373. package/src/self-implement/frozen-merges.ts +23 -0
  374. package/src/self-implement/gate-env-retry.ts +83 -0
  375. package/src/self-implement/goal-author.ts +14 -0
  376. package/src/self-implement/heal-intake.ts +3 -0
  377. package/src/self-implement/orchestrator.ts +234 -6
  378. package/src/self-implement/run-stop.ts +123 -0
  379. package/src/self-implement/run-ttl.ts +258 -0
  380. package/src/self-implement/salvage-retention.ts +123 -0
  381. package/src/self-implement/seams.ts +7 -0
  382. package/src/self-implement/self-implement-cli.ts +2 -0
  383. package/src/self-implement/self-implement-runtime.ts +12 -0
  384. package/src/self-implement/sibling-resync.ts +223 -0
  385. package/src/self-implement/stop-autoheal.ts +204 -0
  386. package/src/session-runtime/index.ts +5 -3
  387. package/src/shell-env-bootstrap.ts +17 -3
  388. package/src/task-agent/board.ts +1 -1
  389. package/src/task-agent/card-evidence.ts +105 -10
  390. package/src/task-agent/judge.ts +7 -0
  391. package/src/task-agent/live-moves.ts +120 -20
  392. package/src/task-agent/mission.ts +1 -1
  393. package/src/task-agent/parent-host.ts +315 -0
  394. package/src/task-agent/shadow.ts +10 -1
  395. package/src/task-agent/task-hand.ts +16 -4
  396. package/src/task-orchestrator/surfaces/pod-command-job.ts +60 -11
  397. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
  398. package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
  399. package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
  400. package/src/task-orchestrator/surfaces/pod-lease.ts +93 -10
  401. package/src/task-orchestrator/surfaces/pod-pool.ts +147 -34
  402. package/src/task-orchestrator/surfaces/self-implement-pod.ts +58 -9
  403. package/src/telegram-commands.ts +41 -0
  404. package/src/testing/real-cli-spawn.ts +106 -0
  405. package/src/tool-runtime/browser-runtime.ts +87 -3
  406. package/src/tool-runtime/index.ts +1 -0
  407. package/src/user-config.ts +76 -7
  408. package/src/version/packed-revision.json +1 -1
  409. package/apps/pwa/out/_next/static/J5H7BvqgSI8DUjtRRcA_S/_buildManifest.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/2375-e061bb24cd2b5cde.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +0 -2
  419. package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
  422. package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +0 -25
  447. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +0 -1
  507. package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +0 -1
  508. /package/apps/pwa/out/_next/static/{J5H7BvqgSI8DUjtRRcA_S → MObU8ujkMciAKr6ljAV05}/_ssgManifest.js +0 -0
  509. /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
@@ -1,7 +1,8 @@
1
- import { execFileSync, spawn } from 'node:child_process';
1
+ import { execFile, execFileSync, spawn } from 'node:child_process';
2
+ import { promisify } from 'node:util';
2
3
  import { dlopen, FFIType } from 'bun:ffi';
3
4
  import { createHash } from 'node:crypto';
4
- import { existsSync, readFileSync } from 'node:fs';
5
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
5
6
  import { constants as osConstants, hostname } from 'node:os';
6
7
  import { basename, dirname, join, resolve, sep } from 'node:path';
7
8
  import { findGitDir } from '../git-fs/locate.js';
@@ -16,14 +17,15 @@ import { Command, Option } from 'commander';
16
17
  import { runGitCommand } from '../git-fs/runner.js';
17
18
  import { GOAL_TYPES, lintGoalFile, parseGoalId, parseGoalType, resolveGoalAuthorGrade, tracedPathReferences, type GoalAuthorGrade, type GoalAuthorGradeSelection, type GoalType } from '../self-implement/goal-author.js';
18
19
  import { templateForGoalType } from '../self-implement/graph-templates.js';
19
- import { listRunLedgers, loadFederatedRunLedger, loadRunLedger, runLedgerDir, type RunLedgerMatch } from '../self-implement/run-ledger.js';
20
+ import { listRunLedgers, loadFederatedRunLedger, loadRunLedger, queryRunScreenKey, runLedgerDir, type RunLedgerMatch } from '../self-implement/run-ledger.js';
21
+ import { classifyPodExitStop, recordRunStop, runStopRecorded, RUN_STOP_EVENT } from '../self-implement/run-stop.js';
20
22
  import { resolveChildLlmEffort, resolveImplementationChildModel } from '../self-dev/dev-cli.js';
21
23
  import { resolveHarnessTarget } from '../self-implement/harness-target-options.js';
22
24
  import { queryRunningRuns } from '../self-implement/running-runs.js';
23
25
  import { DevPipelineError } from '../self-dev/dev-pipeline.js';
24
- import { runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
26
+ import { collectDraftMetrics, DRAFT_SWEEP_CONCURRENCY, mapBounded, runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
25
27
  import { debug } from '../debug/log.js';
26
- import { installHarnessSalvageCommand } from './harness-salvage-cli.js';
28
+ import { installHarnessSalvageCommand, installHarnessSalvageRetentionCommand } from './harness-salvage-cli.js';
27
29
  import { decideNestedElanousLaunch, readNestedElanousDepth } from './nested-elanous-policy.js';
28
30
  import { dispatchTask, type DispatchTaskInput, type DispatchTaskDeps } from '../execution-loop/dispatch-task.js';
29
31
  import { launchRequestId, preLaunchGate, type PreLaunchGateDeps } from '../execution-loop/launch-gate.js';
@@ -32,6 +34,9 @@ import { CODEX_PROVIDER, GROK_PROVIDER, decideBudget, decideLaunchBudget, readBu
32
34
  import { DEFAULT_FALLBACK_CHAIN } from '../oauth/fallback-chain.js';
33
35
  import { installDeliverableVerifyCliCommand, type InstallDeliverableVerifyCliDeps } from './deliverable-verify-cli.js';
34
36
  import { getUserConfig } from '../user-config.js';
37
+ import type { SiblingResyncAdapters, SiblingResyncResult, SiblingRunTarget } from '../self-implement/sibling-resync.js';
38
+ import type { RunTtlAdapters, RunTtlResult } from '../self-implement/run-ttl.js';
39
+ import type { ControlMemoPayload } from './control-inbox.js';
35
40
  import { resolveRepositoryName } from './repository-name.js';
36
41
  import { installHarnessCliSinkHook } from './harness-cli-sink.js';
37
42
  import { addHarnessQueue, HarnessQueueDuplicateError, harnessQueueReceiptPath, listHarnessQueue, queueSeatForCwd, reconcileHarnessQueue, removeHarnessQueue, setHarnessQueuePriority, tickHarnessQueue, type HarnessQueueDeps, type QueueItem, type QueueSeat } from './harness-queue.js';
@@ -42,6 +47,8 @@ import { runHarnessPlanRfc } from './harness-plan-rfc.js';
42
47
  import { classifyGarbage, isGarbageProcessTarget, type GarbageProcess } from './process-garbage.js';
43
48
  import type { MissionSolveOutcome } from './mission-solve-loop.js';
44
49
  import { formatAxis, formatAxisObservations, inspectAskMarkers, inspectUnpressedDecisionSignals } from '../../scripts/ask-marker-check.js';
50
+ import { enterJourneyNode, ensureJourneyKey, exitJourneyNode, JOURNEY_KEY_ENV, passJourneyNode, withJourneyNode } from '../self-dev/graph-journey-nodes.js';
51
+ import { intakeTakesQueue, readJourneyEdge, recordJourneyEdge } from '../self-dev/graph-journey-route.js';
45
52
 
46
53
  let harnessPlanRfcForTesting: typeof runHarnessPlanRfc | undefined;
47
54
  let harnessAskMarkerInspectorForTesting: ((ask: string) => readonly string[]) | undefined;
@@ -670,11 +677,40 @@ async function onPod(opts: unknown, entrance: 'cli-harness-ask' | 'cli-harness-s
670
677
  const classified = classifyHarnessPodExit({ status }, output, { ...(runId ? { runId } : {}) });
671
678
  recordClassifiedHarnessPodExit(runId ?? harnessPodRunId(output), classified.reason, status,
672
679
  { entrance, seat: o.seat, output });
680
+ recordHarnessPodStop(runId ?? harnessPodRunId(output), status, classified);
673
681
  for (const line of classified.lines) console.error(line);
674
682
  process.exitCode = status;
675
683
  }
676
684
  }
677
685
 
686
+ /**
687
+ * STOP-RECORD — a nonzero Pod exit appends one `stop` line to the host run ledger (additive: the
688
+ * `exit-classified` log and the existing ledger stay as they are). A failed Pod Job loses its in-Pod
689
+ * ledger, so without this line the run reads `outcome: unknown`. Skips when a stop is already recorded.
690
+ */
691
+ export function recordHarnessPodStop(
692
+ runId: string | undefined,
693
+ status: number,
694
+ classified: { reason: string; lines: string[] },
695
+ deps: { loadLedger?: typeof loadRunLedger; record?: typeof recordRunStop } = {},
696
+ ): boolean {
697
+ if (!runId) return false;
698
+ try {
699
+ if (runStopRecorded(runId)) return false;
700
+ if ((deps.loadLedger ?? loadRunLedger)(runId)?.some((entry) => entry.event === RUN_STOP_EVENT)) return false;
701
+ // The classified line is our own one-line verdict («Pod 안 자식이 실패했다 — OOMKilled»), not the raw output tail.
702
+ const verdict = classified.lines[0]?.split(' — ').slice(1).join(' — ').trim();
703
+ (deps.record ?? recordRunStop)({
704
+ runId, site: 'pod-exit', class: classifyPodExitStop(classified.reason),
705
+ cause: `Pod exit ${status}: ${classified.reason}${verdict ? ` — ${verdict}` : ''}`,
706
+ evidenceRef: `logs:harness.pod/exit-classified:${runId}`,
707
+ nextMove: classified.reason === 'no-launch' ? 'Check Pod launch gate and pool, then relaunch'
708
+ : 'Inspect Pod exit classification and harvest the branch before relaunch',
709
+ });
710
+ return true;
711
+ } catch { return false; /* ledger observation cannot change Pod output or exit */ }
712
+ }
713
+
678
714
  const LAUNCH_BUDGET_PROVIDER: Readonly<Record<string, string>> = { 'codex-rotate': CODEX_PROVIDER, grok: GROK_PROVIDER };
679
715
 
680
716
  /**
@@ -743,13 +779,17 @@ async function dispatchHarnessAskSay(
743
779
  immediateLaunch?: HarnessQueueDeps['launch'],
744
780
  immediateExit?: () => Promise<number>,
745
781
  ): Promise<void> {
746
- await runInjectedHarnessHandler(async () => {
782
+ // HARNESS-FULL-GRAPH — 여정 조인 키는 이 발사 동안만 환경에 싣는다(자식은 그동안 물려받는다).
783
+ const journeyPreset = Boolean(process.env[JOURNEY_KEY_ENV]?.trim());
784
+ try { await runInjectedHarnessHandler(async () => {
747
785
  assertHarnessChildLlmModel(opts);
748
786
  if (isHarnessDryRun(opts)) {
749
787
  resolveLaunchSubstrate(opts);
750
788
  printHarnessLaunchDryRun(dryRunPreview);
751
789
  return;
752
790
  }
791
+ ensureJourneyKey();
792
+ const journey = { provenance: dryRunPreview.entrance, ...(opts.goalType ? { goalType: opts.goalType as GoalType } : {}) };
753
793
  // Resolve the launch directory once; descendants and the checkpoint retain the same identity.
754
794
  const queued = process.env.ELANOUS_HARNESS_QUEUE_LAUNCH && process.env.ELANOUS_HARNESS_SEAT;
755
795
  const queueSeat = queued === 'OP' || queued === 'TC' || queued === 'MK' || queued === 'UX' ? queued : undefined;
@@ -767,10 +807,21 @@ async function dispatchHarnessAskSay(
767
807
  const podSeat = resolved.substrate === 'pod' && ['OP', 'TC', 'MK', 'UX'].includes(inheritedSeat ?? '')
768
808
  ? inheritedSeat as QueueSeat : undefined;
769
809
  const stampedSeat = resolved.substrate === 'pod' ? opts.seat ?? queueSeat ?? podSeat ?? assigned : assigned;
810
+ // HARNESS-FULL-GRAPH 2판 — 대기열을 탈지는 «그래프»가 정한다. 코드는 상태 값(queued|direct)만 내고,
811
+ // 다음 노드는 YAML(⊕ 조건이 맞는 launch 오버레이)의 intake 간선이 고른다. 기본 YAML 은 오늘과 같은 길이다.
812
+ // ⛔ 대기열은 자리가 있어야 하고 대기열 자식·--no-queue 는 다시 줄 서지 않는다 — 그 셋은 그래프가 못 넘는다.
813
+ const intakeLabel = shouldQueue && stampedSeat ? 'queued' : 'direct';
814
+ // intake 는 판정만 하는 관측 노드 — 진입·출구를 한 자리에서.
815
+ passJourneyNode('intake', { ...journey, outcome: intakeLabel });
816
+ const intakeRoute = readJourneyEdge('intake', intakeLabel, { seat: stampedSeat ?? 'none', substrate: resolved.substrate });
817
+ const queueEligible = opts.queue !== false && !process.env.ELANOUS_HARNESS_QUEUE_LAUNCH && Boolean(stampedSeat);
818
+ const takeQueue = intakeTakesQueue(intakeLabel, intakeRoute, queueEligible);
819
+ // 실제로 탄 간선을 전제 검사 «뒤»에 남긴다(그래프 답과 다르면 honored=false · 이유).
820
+ recordJourneyEdge(intakeRoute, takeQueue ? 'queue' : 'launch-gate', intakeRoute.to === 'queue' && !takeQueue ? (opts.queue === false ? '--no-queue' : process.env.ELANOUS_HARNESS_QUEUE_LAUNCH ? 'queue-child' : 'no-seat') : undefined);
770
821
  if (directSay && opts.queue === false) {
771
822
  try { debug.log('harness.queue', 'bypass', { seat: stampedSeat ?? null, reason: '--no-queue' }); }
772
823
  catch { /* Observation must not prevent an emergency launch. */ }
773
- } else if (shouldQueue && stampedSeat) {
824
+ } else if (takeQueue && stampedSeat) {
774
825
  resolveNestedElanousOption(opts);
775
826
  const flags: string[] = [];
776
827
  const values: Array<[string, string | undefined]> = [
@@ -799,6 +850,7 @@ async function dispatchHarnessAskSay(
799
850
  [opts.forceGate, '--force-gate'],
800
851
  ] as const) if (enabled) flags.push(flag);
801
852
  let row: QueueItem;
853
+ enterJourneyNode('queue', journey);
802
854
  try {
803
855
  row = await addHarnessQueue({ seat: stampedSeat, launchCwd: process.cwd(), refuseDuplicate: true,
804
856
  ...(dryRunPreview.goalPath ? { ask: dryRunPreview.goalPath } : { say: dryRunPreview.input }),
@@ -808,12 +860,17 @@ async function dispatchHarnessAskSay(
808
860
  }, queueDeps);
809
861
  await queueDeps?.afterEnqueue?.(row);
810
862
  } catch (error) {
811
- if (!(error instanceof HarnessQueueDuplicateError)) throw error;
863
+ if (!(error instanceof HarnessQueueDuplicateError)) {
864
+ exitJourneyNode('queue', { ...journey, outcome: 'error', data: { error: error instanceof Error ? error.message : String(error) } });
865
+ throw error;
866
+ }
812
867
  console.error(`발사 거절 — ${error.message.replace(/[\r\n]+/g, ' ')}`);
868
+ exitJourneyNode('queue', { ...journey, outcome: 'refused' });
813
869
  process.exitCode = 1;
814
870
  return;
815
871
  }
816
- const result = await tickHarnessQueue({ ...queueDeps, ...(immediateLaunch ? { launch: immediateLaunch } : {}) }, row.id);
872
+ const result = await withJourneyNode('queue', journey, () => tickHarnessQueue({ ...queueDeps, ...(immediateLaunch ? { launch: immediateLaunch } : {}) }, row.id));
873
+ exitJourneyNode('queue', { ...journey, outcome: result.outcome === 'launched' && result.item?.id === row.id ? 'launched' : 'waiting', data: { queueItemId: row.id, queueTick: result.outcome } });
817
874
  if (result.outcome === 'launched' && result.item?.id === row.id) {
818
875
  if (immediateExit) process.exitCode = await immediateExit();
819
876
  } else if (result.outcome === 'waiting') {
@@ -846,6 +903,8 @@ async function dispatchHarnessAskSay(
846
903
  const previous = process.env.ELANOUS_HARNESS_SEAT;
847
904
  if (stampedSeat) process.env.ELANOUS_HARNESS_SEAT = stampedSeat;
848
905
  else delete process.env.ELANOUS_HARNESS_SEAT;
906
+ // 배치 노드 — 실행 칸(local | pod)이 그 출구 라벨이고, YAML 의 decompose 간선(on: substrate)이 그 값으로 갈린다.
907
+ passJourneyNode('dispatch', { ...journey, outcome: resolved.substrate });
849
908
  try { await dispatch(resolved, stampedOpts); }
850
909
  finally {
851
910
  if (previous === undefined) delete process.env.ELANOUS_HARNESS_SEAT;
@@ -866,11 +925,16 @@ async function dispatchHarnessAskSay(
866
925
  // CLI wiring tests time out on them. Gate tests inject deps or opt in with ELANOUS_LAUNCH_GATE_LIVE=1.
867
926
  if (process.env.NODE_ENV === 'test' && Object.keys(gate).length === 0 && process.env.ELANOUS_LAUNCH_GATE_LIVE !== '1') {
868
927
  try { debug.log('execution-loop.launch-gate', 'skipped-test-env', { goalId }); } catch { /* observation is fail-soft */ }
928
+ passJourneyNode('launch-gate', { ...journey, outcome: 'pass', data: { skipped: 'test-env' } });
869
929
  await launch();
870
930
  return;
871
931
  }
872
- const budget = await (gate.readBudget ?? (() => readHarnessLaunchBudget(opts)))();
873
- const decision = preLaunchGate({ goalId, budget, forceLaunch: opts.forceGate === true }, gate);
932
+ enterJourneyNode('launch-gate', journey);
933
+ const decision = await withJourneyNode('launch-gate', journey, async () => {
934
+ const budget = await (gate.readBudget ?? (() => readHarnessLaunchBudget(opts)))();
935
+ return preLaunchGate({ goalId, budget, forceLaunch: opts.forceGate === true }, gate);
936
+ });
937
+ exitJourneyNode('launch-gate', { ...journey, outcome: decision.action === 'proceed' ? 'pass' : 'refuse', data: { action: decision.action } });
874
938
  if (decision.action !== 'proceed') {
875
939
  console.error(`❌ launch gate: ${decision.action} — ${decision.reason.split(/\r?\n/, 1)[0]}`);
876
940
  process.exitCode = 3;
@@ -882,7 +946,7 @@ async function dispatchHarnessAskSay(
882
946
  ];
883
947
  if (warnings.length > 0) console.error(`⚠️ launch gate: ${warnings.join(' · ').split(/\r?\n/, 1)[0]}`);
884
948
  await launch();
885
- });
949
+ }); } finally { if (!journeyPreset) delete process.env[JOURNEY_KEY_ENV]; }
886
950
  }
887
951
 
888
952
  function normalizeHarnessPlanOptions(opts: HarnessPlanOptions): HarnessPlanOptions {
@@ -1801,7 +1865,7 @@ type GithubPull = {
1801
1865
  title: string;
1802
1866
  draft: boolean;
1803
1867
  state: 'open' | 'closed';
1804
- head: { ref: string };
1868
+ head: { ref: string; sha?: string };
1805
1869
  base?: { ref: string };
1806
1870
  labels: Array<{ name: string }>;
1807
1871
  created_at: string;
@@ -1844,6 +1908,147 @@ export async function supersedeMergedGoalDrafts(
1844
1908
  await supersedeDraftsOnMerge({ repository, merged, adapters: githubDraftSweepAdapters(execute, git, cwd) });
1845
1909
  }
1846
1910
 
1911
+ /**
1912
+ * SIBLING-RESYNC-ON-MERGE — merge-time adapter: open harness PRs whose files intersect the merged PR get a
1913
+ * resync memo in their live run's control inbox, or the quiet `elanous:needs-rebase` marker when no run is alive.
1914
+ */
1915
+ export async function resyncMergedSiblings(
1916
+ number: number, cwd: string,
1917
+ execute: GhExecute = executeGh, git: DraftSweepGitExecute = runGitCommand,
1918
+ overrides: Partial<Pick<SiblingResyncAdapters, 'resolveRun' | 'sendResync'>> & {
1919
+ maxFetch?: number; fetchConcurrency?: number; log?: (category: string, event: string, data: Record<string, unknown>) => void;
1920
+ } = {},
1921
+ ): Promise<SiblingResyncResult> {
1922
+ const remote = git(cwd, ['config', '--get', 'remote.origin.url']);
1923
+ const repository = remote.status === 0 ? githubRemoteRepository(remote.stdout) : undefined;
1924
+ if (!repository) throw new Error('Merge repository unavailable');
1925
+ const sweep = githubDraftSweepAdapters(execute, git, cwd);
1926
+ const [{ requestSiblingResync }, inbox, pod] = await Promise.all([
1927
+ import('../self-implement/sibling-resync.js'),
1928
+ import('./control-inbox.js'),
1929
+ import('./self-send-target.js'),
1930
+ ]);
1931
+ const maxFetch = overrides.maxFetch ?? (() => {
1932
+ try { return getUserConfig().harness?.siblingResync?.maxFetch; } catch { return undefined; }
1933
+ })();
1934
+ let ledgerMatches: readonly RunLedgerMatch[] | undefined;
1935
+ return requestSiblingResync({
1936
+ merged: { number },
1937
+ ...(maxFetch !== undefined ? { maxFetch } : {}),
1938
+ ...(overrides.fetchConcurrency !== undefined ? { fetchConcurrency: overrides.fetchConcurrency } : {}),
1939
+ ...(overrides.log ? { log: overrides.log } : {}),
1940
+ adapters: {
1941
+ mergedFiles: async () => sweep.getPrFiles!(repository, number),
1942
+ listOpenPrs: async () => {
1943
+ const all: GithubPull[] = [];
1944
+ for (let page = 1; page <= 100; page++) {
1945
+ const batch = githubPullsPage(repository, 'open', page, execute);
1946
+ if (!Array.isArray(batch) || batch.length > 100) throw new Error('Incomplete open PR inventory');
1947
+ all.push(...batch);
1948
+ if (batch.length < 100) break;
1949
+ if (page === 100) throw new Error('Open PR inventory exceeded pagination limit');
1950
+ }
1951
+ // No per-PR call here: the core prefilters, prioritizes and caps file fetches (#24893 review should-fix ①).
1952
+ return all.filter((pr) => pr.base?.ref === 'main' && pr.head?.ref?.startsWith('self-impl/') && pr.number !== number)
1953
+ .map((pr) => ({ number: pr.number, branch: pr.head.ref, labels: pr.labels.map((label) => label.name) }));
1954
+ },
1955
+ prFiles: async (prNumber) => sweep.getPrFiles!(repository, prNumber),
1956
+ resolveRun: overrides.resolveRun ?? (async (pr) => {
1957
+ ledgerMatches ??= listRunLedgers().matches;
1958
+ return resolveOwningRunForPr(ledgerMatches, repository, pr.number, 'sibling-resync');
1959
+ }),
1960
+ sendResync: overrides.sendResync ?? (({ spaceId }, memo) => deliverResyncMemo(spaceId, memo, inbox, pod)),
1961
+ addMarker: async (prNumber, label) => sweep.setLabels(repository, prNumber, { add: label, remove: [] }),
1962
+ },
1963
+ });
1964
+ }
1965
+
1966
+ /** The PR's owning run, alive only when running and its control screen resolves (shared by SIBLING-RESYNC and RUN-TTL). */
1967
+ function resolveOwningRunForPr(ledgerMatches: readonly RunLedgerMatch[], repository: string, number: number,
1968
+ caller: string): SiblingRunTarget {
1969
+ const runId = currentDraftSweepRunId(ledgerMatches, repository, number);
1970
+ if (!runId) return { alive: false, reason: 'owner-run-unknown' };
1971
+ const status = queryRunningRuns({ runIds: [runId], caller }).entries.find((entry) => entry.runId === runId)?.status;
1972
+ if (status !== 'running' && status !== 'probable-running') return { alive: false, runId, reason: `run-${status ?? 'not-running'}` };
1973
+ const screen = queryRunScreenKey(runId).screenKey;
1974
+ if (!screen) return { alive: false, runId, reason: 'screen-key-unresolved' };
1975
+ return { alive: true, runId, spaceId: screen };
1976
+ }
1977
+
1978
+ /** One supervisor memo into a live run's control inbox — Pod runs through their fragment, host runs through the file inbox. */
1979
+ function deliverResyncMemo(spaceId: string, memo: ControlMemoPayload,
1980
+ inbox: typeof import('./control-inbox.js'), pod: typeof import('./self-send-target.js')): void {
1981
+ if (pod.readPodFragment(spaceId)) {
1982
+ if (!pod.dispatchPodSelfSend(spaceId, { memo })) throw new Error('pod fragment unreachable');
1983
+ return;
1984
+ }
1985
+ inbox.enqueueControlMemo(spaceId, memo, { explicitInboxDir: inbox.controlInboxPath(spaceId) });
1986
+ }
1987
+
1988
+ const execFileAsync = promisify(execFile);
1989
+ const executeGhAsync = async (args: string[]): Promise<string> => (await execFileAsync('gh', args, {
1990
+ encoding: 'utf8', timeout: 30_000, maxBuffer: 16 * 1024 * 1024, env: process.env,
1991
+ })).stdout;
1992
+
1993
+ /** RUN-TTL adapters: open self-impl PRs, head commit time (async, bounded by the core), owning run, memo, label. */
1994
+ export async function githubRunTtlAdapters(repository: string, execute: GhExecute = executeGh,
1995
+ executeAsync: (args: string[]) => Promise<string> = executeGhAsync, root: string = effectiveInstanceRoot(),
1996
+ shared: Partial<Pick<GithubDraftSweepAdapters, 'openPullRequests' | 'runLedgerMatches'>> = {}): Promise<RunTtlAdapters> {
1997
+ const [inbox, pod] = await Promise.all([import('./control-inbox.js'), import('./self-send-target.js')]);
1998
+ const ledger = join(root, 'run-ttl', 'memos.jsonl');
1999
+ let sent: Set<string> | undefined;
2000
+ const loadSent = (): Set<string> => {
2001
+ if (sent) return sent;
2002
+ sent = new Set<string>();
2003
+ try {
2004
+ for (const line of readFileSync(ledger, 'utf8').split('\n')) {
2005
+ if (!line.trim()) continue;
2006
+ try { const key = (JSON.parse(line) as { key?: unknown }).key; if (typeof key === 'string') sent.add(key); } catch { /* skip */ }
2007
+ }
2008
+ } catch (error) {
2009
+ // Only a missing journal is «nothing sent yet»; an unreadable one cannot prove «once».
2010
+ if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') { sent = undefined; throw error; }
2011
+ }
2012
+ return sent;
2013
+ };
2014
+ let ledgerMatches: readonly RunLedgerMatch[] | undefined;
2015
+ return {
2016
+ listOpenPrs: async () => {
2017
+ const all: GithubPull[] = [];
2018
+ // Inside the draft sweep the open listing is already in memory — one listing per sweep (DRAFT-SWEEP-SLOW).
2019
+ if (shared.openPullRequests) all.push(...await shared.openPullRequests(repository));
2020
+ else for (let page = 1; page <= 100; page++) {
2021
+ const batch = githubPullsPage(repository, 'open', page, execute);
2022
+ if (!Array.isArray(batch) || batch.length > 100) throw new Error('Incomplete open PR inventory');
2023
+ all.push(...batch);
2024
+ if (batch.length < 100) break;
2025
+ if (page === 100) throw new Error('Open PR inventory exceeded pagination limit');
2026
+ }
2027
+ return all.filter((pr) => pr.base?.ref === 'main' && pr.head?.ref?.startsWith('self-impl/') && typeof pr.head.sha === 'string')
2028
+ .map((pr) => ({ number: pr.number, branch: pr.head.ref, labels: pr.labels.map((label) => label.name),
2029
+ createdAt: pr.created_at, headSha: pr.head.sha! }));
2030
+ },
2031
+ lastCommitAt: async (pr) => {
2032
+ const rows = JSON.parse(await executeAsync(['api', `repos/${repository}/commits?sha=${pr.headSha}&per_page=1`])) as
2033
+ Array<{ sha?: string; commit?: { committer?: { date?: string } } }>;
2034
+ const head = Array.isArray(rows) ? rows[0] : undefined;
2035
+ return head?.sha === pr.headSha ? head.commit?.committer?.date : undefined;
2036
+ },
2037
+ resolveRun: async (pr) => {
2038
+ ledgerMatches ??= shared.runLedgerMatches?.() ?? listRunLedgers().matches;
2039
+ return resolveOwningRunForPr(ledgerMatches, repository, pr.number, 'run-ttl');
2040
+ },
2041
+ sendMemo: ({ spaceId }, memo) => deliverResyncMemo(spaceId, memo, inbox, pod),
2042
+ addLabel: async (prNumber, label) => { execute(['pr', 'edit', String(prNumber), '--repo', repository, '--add-label', label]); },
2043
+ memoSent: (key) => loadSent().has(key),
2044
+ recordMemoSent: (key) => {
2045
+ mkdirSync(dirname(ledger), { recursive: true });
2046
+ appendFileSync(ledger, `${JSON.stringify({ key, at: new Date().toISOString() })}\n`);
2047
+ loadSent().add(key);
2048
+ },
2049
+ };
2050
+ }
2051
+
1847
2052
  function ghJson<T>(args: string[], execute: GhExecute): T {
1848
2053
  return JSON.parse(execute(args)) as T;
1849
2054
  }
@@ -1908,7 +2113,94 @@ function githubRemoteRepository(url: string): string | undefined {
1908
2113
  return match?.[1];
1909
2114
  }
1910
2115
 
1911
- export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: DraftSweepGitExecute = runGitCommand, cwd = process.cwd()): DraftSweepAdapters {
2116
+ /** One PR's sweep evidence read in a GraphQL batch. A `null` field was not complete in the batch ⇒ its REST path is used. */
2117
+ interface GraphqlPrDetail {
2118
+ readonly files: string[] | null;
2119
+ readonly comments: string[] | null;
2120
+ readonly reviews: Array<{ state?: string; body?: string }> | null;
2121
+ readonly headSha: string | null;
2122
+ readonly mergeCommit: { oid: string; message: string } | null;
2123
+ }
2124
+
2125
+ /** PRs per GraphQL request: three nested connections × 100 nodes stays far under the node limit and costs ~1 point. */
2126
+ const DRAFT_SWEEP_GRAPHQL_BATCH = 40;
2127
+
2128
+ type GraphqlConnection<T> = { totalCount?: number; nodes?: T[] | null; pageInfo?: { hasNextPage?: boolean } } | null | undefined;
2129
+ const completeNodes = <T>(connection: GraphqlConnection<T>): T[] | null =>
2130
+ connection && Array.isArray(connection.nodes) && connection.pageInfo?.hasNextPage === false
2131
+ && (connection.totalCount === undefined || connection.totalCount === connection.nodes.length) ? connection.nodes : null;
2132
+
2133
+ /**
2134
+ * Batched PR evidence (files · issue comments · reviews · head · merge commit) — one GraphQL request per
2135
+ * DRAFT_SWEEP_GRAPHQL_BATCH PRs instead of 3–7 REST calls per PR (DRAFT-SWEEP-SLOW, 10-08: ~1,300 PRs ⇒ >600 s).
2136
+ * Fail-soft: an unreadable batch or PR yields no entry, and the caller falls back to the per-PR REST path.
2137
+ */
2138
+ async function graphqlPrDetails(repository: string, numbers: readonly number[],
2139
+ executeAsync: (args: string[]) => Promise<string>): Promise<Map<number, GraphqlPrDetail>> {
2140
+ const details = new Map<number, GraphqlPrDetail>();
2141
+ const [owner, name] = repository.split('/');
2142
+ if (!owner || !name || numbers.length === 0) return details;
2143
+ const batches: number[][] = [];
2144
+ for (let index = 0; index < numbers.length; index += DRAFT_SWEEP_GRAPHQL_BATCH) batches.push(numbers.slice(index, index + DRAFT_SWEEP_GRAPHQL_BATCH));
2145
+ let failedBatches = 0;
2146
+ // Fields the batch could not prove complete (each one is read again over REST by the caller).
2147
+ const incompleteFields = { files: 0, comments: 0, reviews: 0 };
2148
+ await mapBounded(batches, DRAFT_SWEEP_CONCURRENCY, async (batch) => {
2149
+ const fields = batch.map((number) => `p${number}: pullRequest(number: ${number}) { number headRefOid `
2150
+ + 'files(first: 100) { totalCount nodes { path } pageInfo { hasNextPage } } '
2151
+ + 'comments(first: 100) { totalCount nodes { body } pageInfo { hasNextPage } } '
2152
+ + 'reviews(first: 100) { totalCount nodes { state body } pageInfo { hasNextPage } } '
2153
+ + 'mergeCommit { oid message } }').join(' ');
2154
+ const query = `query { repository(owner: ${JSON.stringify(owner)}, name: ${JSON.stringify(name)}) { ${fields} } }`;
2155
+ let repositoryNode: Record<string, unknown> | undefined;
2156
+ try {
2157
+ const parsed = JSON.parse(await executeAsync(['api', 'graphql', '-f', `query=${query}`])) as { data?: { repository?: Record<string, unknown> } };
2158
+ repositoryNode = parsed?.data?.repository ?? undefined;
2159
+ } catch { repositoryNode = undefined; }
2160
+ if (!repositoryNode || typeof repositoryNode !== 'object' || Array.isArray(repositoryNode)) { failedBatches += 1; return; }
2161
+ for (const number of batch) {
2162
+ const pr = repositoryNode[`p${number}`] as {
2163
+ number?: number; headRefOid?: string;
2164
+ files?: GraphqlConnection<{ path?: string }>; comments?: GraphqlConnection<{ body?: string }>;
2165
+ reviews?: GraphqlConnection<{ state?: string; body?: string }>; mergeCommit?: { oid?: string; message?: string } | null;
2166
+ } | null | undefined;
2167
+ if (!pr || pr.number !== number) continue;
2168
+ const files = completeNodes(pr.files);
2169
+ const comments = completeNodes(pr.comments);
2170
+ const reviews = completeNodes(pr.reviews);
2171
+ const detail: GraphqlPrDetail = {
2172
+ files: files && files.every((file) => typeof file?.path === 'string') ? files.map((file) => file.path!) : null,
2173
+ comments: comments && comments.every((comment) => typeof comment?.body === 'string') ? comments.map((comment) => comment.body!) : null,
2174
+ reviews: reviews && reviews.every((review) => review && typeof review === 'object')
2175
+ ? reviews.map((review) => ({ state: review.state, body: review.body })) : null,
2176
+ headSha: typeof pr.headRefOid === 'string' && pr.headRefOid ? pr.headRefOid : null,
2177
+ mergeCommit: pr.mergeCommit && typeof pr.mergeCommit.oid === 'string' && typeof pr.mergeCommit.message === 'string'
2178
+ ? { oid: pr.mergeCommit.oid, message: pr.mergeCommit.message } : null,
2179
+ };
2180
+ if (detail.files === null) incompleteFields.files += 1;
2181
+ if (detail.comments === null) incompleteFields.comments += 1;
2182
+ if (detail.reviews === null) incompleteFields.reviews += 1;
2183
+ details.set(number, detail);
2184
+ }
2185
+ });
2186
+ if (failedBatches > 0 || details.size < numbers.length || Object.values(incompleteFields).some((count) => count > 0)) {
2187
+ try {
2188
+ debug.log('self-dev.draft-sweep', 'graphql-fallback', { requested: numbers.length, resolved: details.size, failedBatches, incompleteFields });
2189
+ } catch { /* fail-soft */ }
2190
+ }
2191
+ return details;
2192
+ }
2193
+
2194
+ /** GitHub draft-sweep adapters plus the shared inventory the RUN-TTL step reuses (one open-PR listing per sweep). */
2195
+ export type GithubDraftSweepAdapters = DraftSweepAdapters & {
2196
+ /** The sweep's cached open PR listing — RUN-TTL reads it instead of paging the same list again. */
2197
+ openPullRequests(repository: string): Promise<GithubPull[]>;
2198
+ /** The sweep's cached run-ledger scan. */
2199
+ runLedgerMatches(): readonly RunLedgerMatch[];
2200
+ };
2201
+
2202
+ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: DraftSweepGitExecute = runGitCommand, cwd = process.cwd(),
2203
+ executeAsync: (args: string[]) => Promise<string> = execute === executeGh ? executeGhAsync : async (args) => execute(args)): GithubDraftSweepAdapters {
1912
2204
  const inventory = async (repository: string, state: 'open' | 'closed'): Promise<GithubPull[]> => {
1913
2205
  // ⛔ A large repository has tens of thousands of closed PRs (2026-09-28: 21k → «exceeded pagination limit», no sweep at all).
1914
2206
  // Merged twins only matter after the oldest open draft was opened, so the closed listing stops at that time.
@@ -1937,13 +2229,14 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
1937
2229
  if (!cache.has(repository)) cache.set(repository, inventory(repository, state));
1938
2230
  return cache.get(repository)!;
1939
2231
  };
2232
+ const ghJsonAsync = async <T>(args: string[]): Promise<T> => JSON.parse(await executeAsync(args)) as T;
1940
2233
  // GitHub stops listing a pull request's files at 3,000, and the cut looks like a normal last page.
1941
2234
  // A list that reaches the cap cannot prove it is complete, so it is reported as unknown (undefined):
1942
2235
  // a draft without a provable file list never qualifies for the all-files-landed close.
1943
- const filesFor = (repository: string, number: number): string[] | undefined => {
2236
+ const filesFor = async (repository: string, number: number): Promise<string[] | undefined> => {
1944
2237
  const files: string[] = [];
1945
2238
  for (let page = 1; page <= 100; page++) {
1946
- const batch = ghJson<Array<{ filename: string }>>(['api', `repos/${repository}/pulls/${number}/files?per_page=100&page=${page}`], execute);
2239
+ const batch = await ghJsonAsync<Array<{ filename: string }>>(['api', `repos/${repository}/pulls/${number}/files?per_page=100&page=${page}`]);
1947
2240
  if (!Array.isArray(batch) || batch.length > 100 || batch.some((file) => typeof file.filename !== 'string')) throw new Error('Incomplete PR file inventory');
1948
2241
  files.push(...batch.map((file) => file.filename));
1949
2242
  if (files.length >= GITHUB_PR_FILES_LIST_CAP) {
@@ -1954,18 +2247,31 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
1954
2247
  }
1955
2248
  throw new Error('PR file inventory exceeded pagination limit');
1956
2249
  };
1957
- const latestFileChangesFor = (draft: SweepDraft, repository: string): Record<string, string> | undefined => {
2250
+ // Batched evidence per PR, read once per adapter (= once per sweep) and shared by listing and review/gate.
2251
+ const detailCache = new Map<string, Promise<GraphqlPrDetail | undefined>>();
2252
+ const prefetchDetails = async (repository: string, numbers: readonly number[]): Promise<void> => {
2253
+ const missing = [...new Set(numbers)].filter((number) => !detailCache.has(`${repository}#${number}`));
2254
+ if (missing.length === 0) return;
2255
+ const fetched = graphqlPrDetails(repository, missing, executeAsync);
2256
+ for (const number of missing) detailCache.set(`${repository}#${number}`, fetched.then((map) => map.get(number)));
2257
+ await fetched;
2258
+ };
2259
+ const detailFor = (repository: string, number: number): Promise<GraphqlPrDetail | undefined> =>
2260
+ detailCache.get(`${repository}#${number}`) ?? Promise.resolve(undefined);
2261
+ const prFiles = async (repository: string, number: number): Promise<string[] | undefined> =>
2262
+ (await detailFor(repository, number))?.files ?? filesFor(repository, number);
2263
+ const latestFileChangesFor = async (draft: SweepDraft, repository: string): Promise<Record<string, string> | undefined> => {
1958
2264
  if (!draft.changedFiles?.length) return undefined;
1959
2265
  const changes: Record<string, string> = {};
1960
2266
  for (let page = 1; page <= 100; page++) {
1961
- const batch = ghJson<Array<{ sha: string; commit: { committer: { date: string } } }>>(
1962
- ['api', `repos/${repository}/pulls/${draft.number}/commits?per_page=100&page=${page}`], execute);
2267
+ const batch = await ghJsonAsync<Array<{ sha: string; commit: { committer: { date: string } } }>>(
2268
+ ['api', `repos/${repository}/pulls/${draft.number}/commits?per_page=100&page=${page}`]);
1963
2269
  if (!Array.isArray(batch) || batch.length > 100) throw new Error('Incomplete draft commit inventory');
1964
2270
  for (const commit of batch) {
1965
2271
  const changedAt = commit.commit?.committer?.date;
1966
2272
  if (!commit.sha || !changedAt || !Number.isFinite(Date.parse(changedAt))) return undefined;
1967
- const detail = ghJson<{ files?: Array<{ filename: string; previous_filename?: string }> }>(
1968
- ['api', `repos/${repository}/commits/${commit.sha}?per_page=100`], execute);
2273
+ const detail = await ghJsonAsync<{ files?: Array<{ filename: string; previous_filename?: string }> }>(
2274
+ ['api', `repos/${repository}/commits/${commit.sha}?per_page=100`]);
1969
2275
  // GitHub truncates very large commit file lists; never infer coverage from a partial list.
1970
2276
  if (!Array.isArray(detail.files) || detail.files.length >= 100) return undefined;
1971
2277
  for (const file of detail.files) {
@@ -1979,24 +2285,30 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
1979
2285
  }
1980
2286
  return undefined;
1981
2287
  };
1982
- const reviewGateForDraft = (draft: SweepDraft, repository: string): SweepReviewGate | undefined => {
1983
- const head = ghJson<{ head?: { sha?: string } }>(['api', `repos/${repository}/pulls/${draft.number}`], execute);
1984
- const sha = head?.head?.sha;
1985
- if (!sha) return undefined;
2288
+ const issueCommentsFor = async (repository: string, number: number, what: 'review' | 'landing'): Promise<string[]> => {
1986
2289
  const comments: string[] = [];
1987
2290
  for (let page = 1; page <= 100; page++) {
1988
- const batch = ghJson<Array<{ body?: string }>>(['api', `repos/${repository}/issues/${draft.number}/comments?per_page=100&page=${page}`], execute);
1989
- if (!Array.isArray(batch) || batch.length > 100) throw new Error('Incomplete review comment inventory');
2291
+ const batch = await ghJsonAsync<Array<{ body?: string }>>(['api', `repos/${repository}/issues/${number}/comments?per_page=100&page=${page}`]);
2292
+ if (!Array.isArray(batch) || batch.length > 100) throw new Error(`Incomplete ${what} comment inventory`);
1990
2293
  comments.push(...batch.map((comment) => comment.body ?? ''));
1991
- if (batch.length < 100) break;
1992
- if (page === 100) throw new Error('Review comment inventory exceeded pagination limit');
2294
+ if (batch.length < 100) return comments;
1993
2295
  }
1994
- const reviews = ghJson<Array<{ state?: string; body?: string }>>(['api', `repos/${repository}/pulls/${draft.number}/reviews?per_page=100`], execute);
2296
+ throw new Error(`${what === 'review' ? 'Review' : 'Landing'} comment inventory exceeded pagination limit`);
2297
+ };
2298
+ const reviewGateForDraft = async (draft: SweepDraft, repository: string): Promise<SweepReviewGate | undefined> => {
2299
+ const cached = await detailFor(repository, draft.number);
2300
+ const sha = cached?.headSha
2301
+ ?? (await ghJsonAsync<{ head?: { sha?: string } }>(['api', `repos/${repository}/pulls/${draft.number}`]))?.head?.sha;
2302
+ if (!sha) return undefined;
2303
+ const comments = cached?.comments ?? await issueCommentsFor(repository, draft.number, 'review');
2304
+ const reviews = cached?.reviews
2305
+ ?? await ghJsonAsync<Array<{ state?: string; body?: string }>>(['api', `repos/${repository}/pulls/${draft.number}/reviews?per_page=100`]);
1995
2306
  if (!Array.isArray(reviews) || reviews.length > 100) throw new Error('Incomplete review inventory');
1996
2307
  const text = [...comments, ...reviews.map((review) => review.body ?? '')].join('\n');
1997
- const status = ghJson<{ state?: string }>(['api', `repos/${repository}/commits/${sha}/status`], execute);
1998
- const checkRuns = ghJson<{ check_runs?: Array<{ conclusion?: string | null }> }>(
1999
- ['api', `repos/${repository}/commits/${sha}/check-runs?per_page=100`], execute);
2308
+ const [status, checkRuns] = await Promise.all([
2309
+ ghJsonAsync<{ state?: string }>(['api', `repos/${repository}/commits/${sha}/status`]),
2310
+ ghJsonAsync<{ check_runs?: Array<{ conclusion?: string | null }> }>(['api', `repos/${repository}/commits/${sha}/check-runs?per_page=100`]),
2311
+ ]);
2000
2312
  if (!checkRuns || !Array.isArray(checkRuns.check_runs)) throw new Error('Incomplete check-run inventory');
2001
2313
  const conclusions = checkRuns.check_runs.map((run) => run.conclusion ?? null);
2002
2314
  const gatePass = (status?.state === 'success' || status?.state === undefined)
@@ -2009,15 +2321,42 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
2009
2321
  ...(gatePass && !gateFail ? { gate: 'pass' as const } : gateFail ? { gate: 'fail' as const } : {}),
2010
2322
  };
2011
2323
  };
2012
- const landingCommentsFor = (repository: string, number: number): string[] => {
2013
- const comments: string[] = [];
2014
- for (let page = 1; page <= 100; page++) {
2015
- const batch = ghJson<Array<{ body?: string }>>(['api', `repos/${repository}/issues/${number}/comments?per_page=100&page=${page}`], execute);
2016
- if (!Array.isArray(batch) || batch.length > 100) throw new Error('Incomplete landing comment inventory');
2017
- comments.push(...batch.filter((comment) => /\blanding-verified\b/i.test(comment.body ?? '')).map((comment) => comment.body!));
2018
- if (batch.length < 100) return comments;
2019
- }
2020
- throw new Error('Landing comment inventory exceeded pagination limit');
2324
+ const landingCommentsFor = async (repository: string, number: number): Promise<string[]> =>
2325
+ ((await detailFor(repository, number))?.comments ?? await issueCommentsFor(repository, number, 'landing'))
2326
+ .filter((body) => /\blanding-verified\b/i.test(body));
2327
+ const mergeCommitMessageFor = async (repository: string, pr: GithubPull): Promise<string | undefined> => {
2328
+ if (!pr.merge_commit_sha) return undefined;
2329
+ const cached = (await detailFor(repository, pr.number))?.mergeCommit;
2330
+ if (cached && cached.oid === pr.merge_commit_sha) return cached.message;
2331
+ return (await ghJsonAsync<{ commit: { message: string } }>(['api', `repos/${repository}/commits/${pr.merge_commit_sha}`])).commit.message;
2332
+ };
2333
+ // The whole draft / merged inventory is enriched once (bounded concurrency) and then paged from memory.
2334
+ const draftRows = new Map<string, Promise<SweepDraft[]>>();
2335
+ const mergedRows = new Map<string, Promise<SweepMergedPr[]>>();
2336
+ const enrichedDrafts = (repository: string): Promise<SweepDraft[]> => {
2337
+ if (!draftRows.has(repository)) draftRows.set(repository, (async () => {
2338
+ const prs = (await listed(open, repository, 'open')).filter((pr) => pr.draft);
2339
+ await prefetchDetails(repository, prs.map((pr) => pr.number));
2340
+ const matches = ledgerMatches ??= listRunLedgers().matches;
2341
+ return mapBounded(prs, DRAFT_SWEEP_CONCURRENCY, async (pr): Promise<SweepDraft> => ({
2342
+ number: pr.number, title: pr.title, branch: pr.head.ref,
2343
+ labels: pr.labels.map((item) => item.name), createdAt: pr.created_at, body: pr.body ?? undefined,
2344
+ changedFiles: await prFiles(repository, pr.number), ...(pr.updated_at ? { updatedAt: pr.updated_at } : {}),
2345
+ runId: currentDraftSweepRunId(matches, repository, pr.number) }));
2346
+ })());
2347
+ return draftRows.get(repository)!;
2348
+ };
2349
+ const enrichedMerged = (repository: string): Promise<SweepMergedPr[]> => {
2350
+ if (!mergedRows.has(repository)) mergedRows.set(repository, (async () => {
2351
+ const prs = (await listed(closed, repository, 'closed')).filter((pr) => pr.merged_at !== null && pr.base?.ref === 'main');
2352
+ await prefetchDetails(repository, prs.map((pr) => pr.number));
2353
+ return mapBounded(prs, DRAFT_SWEEP_CONCURRENCY, async (pr): Promise<SweepMergedPr> => ({
2354
+ number: pr.number, title: pr.title, branch: pr.head.ref, body: pr.body ?? undefined,
2355
+ mergedAt: pr.merged_at ?? undefined, changedFiles: await prFiles(repository, pr.number),
2356
+ mergeCommitMessage: await mergeCommitMessageFor(repository, pr),
2357
+ landingVerifiedComments: await landingCommentsFor(repository, pr.number) }));
2358
+ })());
2359
+ return mergedRows.get(repository)!;
2021
2360
  };
2022
2361
  let ledgerMatches: readonly RunLedgerMatch[] | undefined;
2023
2362
  let running: ReadonlyMap<string, string> | undefined;
@@ -2031,22 +2370,35 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
2031
2370
  return draftSweepRunStatus(ledgerMatches, running, repository, draft.number);
2032
2371
  };
2033
2372
  return {
2034
- getPrFiles: async (repository, number) => filesFor(repository, number),
2373
+ openPullRequests: (repository) => listed(open, repository, 'open'),
2374
+ runLedgerMatches: () => (ledgerMatches ??= listRunLedgers().matches),
2375
+ getPrFiles: async (repository, number) => prFiles(repository, number),
2376
+ getPr: async (repository, number) => {
2377
+ const pr = ghJson<GithubPull>(['api', `repos/${repository}/pulls/${number}`], execute);
2378
+ if (!pr || pr.number !== number || typeof pr.head?.ref !== 'string' || !Array.isArray(pr.labels)) return undefined;
2379
+ return { number: pr.number, title: pr.title, branch: pr.head.ref, labels: pr.labels.map((label) => label.name),
2380
+ createdAt: pr.created_at, mergedAt: pr.merged_at ?? undefined };
2381
+ },
2382
+ listRecentClosed: async (repository, createdSince) => {
2383
+ const rows: SweepMergedPr[] = [];
2384
+ for (let page = 1; page <= 100; page++) {
2385
+ const batch = githubPullsPage(repository, 'closed', page, execute);
2386
+ if (!Array.isArray(batch) || batch.length > 100 || batch.some((pr) => !pr.updated_at || !Number.isFinite(Date.parse(pr.updated_at))
2387
+ || !Array.isArray(pr.labels) || !pr.created_at || !Number.isFinite(Date.parse(pr.created_at)))) throw new Error('Incomplete closed metric inventory');
2388
+ for (const pr of batch) if (pr.base?.ref === 'main' && (pr.head?.ref?.startsWith('self-impl/')
2389
+ || pr.labels.some((label) => label.name?.startsWith('elanous:')))) rows.push({
2390
+ number: pr.number, title: pr.title, branch: pr.head.ref,
2391
+ labels: pr.labels.map((label) => label.name),
2392
+ createdAt: pr.created_at, mergedAt: pr.merged_at ?? undefined,
2393
+ });
2394
+ if (batch.length < 100 || Date.parse(batch.at(-1)!.updated_at!) < createdSince.getTime()) return rows;
2395
+ }
2396
+ throw new Error('Closed metric inventory exceeded pagination limit');
2397
+ },
2035
2398
  listDrafts: async (page, perPage, repository): Promise<SweepDraft[]> =>
2036
- (await listed(open, repository, 'open')).filter((pr) => pr.draft)
2037
- .slice((page - 1) * perPage, page * perPage)
2038
- .map((pr) => ({ number: pr.number, title: pr.title, branch: pr.head.ref,
2039
- labels: pr.labels.map((item) => item.name), createdAt: pr.created_at, body: pr.body ?? undefined,
2040
- changedFiles: filesFor(repository, pr.number), ...(pr.updated_at ? { updatedAt: pr.updated_at } : {}),
2041
- runId: currentDraftSweepRunId(ledgerMatches ??= listRunLedgers().matches, repository, pr.number) })),
2399
+ (await enrichedDrafts(repository)).slice((page - 1) * perPage, page * perPage),
2042
2400
  listMerged: async (page, perPage, repository): Promise<SweepMergedPr[]> =>
2043
- (await listed(closed, repository, 'closed')).filter((pr) => pr.merged_at !== null && pr.base?.ref === 'main')
2044
- .slice((page - 1) * perPage, page * perPage)
2045
- .map((pr) => ({ number: pr.number, title: pr.title, branch: pr.head.ref, body: pr.body ?? undefined,
2046
- mergedAt: pr.merged_at ?? undefined, changedFiles: filesFor(repository, pr.number),
2047
- mergeCommitMessage: pr.merge_commit_sha ? ghJson<{ commit: { message: string } }>(
2048
- ['api', `repos/${repository}/commits/${pr.merge_commit_sha}`], execute).commit.message : undefined,
2049
- landingVerifiedComments: landingCommentsFor(repository, pr.number) })),
2401
+ (await enrichedMerged(repository)).slice((page - 1) * perPage, page * perPage),
2050
2402
  getRunStatus: async (draft, repository) => runStatusFor(draft, repository),
2051
2403
  getLatestFileChanges: async (draft, repository) => latestFileChangesFor(draft, repository),
2052
2404
  hasFinalRunResult: async (draft, repository) => {
@@ -2097,6 +2449,18 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
2097
2449
  execute(['pr', 'close', String(number), '--repo', repository, '--comment', comment]);
2098
2450
  },
2099
2451
  getClaimOwner: async (repository, number) => claimCommentOwner(repository, number, execute),
2452
+ getActiveClaimOwner: async (repository, number) => {
2453
+ // DRAFT-METRIC: the batched GraphQL comments (oldest first, proven complete) answer without a per-draft REST call.
2454
+ const batched = (await detailFor(repository, number))?.comments;
2455
+ if (batched) return activeClaimOwnerFromBodies(batched);
2456
+ const pages = ghJson<Array<Array<{ body: string; created_at: string }>>>(['api', '--paginate', '--slurp',
2457
+ `repos/${repository}/issues/${number}/comments?per_page=100`], execute);
2458
+ if (!Array.isArray(pages) || pages.some((page) => !Array.isArray(page)
2459
+ || page.some((comment) => typeof comment.body !== 'string' || !Number.isFinite(Date.parse(comment.created_at)))))
2460
+ throw new Error('Incomplete claim comments');
2461
+ return activeClaimOwnerFromBodies(pages.flat().sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at))
2462
+ .map((comment) => comment.body));
2463
+ },
2100
2464
  getReviewGate: async (draft, repository) => reviewGateForDraft(draft, repository),
2101
2465
  };
2102
2466
  }
@@ -2107,6 +2471,34 @@ export interface HarnessDraftSweepDeps {
2107
2471
  readonly write?: (line: string) => void;
2108
2472
  readonly execute?: GhExecute;
2109
2473
  readonly now?: () => Date;
2474
+ /** RUN-TTL step adapters. Omitted with injected `adapters` ⇒ the step is skipped (fakes never reach real gh). */
2475
+ readonly runTtl?: RunTtlAdapters;
2476
+ }
2477
+
2478
+ /** RUN-TTL step inside the draft sweep — fail-soft: its failure never changes the sweep verdict. */
2479
+ async function draftSweepRunTtl(repository: string, deps: HarnessDraftSweepDeps,
2480
+ shared?: GithubDraftSweepAdapters): Promise<RunTtlResult | { error: string } | undefined> {
2481
+ if (deps.adapters && !deps.runTtl) return undefined;
2482
+ try {
2483
+ const { runRunTtlSweep } = await import('../self-implement/run-ttl.js');
2484
+ const config = (() => { try { return getUserConfig().tools?.selfImplement; } catch { return undefined; } })();
2485
+ const adapters = deps.runTtl ?? await githubRunTtlAdapters(repository, deps.execute ?? executeGh,
2486
+ deps.execute ? async (args) => deps.execute!(args) : executeGhAsync, undefined, shared ?? {});
2487
+ return await runRunTtlSweep({
2488
+ adapters, mode: config?.runTtl?.mode ?? 'shadow', ttlHours: config?.runTtlHours, cap: config?.runTtl?.cap,
2489
+ ...(deps.now ? { now: () => deps.now!().getTime() } : {}),
2490
+ });
2491
+ } catch (error) {
2492
+ const reason = sweepFailureReason(error);
2493
+ try { debug.log('self-implement.run-ttl', 'failed', { reason }); } catch { /* fail-soft */ }
2494
+ return { error: reason };
2495
+ }
2496
+ }
2497
+
2498
+ /** Latest claim or release among comments in chronological order (oldest first); a release means no owner. */
2499
+ export function activeClaimOwnerFromBodies(bodies: readonly string[]): string | undefined {
2500
+ const last = [...bodies].reverse().find((body) => draftClaimOwner(body) !== undefined || body.startsWith('🔧 처리 끝 —'));
2501
+ return last ? draftClaimOwner(last) : undefined;
2110
2502
  }
2111
2503
 
2112
2504
  function draftClaimOwner(body: string): string | undefined {
@@ -2165,6 +2557,25 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
2165
2557
  process.exitCode = 1;
2166
2558
  }
2167
2559
  });
2560
+ drafts.command('metrics').description('draft 재고·최장 나이·needs-owner·48h 전환율 (읽기 전용 · PR 근거는 GraphQL 배치)')
2561
+ .option('--json', 'JSON 으로 출력')
2562
+ .option('--repo <owner/name>', '조회할 GitHub 저장소')
2563
+ .action(async (opts: { json?: boolean; repo?: string }) => {
2564
+ try {
2565
+ const execute = deps.execute ?? executeGh;
2566
+ const repository = resolveRepositoryName({ repo: opts.repo ?? deps.repository?.(), executeGh: (args) => execute(args) });
2567
+ if (!DRAFT_SWEEP_REPOSITORY.test(repository) || repository.includes('..')) throw new HarnessCliInputError(`invalid --repo (expected owner/name): ${repository}`);
2568
+ const started = Date.now();
2569
+ const metrics = await collectDraftMetrics(repository, deps.adapters ?? githubDraftSweepAdapters(deps.execute), deps.now?.() ?? new Date());
2570
+ try { debug.log('self-dev.draft-metrics', 'collected', { repository, ms: Date.now() - started, ...metrics }); } catch { /* fail-soft */ }
2571
+ const write = deps.write ?? console.log;
2572
+ if (opts.json) write(JSON.stringify({ repository, ...metrics }));
2573
+ else write(`draft 재고 ${metrics.inventory} · 최장 나이 ${metrics.oldestAgeHours === null ? '해당 없음' : `${metrics.oldestAgeHours.toFixed(1)}h`} · needs-owner ${metrics.needsOwner} · 48h 전환율 ${metrics.conversion48h === null ? '표본 없음' : `${(metrics.conversion48h * 100).toFixed(1)}% (${metrics.converted48h}/${metrics.cohort48h})`}`);
2574
+ } catch (error) {
2575
+ console.error(humanErrorLine(error));
2576
+ process.exitCode = 1;
2577
+ }
2578
+ });
2168
2579
  return drafts.command('sweep').description('draft PR 을 조사한다 (기본: 변경 없음)')
2169
2580
  .option('--apply', '판정한 라벨 변경과 종료를 적용')
2170
2581
  .option('--json', '판정 결과를 JSON 으로 출력')
@@ -2180,10 +2591,19 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
2180
2591
  if (!DRAFT_SWEEP_REPOSITORY.test(repository) || repository.includes('..')) {
2181
2592
  throw new HarnessCliInputError(`invalid --repo (expected owner/name): ${repository}`);
2182
2593
  }
2183
- const result: DraftSweepResult = await runDraftSweep({ repository, apply: opts.apply === true, adapters: deps.adapters ?? githubDraftSweepAdapters(deps.execute) });
2594
+ // One adapter instance per sweep: its open listing and ledger scan are shared with the RUN-TTL step.
2595
+ const github = deps.adapters ? undefined : githubDraftSweepAdapters(deps.execute);
2596
+ const result: DraftSweepResult = await runDraftSweep({ repository, apply: opts.apply === true, adapters: deps.adapters ?? github! });
2184
2597
  const reason = result.error ? sweepFailureReason(result.error) : undefined;
2185
- if (opts.json) write(JSON.stringify(reason ? { ...result, error: reason } : result));
2598
+ const ttlStarted = Date.now();
2599
+ const runTtl = await draftSweepRunTtl(repository, deps, github);
2600
+ try { debug.log('self-dev.draft-sweep', 'timing', { phase: 'run-ttl', ms: Date.now() - ttlStarted, count: runTtl && !('error' in runTtl) ? runTtl.considered : 0 }); } catch { /* fail-soft */ }
2601
+ if (opts.json) write(JSON.stringify({ ...(reason ? { ...result, error: reason } : result), ...(runTtl ? { runTtl } : {}) }));
2186
2602
  else {
2603
+ if (runTtl) {
2604
+ write('error' in runTtl ? `run-ttl: 실패 (${runTtl.error})`
2605
+ : `run-ttl(${runTtl.mode}, ${runTtl.ttlHours}h): 대상 ${runTtl.considered} · 낡음 ${runTtl.stale.length} · 메모 ${runTtl.memo.length} · 표식 ${runTtl.label.length} · 이미 표식 ${runTtl.alreadyMarked.length} · 상한 초과 ${runTtl.skippedOverCap.length}${runTtl.mode === 'shadow' ? ' (shadow — 쓰기 0)' : ''}`);
2606
+ }
2187
2607
  write(`draft sweep ${result.repository}: ${result.complete ? 'complete' : `incomplete (${reason})`} · ${result.apply ? 'apply' : 'dry-run'}`);
2188
2608
  for (const entry of result.entries) write(`#${entry.number} ${entry.action}: ${entry.reason}${entry.statusLabel ? ` → ${entry.statusLabel}` : ''}${entry.error ? ` ERROR: ${sweepFailureReason(entry.error)}` : ''}`);
2189
2609
  if (result.complete) {
@@ -2513,6 +2933,7 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
2513
2933
  installHarnessProcessObservationCommand(harnessCmd, deps.processObservation);
2514
2934
  installHarnessBudgetCommand(harnessCmd);
2515
2935
  installHarnessSalvageCommand(harnessCmd);
2936
+ installHarnessSalvageRetentionCommand(harnessCmd);
2516
2937
  installHarnessGoalCommand(harnessCmd, deps.goalLookup, deps.goalArchive);
2517
2938
  installHarnessDraftSweepCommand(harnessCmd, deps.draftSweep);
2518
2939
  const queue = harnessCmd.command('queue').description('자리별 영속 발사 대기열');
@@ -2567,9 +2988,11 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
2567
2988
  queue.command('reconcile <id>').description('불확정 발사를 확인하고 종료 또는 미발사 증거가 있으면 예약 해소').action((id: string) => queueAction(async () => {
2568
2989
  console.log(`${id} ${await reconcileHarnessQueue(id, queueDeps)}`);
2569
2990
  }));
2570
- queue.command('tick').description('자리별 맨 앞 한 건을 라운드로빈으로 돌며 자리 몫·Pod 풀 여유·마무리 관문을 확인하고 한 건 발사').action(() => queueAction(async () => {
2991
+ queue.command('tick').description('자리별 맨 앞 항목을 라운드로빈으로 돌며 자리 몫·Pod 풀 여유·마무리 관문을 확인하고 발사 — 여유가 있으면 한 tick 에 여러 건(30초 간격 · harness.queue.burstMax 기본 5)').action(() => queueAction(async () => {
2571
2992
  const result = await tickHarnessQueue(queueDeps);
2572
2993
  console.log(`${result.outcome}${result.item ? ` ${result.item.id}` : ''}: ${result.reason}`);
2994
+ // QUEUE-BURST: the first line stays as before; every further launch of the burst gets its own line.
2995
+ for (const row of result.launched?.slice(1) ?? []) console.log(`launched ${row.id}: spawned`);
2573
2996
  }));
2574
2997
 
2575
2998
  const ask = deps.ask;