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
@@ -0,0 +1,258 @@
1
+ /**
2
+ * RUN-TTL (0.2.21 P1 · first piece · FINDING #24712) — an open harness PR has a «time to landing».
3
+ *
4
+ * Open self-impl PRs linger for days while main moves (~80 merges/day), so an old draft rots silently.
5
+ * Past `tools.selfImplement.runTtlHours` (default 24) since its last commit, the sweep makes one pre-emptive move:
6
+ * · live owning run → one resync memo in its control inbox (same path as SIBLING-RESYNC #24893/#24899),
7
+ * at most once per (PR, head sha) — a rebase moves the head and re-arms it
8
+ * · no live run → the registered `elanous:needs-rebase` label, once (QUIET-PR-COMMENTS: never a comment)
9
+ *
10
+ * Mode `tools.selfImplement.runTtl.mode`: `shadow` (default) only logs what it would do; `live` writes.
11
+ * Cost bound: a PR opened inside the TTL is still inside its own time-to-landing and is never fetched
12
+ * (`freshByCreation`), already-marked PRs without a live run are skipped before any fetch, at most `maxFetch` last-commit lookups per sweep with `concurrency` in flight, and at most `cap` actions.
13
+ *
14
+ * Pure core: every side effect is an injected adapter so tests never touch GitHub or the inbox.
15
+ */
16
+ import { debug } from '../debug/log.js';
17
+ import type { ControlMemoPayload } from '../harness/control-inbox.js';
18
+ import { SIBLING_RESYNC_MARKER_LABEL, type SiblingRunTarget } from './sibling-resync.js';
19
+
20
+ export const RUN_TTL_CATEGORY = 'self-implement.run-ttl';
21
+ export const RUN_TTL_DEFAULT_HOURS = 24;
22
+ export const RUN_TTL_DEFAULT_CAP = 30;
23
+ export const RUN_TTL_DEFAULT_MAX_FETCH = 60;
24
+ export const RUN_TTL_DEFAULT_CONCURRENCY = 6;
25
+ export const RUN_TTL_MARKER_LABEL = SIBLING_RESYNC_MARKER_LABEL;
26
+ const HARNESS_BRANCH_PREFIX = 'self-impl/';
27
+ const KEEP_LABEL = 'elanous:keep';
28
+
29
+ export type RunTtlMode = 'shadow' | 'live';
30
+
31
+ export interface RunTtlCandidate {
32
+ readonly number: number;
33
+ readonly branch: string;
34
+ readonly labels: readonly string[];
35
+ readonly createdAt: string;
36
+ readonly headSha: string;
37
+ }
38
+
39
+ export interface RunTtlAdapters {
40
+ /** Open PRs against the default branch. A rejection is not an empty list. */
41
+ listOpenPrs(): Promise<readonly RunTtlCandidate[]>;
42
+ /** ISO time of the head commit; `undefined` (or a rejection) means unknown and the PR is left alone. */
43
+ lastCommitAt(pr: RunTtlCandidate): Promise<string | undefined>;
44
+ resolveRun(pr: RunTtlCandidate): Promise<SiblingRunTarget>;
45
+ sendMemo(target: { runId: string; spaceId: string }, memo: ControlMemoPayload): Promise<void> | void;
46
+ addLabel(prNumber: number, label: string): Promise<void> | void;
47
+ /** Memo dedupe by `<pr>:<headSha>`. Omitted ⇒ no dedupe. */
48
+ memoSent?(key: string): boolean;
49
+ recordMemoSent?(key: string): void;
50
+ }
51
+
52
+ export interface RunTtlResult {
53
+ readonly mode: RunTtlMode;
54
+ readonly ttlHours: number;
55
+ /** Open self-impl PRs considered (keep-labelled excluded). */
56
+ readonly considered: number;
57
+ readonly freshByCreation: number;
58
+ readonly fetched: number;
59
+ readonly fresh: number;
60
+ readonly unknown: readonly number[];
61
+ readonly stale: readonly number[];
62
+ /** Memo sent (live) or would be sent (shadow). */
63
+ readonly memo: readonly number[];
64
+ /** Label added (live) or would be added (shadow). */
65
+ readonly label: readonly number[];
66
+ readonly alreadyMarked: readonly number[];
67
+ readonly alreadyMemoed: readonly number[];
68
+ /** Owning run lookup failed — left alone (a failed lookup is not a dead run). */
69
+ readonly unresolved?: readonly number[];
70
+ readonly skippedOverCap: readonly number[];
71
+ readonly skippedFetchCap: readonly number[];
72
+ readonly failed: readonly number[];
73
+ readonly durationMs: number;
74
+ readonly reason?: string;
75
+ }
76
+
77
+ export function runTtlMemo(ageHours: number, ttlHours: number): ControlMemoPayload {
78
+ return {
79
+ version: 1,
80
+ kind: 'run-ttl-resync',
81
+ urgency: 'normal',
82
+ body: `[run-ttl] This PR's last commit is ${Math.floor(ageHours)}h old (time-to-landing ${ttlHours}h) while main keeps moving. `
83
+ + 'Before continuing: git fetch origin main, rebase this branch onto origin/main (resolve conflicts keeping both intents), then re-run the gate on your changed files.',
84
+ };
85
+ }
86
+
87
+ async function mapBounded<T, R>(items: readonly T[], limit: number, work: (item: T) => Promise<R>): Promise<R[]> {
88
+ const results = new Array<R>(items.length);
89
+ let next = 0;
90
+ const workers = Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, async () => {
91
+ while (next < items.length) {
92
+ const index = next++;
93
+ results[index] = await work(items[index]!);
94
+ }
95
+ });
96
+ await Promise.all(workers);
97
+ return results;
98
+ }
99
+
100
+ const nonNegativeInt = (value: number | undefined, fallback: number): number =>
101
+ value !== undefined && Number.isSafeInteger(value) && value >= 0 ? value : fallback;
102
+
103
+ export async function runRunTtlSweep(input: {
104
+ adapters: RunTtlAdapters;
105
+ mode?: RunTtlMode;
106
+ ttlHours?: number;
107
+ cap?: number;
108
+ maxFetch?: number;
109
+ concurrency?: number;
110
+ now?: () => number;
111
+ log?: (category: string, event: string, data: Record<string, unknown>) => void;
112
+ }): Promise<RunTtlResult> {
113
+ const { adapters } = input;
114
+ const now = input.now ?? Date.now;
115
+ const startedAt = now();
116
+ const mode: RunTtlMode = input.mode === 'live' ? 'live' : 'shadow';
117
+ const ttlHours = typeof input.ttlHours === 'number' && Number.isFinite(input.ttlHours) && input.ttlHours > 0
118
+ ? input.ttlHours : RUN_TTL_DEFAULT_HOURS;
119
+ const cap = nonNegativeInt(input.cap, RUN_TTL_DEFAULT_CAP);
120
+ const maxFetch = nonNegativeInt(input.maxFetch, RUN_TTL_DEFAULT_MAX_FETCH);
121
+ const concurrency = Math.max(1, nonNegativeInt(input.concurrency, RUN_TTL_DEFAULT_CONCURRENCY));
122
+ const ttlMs = ttlHours * 3_600_000;
123
+ const log = input.log ?? ((category, event, data) => debug.log(category, event, data));
124
+ const emit = (event: string, data: Record<string, unknown>) => {
125
+ try { log(RUN_TTL_CATEGORY, event, { mode, ...data }); } catch { /* fail-soft */ }
126
+ };
127
+ let considered = 0;
128
+ let freshByCreation = 0;
129
+ let fetched = 0;
130
+ let fresh = 0;
131
+ const unknown: number[] = [];
132
+ const stale: number[] = [];
133
+ const memo: number[] = [];
134
+ const label: number[] = [];
135
+ const alreadyMarked: number[] = [];
136
+ const alreadyMemoed: number[] = [];
137
+ const unresolved: number[] = [];
138
+ const skippedOverCap: number[] = [];
139
+ const skippedFetchCap: number[] = [];
140
+ const failed: number[] = [];
141
+ const result = (reason?: string): RunTtlResult => ({
142
+ mode, ttlHours, considered, freshByCreation, fetched, fresh, unknown, stale, memo, label, alreadyMarked,
143
+ alreadyMemoed, unresolved, skippedOverCap, skippedFetchCap, failed, durationMs: Math.max(0, now() - startedAt),
144
+ ...(reason ? { reason } : {}),
145
+ });
146
+ const summary = (out: RunTtlResult): RunTtlResult => {
147
+ emit('summary', {
148
+ ttlHours, cap, maxFetch, concurrency, considered, freshByCreation, fetched, fresh,
149
+ unknown: unknown.length, stale: stale.length, memo: memo.length, label: label.length,
150
+ alreadyMarked: alreadyMarked.length, alreadyMemoed: alreadyMemoed.length, unresolved: unresolved.length,
151
+ skippedOverCap: skippedOverCap.length, skippedFetchCap: skippedFetchCap.length, failed: failed.length,
152
+ memoPrs: memo, labelPrs: label, durationMs: out.durationMs, ...(out.reason ? { reason: out.reason } : {}),
153
+ });
154
+ return out;
155
+ };
156
+
157
+ let open: readonly RunTtlCandidate[];
158
+ try {
159
+ open = await adapters.listOpenPrs();
160
+ } catch (error) {
161
+ emit('skipped', { reason: 'inventory-failed', error: String(error) });
162
+ return summary(result('inventory-failed'));
163
+ }
164
+ const nowMs = now();
165
+ const pending: { pr: RunTtlCandidate; target: SiblingRunTarget }[] = [];
166
+ for (const pr of open) {
167
+ if (!pr.branch.startsWith(HARNESS_BRANCH_PREFIX) || pr.labels.includes(KEEP_LABEL)) continue;
168
+ considered += 1;
169
+ const created = Date.parse(pr.createdAt);
170
+ // A PR opened inside the TTL is inside its own time-to-landing — no fetch needed.
171
+ if (Number.isFinite(created) && nowMs - created < ttlMs) { freshByCreation += 1; continue; }
172
+ let target: SiblingRunTarget;
173
+ try { target = await adapters.resolveRun(pr); } catch (error) {
174
+ // A failed lookup is not proof the run is gone — never label (or memo) an unresolved PR.
175
+ unresolved.push(pr.number);
176
+ emit('run-unresolved', { pr: pr.number, error: String(error) });
177
+ continue;
178
+ }
179
+ // Dead run + marker already present: the quiet signal is in place («once»), nothing to fetch.
180
+ if (!target.alive && pr.labels.includes(RUN_TTL_MARKER_LABEL)) { alreadyMarked.push(pr.number); continue; }
181
+ pending.push({ pr, target });
182
+ }
183
+ // Oldest PR first: the most likely to have rotted.
184
+ pending.sort((a, b) => (Date.parse(a.pr.createdAt) || 0) - (Date.parse(b.pr.createdAt) || 0) || a.pr.number - b.pr.number);
185
+ const toFetch = pending.slice(0, maxFetch);
186
+ skippedFetchCap.push(...pending.slice(maxFetch).map(({ pr }) => pr.number));
187
+ const dated = await mapBounded(toFetch, concurrency, async (entry) => {
188
+ let at: string | undefined;
189
+ try { at = await adapters.lastCommitAt(entry.pr); } catch { at = undefined; }
190
+ fetched += 1;
191
+ const ms = at ? Date.parse(at) : NaN;
192
+ return { ...entry, ageMs: Number.isFinite(ms) ? nowMs - ms : undefined };
193
+ });
194
+ const stalePrs: (typeof dated[number] & { ageMs: number })[] = [];
195
+ for (const entry of dated) {
196
+ if (entry.ageMs === undefined) { unknown.push(entry.pr.number); continue; }
197
+ if (entry.ageMs < ttlMs) { fresh += 1; continue; }
198
+ stale.push(entry.pr.number);
199
+ stalePrs.push({ ...entry, ageMs: entry.ageMs });
200
+ }
201
+ stalePrs.sort((a, b) => b.ageMs - a.ageMs || a.pr.number - b.pr.number);
202
+ let actions = 0;
203
+ for (const { pr, target, ageMs } of stalePrs) {
204
+ const ageHours = ageMs / 3_600_000;
205
+ const key = `${pr.number}:${pr.headSha}`;
206
+ if (target.alive) {
207
+ let already: boolean | undefined;
208
+ try { already = adapters.memoSent?.(key) ?? false; } catch (error) {
209
+ // Without the journal «once» cannot be proven — skip rather than risk a repeat memo.
210
+ failed.push(pr.number);
211
+ emit('memo-journal-failed', { pr: pr.number, stage: 'read', error: String(error) });
212
+ continue;
213
+ }
214
+ if (already) { alreadyMemoed.push(pr.number); continue; }
215
+ }
216
+ if (!target.alive && pr.labels.includes(RUN_TTL_MARKER_LABEL)) { alreadyMarked.push(pr.number); continue; }
217
+ if (actions >= cap) { skippedOverCap.push(pr.number); continue; }
218
+ actions += 1;
219
+ if (target.alive) {
220
+ if (mode === 'shadow') {
221
+ memo.push(pr.number);
222
+ emit('would-memo', { pr: pr.number, runId: target.runId, spaceId: target.spaceId, ageHours: Math.floor(ageHours) });
223
+ continue;
224
+ }
225
+ // Journal first: a memo that cannot be recorded is not sent, so «once per (PR, head sha)» holds.
226
+ try { adapters.recordMemoSent?.(key); } catch (error) {
227
+ failed.push(pr.number);
228
+ emit('memo-journal-failed', { pr: pr.number, stage: 'write', error: String(error) });
229
+ continue;
230
+ }
231
+ try {
232
+ await adapters.sendMemo({ runId: target.runId, spaceId: target.spaceId }, runTtlMemo(ageHours, ttlHours));
233
+ memo.push(pr.number);
234
+ emit('memo', { pr: pr.number, runId: target.runId, spaceId: target.spaceId, ageHours: Math.floor(ageHours) });
235
+ continue;
236
+ } catch (error) {
237
+ // A live run we could not reach still needs to be visible — fall through to the quiet marker.
238
+ emit('memo-failed', { pr: pr.number, runId: target.runId, error: String(error) });
239
+ if (pr.labels.includes(RUN_TTL_MARKER_LABEL)) { alreadyMarked.push(pr.number); continue; }
240
+ }
241
+ }
242
+ const reason = target.alive ? 'memo-failed' : target.reason;
243
+ if (mode === 'shadow') {
244
+ label.push(pr.number);
245
+ emit('would-label', { pr: pr.number, label: RUN_TTL_MARKER_LABEL, reason, ageHours: Math.floor(ageHours) });
246
+ continue;
247
+ }
248
+ try {
249
+ await adapters.addLabel(pr.number, RUN_TTL_MARKER_LABEL);
250
+ label.push(pr.number);
251
+ emit('label', { pr: pr.number, label: RUN_TTL_MARKER_LABEL, reason, ageHours: Math.floor(ageHours) });
252
+ } catch (error) {
253
+ failed.push(pr.number);
254
+ emit('label-failed', { pr: pr.number, error: String(error) });
255
+ }
256
+ }
257
+ return summary(result());
258
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * SALVAGE-RETENTION (RFC-draft-pr-accumulation-root-fix R1 다음 조각) — 원격 `salvage/*` 가지의 보존 규칙.
3
+ *
4
+ * S: DRAFT-NOT-ARCHIVE 로 멈춘 런은 PR 대신 수확 가지를 남긴다. 가지는 이미 1,000개를 넘었다.
5
+ * C: 가지가 지워지지 않고 쌓이면 draft 재고가 «수확 가지 재고»로 옮겨 갈 뿐이다.
6
+ * Q: 어느 가지를 지워도 되나?
7
+ * A: 14일보다 오래됐고, 열린 PR 도(head·본문) 카드도(런 키·본문) 가리키지 않는 가지. 지금은 «그림자»로 수만 낸다.
8
+ * `tools.selfImplement.salvageRetention.mode` = 'shadow'(기본) | 'live'. ⛔ 이 조각은 live 여도 지우지 않는다(다음 조각).
9
+ *
10
+ * 순수 판정 + 주입된 조회. ⛔ 나이를 못 읽은 가지는 «오래됨»으로 세지 않는다(`unknownAge`).
11
+ */
12
+ import { execFileSync } from 'node:child_process';
13
+ import { branchMatchesRun, runSuffixKey } from './salvage-branches.js';
14
+
15
+ export type SalvageRetentionMode = 'shadow' | 'live';
16
+ export const SALVAGE_RETENTION_MAX_AGE_DAYS = 14;
17
+
18
+ export interface DatedSalvageRef {
19
+ readonly branch: string;
20
+ readonly committedAt: string | null;
21
+ }
22
+
23
+ export interface SalvageReferenceSources {
24
+ /** 열린 PR — head 가지 ⊕ 본문(본문에 가지 이름이 적혀 있으면 참조로 센다). */
25
+ readonly openPrs: ReadonlyArray<{ number: number; headRefName: string; body?: string | null }>;
26
+ /** 과제 카드 — 런 id(가지의 `-r<6hex>` 와 맞추기) ⊕ 카드 직렬화 본문. */
27
+ readonly cards: ReadonlyArray<{ id: string; runId?: string; text: string }>;
28
+ }
29
+
30
+ export interface SalvageRetentionReport {
31
+ readonly mode: SalvageRetentionMode;
32
+ readonly maxAgeDays: number;
33
+ readonly total: number;
34
+ readonly older: number;
35
+ readonly unknownAge: number;
36
+ readonly referenced: number;
37
+ /** 지울 후보 — 오래됐고 참조 0. 그림자에서는 수만 보고한다. */
38
+ readonly candidates: number;
39
+ readonly sample: readonly string[];
40
+ readonly deleted: 0;
41
+ }
42
+
43
+ /** 가지를 가리키는 것이 있나 — 열린 PR head/본문 · 카드 런 키/본문. */
44
+ export function salvageBranchReferenced(branch: string, sources: SalvageReferenceSources): boolean {
45
+ if (sources.openPrs.some((pr) => pr.headRefName === branch || (pr.body ?? '').includes(branch))) return true;
46
+ return sources.cards.some((card) => {
47
+ if (card.text.includes(branch)) return true;
48
+ const key = card.runId ? runSuffixKey(card.runId) : null;
49
+ return key !== null && branchMatchesRun(branch, key);
50
+ });
51
+ }
52
+
53
+ export function classifySalvageRetention(input: {
54
+ refs: readonly DatedSalvageRef[];
55
+ sources: SalvageReferenceSources;
56
+ now: Date;
57
+ mode: SalvageRetentionMode;
58
+ maxAgeDays?: number;
59
+ }): SalvageRetentionReport {
60
+ const maxAgeDays = input.maxAgeDays ?? SALVAGE_RETENTION_MAX_AGE_DAYS;
61
+ const cutoff = input.now.getTime() - maxAgeDays * 86_400_000;
62
+ let older = 0;
63
+ let unknownAge = 0;
64
+ let referenced = 0;
65
+ const candidates: string[] = [];
66
+ for (const ref of input.refs) {
67
+ const at = ref.committedAt ? Date.parse(ref.committedAt) : NaN;
68
+ if (!Number.isFinite(at)) { unknownAge += 1; continue; }
69
+ if (at >= cutoff) continue;
70
+ older += 1;
71
+ if (salvageBranchReferenced(ref.branch, input.sources)) { referenced += 1; continue; }
72
+ candidates.push(ref.branch);
73
+ }
74
+ return { mode: input.mode, maxAgeDays, total: input.refs.length, older, unknownAge, referenced, candidates: candidates.length, sample: candidates.slice(0, 5), deleted: 0 };
75
+ }
76
+
77
+ export function formatSalvageRetention(report: SalvageRetentionReport): string {
78
+ return `수확 가지 보존(${report.mode}): 전체 ${report.total} · ${report.maxAgeDays}일 초과 ${report.older} · 그중 참조 있음 ${report.referenced} · 지울 후보(참조 0) ${report.candidates}`
79
+ + `${report.unknownAge ? ` · 나이 모름 ${report.unknownAge}` : ''} · 지운 것 0${report.mode === 'live' ? '(live 삭제는 다음 조각)' : ''}`;
80
+ }
81
+
82
+ type GhExecute = (args: string[]) => string;
83
+ const executeGh: GhExecute = (args) => execFileSync('gh', args, {
84
+ encoding: 'utf8', timeout: 60_000, maxBuffer: 64 * 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'], env: process.env,
85
+ });
86
+
87
+ const REFS_QUERY = `query($owner:String!,$name:String!,$after:String){repository(owner:$owner,name:$name){refs(refPrefix:"refs/heads/salvage/",first:100,after:$after){pageInfo{hasNextPage endCursor} nodes{name target{... on Commit{committedDate}}}}}}`;
88
+
89
+ /** GitHub GraphQL 로 `salvage/*` 가지 ⊕ 마지막 커밋 시각(100개씩). ⛔ 페이지가 깨지면 던진다(부분 목록을 «전체»로 읽지 않는다). */
90
+ export function listDatedSalvageRefs(repository: string, execute: GhExecute = executeGh): DatedSalvageRef[] {
91
+ const [owner, name] = repository.split('/');
92
+ if (!owner || !name) throw new Error(`repository must be owner/name: ${repository}`);
93
+ const refs: DatedSalvageRef[] = [];
94
+ let after: string | null = null;
95
+ for (let page = 0; page < 200; page++) {
96
+ const args = ['api', 'graphql', '-f', `query=${REFS_QUERY}`, '-F', `owner=${owner}`, '-F', `name=${name}`, ...(after ? ['-F', `after=${after}`] : [])];
97
+ const parsed = JSON.parse(execute(args)) as { data?: { repository?: { refs?: { pageInfo?: { hasNextPage?: boolean; endCursor?: string | null }; nodes?: Array<{ name?: unknown; target?: { committedDate?: unknown } | null }> } } } };
98
+ const block = parsed.data?.repository?.refs;
99
+ if (!block || !Array.isArray(block.nodes) || !block.pageInfo) throw new Error('incomplete salvage ref listing');
100
+ for (const node of block.nodes) {
101
+ if (typeof node.name !== 'string') throw new Error('incomplete salvage ref listing');
102
+ refs.push({ branch: `salvage/${node.name}`, committedAt: typeof node.target?.committedDate === 'string' ? node.target.committedDate : null });
103
+ }
104
+ if (!block.pageInfo.hasNextPage) return refs;
105
+ after = block.pageInfo.endCursor ?? null;
106
+ if (!after) throw new Error('incomplete salvage ref listing');
107
+ }
108
+ throw new Error('salvage ref listing exceeded pagination limit');
109
+ }
110
+
111
+ const OPEN_PR_LIMIT = 5000;
112
+
113
+ /** 열린 PR(head ⊕ 본문). ⛔ 상한에 닿으면 «전부 읽었다»를 증명 못 하므로 던진다(참조 있는 가지를 후보로 세지 않는다). */
114
+ export function listOpenPrRefs(repository: string, execute: GhExecute = executeGh): SalvageReferenceSources['openPrs'] {
115
+ const rows = JSON.parse(execute(['pr', 'list', '--repo', repository, '--state', 'open', '--limit', String(OPEN_PR_LIMIT), '--json', 'number,headRefName,body'])) as unknown;
116
+ if (!Array.isArray(rows)) throw new Error('incomplete open PR listing');
117
+ if (rows.length >= OPEN_PR_LIMIT) throw new Error(`incomplete open PR listing: reached the ${OPEN_PR_LIMIT} limit`);
118
+ return rows.map((row) => {
119
+ const pr = row as { number?: unknown; headRefName?: unknown; body?: unknown };
120
+ if (typeof pr.number !== 'number' || typeof pr.headRefName !== 'string') throw new Error('incomplete open PR listing');
121
+ return { number: pr.number, headRefName: pr.headRefName, body: typeof pr.body === 'string' ? pr.body : null };
122
+ });
123
+ }
@@ -972,6 +972,8 @@ export interface DefaultSeamsOptions {
972
972
  signal?: AbortSignal;
973
973
  /** Parent-surface progress receiver supplied by the dev pipeline. */
974
974
  onProgress?: SelfImplementSeams['onProgress'];
975
+ /** Relay the actual worktree soft-stop target to the invoking surface. */
976
+ onSoftStopSpaceReady?: SelfImplementSeams['onSoftStopSpaceReady'];
975
977
  /** ⑤ HITL — PR 승인 게이트. ★fail-closed: 생략 시 PR 안 열림(자동 승인 없음). 실전 필수. */
976
978
  approvePr?: SelfImplementSeams['approvePr'];
977
979
  /** gate 스텝(기본 ['test']). */
@@ -1394,6 +1396,7 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
1394
1396
  };
1395
1397
  return {
1396
1398
  ...(o.onProgress ? { onProgress: o.onProgress } : {}),
1399
+ ...(o.onSoftStopSpaceReady ? { onSoftStopSpaceReady: o.onSoftStopSpaceReady } : {}),
1397
1400
  ...(escalateGoalClarifications ? { escalateGoalClarifications } : {}),
1398
1401
  queryChildProviderErrors,
1399
1402
  persistPrBodyArtifact: (input) => createArtifactStore().put('block', input.body, {
@@ -1423,6 +1426,10 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
1423
1426
  const { supersedeMergedGoalDrafts } = await import('../harness/harness-cli-command.js');
1424
1427
  await supersedeMergedGoalDrafts(number, cwd);
1425
1428
  },
1429
+ resyncSiblingsOnMerge: async (number, cwd) => {
1430
+ const { resyncMergedSiblings } = await import('../harness/harness-cli-command.js');
1431
+ await resyncMergedSiblings(number, cwd);
1432
+ },
1426
1433
  followUp: {
1427
1434
  mode: readFollowUpMode(),
1428
1435
  stateRoot: elanousStateRoot(),
@@ -111,6 +111,8 @@ export function singleRunAsJobResult(feature: string, r: SelfImplementResultWith
111
111
  ...(r.harvestable === true && r.branch ? { harvestable: true } : {}),
112
112
  ...(r.worktreePath ? { worktreePath: r.worktreePath } : {}),
113
113
  ...(r.prUrl ? { prUrl: r.prUrl } : {}),
114
+ // TA-LIVE-REVIEW-POD — 작업 트리 없는 런(Pod)의 live review 가 «런이 낸 PR 머리»를 확인하는 근거.
115
+ ...(r.checkedHeadCommit !== undefined ? { checkedHeadCommit: r.checkedHeadCommit } : {}),
114
116
  ...(r.merged !== undefined ? { merged: r.merged } : {}),
115
117
  ...(r.mergeReason ? { mergeReason: r.mergeReason } : {}),
116
118
  ...(r.completionDisposition ? { completionDisposition: r.completionDisposition } : {}),
@@ -55,6 +55,13 @@ interface SelfImplementRuntimeDeps {
55
55
  }
56
56
  let runtimeDeps: SelfImplementRuntimeDeps = {};
57
57
 
58
+ // TUI-only observer. The tool call id is furnished by the actual dispatcher;
59
+ // other surfaces and calls without an id cannot register a stop target.
60
+ let softStopSpaceObserver: ((toolCallId: string, spaceId: string) => void) | null = null;
61
+ export function setSelfImplementSoftStopSpaceObserver(observer: typeof softStopSpaceObserver): void {
62
+ softStopSpaceObserver = observer;
63
+ }
64
+
58
65
  /** 자식 헤드리스 elanous 의 config/state 격리 + 구현 대기 상한을 주입. */
59
66
  export function setSelfImplementRuntimeDeps(d: SelfImplementRuntimeDeps | null): void {
60
67
  runtimeDeps = d ?? {};
@@ -711,6 +718,11 @@ export const selfImplementRuntime: ToolRuntime<SelfImplementRuntimeRequest, Self
711
718
  ...(runtimeDeps.implementMaxWaitSec ? { implementMaxWaitSec: runtimeDeps.implementMaxWaitSec } : {}),
712
719
  ...(approvePr ? { approvePr } : {}),
713
720
  onProgress: resolveHarnessProgressSink(ctx),
721
+ ...(ctx.surface === 'tui' && ctx.toolCallId && softStopSpaceObserver ? {
722
+ onSoftStopSpaceReady: (spaceId: string) => {
723
+ try { softStopSpaceObserver?.(ctx.toolCallId!, spaceId); } catch { /* UI observer cannot break harness */ }
724
+ },
725
+ } : {}),
714
726
  });
715
727
 
716
728
  const runnableDocumentReferences = documentReferences.filter(({ result }) => result.kind !== 'outside-repository');
@@ -0,0 +1,223 @@
1
+ /**
2
+ * SIBLING-RESYNC-ON-MERGE (0.2.21 P0) — when one harness PR merges, the open sibling PRs that touch the
3
+ * same paths learn it «now», not when their gate fails or main-sync breaks.
4
+ *
5
+ * Measured 2026-10-07: 76% of launches overlap an open PR or a running run on the same paths.
6
+ *
7
+ * Delivery reuses existing channels only:
8
+ * · live owning run → one supervisor memo in its control inbox (`elanous self send --memo` path)
9
+ * · no live run → the registered `elanous:needs-rebase` label (QUIET-PR-COMMENTS: never a comment)
10
+ *
11
+ * Cost bound (#24893 review should-fix ①, measured 186 open PRs → 186 `…/files` calls per merge):
12
+ * · prefilter — a PR already carrying the marker is skipped before any file fetch
13
+ * · priority — PRs whose branch shares path tokens with the merged files are fetched first
14
+ * · fetch cap — at most `maxFetch` file inventories per merge (config `harness.siblingResync.maxFetch`, default 40)
15
+ * · concurrency — at most `fetchConcurrency` (default 6) fetches in flight
16
+ *
17
+ * Pure core: every side effect is an injected adapter so tests never touch GitHub or the inbox.
18
+ */
19
+ import { debug } from '../debug/log.js';
20
+ import type { ControlMemoPayload } from '../harness/control-inbox.js';
21
+
22
+ export const SIBLING_RESYNC_CATEGORY = 'self-implement.sibling-resync';
23
+ export const SIBLING_RESYNC_DEFAULT_CAP = 10;
24
+ export const SIBLING_RESYNC_DEFAULT_MAX_FETCH = 40;
25
+ export const SIBLING_RESYNC_DEFAULT_FETCH_CONCURRENCY = 6;
26
+ export const SIBLING_RESYNC_MARKER_LABEL = 'elanous:needs-rebase';
27
+ const HARNESS_BRANCH_PREFIX = 'self-impl/';
28
+
29
+ /** An open PR before its file inventory is fetched. */
30
+ export interface SiblingResyncCandidate {
31
+ readonly number: number;
32
+ readonly branch: string;
33
+ readonly labels: readonly string[];
34
+ }
35
+
36
+ export interface SiblingResyncPr extends SiblingResyncCandidate {
37
+ /** `undefined` means the file list could not be proven complete — such a PR never qualifies. */
38
+ readonly files: readonly string[] | undefined;
39
+ }
40
+
41
+ export type SiblingRunTarget =
42
+ | { readonly alive: true; readonly runId: string; readonly spaceId: string }
43
+ | { readonly alive: false; readonly runId?: string; readonly reason: string };
44
+
45
+ export interface SiblingResyncAdapters {
46
+ /** Open PRs (draft or ready) against the default branch, without files. A rejection is not an empty list. */
47
+ listOpenPrs(): Promise<readonly SiblingResyncCandidate[]>;
48
+ /** One PR's files; `undefined` (or a rejection) means unknown and that PR never qualifies. */
49
+ prFiles(number: number): Promise<readonly string[] | undefined>;
50
+ /** The merged PR's files; `undefined` means unknown and nothing is sent. */
51
+ mergedFiles(): Promise<readonly string[] | undefined>;
52
+ resolveRun(pr: SiblingResyncPr): Promise<SiblingRunTarget>;
53
+ sendResync(target: { runId: string; spaceId: string }, memo: ControlMemoPayload): Promise<void> | void;
54
+ addMarker(prNumber: number, label: string): Promise<void> | void;
55
+ }
56
+
57
+ export interface SiblingResyncResult {
58
+ readonly requested: readonly number[];
59
+ readonly marked: readonly number[];
60
+ readonly failed: readonly number[];
61
+ readonly skippedOverCap: readonly number[];
62
+ /** Already marked before any fetch — not fetched. */
63
+ readonly skippedPrefilter: readonly number[];
64
+ /** Over the per-merge fetch cap — not fetched. */
65
+ readonly skippedFetchCap: readonly number[];
66
+ readonly filesFetched: number;
67
+ readonly durationMs: number;
68
+ readonly reason?: string;
69
+ }
70
+
71
+ export function siblingResyncMemo(merged: number, overlap: readonly string[]): ControlMemoPayload {
72
+ const shown = overlap.slice(0, 5).join(', ');
73
+ const more = overlap.length > 5 ? ` (+${overlap.length - 5} more)` : '';
74
+ return {
75
+ version: 1,
76
+ kind: 'sibling-resync',
77
+ urgency: 'normal',
78
+ body: `[sibling-resync] Sibling PR #${merged} just merged into main touching the same paths: ${shown}${more}. `
79
+ + 'Before continuing: git fetch origin main, rebase this branch onto origin/main (resolve conflicts keeping both intents), then re-run the gate on your changed files.',
80
+ };
81
+ }
82
+
83
+ /** Exact path intersection; a PR without a provable file list is never a sibling. */
84
+ export function overlappingFiles(merged: readonly string[], sibling: readonly string[] | undefined): string[] {
85
+ if (!sibling || sibling.length === 0) return [];
86
+ const mergedSet = new Set(merged);
87
+ return [...new Set(sibling.filter((file) => mergedSet.has(file)))].sort();
88
+ }
89
+
90
+ const GENERIC_TOKENS = new Set(['src', 'test', 'tests', 'index', 'scripts', 'docs', 'goals', 'goal', 'self', 'impl', 'apps', 'main', 'json', 'html', 'spec', 'types', 'lib']);
91
+
92
+ /** Lower-case path tokens (dir segments and file stems split on `-_.`), generic and short ones removed. */
93
+ export function pathTokens(text: string): Set<string> {
94
+ return new Set(text.toLowerCase().split(/[^a-z0-9]+/).filter((token) => token.length >= 3 && !GENERIC_TOKENS.has(token) && !/^\d+$/.test(token)));
95
+ }
96
+
97
+ /** Shared path tokens between the merged files and a branch name — a cheap ordering hint, never a filter. */
98
+ export function branchAffinity(mergedTokens: ReadonlySet<string>, branch: string): number {
99
+ let score = 0;
100
+ for (const token of pathTokens(branch)) if (mergedTokens.has(token)) score += 1;
101
+ return score;
102
+ }
103
+
104
+ async function mapBounded<T, R>(items: readonly T[], limit: number, work: (item: T) => Promise<R>): Promise<R[]> {
105
+ const results = new Array<R>(items.length);
106
+ let next = 0;
107
+ const workers = Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, async () => {
108
+ while (next < items.length) {
109
+ const index = next++;
110
+ results[index] = await work(items[index]!);
111
+ }
112
+ });
113
+ await Promise.all(workers);
114
+ return results;
115
+ }
116
+
117
+ const nonNegativeInt = (value: number | undefined, fallback: number): number =>
118
+ value !== undefined && Number.isSafeInteger(value) && value >= 0 ? value : fallback;
119
+
120
+ export async function requestSiblingResync(input: {
121
+ merged: { number: number };
122
+ adapters: SiblingResyncAdapters;
123
+ cap?: number;
124
+ maxFetch?: number;
125
+ fetchConcurrency?: number;
126
+ log?: (category: string, event: string, data: Record<string, unknown>) => void;
127
+ now?: () => number;
128
+ }): Promise<SiblingResyncResult> {
129
+ const { merged, adapters } = input;
130
+ const now = input.now ?? Date.now;
131
+ const startedAt = now();
132
+ const cap = nonNegativeInt(input.cap, SIBLING_RESYNC_DEFAULT_CAP);
133
+ const maxFetch = nonNegativeInt(input.maxFetch, SIBLING_RESYNC_DEFAULT_MAX_FETCH);
134
+ const concurrency = Math.max(1, nonNegativeInt(input.fetchConcurrency, SIBLING_RESYNC_DEFAULT_FETCH_CONCURRENCY));
135
+ const log = input.log ?? ((category, event, data) => debug.log(category, event, data));
136
+ const emit = (event: string, data: Record<string, unknown>) => {
137
+ try { log(SIBLING_RESYNC_CATEGORY, event, { merged: merged.number, ...data }); } catch { /* fail-soft */ }
138
+ };
139
+ const requested: number[] = [];
140
+ const marked: number[] = [];
141
+ const failed: number[] = [];
142
+ const skippedOverCap: number[] = [];
143
+ const skippedPrefilter: number[] = [];
144
+ const skippedFetchCap: number[] = [];
145
+ let filesFetched = 0;
146
+ const result = (reason?: string): SiblingResyncResult => ({
147
+ requested, marked, failed, skippedOverCap, skippedPrefilter, skippedFetchCap, filesFetched,
148
+ durationMs: Math.max(0, now() - startedAt), ...(reason ? { reason } : {}),
149
+ });
150
+ let mergedFiles: readonly string[] | undefined;
151
+ let open: readonly SiblingResyncCandidate[];
152
+ try {
153
+ mergedFiles = await adapters.mergedFiles();
154
+ if (!mergedFiles || mergedFiles.length === 0) {
155
+ const out = result('merged-files-unknown');
156
+ emit('skipped', { reason: out.reason, durationMs: out.durationMs });
157
+ return out;
158
+ }
159
+ open = await adapters.listOpenPrs();
160
+ } catch (error) {
161
+ const out = result('inventory-failed');
162
+ emit('skipped', { reason: out.reason, error: String(error), durationMs: out.durationMs });
163
+ return out;
164
+ }
165
+ const mergedTokens = new Set<string>();
166
+ for (const file of mergedFiles) for (const token of pathTokens(file)) mergedTokens.add(token);
167
+ const candidates: { pr: SiblingResyncCandidate; affinity: number }[] = [];
168
+ for (const pr of open) {
169
+ if (pr.number === merged.number || !pr.branch.startsWith(HARNESS_BRANCH_PREFIX)) continue;
170
+ // Already marked: the quiet signal is in place, and a re-fetch would only re-confirm it.
171
+ if (pr.labels.includes(SIBLING_RESYNC_MARKER_LABEL)) { skippedPrefilter.push(pr.number); continue; }
172
+ candidates.push({ pr, affinity: branchAffinity(mergedTokens, pr.branch) });
173
+ }
174
+ // Most likely siblings first; ties go to the newest PR (most likely to have a live run).
175
+ candidates.sort((a, b) => b.affinity - a.affinity || b.pr.number - a.pr.number);
176
+ const toFetch = candidates.slice(0, maxFetch);
177
+ skippedFetchCap.push(...candidates.slice(maxFetch).map(({ pr }) => pr.number));
178
+ const fetched = await mapBounded(toFetch, concurrency, async ({ pr }): Promise<SiblingResyncPr> => {
179
+ let files: readonly string[] | undefined;
180
+ try { files = await adapters.prFiles(pr.number); } catch { files = undefined; }
181
+ filesFetched += 1;
182
+ return { ...pr, files };
183
+ });
184
+ const siblings = fetched
185
+ .map((pr) => ({ pr, overlap: overlappingFiles(mergedFiles!, pr.files) }))
186
+ .filter(({ overlap }) => overlap.length > 0)
187
+ .sort((a, b) => a.pr.number - b.pr.number);
188
+ for (const [index, { pr, overlap }] of siblings.entries()) {
189
+ if (index >= cap) { skippedOverCap.push(pr.number); continue; }
190
+ let target: SiblingRunTarget;
191
+ try {
192
+ target = await adapters.resolveRun(pr);
193
+ } catch (error) {
194
+ target = { alive: false, reason: `run-resolution-failed: ${String(error)}` };
195
+ }
196
+ if (target.alive) {
197
+ try {
198
+ await adapters.sendResync({ runId: target.runId, spaceId: target.spaceId }, siblingResyncMemo(merged.number, overlap));
199
+ requested.push(pr.number);
200
+ emit('requested', { sibling: pr.number, runId: target.runId, spaceId: target.spaceId, overlap });
201
+ continue;
202
+ } catch (error) {
203
+ // A live run we could not reach still needs to be visible — fall through to the quiet marker.
204
+ emit('send-failed', { sibling: pr.number, runId: target.runId, error: String(error) });
205
+ }
206
+ }
207
+ try {
208
+ await adapters.addMarker(pr.number, SIBLING_RESYNC_MARKER_LABEL);
209
+ marked.push(pr.number);
210
+ emit('marked', { sibling: pr.number, label: SIBLING_RESYNC_MARKER_LABEL, reason: target.alive ? 'send-failed' : target.reason, ...(target.runId ? { runId: target.runId } : {}), overlap });
211
+ } catch (error) {
212
+ failed.push(pr.number);
213
+ emit('mark-failed', { sibling: pr.number, error: String(error) });
214
+ }
215
+ }
216
+ const out = result();
217
+ emit('summary', {
218
+ siblings: siblings.length, requested, marked, failed, skippedOverCap, cap,
219
+ filesFetched, skippedPrefilter: skippedPrefilter.length, skippedFetchCap: skippedFetchCap.length, maxFetch, concurrency,
220
+ durationMs: out.durationMs,
221
+ });
222
+ return out;
223
+ }