elanous 0.2.20 → 0.2.22

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 (456) 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/aBi6EyMtLsrl7J8eH4kF7/_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/1307-08b716476f1d50af.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +122 -0
  11. package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +149 -0
  12. package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +79 -0
  13. package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +24 -0
  15. package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +27 -0
  19. package/apps/pwa/out/_next/static/chunks/1948.5f35b39eef222a94.js +5 -0
  20. package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +36 -0
  25. package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +286 -0
  26. package/apps/pwa/out/_next/static/chunks/{2489-ff55a35b202c0f94.js → 2489-d238d3e2eb5b2bf5.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
  34. package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
  38. package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
  41. package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
  45. package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
  47. package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
  48. package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
  50. package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
  55. package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
  60. package/apps/pwa/out/_next/static/chunks/{6158.af2e93b7adff709b.js → 6158.566c890adc06a459.js} +1 -1
  61. package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
  62. package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
  64. package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
  65. package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
  67. package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
  74. package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
  75. package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
  76. package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
  82. package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
  83. package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
  85. package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
  87. package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
  89. package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
  90. package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-c855c24de7db74fe.js → page-88459e0903da2803.js} +1 -1
  93. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-85b316bb4235980b.js → page-d85c43f46f58716b.js} +1 -1
  94. package/apps/pwa/out/_next/static/chunks/app/board/{page-9b2b26a33cb2cd42.js → page-4e645600579ae952.js} +1 -1
  95. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c9955fde67b1bcb5.js → page-87edbfd19117823b.js} +1 -1
  96. package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/app/chat/{page-4ba4abe096b6c2a8.js → page-cbaf66b1e1804adf.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/app/consult/{page-575d4a8b4f7becb2.js → page-80ed511e02c81412.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/app/control/{page-f5d56ff5f45ad164.js → page-9f5610caaa06178c.js} +1 -1
  101. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-2260e9eef0357421.js → page-ec8f3575e339de74.js} +1 -1
  102. package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/app/exec/{page-466bf0ee16ad1432.js → page-2a19c5cd3b562580.js} +1 -1
  105. package/apps/pwa/out/_next/static/chunks/app/field/{page-a23bc71713bcc08f.js → page-b6a09cde2e07521a.js} +1 -1
  106. package/apps/pwa/out/_next/static/chunks/app/inside/page-f7eae1518ca0c353.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/app/layout-8462d6a543cbb2d9.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/app/loops/{page-35bfec2e631d3eae.js → page-abd86cd832aaa011.js} +1 -1
  112. package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-f9ccb1b1adbea14d.js → page-8c3f168fc3f91939.js} +1 -1
  114. package/apps/pwa/out/_next/static/chunks/app/morning/{page-ec102c9b8b691e37.js → page-786fb4504d0f2fa9.js} +1 -1
  115. package/apps/pwa/out/_next/static/chunks/app/not-found-75209f14597dcb8f.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/app/observatory/page-bf30affe3a85af7e.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/app/page-76cf838050a8d681.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-db03ae3589a81c9e.js → page-7ec8e289b4c14df6.js} +1 -1
  123. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-4a6463b97f0ad94f.js → page-8c8ac17d52be2a79.js} +1 -1
  124. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-75bffecc9aa1004e.js → page-d359ab048d63a9d9.js} +1 -1
  125. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-b8b345c880bf78d1.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/settings/{page-31d5b875760bb62d.js → page-f151cc10ad026940.js} +1 -1
  127. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/share/{page-46d5201d2207a511.js → page-bf495a1e284f4e00.js} +1 -1
  130. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/term/{page-cf0a4b4abead71ea.js → page-9abcfac07e02a542.js} +1 -1
  133. package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0acc8e198f7b0dfb.js → page-69cb93d1a2e57e0e.js} +1 -1
  138. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-34882c84f84021c5.js → page-72ec543928d67c3e.js} +1 -1
  139. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
  141. package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
  143. package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
  144. package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +1 -0
  145. package/apps/pwa/out/approvals/index.html +1 -1
  146. package/apps/pwa/out/approvals/index.txt +14 -14
  147. package/apps/pwa/out/autopilot/index.html +1 -1
  148. package/apps/pwa/out/autopilot/index.txt +14 -14
  149. package/apps/pwa/out/board/index.html +1 -1
  150. package/apps/pwa/out/board/index.txt +14 -14
  151. package/apps/pwa/out/botlab/index.html +1 -1
  152. package/apps/pwa/out/botlab/index.txt +14 -14
  153. package/apps/pwa/out/bots/index.html +1 -1
  154. package/apps/pwa/out/bots/index.txt +14 -14
  155. package/apps/pwa/out/ceo/index.html +1 -1
  156. package/apps/pwa/out/ceo/index.txt +14 -14
  157. package/apps/pwa/out/chat/index.html +1 -1
  158. package/apps/pwa/out/chat/index.txt +14 -14
  159. package/apps/pwa/out/consult/index.html +1 -1
  160. package/apps/pwa/out/consult/index.txt +14 -14
  161. package/apps/pwa/out/control/index.html +1 -1
  162. package/apps/pwa/out/control/index.txt +14 -14
  163. package/apps/pwa/out/decisions/index.html +1 -1
  164. package/apps/pwa/out/decisions/index.txt +14 -14
  165. package/apps/pwa/out/design-check/index.html +1 -1
  166. package/apps/pwa/out/design-check/index.txt +14 -14
  167. package/apps/pwa/out/editor/index.html +1 -1
  168. package/apps/pwa/out/editor/index.txt +15 -15
  169. package/apps/pwa/out/exec/index.html +1 -1
  170. package/apps/pwa/out/exec/index.txt +14 -14
  171. package/apps/pwa/out/field/index.html +1 -1
  172. package/apps/pwa/out/field/index.txt +14 -14
  173. package/apps/pwa/out/healthz +1 -1
  174. package/apps/pwa/out/index.html +1 -1
  175. package/apps/pwa/out/index.txt +14 -14
  176. package/apps/pwa/out/inside/index.html +1 -1
  177. package/apps/pwa/out/inside/index.txt +15 -15
  178. package/apps/pwa/out/intake/index.html +1 -1
  179. package/apps/pwa/out/intake/index.txt +14 -14
  180. package/apps/pwa/out/live/index.html +1 -1
  181. package/apps/pwa/out/live/index.txt +14 -14
  182. package/apps/pwa/out/live-run/index.html +1 -0
  183. package/apps/pwa/out/live-run/index.txt +35 -0
  184. package/apps/pwa/out/loops/index.html +1 -1
  185. package/apps/pwa/out/loops/index.txt +14 -14
  186. package/apps/pwa/out/market/index.html +1 -1
  187. package/apps/pwa/out/market/index.txt +14 -14
  188. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  189. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  190. package/apps/pwa/out/morning/index.html +1 -1
  191. package/apps/pwa/out/morning/index.txt +14 -14
  192. package/apps/pwa/out/observatory/index.html +1 -1
  193. package/apps/pwa/out/observatory/index.txt +14 -14
  194. package/apps/pwa/out/ops/checklist/index.html +1 -1
  195. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  196. package/apps/pwa/out/ops/release/index.html +1 -1
  197. package/apps/pwa/out/ops/release/index.txt +14 -14
  198. package/apps/pwa/out/ops/seats/index.html +1 -1
  199. package/apps/pwa/out/ops/seats/index.txt +14 -14
  200. package/apps/pwa/out/outputs/index.html +1 -1
  201. package/apps/pwa/out/outputs/index.txt +14 -14
  202. package/apps/pwa/out/reflection/index.html +1 -1
  203. package/apps/pwa/out/reflection/index.txt +15 -15
  204. package/apps/pwa/out/scheduler/index.html +1 -1
  205. package/apps/pwa/out/scheduler/index.txt +14 -14
  206. package/apps/pwa/out/sessions/index.html +1 -1
  207. package/apps/pwa/out/sessions/index.txt +14 -14
  208. package/apps/pwa/out/settings/devices/index.html +1 -1
  209. package/apps/pwa/out/settings/devices/index.txt +14 -14
  210. package/apps/pwa/out/settings/index.html +1 -1
  211. package/apps/pwa/out/settings/index.txt +14 -14
  212. package/apps/pwa/out/setup/done/index.html +1 -1
  213. package/apps/pwa/out/setup/done/index.txt +14 -14
  214. package/apps/pwa/out/setup/index.html +1 -1
  215. package/apps/pwa/out/setup/index.txt +14 -14
  216. package/apps/pwa/out/share/index.html +1 -1
  217. package/apps/pwa/out/share/index.txt +14 -14
  218. package/apps/pwa/out/showroom/index.html +1 -1
  219. package/apps/pwa/out/showroom/index.txt +14 -14
  220. package/apps/pwa/out/tasks/index.html +1 -1
  221. package/apps/pwa/out/tasks/index.txt +14 -14
  222. package/apps/pwa/out/term/index.html +1 -1
  223. package/apps/pwa/out/term/index.txt +14 -14
  224. package/apps/pwa/out/today/index.html +1 -1
  225. package/apps/pwa/out/today/index.txt +14 -14
  226. package/apps/pwa/out/trace/index.html +1 -1
  227. package/apps/pwa/out/trace/index.txt +14 -14
  228. package/apps/pwa/out/vault/index.html +1 -1
  229. package/apps/pwa/out/vault/index.txt +14 -14
  230. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  231. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  232. package/apps/pwa/out/workflows/index.html +1 -1
  233. package/apps/pwa/out/workflows/index.txt +14 -14
  234. package/apps/pwa/out/workspace/index.html +1 -1
  235. package/apps/pwa/out/workspace/index.txt +14 -14
  236. package/apps/pwa/out/worktrees/index.html +1 -1
  237. package/apps/pwa/out/worktrees/index.txt +14 -14
  238. package/graphs/catalog/catalog.yaml +1 -1
  239. package/graphs/drafts/recipes.yaml +6 -6
  240. package/graphs/implement-loop.yaml +277 -5
  241. package/graphs/test-diet/recipes.yaml +3 -0
  242. package/graphs/test-diet/test-diet.yaml +3 -1
  243. package/package.json +1 -1
  244. package/scripts/coord-channel-watch.sh +31 -4
  245. package/scripts/docs-lint.ts +4 -1
  246. package/scripts/f-session-monitor.ts +69 -0
  247. package/scripts/hq/standby-snapshot.ts +7 -3
  248. package/scripts/knowledge-compress.ts +11 -0
  249. package/scripts/marketing/generate-ax-brief.ts +18 -1
  250. package/scripts/measure-nl-routing-corpus.ts +11 -3
  251. package/scripts/nightly-docops.ts +90 -3
  252. package/scripts/release-loop/gate-node.ts +5 -5
  253. package/scripts/release-loop/shard-plan.ts +50 -5
  254. package/scripts/research-coo-admin-dates.ts +39 -0
  255. package/scripts/rhythm/daily.ts +2 -1
  256. package/scripts/skills/public-check.ts +7 -2
  257. package/scripts/smart-merge/resync-shadow.ts +5 -3
  258. package/scripts/test-diet/lib.ts +81 -1
  259. package/scripts/test-diet/node.ts +48 -2
  260. package/scripts/verify-coo-admin-evidence.ts +83 -0
  261. package/src/acp/client.ts +4 -2
  262. package/src/agent/run-context.ts +4 -0
  263. package/src/agent-env/profile.ts +25 -5
  264. package/src/agent-mission/driver.ts +26 -12
  265. package/src/agent-mission/supervisor-metrics.ts +17 -2
  266. package/src/agent-substrate/acp-reviewer.ts +12 -5
  267. package/src/agent-substrate/pr-reviewer.ts +11 -4
  268. package/src/agent-substrate/review-intent.ts +102 -3
  269. package/src/autopilot/build/llm-conflict-merge.ts +22 -8
  270. package/src/autopilot/pr-manager.ts +18 -8
  271. package/src/away/release-watch.ts +47 -8
  272. package/src/briefing/brief-items.ts +104 -11
  273. package/src/browser-cdp/client.ts +10 -5
  274. package/src/chat/index.ts +1 -0
  275. package/src/cli/brief-cli.ts +20 -2
  276. package/src/cli/config-classify.ts +83 -0
  277. package/src/cli/control-cli.ts +14 -0
  278. package/src/cli/decisions-cli.ts +26 -16
  279. package/src/cli/logs-model-input.ts +1 -1
  280. package/src/cli/ops-cli.ts +11 -0
  281. package/src/cli/ops-overview-cli.ts +50 -0
  282. package/src/cli/pod-cli.ts +1 -0
  283. package/src/cli/pr-cli.ts +65 -8
  284. package/src/cli/release-cli.ts +21 -4
  285. package/src/cli/tasks-cli.ts +214 -1
  286. package/src/connectors/types.ts +6 -6
  287. package/src/context-bus/context-now-surfaces.ts +18 -0
  288. package/src/context-bus/coord-events.ts +32 -4
  289. package/src/context-bus/long-term-memory.ts +23 -6
  290. package/src/context-bus/nightly-condense.ts +169 -21
  291. package/src/control/control-status.ts +87 -0
  292. package/src/control-plane/light-member.ts +5 -3
  293. package/src/control-plane/member.ts +172 -7
  294. package/src/core-turn/run-goal-loop.ts +22 -1
  295. package/src/dashboard/index.ts +40 -4
  296. package/src/dashboard/slash-runtime/dashboard-handlers.ts +9 -0
  297. package/src/decisions/decision-cards.ts +20 -1
  298. package/src/decisions/decision-ledger.ts +11 -8
  299. package/src/decisions/web-push-decision-cards.ts +19 -3
  300. package/src/discord-self-message.ts +2 -2
  301. package/src/domains/coo-admin-dates.evidence.json +876 -0
  302. package/src/domains/coo-admin-tool.ts +48 -14
  303. package/src/domains/ops-status.ts +53 -0
  304. package/src/domains/outbound-alert.ts +24 -1
  305. package/src/esc-abort-gate.ts +3 -1
  306. package/src/exec-requests/planner.ts +6 -0
  307. package/src/graph-kinds/registry.ts +6 -6
  308. package/src/graph-runner/graph-cli.ts +91 -2
  309. package/src/graph-runner/graph-run-api.ts +20 -0
  310. package/src/graph-runner/graph-tick.ts +73 -12
  311. package/src/graph-runner/runner.ts +43 -11
  312. package/src/graph-unify/wf-to-graph.ts +79 -0
  313. package/src/graph-wizard/archetypes.ts +88 -0
  314. package/src/graph-wizard/generate.ts +577 -0
  315. package/src/graph-wizard/saved-steps.ts +120 -0
  316. package/src/graph-wizard/steps.ts +206 -0
  317. package/src/harness/harness-cli-command.ts +186 -32
  318. package/src/harness/harness-queue.ts +58 -14
  319. package/src/harness/launch-advice.ts +23 -0
  320. package/src/index.ts +43 -11
  321. package/src/intake-plane/digest.ts +18 -11
  322. package/src/knowledge/conclusion-compress.ts +93 -0
  323. package/src/knowledge/know-find.ts +9 -2
  324. package/src/knowledge/query.ts +20 -0
  325. package/src/llm.ts +26 -16
  326. package/src/loops/neighbor-runtime.ts +88 -0
  327. package/src/loops/orchestrator/author-depth.ts +29 -11
  328. package/src/loops/orchestrator/overlap.ts +7 -1
  329. package/src/loops/orchestrator/resync-shadow.ts +1 -1
  330. package/src/loops/orchestrator/tick.ts +35 -2
  331. package/src/loops/orchestrator/traffic.ts +2 -0
  332. package/src/loops/registry.ts +5 -0
  333. package/src/market/publish.ts +17 -1
  334. package/src/maturity/feature-maturity.ts +4 -2
  335. package/src/mss/logging/redaction.ts +17 -0
  336. package/src/nexus/api/draft-metrics.ts +128 -0
  337. package/src/nexus/api/graph-peer-edit.ts +77 -0
  338. package/src/nexus/api/graph-wizard-api.ts +61 -0
  339. package/src/nexus/api/graphs-api.ts +193 -17
  340. package/src/nexus/api/harness-traversal.ts +205 -0
  341. package/src/nexus/api/http-server.ts +122 -6
  342. package/src/nexus/api/plugins-market.ts +24 -0
  343. package/src/nexus/index.ts +3 -2
  344. package/src/nexus/outbound/router.ts +28 -8
  345. package/src/oauth/claude-pty-maturity.ts +24 -0
  346. package/src/oauth/fallback-chain.ts +27 -14
  347. package/src/onboarding/first-run.ts +3 -0
  348. package/src/ops/overview-collect.ts +345 -0
  349. package/src/ops/overview-seat-era.ts +90 -0
  350. package/src/ops/overview.ts +223 -0
  351. package/src/plugins/maker/market-bundle.ts +9 -0
  352. package/src/plugins/maker/plugin-maker.ts +9 -3
  353. package/src/plugins/maker/wizard-generate.ts +73 -9
  354. package/src/policy/llm-policy.ts +11 -6
  355. package/src/release-loop/checklist-board.ts +27 -0
  356. package/src/release-loop/checklist.ts +38 -0
  357. package/src/release-loop/gate-shards.ts +7 -2
  358. package/src/seat-address/seat-address.ts +1 -0
  359. package/src/seat-loop/seat-loop.ts +174 -20
  360. package/src/seat-loop/seat-report.ts +29 -1
  361. package/src/self-dev/ask-launch-flow.ts +4 -2
  362. package/src/self-dev/ask-launch-io.ts +3 -1
  363. package/src/self-dev/dev-cli.ts +90 -8
  364. package/src/self-dev/dev-pipeline.ts +20 -1
  365. package/src/self-dev/draft-sweep.ts +45 -8
  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/orchestrate.ts +3 -0
  370. package/src/self-dev/release-path-guard.ts +7 -4
  371. package/src/self-dev/review-provider-fallback.ts +83 -0
  372. package/src/self-dev/run-supervisor.ts +5 -1
  373. package/src/self-implement/frozen-merges.ts +23 -0
  374. package/src/self-implement/goal-author.ts +14 -0
  375. package/src/self-implement/harness-policy-gates.ts +7 -5
  376. package/src/self-implement/heal-intake.ts +3 -0
  377. package/src/self-implement/host-regate.ts +28 -14
  378. package/src/self-implement/l8-merge-queue.ts +80 -8
  379. package/src/self-implement/orchestrator.ts +84 -28
  380. package/src/self-implement/seams.ts +8 -3
  381. package/src/self-implement/self-implement-cli.ts +4 -0
  382. package/src/self-implement/self-implement-runtime.ts +12 -0
  383. package/src/session-runtime/index.ts +5 -3
  384. package/src/shell-env-bootstrap.ts +35 -6
  385. package/src/steward/triage.ts +25 -8
  386. package/src/storage/file-lock.ts +33 -4
  387. package/src/task-agent/card-evidence.ts +6 -0
  388. package/src/task-agent/completion-evidence.ts +31 -3
  389. package/src/task-agent/cover.ts +61 -0
  390. package/src/task-agent/live-moves.ts +126 -3
  391. package/src/task-agent/parent-host.ts +315 -0
  392. package/src/task-agent/shadow.ts +19 -4
  393. package/src/task-agent/task-hand.ts +11 -2
  394. package/src/task-orchestrator/surfaces/pod-command-job.ts +41 -6
  395. package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
  396. package/src/task-orchestrator/surfaces/pod-pool.ts +138 -15
  397. package/src/task-orchestrator/surfaces/self-implement-pod.ts +39 -6
  398. package/src/task-orchestrator/surfaces/self-implement.ts +7 -0
  399. package/src/telegram-commands.ts +8 -0
  400. package/src/telegram-project-command.ts +6 -0
  401. package/src/telegram.ts +2 -2
  402. package/src/tool-runtime/browser-runtime.ts +87 -3
  403. package/src/tool-runtime/index.ts +1 -0
  404. package/src/typecheck-ratchet.ts +2 -1
  405. package/src/user-config.ts +55 -8
  406. package/src/version/packed-revision.json +1 -1
  407. package/src/workflow-runtime/executor.ts +24 -2
  408. package/src/workflow-runtime/triggers/registry.ts +27 -0
  409. package/src/workflow-runtime/triggers/schedule-source.ts +30 -3
  410. package/apps/pwa/out/_next/static/Bam1YziK2iH9bN4K0utGk/_buildManifest.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/1307-77fad80cf54477c6.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
  416. package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +0 -25
  425. package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/bots/page-7a852956c74015a3.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/design-check/page-7c058069c5e7fa63.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/intake/page-9cd565005b93aca6.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/live/page-474cb5888ea0e8f3.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/market/page-9ca21f9ad3208a61.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/not-found-a9c2cfa8dda922a5.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/app/setup/page-68793c6e10331d66.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/app/showroom/page-d81c07a7b4f618ae.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/app/tasks/page-be1c4e8697f22360.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/app/trace/page-0fd895fab9b62c1b.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/webpack-c260571513bf43c2.js +0 -1
  454. package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +0 -1
  455. /package/apps/pwa/out/_next/static/{Bam1YziK2iH9bN4K0utGk → aBi6EyMtLsrl7J8eH4kF7}/_ssgManifest.js +0 -0
  456. /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
