elanous 0.2.36 → 0.2.38

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 (741) 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/{uC-N0emedeNOFuaBQFv-I → RrjXTwz-urRAbChP1bX02}/_buildManifest.js +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{9250-072df22eaadaa6b5.js → 1026-44010f405704050c.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1118.a8a74ea29aac3e46.js +27 -0
  6. package/apps/pwa/out/_next/static/chunks/1204-2aa830479adf80f4.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1341-43b7d2dc77d45c9d.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1440-c3ea8882badfd9bf.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/{4304.e9b2512834a9e23e.js → 1632.7d87f998e714bede.js} +1 -1
  10. package/apps/pwa/out/_next/static/chunks/1698-9c11cbf97f13717e.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/1770-3b1fc92253ef9f2f.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/1923-5c1695332240246a.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/1938.0e6c60ee91c4d7f5.js +148 -0
  14. package/apps/pwa/out/_next/static/chunks/{7784.1f0ac4a16d190cf4.js → 2104.70e086d5174a4736.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/213-20675f20673ba22a.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/{6566.926a1d1b7c911442.js → 214.186a430da28338f1.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/2277.3bd4c0ac7f1b5996.js +93 -0
  18. package/apps/pwa/out/_next/static/chunks/240.e09f433f71c78ff4.js +104 -0
  19. package/apps/pwa/out/_next/static/chunks/{5711.957b095705c12560.js → 2415.738b777642a02233.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/2417.02151393605a7cd0.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/2463.cd2641a76daaa9ff.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/2576.b9e9c629c4b84c52.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/265-c021de53f0865bd9.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/2998-b1cc13956cd6a6e7.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/2e4782d3.ba2f0ab2b63a53e7.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/{4286.b70c4520306817e8.js → 3086.fb14b60ce98b53d3.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/{3148-98efd4869d660a87.js → 3124-561f7f037943cde2.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/3130.626b54382b4dc379.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/3221-1bb3f99a55088613.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/3280.24edb1ffd03a7f3f.js +24 -0
  31. package/apps/pwa/out/_next/static/chunks/3288-eb3c22a1cbf2ae68.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/{3727.7949ccaacaa71242.js → 3291.f6c1005387d514bd.js} +2 -2
  33. package/apps/pwa/out/_next/static/chunks/33.ef99e8374f96c6c4.js +36 -0
  34. package/apps/pwa/out/_next/static/chunks/3341-f09b9efbd7a82f70.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/3389.012c70767a872f9b.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3410-d7c0f14b329d6fda.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/3421.c87d6313a7d670f7.js +287 -0
  38. package/apps/pwa/out/_next/static/chunks/3422-83a3953f9bd67e26.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/{8904.7d51af2dc033965c.js → 3528.e4b8ab70d339f965.js} +2 -2
  40. package/apps/pwa/out/_next/static/chunks/3587-002e24e368c515a9.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/3644.f12a7d821ab940af.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/3660.d7516588e2d875c3.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/3695.b82e2610f4cd4ea9.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/3858.ca1f5bc0f577eac9.js +94 -0
  45. package/apps/pwa/out/_next/static/chunks/3959-e6ac4a139d9d4bcc.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/3f0de3de-2340c532c00395db.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/4032.53f62176514ec0ea.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/{3031-85328a92fcce4da6.js → 4199-f7c6eb40cd0322f8.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/4204-ef8484cbecd5d62f.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/4259-163129ca79913c4b.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/428-eaa77df3d0c85969.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/4432-97e400ffa1e161f5.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/449-aa427cf2cbb75f50.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/4491-39a0fd32bbaf3840.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/4588.b7cf06d382953979.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/4690.70ec8060825366ab.js +32 -0
  57. package/apps/pwa/out/_next/static/chunks/4804.6012b5cd33c22792.js +90 -0
  58. package/apps/pwa/out/_next/static/chunks/4881.e4d22d730e3b9df5.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/4937.ce2e340dc29cad3e.js +79 -0
  60. package/apps/pwa/out/_next/static/chunks/5-bb1c08d84bd71df3.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/5005.de98df23b37cdf3c.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/5035.dc64a89cad5573cc.js +74 -0
  63. package/apps/pwa/out/_next/static/chunks/5041-5aada92ca215ffdd.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/{528.38ce20ef8aa251ff.js → 5203.51e5d8b5f1dcfe53.js} +16 -16
  65. package/apps/pwa/out/_next/static/chunks/5207.1a224035455790fd.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/5255-1b60d50eabe9f057.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/5269.d0436ee6d773382a.js +286 -0
  68. package/apps/pwa/out/_next/static/chunks/5300.677c97e0393742e6.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/{2683.c9773a0c589b031b.js → 5307.d7328020227880c2.js} +4 -4
  70. package/apps/pwa/out/_next/static/chunks/5309.3367c4d50f52b489.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/552-689827d49780fa00.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/5539.34cc9d60622cb69b.js +8 -0
  73. package/apps/pwa/out/_next/static/chunks/5568.218dea0c00ca2ec7.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/{552.b7d061ad91eeac51.js → 5638.107314dc479e2f37.js} +2 -2
  75. package/apps/pwa/out/_next/static/chunks/5645.b7e3b055455a04ea.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/5662.18cc0f30353cbd96.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/5671.da24fc9dc5e98325.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/5b141171.324c40fd49cb6821.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/6142.913b44e260a3f925.js +64 -0
  80. package/apps/pwa/out/_next/static/chunks/6175-d4621f6a10b0cfab.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/6290.3eb53a20746b72ce.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/6415-9307f3eafa90837f.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/{6925.562d532110879886.js → 6493.27ad77de7aac7966.js} +1 -1
  84. package/apps/pwa/out/_next/static/chunks/6579-3de35d00aacd71b5.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/680-2f324c4e039a5b67.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/6845-331b3c7481da3ba3.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/7000.28157393caaf5343.js +62 -0
  88. package/apps/pwa/out/_next/static/chunks/{275-02ea4c2a33980422.js → 7161-8f22db3ec5ed005e.js} +3 -3
  89. package/apps/pwa/out/_next/static/chunks/7311-60a7ea80d23c7521.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/7401.525d0a12f7d02c06.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/7438.c2cf72b66dca6aa4.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/{3136.30aaaa5fa03662a3.js → 7488.966b116789f3d41a.js} +1 -1
  93. package/apps/pwa/out/_next/static/chunks/{4116.a84a92a2492c080e.js → 7524.ce973525d30b3d52.js} +1 -1
  94. package/apps/pwa/out/_next/static/chunks/7573.2376e13c9c21f1f8.js +77 -0
  95. package/apps/pwa/out/_next/static/chunks/7577.ea6e9ac72e035520.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/{3324.a09703033abcbd2f.js → 7628.6b16863ac3972333.js} +1 -1
  97. package/apps/pwa/out/_next/static/chunks/7657.8d1fb3d2f7ebe273.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/7903-1435d5d2f0dd7807.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/{3410.7f9af5ab77cbe65f.js → 7954.4f7a8832663ee979.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/7978-30a91192c5dbe3c2.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/{771.c3cdb6c1f7f47c02.js → 8019.21ca554a821dad01.js} +16 -16
  102. package/apps/pwa/out/_next/static/chunks/8025-8795821525cf1a07.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/8191.3674a00f0b565c23.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/8217-604955d8885c5ef2.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/8298-eed6f5f2585a63f4.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/{4615.8525eb0add35eba6.js → 8487.291666583bebe640.js} +1 -1
  107. package/apps/pwa/out/_next/static/chunks/8509.3a54d4e61893d7cb.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/8758.ac421f726a189125.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/{6943.c6b24124853a47b2.js → 8879.ac06755bcf3e44a1.js} +1 -1
  110. package/apps/pwa/out/_next/static/chunks/891.d6c723a51a067b36.js +149 -0
  111. package/apps/pwa/out/_next/static/chunks/{575.d929793a53f27c4b.js → 8911.bf5e712acb25fb6c.js} +1 -1
  112. package/apps/pwa/out/_next/static/chunks/{9f31e478.fefcbcd3c388ebc5.js → 8994d93e.33ba491f95345fc2.js} +1 -1
  113. package/apps/pwa/out/_next/static/chunks/{da5ecc5c-b005ad8cac737d57.js → 89d6c04f-6b8ab341748e468a.js} +1 -1
  114. package/apps/pwa/out/_next/static/chunks/{599.c39c571b6733f2a2.js → 9223.cbcc88e0e9950426.js} +2 -2
  115. package/apps/pwa/out/_next/static/chunks/9299.24e2a7d9b6d1f91b.js +226 -0
  116. package/apps/pwa/out/_next/static/chunks/9317.6a9869039c231bb3.js +24 -0
  117. package/apps/pwa/out/_next/static/chunks/9383.e4755f275b3c11ff.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/{8037.e254fd797aa224d1.js → 9581.da63ddf41acfad46.js} +1 -1
  119. package/apps/pwa/out/_next/static/chunks/9715.8c5529a0a5c41a36.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/9817.3009d65f0889ef1f.js +2 -0
  121. package/apps/pwa/out/_next/static/chunks/{ceba0382.650712b56b85b582.js → 9fe1b809.738da85c8b17e3fb.js} +1 -1
  122. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-76873dd80da4f715.js +1 -0
  123. package/apps/pwa/out/_next/static/chunks/app/approvals/page-ed32b6ec5dea8f23.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-fdd40e15babf3d25.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/board/page-384efc78e85e400b.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/botlab/page-866aac5ff226a7f3.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/bots/page-d91809c5aeed319a.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/ceo/page-128f3522f89a81ab.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/chat/page-595961abf3b43ad1.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/consult/page-f41dccb76d8c0263.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/control/page-a3205c2e28b7d0d9.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/decisions/page-c39a80277814a3f4.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1ab3f98194513195.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/editor/page-a5246f7e254d77d0.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/exec/page-0f92287bdb81217d.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/field/page-5a37131ff63793ef.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/healthz/route-76873dd80da4f715.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/inside/page-ac4bd14bb2348bfd.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/intake/page-c348a3959c5b5fc6.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/layout-2addd81c0d3e2906.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/live/page-da6b765f58a3eefb.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/live-run/page-0661e40f8780c32c.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/loops/page-b89af1b1c8e4875b.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/market/page-da8aeb82ba751cdb.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-cc71317b239439db.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/morning/page-bde7fc1d64ffe369.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/not-found-e2797214f713fa58.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/observatory/page-2a35bcda70defaf6.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-3af03deedde45032.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d1d1210f99f620ae.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-46d1b5500885861d.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-e7abc34a48a6f71e.js → page-291ab19fe2acb173.js} +1 -1
  153. package/apps/pwa/out/_next/static/chunks/app/page-7f569e533ddeecba.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/reflection/page-990129c9fee46d8f.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-7737f785ebb1cdaf.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f3c73a4f7ffd2f8.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-03be7fb347f01058.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/settings/page-96b0280625e4df2b.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-56855545049eb56d.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/layout-76873dd80da4f715.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/page-79a909c9981f0338.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/share/page-a9d9a3e66459c1e0.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/showroom/page-c383cbf2013f7069.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f57c6a2e54dbdca4.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/term/{page-c15958835534404b.js → page-6b140f77593576eb.js} +1 -1
  166. package/apps/pwa/out/_next/static/chunks/app/today/page-6b767ae2d9d0adfc.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/trace/page-116d6198d5da0e3d.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/vault/page-b9c9b778c7501370.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-a6c78392ab8f6335.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workflows/page-455183deb4ed0cfe.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workspace/page-6a63e99526bd63d0.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31c22caafe788c08.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/{3a3d9966.cc4d4585a2708e7c.js → b7459770.6539b11860eb8a9c.js} +7 -7
  174. package/apps/pwa/out/_next/static/chunks/{053a429e.9a63436527cc751c.js → b7478fef.5917716286213fcc.js} +3 -3
  175. package/apps/pwa/out/_next/static/chunks/bb6987f9.3647e1a23b0e3217.js +122 -0
  176. package/apps/pwa/out/_next/static/chunks/{2812ca6a.3b9f4a7a08885744.js → de8b930d.d94c0f42acf807fc.js} +1 -1
  177. package/apps/pwa/out/_next/static/chunks/{873d6c12.09bd499f57171543.js → eaad0cba.771200940c529df7.js} +1 -1
  178. package/apps/pwa/out/_next/static/chunks/framework-4b12a946f352cee2.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/main-941155250ed2644b.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/main-app-f8b6765abed6d149.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_app-78432194c85388f9.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/pages/_error-cdf7390829512b1b.js +1 -0
  183. package/apps/pwa/out/_next/static/chunks/webpack-d5f0964a9c2e92d2.js +1 -0
  184. package/apps/pwa/out/_next/static/css/{b5d88eba78a3d7bf.css → be9019f696217521.css} +1 -1
  185. package/apps/pwa/out/approvals/index.html +1 -1
  186. package/apps/pwa/out/approvals/index.txt +23 -23
  187. package/apps/pwa/out/autopilot/index.html +1 -1
  188. package/apps/pwa/out/autopilot/index.txt +23 -23
  189. package/apps/pwa/out/board/index.html +1 -1
  190. package/apps/pwa/out/board/index.txt +23 -23
  191. package/apps/pwa/out/botlab/index.html +1 -1
  192. package/apps/pwa/out/botlab/index.txt +22 -22
  193. package/apps/pwa/out/bots/index.html +1 -1
  194. package/apps/pwa/out/bots/index.txt +22 -22
  195. package/apps/pwa/out/ceo/index.html +1 -1
  196. package/apps/pwa/out/ceo/index.txt +23 -23
  197. package/apps/pwa/out/chat/index.html +1 -1
  198. package/apps/pwa/out/chat/index.txt +22 -22
  199. package/apps/pwa/out/consult/index.html +1 -1
  200. package/apps/pwa/out/consult/index.txt +23 -23
  201. package/apps/pwa/out/control/index.html +1 -1
  202. package/apps/pwa/out/control/index.txt +23 -23
  203. package/apps/pwa/out/decisions/index.html +1 -1
  204. package/apps/pwa/out/decisions/index.txt +22 -22
  205. package/apps/pwa/out/design-check/index.html +1 -1
  206. package/apps/pwa/out/design-check/index.txt +23 -23
  207. package/apps/pwa/out/editor/index.html +1 -1
  208. package/apps/pwa/out/editor/index.txt +23 -23
  209. package/apps/pwa/out/exec/index.html +1 -1
  210. package/apps/pwa/out/exec/index.txt +23 -23
  211. package/apps/pwa/out/field/index.html +1 -1
  212. package/apps/pwa/out/field/index.txt +23 -23
  213. package/apps/pwa/out/healthz +1 -1
  214. package/apps/pwa/out/index.html +1 -1
  215. package/apps/pwa/out/index.txt +22 -22
  216. package/apps/pwa/out/inside/index.html +1 -1
  217. package/apps/pwa/out/inside/index.txt +22 -22
  218. package/apps/pwa/out/intake/index.html +1 -1
  219. package/apps/pwa/out/intake/index.txt +23 -23
  220. package/apps/pwa/out/live/index.html +1 -1
  221. package/apps/pwa/out/live/index.txt +23 -23
  222. package/apps/pwa/out/live-run/index.html +1 -1
  223. package/apps/pwa/out/live-run/index.txt +23 -23
  224. package/apps/pwa/out/loops/index.html +1 -1
  225. package/apps/pwa/out/loops/index.txt +22 -22
  226. package/apps/pwa/out/market/index.html +1 -1
  227. package/apps/pwa/out/market/index.txt +22 -22
  228. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  229. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  230. package/apps/pwa/out/morning/index.html +1 -1
  231. package/apps/pwa/out/morning/index.txt +23 -23
  232. package/apps/pwa/out/observatory/index.html +1 -1
  233. package/apps/pwa/out/observatory/index.txt +23 -23
  234. package/apps/pwa/out/ops/checklist/index.html +1 -1
  235. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  236. package/apps/pwa/out/ops/release/index.html +1 -1
  237. package/apps/pwa/out/ops/release/index.txt +22 -22
  238. package/apps/pwa/out/ops/seats/index.html +1 -1
  239. package/apps/pwa/out/ops/seats/index.txt +22 -22
  240. package/apps/pwa/out/outputs/index.html +1 -1
  241. package/apps/pwa/out/outputs/index.txt +22 -22
  242. package/apps/pwa/out/reflection/index.html +1 -1
  243. package/apps/pwa/out/reflection/index.txt +23 -23
  244. package/apps/pwa/out/scheduler/index.html +1 -1
  245. package/apps/pwa/out/scheduler/index.txt +23 -23
  246. package/apps/pwa/out/sessions/index.html +1 -1
  247. package/apps/pwa/out/sessions/index.txt +22 -22
  248. package/apps/pwa/out/settings/devices/index.html +1 -1
  249. package/apps/pwa/out/settings/devices/index.txt +23 -23
  250. package/apps/pwa/out/settings/index.html +1 -1
  251. package/apps/pwa/out/settings/index.txt +23 -23
  252. package/apps/pwa/out/setup/done/index.html +1 -1
  253. package/apps/pwa/out/setup/done/index.txt +23 -23
  254. package/apps/pwa/out/setup/index.html +1 -1
  255. package/apps/pwa/out/setup/index.txt +23 -23
  256. package/apps/pwa/out/share/index.html +1 -1
  257. package/apps/pwa/out/share/index.txt +23 -23
  258. package/apps/pwa/out/showroom/index.html +1 -1
  259. package/apps/pwa/out/showroom/index.txt +22 -22
  260. package/apps/pwa/out/tasks/index.html +1 -1
  261. package/apps/pwa/out/tasks/index.txt +23 -23
  262. package/apps/pwa/out/term/index.html +1 -1
  263. package/apps/pwa/out/term/index.txt +23 -23
  264. package/apps/pwa/out/today/index.html +1 -1
  265. package/apps/pwa/out/today/index.txt +23 -23
  266. package/apps/pwa/out/trace/index.html +1 -1
  267. package/apps/pwa/out/trace/index.txt +23 -23
  268. package/apps/pwa/out/vault/index.html +1 -1
  269. package/apps/pwa/out/vault/index.txt +23 -23
  270. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  271. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  272. package/apps/pwa/out/workflows/index.html +1 -1
  273. package/apps/pwa/out/workflows/index.txt +23 -23
  274. package/apps/pwa/out/workspace/index.html +1 -1
  275. package/apps/pwa/out/workspace/index.txt +23 -23
  276. package/apps/pwa/out/worktrees/index.html +1 -1
  277. package/apps/pwa/out/worktrees/index.txt +23 -23
  278. package/graphs/drafts/recipes.yaml +27 -7
  279. package/graphs/release/recipes.yaml +9 -0
  280. package/graphs/release/release-loop.yaml +26 -18
  281. package/graphs/work/draft-close.yaml +54 -0
  282. package/graphs/work/green-close.yaml +37 -0
  283. package/graphs/work/recipes.yaml +46 -1
  284. package/graphs/work/run-research.yaml +35 -0
  285. package/package.json +1 -1
  286. package/scripts/__fixtures__/authoring-paths-10x3.json +32 -0
  287. package/scripts/ask-op-inbox-once.ts +17 -0
  288. package/scripts/boundary-proof-mapping-audit.ts +88 -0
  289. package/scripts/brand/check.ts +13 -4
  290. package/scripts/codex-fallback-live-probe.ts +17 -15
  291. package/scripts/fleet/node-setup.sh +32 -2
  292. package/scripts/fleet/run-light-worker.sh +9 -0
  293. package/scripts/gate-0226-accepted-remaining-90.txt +91 -0
  294. package/scripts/gate-0226-failure-census.ts +546 -0
  295. package/scripts/goals/authoring-metrics.ts +258 -0
  296. package/scripts/graph-units/draft-close-node.ts +222 -0
  297. package/scripts/hook-order-sweep.ts +7 -4
  298. package/scripts/intake-collect-telegram-saved-stage.ts +48 -0
  299. package/scripts/land-global-checks.ts +69 -1
  300. package/scripts/measure-nl-routing-corpus.ts +207 -2
  301. package/scripts/provision-export-redactions.ts +15 -0
  302. package/scripts/public-copy-lint.ts +122 -0
  303. package/scripts/public-export.ts +69 -2
  304. package/scripts/ratchets/next-tui-entry-budget.ts +124 -0
  305. package/scripts/ratchets/train-side-invariants.sh +7 -1
  306. package/scripts/release-loop/cut-branch.ts +61 -12
  307. package/scripts/release-loop/docs-node.ts +140 -0
  308. package/scripts/release-loop/gate-closure.ts +48 -0
  309. package/scripts/release-loop/gate-diff.ts +2 -1
  310. package/scripts/release-loop/gate-node.ts +161 -25
  311. package/scripts/release-loop/gate-partial.ts +82 -3
  312. package/scripts/release-loop/gate-prewarm-plan.ts +35 -0
  313. package/scripts/release-loop/gate-radius-live.ts +4 -2
  314. package/scripts/release-loop/known-issues-node.ts +6 -4
  315. package/scripts/release-loop/notes-repair.ts +216 -0
  316. package/scripts/release-loop/prepare-node.ts +61 -5
  317. package/scripts/release-loop/resume-cap.ts +2 -1
  318. package/scripts/release-loop/resume-release.ts +406 -7
  319. package/scripts/release-loop/resume-test-traffic.ts +10 -0
  320. package/scripts/release-loop/resume.ts +13 -5
  321. package/scripts/release-loop/traffic-node.ts +12 -4
  322. package/scripts/release-loop/traffic-transition.ts +53 -13
  323. package/scripts/release-loop/version-node.ts +109 -15
  324. package/scripts/train/green-leg.ts +176 -0
  325. package/scripts/tui-continue-real-evidence.ts +47 -0
  326. package/scripts/ux-sim.ts +1 -1
  327. package/scripts/video-pipeline.ts +2 -2
  328. package/src/acp/client.ts +31 -5
  329. package/src/acp/core-turn-bridge.ts +12 -1
  330. package/src/acp/stderr-summary.ts +49 -0
  331. package/src/agent/monad-agent-turn.ts +1 -0
  332. package/src/agent/run-context.ts +1 -1
  333. package/src/agent/task-notification.ts +3 -1
  334. package/src/agent/terminal-surface.ts +217 -0
  335. package/src/agent-substrate/pr-reviewer.ts +11 -3
  336. package/src/agent-substrate/review-observation.ts +10 -0
  337. package/src/agent-substrate/self-review-cli.ts +537 -57
  338. package/src/auto-research/question-ladder-contract.ts +137 -0
  339. package/src/auto-research/research-turn.ts +134 -0
  340. package/src/autopilot/discovery/doc-lint.ts +1 -1
  341. package/src/autopilot/lineage/sources.ts +1 -1
  342. package/src/autopilot/pr-manager.ts +64 -13
  343. package/src/boot/daemon-runtime.ts +35 -3
  344. package/src/boot/daemon-tools/pty-shell.ts +2 -1
  345. package/src/bots/screen-probe.ts +1 -1
  346. package/src/budget/run-usage-rollup.ts +12 -7
  347. package/src/budget/unified-usage.ts +20 -0
  348. package/src/capture/tui-self-report.ts +42 -5
  349. package/src/chat/index.ts +1 -0
  350. package/src/chat/tool-render/dispatch.ts +8 -1
  351. package/src/cli/autopilot-cli.ts +24 -9
  352. package/src/cli/decisions-cli.ts +7 -4
  353. package/src/cli/doctor-cli.ts +116 -7
  354. package/src/cli/guardian-cli.ts +3 -1
  355. package/src/cli/intake-cli.ts +48 -12
  356. package/src/cli/lesson-cli.ts +69 -9
  357. package/src/cli/logs-cli.ts +357 -2
  358. package/src/cli/memory-cli.ts +30 -1
  359. package/src/cli/msg-cli.ts +15 -11
  360. package/src/cli/pr-granularity.ts +5 -3
  361. package/src/cli/pr-land-overlap-evidence.ts +38 -5
  362. package/src/cli/pr-land-refusal.ts +1 -1
  363. package/src/cli/pty-takeover-cli.ts +58 -5
  364. package/src/cli/release-checklist-read-cli.ts +112 -4
  365. package/src/cli/release-cli.ts +89 -18
  366. package/src/cli/review-carry.ts +77 -0
  367. package/src/cli/self-update.ts +24 -6
  368. package/src/cli/self-utterances-cli.ts +13 -16
  369. package/src/cli/tasks-card-show.ts +31 -1
  370. package/src/cli/tasks-cli.ts +370 -33
  371. package/src/cli/tasks-read-cli.ts +13 -5
  372. package/src/cli/usage-cli.ts +12 -10
  373. package/src/context-bus/claude-memory-import.ts +99 -0
  374. package/src/context-bus/context-now.ts +86 -2
  375. package/src/context-bus/long-term-memory.ts +125 -0
  376. package/src/core-turn/run-core-turn.ts +9 -2
  377. package/src/core-turn/run-goal-loop.ts +5 -2
  378. package/src/core-turn/types.ts +5 -0
  379. package/src/dashboard/chat/acp-chat.ts +9 -0
  380. package/src/dashboard/index.ts +115 -30
  381. package/src/dashboard/input/agent-wake.ts +43 -0
  382. package/src/dashboard/input/chat-main-acp-dispatch.ts +21 -1
  383. package/src/dashboard/input/terminal-modal-router.ts +8 -159
  384. package/src/dashboard/replay-preview.ts +25 -0
  385. package/src/dashboard/side-basic-runtime.ts +61 -0
  386. package/src/dashboard/slash-runtime/dashboard-handlers.ts +19 -8
  387. package/src/dashboard/synchronized-frame-output.ts +74 -0
  388. package/src/dashboard/term-slash-runtime.ts +2 -0
  389. package/src/dashboard/turn-preamble.ts +28 -4
  390. package/src/display/coordinator.ts +45 -12
  391. package/src/display/resize-listener.ts +41 -20
  392. package/src/display/synchronized-output-region.ts +60 -0
  393. package/src/domains/guardian-tick.ts +9 -0
  394. package/src/domains/surface-events.ts +80 -1
  395. package/src/exec-requests/planner.ts +37 -2
  396. package/src/execution-loop/launch-gate.ts +4 -3
  397. package/src/git-fs/cli-stderr.ts +45 -0
  398. package/src/git-fs/gh-cli.ts +6 -3
  399. package/src/git-fs/git-cli.ts +11 -5
  400. package/src/git-fs/watcher.ts +27 -22
  401. package/src/git-fs/worktree.ts +19 -3
  402. package/src/graph-runner/graph-cli.ts +26 -2
  403. package/src/graph-runner/graph-grow.ts +37 -9
  404. package/src/graph-runner/graph-run-api.ts +3 -2
  405. package/src/graph-runner/runner.ts +34 -7
  406. package/src/graph-wizard/generate.ts +29 -6
  407. package/src/grok/credential.ts +107 -36
  408. package/src/harness/harness-cli-command.ts +179 -33
  409. package/src/harness/review-adapter.ts +26 -6
  410. package/src/hq/fence-wrapper.ts +1 -1
  411. package/src/hq/transfer.ts +1 -1
  412. package/src/index.ts +151 -18
  413. package/src/instance/test-write-guard-real-transport.fixture.ts +52 -0
  414. package/src/intake-plane/collect-telegram-saved.ts +54 -5
  415. package/src/interactive-terminal-modal.ts +3 -0
  416. package/src/keybindings.ts +17 -22
  417. package/src/llm/provider-credentials.ts +1 -1
  418. package/src/llm/route-decision.ts +13 -0
  419. package/src/llm-tool-name.ts +37 -0
  420. package/src/llm-tool-schema.ts +55 -0
  421. package/src/llm.ts +203 -21
  422. package/src/loops/checker.ts +100 -0
  423. package/src/loops/consensus.ts +26 -2
  424. package/src/loops/loop-cli.ts +9 -4
  425. package/src/loops/orchestrator/anomaly-cluster.ts +243 -0
  426. package/src/loops/orchestrator/inbox-once.ts +28 -0
  427. package/src/loops/orchestrator/inbox.ts +130 -1
  428. package/src/loops/orchestrator/tick.ts +55 -7
  429. package/src/maturity/feature-maturity.ts +1 -0
  430. package/src/model-tier/llm-tier-map.ts +12 -17
  431. package/src/msg/msg-store.ts +17 -0
  432. package/src/native-tool-catalog.ts +1 -1
  433. package/src/nexus/api/channel-ask-op.ts +96 -0
  434. package/src/nexus/api/discord-trigger-bot.ts +28 -1
  435. package/src/nexus/api/graph-wizard-api.ts +27 -2
  436. package/src/nexus/api/grid.ts +24 -4
  437. package/src/nexus/api/http-server.ts +1 -1
  438. package/src/nexus/api/ops-api.ts +14 -6
  439. package/src/nexus/api/telegram-trigger-bot.ts +24 -0
  440. package/src/nexus/api/terminals.ts +28 -6
  441. package/src/nexus/state/state.ts +7 -1
  442. package/src/oauth/codex-account-store.ts +34 -1
  443. package/src/ops/loops-map.ts +46 -0
  444. package/src/ops/overview-cache.ts +22 -4
  445. package/src/ops/overview.ts +41 -5
  446. package/src/patents/ledger.ts +18 -1
  447. package/src/policy/llm-policy.ts +3 -1
  448. package/src/preview/terminal.ts +4 -2
  449. package/src/prompt-cache/anthropic.ts +2 -0
  450. package/src/prompt-enhance/enhance.ts +9 -4
  451. package/src/pty-shell/availability.ts +7 -0
  452. package/src/pty-shell/herdr-agent-report.ts +40 -31
  453. package/src/pty-shell/herdr-prune-stale.ts +17 -0
  454. package/src/pty-shell/herdr-sequence-lease.ts +60 -0
  455. package/src/pty-shell/pty-control-ipc.ts +187 -18
  456. package/src/pty-shell/pty-manifest.ts +16 -2
  457. package/src/pty-shell/registry.ts +25 -15
  458. package/src/pty-shell/tui-run-state.ts +235 -1
  459. package/src/release-loop/checklist.ts +50 -5
  460. package/src/release-loop/feature-store.ts +9 -9
  461. package/src/release-loop/gate-shards.ts +61 -0
  462. package/src/release-loop/landed-but-yellow.ts +93 -12
  463. package/src/release-loop/light-rc.ts +7 -4
  464. package/src/release-loop/rehearsal.ts +1 -1
  465. package/src/repl/index.ts +18 -0
  466. package/src/research/axis-fanout.ts +133 -0
  467. package/src/research/edge-decision.ts +88 -0
  468. package/src/research/output-router.ts +127 -0
  469. package/src/research-bridge/format.ts +16 -1
  470. package/src/research-bridge/index.ts +1 -0
  471. package/src/research-bridge/invoke.ts +41 -5
  472. package/src/seat-loop/seat-report.ts +1 -0
  473. package/src/self-dev/ask-launch-flow.ts +86 -10
  474. package/src/self-dev/decompose-proposal.ts +29 -18
  475. package/src/self-dev/decompose.ts +45 -9
  476. package/src/self-dev/dev-pipeline.ts +29 -7
  477. package/src/self-dev/draft-residue.ts +221 -2
  478. package/src/self-dev/draft-sweep.ts +254 -16
  479. package/src/self-dev/draft-triage-rules.ts +19 -5
  480. package/src/self-dev/orchestrate.ts +1 -0
  481. package/src/self-dev/review-provider-fallback.ts +45 -7
  482. package/src/self-dev/run-supervisor.ts +22 -7
  483. package/src/self-dev/shard-plan.ts +48 -0
  484. package/src/self-dev/shard-run.ts +310 -6
  485. package/src/self-implement/comment-only-source.ts +54 -0
  486. package/src/self-implement/doctor-visit.ts +108 -0
  487. package/src/self-implement/gate-artifacts.ts +6 -2
  488. package/src/self-implement/gate-baseline.ts +99 -1
  489. package/src/self-implement/gate-cli.ts +231 -82
  490. package/src/self-implement/gate-failure-signature.ts +44 -0
  491. package/src/self-implement/gate-remote.ts +30 -4
  492. package/src/self-implement/gate-scope.ts +13 -1
  493. package/src/self-implement/goal-author.ts +407 -21
  494. package/src/self-implement/goal-doc/section.ts +22 -0
  495. package/src/self-implement/goal-supervisor-fill.ts +110 -27
  496. package/src/self-implement/importer-test-index.ts +215 -2
  497. package/src/self-implement/knowhow-pack.ts +125 -12
  498. package/src/self-implement/l8-merge-queue.ts +198 -32
  499. package/src/self-implement/off-diff-evidence.ts +23 -0
  500. package/src/self-implement/orchestrator.ts +369 -33
  501. package/src/self-implement/pod-inplace-worktree.ts +82 -0
  502. package/src/self-implement/pod-warm-pool-profile.ts +2 -2
  503. package/src/self-implement/premise-review.ts +7 -5
  504. package/src/self-implement/review-round-brief.ts +300 -0
  505. package/src/self-implement/rework-policy.ts +14 -0
  506. package/src/self-implement/run-ledger.ts +428 -6
  507. package/src/self-implement/run-stop.ts +1 -1
  508. package/src/self-implement/running-runs.ts +17 -3
  509. package/src/self-implement/seams.ts +45 -16
  510. package/src/self-implement/self-implement-cli.ts +26 -2
  511. package/src/self-implement/stop-autoheal.ts +1 -0
  512. package/src/self-implement/test-seams.ts +13 -1
  513. package/src/self-implement/train-reach-grouping.ts +8 -2
  514. package/src/session/channel-ask-op-envelope.ts +36 -0
  515. package/src/session/channel-op-reply-route.ts +200 -0
  516. package/src/session/chat.ts +10 -1
  517. package/src/session/id-prefix.ts +60 -0
  518. package/src/session/index.ts +6 -0
  519. package/src/session-runtime/index.ts +18 -23
  520. package/src/skills/runner.ts +7 -3
  521. package/src/skills/tools/dev-harness.ts +2 -2
  522. package/src/skills/tools/pty.ts +5 -0
  523. package/src/skills/tools/schedule-wakeup.ts +1 -6
  524. package/src/status/bar.ts +3 -3
  525. package/src/status/metrics.ts +12 -1
  526. package/src/steward/rejudge-tick.ts +12 -5
  527. package/src/steward/resync-tick.ts +31 -4
  528. package/src/storage/file-lock.ts +17 -4
  529. package/src/task-agent/actions.ts +199 -4
  530. package/src/task-agent/adopt-pr.ts +63 -2
  531. package/src/task-agent/boundary-proof-mapping.ts +12 -0
  532. package/src/task-agent/boundary-proof-producer.ts +147 -0
  533. package/src/task-agent/completion-evidence.ts +2 -1
  534. package/src/task-agent/evidence-command-normalize.ts +42 -0
  535. package/src/task-agent/export-redactions-provision.ts +43 -0
  536. package/src/task-agent/green-close-recipe.ts +239 -0
  537. package/src/task-agent/green-sweep.ts +64 -10
  538. package/src/task-agent/live-moves.ts +48 -3
  539. package/src/task-agent/map-host-adapter.ts +134 -0
  540. package/src/task-agent/map-reduce.ts +158 -0
  541. package/src/task-agent/map-run-ledger.ts +226 -0
  542. package/src/task-agent/parent-host.ts +74 -17
  543. package/src/task-agent/rejudge-on-head.ts +72 -5
  544. package/src/task-agent/repair-cycle.ts +108 -8
  545. package/src/task-agent/repair-next-move.ts +12 -2
  546. package/src/task-agent/repair-worker.ts +37 -1
  547. package/src/task-agent/review-prior-fix.ts +12 -2
  548. package/src/task-agent/shadow.ts +188 -5
  549. package/src/task-agent/task-hand.ts +253 -20
  550. package/src/task-agent/task-parent-role-llm.ts +9 -0
  551. package/src/task-agent/tier-split.ts +40 -5
  552. package/src/task-agent/work-order.ts +53 -2
  553. package/src/task-orchestrator/dispatcher.ts +46 -1
  554. package/src/task-orchestrator/generator-prompt.ts +6 -0
  555. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +16 -6
  556. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +31 -4
  557. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +8 -6
  558. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +97 -0
  559. package/src/task-orchestrator/surfaces/pod-pool.ts +77 -8
  560. package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +15 -9
  561. package/src/task-orchestrator/surfaces/self-implement-pod.ts +338 -79
  562. package/src/task-orchestrator/surfaces/self-implement.ts +37 -2
  563. package/src/terminal/session-registry.ts +1 -0
  564. package/src/terminal/terminal-modal-router.ts +156 -0
  565. package/src/testing/linux-env.ts +177 -0
  566. package/src/tool-hints/gate.ts +6 -4
  567. package/src/tool-hints/signals.ts +33 -1
  568. package/src/tui/chat/background-job-status.ts +8 -2
  569. package/src/tui/chat/goal-interim.ts +5 -1
  570. package/src/tui/entry.ts +722 -30
  571. package/src/tui/input/at-picker.ts +55 -0
  572. package/src/tui/sidetab/attach.ts +304 -0
  573. package/src/tui/sidetab/list.ts +176 -0
  574. package/src/tui/sidetab/nl-tool.ts +136 -0
  575. package/src/tui/slash/resume-command.ts +23 -0
  576. package/src/tui/slash/resume.ts +38 -35
  577. package/src/tui/slash/signals.ts +114 -0
  578. package/src/tui/slash/term-next.ts +72 -0
  579. package/src/tui-client/local-chat-turn.ts +31 -9
  580. package/src/user-config.ts +30 -3
  581. package/src/ux-sim/boot-daemon-tools-pty-shell-device.ts +136 -0
  582. package/src/ux-sim/classic-tui-seat-scenarios.ts +158 -4
  583. package/src/ux-sim/fake-gh.ts +174 -0
  584. package/src/ux-sim/fake-kubectl.ts +142 -0
  585. package/src/ux-sim/fake-llm.ts +17 -6
  586. package/src/ux-sim/next-tui-seat-scenarios.ts +176 -6
  587. package/src/ux-sim/resume-picker-device.ts +173 -0
  588. package/src/ux-sim/session-id-seed.ts +60 -0
  589. package/src/ux-sim/skills-tools-shell-device.ts +156 -0
  590. package/src/ux-sim/task-agent-parent-host-device.ts +264 -0
  591. package/src/ux-sim/task-agent-rejudge-on-head-device.ts +114 -0
  592. package/src/version/packed-revision.json +1 -1
  593. package/apps/pwa/out/_next/static/chunks/109.f196abf3484bb97f.js +0 -287
  594. package/apps/pwa/out/_next/static/chunks/1201.afbfc5b1c5e5cbe4.js +0 -1
  595. package/apps/pwa/out/_next/static/chunks/1517.9275b89572098ff3.js +0 -1
  596. package/apps/pwa/out/_next/static/chunks/1574.ad11f8db8396ab45.js +0 -1
  597. package/apps/pwa/out/_next/static/chunks/1614.7a8c5c40aee0261f.js +0 -1
  598. package/apps/pwa/out/_next/static/chunks/1871-f1eb29d8dbb4065b.js +0 -1
  599. package/apps/pwa/out/_next/static/chunks/190.285c832bd9626f8c.js +0 -64
  600. package/apps/pwa/out/_next/static/chunks/2193.0ea24c4a7aa1d004.js +0 -36
  601. package/apps/pwa/out/_next/static/chunks/223-ad865c1e80491d78.js +0 -1
  602. package/apps/pwa/out/_next/static/chunks/2265-f0d90b6dbec05f81.js +0 -1
  603. package/apps/pwa/out/_next/static/chunks/2267.eb4a62a209f09ec4.js +0 -149
  604. package/apps/pwa/out/_next/static/chunks/2341-86cd06c0cf01aa33.js +0 -1
  605. package/apps/pwa/out/_next/static/chunks/2352.9abc42422bade876.js +0 -1
  606. package/apps/pwa/out/_next/static/chunks/2397-c1653a816061842d.js +0 -1
  607. package/apps/pwa/out/_next/static/chunks/2500.cbc1257b774aae52.js +0 -1
  608. package/apps/pwa/out/_next/static/chunks/2709.8e1889034255fc5f.js +0 -24
  609. package/apps/pwa/out/_next/static/chunks/284.ac77c0db55bfd3a8.js +0 -1
  610. package/apps/pwa/out/_next/static/chunks/2941.3c3546f96bc6efa7.js +0 -1
  611. package/apps/pwa/out/_next/static/chunks/2976.b000698a3c6512dc.js +0 -24
  612. package/apps/pwa/out/_next/static/chunks/2986.45d7652e4f13557e.js +0 -2
  613. package/apps/pwa/out/_next/static/chunks/3155.2587a55196ecb394.js +0 -226
  614. package/apps/pwa/out/_next/static/chunks/3277.785db1cf09f98601.js +0 -1
  615. package/apps/pwa/out/_next/static/chunks/3320-f9c77381f85a2c0a.js +0 -1
  616. package/apps/pwa/out/_next/static/chunks/3386-91049feabff9a94e.js +0 -1
  617. package/apps/pwa/out/_next/static/chunks/339.80afb7e8839372af.js +0 -8
  618. package/apps/pwa/out/_next/static/chunks/3538.a445e50bb359d068.js +0 -32
  619. package/apps/pwa/out/_next/static/chunks/3540c645.2595a6af01a97672.js +0 -1
  620. package/apps/pwa/out/_next/static/chunks/3621.581b2054cc14bee4.js +0 -286
  621. package/apps/pwa/out/_next/static/chunks/3676.8c28d16fd84ca843.js +0 -1
  622. package/apps/pwa/out/_next/static/chunks/375-b84540853523fe52.js +0 -1
  623. package/apps/pwa/out/_next/static/chunks/3757-180308bc61021b05.js +0 -1
  624. package/apps/pwa/out/_next/static/chunks/379.ee262e37586fe1ae.js +0 -74
  625. package/apps/pwa/out/_next/static/chunks/3840-5825d56d46976a92.js +0 -1
  626. package/apps/pwa/out/_next/static/chunks/3969-81054381eaec4c4c.js +0 -1
  627. package/apps/pwa/out/_next/static/chunks/4067-bf9aa60547972937.js +0 -1
  628. package/apps/pwa/out/_next/static/chunks/4176.e5729c93c29151eb.js +0 -104
  629. package/apps/pwa/out/_next/static/chunks/4227-e6c84686bc8946ca.js +0 -1
  630. package/apps/pwa/out/_next/static/chunks/4327.b9e08a71c0009348.js +0 -1
  631. package/apps/pwa/out/_next/static/chunks/4946.bd106eafd668f248.js +0 -148
  632. package/apps/pwa/out/_next/static/chunks/5085-bce4904931767ca6.js +0 -1
  633. package/apps/pwa/out/_next/static/chunks/5125.2eb9968b83767193.js +0 -93
  634. package/apps/pwa/out/_next/static/chunks/5128-75b733fd74a9a1eb.js +0 -1
  635. package/apps/pwa/out/_next/static/chunks/5489f3fe.4bb1458b09f6dead.js +0 -122
  636. package/apps/pwa/out/_next/static/chunks/5560-2a4648f6a30f16e7.js +0 -1
  637. package/apps/pwa/out/_next/static/chunks/5772-e29e14f8ff127122.js +0 -1
  638. package/apps/pwa/out/_next/static/chunks/5887.5b2f5b44ce0f7e9a.js +0 -1
  639. package/apps/pwa/out/_next/static/chunks/5899-4c112a2d908cf97f.js +0 -1
  640. package/apps/pwa/out/_next/static/chunks/6041.1b65efb796ebc2a5.js +0 -79
  641. package/apps/pwa/out/_next/static/chunks/6057-765c0a26028c86b2.js +0 -1
  642. package/apps/pwa/out/_next/static/chunks/6142.f289e0d039f453a6.js +0 -1
  643. package/apps/pwa/out/_next/static/chunks/6199-6bcd61d2a3910e04.js +0 -1
  644. package/apps/pwa/out/_next/static/chunks/6236.cdf7293e710a9b2d.js +0 -1
  645. package/apps/pwa/out/_next/static/chunks/6387-237e079dac9a3ae6.js +0 -1
  646. package/apps/pwa/out/_next/static/chunks/6448-b6d272d074276e14.js +0 -1
  647. package/apps/pwa/out/_next/static/chunks/6466-92e65a48490b68a9.js +0 -1
  648. package/apps/pwa/out/_next/static/chunks/6580-cdc0e4a634ce4708.js +0 -1
  649. package/apps/pwa/out/_next/static/chunks/6659-b242dc31b20b3f29.js +0 -1
  650. package/apps/pwa/out/_next/static/chunks/6690.c5d9b321b1f79384.js +0 -1
  651. package/apps/pwa/out/_next/static/chunks/671-e9b6ad0c771c5514.js +0 -1
  652. package/apps/pwa/out/_next/static/chunks/6862.bcbf89dd0f4496b3.js +0 -27
  653. package/apps/pwa/out/_next/static/chunks/6912-7b375ee48732c767.js +0 -1
  654. package/apps/pwa/out/_next/static/chunks/727.7deb87749b0a343a.js +0 -1
  655. package/apps/pwa/out/_next/static/chunks/7481.39f7a4654907bf6e.js +0 -1
  656. package/apps/pwa/out/_next/static/chunks/7545.b1aa42cd15503319.js +0 -1
  657. package/apps/pwa/out/_next/static/chunks/7551-416d298c2dc4a9ab.js +0 -1
  658. package/apps/pwa/out/_next/static/chunks/7870-309de463786ac1fc.js +0 -1
  659. package/apps/pwa/out/_next/static/chunks/7888-e29b151bb6a3bb8b.js +0 -1
  660. package/apps/pwa/out/_next/static/chunks/7891.03f910124a737985.js +0 -1
  661. package/apps/pwa/out/_next/static/chunks/7905-6ba78d9234608f79.js +0 -1
  662. package/apps/pwa/out/_next/static/chunks/8207.f527886b7c464529.js +0 -1
  663. package/apps/pwa/out/_next/static/chunks/856.50bda61439d465e6.js +0 -62
  664. package/apps/pwa/out/_next/static/chunks/9031-92e9b487edb681ee.js +0 -1
  665. package/apps/pwa/out/_next/static/chunks/9060.5410d9f6a95f6287.js +0 -90
  666. package/apps/pwa/out/_next/static/chunks/914.8abefd48d579547b.js +0 -94
  667. package/apps/pwa/out/_next/static/chunks/9187-c87a2207d4ac5d46.js +0 -1
  668. package/apps/pwa/out/_next/static/chunks/9289.3e92f278821e18e0.js +0 -1
  669. package/apps/pwa/out/_next/static/chunks/9319.7816d3cc741cd34f.js +0 -1
  670. package/apps/pwa/out/_next/static/chunks/9327-e016bb9f1674d24f.js +0 -1
  671. package/apps/pwa/out/_next/static/chunks/9365.3de48bf75193ff9a.js +0 -77
  672. package/apps/pwa/out/_next/static/chunks/9383.5b5e9aa56b5fea97.js +0 -1
  673. package/apps/pwa/out/_next/static/chunks/9387-cb929573ef436f39.js +0 -1
  674. package/apps/pwa/out/_next/static/chunks/9489.c27becacc3283dec.js +0 -1
  675. package/apps/pwa/out/_next/static/chunks/9514.131c52cc75fe6439.js +0 -1
  676. package/apps/pwa/out/_next/static/chunks/9536.28962663c7734376.js +0 -1
  677. package/apps/pwa/out/_next/static/chunks/9686-12286f5bbf15a5a0.js +0 -1
  678. package/apps/pwa/out/_next/static/chunks/9776.289d40cc5a4c342a.js +0 -1
  679. package/apps/pwa/out/_next/static/chunks/9789.60f723d18097ec30.js +0 -1
  680. package/apps/pwa/out/_next/static/chunks/989.f0c17523c5e61f65.js +0 -1
  681. package/apps/pwa/out/_next/static/chunks/9972-1df393aa8d4f7d52.js +0 -1
  682. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-96750aba7430959f.js +0 -1
  683. package/apps/pwa/out/_next/static/chunks/app/approvals/page-4105e5bbbb043024.js +0 -1
  684. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5d4fa75c2acb113e.js +0 -1
  685. package/apps/pwa/out/_next/static/chunks/app/board/page-a79a9bdb3b2142bf.js +0 -1
  686. package/apps/pwa/out/_next/static/chunks/app/botlab/page-f1c2047484992a35.js +0 -1
  687. package/apps/pwa/out/_next/static/chunks/app/bots/page-15ffc49b2d8c70e2.js +0 -1
  688. package/apps/pwa/out/_next/static/chunks/app/ceo/page-67d53a3fc8734135.js +0 -1
  689. package/apps/pwa/out/_next/static/chunks/app/chat/page-e30c8dc696491336.js +0 -1
  690. package/apps/pwa/out/_next/static/chunks/app/consult/page-2136e04fa77696e2.js +0 -1
  691. package/apps/pwa/out/_next/static/chunks/app/control/page-fecd2814790c2e12.js +0 -1
  692. package/apps/pwa/out/_next/static/chunks/app/decisions/page-280aa4a98fc5bb44.js +0 -1
  693. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c14e96b65fb4b883.js +0 -1
  694. package/apps/pwa/out/_next/static/chunks/app/editor/page-187dac74825ed6bf.js +0 -1
  695. package/apps/pwa/out/_next/static/chunks/app/exec/page-6bc7246ea8c92448.js +0 -1
  696. package/apps/pwa/out/_next/static/chunks/app/field/page-083747f1d4518f61.js +0 -1
  697. package/apps/pwa/out/_next/static/chunks/app/healthz/route-96750aba7430959f.js +0 -1
  698. package/apps/pwa/out/_next/static/chunks/app/inside/page-15ca95a16ec96e1f.js +0 -1
  699. package/apps/pwa/out/_next/static/chunks/app/intake/page-a8ac031b3810eb64.js +0 -1
  700. package/apps/pwa/out/_next/static/chunks/app/layout-8835f93110494cda.js +0 -1
  701. package/apps/pwa/out/_next/static/chunks/app/live/page-e2de2e59111c9aad.js +0 -1
  702. package/apps/pwa/out/_next/static/chunks/app/live-run/page-f2b2e4bf6e981450.js +0 -1
  703. package/apps/pwa/out/_next/static/chunks/app/loops/page-5b92b7f75df20948.js +0 -1
  704. package/apps/pwa/out/_next/static/chunks/app/market/page-468eddf2b1a84ecb.js +0 -1
  705. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2af16e62c12f22df.js +0 -1
  706. package/apps/pwa/out/_next/static/chunks/app/morning/page-18b40867a77a5793.js +0 -1
  707. package/apps/pwa/out/_next/static/chunks/app/not-found-fe934f2979f5a4a2.js +0 -1
  708. package/apps/pwa/out/_next/static/chunks/app/observatory/page-3eec435537357170.js +0 -1
  709. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ff88cbfd2aa99fc8.js +0 -1
  710. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a5b39fb780c70ab5.js +0 -1
  711. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ae465dde1d1b3933.js +0 -1
  712. package/apps/pwa/out/_next/static/chunks/app/page-cab9674fde9ead26.js +0 -1
  713. package/apps/pwa/out/_next/static/chunks/app/reflection/page-1da49bc8712284d0.js +0 -1
  714. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-5e8aa657551ac912.js +0 -1
  715. package/apps/pwa/out/_next/static/chunks/app/sessions/page-fcc36f36c6dc1fe2.js +0 -1
  716. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-d22db22f2a7296ca.js +0 -1
  717. package/apps/pwa/out/_next/static/chunks/app/settings/page-d12dc7b1773878f0.js +0 -1
  718. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-99debec9ea7fbec0.js +0 -1
  719. package/apps/pwa/out/_next/static/chunks/app/setup/layout-96750aba7430959f.js +0 -1
  720. package/apps/pwa/out/_next/static/chunks/app/setup/page-2a088a78bad7e924.js +0 -1
  721. package/apps/pwa/out/_next/static/chunks/app/share/page-2e8ed57db7e69993.js +0 -1
  722. package/apps/pwa/out/_next/static/chunks/app/showroom/page-4b3a16213c2ae8fe.js +0 -1
  723. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f098747ddf38c98b.js +0 -1
  724. package/apps/pwa/out/_next/static/chunks/app/today/page-68279e3c843c1fd7.js +0 -1
  725. package/apps/pwa/out/_next/static/chunks/app/trace/page-a34a74453367a39d.js +0 -1
  726. package/apps/pwa/out/_next/static/chunks/app/vault/page-8767a7db2155a5bd.js +0 -1
  727. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-bd5976683460d7f2.js +0 -1
  728. package/apps/pwa/out/_next/static/chunks/app/workflows/page-b7364835e7f2ef54.js +0 -1
  729. package/apps/pwa/out/_next/static/chunks/app/workspace/page-311af16b3ee0ffca.js +0 -1
  730. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fb670757274ae990.js +0 -1
  731. package/apps/pwa/out/_next/static/chunks/ccae1c4b-0cf414f5eff1b10d.js +0 -1
  732. package/apps/pwa/out/_next/static/chunks/f9a61e4d.1fc2c1dbdd49fe46.js +0 -1
  733. package/apps/pwa/out/_next/static/chunks/framework-6d497cbf1eef04c6.js +0 -1
  734. package/apps/pwa/out/_next/static/chunks/main-app-012f5edec932f337.js +0 -1
  735. package/apps/pwa/out/_next/static/chunks/main-da5aeb809edc94a2.js +0 -1
  736. package/apps/pwa/out/_next/static/chunks/pages/_app-5ee319af6f749dfe.js +0 -1
  737. package/apps/pwa/out/_next/static/chunks/pages/_error-4bc84653a718d6c6.js +0 -1
  738. package/apps/pwa/out/_next/static/chunks/webpack-8a96cf6775c73d6e.js +0 -1
  739. package/scripts/migrate-monad-to-elanous.ts +0 -195
  740. package/scripts/rebrand-elanous.ts +0 -242
  741. /package/apps/pwa/out/_next/static/{uC-N0emedeNOFuaBQFv-I → RrjXTwz-urRAbChP1bX02}/_ssgManifest.js +0 -0
@@ -14,24 +14,46 @@
14
14
  //
15
15
  // index.ts 의 액션은 옵션 파싱과 실제 의존 배선만 남는다(얇은 껍데기).
16
16
 
17
- import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
17
+ import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
18
18
  import { homedir, tmpdir } from 'node:os';
19
19
  import { dirname, join } from 'node:path';
20
20
  import { effectiveInstanceRoot } from '../instance/resolve.js';
21
21
  import { tierModel } from '../llm/model-defaults.js';
22
- import { buildReviewObservation, safeLogText } from './review-observation.js';
22
+ import { buildReviewObservation, reviewIntentFingerprint, safeLogText } from './review-observation.js';
23
+ import { checkCitedReviewPathsAtHead } from './review-finding-key.js';
23
24
  import { DEFAULT_REVIEW_BACKEND, defaultAcpReviewTimeoutSec } from './acp-reviewer.js';
24
25
  import { ZAI_OPENROUTER_FALLBACK_MODEL } from '../llm/zai-coding-plan.js';
25
26
  import { debug } from '../debug/log.js';
26
27
  import { ACP_BACKENDS, canonicalizeBackendId } from '../acp/backend-registry.js';
27
28
  import { buildManualReviewIntent, intentFromPr, prIntentSectionCoverage, reviewIntentTruncationFromPr, reviewIntentTruncationObservation } from './review-intent.js';
28
- import { budgetReviewerContext, reviewDiffBudgetObservation, splitDiffByFile, type ReviewImage, type ReviewInput, type ReviewerContextItem, type ReviewResult } from './pr-reviewer.js';
29
+ import { budgetReviewerContext, isNoneSentinelFinding, reviewDiffBudgetObservation, splitDiffByFile, type ReviewImage, type ReviewInput, type ReviewerContextItem, type ReviewResult } from './pr-reviewer.js';
29
30
  import type { ReferencedFileReader } from '../self-implement/goal-file-reader.js';
30
- import { getUserConfig, type LLMProviderName, type RoleLlmSpec, type SubscriptionReviewerSpec } from '../user-config.js';
31
+ import { getUserConfig, type LLMProviderName, type ReviewerMixSpec, type RoleLlmSpec, type SubscriptionReviewerSpec } from '../user-config.js';
32
+ import { loadLlmPolicy } from '../policy/llm-policy.js';
31
33
  import { listRunLedgers, lookupPrGoalAcceptance, lookupRunLedger, type PrGoalAcceptanceLookup, type RunLedgerEntry, type RunLedgerLookup } from '../self-implement/run-ledger.js';
32
34
 
33
35
  /** SUBSCRIPTION-EXHAUST-REROUTE — API reviewer when the configured `roleLlm.reviewer` lane is unavailable (off codex). */
34
36
  export const ROLE_REVIEWER_FALLBACK_MODEL = 'openrouter/z-ai/glm-5.3';
37
+ export const DEFAULT_REVIEWER_MIX: ReviewerMixSpec = { sol: 1, claude: 1, glm: 1 };
38
+ type ReviewLane = keyof ReviewerMixSpec;
39
+ const REVIEW_LANES: readonly ReviewLane[] = ['sol', 'claude', 'glm'];
40
+
41
+ /** A PR keeps the same primary lane through every review round, regardless of head SHA. */
42
+ export function reviewLaneOrder(pr: string, mix: ReviewerMixSpec): ReviewLane[] {
43
+ if (!/^\d+$/.test(pr)) return [];
44
+ const total = REVIEW_LANES.reduce((sum, lane) => sum + mix[lane], 0);
45
+ if (!Number.isSafeInteger(total) || total <= 0) return [];
46
+ const slot = Number(BigInt(pr) % BigInt(total));
47
+ let offset = 0;
48
+ let primary: ReviewLane | undefined;
49
+ for (const lane of REVIEW_LANES) {
50
+ offset += mix[lane];
51
+ if (slot < offset) { primary = lane; break; }
52
+ }
53
+ if (!primary) return [];
54
+ const start = REVIEW_LANES.indexOf(primary);
55
+ return [...REVIEW_LANES.slice(start), ...REVIEW_LANES.slice(0, start)].filter(lane => mix[lane] > 0);
56
+ }
35
57
 
36
58
  /** Only an explicit provider+model role setting selects the API review model. */
37
59
  export function apiReviewModelFromRoleSpec(spec: RoleLlmSpec | undefined): string | undefined {
@@ -54,6 +76,15 @@ export interface SelfReviewCliOpts {
54
76
  reviewDenyBackends?: string[] | undefined;
55
77
  /** `roleLlm.reviewer` (same resolution as the harness). Used only when no explicit reviewer flag is given. */
56
78
  roleReviewer?: SubscriptionReviewerSpec | undefined;
79
+ /** Default resolves from policy/config; null keeps the legacy single reviewer for isolated callers. */
80
+ reviewerMix?: ReviewerMixSpec | null | undefined;
81
+ /** Internal route metadata; the user-facing mix is selected once per PR. */
82
+ reviewerMixPrimaryLane?: ReviewLane | undefined;
83
+ reviewerMixAttemptLane?: ReviewLane | undefined;
84
+ reviewerMixExpectedHead?: string | undefined;
85
+ /** Internal: the same command already claimed its single over-cap exception for this pinned head. */
86
+ reviewerMixCapClaimed?: boolean | undefined;
87
+ disableClaudeGlmFallback?: boolean | undefined;
57
88
  acpTimeout?: string | undefined;
58
89
  context?: string[] | undefined;
59
90
  contextText?: string[] | undefined;
@@ -67,6 +98,8 @@ export interface SelfReviewCliOpts {
67
98
 
68
99
  /** 셸/네트워크/로그 경계 — 테스트는 전부 대역으로 채운다. */
69
100
  export interface SelfReviewCliDeps {
101
+ /** Explicit repository root for exact-head cited-path checks; absent means unmeasured. */
102
+ reviewRepositoryPath?: string;
70
103
  /** `gh` 호출. 실패도 예외 없이 `{status, stdout, stderr}` 로 돌려준다(spawnSync 동형). */
71
104
  gh(args: string[], timeoutMs: number): { status: number | null; stdout: string; stderr: string };
72
105
  /** ⭐ `git` 호출(선택). 주입되면 리뷰 증거를 **이 PR 이 저작한 변경**으로 좁힌다(아래 참조).
@@ -138,37 +171,77 @@ export interface SelfReviewCliOutcome {
138
171
  failed: number;
139
172
  }
140
173
 
141
- /** Saved results are round evidence only when their PR, exact head and reviewed status agree. */
142
- function savedReviewRecords(pr: string, root = effectiveInstanceRoot()): Array<{ head: string; reviewedAt: number }> {
174
+ interface SavedReviewEvidence {
175
+ heads: string[];
176
+ records: Array<{ head: string; reviewedAt: number }>;
177
+ unreadableFiles: string[];
178
+ }
179
+
180
+ function savedReviewEvidence(
181
+ pr: string,
182
+ root = effectiveInstanceRoot(),
183
+ onUnreadable?: (file: string) => void,
184
+ ): SavedReviewEvidence {
143
185
  const dir = join(root, 'review-results');
144
186
  let names: string[];
145
- try { names = readdirSync(dir); } catch { return []; }
146
- // Manual review saves in the operating house also use head10; the JSON's full head is authoritative.
147
- const matching = new RegExp(`^.+-${pr}-([a-f0-9]{7,40})\\.json$`, 'i');
187
+ try { names = readdirSync(dir); } catch { return { heads: [], records: [], unreadableFiles: [] }; }
188
+ const escapedPr = pr.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
189
+ const matchingPrInName = new RegExp(`(?:^|-)${escapedPr}(?:-|\\.json$)`, 'i');
148
190
  const records = new Map<string, { head: string; reviewedAt: number }>();
191
+ const unreadableFiles: string[] = [];
149
192
  for (const name of names) {
150
- const match = matching.exec(name);
151
- if (!match) continue;
193
+ if (!name.toLowerCase().endsWith('.json')) continue;
194
+ const nameSuggestsPr = matchingPrInName.test(name);
152
195
  try {
153
- const recordPath = join(dir, name);
154
- const raw: unknown = JSON.parse(readFileSync(recordPath, 'utf8'));
155
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) continue;
196
+ const raw: unknown = JSON.parse(readFileSync(join(dir, name), 'utf8'));
197
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
198
+ if (nameSuggestsPr) { unreadableFiles.push(name); onUnreadable?.(name); }
199
+ continue;
200
+ }
156
201
  const result = raw as Record<string, unknown>;
202
+ const recordPr = typeof result.pr === 'string' ? result.pr
203
+ : Number.isSafeInteger(result.pr) ? String(result.pr) : undefined;
204
+ if (recordPr !== pr) {
205
+ // A parseable, explicit foreign PR identity outranks a misleading filename.
206
+ if (recordPr === undefined && nameSuggestsPr) { unreadableFiles.push(name); onUnreadable?.(name); }
207
+ continue;
208
+ }
209
+ if (result.reviewed !== true) continue;
157
210
  const head = result.headCommit;
158
- if (String(result.pr) !== pr || result.reviewed !== true || typeof head !== 'string'
159
- || !COMMIT_OID.test(head) || !head.toLowerCase().startsWith(match[1]!.toLowerCase())) continue;
160
- const normalized = head.toLowerCase();
161
- const existing = records.get(normalized);
211
+ if (typeof head !== 'string' || !COMMIT_OID.test(head)) {
212
+ unreadableFiles.push(name);
213
+ onUnreadable?.(name);
214
+ continue;
215
+ }
216
+ // The result payload is authoritative: round-tagged filenames and stale suffixes
217
+ // still count by their full PR/head identity, then deduplicate on that identity.
218
+ const recordPath = join(dir, name);
162
219
  const reviewedAt = typeof result.reviewedAt === 'number' && Number.isFinite(result.reviewedAt)
163
220
  ? result.reviewedAt : statSync(recordPath).mtimeMs;
221
+ const normalized = head.toLowerCase();
222
+ const existing = records.get(normalized);
164
223
  if (!existing || reviewedAt > existing.reviewedAt) records.set(normalized, { head: normalized, reviewedAt });
165
- } catch { /* corrupt or partial result is not a completed round */ }
224
+ } catch {
225
+ // A filename can associate an unreadable record with this PR; fail closed at the cap.
226
+ if (nameSuggestsPr) { unreadableFiles.push(name); onUnreadable?.(name); }
227
+ }
166
228
  }
167
- return [...records.values()];
229
+ const saved = [...records.values()];
230
+ return { heads: saved.map(({ head }) => head), records: saved, unreadableFiles };
168
231
  }
169
232
 
170
233
  export function savedReviewHeads(pr: string, root = effectiveInstanceRoot()): string[] {
171
- return savedReviewRecords(pr, root).map(({ head }) => head);
234
+ return savedReviewEvidence(pr, root).heads;
235
+ }
236
+
237
+ function isReplaceableErrorArtifact(path: string, pr: string, head: string): boolean {
238
+ try {
239
+ const result = JSON.parse(readFileSync(path, 'utf8')) as Record<string, unknown>;
240
+ const recordPr = typeof result.pr === 'string' ? result.pr
241
+ : Number.isSafeInteger(result.pr) ? String(result.pr) : undefined;
242
+ return recordPr === pr && result.headCommit === head && result.reviewed === false
243
+ && typeof result.error === 'string' && result.error.length > 0;
244
+ } catch { return false; }
172
245
  }
173
246
 
174
247
  function normalizeReviewPrArg(input: string, deps: SelfReviewCliDeps): string | null {
@@ -1371,6 +1444,105 @@ function createDetachedReviewWorktree(commit: string, deps: SelfReviewCliDeps):
1371
1444
  return { cwd, commit, remove };
1372
1445
  }
1373
1446
 
1447
+ // ── 착지 관문(landing gate) — requested/actual ────────────────────────────────────────────
1448
+ //
1449
+ // ⚠️ 왜 생산자(여기)에 있나: 착지 관문(`src/cli/pr-land-overlap-evidence.ts`)도 결과 JSON 을 읽어
1450
+ // 같은 대조를 하지만, 그 파일을 **만드는 자리**가 틀린 requested/actual 을 적으면 관문은 그
1451
+ // 거짓을 근거로 판정한다. 그리고 `acp.reviewDenyBackends` 가 금지한 백엔드가 낸 판정은 파일이
1452
+ // 되기 **전에** 거부돼야 `--save` 로도 착지 근거가 될 수 없다(fail-closed).
1453
+ //
1454
+ // ⛔ 못 잰 값(`unmeasured`)을 «일치»로 적지 않는다 — 부재와 미지를 같은 값으로 두지 않는다.
1455
+
1456
+ /** 사람이 요청한 리뷰어. `value` 는 화면·로그에 그대로 나가는 **요청 원문**이다. */
1457
+ interface LandingGateRequest {
1458
+ kind: 'api-model' | 'acp-backend' | 'role-lane';
1459
+ value: string;
1460
+ /** api-model 요청의 정규화된 기대 모델(짧은 별칭 → 정본 ID). */
1461
+ model?: string;
1462
+ /** acp-backend 요청의 기대 백엔드. */
1463
+ backend?: string;
1464
+ }
1465
+
1466
+ /** 실제로 판정을 낸 리뷰어. `value` 는 실측 원문(`api:<model>` · `acp:<backend>` · `unmeasured`). */
1467
+ interface LandingGateActual {
1468
+ kind: 'api' | 'acp' | 'unknown';
1469
+ value: string;
1470
+ model?: string;
1471
+ backend?: string;
1472
+ respondedProvider?: string;
1473
+ respondedModel?: string;
1474
+ }
1475
+
1476
+ type LandingGateOutcome =
1477
+ | { state: 'consistent'; requested: string; actual: string }
1478
+ | { state: 'mismatch'; requested: string; actual: string; warning: string }
1479
+ | { state: 'denied'; requested: string; actual: string; warning: string; refusal: 'review-forbidden-acp-backend'; deniedBackend: string };
1480
+
1481
+ function normalizedReviewBackend(value: string | undefined): string {
1482
+ return canonicalizeBackendId((value ?? '').trim()).trim().toLowerCase();
1483
+ }
1484
+
1485
+ /** 금지 목록 대조. 문자열 엔트리와 구조형(`{backend, expiresAt}`) 엔트리가 같은 값으로 접힌다. */
1486
+ function deniedReviewBackend(backend: string | undefined, denyBackends: readonly string[]): boolean {
1487
+ const target = normalizedReviewBackend(backend);
1488
+ if (!target) return false;
1489
+ return denyBackends.some((denied) => normalizedReviewBackend(denied) === target);
1490
+ }
1491
+
1492
+ function landingGateAgrees(request: LandingGateRequest, actual: LandingGateActual): boolean {
1493
+ // ⛔ 실측을 못 잰 것을 «일치»로 적지 않는다(요청이 있으면 불일치로 남긴다).
1494
+ if (actual.kind === 'unknown') return false;
1495
+ if (request.kind === 'api-model') {
1496
+ if (actual.kind !== 'api') return false;
1497
+ const requested = (request.model ?? request.value).trim().toLowerCase();
1498
+ const selected = (actual.model ?? '').trim().toLowerCase();
1499
+ if (selected && selected === requested) return true;
1500
+ const respondedProvider = (actual.respondedProvider ?? '').trim().toLowerCase();
1501
+ const respondedModel = (actual.respondedModel ?? '').trim().toLowerCase();
1502
+ // 요청이 공급자 한정 형태(`openrouter/<model>`)일 때만 공급자·응답 모델을 대조한다 —
1503
+ // 맨 모델 ID(`gpt-6-sol`·`claude-sonnet-4`)는 공급자와 비교할 수 없다.
1504
+ const slash = requested.indexOf('/');
1505
+ if (slash <= 0) return false;
1506
+ const requestedProvider = requested.slice(0, slash);
1507
+ const requestedTail = requested.slice(slash + 1);
1508
+ return Boolean(respondedProvider) && respondedProvider === requestedProvider
1509
+ && Boolean(respondedModel) && (respondedModel === requestedTail || respondedModel === requested);
1510
+ }
1511
+ if (request.kind === 'acp-backend') {
1512
+ return actual.kind === 'acp' && normalizedReviewBackend(actual.backend) === normalizedReviewBackend(request.backend);
1513
+ }
1514
+ // role-lane: 설정이 고른 레인은 provider 표기(`claude-acp`)와 백엔드 표기(`claude`)가 달라
1515
+ // 백엔드 이름으로 비교하지 않는다 — 「ACP 레인으로 판정했나」만 본다.
1516
+ return actual.kind === 'acp';
1517
+ }
1518
+
1519
+ function landingGateOutcome(input: {
1520
+ pr: string;
1521
+ reviewRoute: 'api' | 'acp' | 'acp-fallback';
1522
+ request?: LandingGateRequest | undefined;
1523
+ actual: LandingGateActual;
1524
+ deniedBackends: readonly string[];
1525
+ }): LandingGateOutcome {
1526
+ const requested = input.request?.value ?? '(none)';
1527
+ const actual = input.actual.value;
1528
+ if (input.actual.kind === 'acp' && deniedReviewBackend(input.actual.backend, input.deniedBackends)) {
1529
+ const deniedBackend = normalizedReviewBackend(input.actual.backend);
1530
+ return {
1531
+ state: 'denied', requested, actual, refusal: 'review-forbidden-acp-backend', deniedBackend,
1532
+ warning: `⛔ LANDING GATE DENY (fail-closed): requested=${requested} actual=${actual} denied=${deniedBackend} reviewRoute=${input.reviewRoute} — acp.reviewDenyBackends 가 금지한 백엔드가 판정을 냈습니다. PR ${input.pr} 의 이 리뷰는 착지 근거가 아니며 self review 는 실패(rc≠0)로 끝납니다.`,
1533
+ };
1534
+ }
1535
+ if (!input.request || landingGateAgrees(input.request, input.actual)) return { state: 'consistent', requested, actual };
1536
+ const responded = [
1537
+ input.actual.respondedProvider ? `respondedProvider=${input.actual.respondedProvider}` : '',
1538
+ input.actual.respondedModel ? `respondedModel=${input.actual.respondedModel}` : '',
1539
+ ].filter(Boolean).join(' ');
1540
+ return {
1541
+ state: 'mismatch', requested, actual,
1542
+ warning: `⚠️ LANDING GATE REQUESTED/ACTUAL MISMATCH: requested=${requested} actual=${actual} reviewRoute=${input.reviewRoute}${responded ? ` ${responded}` : ''} — 요청한 리뷰어와 실제로 판정을 낸 리뷰어가 다릅니다(PR ${input.pr}). 이 리뷰를 착지 근거로 쓰기 전에 확인하세요.`,
1543
+ };
1544
+ }
1545
+
1374
1546
  /** `elanous self review <pr...>` 본문. read-only — 머지하지 않는다. */
1375
1547
  /**
1376
1548
  * `self review --json` stdout 한 줄 — 결과가 하나면 객체, 여럿이면 배열. index.ts 액션이 이것을 그대로 쓴다.
@@ -1380,27 +1552,13 @@ export function selfReviewJsonLine(results: readonly unknown[]): string {
1380
1552
  return JSON.stringify(results.length === 1 ? results[0] : results) + '\n';
1381
1553
  }
1382
1554
 
1383
- // Normalize only whole-item forms whose complete text is an explicit no-finding
1384
- // sentinel. Any extra clause stays in mustFix (fail-closed).
1385
- const NO_FINDING_REVIEW_NOTE_ALLOWLIST: readonly RegExp[] = [
1386
- /^(?:없음|해당\s*없음|none|nothing|n\/a)[.!。]?$/i,
1387
- /^\((?:없음|해당\s*없음|none|nothing|n\/a)\)[.!。]?$/i,
1388
- /^\(이 조각에서 확인된 코어 결함 없음\s*[—–-]\s*명시 intent 기준 mustFix 는 비움\)$/i,
1389
- /^\(이 조각에서 검증된 (?:차단 )?결함은 발견되지 않음(?:\s*\(mustFix \[\]\))?\)$/i,
1390
- ];
1391
-
1392
- function isNoFindingReviewNote(finding: string): boolean {
1393
- const wholeItem = finding.trim();
1394
- return NO_FINDING_REVIEW_NOTE_ALLOWLIST.some((form) => form.test(wholeItem));
1395
- }
1396
-
1397
1555
  function normalizeSavedReviewMustFix(result: Record<string, unknown>): { result: Record<string, unknown>; count: number } {
1398
1556
  if (!Array.isArray(result.mustFix)) return { result, count: 0 };
1399
1557
  const noFindingNotes = result.mustFix.filter((finding): finding is string =>
1400
- typeof finding === 'string' && isNoFindingReviewNote(finding));
1558
+ typeof finding === 'string' && isNoneSentinelFinding(finding));
1401
1559
  if (noFindingNotes.length === 0) return { result, count: 0 };
1402
1560
  const retained = result.mustFix.filter((finding) =>
1403
- typeof finding !== 'string' || !isNoFindingReviewNote(finding));
1561
+ typeof finding !== 'string' || !isNoneSentinelFinding(finding));
1404
1562
  const existingNotes = Array.isArray(result.noFindingNotes) ? result.noFindingNotes : [];
1405
1563
  return {
1406
1564
  result: { ...result, mustFix: retained, noFindingNotes: [...existingNotes, ...noFindingNotes] },
@@ -1415,6 +1573,141 @@ export async function runSelfReviewCliCommand(
1415
1573
  ): Promise<SelfReviewCliOutcome> {
1416
1574
  const pins: ReviewWorktree[] = [];
1417
1575
  try {
1576
+ // Resolve inside the leaf CLI seam so src/index.ts needs no mix-specific registration.
1577
+ const reviewerMix = opts.reviewerMix === undefined
1578
+ ? loadLlmPolicy().policy.roles.reviewerMix ?? getUserConfig().roleLlm?.reviewerMix ?? DEFAULT_REVIEWER_MIX
1579
+ : opts.reviewerMix;
1580
+ // The route is chosen outside the existing single-review pipeline so explicit CLI flags retain
1581
+ // their exact behavior. A failed reviewer is retried on the same pinned PR diff by the next lane.
1582
+ if (reviewerMix && (opts.save === undefined || /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(opts.save)) &&
1583
+ !opts.model?.trim() && !opts.acp && !opts.acpBackend && !opts.acpModel && !deps.envModel()) {
1584
+ const results: Array<Record<string, unknown>> = [];
1585
+ let failed = 0;
1586
+ for (const arg of prArgs) {
1587
+ const pr = normalizeReviewPrArg(arg, deps);
1588
+ const lanes = pr ? reviewLaneOrder(pr, reviewerMix) : [];
1589
+ if (!pr || lanes.length === 0) {
1590
+ const outcome = await runSelfReviewCliCommandPinned([arg], { ...opts, reviewerMix: undefined }, deps, pins);
1591
+ results.push(...outcome.results);
1592
+ failed += outcome.failed;
1593
+ continue;
1594
+ }
1595
+ const initialHead = prHeadCommit(pr, deps);
1596
+ if (initialHead.state !== 'known') {
1597
+ deps.log('review.lane-head-unknown', { pr, headState: initialHead.state }, { level: 'warn' });
1598
+ results.push({ pr, reviewed: false, refusal: 'reviewer-mix-head-unknown',
1599
+ failureReason: 'PR head could not be pinned before review', reviewerMixPrimaryLane: lanes[0] });
1600
+ failed += 1;
1601
+ continue;
1602
+ }
1603
+ let completed: Record<string, unknown> | undefined;
1604
+ let last: Record<string, unknown> | undefined;
1605
+ let completedLane: ReviewLane | undefined;
1606
+ let attemptedLane: ReviewLane | undefined;
1607
+ let completedWithError = false;
1608
+ const pinnedHead = initialHead.value;
1609
+ const priorEvidence = savedReviewEvidence(pr);
1610
+ const capWasActive = priorEvidence.heads.length + priorEvidence.unreadableFiles.length >= 5;
1611
+ const canonicalResultPath = join(effectiveInstanceRoot(), 'review-results', `pr-${pr}-${pinnedHead.slice(0, 12)}.json`);
1612
+ if (capWasActive && existsSync(canonicalResultPath)
1613
+ && !isReplaceableErrorArtifact(canonicalResultPath, pr, pinnedHead)) {
1614
+ const message = `PR ${pr}: 동일 head 리뷰 결과가 이미 저장되어 있습니다: ${canonicalResultPath}`;
1615
+ results.push({ pr, reviewed: false, refusal: 'cap-refused', existingResult: canonicalResultPath, headCommit: pinnedHead });
1616
+ deps.error(message);
1617
+ failed += 1;
1618
+ continue;
1619
+ }
1620
+ let capClaimed = false;
1621
+ let pendingFallback: { from: ReviewLane; reason: string } | undefined;
1622
+ for (const [index, lane] of lanes.entries()) {
1623
+ const observedHead = prHeadCommit(pr, deps);
1624
+ if (observedHead.state !== 'known' || observedHead.value !== pinnedHead) {
1625
+ last = { pr, reviewed: false, refusal: 'reviewer-mix-head-changed', stale: true,
1626
+ expectedHeadCommit: pinnedHead, headCommit: observedHead.value };
1627
+ deps.log('review.lane-head-changed', { pr, expectedHead: pinnedHead,
1628
+ observedHead: observedHead.value }, { level: 'warn' });
1629
+ break;
1630
+ }
1631
+ const laneOpts: SelfReviewCliOpts = {
1632
+ ...opts, reviewerMix: undefined, reviewerMixPrimaryLane: lanes[0], reviewerMixAttemptLane: lane,
1633
+ reviewerMixExpectedHead: pinnedHead, reviewerMixCapClaimed: capClaimed,
1634
+ disableClaudeGlmFallback: true, json: true,
1635
+ ...(lane === 'claude' ? { acp: true, acpBackend: 'claude', model: undefined } :
1636
+ { acp: false, acpBackend: undefined, model: lane === 'sol' ? 'gpt-6-sol' : ROLE_REVIEWER_FALLBACK_MODEL }),
1637
+ };
1638
+ let reviewerStarted = false;
1639
+ const laneDeps: SelfReviewCliDeps = { ...deps,
1640
+ reviewPullRequest: (input, llm) => { reviewerStarted = true; return deps.reviewPullRequest(input, llm); },
1641
+ observeDoctorReview: (event, data, options) => {
1642
+ if (event === 'cap-override') capClaimed = true;
1643
+ deps.observeDoctorReview?.(event, data, options);
1644
+ },
1645
+ };
1646
+ try {
1647
+ const outcome = await runSelfReviewCliCommandPinned([arg], laneOpts, laneDeps, pins);
1648
+ last = outcome.results[0];
1649
+ completedWithError = last?.reviewed === true && outcome.failed > 0;
1650
+ } catch (error) {
1651
+ last = { pr, reviewed: false, failureReason: safeLogText(error instanceof Error ? error.message : String(error), 400) };
1652
+ }
1653
+ if (last?.refusal === 'reviewer-mix-head-changed' || last?.refusal === 'cap-refused' || last?.stale === true) break;
1654
+ if (reviewerStarted) {
1655
+ attemptedLane = lane;
1656
+ deps.log('review.lane-chosen', { pr, lane, primaryLane: lanes[0], attempt: index + 1, weights: reviewerMix });
1657
+ }
1658
+ if (pendingFallback && reviewerStarted) {
1659
+ deps.log('review.lane-fallback', { pr, from: pendingFallback.from, to: lane,
1660
+ reason: pendingFallback.reason }, { level: 'warn' });
1661
+ pendingFallback = undefined;
1662
+ }
1663
+ if (last?.reviewed === true) {
1664
+ completed = last;
1665
+ completedLane = lane;
1666
+ break;
1667
+ }
1668
+ if (last?.refusal || last?.stale === true || index === lanes.length - 1) break;
1669
+ pendingFallback = { from: lane,
1670
+ reason: safeLogText(String(last?.failureReason ?? last?.error ?? 'reviewed=false'), 400) };
1671
+ }
1672
+ const result = completed ?? last;
1673
+ if (result) {
1674
+ const selectedLane = completedLane ?? attemptedLane;
1675
+ const enriched: Record<string, unknown> = { ...result, reviewerMixPrimaryLane: lanes[0],
1676
+ ...(selectedLane ? { reviewerMixSelectedLane: selectedLane } : {}) };
1677
+ results.push(enriched);
1678
+ if (completed && completed.reviewed === true) {
1679
+ try {
1680
+ mkdirSync(dirname(canonicalResultPath), { recursive: true });
1681
+ const normalized = normalizeSavedReviewMustFix(enriched);
1682
+ if (enriched.headCommit !== pinnedHead || enriched.stale === true
1683
+ || enriched.currentHeadCommit !== pinnedHead) {
1684
+ throw new Error('review result is not pinned to the confirmed current PR head');
1685
+ }
1686
+ writeFileSync(canonicalResultPath, selfReviewJsonLine([normalized.result]));
1687
+ } catch (error) {
1688
+ failed += 1;
1689
+ deps.error(`PR ${pr}: 리뷰 결과 저장 실패 (${canonicalResultPath}): ${error instanceof Error ? error.message : String(error)}`);
1690
+ if (capWasActive) {
1691
+ const authorization = readCapOverride(pr);
1692
+ const claimPath = authorization?.head === pinnedHead.toLowerCase()
1693
+ ? join(effectiveInstanceRoot(), 'review-results', `cap-override-${pr}.json`)
1694
+ : join(effectiveInstanceRoot(), 'review-results', `cap-override-${pr}-head-${pinnedHead.toLowerCase()}.json`);
1695
+ try { unlinkSync(claimPath); } catch { /* already absent or owned by another lane */ }
1696
+ }
1697
+ }
1698
+ } else if (capWasActive && !completed && capClaimed) {
1699
+ const authorization = readCapOverride(pr);
1700
+ const claimPath = authorization?.head === pinnedHead.toLowerCase()
1701
+ ? join(effectiveInstanceRoot(), 'review-results', `cap-override-${pr}.json`)
1702
+ : join(effectiveInstanceRoot(), 'review-results', `cap-override-${pr}-head-${pinnedHead.toLowerCase()}.json`);
1703
+ try { unlinkSync(claimPath); } catch { /* already absent or owned by another lane */ }
1704
+ }
1705
+ if (!opts.json) deps.print(`\n━━ PR ${pr} (${String(enriched.model ?? 'review failed')}) ━━\n${deps.renderReview(enriched as unknown as ReviewResult)}`);
1706
+ }
1707
+ if (!completed || completedWithError) failed += 1;
1708
+ }
1709
+ return { results, failed };
1710
+ }
1418
1711
  return await runSelfReviewCliCommandPinned(prArgs, opts, deps, pins);
1419
1712
  } finally {
1420
1713
  for (const pin of pins) pin.remove();
@@ -1468,8 +1761,10 @@ async function runSelfReviewCliCommandPinned(
1468
1761
  deps.error(message);
1469
1762
  return { results: opts.json ? [{ error: message }] : [], failed: 1 };
1470
1763
  }
1471
- const automaticFallbacks = orderedAcpReviewFallbackBackends(opts.configuredAcpBackend,
1472
- opts.reviewDenyBackends === undefined ? configuredReviewDenyBackends : reviewDenyBackends);
1764
+ // 이 명령이 실제로 적용하는 금지 목록 — 자동 폴백 후보에서 빼는 데 쓰이고(종전과 같은 값),
1765
+ // 이제 착지 관문이 «실제로 판정을 낸 백엔드»를 대조하는 데에도 같은 값으로 쓰인다.
1766
+ const effectiveReviewDenyBackends: string[] = opts.reviewDenyBackends === undefined ? configuredReviewDenyBackends : reviewDenyBackends;
1767
+ const automaticFallbacks = orderedAcpReviewFallbackBackends(opts.configuredAcpBackend, effectiveReviewDenyBackends);
1473
1768
  const fallbackAcpBackends = opts.acpBackend
1474
1769
  ? [canonicalizeBackendId(opts.acpBackend)]
1475
1770
  : automaticFallbacks.backends;
@@ -1520,7 +1815,13 @@ async function runSelfReviewCliCommandPinned(
1520
1815
  const made = deps.makeRoleReviewerLlm
1521
1816
  ? deps.makeRoleReviewerLlm(opts.roleReviewer, { timeoutMs: acpTimeoutSec * 1000 })
1522
1817
  : { reason: 'role-reviewer-seam-missing' };
1523
- if ('llm' in made) roleReviewer = { llm: made.llm, model: `acp:${made.backend}` };
1818
+ // ⛔ 금지 목록(acp.reviewDenyBackends)은 «자동 폴백 후보»만이 아니라 **자동 선택 전부**를 막는다.
1819
+ // 역할 레인은 사람이 친 플래그가 아니라 설정이 고른 자동 선택이므로, 금지된 백엔드가 레인을
1820
+ // 잡으면 그 리뷰가 그대로 착지 근거가 됐다(fail-open). ⇒ 레인을 쓰지 않고 같은 사유 코드로
1821
+ // API 로 내려간다(`role-reviewer-fallback · stage=availability` 가 관측에 남는다).
1822
+ if ('llm' in made && deniedReviewBackend(made.backend, effectiveReviewDenyBackends)) {
1823
+ roleReviewerUnavailableReason = 'denied-by-acp.reviewDenyBackends';
1824
+ } else if ('llm' in made) roleReviewer = { llm: made.llm, model: `acp:${made.backend}` };
1524
1825
  else roleReviewerUnavailableReason = safeLogText(made.reason, 400);
1525
1826
  } catch (error) {
1526
1827
  roleReviewerUnavailableReason = roleAcpErrorReason(error);
@@ -1546,8 +1847,12 @@ async function runSelfReviewCliCommandPinned(
1546
1847
  const providedReviewerContext = loadReviewerContext(opts, deps.readReferencedFile);
1547
1848
  const hasProvidedReviewerContext = (opts.contextOrder?.length ?? 0) > 0 || (opts.context?.length ?? 0) > 0 || (opts.contextText?.length ?? 0) > 0;
1548
1849
  for (const pr of normalizedPrArgs as string[]) {
1549
- const previousHeads = savedReviewHeads(pr);
1550
- const previousRoundCount = previousHeads.length;
1850
+ const savedEvidence = savedReviewEvidence(pr, undefined, (file) => {
1851
+ deps.observeDoctorReview?.('saved-result-unreadable', { pr, file }, { level: 'warn' });
1852
+ });
1853
+ const previousHeads = savedEvidence.heads;
1854
+ const previousRoundCount = previousHeads.length + savedEvidence.unreadableFiles.length;
1855
+ const shouldPersistCapResult = previousRoundCount >= 5 && !opts.reviewerMixPrimaryLane;
1551
1856
  const reason = opts.overCapReason?.trim();
1552
1857
  const authorization = previousRoundCount >= 5 ? readCapOverride(pr) : undefined;
1553
1858
  deps.observeDoctorReview?.('round-counted', { pr, previousRoundCount, round: previousRoundCount + 1 });
@@ -1555,7 +1860,7 @@ async function runSelfReviewCliCommandPinned(
1555
1860
  if (previousRoundCount >= 5) {
1556
1861
  deps.observeDoctorReview?.('cap-attempt', { pr, previousRoundCount, round: previousRoundCount + 1, reasonProvided: Boolean(reason), opAllowed }, { level: 'warn' });
1557
1862
  }
1558
- if (previousRoundCount >= 5 && (!opAllowed || (!authorization && !reason))) {
1863
+ if (previousRoundCount >= 5 && (!opAllowed || (!authorization && !reason && !opts.reviewerMixCapClaimed))) {
1559
1864
  const refusalHead = prHeadCommit(pr, deps).value;
1560
1865
  const message = !opAllowed
1561
1866
  ? `PR ${pr}: r5 상한 초과 리뷰는 OP traffic.json reviewOverCapAllow 허용 목록에 있어야 합니다.`
@@ -1567,11 +1872,45 @@ async function runSelfReviewCliCommandPinned(
1567
1872
  continue;
1568
1873
  }
1569
1874
  const diffRead = readDiffAtHead(pr, deps);
1570
- if (previousRoundCount >= 5 && authorization && diffRead.headCommit.toLowerCase() === authorization.head) {
1571
- const message = `PR ${pr}: 동일 head는 이미 승인 소모 기록이 있습니다.`;
1875
+ const canonicalResultPath = COMMIT_OID.test(diffRead.headCommit)
1876
+ ? join(effectiveInstanceRoot(), 'review-results', `pr-${pr}-${diffRead.headCommit.slice(0, 12)}.json`)
1877
+ : undefined;
1878
+ let claimedCapPath: string | undefined;
1879
+ const releaseCapClaim = (): void => {
1880
+ if (!claimedCapPath || opts.reviewerMixPrimaryLane) return;
1881
+ try {
1882
+ unlinkSync(claimedCapPath);
1883
+ claimedCapPath = undefined;
1884
+ } catch (error) {
1885
+ deps.error(`PR ${pr}: 상한 예외 사용 기록 롤백 실패 (${claimedCapPath}): ${error instanceof Error ? error.message : String(error)}`);
1886
+ }
1887
+ };
1888
+ if (shouldPersistCapResult && canonicalResultPath && existsSync(canonicalResultPath)
1889
+ && !isReplaceableErrorArtifact(canonicalResultPath, pr, diffRead.headCommit)) {
1890
+ const message = `PR ${pr}: 동일 head 리뷰 결과가 이미 저장되어 있습니다: ${canonicalResultPath}`;
1891
+ deps.error(message);
1892
+ results.push({ pr, error: message, reviewed: false, refusal: 'cap-refused', previousRoundCount, headCommit: diffRead.headCommit, existingResult: canonicalResultPath });
1893
+ failed += 1;
1894
+ continue;
1895
+ }
1896
+ if (opts.reviewerMixExpectedHead && diffRead.headCommit !== opts.reviewerMixExpectedHead) {
1897
+ const refusal = { pr, reviewed: false, verdict: null, mustFix: [], shouldFix: [],
1898
+ refusal: 'reviewer-mix-head-changed', stale: true, headCommit: diffRead.headCommit,
1899
+ expectedHeadCommit: opts.reviewerMixExpectedHead };
1900
+ deps.log('review.lane-head-changed', { pr, expectedHead: opts.reviewerMixExpectedHead,
1901
+ observedHead: diffRead.headCommit }, { level: 'warn' });
1902
+ if (opts.json || opts.save !== undefined) results.push(refusal);
1903
+ failed += 1;
1904
+ continue;
1905
+ }
1906
+ if (previousRoundCount >= 5 && authorization && diffRead.headCommit.toLowerCase() === authorization.head && !opts.reviewerMixCapClaimed) {
1907
+ const resultExists = canonicalResultPath && existsSync(canonicalResultPath);
1908
+ const message = resultExists
1909
+ ? `PR ${pr}: 동일 head 리뷰 결과가 이미 저장되어 있습니다: ${canonicalResultPath}`
1910
+ : `PR ${pr}: 동일 head는 이미 승인 소모 기록이 있습니다. 결과 파일을 찾을 수 없어 재리뷰하지 않습니다.`;
1572
1911
  deps.observeDoctorReview?.('cap-refused', { pr, head: diffRead.headCommit, previousRoundCount, round: previousRoundCount + 1, reason: 'same-head-already-used' }, { level: 'warn' });
1573
1912
  deps.error(message);
1574
- if (opts.json || opts.save !== undefined) results.push({ pr, error: message, reviewed: false, refusal: 'cap-refused', previousRoundCount, headCommit: diffRead.headCommit });
1913
+ if (opts.json || opts.save !== undefined || resultExists) results.push({ pr, error: message, reviewed: false, refusal: 'cap-refused', previousRoundCount, headCommit: diffRead.headCommit, ...(resultExists ? { existingResult: canonicalResultPath } : {}) });
1575
1914
  failed += 1;
1576
1915
  continue;
1577
1916
  }
@@ -1584,7 +1923,7 @@ async function runSelfReviewCliCommandPinned(
1584
1923
  continue;
1585
1924
  }
1586
1925
  let mergeOnlyDeltaRange: { base: string; head: string } | undefined;
1587
- if (previousRoundCount >= 5 && authorization) {
1926
+ if (previousRoundCount >= 5 && authorization && !opts.reviewerMixCapClaimed) {
1588
1927
  const git = deps.git;
1589
1928
  if (!git || diffRead.source !== 'sha-pinned' || diffRead.headCommitState !== 'known'
1590
1929
  || diffRead.currentHeadCommitState !== 'known' || diffRead.stale || diffRead.diff.status !== 0) {
@@ -1594,7 +1933,7 @@ async function runSelfReviewCliCommandPinned(
1594
1933
  failed += 1;
1595
1934
  continue;
1596
1935
  }
1597
- const savedRecords = savedReviewRecords(pr);
1936
+ const savedRecords = savedEvidence.records;
1598
1937
  const latestReviewed = savedRecords.reduce<(typeof savedRecords)[number] | undefined>(
1599
1938
  (latest, record) => !latest || record.reviewedAt > latest.reviewedAt ? record : latest, undefined,
1600
1939
  );
@@ -1629,7 +1968,9 @@ async function runSelfReviewCliCommandPinned(
1629
1968
  }
1630
1969
  if (commits.stdout.trim() === '') mergeOnlyDeltaRange = { base: classificationBase, head: diffRead.headCommit.toLowerCase() };
1631
1970
  }
1632
- } else if (previousRoundCount >= 5 && reason) {
1971
+ } else if (previousRoundCount >= 5 && reason && !opts.reviewerMixCapClaimed) {
1972
+ // Claim only the exact SHA-pinned diff that the reviewer will see; never spend an OP
1973
+ // authorization on a separately read head that may move before readDiffAtHead.
1633
1974
  const claimable = diffRead.source === 'sha-pinned' && diffRead.headCommitState === 'known'
1634
1975
  && diffRead.currentHeadCommitState === 'known' && !diffRead.stale && diffRead.diff.status === 0;
1635
1976
  if (!claimable || !claimCapOverride(pr, diffRead.headCommit, reason, deps.now())) {
@@ -1640,6 +1981,9 @@ async function runSelfReviewCliCommandPinned(
1640
1981
  failed += 1;
1641
1982
  continue;
1642
1983
  }
1984
+ claimedCapPath = join(effectiveInstanceRoot(), 'review-results', authorization
1985
+ ? `cap-override-${pr}-head-${diffRead.headCommit.toLowerCase()}.json`
1986
+ : `cap-override-${pr}.json`);
1643
1987
  deps.observeDoctorReview?.('cap-override', { pr, head: diffRead.headCommit, previousRoundCount, round: previousRoundCount + 1, reason }, { level: 'warn' });
1644
1988
  }
1645
1989
  if (mergeOnlyDeltaRange) {
@@ -1648,19 +1992,24 @@ async function runSelfReviewCliCommandPinned(
1648
1992
  const message = `PR ${pr}: merge-only 후속 head의 delta diff를 고정할 수 없습니다.`;
1649
1993
  deps.error(message);
1650
1994
  if (opts.json || opts.save !== undefined) results.push({ pr, error: message, reviewed: false, refusal: 'cap-refused', previousRoundCount, headCommit: diffRead.headCommit });
1995
+ releaseCapClaim();
1651
1996
  failed += 1;
1652
1997
  continue;
1653
1998
  }
1654
1999
  diffRead.diff = delta;
1655
2000
  diffRead.baseCommit = mergeOnlyDeltaRange.base;
1656
2001
  }
1657
- if (previousRoundCount >= 5 && authorization && !claimCapOverride(pr, diffRead.headCommit, authorization.reason, deps.now())) {
2002
+ if (previousRoundCount >= 5 && authorization && !opts.reviewerMixCapClaimed && !claimCapOverride(pr, diffRead.headCommit, authorization.reason, deps.now())) {
1658
2003
  const message = `PR ${pr}: 후속 head 승인 사용 기록을 남기지 못했습니다.`;
1659
2004
  deps.error(message);
1660
2005
  if (opts.json || opts.save !== undefined) results.push({ pr, error: message, reviewed: false, refusal: 'cap-refused', previousRoundCount, headCommit: diffRead.headCommit });
1661
2006
  failed += 1;
1662
2007
  continue;
1663
2008
  }
2009
+ if (previousRoundCount >= 5 && authorization && !opts.reviewerMixCapClaimed) {
2010
+ claimedCapPath = join(effectiveInstanceRoot(), 'review-results', `cap-override-${pr}-head-${diffRead.headCommit.toLowerCase()}.json`);
2011
+ deps.observeDoctorReview?.('cap-override', { pr, head: diffRead.headCommit, previousRoundCount, round: previousRoundCount + 1, reason: authorization.reason }, { level: 'warn' });
2012
+ }
1664
2013
  const localHeadComparison = compareLocalHead(pr, { value: diffRead.headCommit, state: diffRead.headCommitState }, deps);
1665
2014
  const { diff } = diffRead;
1666
2015
  const baseReviewerContext = hasProvidedReviewerContext
@@ -1751,8 +2100,24 @@ async function runSelfReviewCliCommandPinned(
1751
2100
  { pr: safeLogText(pr), exit: diff.status, error: safeLogText(diff.stderr || err, 160) },
1752
2101
  { level: 'warn' });
1753
2102
  const contextOutput = reviewerContextOutput(reviewerContext, decompositionContext);
1754
- if (opts.json || opts.save !== undefined) results.push({ pr, error: err, ...contextOutput, ...comparisonData, ...(mismatchWarning ?? {}) });
2103
+ const errorResult = { pr, error: err, reviewed: false, headCommit: diffRead.headCommit,
2104
+ currentHeadCommit: diffRead.currentHeadCommit, stale: diffRead.stale,
2105
+ ...contextOutput, ...comparisonData, ...(mismatchWarning ?? {}) };
2106
+ if (opts.json || opts.save !== undefined) results.push(errorResult);
2107
+ if (canonicalResultPath && COMMIT_OID.test(diffRead.headCommit)
2108
+ && diffRead.headCommitState === 'known' && diffRead.currentHeadCommitState === 'known'
2109
+ && diffRead.currentHeadCommit === diffRead.headCommit && !diffRead.stale) {
2110
+ try {
2111
+ if (!existsSync(canonicalResultPath) || isReplaceableErrorArtifact(canonicalResultPath, pr, diffRead.headCommit)) {
2112
+ mkdirSync(dirname(canonicalResultPath), { recursive: true });
2113
+ writeFileSync(canonicalResultPath, selfReviewJsonLine([errorResult]));
2114
+ }
2115
+ } catch (error) {
2116
+ deps.error(`PR ${pr}: 오류 리뷰 결과 저장 실패 (${canonicalResultPath}): ${error instanceof Error ? error.message : String(error)}`);
2117
+ }
2118
+ }
1755
2119
  if (!opts.json) deps.error(`PR ${pr}: ${err}\n${reviewerContextStatus}`);
2120
+ releaseCapClaim();
1756
2121
  failed += 1;
1757
2122
  continue;
1758
2123
  }
@@ -1814,6 +2179,7 @@ async function runSelfReviewCliCommandPinned(
1814
2179
  };
1815
2180
  if (opts.json || opts.save !== undefined) results.push(refused);
1816
2181
  if (!opts.json) deps.print(`\n━━ PR ${pr} (${primaryModel}) ━━\n${renderStaleRefusal(diffRead)}\n${reviewerContextStatus}`);
2182
+ releaseCapClaim();
1817
2183
  continue;
1818
2184
  }
1819
2185
  // ⭐ 머지된 PR 만 머지 커밋 detached worktree 에서 리뷰한다 — main 최신이 아니라 그 PR 이 머지된 시점의 코드를 본다.
@@ -1867,6 +2233,7 @@ async function runSelfReviewCliCommandPinned(
1867
2233
  ...(deps.readReferencedFile ? { readReferencedFile: deps.readReferencedFile } : {}),
1868
2234
  ...(goalAcceptance.acceptance ? { acceptance: goalAcceptance.acceptance } : {}),
1869
2235
  };
2236
+ const intentFingerprint = reviewIntentFingerprint(reviewInput.phaseIntent);
1870
2237
  // ⭐ Role-config ACP lane: a completed ACP review is used as-is; anything else (throw, reviewed≠true)
1871
2238
  // falls to the same API review this command ran before the lane existed — and is observed.
1872
2239
  // ⚠️ Stricter than the harness lane on purpose: `runSubscriptionReviewer` accepts any non-empty text;
@@ -1936,7 +2303,7 @@ async function runSelfReviewCliCommandPinned(
1936
2303
  // An explicit `--acp-model` is the operator's model choice: it is not silently swapped for GLM.
1937
2304
  // GLM that throws or also ends without a verdict keeps the claude result, and says so (`glmReviewed`).
1938
2305
  let claudeFallbackGlm: { reason: string; model: string; glmReviewed: boolean; glmFailureReason?: string } | undefined;
1939
- if (useAcp && !roleReview && !acpModel && canonicalizeBackendId(acpBackend) === 'claude' && review.reviewed !== true) {
2306
+ if (useAcp && !roleReview && !acpModel && !opts.disableClaudeGlmFallback && canonicalizeBackendId(acpBackend) === 'claude' && review.reviewed !== true) {
1940
2307
  const reason = safeLogText(review.failureReason || 'reviewed=false', 400);
1941
2308
  debug.log('review.acp', 'claude-fallback-glm', { reason }, { level: 'warn' });
1942
2309
  fallbackAttempted = true;
@@ -1967,6 +2334,50 @@ async function runSelfReviewCliCommandPinned(
1967
2334
  ...(reviewPin && reviewedOnPin ? { reviewCwdCommit: reviewPin.commit } : {}),
1968
2335
  ...(reviewCwdPinFallbackReason ? { reviewCwdPinFallbackReason } : {}),
1969
2336
  };
2337
+ // ⭐ 착지 관문 — 요청한 리뷰어와 실제로 판정을 낸 리뷰어를 대조한다. 판정이 나온 리뷰에만
2338
+ // 의미가 있다(리뷰가 없으면 근거도 없고, 실패는 기존 회계가 이미 센다).
2339
+ const acpActual = (backendText: string | undefined): LandingGateActual => {
2340
+ const backend = normalizedReviewBackend(backendText);
2341
+ return backend ? { kind: 'acp', value: `acp:${backend}`, backend } : { kind: 'unknown', value: 'unmeasured' };
2342
+ };
2343
+ const landingGateRequest: LandingGateRequest | undefined = requestedModel
2344
+ ? { kind: 'api-model', value: opts.model?.trim() || requestedModel, model: explicitApiModel ?? requestedModel }
2345
+ : useAcp
2346
+ ? { kind: 'acp-backend', value: `acp:${canonicalizeBackendId(acpBackend)}`, backend: acpBackend }
2347
+ : reviewerSource === 'role-config' && opts.roleReviewer
2348
+ ? { kind: 'role-lane', value: `acp:${opts.roleReviewer.provider}` }
2349
+ : undefined;
2350
+ const landingGateActual: LandingGateActual = reviewRoute === 'acp-fallback'
2351
+ ? acpActual(fallbackBackend)
2352
+ : reviewRoute === 'acp'
2353
+ ? acpActual(roleReview && prRoleReviewer ? prRoleReviewer.model.replace(/^acp:/, '') : acpBackend)
2354
+ : ((): LandingGateActual => {
2355
+ const apiModel = (selectedModel ?? '').trim();
2356
+ if (!apiModel) return { kind: 'unknown', value: 'unmeasured' };
2357
+ return {
2358
+ kind: 'api', value: `api:${apiModel}`, model: apiModel,
2359
+ ...(responded.provider ? { respondedProvider: responded.provider } : {}),
2360
+ ...(responded.model ? { respondedModel: responded.model } : {}),
2361
+ };
2362
+ })();
2363
+ const landingGate = review.reviewed === true
2364
+ ? landingGateOutcome({ pr, reviewRoute, request: landingGateRequest, actual: landingGateActual, deniedBackends: effectiveReviewDenyBackends })
2365
+ : undefined;
2366
+ if (landingGate && landingGate.state !== 'consistent') {
2367
+ deps.error(landingGate.warning);
2368
+ deps.log('landing-gate', {
2369
+ pr: safeLogText(pr), decision: landingGate.state, reviewRoute,
2370
+ requested: safeLogText(landingGate.requested), actual: safeLogText(landingGate.actual),
2371
+ ...(landingGate.state === 'denied' ? { deniedBackend: landingGate.deniedBackend } : {}),
2372
+ }, { level: 'warn' });
2373
+ }
2374
+ const landingGateAdmitted = landingGate?.state !== 'denied';
2375
+ const landingGateDeniedBackend = landingGate?.state === 'denied' ? landingGate.deniedBackend : undefined;
2376
+ const landingGateFields: Record<string, unknown> = landingGate?.state === 'mismatch'
2377
+ ? { landingGateDecision: 'mismatch', landingGateRequested: landingGate.requested, landingGateActual: landingGate.actual }
2378
+ : landingGate?.state === 'denied'
2379
+ ? { landingGateDecision: 'denied', landingGateRequested: landingGate.requested, landingGateActual: landingGate.actual, landingGateDeniedBackend: landingGate.deniedBackend }
2380
+ : {};
1970
2381
  if (!fallbackAttempted) {
1971
2382
  fallbackNotAttemptedReason = useAcp ? 'explicit-acp' : requestedModel && review.reviewed !== true ? 'explicit-model' : 'primary-review-succeeded';
1972
2383
  }
@@ -1991,13 +2402,33 @@ async function runSelfReviewCliCommandPinned(
1991
2402
  const reviewerSelection = {
1992
2403
  ...reviewerSourceObservation(),
1993
2404
  ...(roleReviewerFallbackReason !== undefined ? { roleReviewerFallbackReason } : {}),
2405
+ ...(opts.reviewerMixPrimaryLane ? { reviewerMixPrimaryLane: opts.reviewerMixPrimaryLane,
2406
+ reviewerMixSelectedLane: opts.reviewerMixAttemptLane } : {}),
1994
2407
  };
2408
+ // The reviewer owns its finding severity. A missing citation is separate evidence,
2409
+ // never a new must-fix or a verified defect. Keep unreadable heads distinct too.
2410
+ const reviewRepositoryPath = deps.reviewRepositoryPath;
2411
+ const findingPathHeadChecks = review.reviewed === true && landingGateAdmitted
2412
+ && diffRead.headCommitState === 'known' && reviewRepositoryPath
2413
+ ? ([...review.mustFix.map(finding => ({ severity: 'mustFix' as const, finding })),
2414
+ ...review.shouldFix.map(finding => ({ severity: 'shouldFix' as const, finding }))]
2415
+ .map(({ severity, finding }) => ({ severity, finding,
2416
+ ...checkCitedReviewPathsAtHead(finding, reviewRepositoryPath, diffRead.headCommit) }))
2417
+ .filter(check => check.paths.length > 0))
2418
+ : [];
2419
+ const pathWarnings = findingPathHeadChecks.flatMap(check => check.paths
2420
+ .filter(path => path.status !== 'present')
2421
+ .map(path => `${path.status === 'missing' ? '경로 미확인' : '경로 검증 불가'}: ${path.path} (PR head ${check.head})`));
2422
+ if (pathWarnings.length) deps.log('finding-path-head-check', {
2423
+ pr, headCommit: diffRead.headCommit, missing: findingPathHeadChecks.flatMap(check => check.paths).filter(path => path.status === 'missing').length,
2424
+ unreadable: findingPathHeadChecks.flatMap(check => check.paths).filter(path => path.status === 'unreadable').length,
2425
+ }, { level: 'warn' });
1995
2426
  // ⚠️ verdict=fail 은 **정상 동작**(리뷰가 일한 것)이라 warn 으로 올리지 않는다. 대신 `reviewed:false`
1996
2427
  // (fail-soft pass·리뷰 미실행)는 조용히 통과로 읽히면 위험하므로 warn 이다.
1997
2428
  deps.log('done',
1998
2429
  {
1999
2430
  ...buildReviewObservation({
2000
- pr, model: selectedModel, intent, review, durationMs: deps.now() - startedAt,
2431
+ pr, model: selectedModel, intent, intentFingerprint, review, durationMs: deps.now() - startedAt,
2001
2432
  ...reviewProvenance,
2002
2433
  ...intentTruncation,
2003
2434
  ...reviewDiffBudgetObservation(review),
@@ -2011,6 +2442,7 @@ async function runSelfReviewCliCommandPinned(
2011
2442
  acceptanceChars: goalAcceptance.acceptanceChars,
2012
2443
  ...reviewerSelection,
2013
2444
  ...reviewCwdOutput,
2445
+ ...landingGateFields,
2014
2446
  },
2015
2447
  { level: review.reviewed === false ? 'warn' : 'info' });
2016
2448
  // --acp 는 대표가 명시 요청한 리뷰 — fail-soft(reviewed=false)면 조용히 pass 하지 말고 표면화(관측=acp-review).
@@ -2019,17 +2451,43 @@ async function runSelfReviewCliCommandPinned(
2019
2451
  const contextOutput = reviewerContextOutput(reviewerContext, decompositionContext);
2020
2452
  if (opts.json || opts.save !== undefined) results.push({ pr, error: err, ...review, verdict: null, ...reviewProvenance, ...respondedOutput, ...reviewerSelection, ...reviewCwdOutput, ...provenance, ...contextOutput });
2021
2453
  if (!opts.json) deps.error(`PR ${pr}: ${err}\n${renderProvenance(diffRead)}\n${reviewerContextStatus}`);
2454
+ releaseCapClaim();
2022
2455
  failed += 1;
2023
2456
  continue;
2024
2457
  }
2025
2458
  const contextOutput = reviewerContextOutput(reviewerContext, decompositionContext);
2026
2459
  const jsonReview = review.reviewed === true ? review : { ...review, verdict: null };
2027
- if (opts.json || opts.save !== undefined) results.push({ pr, model: selectedModel, ...jsonReview, ...reviewProvenance, ...respondedOutput, ...reviewerSelection, ...reviewCwdOutput, ...provenance, ...contextOutput, evidence: evidence.evidence, prDiffFiles: evidence.prDiffFiles, ...(evidence.authoredFiles !== undefined ? { authoredFiles: evidence.authoredFiles } : {}) });
2028
- if (!opts.json) deps.print(`\n━━ PR ${pr} (${selectedModel}) ━━\n${renderProvenance(diffRead)}\n${evidenceLine}\n${reviewerContextStatus}\n${deps.renderReview(review)}`);
2460
+ // ⛔ 금지 백엔드가 낸 판정은 «근거 거부»다 — verdict·mustFix 를 그대로 실어 보내면 읽는 쪽이
2461
+ // 그것을 착지 판정으로 쓴다(관문도 reviewed===true 를 근거 조건으로 본다).
2462
+ const refusalOverride = landingGateAdmitted ? {} : { reviewed: false, verdict: null, refusal: 'review-forbidden-acp-backend' as const };
2463
+ const resultOutput = { pr, model: selectedModel, ...jsonReview, ...reviewProvenance, ...respondedOutput, ...reviewerSelection, ...reviewCwdOutput, ...provenance, ...contextOutput, ...refusalOverride, ...landingGateFields, findingPathHeadChecks, pathWarnings, evidence: evidence.evidence, prDiffFiles: evidence.prDiffFiles, ...(evidence.authoredFiles !== undefined ? { authoredFiles: evidence.authoredFiles } : {}) };
2464
+ if (opts.json || opts.save !== undefined || shouldPersistCapResult) results.push(resultOutput);
2465
+ if (!shouldPersistCapResult
2466
+ && landingGateAdmitted && review.reviewed === true && diffRead.headCommitState === 'known'
2467
+ && diffRead.currentHeadCommitState === 'known' && diffRead.currentHeadCommit === diffRead.headCommit
2468
+ && !diffRead.stale && COMMIT_OID.test(diffRead.headCommit)) {
2469
+ const path = canonicalResultPath ?? join(effectiveInstanceRoot(), 'review-results', `pr-${pr}-${diffRead.headCommit.slice(0, 12)}.json`);
2470
+ const normalized = normalizeSavedReviewMustFix(resultOutput);
2471
+ try {
2472
+ mkdirSync(dirname(path), { recursive: true });
2473
+ // A successful review replaces an earlier error artifact for the same PR head.
2474
+ writeFileSync(path, selfReviewJsonLine([normalized.result]));
2475
+ if (normalized.count > 0 && opts.save === undefined) deps.log('mustfix-normalized', { pr, head: diffRead.headCommit, count: normalized.count });
2476
+ } catch (error) {
2477
+ deps.error(`PR ${pr}: 리뷰 결과 저장 실패 (${path}): ${error instanceof Error ? error.message : String(error)}`);
2478
+ }
2479
+ }
2480
+ if (!opts.json) {
2481
+ if (!landingGateAdmitted) deps.print(`\n━━ PR ${pr} (${selectedModel}) ━━\n${renderProvenance(diffRead)}\n⛔ LANDING GATE: review-forbidden-acp-backend (${landingGateDeniedBackend ?? 'unknown'}) — 이 리뷰는 착지 근거가 아닙니다.\n${reviewerContextStatus}`);
2482
+ else deps.print(`\n━━ PR ${pr} (${selectedModel}) ━━\n${renderProvenance(diffRead)}\n${evidenceLine}\n${reviewerContextStatus}\n${deps.renderReview(review)}${pathWarnings.length ? `\n${pathWarnings.join('\n')}` : ''}`);
2483
+ }
2029
2484
  // REVIEW-ERROR-RC — 기본(API)·role 경로가 1차·폴백 모두 실패해 reviewed≠true 로 끝나도 «못 리뷰한 PR» 이다(verdict:null).
2030
- if (review.reviewed !== true) failed += 1;
2485
+ // ⛔ 착지 관문 거부도 같은 회계다 — 금지 백엔드가 판정을 냈으면 리뷰가 «성공»해도 근거가 아니다(rc≠0).
2486
+ if (review.reviewed !== true || !landingGateAdmitted) failed += 1;
2031
2487
  if (opts.save !== undefined) {
2032
- if (review.reviewed !== true || diffRead.headCommitState !== 'known' || diffRead.currentHeadCommitState !== 'known' || diffRead.currentHeadCommit !== diffRead.headCommit || diffRead.headCommit.length !== 40) {
2488
+ if (!landingGateAdmitted) {
2489
+ deps.error(`PR ${pr}: --save 거부 — acp.reviewDenyBackends 가 금지한 백엔드(${landingGateDeniedBackend ?? 'unknown'})의 결과는 착지 근거로 저장하지 않습니다.`);
2490
+ } else if (review.reviewed !== true || diffRead.headCommitState !== 'known' || diffRead.currentHeadCommitState !== 'known' || diffRead.currentHeadCommit !== diffRead.headCommit || diffRead.headCommit.length !== 40) {
2033
2491
  deps.error(`PR ${pr}: --save 불가 — 완료된 리뷰와 확인된 40자 PR head가 필요합니다.`);
2034
2492
  if (review.reviewed === true) failed += 1;
2035
2493
  } else {
@@ -2049,6 +2507,28 @@ async function runSelfReviewCliCommandPinned(
2049
2507
  }
2050
2508
  }
2051
2509
  }
2510
+ if (shouldPersistCapResult) {
2511
+ if (!landingGateAdmitted || review.reviewed !== true || !canonicalResultPath
2512
+ || diffRead.headCommitState !== 'known' || diffRead.currentHeadCommitState !== 'known'
2513
+ || diffRead.currentHeadCommit !== diffRead.headCommit || !COMMIT_OID.test(diffRead.headCommit)) {
2514
+ releaseCapClaim();
2515
+ deps.error(`PR ${pr}: 상한 예외 리뷰 결과가 저장 가능한 상태가 아니어서 승인 사용 기록을 되돌렸습니다.`);
2516
+ } else {
2517
+ const rawResult = results.at(-1);
2518
+ const normalized = rawResult && typeof rawResult === 'object'
2519
+ ? normalizeSavedReviewMustFix(rawResult)
2520
+ : { result: rawResult, count: 0 };
2521
+ try {
2522
+ mkdirSync(dirname(canonicalResultPath), { recursive: true });
2523
+ writeFileSync(canonicalResultPath, selfReviewJsonLine([normalized.result]));
2524
+ if (normalized.count > 0) deps.log('mustfix-normalized', { pr, head: diffRead.headCommit, count: normalized.count });
2525
+ } catch (error) {
2526
+ releaseCapClaim();
2527
+ failed += 1;
2528
+ deps.error(`PR ${pr}: 상한 예외 리뷰 결과 저장 실패${canonicalResultPath ? ` (${canonicalResultPath})` : ''}: ${error instanceof Error ? error.message : String(error)}`);
2529
+ }
2530
+ }
2531
+ }
2052
2532
  }
2053
2533
  return { results, failed };
2054
2534
  }