@humanbased/crosscheck 1.2.0 → 1.3.0-beta.100

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 (438) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +183 -375
  3. package/README.zh.md +1 -1
  4. package/assets/icon-256.png +0 -0
  5. package/assets/linear-comment.svg +18 -0
  6. package/assets/linear-onboard.svg +30 -0
  7. package/assets/linear-status.svg +23 -0
  8. package/assets/linear-test.svg +34 -0
  9. package/assets/skills/code-review/.crosscheck-skill.json +9 -0
  10. package/assets/skills/code-review/LICENSE +21 -0
  11. package/assets/skills/code-review/SKILL.md +89 -0
  12. package/assets/skills/code-review/agents/openai.yaml +3 -0
  13. package/assets/skills/code-review-skill/.crosscheck-skill.json +9 -0
  14. package/assets/skills/code-review-skill/LICENSE +21 -0
  15. package/assets/skills/code-review-skill/SKILL.md +231 -0
  16. package/assets/skills/code-review-skill/assets/pr-review-template.md +137 -0
  17. package/assets/skills/code-review-skill/assets/review-checklist.md +123 -0
  18. package/assets/skills/code-review-skill/reference/angular.md +768 -0
  19. package/assets/skills/code-review-skill/reference/architecture-review-guide.md +472 -0
  20. package/assets/skills/code-review-skill/reference/c.md +890 -0
  21. package/assets/skills/code-review-skill/reference/code-quality-universal.md +488 -0
  22. package/assets/skills/code-review-skill/reference/code-review-best-practices.md +136 -0
  23. package/assets/skills/code-review-skill/reference/common-bugs-checklist.md +286 -0
  24. package/assets/skills/code-review-skill/reference/cpp.md +893 -0
  25. package/assets/skills/code-review-skill/reference/cross-cutting/async-concurrency-patterns.md +515 -0
  26. package/assets/skills/code-review-skill/reference/cross-cutting/error-handling-principles.md +492 -0
  27. package/assets/skills/code-review-skill/reference/cross-cutting/n-plus-one-queries.md +309 -0
  28. package/assets/skills/code-review-skill/reference/cross-cutting/sql-injection-prevention.md +308 -0
  29. package/assets/skills/code-review-skill/reference/cross-cutting/xss-prevention.md +264 -0
  30. package/assets/skills/code-review-skill/reference/csharp.md +525 -0
  31. package/assets/skills/code-review-skill/reference/css-less-sass.md +661 -0
  32. package/assets/skills/code-review-skill/reference/django.md +985 -0
  33. package/assets/skills/code-review-skill/reference/fastapi.md +580 -0
  34. package/assets/skills/code-review-skill/reference/go.md +993 -0
  35. package/assets/skills/code-review-skill/reference/java.md +409 -0
  36. package/assets/skills/code-review-skill/reference/java8.md +586 -0
  37. package/assets/skills/code-review-skill/reference/kotlin.md +1018 -0
  38. package/assets/skills/code-review-skill/reference/nestjs.md +593 -0
  39. package/assets/skills/code-review-skill/reference/performance-review-guide.md +816 -0
  40. package/assets/skills/code-review-skill/reference/php.md +684 -0
  41. package/assets/skills/code-review-skill/reference/python.md +1073 -0
  42. package/assets/skills/code-review-skill/reference/qt.md +757 -0
  43. package/assets/skills/code-review-skill/reference/react.md +871 -0
  44. package/assets/skills/code-review-skill/reference/ruby.md +964 -0
  45. package/assets/skills/code-review-skill/reference/rust.md +846 -0
  46. package/assets/skills/code-review-skill/reference/security-review-guide.md +494 -0
  47. package/assets/skills/code-review-skill/reference/svelte.md +1064 -0
  48. package/assets/skills/code-review-skill/reference/swift.md +936 -0
  49. package/assets/skills/code-review-skill/reference/typescript.md +1016 -0
  50. package/assets/skills/code-review-skill/reference/vue.md +924 -0
  51. package/assets/skills/code-review-skill/reference/zig.md +440 -0
  52. package/assets/skills/code-review-skill/scripts/pr-analyzer.py +435 -0
  53. package/assets/skills/code-review-skill/scripts/test_pr_analyzer.py +380 -0
  54. package/assets/skills/codebase-design/.crosscheck-skill.json +9 -0
  55. package/assets/skills/codebase-design/DEEPENING.md +37 -0
  56. package/assets/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
  57. package/assets/skills/codebase-design/LICENSE +21 -0
  58. package/assets/skills/codebase-design/SKILL.md +114 -0
  59. package/assets/skills/codebase-design/agents/openai.yaml +3 -0
  60. package/assets/skills/diagnosing-bugs/.crosscheck-skill.json +9 -0
  61. package/assets/skills/diagnosing-bugs/LICENSE +21 -0
  62. package/assets/skills/diagnosing-bugs/SKILL.md +134 -0
  63. package/assets/skills/diagnosing-bugs/agents/openai.yaml +3 -0
  64. package/assets/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
  65. package/crosscheck.config.example.yml +124 -11
  66. package/dist/__tests__/adoption.test.d.ts +2 -0
  67. package/dist/__tests__/adoption.test.d.ts.map +1 -0
  68. package/dist/__tests__/adoption.test.js +175 -0
  69. package/dist/__tests__/adoption.test.js.map +1 -0
  70. package/dist/__tests__/auto-fix-branch.test.d.ts +2 -0
  71. package/dist/__tests__/auto-fix-branch.test.d.ts.map +1 -0
  72. package/dist/__tests__/auto-fix-branch.test.js +143 -0
  73. package/dist/__tests__/auto-fix-branch.test.js.map +1 -0
  74. package/dist/__tests__/board.test.js +13 -0
  75. package/dist/__tests__/board.test.js.map +1 -1
  76. package/dist/__tests__/can-write-verdict.test.d.ts +2 -0
  77. package/dist/__tests__/can-write-verdict.test.d.ts.map +1 -0
  78. package/dist/__tests__/can-write-verdict.test.js +31 -0
  79. package/dist/__tests__/can-write-verdict.test.js.map +1 -0
  80. package/dist/__tests__/codex-env.test.d.ts +2 -0
  81. package/dist/__tests__/codex-env.test.d.ts.map +1 -0
  82. package/dist/__tests__/codex-env.test.js +70 -0
  83. package/dist/__tests__/codex-env.test.js.map +1 -0
  84. package/dist/__tests__/codex.test.js +100 -25
  85. package/dist/__tests__/codex.test.js.map +1 -1
  86. package/dist/__tests__/comment-bodies.test.js +49 -1
  87. package/dist/__tests__/comment-bodies.test.js.map +1 -1
  88. package/dist/__tests__/commit-subject.test.d.ts +2 -0
  89. package/dist/__tests__/commit-subject.test.d.ts.map +1 -0
  90. package/dist/__tests__/commit-subject.test.js +72 -0
  91. package/dist/__tests__/commit-subject.test.js.map +1 -0
  92. package/dist/__tests__/conflict-resolve.test.js +44 -1
  93. package/dist/__tests__/conflict-resolve.test.js.map +1 -1
  94. package/dist/__tests__/credential-free-origin.test.d.ts +2 -0
  95. package/dist/__tests__/credential-free-origin.test.d.ts.map +1 -0
  96. package/dist/__tests__/credential-free-origin.test.js +59 -0
  97. package/dist/__tests__/credential-free-origin.test.js.map +1 -0
  98. package/dist/__tests__/detector.test.js +87 -1
  99. package/dist/__tests__/detector.test.js.map +1 -1
  100. package/dist/__tests__/error-classification.test.js +34 -0
  101. package/dist/__tests__/error-classification.test.js.map +1 -1
  102. package/dist/__tests__/fix.test.js +69 -0
  103. package/dist/__tests__/fix.test.js.map +1 -1
  104. package/dist/__tests__/harness-section.test.d.ts +2 -0
  105. package/dist/__tests__/harness-section.test.d.ts.map +1 -0
  106. package/dist/__tests__/harness-section.test.js +46 -0
  107. package/dist/__tests__/harness-section.test.js.map +1 -0
  108. package/dist/__tests__/kickass.test.js +31 -0
  109. package/dist/__tests__/kickass.test.js.map +1 -1
  110. package/dist/__tests__/linear-branding.test.d.ts +2 -0
  111. package/dist/__tests__/linear-branding.test.d.ts.map +1 -0
  112. package/dist/__tests__/linear-branding.test.js +156 -0
  113. package/dist/__tests__/linear-branding.test.js.map +1 -0
  114. package/dist/__tests__/linear-client.test.d.ts +2 -0
  115. package/dist/__tests__/linear-client.test.d.ts.map +1 -0
  116. package/dist/__tests__/linear-client.test.js +120 -0
  117. package/dist/__tests__/linear-client.test.js.map +1 -0
  118. package/dist/__tests__/linear-comment.test.d.ts +2 -0
  119. package/dist/__tests__/linear-comment.test.d.ts.map +1 -0
  120. package/dist/__tests__/linear-comment.test.js +151 -0
  121. package/dist/__tests__/linear-comment.test.js.map +1 -0
  122. package/dist/__tests__/linear-identity.test.d.ts +2 -0
  123. package/dist/__tests__/linear-identity.test.d.ts.map +1 -0
  124. package/dist/__tests__/linear-identity.test.js +253 -0
  125. package/dist/__tests__/linear-identity.test.js.map +1 -0
  126. package/dist/__tests__/linear-notify.test.d.ts +2 -0
  127. package/dist/__tests__/linear-notify.test.d.ts.map +1 -0
  128. package/dist/__tests__/linear-notify.test.js +144 -0
  129. package/dist/__tests__/linear-notify.test.js.map +1 -0
  130. package/dist/__tests__/linear-ref.test.d.ts +2 -0
  131. package/dist/__tests__/linear-ref.test.d.ts.map +1 -0
  132. package/dist/__tests__/linear-ref.test.js +261 -0
  133. package/dist/__tests__/linear-ref.test.js.map +1 -0
  134. package/dist/__tests__/linear-test-ref.test.d.ts +2 -0
  135. package/dist/__tests__/linear-test-ref.test.d.ts.map +1 -0
  136. package/dist/__tests__/linear-test-ref.test.js +81 -0
  137. package/dist/__tests__/linear-test-ref.test.js.map +1 -0
  138. package/dist/__tests__/linear-verify.test.d.ts +2 -0
  139. package/dist/__tests__/linear-verify.test.d.ts.map +1 -0
  140. package/dist/__tests__/linear-verify.test.js +132 -0
  141. package/dist/__tests__/linear-verify.test.js.map +1 -0
  142. package/dist/__tests__/linear-worker.test.d.ts +2 -0
  143. package/dist/__tests__/linear-worker.test.d.ts.map +1 -0
  144. package/dist/__tests__/linear-worker.test.js +83 -0
  145. package/dist/__tests__/linear-worker.test.js.map +1 -0
  146. package/dist/__tests__/linear-write-possible.test.d.ts +2 -0
  147. package/dist/__tests__/linear-write-possible.test.d.ts.map +1 -0
  148. package/dist/__tests__/linear-write-possible.test.js +30 -0
  149. package/dist/__tests__/linear-write-possible.test.js.map +1 -0
  150. package/dist/__tests__/loader.test.js +2 -2
  151. package/dist/__tests__/loader.test.js.map +1 -1
  152. package/dist/__tests__/onboard-preservation.test.js +59 -4
  153. package/dist/__tests__/onboard-preservation.test.js.map +1 -1
  154. package/dist/__tests__/optimize.test.js +2 -0
  155. package/dist/__tests__/optimize.test.js.map +1 -1
  156. package/dist/__tests__/pr-status.test.js +213 -3
  157. package/dist/__tests__/pr-status.test.js.map +1 -1
  158. package/dist/__tests__/pr-workflow-state.test.js +247 -7
  159. package/dist/__tests__/pr-workflow-state.test.js.map +1 -1
  160. package/dist/__tests__/repo-picker.test.js +7 -1
  161. package/dist/__tests__/repo-picker.test.js.map +1 -1
  162. package/dist/__tests__/repository-guidance.test.d.ts +2 -0
  163. package/dist/__tests__/repository-guidance.test.d.ts.map +1 -0
  164. package/dist/__tests__/repository-guidance.test.js +107 -0
  165. package/dist/__tests__/repository-guidance.test.js.map +1 -0
  166. package/dist/__tests__/review-comment-body.test.js +35 -0
  167. package/dist/__tests__/review-comment-body.test.js.map +1 -1
  168. package/dist/__tests__/review-models.test.js +19 -3
  169. package/dist/__tests__/review-models.test.js.map +1 -1
  170. package/dist/__tests__/review-strategy.test.d.ts +2 -0
  171. package/dist/__tests__/review-strategy.test.d.ts.map +1 -0
  172. package/dist/__tests__/review-strategy.test.js +397 -0
  173. package/dist/__tests__/review-strategy.test.js.map +1 -0
  174. package/dist/__tests__/reviewed-sha.test.d.ts +2 -0
  175. package/dist/__tests__/reviewed-sha.test.d.ts.map +1 -0
  176. package/dist/__tests__/reviewed-sha.test.js +116 -0
  177. package/dist/__tests__/reviewed-sha.test.js.map +1 -0
  178. package/dist/__tests__/runner.test.js +206 -2
  179. package/dist/__tests__/runner.test.js.map +1 -1
  180. package/dist/__tests__/skill-attribution.test.d.ts +2 -0
  181. package/dist/__tests__/skill-attribution.test.d.ts.map +1 -0
  182. package/dist/__tests__/skill-attribution.test.js +53 -0
  183. package/dist/__tests__/skill-attribution.test.js.map +1 -0
  184. package/dist/__tests__/skill-broker.test.d.ts +2 -0
  185. package/dist/__tests__/skill-broker.test.d.ts.map +1 -0
  186. package/dist/__tests__/skill-broker.test.js +186 -0
  187. package/dist/__tests__/skill-broker.test.js.map +1 -0
  188. package/dist/__tests__/skill-catalog.test.d.ts +2 -0
  189. package/dist/__tests__/skill-catalog.test.d.ts.map +1 -0
  190. package/dist/__tests__/skill-catalog.test.js +40 -0
  191. package/dist/__tests__/skill-catalog.test.js.map +1 -0
  192. package/dist/__tests__/skill-installer.test.d.ts +2 -0
  193. package/dist/__tests__/skill-installer.test.d.ts.map +1 -0
  194. package/dist/__tests__/skill-installer.test.js +96 -0
  195. package/dist/__tests__/skill-installer.test.js.map +1 -0
  196. package/dist/__tests__/skills-config.test.d.ts +2 -0
  197. package/dist/__tests__/skills-config.test.d.ts.map +1 -0
  198. package/dist/__tests__/skills-config.test.js +12 -0
  199. package/dist/__tests__/skills-config.test.js.map +1 -0
  200. package/dist/__tests__/superseded-fix-pr.test.d.ts +2 -0
  201. package/dist/__tests__/superseded-fix-pr.test.d.ts.map +1 -0
  202. package/dist/__tests__/superseded-fix-pr.test.js +225 -0
  203. package/dist/__tests__/superseded-fix-pr.test.js.map +1 -0
  204. package/dist/__tests__/webhook.test.js +60 -0
  205. package/dist/__tests__/webhook.test.js.map +1 -1
  206. package/dist/cli.js +37 -0
  207. package/dist/cli.js.map +1 -1
  208. package/dist/commands/adoption.d.ts +6 -0
  209. package/dist/commands/adoption.d.ts.map +1 -0
  210. package/dist/commands/adoption.js +132 -0
  211. package/dist/commands/adoption.js.map +1 -0
  212. package/dist/commands/detect-step.d.ts.map +1 -1
  213. package/dist/commands/detect-step.js +5 -1
  214. package/dist/commands/detect-step.js.map +1 -1
  215. package/dist/commands/diagnose.d.ts.map +1 -1
  216. package/dist/commands/diagnose.js +2 -2
  217. package/dist/commands/diagnose.js.map +1 -1
  218. package/dist/commands/init.d.ts.map +1 -1
  219. package/dist/commands/init.js +1 -2
  220. package/dist/commands/init.js.map +1 -1
  221. package/dist/commands/issue.js +1 -1
  222. package/dist/commands/issue.js.map +1 -1
  223. package/dist/commands/kickass.d.ts +1 -1
  224. package/dist/commands/kickass.d.ts.map +1 -1
  225. package/dist/commands/kickass.js +36 -22
  226. package/dist/commands/kickass.js.map +1 -1
  227. package/dist/commands/linear-test.d.ts +18 -0
  228. package/dist/commands/linear-test.d.ts.map +1 -0
  229. package/dist/commands/linear-test.js +130 -0
  230. package/dist/commands/linear-test.js.map +1 -0
  231. package/dist/commands/onboard.d.ts +36 -3
  232. package/dist/commands/onboard.d.ts.map +1 -1
  233. package/dist/commands/onboard.js +263 -45
  234. package/dist/commands/onboard.js.map +1 -1
  235. package/dist/commands/optimize.d.ts.map +1 -1
  236. package/dist/commands/optimize.js +1 -0
  237. package/dist/commands/optimize.js.map +1 -1
  238. package/dist/commands/review.d.ts.map +1 -1
  239. package/dist/commands/review.js +65 -6
  240. package/dist/commands/review.js.map +1 -1
  241. package/dist/commands/run.d.ts.map +1 -1
  242. package/dist/commands/run.js +85 -11
  243. package/dist/commands/run.js.map +1 -1
  244. package/dist/commands/scan.d.ts.map +1 -1
  245. package/dist/commands/scan.js +6 -0
  246. package/dist/commands/scan.js.map +1 -1
  247. package/dist/commands/skill.d.ts +2 -0
  248. package/dist/commands/skill.d.ts.map +1 -0
  249. package/dist/commands/skill.js +16 -0
  250. package/dist/commands/skill.js.map +1 -0
  251. package/dist/commands/status.d.ts.map +1 -1
  252. package/dist/commands/status.js +53 -1
  253. package/dist/commands/status.js.map +1 -1
  254. package/dist/commands/watch.d.ts.map +1 -1
  255. package/dist/commands/watch.js +215 -66
  256. package/dist/commands/watch.js.map +1 -1
  257. package/dist/config/loader.d.ts +3 -1
  258. package/dist/config/loader.d.ts.map +1 -1
  259. package/dist/config/loader.js +19 -6
  260. package/dist/config/loader.js.map +1 -1
  261. package/dist/config/review-model-tiers.json +3 -3
  262. package/dist/config/review-strategy.json +204 -0
  263. package/dist/config/schema.d.ts +269 -15
  264. package/dist/config/schema.d.ts.map +1 -1
  265. package/dist/config/schema.js +109 -10
  266. package/dist/config/schema.js.map +1 -1
  267. package/dist/github/client.d.ts +21 -1
  268. package/dist/github/client.d.ts.map +1 -1
  269. package/dist/github/client.js +46 -7
  270. package/dist/github/client.js.map +1 -1
  271. package/dist/github/detector.d.ts.map +1 -1
  272. package/dist/github/detector.js +28 -1
  273. package/dist/github/detector.js.map +1 -1
  274. package/dist/github/merge.d.ts +0 -5
  275. package/dist/github/merge.d.ts.map +1 -1
  276. package/dist/github/merge.js +0 -9
  277. package/dist/github/merge.js.map +1 -1
  278. package/dist/github/reviewed-sha.d.ts +13 -0
  279. package/dist/github/reviewed-sha.d.ts.map +1 -0
  280. package/dist/github/reviewed-sha.js +62 -0
  281. package/dist/github/reviewed-sha.js.map +1 -0
  282. package/dist/github/superseded-fix-pr.d.ts +19 -0
  283. package/dist/github/superseded-fix-pr.d.ts.map +1 -0
  284. package/dist/github/superseded-fix-pr.js +97 -0
  285. package/dist/github/superseded-fix-pr.js.map +1 -0
  286. package/dist/github/webhook.d.ts +8 -1
  287. package/dist/github/webhook.d.ts.map +1 -1
  288. package/dist/github/webhook.js +8 -1
  289. package/dist/github/webhook.js.map +1 -1
  290. package/dist/issues/ticket-ref.d.ts.map +1 -1
  291. package/dist/issues/ticket-ref.js +6 -5
  292. package/dist/issues/ticket-ref.js.map +1 -1
  293. package/dist/lib/adoption.d.ts +64 -0
  294. package/dist/lib/adoption.d.ts.map +1 -0
  295. package/dist/lib/adoption.js +165 -0
  296. package/dist/lib/adoption.js.map +1 -0
  297. package/dist/lib/annotation.d.ts +7 -0
  298. package/dist/lib/annotation.d.ts.map +1 -1
  299. package/dist/lib/annotation.js +11 -8
  300. package/dist/lib/annotation.js.map +1 -1
  301. package/dist/lib/auto-fix-branch.d.ts +35 -0
  302. package/dist/lib/auto-fix-branch.d.ts.map +1 -0
  303. package/dist/lib/auto-fix-branch.js +88 -0
  304. package/dist/lib/auto-fix-branch.js.map +1 -0
  305. package/dist/lib/board.d.ts +3 -0
  306. package/dist/lib/board.d.ts.map +1 -1
  307. package/dist/lib/board.js +4 -2
  308. package/dist/lib/board.js.map +1 -1
  309. package/dist/lib/clone.d.ts +2 -0
  310. package/dist/lib/clone.d.ts.map +1 -1
  311. package/dist/lib/clone.js +69 -10
  312. package/dist/lib/clone.js.map +1 -1
  313. package/dist/lib/comment-bodies.d.ts +37 -0
  314. package/dist/lib/comment-bodies.d.ts.map +1 -1
  315. package/dist/lib/comment-bodies.js +50 -9
  316. package/dist/lib/comment-bodies.js.map +1 -1
  317. package/dist/lib/logger.d.ts.map +1 -1
  318. package/dist/lib/logger.js +17 -1
  319. package/dist/lib/logger.js.map +1 -1
  320. package/dist/lib/pr-picker.d.ts +1 -1
  321. package/dist/lib/pr-picker.d.ts.map +1 -1
  322. package/dist/lib/pr-picker.js +10 -5
  323. package/dist/lib/pr-picker.js.map +1 -1
  324. package/dist/lib/pr-status.d.ts +2 -1
  325. package/dist/lib/pr-status.d.ts.map +1 -1
  326. package/dist/lib/pr-status.js +70 -6
  327. package/dist/lib/pr-status.js.map +1 -1
  328. package/dist/lib/pr-workflow-state.d.ts +17 -1
  329. package/dist/lib/pr-workflow-state.d.ts.map +1 -1
  330. package/dist/lib/pr-workflow-state.js +110 -5
  331. package/dist/lib/pr-workflow-state.js.map +1 -1
  332. package/dist/lib/repo-picker.d.ts +3 -0
  333. package/dist/lib/repo-picker.d.ts.map +1 -1
  334. package/dist/lib/repo-picker.js +45 -10
  335. package/dist/lib/repo-picker.js.map +1 -1
  336. package/dist/lib/repository-guidance.d.ts +2 -0
  337. package/dist/lib/repository-guidance.d.ts.map +1 -0
  338. package/dist/lib/repository-guidance.js +55 -0
  339. package/dist/lib/repository-guidance.js.map +1 -0
  340. package/dist/lib/review-models.d.ts +15 -2
  341. package/dist/lib/review-models.d.ts.map +1 -1
  342. package/dist/lib/review-models.js +26 -6
  343. package/dist/lib/review-models.js.map +1 -1
  344. package/dist/lib/review-strategy.d.ts +92 -0
  345. package/dist/lib/review-strategy.d.ts.map +1 -0
  346. package/dist/lib/review-strategy.js +282 -0
  347. package/dist/lib/review-strategy.js.map +1 -0
  348. package/dist/lib/runner.d.ts +123 -1
  349. package/dist/lib/runner.d.ts.map +1 -1
  350. package/dist/lib/runner.js +919 -101
  351. package/dist/lib/runner.js.map +1 -1
  352. package/dist/lib/vendor.d.ts +1 -0
  353. package/dist/lib/vendor.d.ts.map +1 -1
  354. package/dist/lib/vendor.js +6 -0
  355. package/dist/lib/vendor.js.map +1 -1
  356. package/dist/lib/workflow.d.ts +13 -0
  357. package/dist/lib/workflow.d.ts.map +1 -1
  358. package/dist/lib/workflow.js +24 -1
  359. package/dist/lib/workflow.js.map +1 -1
  360. package/dist/linear/client.d.ts +18 -0
  361. package/dist/linear/client.d.ts.map +1 -0
  362. package/dist/linear/client.js +67 -0
  363. package/dist/linear/client.js.map +1 -0
  364. package/dist/linear/comment.d.ts +20 -0
  365. package/dist/linear/comment.d.ts.map +1 -0
  366. package/dist/linear/comment.js +57 -0
  367. package/dist/linear/comment.js.map +1 -0
  368. package/dist/linear/identity.d.ts +59 -0
  369. package/dist/linear/identity.d.ts.map +1 -0
  370. package/dist/linear/identity.js +187 -0
  371. package/dist/linear/identity.js.map +1 -0
  372. package/dist/linear/notify.d.ts +35 -0
  373. package/dist/linear/notify.d.ts.map +1 -0
  374. package/dist/linear/notify.js +76 -0
  375. package/dist/linear/notify.js.map +1 -0
  376. package/dist/linear/ref.d.ts +13 -0
  377. package/dist/linear/ref.d.ts.map +1 -0
  378. package/dist/linear/ref.js +90 -0
  379. package/dist/linear/ref.js.map +1 -0
  380. package/dist/linear/verify.d.ts +26 -0
  381. package/dist/linear/verify.d.ts.map +1 -0
  382. package/dist/linear/verify.js +67 -0
  383. package/dist/linear/verify.js.map +1 -0
  384. package/dist/reviewers/address.d.ts +1 -1
  385. package/dist/reviewers/address.d.ts.map +1 -1
  386. package/dist/reviewers/address.js +1 -1
  387. package/dist/reviewers/address.js.map +1 -1
  388. package/dist/reviewers/claude.d.ts +4 -1
  389. package/dist/reviewers/claude.d.ts.map +1 -1
  390. package/dist/reviewers/claude.js +39 -7
  391. package/dist/reviewers/claude.js.map +1 -1
  392. package/dist/reviewers/codex-env.d.ts +12 -0
  393. package/dist/reviewers/codex-env.d.ts.map +1 -0
  394. package/dist/reviewers/codex-env.js +54 -0
  395. package/dist/reviewers/codex-env.js.map +1 -0
  396. package/dist/reviewers/codex.d.ts +18 -1
  397. package/dist/reviewers/codex.d.ts.map +1 -1
  398. package/dist/reviewers/codex.js +164 -81
  399. package/dist/reviewers/codex.js.map +1 -1
  400. package/dist/reviewers/conflict-resolve.d.ts +3 -1
  401. package/dist/reviewers/conflict-resolve.d.ts.map +1 -1
  402. package/dist/reviewers/conflict-resolve.js +21 -6
  403. package/dist/reviewers/conflict-resolve.js.map +1 -1
  404. package/dist/reviewers/fix.d.ts +5 -2
  405. package/dist/reviewers/fix.d.ts.map +1 -1
  406. package/dist/reviewers/fix.js +38 -12
  407. package/dist/reviewers/fix.js.map +1 -1
  408. package/dist/skills/attribution.d.ts +4 -0
  409. package/dist/skills/attribution.d.ts.map +1 -0
  410. package/dist/skills/attribution.js +14 -0
  411. package/dist/skills/attribution.js.map +1 -0
  412. package/dist/skills/broker-server.d.ts +2 -0
  413. package/dist/skills/broker-server.d.ts.map +1 -0
  414. package/dist/skills/broker-server.js +17 -0
  415. package/dist/skills/broker-server.js.map +1 -0
  416. package/dist/skills/broker.d.ts +43 -0
  417. package/dist/skills/broker.d.ts.map +1 -0
  418. package/dist/skills/broker.js +311 -0
  419. package/dist/skills/broker.js.map +1 -0
  420. package/dist/skills/catalog.d.ts +28 -0
  421. package/dist/skills/catalog.d.ts.map +1 -0
  422. package/dist/skills/catalog.js +104 -0
  423. package/dist/skills/catalog.js.map +1 -0
  424. package/dist/skills/installer.d.ts +10 -0
  425. package/dist/skills/installer.d.ts.map +1 -0
  426. package/dist/skills/installer.js +138 -0
  427. package/dist/skills/installer.js.map +1 -0
  428. package/dist/skills/integrity.d.ts +4 -0
  429. package/dist/skills/integrity.d.ts.map +1 -0
  430. package/dist/skills/integrity.js +36 -0
  431. package/dist/skills/integrity.js.map +1 -0
  432. package/docs/dynamic-thoroughness.md +738 -0
  433. package/docs/linear-identity-contract.md +139 -0
  434. package/docs/linear-identity.md +293 -0
  435. package/docs/metrics.md +115 -0
  436. package/docs/trust.md +159 -0
  437. package/get-started.md +322 -16
  438. package/package.json +6 -3