@@ -1,13 +1,14 @@
1
1
  import { execFile } from 'node:child_process';
2
2
  import { createHash, randomUUID } from 'node:crypto';
3
- import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
3
+ import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
4
4
  import { Database } from 'bun:sqlite';
5
- import { dirname, join, resolve } from 'node:path';
5
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
6
6
  import { promisify } from 'node:util';
7
7
  import { TRACK_AGENT_FORBIDDEN_ACTION_REGEX, universeLaunch } from '../autopilot/track-agent.js';
8
8
  import { debug } from '../debug/log.js';
9
9
  import { openSurfaceEventsDb, recordEvent, surfaceEventsDbPath } from '../domains/surface-events.js';
10
10
  import { judgeNeighbor } from '../loops/neighbors.js';
11
+ import { checkLoopNeighbors, emitLoopHeartbeat, SEAT_NEIGHBORS } from '../loops/neighbor-runtime.js';
11
12
  import { addHarnessQueue, harnessQueueIdForKey, harnessQueueOutcome, listHarnessQueue } from '../harness/harness-queue.js';
12
13
  import { isRescueAllowedAction } from '../steward/rescue.js';
13
14
  import { DecisionLedger, type DecisionEntry, type DecisionOption, type Recommendation } from '../decisions/decision-ledger.js';
