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
@@ -16,6 +16,7 @@ import { createGitHubIssueCommentFetchStatus, lintDocLinks, lintGitHubIssueComme
16
16
  import { proposeWikiClaimsFromHandoff, type Claim } from '../src/autopilot/discovery/wiki-claim-proposer.js';
17
17
  import { runNightlyDocOpsCycle, assertLoopbackModelUrl } from '../src/autopilot/discovery/nightly-docops-runner.js';
18
18
  import { assessDocsStaleness, defaultStalenessRevisions } from '../src/autopilot/discovery/doc-staleness.js';
19
+ import { defaultManualFiles, scanDocRot } from './docs/doc-rot.js';
19
20
  import { debug } from '../src/debug/log.js';
20
21
  import { registerStandaloneLogSink } from '../src/domains/standalone-log-sink.js';
21
22
  import {
@@ -51,13 +52,27 @@ const githubHeaders: Record<string, string> = { Accept: 'application/vnd.github+
51
52
  if (process.env.GITHUB_TOKEN) githubHeaders.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
52
53
  const issueCommentFetchStatus = createGitHubIssueCommentFetchStatus(fetch, githubHeaders);
53
54
 
54
- export function formatNightlyDocOpsCompletion(metrics: Awaited<ReturnType<typeof runNightlyDocOpsCycle>>): string {
55
+ export type DocRotStageResult =
56
+ | { status: 'measured'; totals: ReturnType<typeof scanDocRot>['totals']; staleFiles: Array<{
57
+ file: string;
58
+ missingPaths: ReturnType<typeof scanDocRot>['files'][number]['missingPaths'];
59
+ badCommands: ReturnType<typeof scanDocRot>['files'][number]['commands'];
60
+ }> }
61
+ | { status: 'failed'; error: string };
62
+
63
+ export function formatNightlyDocOpsCompletion(
64
+ metrics: Awaited<ReturnType<typeof runNightlyDocOpsCycle>>,
65
+ docRot?: DocRotStageResult,
66
+ ): string {
55
67
  const stalenessLine = metrics.staleness?.status === 'measured'
56
68
  ? `늙음 검사 ${metrics.staleness.checked}·사라진 식별자 ${metrics.staleness.byAxis.removedIdentifiers}·깨진 링크 ${metrics.staleness.byAxis.brokenLinks}·superseded ${metrics.staleness.byAxis.supersededMarked}·staleScore ${metrics.staleness.byAxis.staleScoreOverThreshold}·문서목록 ${metrics.staleness.removedIdentifierDocuments.length}${metrics.staleness.removedIdentifierDocumentsTruncated ? '+' : ''}`
57
69
  : metrics.staleness?.status === 'failed'
58
70
  ? `늙음 측정 실패(${metrics.staleness.error})`
59
71
  : '늙음 측정 미지정';
60
- return `[nightly-docops] 완료 — 처리 ${metrics.processed}·후보 ${metrics.candidates}·제안 ${metrics.proposals}·억제 ${metrics.suppressed}·에러 ${metrics.errors}·${stalenessLine}·${metrics.durationMs}ms → ${outDir} (HITL 승인 대기)`;
72
+ const docRotLine = docRot?.status === 'measured'
73
+ ? `·doc-rot 낡은 파일 ${docRot.totals.staleFiles}·없는 경로 ${docRot.totals.missingPaths}·깨진 명령 ${docRot.totals.badCommands}·못 잰 명령 ${docRot.totals.unmeasured}`
74
+ : docRot?.status === 'failed' ? `·doc-rot 측정 실패(${docRot.error})` : '';
75
+ return `[nightly-docops] 완료 — 처리 ${metrics.processed}·후보 ${metrics.candidates}·제안 ${metrics.proposals}·억제 ${metrics.suppressed}·에러 ${metrics.errors}·${stalenessLine}${docRotLine}·${metrics.durationMs}ms → ${outDir} (HITL 승인 대기)`;
61
76
  }
62
77
 
63
78
  /**
@@ -102,6 +117,76 @@ export function stalenessEventName(staleness: { status: string } | undefined): s
102
117
  return staleness.status === 'failed' ? 'staleness-failed' : 'staleness-measured';
103
118
  }
104
119
 
120
+ export function buildDocRotStage(
121
+ repoRoot: string,
122
+ deps: { scan?: typeof scanDocRot; files?: () => string[] } = {},
123
+ ): () => DocRotStageResult {
124
+ const scan = deps.scan ?? scanDocRot;
125
+ const files = deps.files ?? (() => defaultManualFiles(repoRoot));
126
+ return () => {
127
+ try {
128
+ const report = scan({ repoRoot, files: files() });
129
+ return {
130
+ status: 'measured',
131
+ totals: report.totals,
132
+ staleFiles: report.files.flatMap((entry) => {
133
+ const badCommands = entry.commands.filter((finding) =>
134
+ finding.kind === 'unknown-command' || finding.kind === 'unknown-subcommand' || finding.kind === 'unknown-flag');
135
+ return entry.missingPaths.length || badCommands.length
136
+ ? [{ file: entry.file, missingPaths: entry.missingPaths, badCommands }]
137
+ : [];
138
+ }),
139
+ };
140
+ } catch (error) {
141
+ return { status: 'failed', error: error instanceof Error ? error.message : String(error) };
142
+ }
143
+ };
144
+ }
145
+
146
+ export interface DocRotRecordDeps {
147
+ outDir: string;
148
+ nowDate: string;
149
+ mkdir?: (dir: string) => void;
150
+ write?: (path: string, body: string) => void;
151
+ log?: (category: string, event: string, data: Record<string, unknown>) => void;
152
+ }
153
+
154
+ /**
155
+ * doc-rot 단계 결과를 ⑴ `doc-rot-<날짜>.json` 보고서 ⑵ `docs.rot` 관측 이벤트로 남긴다.
156
+ * 보고서·관측 어느 쪽이 실패해도 던지지 않는다 — 야간 사이클의 종료 코드를 바꾸지 않는다.
157
+ */
158
+ export function recordDocRotStage(
159
+ docRot: DocRotStageResult,
160
+ deps: DocRotRecordDeps,
161
+ ): { reportPath: string | null; writeError: string | null } {
162
+ const mkdir = deps.mkdir ?? ((dir: string) => { mkdirSync(dir, { recursive: true }); });
163
+ const write = deps.write ?? ((path: string, body: string) => { writeFileSync(path, body); });
164
+ const log = deps.log ?? ((category: string, event: string, data: Record<string, unknown>) => { debug.log(category, event, data); });
165
+ const reportPath = join(deps.outDir, `doc-rot-${deps.nowDate}.json`);
166
+ let writeError: string | null = null;
167
+ try {
168
+ mkdir(deps.outDir);
169
+ write(reportPath, JSON.stringify(docRot, null, 2));
170
+ } catch (error) {
171
+ // 보고서 파일을 못 써도 측정 결과는 버리지 않는다 — 원인은 관측 이벤트에 따로 싣는다.
172
+ writeError = error instanceof Error ? error.message : String(error);
173
+ }
174
+ try {
175
+ const totals = docRot.status === 'measured' ? docRot.totals : null;
176
+ log('docs.rot', docRot.status === 'measured' ? 'nightly-scanned' : 'nightly-failed', {
177
+ files: totals?.files ?? null,
178
+ staleFiles: totals?.staleFiles ?? null,
179
+ missingPaths: totals?.missingPaths ?? null,
180
+ badCommands: totals?.badCommands ?? null,
181
+ unmeasured: totals?.unmeasured ?? null,
182
+ error: docRot.status === 'failed' ? docRot.error : null,
183
+ reportPath: writeError ? null : reportPath,
184
+ writeError,
185
+ });
186
+ } catch { /* 관측 실패가 야간 사이클의 종료 코드를 바꾸지 않는다 */ }
187
+ return { reportPath: writeError ? null : reportPath, writeError };
188
+ }
189
+
105
190
  if (import.meta.main) {
106
191
  await registerStandaloneLogSink('scheduler');
107
192
  mkdirSync(ckDir, { recursive: true });
@@ -156,7 +241,9 @@ const metrics = await runNightlyDocOpsCycle({
156
241
  },
157
242
  }, { batchLimit: 15, maxRetryPerItem: 1 });
158
243
 
159
- console.log(formatNightlyDocOpsCompletion(metrics));
244
+ const docRot = buildDocRotStage(repoRoot)();
245
+ recordDocRotStage(docRot, { outDir, nowDate });
246
+ console.log(formatNightlyDocOpsCompletion(metrics, docRot));
160
247
  }
161
248
 
162
249
  // ── arc2 되살리기 · 의미 supersede 계층 (gemma-4 · 제목/토픽 클러스터 bounded · 미션 668871) ──
@@ -20,6 +20,7 @@ import { runPodCommand, parsePodCpu, resolvePodCpu, type RunPodCommandOptions, t
20
20
  import { POD_BUN_CACHE_HOST_PATH, parseInstallSeconds, podBunCacheVolume } from '../../src/task-orchestrator/surfaces/pod-bun-cache.js';
21
21
  import { PodPoolScheduler, parsePodPool, checkPodPool } from '../../src/task-orchestrator/surfaces/pod-pool.js';
22
22
  import { POD_INSTALL_SLOTS_DEFAULT, POD_INSTALL_SLOTS_HOST_PATH, installSlotScript } from '../../src/task-orchestrator/surfaces/pod-install-slots.js';
23
+ import { gateDepsInstallScript, parseGateDeps } from '../../src/task-orchestrator/surfaces/pod-gate-deps.js';
23
24
  import { gateShardsPath, writeGateShards, type GateShard } from '../../src/release-loop/gate-shards.js';
24
25
  import { defaultKubectl } from '../../src/task-orchestrator/surfaces/self-implement-pod.js';
25
26
 
@@ -253,7 +254,7 @@ export function shardParts(paths: readonly string[], seconds: (file: string) =>
253
254
  * leaving `install.log`/`install.rc`, `part-<i>.log`/`.rc`/`.junit.xml`, and `shard.timeout` (the part index) when the
254
255
  * deadline cut the shard. A part without `.rc` did not finish. The shell itself always exits 0 (as before).
255
256
  */
256
- export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPatterns: readonly string[]; softSeconds: number; cachePrefix: string; installSlots: number }): string {
257
+ export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPatterns: readonly string[]; softSeconds: number; cachePrefix: string; installSlots: number; depsDir?: string }): string {
257
258
  const run = o.parts.map((part, index) => {
258
259
  const ignores = o.cdpPatterns.filter((pattern) => part.includes(pattern)).flatMap((pattern) => ['--path-ignore-patterns', pattern]);
259
260
  return `run_part ${index} ${['bun', 'run', 'test:deterministic', ...ignores, ...part.map(asPath)].map(quote).join(' ')}`;
@@ -276,9 +277,8 @@ export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPa
276
277
  ' echo "$prc" > "$O/part-$i.rc"',
277
278
  '}',
278
279
  'if cd .. && cd repo; then',
279
- ...(o.installSlots > 0 ? [' install_slot_acquire >> "$O/install.log" 2>&1'] : []),
280
- ' { bun install && (cd apps/pwa && bun install); } >> "$O/install.log" 2>&1; irc=$?',
281
- ...(o.installSlots > 0 ? [' install_slot_release'] : []),
280
+ // GATE-IMAGE-DEPS: link the image's baked node_modules when the lockfiles match; only a miss installs (behind a slot).
281
+ ...gateDepsInstallScript({ log: '"$O/install.log"', installSlots: o.installSlots, ...(o.depsDir ? { depsDir: o.depsDir } : {}) }).split('\n').map((line) => ` ${line}`),
282
282
  ' echo "$irc" > "$O/install.rc"',
283
283
  ` if [ "$irc" -eq 0 ]; then ${run || 'true'}; fi`,
284
284
  'else echo 5 > "$O/install.rc"; fi',
@@ -800,7 +800,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
800
800
  }
801
801
  const installSeconds = parseInstallSeconds(output ?? '');
802
802
  // GATE-TIMEOUT-HEAL ③: a cut shard is «reason: timeout» (with the files that still finished), not a bare rc=null.
803
- if (!reused && !descent) debug.log('release-loop.gate', 'pod-shard', { shard, files: paths, durationMs, rc: rc ?? null, attempt: depth + 1, installSeconds, deadlineSeconds: shardDeadline, parts: parts.length,
803
+ if (!reused && !descent) debug.log('release-loop.gate', 'pod-shard', { shard, files: paths, durationMs, rc: rc ?? null, attempt: depth + 1, installSeconds, gateDeps: parseGateDeps(output ?? ''), deadlineSeconds: shardDeadline, parts: parts.length,
804
804
  ...(timedOut ? { reason: 'timeout', finishedFiles: finished.length } : {}) });
805
805
  if (!reused && !descent) markShard(shard, { ...(installSeconds !== null ? { installSec: installSeconds } : {}), ...(rc !== undefined ? { rc } : {}), ...(timedOut ? { state: 'timeout' as const } : {}) });
806
806
  let clean = output?.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
@@ -1,15 +1,60 @@
1
- import { readdirSync, readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
1
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
2
+ import { basename, dirname, join, resolve } from 'node:path';
3
3
  import { debug } from '../../src/debug/log.js';
4
4
 
5
5
  /** Plan weights for a Pod sweep: wall-scaled when the previous cut recorded shard wall times, junit seconds otherwise. */
6
- export function planDurations(durationSource: string | undefined): { durations: Map<string, number>; wall?: { shards: number; scaledFiles: number } } {
6
+ export function planDurations(durationSource: string | undefined): { durations: Map<string, number>; wall?: { shards: number; scaledFiles: number }; fallback?: string } {
7
7
  if (!durationSource) return { durations: new Map() };
8
- const junit = readFileDurations(durationSource);
9
- const wall = readFileWallWeights(durationSource, junit);
8
+ const primary = planDurationsFrom(durationSource);
9
+ if (primary.durations.size) return primary;
10
+ // GATE-PLAN-TIMINGS: the baseline cut left no reports (0.2.20: baseline 0.2.19 was gated from another universe, so
11
+ // all 5,585 files were «unknown», planned 3.9 min, measured 13–20 min). Use the most recent sibling cut instead.
12
+ const fallback = latestSiblingCutDir(durationSource);
13
+ if (!fallback) {
14
+ debug.log('release-loop.gate', 'plan-duration-fallback', { source: durationSource, fallback: null });
15
+ return primary;
16
+ }
17
+ const result = planDurationsFrom(fallback);
18
+ debug.log('release-loop.gate', 'plan-duration-fallback', { source: durationSource, fallback, files: result.durations.size, wall: !!result.wall });
19
+ return result.durations.size ? { ...result, fallback } : primary;
20
+ }
21
+
22
+ function planDurationsFrom(dir: string): { durations: Map<string, number>; wall?: { shards: number; scaledFiles: number } } {
23
+ const junit = readFileDurations(dir);
24
+ const wall = readFileWallWeights(dir, junit);
10
25
  return wall ? { durations: wall.weights, wall: { shards: wall.shards, scaledFiles: wall.scaledFiles } } : { durations: junit };
11
26
  }
12
27
 
28
+ /**
29
+ * For a `<root>/<version>/<a>/<b>` duration source (the gate passes `<ledger>/release/<baseline>/gate-logs/cut`), the
30
+ * sibling `<root>/<other>/<a>/<b>` directory whose newest `*.junit.xml` is the most recent — «the latest gate». The
31
+ * source itself is skipped. Undefined when the path is too shallow or no sibling holds a junit report.
32
+ */
33
+ export function latestSiblingCutDir(durationSource: string): string | undefined {
34
+ const source = resolve(durationSource);
35
+ const b = basename(source);
36
+ const a = basename(dirname(source));
37
+ const root = dirname(dirname(dirname(source)));
38
+ if (!a || !b || root === dirname(dirname(source))) return undefined;
39
+ let versions: string[];
40
+ try { versions = readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name); }
41
+ catch { return undefined; }
42
+ let best: { dir: string; mtimeMs: number } | undefined;
43
+ for (const version of versions.sort()) {
44
+ const dir = join(root, version, a, b);
45
+ if (dir === source) continue;
46
+ let newest = -1;
47
+ try {
48
+ for (const name of readdirSync(dir)) {
49
+ if (!name.endsWith('.junit.xml')) continue;
50
+ try { newest = Math.max(newest, statSync(join(dir, name)).mtimeMs); } catch { /* vanished mid-scan */ }
51
+ }
52
+ } catch { continue; }
53
+ if (newest >= 0 && (!best || newest > best.mtimeMs)) best = { dir, mtimeMs: newest };
54
+ }
55
+ return best?.dir;
56
+ }
57
+
13
58
  /** Observation for GATE-PLAN-WALLTIME, emitted once the shards are planned so `maxPlannedMin` is the heaviest shard. */
14
59
  export function logPlanWalltime(wall: { shards: number; scaledFiles: number } | undefined, shards: ReadonlyArray<{ plannedSeconds: number }>): void {
15
60
  if (!wall) return;
@@ -0,0 +1,39 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { searchFirecrawl } from '../skills/omni-crawl/src/firecrawl.js';
3
+
4
+ type Check = { identifier: string; query: string; inspectedLead: { url: string } | null };
5
+ type Search = (query: string, options: { limit: number }) => Promise<{ items: Array<{ url?: string }> }>;
6
+
7
+ export async function recheckCooAdminLeads(checks: Check[], search: Search): Promise<string[]> {
8
+ let verified = 0;
9
+ let withUrl = 0;
10
+ let withoutUrl = 0;
11
+ const lines: string[] = [];
12
+ for (const check of checks) {
13
+ const result = await search(check.query, { limit: 3 });
14
+ const recordedUrl = check.inspectedLead?.url?.trim();
15
+ if (!recordedUrl) {
16
+ withoutUrl++;
17
+ lines.push(`${check.identifier} hits=${result.items.length} recordedUrl=none (not verified) query=${check.query}`);
18
+ continue;
19
+ }
20
+ withUrl++;
21
+ const found = result.items.some(item => item.url === recordedUrl);
22
+ if (found) verified++;
23
+ lines.push(`${check.identifier} hits=${result.items.length} recordedUrlVerified=${found} query=${check.query}`);
24
+ }
25
+ lines.push(`SEARCH checked=${checks.length} recordedUrlVerified=${verified}/${withUrl} noRecordedUrl=${withoutUrl} recordedUrlNotInCurrentTop3=${withUrl - verified} (search rankings change; no official date inferred)`);
26
+ return lines;
27
+ }
28
+
29
+ if (import.meta.main) {
30
+ const evidence = JSON.parse(await readFile(new URL('../src/domains/coo-admin-dates.evidence.json', import.meta.url), 'utf8')) as {
31
+ verifiedIssues: Array<{ identifier: string }>;
32
+ research: { issueChecks: Check[] };
33
+ };
34
+ const checks = evidence.research.issueChecks;
35
+ if (checks.length !== 29 || checks.some((check, index) => check.identifier !== evidence.verifiedIssues[index]?.identifier || !check.query)) {
36
+ throw new Error('29 issue search queries do not match recorded issue identifiers');
37
+ }
38
+ for (const line of await recheckCooAdminLeads(checks, searchFirecrawl)) console.log(line);
39
+ }
@@ -587,8 +587,9 @@ export async function main(args: string[] = process.argv.slice(2), deps: DailyDe
587
587
  for (const arg of args) if (!['--dry-run', '--no-news', '--json'].includes(arg)) throw new Error(`알 수 없는 옵션: ${arg}`);
588
588
  const result = await runDaily({ dryRun: args.includes('--dry-run'), noNews: args.includes('--no-news'), stage }, deps);
589
589
  (deps.print ?? console.log)(args.includes('--json') ? JSON.stringify(result) : `${result.markdown}\n파일: ${result.file} · ${result.deliveryLine}`);
590
- // 발송 설정 OFF 는 degraded 로 기록하고 done 으로 진행한다. 실제 발송 실패·볼트 사본 실패는 그래프를 멈춘다.
590
+ // 그래프 deliver 는 미발송도 출력·tick 에 기록한 뒤 실패로 종결한다.
591
591
  if ((stage === 'compose' || !stage) && result.vaultFatal) throw new Error(`볼트 사본 실패 · ${result.vaultError}`);
592
+ if (stage === 'deliver' && result.deliveryState === 'send-disabled') throw new Error('발송 실패 · 발송 비활성');
592
593
  if (stage === 'deliver' && result.sendError) throw new Error(`발송 실패 · ${result.sendError}`);
593
594
  return result;
594
595
  }
@@ -140,10 +140,15 @@ export function checkPublicSkills(root: string, boundary: SkillBoundaryReport =
140
140
  const errors = [...boundary.errors];
141
141
  const skills: PublicSkillRow[] = [];
142
142
  const skillsDir = join(root, 'skills');
143
- const addon = new Set(boundary.skills.filter((row) => row.verdict === 'addon').map((row) => row.skill));
143
+ // Scope discrepancy: the stated 15 public deployments have no named fifteenth
144
+ // skill in this tree's boundary inventory (14 core, 5 addons). No verified
145
+ // deployment-name list is available here to justify adding a row. Never use
146
+ // an addon to fill the gap; keep the existing report shape and exit semantics.
147
+ // Unclassified folders retain their check rows and boundary errors.
148
+ const verdicts = new Map(boundary.skills.map((row) => [row.skill, row.verdict]));
144
149
  const names = existsSync(skillsDir) ? readdirSync(skillsDir).filter((name) => {
145
150
  const dir = join(skillsDir, name);
146
- return lstatSync(dir).isDirectory() && !addon.has(name);
151
+ return lstatSync(dir).isDirectory() && verdicts.get(name) !== 'addon';
147
152
  }).sort() : [];
148
153
  if (!existsSync(skillsDir)) errors.push('skills/ 디렉터리가 없다');
149
154
  for (const row of boundary.skills) {
@@ -8,7 +8,6 @@ import {
8
8
  defaultGitMergeSeam, defaultLlmResolve, mergeMainWithLlmResolve,
9
9
  type MergeGitSeam,
10
10
  } from '../../src/autopilot/build/llm-conflict-merge.js';
11
- import { NEXT_MD_PATH } from '../../src/release-loop/next-md-merge.js';
12
11
 
13
12
  type Command = (cwd: string, program: 'git' | 'gh', args: readonly string[]) => { status: number; stdout: string; stderr: string };
14
13
  export type ShadowOutcome = 'clean' | 'resolved-deterministic' | 'resolved-llm' | 'unresolved' | 'unmeasured';
@@ -108,9 +107,12 @@ export async function runResyncShadow(options: ShadowOptions): Promise<ShadowRow
108
107
  const result = await mergeMainWithLlmResolve(temp, 'origin/main',
109
108
  options.resolve ?? ((file, conflicted) => defaultLlmResolve(file, conflicted, 'origin/main', { worktreePath: temp })), observedGit);
110
109
  if (result.status === 'merged' || result.status === 'up-to-date') outcome = 'clean';
111
- else if (result.status === 'llm-resolved') {
110
+ else if (result.status === 'deterministic-resolved') {
112
111
  files = result.resolvedFiles ?? files;
113
- outcome = files.length > 0 && files.every((file) => file === NEXT_MD_PATH) ? 'resolved-deterministic' : 'resolved-llm';
112
+ outcome = 'resolved-deterministic';
113
+ } else if (result.status === 'llm-resolved') {
114
+ files = result.resolvedFiles ?? files;
115
+ outcome = 'resolved-llm';
114
116
  } else if (result.status === 'conflict-unresolved') outcome = 'unresolved';
115
117
  // A git error is a measurement failure, not evidence of an unresolvable conflict.
116
118
  } catch {
@@ -1,8 +1,88 @@
1
1
  // TD2 — a daily look at the test suite, one cost-weighted slice at a time (shadow: it measures and drafts, it
2
2
  // never deletes or moves a test). Design: 내부 문서 `TD1-v2-gate-content-review-2026-10-01`.
3
- import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
5
 
6
+ /** These were reported as preexisting in the recent gate/harvest; remeasurement, not this list, decides today's count. */
7
+ export const DEFAULT_MAIN_FAILURE_FILES = [
8
+ 'src/task-orchestrator/surfaces/pod-pool-cli.test.ts',
9
+ 'src/self-dev/dev-cli.test.ts',
10
+ 'src/self-dev/dev-pipeline.test.ts',
11
+ 'src/agent-mission/agent-backend-flag-smoke.test.ts',
12
+ 'scripts/release-story/draft.test.ts',
13
+ 'scripts/release-loop/freeze-off-parity.test.ts',
14
+ ] as const;
15
+
16
+ export type MainFailureClass = '시험이 낡음' | '코드 결함' | '환경·부하 의존' | '외부 의존';
17
+ export interface MainFailureResult { file: string; rc: number | null; stdout: string; stderr: string; timedOut?: boolean }
18
+ export interface MainFailureRow { file: string; failures: number; firstError: string; classification: MainFailureClass; draft: '고침' | '격리' }
19
+ export interface MainFailureDay { date: string; at: string; owner: 'TC'; mode: 'shadow'; commit: string; selected: string[]; rows: MainFailureRow[]; unmeasured: { file: string; reason: string }[]; failingFiles: number; previousFailingFiles: number | null; delta: number | null }
20
+
21
+ export function mainFailureLedgerPath(root: string, date: string): string { return join(root, 'test-diet', 'main-failures', `${date}.json`); }
22
+ export function kstDay(at: Date): string { return new Date(at.getTime() + 9 * 3600_000).toISOString().slice(0, 10); }
23
+
24
+ export function mainFailureFiles(value: unknown): string[] {
25
+ const files = value === undefined ? [...DEFAULT_MAIN_FAILURE_FILES] : value;
26
+ if (!Array.isArray(files) || files.length === 0 || !files.every((file) => typeof file === 'string' && /^(?:src|scripts|test)\/[A-Za-z0-9_./-]+\.test\.tsx?$/.test(file) && !file.split('/').includes('..'))) {
27
+ throw new Error('main failure files must be nonempty repository-relative test paths');
28
+ }
29
+ return [...new Set(files as string[])];
30
+ }
31
+
32
+ /** A heuristic draft, never a finding about the root cause or permission to skip a test. */
33
+ export function mainFailureRow(result: MainFailureResult): MainFailureRow | null {
34
+ const text = `${result.stdout}\n${result.stderr}`.replace(/\x1b\[[0-9;]*m/g, '');
35
+ const count = [...text.matchAll(/^\s*(\d+) fail\b/gm)].at(-1);
36
+ const failures = count ? Number(count[1]) : 0;
37
+ const ran = /Ran [1-9]\d* tests? across [1-9]\d* files?/m.test(text);
38
+ if (result.timedOut || !ran || !Number.isSafeInteger(failures) || (result.rc !== 0 && failures === 0)
39
+ || (result.rc === 0 && failures > 0) || /^(?:\s*[1-9]\d* errors?\b|# Unhandled error between tests)/m.test(text)) {
40
+ throw new Error(`test not measured: ${result.file}: ${text.split('\n').map((line) => line.trim()).find((line) => /error:|Error:|timed out|timeout|Cannot find|not found|errors?\b/i.test(line)) ?? 'no completed test result'}`);
41
+ }
42
+ if (failures === 0 && result.rc === 0) return null;
43
+ const firstError = text.split('\n').map((line) => line.trim()).find((line) => /^(?:\(fail\)|error:|\w*Error:|Expected:|Received:|Cannot find|Unable to)/i.test(line))
44
+ ?? `test exited ${result.rc} without a diagnostic`;
45
+ const classification: MainFailureClass = /timed out|timeout|load average|resource temporarily unavailable/i.test(firstError) ? '환경·부하 의존'
46
+ : /codex|--yolo|network|ECONN|API key|Unable to|Cannot find module|ENOENT/i.test(firstError) ? '외부 의존'
47
+ : /snapshot|next\.md|expected|Received|toEqual|toContain/i.test(firstError) ? '시험이 낡음' : '코드 결함';
48
+ return { file: result.file, failures, firstError: firstError.slice(0, 240), classification,
49
+ draft: classification === '환경·부하 의존' || classification === '외부 의존' ? '격리' : '고침' };
50
+ }
51
+
52
+ /** One KST-day snapshot, replaced on remeasurement. Incomplete runs cannot masquerade as zero failures. */
53
+ export function writeMainFailureDay(root: string, selected: string[], results: MainFailureResult[], commit: string, at = new Date()): MainFailureDay {
54
+ if (results.length !== selected.length || results.some((r, i) => r.file !== selected[i])) throw new Error('incomplete main failure measurement');
55
+ const date = kstDay(at);
56
+ const dir = join(root, 'test-diet', 'main-failures');
57
+ mkdirSync(dir, { recursive: true });
58
+ const dates = earlierMainFailureDays(dir, date);
59
+ const previous = dates.at(-1);
60
+ const previousDay = previous ? JSON.parse(readFileSync(join(dir, previous), 'utf8')) as MainFailureDay : null;
61
+ const rows: MainFailureRow[] = [];
62
+ const unmeasured: MainFailureDay['unmeasured'] = [];
63
+ for (const result of results) {
64
+ try {
65
+ const row = mainFailureRow(result);
66
+ if (row) rows.push(row);
67
+ } catch (error) {
68
+ // 못 잰 파일은 «실패»가 아니다 — 행으로 접으면 측정 실패가 main 결함 1건으로 둔갑한다.
69
+ unmeasured.push({ file: result.file, reason: (error instanceof Error ? error.message : String(error)).slice(0, 240) });
70
+ }
71
+ }
72
+ const previousFailingFiles = unmeasured.length === 0 && previousDay?.unmeasured?.length === 0
73
+ && previousDay.selected.join('\0') === selected.join('\0') ? previousDay.failingFiles : null;
74
+ const line: MainFailureDay = { date, at: at.toISOString(), owner: 'TC', mode: 'shadow', commit, selected, rows, unmeasured, failingFiles: rows.length,
75
+ previousFailingFiles, delta: previousFailingFiles === null ? null : rows.length - previousFailingFiles };
76
+ const path = mainFailureLedgerPath(root, date);
77
+ writeFileSync(`${path}.${process.pid}.tmp`, JSON.stringify(line, null, 2) + '\n');
78
+ renameSync(`${path}.${process.pid}.tmp`, path);
79
+ return line;
80
+ }
81
+
82
+ function earlierMainFailureDays(dir: string, date: string): string[] {
83
+ return readdirSync(dir).filter((name) => /^\d{4}-\d{2}-\d{2}\.json$/.test(name) && name < `${date}.json`).sort();
84
+ }
85
+
6
86
  export const DEFAULT_COST_SECS = 10;
7
87
  export const SLOW_SECS = 60;
8
88
  export const HEAVY_MB = 2048;
@@ -3,13 +3,15 @@
3
3
  // bun scripts/test-diet/node.ts pick|measure|audit|record (test-diet or nightly-audit graph)
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import { createHash } from 'node:crypto';
6
- import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
6
+ import { appendFileSync, existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
7
+ import { tmpdir } from 'node:os';
7
8
  import { join } from 'node:path';
8
9
  import { debug } from '../../src/debug/log.js';
9
10
  import { effectiveInstanceRoot } from '../../src/instance/resolve.js';
10
11
  import { CardStore } from '../../src/task-cards/card-store.js';
11
12
  import { GATE_NIGHTLY_AUDITS } from '../release-loop/gate-node.js';
12
- import { appendLedger, costTable, judge, lastLedgerLine, ledgerPath, nightlyAuditLedgerPath, pickRange, propose, td1Dispositions, writeCardDraft, type LedgerLine, type Measurement, type Td1Disposition } from './lib.js';
13
+ import { isCredentialKey } from '../lib/deterministic-env.js';
14
+ import { appendLedger, costTable, judge, lastLedgerLine, ledgerPath, mainFailureFiles, nightlyAuditLedgerPath, pickRange, propose, td1Dispositions, writeCardDraft, writeMainFailureDay, type LedgerLine, type MainFailureResult, type Measurement, type Td1Disposition } from './lib.js';
13
15
  import { fixedCountAssertions, flakeVerdict, sweepSlice, unreachableImports, type Mutation, type TestRun } from './effectiveness.js';
14
16
 
15
17
  type Context = { graphId?: string; input: Record<string, unknown>; outputs: Record<string, Record<string, unknown> | null> };
@@ -235,6 +237,49 @@ export function record(ctx: Context, deps: { createStore?: (root: string) => Pic
235
237
  return emit({ outcome: 'ok', summary: `test-diet ${line.range} · keep ${counts.keep} · review ${counts.review} · failing ${counts.failing}${card ? ' · card draft' : ''}`, range: line.range, ...counts, ...(card ? { card } : {}) });
236
238
  }
237
239
 
240
+ export function mainFailures(ctx: Context, deps: { root?: string; checkout?: string; runFile?: (file: string, cwd: string) => MainFailureResult; now?: Date } = {}): number {
241
+ const selected = mainFailureFiles(ctx.input.files);
242
+ const root = deps.root ?? effectiveInstanceRoot();
243
+ const guarded = process.env.NODE_ENV === 'test' || !!process.env.ELANOUS_TEST_HOME;
244
+ const directory = deps.checkout || guarded ? undefined : mkdtempSync(join(tmpdir(), 'test-diet-main-'));
245
+ try {
246
+ let checkout = deps.checkout ?? directory ?? '';
247
+ let commit: string;
248
+ if (guarded) {
249
+ // No clone, install, test subprocess or harness dispatch in the test universe.
250
+ const at = deps.now ?? new Date();
251
+ const ledger = join(root, 'test-diet', 'main-failures', `${at.toISOString().slice(0, 10)}-guard.json`);
252
+ mkdirSync(join(root, 'test-diet', 'main-failures'), { recursive: true });
253
+ writeFileSync(ledger, JSON.stringify({ at: at.toISOString(), selected, measured: false, reason: 'test environment', owner: 'TC', mode: 'shadow' }) + '\n');
254
+ return emit({ outcome: 'ok', summary: 'main failures record only: test environment (not measured)', ledger, selected, measured: false });
255
+ } else if (deps.checkout) {
256
+ const head = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: checkout, encoding: 'utf8' });
257
+ if (head.status !== 0) throw new Error(`main checkout unreadable: ${checkout}`);
258
+ commit = head.stdout.trim();
259
+ } else {
260
+ const source = process.cwd();
261
+ const clone = spawnSync('git', ['clone', '--quiet', '--shared', '--single-branch', '--branch', 'main', source, checkout], { encoding: 'utf8', timeout: 120_000 });
262
+ if (clone.status !== 0) return emit({ outcome: 'error', summary: `main checkout failed: ${clone.stderr || clone.error}` });
263
+ commit = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: checkout, encoding: 'utf8' }).stdout.trim();
264
+ const install = spawnSync('bun', ['install', '--frozen-lockfile'], { cwd: checkout, encoding: 'utf8', timeout: 300_000, maxBuffer: 1024 * 1024 });
265
+ if (install.status !== 0) return emit({ outcome: 'error', summary: `main install failed: ${install.stderr || install.error}` });
266
+ }
267
+ const runFile = deps.runFile ?? ((file: string, cwd: string): MainFailureResult => {
268
+ if (!existsSync(join(cwd, file))) return { file, rc: null, stdout: '', stderr: 'test file not present on main' };
269
+ const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !isCredentialKey(key)));
270
+ const child = spawnSync('bun', ['run', 'test:deterministic', file], {
271
+ cwd, encoding: 'utf8', timeout: 300_000, maxBuffer: 4 * 1024 * 1024,
272
+ env: { ...env, NODE_ENV: 'test', ELANOUS_STATE_DIR: join(root, 'test-diet', 'isolated-test-state') },
273
+ });
274
+ return { file, rc: child.status, stdout: child.stdout ?? '', stderr: child.stderr ?? String(child.error ?? ''), timedOut: (child.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT' };
275
+ });
276
+ const results = selected.map((file) => runFile(file, checkout));
277
+ const day = writeMainFailureDay(root, selected, results, commit, deps.now);
278
+ debug.log('test-diet.main-failures', 'recorded', { selected: selected.length, failingFiles: day.failingFiles, delta: day.delta, guarded });
279
+ return emit({ outcome: 'ok', summary: `main failures ${day.failingFiles}/${selected.length} · delta ${day.delta ?? 'n/a'} · shadow TC`, ledger: join(root, 'test-diet', 'main-failures', `${day.date}.json`), ...day });
280
+ } finally { if (directory) rmSync(directory, { recursive: true, force: true }); }
281
+ }
282
+
238
283
  function status(root: string, options: { json?: boolean; days?: number } = {}): number {
239
284
  const path = ledgerPath(root);
240
285
  const ledger = existsSync(path) ? readFileSync(path, 'utf8') : '';
@@ -286,6 +331,7 @@ if (import.meta.main) {
286
331
  const ctx = context();
287
332
  const flags = process.argv.slice(3);
288
333
  process.exitCode = step === 'pick' ? pick(ctx, flags.includes('--sweep')) : step === 'measure' ? measure(ctx, false, flags.includes('--effectiveness')) : step === 'audit' ? measure(ctx, true)
334
+ : step === 'main-failures' ? mainFailures(ctx)
289
335
  : step === 'record' ? (ctx.graphId === 'nightly-audit' || Object.hasOwn(ctx.outputs, 'audit') ? recordNightlyAudit(ctx) : record(ctx)) : emit({ outcome: 'error', summary: `unknown step: ${step}` });
290
336
  }
291
337
  } catch (error) {
@@ -0,0 +1,83 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { fetchLinearProjectIssues, type LinearProjectIssue } from '../src/connectors/linear.js';
3
+
4
+ type RecordedIssue = {
5
+ identifier: string; title: string; url: string; createdAt: string; updatedAt: string;
6
+ existingDueDate: string | null; priority: number;
7
+ };
8
+ type Evidence = {
9
+ project: string;
10
+ verifiedIssues: RecordedIssue[];
11
+ cohort: { excludedCurrentIssues: Array<{identifier: string; url: string; dueDate: string | null; priority: number; createdAt: string}> };
12
+ };
13
+ type LiveIssue = LinearProjectIssue & { createdAt: string };
14
+
15
+ export function compareCooEvidence(record: Evidence, live: LiveIssue[], truncated: boolean): string[] {
16
+ const errors: string[] = [];
17
+ if (truncated) errors.push('Linear result truncated');
18
+ if (live.length !== 33) errors.push(`current open count: ${live.length} != 33`);
19
+ if (record.verifiedIssues.length !== 29) errors.push(`recorded count: ${record.verifiedIssues.length} != 29`);
20
+ const sorted = [...live].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
21
+ const selected = sorted.slice(0, 29);
22
+ const excluded = sorted.slice(29);
23
+ const actual = new Map(live.map(issue => [issue.identifier, issue]));
24
+ if (actual.size !== live.length) errors.push('duplicate Linear identifier');
25
+ const seen = new Set<string>();
26
+ for (const row of record.verifiedIssues) {
27
+ if (seen.has(row.identifier)) errors.push(`duplicate recorded identifier: ${row.identifier}`);
28
+ seen.add(row.identifier);
29
+ const issue = actual.get(row.identifier);
30
+ if (!issue) { errors.push(`not in Linear: ${row.identifier}`); continue; }
31
+ for (const [field, expected, value] of [
32
+ ['title', row.title, issue.title], ['url', row.url, issue.url],
33
+ ['createdAt', row.createdAt, issue.createdAt], ['updatedAt', row.updatedAt, issue.updatedAt],
34
+ ['dueDate', row.existingDueDate, issue.dueDate], ['priority', row.priority, issue.priority],
35
+ ] as const) if (expected !== value) errors.push(`${row.identifier} ${field}: recorded=${String(expected)} Linear=${String(value)}`);
36
+ }
37
+ const selectedIds = selected.map(issue => issue.identifier).sort().join(',');
38
+ if ([...seen].sort().join(',') !== selectedIds) errors.push('recorded identifiers differ from oldest 29 currently open by createdAt');
39
+ if (selected.filter(issue => issue.dueDate !== null).length !== 4) errors.push('Linear selected dueDate count != 4');
40
+ if (selected.filter(issue => issue.priority !== 0).length !== 7) errors.push('Linear selected priority count != 7');
41
+ const excludedRecorded = record.cohort.excludedCurrentIssues;
42
+ if (excludedRecorded.map(issue => issue.identifier).sort().join(',') !== excluded.map(issue => issue.identifier).sort().join(',')) errors.push('excluded identifiers differ from Linear');
43
+ for (const row of excludedRecorded) {
44
+ const issue = actual.get(row.identifier);
45
+ if (!issue) continue;
46
+ if (row.url !== issue.url || row.createdAt !== issue.createdAt || row.dueDate !== issue.dueDate || row.priority !== issue.priority) errors.push(`excluded ${row.identifier} differs from Linear`);
47
+ }
48
+ return errors;
49
+ }
50
+
51
+ // Linear GraphQL read-only fetchLinearProjectIssues resolves the project and paginates the open issues.
52
+ // createdAt is queried separately because the shared COO tool's projection does not include it.
53
+ export async function readLiveCooIssues(apiKey: string, project: string): Promise<{ issues: LiveIssue[]; truncated: boolean }> {
54
+ const issues = await fetchLinearProjectIssues({apiKey, project});
55
+ const identifiers = issues.map(issue => issue.identifier);
56
+ if (new Set(identifiers).size !== identifiers.length) throw new Error('duplicate Linear identifier');
57
+ const query = 'query CooIssueCreation($id: String!) { issue(id: $id) { identifier createdAt } }';
58
+ const creations = await Promise.all(identifiers.map(async identifier => {
59
+ const response = await fetch('https://api.linear.app/graphql', {
60
+ method: 'POST', headers: {Authorization: apiKey, 'Content-Type': 'application/json'},
61
+ body: JSON.stringify({query, variables: {id: identifier}}),
62
+ });
63
+ if (!response.ok) throw new Error(`Linear creation read HTTP ${response.status}`);
64
+ const body = await response.json() as { errors?: unknown[]; data?: {issue?: {identifier: string; createdAt: string}} };
65
+ if (body.errors?.length || body.data?.issue?.identifier !== identifier || !body.data.issue.createdAt) throw new Error(`Linear creation read failed: ${identifier}`);
66
+ return body.data.issue.createdAt;
67
+ }));
68
+ return {issues: issues.map((issue, index) => ({...issue, createdAt: creations[index]!})), truncated: issues.truncated};
69
+ }
70
+
71
+ if (import.meta.main) {
72
+ const apiKey = process.env.LINEAR_API_KEY?.trim();
73
+ if (!apiKey) throw new Error('LINEAR_API_KEY absent; cannot compare recorded evidence to live Linear');
74
+ const record = JSON.parse(await readFile(new URL('../src/domains/coo-admin-dates.evidence.json', import.meta.url), 'utf8')) as Evidence;
75
+ const {issues, truncated} = await readLiveCooIssues(apiKey, record.project);
76
+ const errors = compareCooEvidence(record, issues, truncated);
77
+ if (errors.length) {
78
+ for (const error of errors) console.error(error);
79
+ process.exitCode = 1;
80
+ } else {
81
+ console.log(`PASS Linear live comparison: open=${issues.length} selected=${record.verifiedIssues.length} dueDates=${record.verifiedIssues.filter(issue => issue.existingDueDate !== null).length} priorities=${record.verifiedIssues.filter(issue => issue.priority !== 0).length} excluded=${record.cohort.excludedCurrentIssues.length}`);
82
+ }
83
+ }
package/src/acp/client.ts CHANGED
@@ -371,6 +371,8 @@ export function readClientInfo(
371
371
  export interface AcpAgentOpts {
372
372
  /** Backend id from ACP_BACKENDS. */
373
373
  backendId: string;
374
+ /** Review-owned ACP executable override; never changes the shared backend registry. */
375
+ backendSpec?: AcpBackendSpec;
374
376
  /** Working directory the agent runs in (cwd argument to spawn).
375
377
  * Affects file-tool resolution. Default: getSessionCwd() so a
376
378
  * Ctrl+W / /wd in the dashboard retargets new ACP agents. */
@@ -443,7 +445,7 @@ export class AcpAgent {
443
445
  private authMethods: string[] = [];
444
446
 
445
447
  constructor(opts: AcpAgentOpts) {
446
- this.spec = getAcpBackend(opts.backendId);
448
+ this.spec = opts.backendSpec ?? getAcpBackend(opts.backendId);
447
449
  // WD7 — default to the session working directory; ACP agents
448
450
  // inherit the "active project" concept.
449
451
  this.cwd = opts.cwd ?? getSessionCwd();
@@ -456,7 +458,7 @@ export class AcpAgent {
456
458
  // proactively. Mirrors zed's env-strip pattern for provider
457
459
  // tokens (acp.rs:232-243), generalized here for any env that
458
460
  // breaks nested agents.
459
- const cleanEnv: NodeJS.ProcessEnv = { ...process.env, ...opts.env };
461
+ const cleanEnv: NodeJS.ProcessEnv = opts.backendSpec && opts.env ? { ...opts.env, NODE_ENV: process.env.NODE_ENV } : { ...process.env, ...opts.env };
460
462
  for (const key of Object.keys(cleanEnv)) {
461
463
  if (NESTED_AGENT_ENV_BLOCKLIST.has(key) || /^CLAUDE_CODE_/.test(key)) {
462
464
  delete cleanEnv[key];
@@ -54,6 +54,8 @@ export interface ChildLlmSelection {
54
54
  effort?: ChildLlmEffort;
55
55
  /** 선택이 CLI 플래그에서 왔나, self-dev 설정에서 왔나. 같은 모델이라도 다른 사실이다. */
56
56
  source: 'flag' | 'config';
57
+ /** Grok weekly cap rerouted this run to Codex: allow prepaid credits even when subscription quota is exhausted. */
58
+ codexQuotaPolicy?: 'credits';
57
59
  }
58
60
 
59
61
  /** ⭐⭐ **가르는 축은 「키냐 주소냐」가 «아니라» 「그 자격이 «파일»에 사나 «env»에 사나」다**
@@ -91,6 +93,8 @@ export function childLlmSelectionEnv(selection?: ChildLlmSelection): Record<stri
91
93
  return {
92
94
  ELANOUS_LLM_PROVIDER: provider,
93
95
  ELANOUS_LLM_MODEL: childRelayModel(provider, selection.model),
96
+ ...(selection.codexQuotaPolicy === 'credits' && provider === 'openai-codex'
97
+ ? { ELANOUS_CODEX_QUOTA_POLICY: 'credits' } : {}),
94
98
  // ⛔⭐ 새 env 이름을 «짓지 않는다» — `ELANOUS_ESCALATE_EFFORT` 가 «이미» 있고
95
99
  // `user-config.ts` 가 그것으로 `reasoningLevel`(anthropic)과
96
100
  // `codexReasoning.effort`(openai-codex/sol) 를 ***둘 다*** 덮는다.