@@ -0,0 +1,738 @@
1
+ # Dynamic thoroughness — design & evidence
2
+
3
+ | | |
4
+ |---|---|
5
+ | **Strategy version** | `1.1.0` — [`src/config/review-strategy.json`](../src/config/review-strategy.json) |
6
+ | **Analysis run** | 2026-08-07T05:00:00Z |
7
+ | **Census window** | 2026-07-30 → 2026-08-07 |
8
+ | **Corpus** | 400 merged PRs · 199 crosscheck-engaged · 597 agent calls · 43 locally-logged runs |
9
+ | **Hand-labelled** | 40 BLOCK findings read against cited source |
10
+ | **Next review due** | 2026-10-06 (60-day interval) |
11
+ | **Re-verify** | `npm run verify:strategy` |
12
+
13
+ Status: **adopted and active**. This document is the evidence behind
14
+ [`review-strategy.json`](../src/config/review-strategy.json); the JSON is what
15
+ crosscheck reads at runtime. The runner classifies every PR from its cloned
16
+ working copy, folds the result into the quality config the reviewers receive,
17
+ and stamps the version, class, and tier into each annotation. When they disagree, the JSON wins and this document is
18
+ stale — the weekly `Review Strategy` workflow files an issue when either drifts.
19
+
20
+ **Enforced today:** the `Tier`, `Effort`, and `Steps` columns below all reach the
21
+ run. A class resolving to a null tier (class 1) skips the workflow outright; the
22
+ per-class step set narrows the configured pipeline (never widens it — a repo
23
+ pinned to review-only stays review-only); and rounds past the first escalate via
24
+ `escalate()`, raising effort where the model supports it and promoting the tier
25
+ where it does not.
26
+
27
+ Comments cite only what actually applied — no tier is named when the class
28
+ selects none, or when an explicit `vendors.*.model` outranks the strategy — so
29
+ the citation never claims more than the runtime does.
30
+
31
+ Every crosscheck comment cites the strategy version it ran under, so a review from
32
+ last month can be explained by the policy in force when it ran rather than the policy
33
+ in force today.
34
+
35
+ ---
36
+
37
+ ## 0. The goal, and what it implies
38
+
39
+ The objective is **shipping quality code at a faster pace**. Code review is
40
+ instrumental to that, not an end in itself. So the metric that matters is not cost per
41
+ review, and not findings per review. It is:
42
+
43
+ > **Actioned-finding rate** — the share of real defects that get fixed *inside the
44
+ > merge window*.
45
+
46
+ A finding that arrives after merge is worth nothing. A finding the author declines to
47
+ act on is worth nothing. A correct finding nobody reads is worth nothing. Everything
48
+ below is organized around that.
49
+
50
+ Measured against it, the current system has a specific and non-obvious problem:
51
+
52
+ | | Measured | Reading |
53
+ |---|---|---|
54
+ | Review lands before merge | **94%** | Delivery works. |
55
+ | Findings that are real defects | **~85%** (hand-labelled, n=39) | Quality works. Noise is not the bottleneck. |
56
+ | PRs reaching APPROVE | **38%** | Action does not happen. |
57
+ | Median gap, last review → merge | **4 minutes** | **The author was already merging.** |
58
+
59
+ The reviewer is fast and it is right, and 62% of its output is merged past anyway — with
60
+ 56% of those merges landing within five minutes of the review, or before it.
61
+
62
+ **The structural reason: crosscheck never publishes a merge-blocking signal.** It reads
63
+ commit statuses and check-runs (`client.ts:555`, `client.ts:684`) but creates neither.
64
+ Its entire output is a comment. Branch protection requires:
65
+
66
+ | Branch | Required checks |
67
+ |---|---|
68
+ | `staging` | `PR size, template, and reviewability`, `ci-gate` |
69
+ | `main` | + `release-guard`, 1 approving review |
70
+
71
+ Crosscheck appears in neither list, so GitHub merges the moment `ci-gate` goes green.
72
+ Nothing stops a merge at minute four. (`enforce_admins` is `false` on both branches, so
73
+ even a required check would be admin-bypassable.)
74
+
75
+ Two distinct failure modes follow, and they need different fixes:
76
+
77
+ 1. **The fast majority — no gate.** Merging before the review lands is undisciplined
78
+ practice, not a considered trade-off. No improvement to review quality, latency, or
79
+ phrasing changes it, because the review is not part of the merge decision at all.
80
+ Only a gate does.
81
+ 2. **The slow tail — no terminal state.** Findings never stop arriving (§3), so the
82
+ loop is an open-ended commitment. This is what makes the process intolerable *once
83
+ authors are forced to engage with it*.
84
+
85
+ These compose in a specific order. **Gating without first bounding the loop would be
86
+ actively harmful** — it converts "merged past findings in 4 minutes" into "blocked for
87
+ 51 hours," which is the opposite of the goal. Bound the loop first, then gate, then
88
+ optimize latency.
89
+
90
+ ---
91
+
92
+ ## 1. Methodology
93
+
94
+ | Input | What it gives | Confidence |
95
+ |---|---|---|
96
+ | **A.** Census of all 400 merged PRs | Population-scale cost and outcome | High |
97
+ | **B.** 40 BLOCK findings read against the cited code | Whether findings are real | High — hand-labelled |
98
+ | **C.** Local run logs (43 runs) | Tokens + wall-clock per step | High — measured |
99
+ | **D.** Vendor catalogs + live CLI probe | Price, effort, what actually runs | High — primary |
100
+ | **E.** SWE-bench, CR-Bench | Relative capability by task shape | Medium — see §5 |
101
+
102
+ Two earlier approaches were abandoned, recorded here so they are not re-derived:
103
+
104
+ - **Sampling 30 PRs and fitting a complexity score to agent-call count.** Circular:
105
+ call count is set by whether the loop converged, which is a property of the reviewer
106
+ configuration, not the PR. The census replaces it.
107
+ - **`breadth = dirs × languages` as a complexity metric.** Scored a perfect AUC on the
108
+ 30-PR sample; the census falsifies it outright (§4).
109
+
110
+ For the labelling pass, BLOCK findings were extracted from PRs that merged *without*
111
+ reaching APPROVE, deduplicated, sampled 40 (seeded; 20 P1-code / 12 P2-code / 8 docs),
112
+ and each was read against the file it cites at the SHA it was reviewed at.
113
+
114
+ ---
115
+
116
+ ## 2. Finding 1 — the findings are real
117
+
118
+ This was the decisive experiment, and it inverted the design.
119
+
120
+ Of 40 sampled findings: 1 unverifiable (file since deleted), **0 false**. Every
121
+ remaining claim was a true statement about the code it cited.
122
+
123
+ | Label | n | Meaning |
124
+ |---|---|---|
125
+ | Real, defect-grade | **33** | Genuine bug, security gap, or documented-convention violation |
126
+ | Real, severity-inflated | 6 | True, but should not have contributed to BLOCK |
127
+ | False | **0** | — |
128
+ | Unverifiable | 1 | Cited file no longer retrievable |
129
+
130
+ Representative confirmed defects, each verified against source:
131
+
132
+ - **#8** — `change_rule_mode` accepts an `expected_mode` parameter and never puts it in
133
+ the POST body. Dual-control bypass. Two lines apart in the same function.
134
+ - **#6** — `body.get("data")` sits *outside* the `try` that catches JSON errors; a
135
+ non-object response raises an uncaught `AttributeError`.
136
+ - **#10** — `_validated_gate` hard-rejects a gate response missing `profile`, so during
137
+ a normal rolling deploy every enroll returns 503 until both services are upgraded.
138
+ - **#13** — the promote payload sends three fields the receiving `extra="forbid"` model
139
+ no longer defines: every promotion and rollback 422s. Feature entirely broken.
140
+ - **#26** — `min_a2_shared_wrong` bounded at `_Bound(int, 0)` while its sibling and the
141
+ request contract require ≥ 1.
142
+ - **#4** — migration named `20260820000000`. `docs/architecture/database-conventions.md`
143
+ says verbatim: *"**Never hand-write a round-number timestamp**"*, and documents a
144
+ prior silent-skip incident. The reviewer cited the correct line range.
145
+
146
+ The reviews also demonstrate project-specific reasoning that a generic linter cannot
147
+ do: they read repo convention docs, cross-reference design ADRs, and trace call paths
148
+ across services.
149
+
150
+ **The six severity-inflated cases are the real defect in the output**, not the
151
+ findings themselves: a webhook-secret hardening that requires operator misconfiguration
152
+ to exploit (#2), a request for validation stricter than the contract claims (#3), and
153
+ screenshot binaries committed to a feature branch (#20) — a process violation, correctly
154
+ identified, that should never gate a merge.
155
+
156
+ > **Calibration note.** CR-Bench reports 3.6–5.1% precision for review agents (§5).
157
+ > This system measures ~85% defect-grade. The gap is real and worth naming: CR-Bench
158
+ > scores against a fixed ground-truth defect list, penalizing any finding outside it,
159
+ > whereas this labelling asked "is the claim true and worth fixing." Those measure
160
+ > different things. The operational conclusion stands regardless — **do not tune this
161
+ > system as though its output were noise.**
162
+
163
+ ---
164
+
165
+ ## 3. Finding 2 — the loop cannot converge, by construction
166
+
167
+ Findings per review pass, across the census:
168
+
169
+ | Pass # | Passes | Mean findings | Total |
170
+ |---|---|---|---|
171
+ | 1 | 198 | 2.0 | 391 |
172
+ | 2 | 90 | 2.5 | 221 |
173
+ | 3 | 46 | 2.5 | 117 |
174
+ | 4 | 32 | **3.3** | 105 |
175
+ | 5 | 23 | **3.7** | 86 |
176
+ | 6+ | 94 | 2.3 | 216 |
177
+
178
+ **Later passes find more, not fewer.** And the findings do not repeat: across 684 raw
179
+ findings there are 676 distinct ones — a dedup ratio of **1.0**. Nothing recurs.
180
+
181
+ So the fixer *is* fixing (issues do not come back), and each pass mines a fresh seam.
182
+ Two sampled findings are regressions introduced by the *previous* round's fix:
183
+
184
+ - **#15 → #19** — round N flags a dual-control TOCTOU; the fix adds
185
+ `expected_mode=current_mode`; round N+1 correctly observes that this now 409s every
186
+ legitimate retry.
187
+ - **#32 → #33** — round N flags two data streams missing from a legal retention table;
188
+ the fix adds them but describes the device ID as `raw`, contradicting the ADR that
189
+ requires `key_version:HMAC`. Round N+1 catches it.
190
+
191
+ This is legitimate, valuable work. It is also **an unbounded process**: there is no
192
+ round at which the supply is exhausted, so "iterate until APPROVE" has no terminal
193
+ state. PR #1998 ran 12 recheck rounds — 26 agent calls, 4.6 hours — and merged unresolved.
194
+
195
+ The remedy is therefore not a better fixer or a stronger recheck model. It is to
196
+ **stop treating APPROVE as the exit condition** and bound the process explicitly.
197
+
198
+ ---
199
+
200
+ ## 4. Finding 3 — crosscheck is racing the merge, and the race is tight
201
+
202
+ Across the 122 PRs that merged unresolved, time from the **last** review comment to
203
+ merge:
204
+
205
+ | | Share |
206
+ |---|---|
207
+ | Review landed *after* the merge — too late entirely | 12% |
208
+ | Merged within 5 minutes of the review | **44%** |
209
+ | Merged 5–60 minutes after | 25% |
210
+ | Merged > 1 hour after (saw it, chose to ship) | 18% |
211
+
212
+ Median: **4 minutes**. Only ~18% of unresolved merges look like a considered decision.
213
+
214
+ The elapsed-time hypothesis — that authors bail because loops run too long — is **true
215
+ in the tail and insufficient on its own**. Convergence is flat at 33–41% across every
216
+ span and round bucket, including the 108 single-round PRs that resolved in a median of
217
+ 11 minutes. Long loops do not predict merging unresolved; the behavior is uniform.
218
+
219
+ The uniformity is the tell. If authors were weighing findings, the rate would vary with
220
+ how painful the PR was. It does not vary at all — which is what an **absent gate** looks
221
+ like (§0). The fast majority is not a considered trade-off; it is merging that nothing
222
+ prevents. The tail is a separate problem: those PRs have the *highest* rate of reviews
223
+ landing after merge (20%) and a median span of **51 hours**, which is what makes the
224
+ loop intolerable once a gate forces engagement.
225
+
226
+ The window is genuinely tight. First review lands at a median of 6 minutes; merge at 11
227
+ minutes for single-round PRs and 36 overall. At p25, merge happens at 9 minutes against
228
+ a p25 review at 5. **For a large share of PRs the usable window is single-digit
229
+ minutes** — which makes time-to-first-review a first-class design constraint, not a
230
+ nice-to-have.
231
+
232
+ ### Static prediction is weak
233
+
234
+ **Static complexity does not predict cost.** Spearman vs agent-call count across all 199
235
+ engaged PRs: churn 0.513, files 0.428, churn/files 0.321. The distributions overlap
236
+ severely — the largest one-call PR was **101,118 lines**; the smallest 7+-call PR was
237
+ **103**; and the single most expensive PR in the census (#1998, 26 calls) changed
238
+ **2 files**.
239
+
240
+ Conclusion: static features can answer only *"is this trivially safe to start cheap?"*
241
+ They set a floor. They cannot identify the hard PRs in advance, so they must never set
242
+ the ceiling.
243
+
244
+ ---
245
+
246
+ ## 5. Finding 4 — model selection is currently inert
247
+
248
+ Three breaks in one path, all of which must be fixed before any tiering policy can have
249
+ an effect.
250
+
251
+ **a. The Claude override bypasses tiers.** `vendors.claude.model: sonnet` wins over the
252
+ tier table, so all 43 logged runs were `claude-sonnet-5` regardless of `quality.tier`.
253
+
254
+ **b. The Codex model is silently discarded.**
255
+
256
+ ```ts
257
+ export function resolveCodexModel(quality, vendor): string {
258
+ if (vendor.auth !== 'api-key') return 'default' // ← drops the configured model
259
+ ...
260
+ }
261
+ ```
262
+
263
+ The config sets `vendors.codex.model: gpt-5.6-sol` with `auth: subscription`, so it
264
+ never reaches the CLI. **148 of 199 engaged PRs and 482 of 597 calls (81%) ran on
265
+ `model=default`.** A live probe contradicts the guard's rationale: `codex -m/--model`
266
+ is a top-level flag and the CLI accepted it under subscription auth.
267
+
268
+ **c. The installed CLI is too old for the configured model.** The same probe returned
269
+ `The 'gpt-5.6-terra' model requires a newer version of Codex.` Installed
270
+ `codex-cli 0.141.0`; npm latest `0.147.0`. That error text is exactly what
271
+ `isVendorUnavailableError` matches, so smart-switch has been silently absorbing it.
272
+
273
+ Outcomes by first model:
274
+
275
+ | First model | PRs | Calls | Merged without APPROVE |
276
+ |---|---|---|---|
277
+ | `default` | 148 | 482 | **74%** |
278
+ | `claude-opus-5` | 22 | 60 | **5%** |
279
+ | `claude-sonnet-5` | 15 | 19 | 27% |
280
+ | `gpt-5.6-luna` | 6 | 22 | 17% |
281
+ | `gpt-5.6-sol` | 7 | 14 | 86% |
282
+
283
+ > **Confound, stated plainly.** Assignment was not random — it followed origin routing
284
+ > and smart-switch state. `gpt-5.6-sol`'s 86% is the clearest artifact: n=7, used as an
285
+ > *escalation target* on already-stuck PRs. The same bias inflates part of the `default`
286
+ > column. But `default` spans 148 PRs, and 74%-vs-5% is wider than selection alone
287
+ > plausibly explains.
288
+
289
+ ### Price and capability
290
+
291
+ | Model | In | Out | Cost @ 48k out | Effort |
292
+ |---|---|---|---|---|
293
+ | `claude-fable-5` | $10 | $50 | $2.40 | low→max |
294
+ | `claude-opus-5` | $5 | $25 | $1.20 | low→max |
295
+ | `claude-sonnet-5` | $3 | $15 | $0.72 | low→max |
296
+ | `claude-haiku-4-5` | $1 | $5 | $0.24 | **none** |
297
+ | `gpt-5.6-sol` | $5 | $30 | $1.44 | none→max |
298
+ | `gpt-5.6-terra` | $2 | $12 | $0.58 | none→max |
299
+ | `gpt-5.6-luna` | $0.20 | $1.20 | **$0.058** | none→max |
300
+
301
+ SWE-bench Verified (one leaderboard, refreshed 2026-08-06, consistent within the Claude
302
+ family): Opus 5 **96.0%**, Fable 5 95.0%, Sonnet 5 **85.2%**, Haiku 4.5 **73.3%**.
303
+ Cross-family figures (Sol ~96.2%, Luna ~93%) come from a *different* aggregator and are
304
+ directional only.
305
+
306
+ Measured step costs (43 runs): review 643 s / 48.0k output tokens; **recheck 663 s /
307
+ 48.9k — the same as a full review**; fix 409 s; conflict-resolve **37 s**.
308
+
309
+ Anthropic's effort guidance supports varying effort per call, with the constraint that
310
+ it must be constant *within* a cached conversation. Note Haiku 4.5 has no effort
311
+ parameter at all.
312
+
313
+ ---
314
+
315
+ ## 6. Design
316
+
317
+ Everything follows from §0: maximize actioned findings inside the merge window.
318
+
319
+ ### 6.1 Front-load capability into pass 1 — against a latency budget
320
+
321
+ Pass 1 is the only pass reliably read before merge, and later passes find *more*
322
+ defects, not fewer (§3). So capability spent on pass 1 converts to action; capability
323
+ spent on pass 7 mostly does not.
324
+
325
+ But capability costs time, and §4 shows the window is often single-digit minutes. **This
326
+ tension is the actual case for dynamic thoroughness** — and it is the one justification
327
+ that survives the census. It is not cost. A review that is 10 points better on SWE-bench
328
+ and arrives after the merge scores zero.
329
+
330
+ So the tier is chosen against a **latency budget derived from expected merge speed**,
331
+ not from predicted difficulty:
332
+
333
+ | PR class | Expected merge | Budget | Tier |
334
+ |---|---|---|---|
335
+ | Trivial (§6.5) | ~9–11 min | **< 3 min** | `fast` — latency-driven, not cost-driven |
336
+ | Standard | ~36 min | < 10 min | `balanced` + `xhigh` |
337
+ | Risky / `risk:T3` | hours | < 20 min | `thorough` |
338
+
339
+ This is why the `fast` tier earns its place: on a PR that merges in nine minutes, Haiku
340
+ at 73.3% delivered in two minutes beats Opus at 96.0% delivered in twelve. The 12% of
341
+ reviews that currently land after merge are pure waste at any capability level.
342
+
343
+ - **Pin the models** (§5). Drop the `auth !== 'api-key'` guard; honor a configured model
344
+ under either auth, as `resolveClaudeModel` already does. Require `codex >= 0.147.0`
345
+ when a `gpt-5.6-*` model is set, and fail loudly at startup.
346
+ - **Refresh `review-model-tiers.json`.** `thorough` currently pins `claude-opus-4-8`,
347
+ now legacy; `claude-opus-5` supersedes it at the identical $5/$25 and scores 96.0% vs
348
+ 88.6%. Free upgrade, ship independently.
349
+ - **Raise the pass-1 floor to `balanced`, and use `xhigh` effort.** Effort is the cheap
350
+ lever: Sonnet 5 at `xhigh` costs the same per token as at `medium` and merely spends
351
+ more of them, where Sonnet→Opus is a flat 1.67× on every token.
352
+
353
+ ```json
354
+ {
355
+ "claude": { "fast": "claude-haiku-4-5-20251001",
356
+ "balanced": "claude-sonnet-5",
357
+ "thorough": "claude-opus-5" },
358
+ "codex_api": { "fast": "gpt-5.6-luna",
359
+ "balanced": "gpt-5.6-terra",
360
+ "thorough": "gpt-5.6-sol" }
361
+ }
362
+ ```
363
+
364
+ No `frontier` tier for `claude-fable-5`: 2× Opus 5's output price for a *lower*
365
+ SWE-bench score.
366
+
367
+ ### 6.2 Make the terminal state visible — the closing contract
368
+
369
+ The exit condition changes from "APPROVE" to "**the blocking set is closed**," and —
370
+ critically — **pass 1 must say so in the comment.**
371
+
372
+ Authors merge past findings because entering the loop is an open-ended commitment (§0).
373
+ Bounding the set internally does not fix that; the author cannot see the bound. The
374
+ first review has to carry the promise explicitly:
375
+
376
+ > **3 blocking issues. This is the complete blocking set — later rounds will not add to
377
+ > it.** 7 further observations are advisory and do not gate this PR.
378
+
379
+ That sentence is the whole intervention. It converts an open-ended commitment into a
380
+ bounded one, which is the difference between a decision an author can make in the merge
381
+ window and one they resolve by merging.
382
+
383
+ It also has to be *true*, which constrains the implementation:
384
+
385
+ - Pass 1 emits at most N blocking findings (default 5), ranked; everything else is
386
+ advisory and explicitly non-gating.
387
+ - **Advisory findings never trigger a fix round**, and later rounds may not promote a
388
+ finding into the blocking set. A new defect discovered in round 3 is reported as
389
+ advisory — otherwise the promise breaks and the learned disengagement returns.
390
+ - **Hard cap: 3 fix/recheck rounds, and a wall-clock budget.** Whichever binds first
391
+ ends the loop with a digest and a human assignment. Given §3 the process has no
392
+ natural terminal state, and given §4 the round count is the wrong unit — 7+ round PRs
393
+ span a median of 51 hours.
394
+
395
+ The one real cost: capping the blocking set means some genuine defects ship as advisory.
396
+ Given ~85% precision that is a real loss, and it is the right trade — a bounded set that
397
+ gets acted on beats an unbounded one that gets merged past. The advisory list preserves
398
+ the finding for the author and for follow-up.
399
+
400
+ ### 6.3 Tighten severity so BLOCK means something
401
+
402
+ Six of 40 sampled findings were true but should not have gated a merge. BLOCK should
403
+ require a concrete failure path in the changed code: data loss, security boundary,
404
+ broken API contract, or a correctness bug with a reachable trigger. Explicitly **not**
405
+ BLOCK-grade:
406
+
407
+ - repository process and hygiene conventions (#20 — screenshot binaries)
408
+ - hardening that requires operator misconfiguration to exploit (#2)
409
+ - requests for validation beyond what the code's stated contract claims (#3)
410
+
411
+ These stay in the review as advisory. This costs nothing and raises the signal on the
412
+ verdict that gates the pipeline.
413
+
414
+ ### 6.4 The ladder — narrow the scope, hold the model, escalate the effort
415
+
416
+ Start strong but not maxed (54% of engaged PRs are one-and-done — maxing every pass 1
417
+ wastes budget on PRs that need one), then escalate on measured non-convergence.
418
+
419
+ | Round | Scope | Model | Effort | Rationale |
420
+ |---|---|---|---|---|
421
+ | **1 · review** | Whole PR | `balanced`, or `thorough` for risk classes (§6.5) | `medium` | Only pass reliably read. Sets the blocking set. |
422
+ | **· fix** | The N blocking items | **same as review** | `medium` | See below — cheap fixes cost a whole round. |
423
+ | **2 · recheck** | Delta + open items only | **same as review** | `high` | Narrower task, not a weaker judge. |
424
+ | **3 · recheck** | Delta + open items | same | `xhigh` | Non-convergence *is* the difficulty signal. |
425
+ | **4+** | — | — | — | Stop. Digest + human (§6.2). |
426
+
427
+ **Should review get cheaper over rounds? Yes — but by narrowing scope, never by
428
+ weakening the model.** The two levers look similar on a cost report and are opposite in
429
+ effect:
430
+
431
+ - *Scope* legitimately shrinks. Round 1 reads the whole PR; round N only has to check N
432
+ known items against the delta. Today recheck costs the same as a full review (663 s
433
+ vs 643 s, 48.9k vs 48.0k tokens) **because it re-reviews everything** — that is
434
+ waste, and scoping it is a real saving that the closing contract (§6.2) already
435
+ implies, since later rounds may not add blocking findings anyway.
436
+ - *Model* must not weaken. CR-Bench shows weak models degrade fastest under iteration —
437
+ GPT-5-mini's SNR fell to 0.91, below 1, more noise than signal. Recheck is the call
438
+ that decides whether to spend another round; a weak judge there is how loops become
439
+ unbounded.
440
+
441
+ So the per-round cost curve falls because the **input shrinks**, while capability holds
442
+ and effort rises. That is the opposite of the intuitive "reviews are expensive, make
443
+ rechecks cheap," which optimizes the one call where cheapness is most damaging.
444
+
445
+ **On making `fix` cheaper:** it is the safest step to cheapen — generation against an
446
+ explicit list is the task models are strongest at (SWE-bench-shaped), unlike detection.
447
+ But the census argues for restraint. Two of 40 sampled findings are regressions
448
+ *introduced by a previous round's fix* (#15→#19, #32→#33), and each cost a full extra
449
+ round at ~10.7 minutes. Against that, a stronger fixer costs ~1.67× on a single call.
450
+ **Hold the model, drop effort one notch** — cheaper tokens without buying new defects.
451
+ A tier drop is defensible only for mechanical fix steps on trivial PRs.
452
+
453
+ ### 6.5 The strategy table
454
+
455
+ Encoded in [`src/config/review-strategy.json`](../src/config/review-strategy.json), which
456
+ follows the pattern `review-model-tiers.json` already established: an `updated` date,
457
+ `sources` with verifiable `checks` strings, and a `review_interval_days` so drift is
458
+ detectable rather than silent (§6.6).
459
+
460
+ **Vendor tiers.** Cost is per 48k-output call, the census median.
461
+
462
+ | Tier | Claude | $/call | SWE-b | Codex | $/call | Effort |
463
+ |---|---|---|---|---|---|---|
464
+ | `fast` | `claude-haiku-4-5` | $0.240 | 73.3% | `gpt-5.6-luna` | **$0.058** | Claude: **none** · Codex: all |
465
+ | `balanced` | `claude-sonnet-5` | $0.720 | 85.2% | `gpt-5.6-terra` | $0.576 | both: all |
466
+ | `thorough` | `claude-opus-5` | $1.200 | 96.0% | `gpt-5.6-sol` | $1.440 | both: all |
467
+
468
+ *Claude strengths:* highest measured capability at `thorough`, most recent knowledge
469
+ cutoff (May 2026). *Weakness:* the `fast` tier has no effort parameter and only a 200k
470
+ context. *Codex strengths:* `fast` is 4× cheaper than Haiku on output, effort available
471
+ at every tier, 1.05M context throughout. *Weakness:* cross-family benchmarks are not
472
+ comparable — validate locally before assuming tier parity.
473
+
474
+ **`claude-fable-5` is banned from code review** (`banned_models` in the strategy file,
475
+ enforced by `verify:strategy`). At $50/MTok output it is 2× `claude-opus-5` for a
476
+ *lower* SWE-bench Verified score — $2.40 per call at the measured 48k output tokens,
477
+ with no capability justification at any tier.
478
+
479
+ ### The second axis: domain
480
+
481
+ Capability is domain-shaped, not scalar, and a single tier ladder cannot express that.
482
+ SWE-bench Verified resolves issues in *Python* repositories; in this census **42% of
483
+ reviewed PRs are frontend-dominant and 55% touch frontend at all**. On the domain that
484
+ carries most of the workload the ordering inverts:
485
+
486
+ | Frontend Code Arena (Elo, blind pairwise) | | SWE-bench Verified | |
487
+ |---|---|---|---|
488
+ | **Kimi K3** *(open)* | **1679** | **Claude Opus 5** | **96.0** |
489
+ | Claude Fable 5 | 1631 | Claude Sonnet 5 | 85.2 |
490
+ | GPT-5.6 Sol | 1618 | DeepSeek V4 Pro *(open)* | 80.6 |
491
+ | GLM-5.2 *(open)* | 1587 | DeepSeek V4 Flash *(open)* | 79.0 |
492
+ | Claude Opus 4.8 | 1562 | GLM-5 *(open)* | 77.8 |
493
+ | Grok-4.5 | 1558 | Claude Haiku 4.5 | 73.3 |
494
+
495
+ The most capable frontend model available is open-weight, and it is a different model
496
+ from the backend leader. Hence `domains` in the strategy file: `frontend` prefers
497
+ `kimi-k3` → `gpt-5.6-sol` → `claude-opus-5` at `thorough`; `backend` prefers
498
+ `claude-opus-5` → `gpt-5.6-sol`. `mixed` and `unknown` fall back to `backend`.
499
+
500
+ > **Confidence: hypothesis, marked as such in the JSON.** Arena measures blind human
501
+ > preference on *generated* frontend output — a generation task, where review is
502
+ > detection. The same asymmetry that limits SWE-bench applies. And **Claude Opus 5 is
503
+ > absent from the retrieved frontend leaderboard** (only Opus 4.8 at 1562) — a missing
504
+ > measurement, not a low score. The backend row is `measured`; the frontend row needs a
505
+ > local A/B before it should be trusted over the default.
506
+
507
+ **PR classes.** First match wins, so ordering is the routing logic.
508
+
509
+ | # | Class | Detection | Tier | Effort | Steps |
510
+ |---|---|---|---|---|---|
511
+ | 1 | Generated / vendored only | every file is a lockfile, build output, or generated stub | — | — | **skip** |
512
+ | 2 | **Security / data-critical** | risky path, `risk:T3`, or hotfix→default branch | `thorough` | high | review, fix, recheck |
513
+ | 3 | Deletion-only | ≤ 5 additions with ≥ 20 deletions | `fast` | medium | review |
514
+ | 4 | Documentation / specification | ≥ 50% `.md`/`.rst`/`.adoc` | `balanced` | high | review |
515
+ | 5 | Test-only | every file is a test or fixture | `fast` | medium | review, fix |
516
+ | 6 | Config / infrastructure | ≥ 50% config, no source files | `balanced` | high | review, fix, recheck |
517
+ | 7 | Trivial | ≤ 3 files and ≤ 150 source churn | `fast` | medium | review, fix |
518
+ | 8 | Standard code | fallthrough | `balanced` | medium | review, fix, recheck |
519
+
520
+ Three classes carry reasoning that is not obvious from the row:
521
+
522
+ - **Deletion-only** is not "small, therefore cheap." Its risk is *reference breakage* —
523
+ dangling imports, call sites, route registrations, docs pointing at removed symbols —
524
+ which is a narrow lookup task, not defect detection. And it gets no fix loop: a wrong
525
+ deletion is reverted, not patched.
526
+ - **Docs is review-only.** Census: docs PRs converge worst (mean 4.1 calls vs 3.0), and
527
+ #1998 — the most expensive PR in the census at 26 calls — was two markdown files. The
528
+ findings were real, but they were *design negotiation*, which no automated loop
529
+ terminates. Removing the fix/recheck loop for this class is the single largest tail
530
+ saving available.
531
+ - **Security is the only promotion rule**, applied because a missed defect there is
532
+ expensive — *not* because those PRs are predicted to be hard. §4 shows static features
533
+ cannot predict difficulty.
534
+
535
+ **Mode.**
536
+
537
+ | | Cross-vendor | Single-vendor |
538
+ |---|---|---|
539
+ | Strength | Independent second perspective | One CLI to auth, version, monitor |
540
+ | Round-3 lever | **switch vendor + raise effort** | raise effort, then promote one tier |
541
+ | Cost | both CLIs must stay current | loses the deadlock-breaker |
542
+ | `max_rounds` | 3 | **2** |
543
+
544
+ Cross-vendor's specific value is empirical: a vendor switch broke a stalled loop twice
545
+ in the census (#2057, #2060) *after* same-vendor escalation had failed for 3–6 rounds.
546
+ Single-vendor has no equivalent move, so it should cap rounds more aggressively rather
547
+ than grind.
548
+
549
+ ### 6.6 Citing the strategy in every review
550
+
551
+ A review is only auditable if it says what policy produced it. Every crosscheck comment
552
+ carries the strategy version, the PR class that was matched, and the tier that class
553
+ selected — in both the machine annotation and the human-readable line.
554
+
555
+ ```
556
+ <!-- crosscheck: origin=claude reviewer=claude model=claude-opus-5 type=review
557
+ round=1 verdict=BLOCK strategy=1.1.0 class=risky tier=thorough
558
+ service=crosscheck sha=abc123 -->
559
+ ```
560
+
561
+ > _Reviewed with [Claude Code](https://claude.ai/code) via [Crosscheck](https://github.com/humanbased-ai/crosscheck)
562
+ > with Opus 5 (high) — thorough tier · touches a security or data-critical path, where a
563
+ > missed defect is expensive · strategy v1.1.0_
564
+
565
+ The rationale text is not written per review — it is the matched class's `reason` field,
566
+ so the explanation and the routing decision cannot drift apart. `strategy`, `class`, and
567
+ `tier` are **additive** annotation fields: per the annotation contract in `CLAUDE.md`,
568
+ parsers already tolerate unknown fields, so this is a minor version bump, not a breaking
569
+ change.
570
+
571
+ This is what makes a past review explicable. A BLOCK from six weeks ago was produced by
572
+ whatever policy was current then; without the version stamp, re-reading it under today's
573
+ policy is a guess.
574
+
575
+ ### 6.7 Keeping the table current
576
+
577
+ The table is a perishable asset — `review-model-tiers.json` already went stale, pinning
578
+ `thorough` to `claude-opus-4-8` after `claude-opus-5` superseded it at the same price
579
+ and +7.4 points. That is a free capability loss, and it happened silently.
580
+
581
+ Staleness is now loud, by machine:
582
+
583
+ 1. **`npm run verify:strategy`** ([`scripts/verify-review-strategy.mjs`](../scripts/verify-review-strategy.mjs))
584
+ checks three things — internal consistency (every routed model exists; no banned
585
+ model is routed; every class carries a citable reason), freshness against
586
+ `review_interval_days`, and source drift (each `sources[].checks` string still
587
+ appears on the live page).
588
+ 2. **The `Review Strategy` workflow** ([`.github/workflows/review-strategy.yml`](../.github/workflows/review-strategy.yml))
589
+ runs it on every PR touching the strategy, and weekly on a schedule. A scheduled
590
+ failure opens a `strategy-drift` issue with the report and the remediation steps —
591
+ it deliberately does *not* auto-edit the file, because model choice is a judgement
592
+ backed by benchmarks a human has to read.
593
+ 3. **Fail loudly on model rejection.** The `gpt-5.6-terra` 400 (`requires a newer
594
+ version of Codex`) was silently absorbed by smart-switch, so the operator never
595
+ learned their configured model was dead. Version and model errors should surface as
596
+ configuration errors, not degrade quietly.
597
+ 4. **`min_cli_version` per vendor**, checked at startup when a model needs it.
598
+
599
+ ### 6.8 What classification may and may not decide
600
+
601
+ Every class in §6.5 is computed from the file list alone — no model call, no cost, and
602
+ available before the first review starts.
603
+
604
+ The hard constraint is what classification is *allowed* to conclude. It may:
605
+
606
+ - set the **floor** (`trivial`, `deletion_only`, `test_only` → `fast`)
607
+ - **promote** on consequence, not on predicted difficulty (`risky` → `thorough`)
608
+ - choose the **step set** (`docs` → review-only; `generated` → skip)
609
+
610
+ It may **not** predict that a PR will be hard and pre-emptively assign `thorough` for
611
+ that reason. §4 shows static features cannot support that inference: churn correlates
612
+ only 0.513 with realized cost, the largest one-call PR was 101,118 lines, and the most
613
+ expensive PR in the census changed 2 files. Difficulty is discovered by reviewing, and
614
+ that is what the ladder in §6.4 is for.
615
+
616
+ This is the discipline that keeps the table honest as it grows: new classes may adjust
617
+ the floor or the step set, but escalation stays evidence-driven.
618
+
619
+ ---
620
+
621
+ ## 7. Expected effect
622
+
623
+ | Change | Mechanism | Effect on the goal |
624
+ |---|---|---|
625
+ | Bounded set + closing contract (§6.2) | Loop gets a terminal state | Prerequisite for everything else |
626
+ | **Docs → review-only** (§6.5) | Removes the loop from the worst-converging class | Largest single tail saving |
627
+ | Pin models (§5, §6.1) | 148 PRs leave `default` | Unresolved 74% → plausibly ~25% |
628
+ | Cap rounds + wall-clock (§6.2) | 18 PRs truncated to ≤7 calls | **139 of 597 calls removed (-23%)**, verified |
629
+ | Latency budget (§6.1) | Review lands inside the merge window | Recovers the 12% arriving after merge |
630
+ | Severity discipline (§6.3) | ~15% fewer spurious BLOCKs | BLOCK becomes actionable |
631
+ | Skip generated / deletion-only (§6.5) | Removes calls with no defect surface | Pure saving, no quality cost |
632
+ | Publish a check run | Lets a team gate if they choose | Enables adoption; team's decision, not ours |
633
+
634
+ **The gate is what converts every other item here from advisory to load-bearing.** Today
635
+ review latency and review quality are both free variables — nothing downstream consumes
636
+ them. Once `crosscheck/review` is a required check, review latency becomes time-to-merge
637
+ directly, and *that* is what makes dynamic thoroughness a velocity lever rather than a
638
+ cost optimization.
639
+
640
+ Cost moves in both directions — up on pass 1, down hard on the tail — and nets roughly
641
+ flat to modestly lower. **That is the correct trade for this goal.** The call-count
642
+ reduction is the firm number; it follows from the round cap alone.
643
+
644
+ The measurable success criterion is **actioned-finding rate**, not cost: today 38% of
645
+ engaged PRs reach a closed state. Everything above should be judged on whether it moves
646
+ that number.
647
+
648
+ Throughput: #1998 goes from 4.6 hours of reviewer wall-clock, merged unresolved, to
649
+ ~1.2 hours with a human handoff carrying a bounded list.
650
+
651
+ Two levers not modelled: `gpt-5.6-luna` at $0.058/call is 4× cheaper than Haiku on
652
+ output and reportedly much stronger — but that number is cross-family, so it needs a
653
+ local A/B, not a config change. And prompt caching cuts cached input 90%.
654
+
655
+ ---
656
+
657
+ ## 8. Rollout
658
+
659
+ Scoped to the tool. Whether a team gates on the result, and how they merge, is theirs to
660
+ decide — crosscheck's job is to be correct, bounded, and current by default.
661
+
662
+ **Silent-failure fixes** — pure defects, independent of every open design question:
663
+
664
+ 1. **`resolveCodexModel` discards the configured model** under subscription auth (§5).
665
+ Verified: the CLI accepts `--model` under either auth. 81% of census calls ran on an
666
+ unpinned `default` because of this one guard.
667
+ 2. **Model/version rejection is absorbed by smart-switch.** The `gpt-5.6-terra` 400 was
668
+ swallowed, so the operator never learned their configured model was dead. Surface
669
+ configuration errors as configuration errors; add `min_cli_version` checks at startup.
670
+ 3. **Audit the round cap.** `workflow.yml` sets `max_rounds: 3`; #1998 reached round 12.
671
+ `exceedsMaxRounds` (`runner.ts:184`) exempts a review step coerced to recheck — a
672
+ plausible path, not yet confirmed. No bound holds until this does.
673
+ 4. **Refresh the tier table** — `claude-opus-4-8` → `claude-opus-5`, same price, +7.4pts.
674
+
675
+ **Behavior** — needs the fixes above to be measurable:
676
+
677
+ 5. **Bound and rank the finding set; state the closing contract** (§6.2); **tighten
678
+ BLOCK** (§6.3). Prompt-level only, reversible in one commit.
679
+ 6. **Land the strategy table** (§6.5) in shadow mode — log the class and tier beside the
680
+ realized outcome, change nothing. Start with the three highest-confidence classes:
681
+ `generated` (skip), `deletion_only`, and `docs` (review-only).
682
+ 7. **Measure per-tier latency**, then enable the latency budget (§6.1). The fast-lane
683
+ argument depends on numbers not yet measured.
684
+ 8. **Enable the ladder** (§6.4) last — highest impact, easiest to get wrong.
685
+
686
+ **New capability** — makes crosscheck adoptable where review must be load-bearing:
687
+
688
+ 9. **Publish a `crosscheck/review` check run** — `pending` while reviewing, `success`
689
+ when the blocking set is closed or empty, `failure` when it is not. `client.ts` reads
690
+ check-runs today but creates none, so a team *cannot* gate on crosscheck even if they
691
+ want to. Ship it off by default and let teams opt in.
692
+
693
+ ```yaml
694
+ quality:
695
+ tier: balanced
696
+ mode: smart # already in schema.ts; currently inert
697
+ smart:
698
+ shadow: false
699
+ max_blocking: 5 # above this → advisory, never gating
700
+ max_rounds: 3 # hard stop, then hand to a human
701
+ max_wall_clock_min: 60 # whichever binds first ends the loop
702
+ ```
703
+
704
+ ### Open questions
705
+
706
+ - **What block rate would a required check actually produce?** Today 62% of engaged PRs
707
+ end non-APPROVE. Gating naively would block roughly that share — untenable. Step 3's
708
+ unrequired rollout measures the real number after §6.2–6.3 land, and that number
709
+ decides whether gating is viable at all.
710
+ - **Does the closing contract change behavior on its own?** It is the least-proven part
711
+ of the design, and it only matters once a gate forces engagement. Cheapest test: ship
712
+ the bounded-set wording for two weeks and watch the actioned-finding rate.
713
+ - **Should the gate apply to every PR?** A required check on trivial PRs that merge in
714
+ nine minutes may cost more velocity than the defects it catches. Gating only the
715
+ risky-path and `risk:T3` classes (§6.5) is the conservative first cut.
716
+ - **What is the real time-to-first-review at each tier?** The latency budget in §6.1 is
717
+ built on one measured configuration (Sonnet 5, 643 s median). Haiku and Luna
718
+ latencies on real diffs are unmeasured, and the whole fast-lane argument depends on
719
+ them. Measure before committing thresholds.
720
+ - **Does `fast` hold on trivial PRs?** All one-call PRs ran Sonnet 5. Whether Haiku 4.5
721
+ (73.3%) reaches the same verdicts is untested. Replay those diffs at both tiers.
722
+ - **Is `default` causal or a proxy for routing?** Shadow mode with a pinned model on
723
+ unchanged routing settles it.
724
+ - **Should docs PRs enter the fix/recheck loop at all?** They converge worst (#1998).
725
+ Their findings are real but constitute design negotiation. A review-only workflow may
726
+ fit them better.
727
+ - **Should coverage widen?** 201 of 400 merged PRs saw no review at all — an author-scope
728
+ gate. Out of scope here, but it dominates any projection.
729
+
730
+ ---
731
+
732
+ ## Sources
733
+
734
+ - [Anthropic model catalog](https://platform.claude.com/docs/en/about-claude/models/overview) — pricing, legacy status
735
+ - [Anthropic effort parameter](https://platform.claude.com/docs/en/build-with-claude/effort) — levels, guidance, caching constraint
736
+ - [OpenAI model catalog](https://developers.openai.com/api/docs/models) — GPT-5.6 family
737
+ - [CR-Bench](https://arxiv.org/html/2603.11078v1) — review precision/recall/SNR
738
+ - [SWE-bench Verified leaderboard](https://benchlm.ai/benchmarks/sweVerified) — Claude-family scores