@@ -20,6 +21,7 @@ import { openMsgStore, type MessageEnvelope } from '../msg/msg-store.js';
20
21
  import { loadLayeredPersonaDirs, resolveRepositoryPersonaDir, resolveStatePersonaDir } from '../persona/global-registry.js';
21
22
  import { orderedPersonaTodos, personaTodoPath, readPersonaTodos, type PersonaTodo } from '../persona/persona-todo.js';
22
23
  import { PersonaRegistry } from '../persona/registry.js';
24
+ import { registeredSeats, resolveSeat } from '../seat-address/seat-address.js';
23
25
  import { recordSeatAskShadow } from '../seat-dispatch/seat-ask.js';
24
26
  import { appendSeatRequestRows, listSeatRequests, withSeatRequestLedgerLock } from '../seat-dispatch/seat-request-ledger.js';
25
27
  import { answerCrossCheck, answerSeat, askCrossCheck, askSeat, crossCheckAnswer, deliveredSeatQuestion, hasSeatAnswer, isSeatId, seatCrossChecks, seatQuestions, type CrossCheckJudgment, type SeatId } from '../seat-dispatch/seat-questions.js';
@@ -29,7 +31,7 @@ import { readSchedules, releasedVersion } from '../release-loop/feature-store.js
29
31
  import { runOpDecide, type OpDecideCandidate } from './op-decide.js';
30
32
 
31
33
  // `kind`/`createdAt` are the V3 shadow-compare keys (내부 문서 `METHOD-v3-shadow-compare-2026-10-02` · MK 10-02 18:54).
32
- export type SeatItem = { source: 'request' | 'checklist' | 'hook' | 'seat-question'; kind?: 'request' | 'cell' | 'hook-task' | 'seat-question'; id: string; title: string; text: string; evidence?: string; status?: string; version?: string; seat?: string; evidenceHash?: string; asOf?: string; queuedAt?: string; createdAt?: string; from?: SeatId };
34
+ export type SeatItem = { source: 'request' | 'checklist' | 'hook' | 'seat-question' | 'idle'; kind?: 'request' | 'cell' | 'hook-task' | 'seat-question'; id: string; title: string; text: string; evidence?: string; status?: string; version?: string; seat?: string; evidenceHash?: string; asOf?: string; queuedAt?: string; createdAt?: string; from?: SeatId; idleRung?: 1 | 2 | 3 | 4 | 5 };
33
35
  export type SeatInputs = { requests: SeatItem[]; checklist: SeatItem[]; role: string };
34
36
  // V3 ledger row: `ts` · `item.id` · `item.kind` · `item.createdAt` · `action` (door, wait or skipped-*) · `reason` (matched rule or forbidden word).
35
37
  export type SeatAction = 'decision' | 'harness' | 'wait' | 'seat-question' | 'seat-answer' | 'skipped-budget' | 'skipped-empty';
@@ -44,7 +46,7 @@ export type TcCandidate =
44
46
  | { kind: 'publication-pr-approval-wait'; pr: number; title: string; readyAt: string; approvalWaitAt: string; paths: string[] }
45
47
  | { kind: 'other-seat-cell-defect'; version: string; id: string; title: string; to: SeatId };
46
48
  export type TcPullRequest = { number: number; title: string; body?: string; state: 'OPEN' | 'MERGED' | 'CLOSED'; isDraft: boolean; createdAt: string; updatedAt?: string; mergedAt?: string | null; readyAt?: string; approvalWaitAt?: string; paths?: string[]; reviewDecision?: string };
47
- export type SeatEntry = { seat: string; ts?: string; at: string; modeDowngradeReason?: string; status: 'shadow' | 'attempting' | 'outcome-unknown' | 'queued' | 'refused' | 'held' | 'launched' | 'hitl' | 'wait' | 'asked' | 'answered' | 'awaiting-answer' | 'awaiting-xcheck' | 'awaiting-resolution' | 'resolved-by-neighbor' | 'resolved' | 'rejected-no-evidence' | 'skipped-budget' | 'skipped-empty'; item?: SeatItem; candidate?: OpCandidate | TcCandidate | OpDecideCandidate; action?: SeatAction; reason?: string; runId?: string; queueId?: string; inquiry?: SeatInquiry; answer?: string; escalated?: boolean; xcheckRequestedAt?: string; xcheckQuestionId?: number; xcheckNeighbor?: SeatId; xcheckNote?: string; xcheckJudgmentEvidence?: string; xcheckWaitMinutes?: number };
49
+ export type SeatEntry = { seat: string; ts?: string; at: string; modeDowngradeReason?: string; status: 'shadow' | 'attempting' | 'outcome-unknown' | 'queued' | 'refused' | 'held' | 'launched' | 'hitl' | 'wait' | 'asked' | 'answered' | 'awaiting-answer' | 'awaiting-xcheck' | 'awaiting-resolution' | 'resolved-by-neighbor' | 'resolved' | 'rejected-no-evidence' | 'skipped-budget' | 'skipped-empty'; item?: SeatItem; candidate?: OpCandidate | TcCandidate | OpDecideCandidate; action?: SeatAction; reason?: string; runId?: string; queueId?: string; inquiry?: SeatInquiry; answer?: string; escalated?: boolean; xcheckRequestedAt?: string; xcheckQuestionId?: number; xcheckNeighbor?: SeatId; xcheckNote?: string; xcheckJudgmentEvidence?: string; xcheckWaitMinutes?: number; emptyQueueMinutes?: number };
48
50
  export type SeatLoopResult = SeatEntry | { seat: string; status: 'skipped-off'; modeDowngradeReason?: string };
49
51
 
50
52
  export function seatLoopTickLine(result: SeatLoopResult): string {
@@ -93,6 +95,8 @@ export type SeatDeps = {
93
95
  pullRequests?: () => Promise<TcPullRequest[]>;
94
96
  ledgerFiles?: (directory: string) => string[];
95
97
  lockContended?: () => void;
98
+ /** Override the heartbeat timer in isolated long-turn tests; production uses the seat contract's interval. */
99
+ heartbeatIntervalMs?: number;
96
100
  /** Supply a question only when this seat lacks evidence for the selected work item. */
97
101
  inquire?: (seat: SeatId, item: SeatItem) => Promise<SeatInquiry | null> | SeatInquiry | null;
98
102
  /** Supply the receiving seat's answer; absent evidence must not invent a reply. */
@@ -570,8 +574,8 @@ function opDecidePass(config: SeatLoopConfig, deps: SeatDeps, now: Date, path: s
570
574
  mode,
571
575
  ledger: cardLedger(stateRoot, deps, now),
572
576
  runOwner: deps.runOwner ?? ((runId) => {
573
- owners ??= new Map((['OP', 'UX', 'MK', 'TC'] as const).flatMap((owner) =>
574
- readSeatLedger(owner, deps, now).filter((row) => row.runId).map((row) => [row.runId!, owner] as const)));
577
+ owners ??= new Map(registeredSeats().filter(({ id }) => isSeatId(id)).flatMap(({ id: owner }) =>
578
+ readSeatLedger(owner, deps, now).filter((row) => row.runId).map((row) => [row.runId!, owner as SeatId] as const)));
575
579
  return owners.get(runId);
576
580
  }),
577
581
  routeToSeat: (to, card, reason) => {
@@ -691,9 +695,11 @@ function readTcChecklist(root: string): Array<{ version: string; id: string; tit
691
695
  if (!existsSync(path)) return [];
692
696
  const db = new Database(path, { readonly: true, strict: true });
693
697
  try {
698
+ const seats = registeredSeats().filter(({ id }) => isSeatId(id)).map(({ id }) => id);
699
+ if (!seats.length) return [];
694
700
  return db.query(`SELECT a.version, f.id, COALESCE(a.title_override, f.title) AS title, a.status, a.owner
695
- FROM assignments a JOIN features f ON f.id = a.feature_id WHERE a.status IN ('yellow', 'red') AND a.owner IN ('OP', 'TC', 'MK', 'UX')
696
- ORDER BY a.version, f.id`).all() as Array<{ version: string; id: string; title: string; status: string; owner: string | null }>;
701
+ FROM assignments a JOIN features f ON f.id = a.feature_id WHERE a.status IN ('yellow', 'red') AND a.owner IN (${seats.map(() => '?').join(', ')})
702
+ ORDER BY a.version, f.id`).all(...seats) as Array<{ version: string; id: string; title: string; status: string; owner: string | null }>;
697
703
  } finally { db.close(); }
698
704
  }
699
705
 
@@ -946,13 +952,48 @@ export function planAction(item: SeatItem, seat?: string): { kind: 'decision' |
946
952
  const kind = rule?.kind ?? 'harness';
947
953
  const reason = rule?.reason;
948
954
  observe('plan', { kind, reason: reason ?? '' });
949
- return { kind, text: seat && kind === 'harness' ? seatTaskText(seat, item) : `${item.id} ${item.title}`, ...(reason ? { reason } : {}) };
955
+ const text = seat && kind === 'harness' ? seatTaskText(seat, item) : `${item.id} ${item.title}`;
956
+ // Observed once per plan (not per queue comparison): how many target paths the launched ask carries.
957
+ if (seat && kind === 'harness' && text.startsWith('대상 경로: ')) observe('target-paths', { item: item.id, count: text.split('\n', 1)[0]!.split(' · ').length });
958
+ return { kind, text, ...(reason ? { reason } : {}) };
959
+ }
960
+
961
+ /** A path counts only when it resolves to a regular file whose real location stays inside `root` (symlinks out are refused). */
962
+ export function repoFileExists(root: string = repoRoot): (path: string) => boolean {
963
+ return (path) => {
964
+ try {
965
+ const absolute = resolve(root, path);
966
+ const real = relative(realpathSync(root), realpathSync(absolute));
967
+ return !!real && real !== '..' && !real.startsWith(`..${sep}`) && !isAbsolute(real) && statSync(absolute).isFile();
968
+ } catch { return false; }
969
+ };
970
+ }
971
+
972
+ // Repository paths already written in a checklist item's title or evidence (dedup · first-seen order · at most six).
973
+ export function seatTargetPaths(item: SeatItem, isFile: (path: string) => boolean = repoFileExists()): string[] {
974
+ const paths: string[] = [];
975
+ const text = `${item.title}\n${item.evidence ?? ''}`;
976
+ const matches = text.matchAll(/(?:^|[^A-Za-z0-9_./-])((?:src|apps|scripts|test|docs|bin|graphs|packs|plugins)\/[A-Za-z0-9_./-]+)/g);
977
+ for (const match of matches) {
978
+ const path = match[1]!.replace(/[./]+$/, '');
979
+ if (path.split('/').includes('..')) continue;
980
+ const fromRoot = relative(repoRoot, resolve(repoRoot, path));
981
+ if (!fromRoot || fromRoot === '..' || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot) || !isFile(path)) continue;
982
+ if (!paths.includes(path)) paths.push(path);
983
+ if (paths.length === 6) break;
984
+ }
985
+ return paths;
950
986
  }
951
987
 
952
988
  // The launched sentence names the seat, the source and the release so same-named items stay distinct.
953
- export function seatTaskText(seat: string, item: SeatItem): string {
954
- const where = item.source === 'checklist' ? `${item.version ?? '?'} 체크리스트 칸 ${item.id}` : item.source === 'hook' ? `웹훅 작업 카드 ${item.id}` : `자리 요청 ${item.id}`;
955
- return `[${seat} 자리 · ${where} · 역할 docs/roles/${seat}.md] ${item.title.replace(/[\r\n]+/g, ' ')}`;
989
+ // Checklist items whose title/evidence already name repository files get a leading `대상 경로:` line
990
+ // (the label parseAskTargetPathHints reads); without paths the text is byte-identical to the old one-liner.
991
+ export function seatTaskText(seat: string, item: SeatItem, isFile?: (path: string) => boolean): string {
992
+ const where = item.source === 'checklist' ? `${item.version ?? '?'} 체크리스트 칸 ${item.id}` : item.source === 'idle' ? `쉬는 시간 사다리 ${item.id} · ${item.idleRung}단계${item.version ? ` · ${item.version}` : ''}` : item.source === 'hook' ? `웹훅 작업 카드 ${item.id}` : `자리 요청 ${item.id}`;
993
+ const text = `[${seat} 자리 · ${where} · 역할 docs/roles/${seat}.md] ${item.title.replace(/[\r\n]+/g, ' ')}`;
994
+ if (item.source !== 'checklist') return text;
995
+ const paths = seatTargetPaths(item, isFile);
996
+ return paths.length ? `대상 경로: ${paths.join(' · ')}\n${text}` : text;
956
997
  }
957
998
 
958
999
  function xcheckEvent(event: string, seat: string, neighbor: string, item: string, outcome: string): void {
@@ -986,8 +1027,10 @@ function neighborLastSeen(root: string, isolated: boolean, now: Date, wanted: Re
986
1027
  }
987
1028
 
988
1029
  function neighborSeat(id: string): string | null {
989
- const match = /^(op|tc|mk|ux)-seat$/i.exec(id);
990
- return match ? match[1]!.toUpperCase() : null;
1030
+ const match = /^([A-Za-z][A-Za-z0-9_-]*)-seat$/i.exec(id);
1031
+ if (!match) return null;
1032
+ const resolved = resolveSeat(match[1]!)?.id;
1033
+ return resolved && isSeatId(resolved) ? resolved : null;
991
1034
  }
992
1035
 
993
1036
  function judgeSeatNeighbors(seat: string, config: SeatLoopConfig, deps: SeatDeps, root: string, now: Date): void {
@@ -1034,8 +1077,89 @@ function judgeSeatNeighbors(seat: string, config: SeatLoopConfig, deps: SeatDeps
1034
1077
  }
1035
1078
  }
1036
1079
 
1080
+ const IDLE_WORK: Record<SeatId, string> = {
1081
+ MK: '문서 최적화: 현재 문서의 탐색·명료성·검색 유입을 실물 근거로 개선',
1082
+ UX: '멀티 서피스: 실제 채널별 사용자 흐름을 대조하고 결함을 개선',
1083
+ TC: '하니스 자가 치유: 실패 원장을 대조하여 재현 가능한 결함을 수리',
1084
+ OP: '보충 저작: 판 체크리스트의 빠진 칸을 근거와 반증 기준으로 보완',
1085
+ };
1086
+
1087
+ function idleCell(seat: string, rung: 1 | 2 | 3 | 4 | 5, id: string, title: string, now: Date, version?: string, owner?: string, evidence?: string): SeatItem {
1088
+ return { source: 'idle', id, title, text: title, idleRung: rung, seat: owner ?? seat,
1089
+ ...(version ? { version } : {}), ...(evidence ? { evidence } : {}), asOf: now.toISOString() };
1090
+ }
1091
+
1092
+ function emptyQueueMinutes(ledger: readonly SeatEntry[], now: Date): number {
1093
+ const previousIndex = [...ledger].reverse().findIndex((row) => row.emptyQueueMinutes !== undefined);
1094
+ const workIndex = [...ledger].reverse().findIndex((row) => row.item && row.item.source !== 'idle' && row.status !== 'skipped-empty');
1095
+ const previousPosition = previousIndex < 0 ? -1 : ledger.length - 1 - previousIndex;
1096
+ const workPosition = workIndex < 0 ? -1 : ledger.length - 1 - workIndex;
1097
+ const previous = ledger[previousPosition];
1098
+ const lastWork = ledger[workPosition];
1099
+ const previousAt = previous ? Date.parse(previous.at) : NaN;
1100
+ const workAt = lastWork ? Date.parse(lastWork.at) : NaN;
1101
+ const start = Number.isFinite(previousAt) && Number.isFinite(workAt) ? Math.max(previousAt, workAt)
1102
+ : Number.isFinite(previousAt) ? previousAt : workAt;
1103
+ if (!Number.isFinite(start)) return 0;
1104
+ const prior = previousPosition > workPosition ? previous!.emptyQueueMinutes! : 0;
1105
+ return prior + Math.max(0, (now.getTime() - start) / 60_000);
1106
+ }
1107
+
1108
+ function pickIdleWork(seat: SeatId, deps: SeatDeps, ledger: readonly SeatEntry[], now: Date, root: string, shadow: boolean): SeatItem | null {
1109
+ const releaseRoot = deps.root ?? releaseLedgerRoot();
1110
+ const schedules = (deps.schedules ?? (() => readSchedules(releaseRoot)))()
1111
+ .filter((row) => /^\d+\.\d+\.\d+$/.test(row.version) && Number.isFinite(Date.parse(row.cutAt)))
1112
+ .sort((a, b) => versionOrder(a.version, b.version));
1113
+ const shipped = releasedVersion(releaseRoot);
1114
+ const current = schedules.find((row) => Date.parse(row.cutAt) > now.getTime() && (!shipped || versionOrder(row.version, shipped) > 0));
1115
+ const itemsOf = deps.checklistItems ?? ((version: string) => listChecklist(version, releaseRoot).items);
1116
+ const handled = handledKeys(ledger, shadow, root, !shadow);
1117
+ const available = (item: SeatItem) => eligibleItem(item, ledger, handled, shadow);
1118
+ // The normal queue handles open owned cells first; a landed yellow cell is revisited here for physical rebuttal.
1119
+ for (const row of schedules.filter((row) => row.version === current?.version || Date.parse(row.cutAt) <= now.getTime() || (shipped && versionOrder(row.version, shipped) <= 0))) {
1120
+ for (const cell of itemsOf(row.version).filter((cell) => cell.owner === seat && cell.status === 'yellow' && /착지|merged|병합/i.test(cell.evidence ?? '')).sort((a, b) => a.id.localeCompare(b.id))) {
1121
+ const item = idleCell(seat, 2, cell.id, `착지 후 노랑 실물 반증: ${cell.id} · ${cell.title}`, now, row.version, seat, cell.evidence);
1122
+ item.evidenceHash = createHash('sha256').update(cell.evidence ?? '').digest('hex');
1123
+ if (available(item)) return item;
1124
+ }
1125
+ }
1126
+ const next = schedules.find((row) => current && versionOrder(row.version, current.version) > 0);
1127
+ if (next) {
1128
+ const title = `${next.version} 다음 판 칸 저작: ${seat} 자리의 자율성·효율성·동시성을 높일 칸을 근거와 실물 반증부터 작성`;
1129
+ const item = idleCell(seat, 3, `author:${seat}`, title, now, next.version);
1130
+ if (available(item)) return item;
1131
+ }
1132
+ const recurring = idleCell(seat, 4, `recurring:${seat}:${seatDay(now)}`, IDLE_WORK[seat], now);
1133
+ if (available(recurring)) return recurring;
1134
+ const queue = (deps.queueItems ?? ((stateRoot: string) => listHarnessQueue({ root: stateRoot })))(root);
1135
+ const candidates: Array<{ owner: SeatId; item: SeatItem; pressure: number }> = [];
1136
+ for (const owner of ['OP', 'MK', 'UX', 'TC'] as const) {
1137
+ if (owner === seat) continue;
1138
+ const ownerLedger = readSeatLedger(owner, deps, now);
1139
+ const busy = queue.filter((row) => row.seat === owner && ['queued', 'launching', 'launched'].includes(row.status)).length;
1140
+ for (const row of schedules.filter((row) => Date.parse(row.cutAt) > now.getTime() && (!shipped || versionOrder(row.version, shipped) > 0))) {
1141
+ for (const cell of itemsOf(row.version).filter((cell) => cell.owner === owner && cell.status === 'yellow')) {
1142
+ const evidence = cell.evidence ?? '';
1143
+ const runIds = [...evidence.matchAll(RUN_REFERENCE)].map(([id]) => id.toLowerCase());
1144
+ if (runIds.length) {
1145
+ const observed = (deps.cutoffRuns ?? ((ids: readonly string[]) => queryRunningRuns({ runIds: ids, caller: 'seat-loop.idle' })))(runIds);
1146
+ if (observed.completeness !== 'complete' || observed.pty.unreadable.length || observed.entries.some((entry) => entry.status !== 'ended-unclosed')) continue;
1147
+ }
1148
+ if (ownerLedger.some((entry) => entry.item?.source === 'checklist' && entry.item.id === cell.id && entry.item.version === row.version
1149
+ && ['queued', 'attempting', 'outcome-unknown', 'launched'].includes(entry.status))) continue;
1150
+ if (queue.some((entry) => entry.seat === owner && ['queued', 'launching', 'launched'].includes(entry.status)
1151
+ && entry.input.includes(`체크리스트 칸 ${cell.id} ·`))) continue;
1152
+ const item = idleCell(seat, 5, `borrow:${owner}:${cell.id}`, `${row.version} ${cell.id} · ${cell.title} — ${seat} 지원 (주인 ${owner} 유지)`, now, row.version, owner, evidence);
1153
+ item.evidenceHash = createHash('sha256').update(evidence).digest('hex');
1154
+ if (available(item)) candidates.push({ owner, item, pressure: busy + itemsOf(row.version).filter((entry) => entry.owner === owner && entry.status === 'yellow').length });
1155
+ }
1156
+ }
1157
+ }
1158
+ return candidates.sort((a, b) => b.pressure - a.pressure || a.owner.localeCompare(b.owner) || a.item.id.localeCompare(b.item.id))[0]?.item ?? null;
1159
+ }
1160
+
1037
1161
  export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promise<SeatLoopResult> {
1038
- if (!/^(?:MK|OP|TC|UX)$/.test(seat)) throw new Error(`unknown seat: ${seat}`);
1162
+ if (!isSeatId(seat) || !registeredSeats().some(({ id }) => id === seat)) throw new Error(`unknown seat: ${seat}`);
1039
1163
  const config = deps.config ?? getUserConfig().loops?.seat ?? { mode: 'shadow' };
1040
1164
  if (config.mode === 'off') {
1041
1165
  observe('skipped-off', { seat });
@@ -1060,6 +1184,25 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
1060
1184
  } catch (error) { observe('heartbeat-write-failed', { seat, error: String(error).slice(0, 200) }); }
1061
1185
  const recorded = readSeatLedger(seat, deps, now);
1062
1186
  const ledger = config.mode === 'live-safe' ? reconciledQueueLedger(recorded, stateRoot, deps) : recorded;
1187
+ const loopId = seat === 'MK' ? 'cmo-seat' : `${seat.toLowerCase()}-seat`;
1188
+ const heartbeatEveryMs = (SEAT_NEIGHBORS[seat]?.find((peer) => peer.id !== 'orchestrator')?.heartbeat.everyMinutes ?? 15) * 60_000;
1189
+ const leaseMs = heartbeatEveryMs * 2;
1190
+ const pulse = (health: 'healthy' | 'degraded', at: Date): void => {
1191
+ try { emitLoopHeartbeat(stateRoot, loopId, health, at, new Date(at.getTime() + leaseMs).toISOString()); }
1192
+ catch (error) { observe('neighbor-runtime-failed', { seat, error: String(error).slice(0, 200) }); }
1193
+ };
1194
+ pulse('healthy', (deps.now ?? (() => new Date()))());
1195
+ const watchNeighbors = (at: Date): void => {
1196
+ try { checkLoopNeighbors(stateRoot, loopId, SEAT_NEIGHBORS[seat] ?? [], at); }
1197
+ catch (error) { observe('neighbor-runtime-failed', { seat, error: String(error).slice(0, 200) }); }
1198
+ };
1199
+ // A long turn keeps both duties periodic: renew our lease and watch neighbors (incidents are idempotent).
1200
+ const heartbeatTimer = setInterval(() => {
1201
+ const at = (deps.now ?? (() => new Date()))();
1202
+ pulse('healthy', at);
1203
+ watchNeighbors(at);
1204
+ }, deps.heartbeatIntervalMs ?? heartbeatEveryMs / 2);
1205
+ heartbeatTimer.unref();
1063
1206
  if (seat === 'OP') opDecidePass(config, deps, now, path, stateRoot, ledger);
1064
1207
  let turnError: unknown;
1065
1208
  try {
@@ -1259,8 +1402,14 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
1259
1402
  checklist: inputs.checklist.filter((candidate) => !ledger.some((row) => row.status === 'awaiting-resolution'
1260
1403
  && row.item && itemKey(row.item) === itemKey(candidate) && itemSnapshot(row.item) === itemSnapshot(candidate))) }, ledger,
1261
1404
  { shadow: config.mode === 'shadow', liveSafe: config.mode === 'live-safe', root: stateRoot, history: deps.checklistHistory ?? checklistHistory });
1262
- const item = pendingQuestion ?? ordinary;
1263
- const append = deps.append ?? defaultAppend;
1405
+ const ownQueueEmpty = !pendingQuestion && !ordinary;
1406
+ const idleMinutes = ownQueueEmpty ? emptyQueueMinutes(ledger, now) : undefined;
1407
+ // IDLE-LADDER: off unless loops.seat.idleLadder is set; 'shadow' records the idle pick only, 'live' lets live-safe/on launch it.
1408
+ const idleShadow = config.mode === 'shadow' || config.idleLadder !== 'live';
1409
+ const item = pendingQuestion ?? ordinary ?? (ownQueueEmpty && !lastDecisionFailure && (config.idleLadder === 'shadow' || config.idleLadder === 'live')
1410
+ ? pickIdleWork(seat as SeatId, deps, ledger, now, stateRoot, idleShadow) : null);
1411
+ const appendBase = deps.append ?? defaultAppend;
1412
+ const append: typeof appendBase = (file, row) => appendBase(file, idleMinutes === undefined ? row : { ...row, emptyQueueMinutes: idleMinutes });
1264
1413
  const revisedProposal = item?.source === 'checklist' && ledger.some((row) => row.status === 'awaiting-resolution'
1265
1414
  && row.item && itemKey(row.item) === itemKey(item) && itemSnapshot(row.item) !== itemSnapshot(item));
1266
1415
  if (config.mode === 'live-safe' && !pendingQuestion) {
@@ -1448,11 +1597,12 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
1448
1597
  }
1449
1598
  if (!item && config.mode === 'live-safe' && tcDowngrade) return tcDowngrade;
1450
1599
  const entry: SeatEntry = { seat, ts: now.toISOString(), at: now.toISOString(), status: item ? 'shadow' : 'skipped-empty',
1600
+ ...(idleMinutes === undefined ? {} : { emptyQueueMinutes: idleMinutes }),
1451
1601
  ...(item ? { item, action: planned!.kind, ...(planned!.reason ? { reason: planned!.reason } : {}) } : { action: 'skipped-empty' as const }) };
1452
- if (config.mode === 'shadow' || !item) {
1602
+ if (config.mode === 'shadow' || !item || (item.source === 'idle' && idleShadow)) {
1453
1603
  if (!item && lastDecisionFailure) return lastDecisionFailure;
1454
1604
  append(path, entry);
1455
- if (config.mode === 'shadow') observe('shadow', { seat, item, status: entry.status });
1605
+ if (config.mode === 'shadow' || item?.source === 'idle') observe('shadow', { seat, item, status: entry.status });
1456
1606
  return entry;
1457
1607
  }
1458
1608
  if (planned!.kind === 'wait') {
@@ -1669,8 +1819,12 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
1669
1819
  turnError = error;
1670
1820
  throw error;
1671
1821
  } finally {
1822
+ clearInterval(heartbeatTimer);
1672
1823
  // A turn ends even when there is no picked item or a launch fails.
1673
- try { judgeSeatNeighbors(seat, config, deps, stateRoot, (deps.now ?? (() => new Date()))()); }
1824
+ const endedAt = (deps.now ?? (() => new Date()))();
1825
+ pulse(turnError === undefined ? 'healthy' : 'degraded', endedAt);
1826
+ watchNeighbors(endedAt);
1827
+ try { judgeSeatNeighbors(seat, config, deps, stateRoot, endedAt); }
1674
1828
  catch (error) {
1675
1829
  observe('neighbor-judgment-failed', { seat, error: String(error).slice(0, 200) });
1676
1830
  const neighbors = config.neighbors?.[seat as keyof NonNullable<SeatLoopConfig['neighbors']>] ?? [];
@@ -7,6 +7,10 @@ import { debug } from '../debug/log.js';
7
7
  import { effectiveInstanceRoot } from '../instance/resolve.js';
8
8
  import { getUserConfig, type SeatLoopConfig } from '../user-config.js';
9
9
  import { collectDraftMetrics, collectOverlapMetrics, countSalvagedToday, type DraftMetrics, type OverlapMetrics } from '../self-dev/draft-sweep.js';
10
+ import { queryRunningRuns } from '../self-implement/running-runs.js';
11
+ import { readTaskAgentCover } from '../cli/tasks-cli.js';
12
+ import { formatTaskAgentCover } from '../task-agent/cover.js';
13
+ import type { TasksCliDeps } from '../cli/tasks-cli.js';
10
14
  import { seatDay, seatLedgerPath, type OpCandidate, type SeatEntry } from './seat-loop.js';
11
15
 
12
16
  export type SeatReportDeps = {
@@ -19,8 +23,10 @@ export type SeatReportDeps = {
19
23
  send?: (body: string, seat: string, pr: number) => Promise<void> | void;
20
24
  draftMetrics?: (now: Date) => Promise<DraftMetrics>;
21
25
  overlapMetrics?: (now: Date) => Promise<OverlapMetrics>;
26
+ coverLogs?: TasksCliDeps['coverLogs'];
22
27
  /** DRAFT-NOT-ARCHIVE salvage branches since the KST day start; null = unreadable. */
23
28
  salvagedToday?: (dayStart: Date, now: Date) => Promise<number | null>;
29
+ runningRuns?: (runIds: readonly string[]) => { running: number; unknown: number } | null;
24
30
  };
25
31
  export type SeatReportResult = { seat: string; date: string; body: string; posted: boolean };
26
32
  const repoRoot = resolve(import.meta.dir, '../..');
@@ -144,8 +150,30 @@ export async function seatReport(seat: string, deps: SeatReportDeps = {}): Promi
144
150
  }))(new Date(`${date}T00:00:00+09:00`), now);
145
151
  } catch { salvagedToday = null; }
146
152
  draftDetail += ` · 수확 가지 오늘 ${salvagedToday === null ? '못 잼' : salvagedToday}`;
153
+ try {
154
+ const cover = readTaskAgentCover({}, deps.coverLogs);
155
+ draftDetail += ` · TASK-AGENT cover ${formatTaskAgentCover(cover.rows[0]!)}${cover.unreadableStores ? ` (logs.db ${cover.unreadableStores}개 못 읽음)` : ''}`;
156
+ } catch {
157
+ draftDetail += ` · TASK-AGENT cover ${formatTaskAgentCover({ verb: 'land', byTaskAgent: null, total: null, observedActions: null, ratio: null, state: 'unreadable', liveActions: null, shadowActions: null, stewardTransition: 'unmeasured' })}`;
158
+ }
159
+ }
160
+ const runIds = [...new Set(entries.filter((entry) => entry.status === 'launched' && entry.runId).map((entry) => entry.runId!))];
161
+ let runningDetail = ' · 도는 런 0';
162
+ if (runIds.length) {
163
+ try {
164
+ const counts = (deps.runningRuns ?? ((ids: readonly string[]) => {
165
+ const observed = queryRunningRuns({ runIds: ids, caller: 'seat-report' });
166
+ if (observed.completeness !== 'complete') return null;
167
+ return {
168
+ running: observed.entries.filter((entry) => ids.includes(entry.runId) && entry.status === 'running').length,
169
+ unknown: observed.entries.filter((entry) => ids.includes(entry.runId) && (entry.status === 'probable-running' || entry.status === 'unknown')).length,
170
+ };
171
+ }))(runIds);
172
+ runningDetail = counts === null ? ' · 도는 런 못 잼'
173
+ : ` · 도는 런 ${counts.running}(불확실 ${counts.unknown})`;
174
+ } catch { runningDetail = ' · 도는 런 못 잼'; }
147
175
  }
148
- const body = `**[${seat}]** {{TS}} → 보고 ${date}: ${detail.join(' · ') || (classified.length ? 'OP 그림자 판단' : '원장 기록 없음')}${opDetail}${draftDetail}`;
176
+ const body = `**[${seat}]** {{TS}} → 보고 ${date}: ${detail.join(' · ') || (classified.length ? 'OP 그림자 판단' : '원장 기록 없음')}${opDetail}${draftDetail}${runningDetail}`;
149
177
  const pr = (deps.config ?? getUserConfig().loops?.seat)?.reportPr;
150
178
  const posted = deps.post === true && pr !== undefined;
151
179
  if (posted) await (deps.send ?? ((text, id, number) => defaultSend(text, id, number, deps.repo ?? repoRoot)))(body, seat, pr);
@@ -852,7 +852,7 @@ export async function runAskLaunchFlow(
852
852
  if (launchPreflight === false) return { kind: 'stopped-before-authoring' };
853
853
 
854
854
  await deps.beforeAuthoring?.();
855
- const authorAndPreflight = async (authorAskText: string = input.askText): Promise<{
855
+ const authorAndPreflight = async (authorAskText: string = input.askText, machineSuggestedTargetPaths: readonly string[] = []): Promise<{
856
856
  readonly goalFile: string;
857
857
  readonly decision: ReturnType<typeof decideAskPreflight>;
858
858
  }> => {
@@ -950,6 +950,8 @@ export async function runAskLaunchFlow(
950
950
  recentChangeWindowDays: input.recentChangeWindowDays,
951
951
  openPrsLimit: OPEN_PR_LIMIT,
952
952
  ...(askOutsidePathHints.length > 0 ? { askOutsidePathHints } : {}),
953
+ // AUTO-TARGET: paths injected by the candidate search (not written in the ask) — a NOT-GROUNDED goal stops on them.
954
+ ...(machineSuggestedTargetPaths.length > 0 ? { machineSuggestedTargetPaths } : {}),
953
955
  declaredPathsRoot: deps.cwd(),
954
956
  },
955
957
  preflightDeps,
@@ -1032,7 +1034,7 @@ export async function runAskLaunchFlow(
1032
1034
  }
1033
1035
  authored = await authorAndPreflight(selectedPaths.length > 0
1034
1036
  ? `대상 경로: ${selectedPaths.join(' · ')}\n${input.askText}`
1035
- : input.askText);
1037
+ : input.askText, selectedPaths);
1036
1038
  deps.log('ask-auto-reauthor', {
1037
1039
  candidatePathCount: candidates.paths.length,
1038
1040
  firstGoalFile: first.goalFile,
@@ -97,7 +97,7 @@ function inspectLaunchingTree(cwd: string): LaunchingTreeProbe {
97
97
 
98
98
  /** 발사 전 검사가 쓰는 조회 구현 — ⛔ 이 «한 벌»을 모든 표면이 공유한다. */
99
99
  export async function buildAskPreflightDeps(options: BuildAskPreflightDepsOptions = {}): Promise<AskPreflightDeps & { inspectLaunchingTree(cwd: string): LaunchingTreeProbe }> {
100
- const [{ tracedPathReferences, verbatimOriginalAsk }, { loadRunLedger, queryFederatedCompletedRunLedgers, queryFederatedInterruptedRunLedgers, queryFederatedUnfinishedRunLedgers }] = await Promise.all([
100
+ const [{ tracedPathReferences, verbatimOriginalAsk, isNotGroundedWithoutPersistentEvidence }, { loadRunLedger, queryFederatedCompletedRunLedgers, queryFederatedInterruptedRunLedgers, queryFederatedUnfinishedRunLedgers }] = await Promise.all([
101
101
  import('../self-implement/goal-author.js'),
102
102
  import('../self-implement/run-ledger.js'),
103
103
  ]);
@@ -166,6 +166,8 @@ export async function buildAskPreflightDeps(options: BuildAskPreflightDepsOption
166
166
  // ⛔ ask 표지가 없으면 빈 배열 — 그때는 표시가 «안 붙는다»(지어내지 않는다).
167
167
  askTargetPaths: (document: string) => parseAskTargetPathHints(verbatimOriginalAsk(document) ?? ''),
168
168
  askTargetPathRejections: (document: string) => parseAskTargetPathHintsResult(verbatimOriginalAsk(document) ?? '').rejected,
169
+ // AUTO-TARGET: a NOT-GROUNDED goal (no persistent evidence) is a scope card — machine-suggested paths do not launch it.
170
+ notGroundedWithoutPersistentEvidence: isNotGroundedWithoutPersistentEvidence,
169
171
  // ⭐ 충돌 문면이 경로마다 「대상/근거」를 말하게 한다 — 판정은 «안» 바꾼다(문면만 는다).
170
172
  // ⛔ TRACED PATHS 는 「바꿀 파일」과 「근거로 읽은 파일」을 섞는다(실측: TRACED 8 · 변경 3).
171
173
  // ⇒ 사람이 막혔을 때 「그게 근거였네」를 «즉시» 알 수 있어야 한다.
@@ -45,6 +45,7 @@ import { getUserConfig, type UserConfig } from '../user-config.js';
45
45
  import { resolveChildLlmPreference, type ChildLlmPreferenceInput, type ResolvedChildLlmPreference } from '../self-implement/child-llm-preference.js';
46
46
  import { applyLaunchOverloadFailover, codexProviderNamed } from '../oauth/overload-failover-launch.js';
47
47
  import { readCachedGrokQuota } from '../oauth/codex-account-store.js';
48
+ import { appendRunLedgerEntry } from '../self-implement/run-ledger.js';
48
49
  import { queryAbandonedDraftPrs, type AbandonedDraftPr } from '../cli/logs-abandoned-draft-prs.js';
49
50
  import { branchLineageSlug } from '../cli/pr-lineage.js';
50
51
  import { decideDraft, type DraftTriagePr } from './draft-triage-rules.js';
@@ -1494,6 +1495,20 @@ export function formatChildLlmInterpretationLine(
1494
1495
  return `[dev] child-llm: entered=${resolution.entered} · resolved=${resolution.resolvedId} · tier=${resolution.tier} · thinking=${resolution.supportsThinking}${tail}`;
1495
1496
  }
1496
1497
 
1498
+ /** Only the weekly Grok credits period measures the weekly cap; missing or malformed periods are unmeasured. */
1499
+ export function grokWeeklyUsedPctFromUsageJson(stdout: string): number | undefined {
1500
+ try {
1501
+ const parsed = JSON.parse(stdout) as { rows?: Array<{ provider?: string; credits?: { status?: string; periodType?: string | null; usedPercent?: unknown } }> };
1502
+ if (!Array.isArray(parsed.rows)) return undefined;
1503
+ const percents = parsed.rows
1504
+ .filter((row) => row?.provider === 'grok' && row.credits?.status === 'ok'
1505
+ && row.credits.periodType === 'USAGE_PERIOD_TYPE_WEEKLY')
1506
+ .map((row) => row.credits?.usedPercent)
1507
+ .filter((pct): pct is number => typeof pct === 'number' && Number.isFinite(pct) && pct >= 0 && pct <= 100);
1508
+ return percents.length > 0 ? Math.max(...percents) : undefined;
1509
+ } catch { return undefined; }
1510
+ }
1511
+
1497
1512
  /** `elanous usage --json` 의 grok 행 → 잔량 판정. 한 행이라도 100% 미만이면 usable, 모두 100% 이상이면 exhausted. */
1498
1513
  export function grokQuotaFromUsageJson(stdout: string): 'usable' | 'exhausted' | 'unknown' {
1499
1514
  try {
@@ -1519,17 +1534,75 @@ export function setLaunchGrokQuotaReaderForTesting(reader: (() => 'usable' | 'ex
1519
1534
  launchGrokQuotaReaderForTesting = reader;
1520
1535
  }
1521
1536
 
1537
+ let launchGrokWeeklyReaderForTesting: (() => number | undefined) | undefined;
1538
+ export function setLaunchGrokWeeklyReaderForTesting(reader: (() => number | undefined) | undefined): void {
1539
+ launchGrokWeeklyReaderForTesting = reader;
1540
+ }
1541
+
1542
+ /**
1543
+ * 발사 한 번에 `elanous usage --json`(약 3초) 을 «한 번만» 부른다 — 주간 사용률(재배분)과 잔량(경고)이 같은 출력을 나눈다.
1544
+ * 한 CLI 프로세스 = 한 발사이므로 프로세스 안 60초 캐시로 충분하다(실패 null 도 캐시 — 같은 발사에서 20초 타임아웃을 두 번 물지 않게).
1545
+ */
1546
+ let launchUsageJsonCache: { at: number; stdout: string | null } | undefined;
1547
+ const LAUNCH_USAGE_CACHE_MS = 60_000;
1548
+ export function runLaunchUsageJsonOnce(): string | null {
1549
+ const now = Date.now();
1550
+ if (launchUsageJsonCache && now - launchUsageJsonCache.at < LAUNCH_USAGE_CACHE_MS) return launchUsageJsonCache.stdout;
1551
+ let stdout: string | null = null;
1552
+ try {
1553
+ const cli = new URL('../../bin/elanous.mjs', import.meta.url).pathname;
1554
+ const r = spawnSync(process.execPath, [cli, ...(IN_TEST_PROCESS() || process.env.ELANOUS_TEST_MODE === '1' ? ['--test'] : []), 'usage', '--json'], { encoding: 'utf8', timeout: 20_000 });
1555
+ stdout = r.status === 0 ? r.stdout : null;
1556
+ } catch { stdout = null; }
1557
+ launchUsageJsonCache = { at: now, stdout };
1558
+ return stdout;
1559
+ }
1560
+
1561
+ /** Launch's weekly percentage comes from the live usage period, not the coarse cached usable flag. */
1562
+ export function readGrokWeeklyUsedPctForLaunch(runUsage?: () => string | null): number | undefined {
1563
+ if (launchGrokWeeklyReaderForTesting && !runUsage) return launchGrokWeeklyReaderForTesting();
1564
+ try {
1565
+ const stdout = (runUsage ?? runLaunchUsageJsonOnce)();
1566
+ return stdout ? grokWeeklyUsedPctFromUsageJson(stdout) : undefined;
1567
+ } catch { return undefined; }
1568
+ }
1569
+
1570
+ export function rebalanceGrokChildForLaunch(
1571
+ child: ChildLlmSelection,
1572
+ cap: number,
1573
+ used: number | undefined,
1574
+ runId?: string,
1575
+ deps: { write?: (line: string) => void; record?: typeof appendRunLedgerEntry } = {},
1576
+ ): ChildLlmSelection {
1577
+ if (child.provider !== 'grok') return child;
1578
+ const measured = typeof used === 'number' && Number.isFinite(used) && used >= 0 && used <= 100;
1579
+ const rebalanced = measured && used >= cap;
1580
+ const selected = rebalanced
1581
+ ? { ...child, provider: 'openai-codex', model: defaultChildLlmModel('openai-codex'), codexQuotaPolicy: 'credits' as const }
1582
+ : child;
1583
+ const reason = !measured ? '측정 불가' : rebalanced ? `grok 주간 사용률 ${used}% ≥ 상한 ${cap}%` : `grok 주간 사용률 ${used}% < 상한 ${cap}%`;
1584
+ if (rebalanced || !measured) (deps.write ?? ((line) => process.stderr.write(line)))(
1585
+ `[dev] child-llm ${child.provider} → ${selected.provider} · ${reason}\n`,
1586
+ );
1587
+ if (rebalanced || !measured) {
1588
+ const data = { requestedProvider: child.provider, selectedProvider: selected.provider,
1589
+ weeklyUsedPct: measured ? used : null, capPct: cap, reason, selection: child.source };
1590
+ // 실제 전환과 «측정 못 해 그대로 둠» 을 다른 이벤트로 — 관측에서 둘을 섞지 않는다.
1591
+ const event = rebalanced ? 'launch-provider-rebalanced' : 'launch-provider-weekly-unmeasured';
1592
+ debug.log('self-implement', event, { runId: runId ?? null, ...data });
1593
+ if (runId) (deps.record ?? appendRunLedgerEntry)({ timestamp: new Date().toISOString(), runId,
1594
+ event, data: { runId, ...data } });
1595
+ }
1596
+ return selected;
1597
+ }
1598
+
1522
1599
  export function readGrokQuotaForLaunch(
1523
1600
  deps: { readCached?: typeof readCachedGrokQuota; runUsage?: () => string | null } = {},
1524
1601
  ): 'usable' | 'exhausted' | 'unknown' {
1525
1602
  if (launchGrokQuotaReaderForTesting && !deps.readCached && !deps.runUsage) return launchGrokQuotaReaderForTesting();
1526
1603
  const cached = (deps.readCached ?? readCachedGrokQuota)();
1527
1604
  if (cached !== 'unknown') return cached;
1528
- const runUsage = deps.runUsage ?? (() => {
1529
- const cli = new URL('../../bin/elanous.mjs', import.meta.url).pathname;
1530
- const r = spawnSync(process.execPath, [cli, 'usage', '--json'], { encoding: 'utf8', timeout: 20_000 });
1531
- return r.status === 0 ? r.stdout : null;
1532
- });
1605
+ const runUsage = deps.runUsage ?? runLaunchUsageJsonOnce;
1533
1606
  let stdout: string | null = null;
1534
1607
  try { stdout = runUsage(); } catch { stdout = null; }
1535
1608
  return stdout ? grokQuotaFromUsageJson(stdout) : 'unknown';
@@ -1851,6 +1924,7 @@ export function buildDriveAliasDevSpec(
1851
1924
  command: string | undefined,
1852
1925
  opts: DevCliOpts,
1853
1926
  explicitOptionNames?: readonly string[],
1927
+ runId?: string,
1854
1928
  ): DevPipelineSpec {
1855
1929
  if (opts.elanous === true) {
1856
1930
  throw new DevPipelineError('drive: --elanous 는 지원하지 않음 — TUI target은 elanous dev --elanous 를 사용');
@@ -1861,7 +1935,7 @@ export function buildDriveAliasDevSpec(
1861
1935
  if (!opts.goal?.trim()) {
1862
1936
  throw new DevPipelineError('drive: --goal 필요');
1863
1937
  }
1864
- const spec = buildDevCliSpec({ text: command.trim() }, { kind: 'self' }, opts, explicitOptionNames, 'cli-drive');
1938
+ const spec = buildDevCliSpec({ text: command.trim() }, { kind: 'self' }, opts, explicitOptionNames, 'cli-drive', undefined, undefined, runId);
1865
1939
  return { ...spec, completion: 'worktree-only', autoReview: false };
1866
1940
  }
1867
1941
 
@@ -1872,6 +1946,9 @@ export function buildDevCliSpec(
1872
1946
  explicitOptionNames?: readonly string[],
1873
1947
  entrance: EntranceId = 'cli-dev-ask',
1874
1948
  readGrokQuota: typeof readCachedGrokQuota = readGrokQuotaForLaunch,
1949
+ readGrokWeekly: () => number | undefined = readGrokWeeklyUsedPctForLaunch,
1950
+ runId?: string,
1951
+ recordLaunch?: typeof appendRunLedgerEntry,
1875
1952
  ): DevPipelineSpec {
1876
1953
  if (opts.plan === true) {
1877
1954
  throw new DevPipelineError(`--plan 은 은퇴했고 명시적으로 거부됨 · 대응 문: ${DEV_PLAN_REPLACEMENT}`);
@@ -2016,8 +2093,13 @@ export function buildDevCliSpec(
2016
2093
  }
2017
2094
 
2018
2095
  if (path === 'self-mission') {
2019
- const builtChild = buildChildLlmSelection({ ...opts, readPreference: () => getUserConfig() });
2020
- const childLlm = applyLaunchOverloadFailover(builtChild, {
2096
+ const requestedChildLlm = buildChildLlmSelection({ ...opts, readPreference: () => getUserConfig() });
2097
+ // 주간 상한 재배분(grok → codex)을 먼저, 과부하 페일오버(codex → grok)를 그 뒤에 — 과부하는 «지금 실패 중»이라 더 강한 신호다.
2098
+ const capped = requestedChildLlm?.provider === 'grok'
2099
+ ? rebalanceGrokChildForLaunch(requestedChildLlm, getUserConfig().llm.grokWeeklyCapPct ?? 80, readGrokWeekly(), runId,
2100
+ recordLaunch ? { record: recordLaunch } : {})
2101
+ : requestedChildLlm;
2102
+ const childLlm = applyLaunchOverloadFailover(capped, {
2021
2103
  provider: opts.childLlmProvider,
2022
2104
  model: opts.childLlmModel,
2023
2105
  codexPinned: childLlmCodexPinned(opts.childLlmProvider),
@@ -43,7 +43,8 @@ import { addSelfDevRunParticipant, processBirthId, saveSelfDevRun } from './run-
43
43
  import { getUserConfig, resolveRoleLlm } from '../user-config.js';
44
44
  import { reviewReasoningEffort } from '../model-tier/review-effort.js';
45
45
  import { getProvider, inferProviderFromModel } from '../llm.js';
46
- import { buildReviewProviderAttempts, defaultReviewFallbackModel, runReviewWithFallback, reviewFallbackModelsFromConfig } from './review-provider-fallback.js';
46
+ import { buildReviewProviderAttempts, defaultReviewFallbackModel, runReviewWithFallback, reviewFallbackModelsFromConfig, runSubscriptionReviewer, type SubscriptionReviewerDeps } from './review-provider-fallback.js';
47
+ import { loadLlmPolicy } from '../policy/llm-policy.js';
47
48
  import type { DefaultSeamsOptions } from '../self-implement/seams.js';
48
49
  import { harnessTargetOptions, resolveHarnessTarget, revalidateHarnessTarget, type HarnessTargetResolution } from '../self-implement/harness-target-options.js';
49
50
  import { provisionRepository, type RepoProvisionResult } from '../self-implement/repo-provision.js';
@@ -1077,6 +1078,8 @@ export async function buildDefaultSelfImplementSeams(
1077
1078
  toolReviewerRoll?: () => number;
1078
1079
  /** Tool-reviewer factory (same prompt→text shape as llmReview). Tests stub this; default is ACP. */
1079
1080
  makeToolReviewLLM?: () => UnmannedToolReviewLLM | Promise<UnmannedToolReviewLLM>;
1081
+ subscriptionReviewerDeps?: SubscriptionReviewerDeps;
1082
+ makeSubscriptionReviewLLM?: (spawn: ReturnType<typeof import('./review-provider-fallback.js').subscriptionReviewerSpawn>) => (prompt: string) => Promise<string>;
1080
1083
  } = {},
1081
1084
  ): Promise<SelfImplementSeams> {
1082
1085
  const { defaultSeams } = await import('../self-implement/seams.js');
@@ -1143,6 +1146,21 @@ export async function buildDefaultSelfImplementSeams(
1143
1146
  reviewerCanSelfRead = false;
1144
1147
  }
1145
1148
  }
1149
+ let subscriptionReviewLLM: ((prompt: string) => Promise<string>) | undefined;
1150
+ if (reviewerIsDefaultApiCall) {
1151
+ const userConfig = getUserConfig();
1152
+ const policy = loadLlmPolicy();
1153
+ const spec = policy.policy.roles.reviewer ?? userConfig.roleLlm?.reviewer;
1154
+ if (spec) {
1155
+ const cap = policy.policy.caps.claude.harness;
1156
+ subscriptionReviewLLM = (prompt) => runSubscriptionReviewer(spec, cap, prompt, defaultApiReview, async (text, spawn) => {
1157
+ if (deps.makeSubscriptionReviewLLM) return deps.makeSubscriptionReviewLLM(spawn)(text);
1158
+ const { makeAcpReviewLLM } = await import('../agent-substrate/acp-reviewer.js');
1159
+ return makeAcpReviewLLM({ cwd: targetOptions?.repoRoot ?? childScope.elanousBinRoot, backend: 'claude', backendSpec: spawn.backendSpec, env: spawn.env })(text);
1160
+ }, process.env, deps.subscriptionReviewerDeps);
1161
+ reviewerCanSelfRead = undefined;
1162
+ }
1163
+ }
1146
1164
  const opensPr = plan.completion !== 'worktree-only';
1147
1165
  const target = plan.target === undefined ? undefined : revalidateHarnessTarget(plan.target);
1148
1166
  if (target?.status === 'outside-home') {
@@ -1159,6 +1177,7 @@ export async function buildDefaultSelfImplementSeams(
1159
1177
  ...(plan.self?.maxWaitSec !== undefined ? { implementMaxWaitSec: plan.self.maxWaitSec } : {}),
1160
1178
  ...(plan.self?.activityGraceSec !== undefined ? { implementActivityGraceSec: plan.self.activityGraceSec } : {}),
1161
1179
  llmReview,
1180
+ ...(subscriptionReviewLLM ? { subscriptionReviewLLM } : {}),
1162
1181
  ...(reviewerCanSelfRead !== undefined ? { reviewerCanSelfRead } : {}),
1163
1182
  ...(deps.reviewScopeDiff ? { reviewScopeDiff: deps.reviewScopeDiff } : {}),
1164
1183
  onProgress: ({ stage, message }) => {