@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
@@ -1 +1 @@
1
- {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/commands/watch.ts"],"names":[],"mappings":"AAyKA,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,wBAAsB,QAAQ,CAAC,IAAI,GAAE,SAAc,iBAqvClD"}
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/commands/watch.ts"],"names":[],"mappings":"AA4KA,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAuBD,wBAAsB,QAAQ,CAAC,IAAI,GAAE,SAAc,iBAo4ClD"}
@@ -2,19 +2,21 @@ import { execSync, spawn } from 'child_process';
2
2
  import chalk from 'chalk';
3
3
  import { createWebhookServer } from '../github/webhook.js';
4
4
  import { createGithubClient, getCommitMessage, registerOrgWebhook, deleteOrgWebhook, registerRepoWebhook, deleteRepoWebhook, patchOrgWebhookEvents, patchRepoWebhookEvents, findOrgWebhook, findRepoWebhook, listUserRepos, checkRepoAccessible, } from '../github/client.js';
5
+ import { closeSupersededAutoFixPRs } from '../github/superseded-fix-pr.js';
5
6
  import { detectOriginFull, assignReviewer } from '../github/detector.js';
6
- import { loadConfig, getGithubToken, getWebhookSecret, resolveConfigPath, promptDeploymentMode, detectScopesForDeployment, patchDeploymentConfig, detectGitHubLogin, } from '../config/loader.js';
7
+ import { loadConfig, getGithubToken, getWebhookSecret, resolveConfigPath, promptDeploymentMode, detectScopesForDeployment, patchDeploymentConfig, detectGitHubLogin, getLinearCredentials, } from '../config/loader.js';
7
8
  import { randomFortune } from '../lib/fortune.js';
8
9
  import { scanUnreviewedPRs } from '../lib/backtrace.js';
9
10
  import { initLogger, log as fileLog, logError, logUncaught } from '../lib/logger.js';
10
11
  import { isAuthorAllowed } from '../lib/filter.js';
11
12
  import { runWorkflow } from '../lib/runner.js';
12
- import { loadWorkflow, DEFAULT_RECHECK_INSTRUCTIONS } from '../lib/workflow.js';
13
- import { filterStepsByTypes, formatRepoWorkflowSteps, isRecheckWithoutFix, isReviewOnlyWorkflow, readRepoWorkflowStepTypes, resolveRepoWorkflowSteps, workflowHasStep } from '../lib/repo-workflow.js';
13
+ import { loadWorkflow, linearWritePossible, DEFAULT_RECHECK_INSTRUCTIONS } from '../lib/workflow.js';
14
+ import { filterStepsByTypes, formatRepoWorkflowSteps, isReviewOnlyWorkflow, readRepoWorkflowStepTypes, resolveRepoWorkflowSteps, workflowHasStep } from '../lib/repo-workflow.js';
14
15
  import { fetchStepHistory, identifyNextWorkflowStep, decideReviewOnly } from '../lib/pr-workflow-state.js';
15
16
  import { parseAnnotation } from '../lib/annotation.js';
16
17
  import { PRBoard, fmtTime, FMT_TIME_WIDTH } from '../lib/board.js';
17
18
  import { clonePRForReview } from '../lib/clone.js';
19
+ import { resolveLinearAuth, isLinearConfigError } from '../linear/identity.js';
18
20
  import { getSmartSwitch, isSubscriptionLimitError, detectFailedVendor, triggerSwitch, notifyReviewSuccess, stopSmartSwitch, } from '../lib/smart-switch.js';
19
21
  import { buildReviewFailedCommentBody } from '../lib/comment-bodies.js';
20
22
  import { classifyReviewerError } from '../lib/reviewer-error.js';
@@ -128,6 +130,21 @@ function openTunnel(localPort) {
128
130
  });
129
131
  });
130
132
  }
133
+ // PR history is what decides whether a step runs at all — the approval stop, the
134
+ // per-SHA dedup, the resume point. Acting without it means posting a duplicate review
135
+ // or modifying an already-approved commit, so callers fail closed rather than assume an
136
+ // empty history. One short retry first: the usual cause is a transient GitHub blip or a
137
+ // rate-limit tick, and deferring the event strands the PR until something else happens.
138
+ const HISTORY_RETRY_DELAY_MS = 3_000;
139
+ async function fetchStepHistoryWithRetry(owner, repo, prNumber, token) {
140
+ try {
141
+ return await fetchStepHistory(owner, repo, prNumber, token);
142
+ }
143
+ catch {
144
+ await new Promise(resolve => setTimeout(resolve, HISTORY_RETRY_DELAY_MS));
145
+ return await fetchStepHistory(owner, repo, prNumber, token);
146
+ }
147
+ }
131
148
  export async function runWatch(opts = {}) {
132
149
  const configPath = opts.config;
133
150
  let config = loadConfig(configPath);
@@ -156,6 +173,25 @@ export async function runWatch(opts = {}) {
156
173
  console.error(chalk.red(`✗ ${err instanceof Error ? err.message : String(err)}`));
157
174
  process.exit(1);
158
175
  }
176
+ // Preflight Linear identity at startup. watch is a long-running daemon: a bad
177
+ // credential discovered per-event would either drop events silently or spam the
178
+ // board. Fail loudly at boot instead, the way the run path fails before its
179
+ // clone. The token is discarded — per-event resolution mints a fresh one, since
180
+ // app tokens outlive neither a long daemon run nor a rotation.
181
+ // Same gate as the run and per-event paths: a daemon whose configured workflow
182
+ // is fix/conflict-resolve only can never call notifyLinear, so a missing
183
+ // credential must not stop it booting.
184
+ if (linearWritePossible(config.linear, loadWorkflow(process.cwd()))) {
185
+ try {
186
+ const preflight = await resolveLinearAuth(config.linear, getLinearCredentials(config.linear.auth));
187
+ console.log(chalk.dim(` linear: ${preflight.mode} — writes as ${preflight.actor}`));
188
+ }
189
+ catch (err) {
190
+ logError({ command: 'watch', phase: 'linear-auth' }, err);
191
+ console.error(chalk.red(`✗ ${err instanceof Error ? err.message : String(err)}`));
192
+ process.exit(isLinearConfigError(err) ? 1 : 2);
193
+ }
194
+ }
159
195
  fileLog({ level: 'info', event: 'session_start', command: 'watch' });
160
196
  // Fetch the authenticated user so the onComment handler can reject annotation
161
197
  // injection from non-token accounts. If this fails, the handler is disabled
@@ -191,12 +227,16 @@ export async function runWatch(opts = {}) {
191
227
  // Last-reviewed diff hash per PR — skip reviews when a new SHA has identical diff vs base
192
228
  // (force-push, amend, no-op rebase). Persisted so restarts don't re-review unchanged content.
193
229
  const diffHashes = new PersistentDiffHashMap();
194
- // PRs reviewed at least once this session synchronize events on these run as recheck rounds
195
- const reviewedPRKeys = new Set();
230
+ // Completed round count per PRthe auto-loop's round counter, which history cannot
231
+ // supply (it returns the last recheck's round unchanged across iterations).
196
232
  const prRoundCounts = new Map();
197
- // PR+sha pairs completed by this watch session used to suppress issue_comment
198
- // re-entries for reviews that watch posted itself (as opposed to kickass).
199
- const reviewedPRShaKeys = new Set();
233
+ // Comment IDs the issue_comment bridge has already handled, so a webhook redelivery
234
+ // of the same comment does not dispatch twice. There is no need to guess at which
235
+ // comments are "ours": webhook.ts forwards only `trigger=kickass` annotations, and
236
+ // this watcher's own reviews carry trigger=watch/comment/backtrace, so they never
237
+ // reach the bridge. Every comment that does reach it is a kickass dispatch whose
238
+ // verdict this watcher is meant to act on — history detection decides how.
239
+ const handledCommentIds = new Set();
200
240
  // Idle tracking — reset on any PR activity; used by the idle-issue timer.
201
241
  let lastActivityAt = Date.now();
202
242
  let idleAnalysisRunning = false;
@@ -215,6 +255,7 @@ export async function runWatch(opts = {}) {
215
255
  fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'author_not_allowed', author: params.author });
216
256
  return;
217
257
  }
258
+ const prKey = `${owner}/${repoName}#${prNumber}`;
218
259
  const { origin, method: originMethod } = await detectOriginFull(params.body ?? '', params.headRef, owner, repoName, prNumber, config, token, params.author);
219
260
  // Smart-switch: when cross-vendor is degraded, override to single-vendor with the healthy vendor
220
261
  const ss = getSmartSwitch();
@@ -235,9 +276,17 @@ export async function runWatch(opts = {}) {
235
276
  title: params.title,
236
277
  body: params.body ?? '',
237
278
  head: { ref: params.headRef, sha: params.headSha, repo: params.headRepo ? { full_name: params.headRepo } : null },
238
- base: { ref: params.baseRef, repo: { full_name: `${owner}/${repoName}` } },
279
+ base: {
280
+ ref: params.baseRef,
281
+ repo: {
282
+ full_name: `${owner}/${repoName}`,
283
+ ...(params.defaultBranch !== undefined && { default_branch: params.defaultBranch }),
284
+ },
285
+ },
239
286
  html_url: `https://github.com/${owner}/${repoName}/pull/${prNumber}`,
240
287
  user: { login: params.author },
288
+ ...(params.labels !== undefined && { labels: params.labels.map(name => ({ name })) }),
289
+ ...(params.createdAt !== undefined && { created_at: params.createdAt }),
241
290
  };
242
291
  if (!acquirePRLock(owner, repoName, prNumber, params.headSha)) {
243
292
  fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'in_progress_local' });
@@ -257,21 +306,26 @@ export async function runWatch(opts = {}) {
257
306
  logError({ repo: `${owner}/${repoName}`, pr: prNumber, phase: 'lock' }, err);
258
307
  return;
259
308
  }
260
- const prKey = `${owner}/${repoName}#${prNumber}`;
261
- // Determine the correct starting step from PR comment history so watch
262
- // behaves correctly after a restart (reviewedPRKeys is in-memory only).
263
- // Fast-path: if the PR was reviewed in this session, skip the API call.
264
- // Exception: comment-triggered runs must always do live detection
265
- // reviewedPRKeys is SHA-agnostic and would misroute a kickass review
266
- // annotation for a new SHA as a recheck run instead of routing to fix.
267
- // auto_loop runs also skip the session fast-path so history detection
268
- // picks the correct starting step (fix, not recheck); round is instead
269
- // incremented from prRoundCounts to match the completed round count.
270
- let isRecheckRun = params.action === 'comment'
271
- ? reviewedPRShaKeys.has(key) // SHA-specific: already processed this exact sha
272
- : params.action === 'auto_loop'
273
- ? false // always use history detection for auto-loop rounds
274
- : reviewedPRKeys.has(prKey); // session fast-path for webhook/backtrace events
309
+ // Determine the correct starting step from PR comment history. Webhook, backtrace,
310
+ // and auto_loop events always ask there is no PR-level session fast-path.
311
+ //
312
+ // There used to be one (`reviewedPRKeys`): reviewed-in-this-session meant "run the
313
+ // next event as a recheck". It was wrong by construction on two axes. It is
314
+ // SHA-agnostic, so a push it had never seen inherited the previous SHA's routing;
315
+ // and it only knows what THIS process did, so an approval, a forced re-review, or a
316
+ // fix pushed by `kickass`, `crosscheck run`, or a second watcher was invisible to
317
+ // it. Each case where it guessed wrong grew its own guard — recheck-no-fix depth,
318
+ // repo overrides, approvals and every one of those guards did the same thing:
319
+ // undo the guess and ask history. So ask history. It costs one call per event, and
320
+ // it is the only thing that sees the PR as it actually is.
321
+ //
322
+ // Comment triggers ask too. The SHA-specific fast path they used to take ran an
323
+ // externally-posted NEEDS_WORK as a recheck instead of routing to fix, because it
324
+ // skipped detection whenever this session had already touched the SHA.
325
+ let isRecheckRun = false;
326
+ // auto_loop's round is pre-computed from the completed round count rather than
327
+ // from history, which returns the last recheck's round unchanged across iterations
328
+ // and would defeat the max_rounds cap.
275
329
  let round = params.action === 'auto_loop'
276
330
  ? (prRoundCounts.get(prKey) ?? 1) + 1
277
331
  : isRecheckRun ? (prRoundCounts.get(prKey) ?? 1) + 1 : 1;
@@ -285,17 +339,6 @@ export async function runWatch(opts = {}) {
285
339
  isRecheckRun = false;
286
340
  round = 1;
287
341
  }
288
- // A recheck-no-fix depth (`review,recheck`) decides review-vs-recheck per SHA from
289
- // the last verdict: an unresolved review gets a recheck, an APPROVE gets a fresh
290
- // review (identifyNextWorkflowStep). The session fast-path above is PR-level and
291
- // SHA-agnostic, so it would coerce every later push in this process into a recheck
292
- // and never consult that decision. Force history detection instead. Comment-
293
- // triggered runs keep their SHA-specific fast path — a kickass re-review targets a
294
- // SHA history detection would report as already complete.
295
- if (isRecheckWithoutFix(repoStepOverride) && isRecheckRun && params.action !== 'comment') {
296
- isRecheckRun = false;
297
- round = 1;
298
- }
299
342
  if (reviewOnlyForRepo) {
300
343
  // Review-only comes from a per-repo override (crosscheck alter --review-only).
301
344
  // Restrict the workflow to review step(s) and dedup on the exact head sha so
@@ -312,7 +355,7 @@ export async function runWatch(opts = {}) {
312
355
  round = 1;
313
356
  resolvedSteps = reviewSteps;
314
357
  try {
315
- const history = await fetchStepHistory(owner, repoName, prNumber, token);
358
+ const history = await fetchStepHistoryWithRetry(owner, repoName, prNumber, token);
316
359
  const decision = decideReviewOnly(history, params.headSha);
317
360
  if (decision.alreadyReviewed) {
318
361
  await releaseRemoteLock(lockOctokit, owner, repoName, params.headSha, 'success');
@@ -322,7 +365,18 @@ export async function runWatch(opts = {}) {
322
365
  }
323
366
  round = decision.round;
324
367
  }
325
- catch { /* best-effort — fall back to a fresh round-1 review */ }
368
+ catch (err) {
369
+ // The dedup this mode relies on lives in that history. Reviewing without it
370
+ // would post a second review on a SHA already covered.
371
+ // Released as `failure`, not `success`: this SHA was not reviewed, and
372
+ // `crosscheck/review` is a commit status a repo can require for merge — a
373
+ // green one here would let an unreviewed HEAD satisfy branch protection
374
+ // during a GitHub outage.
375
+ await releaseRemoteLock(lockOctokit, owner, repoName, params.headSha, 'failure');
376
+ releasePRLock(owner, repoName, prNumber, params.headSha);
377
+ fileLog({ level: 'warn', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'history_unavailable', sha: params.headSha, error: err instanceof Error ? err.message : String(err) });
378
+ return;
379
+ }
326
380
  }
327
381
  else if (!isRecheckRun) {
328
382
  try {
@@ -331,18 +385,34 @@ export async function runWatch(opts = {}) {
331
385
  // fresh so live edits to workflow.yml apply across events.
332
386
  const globalWorkflow = loadWorkflow(process.cwd());
333
387
  const allSteps = repoStepOverride ? filterStepsByTypes(globalWorkflow, repoStepOverride) : globalWorkflow;
334
- const history = await fetchStepHistory(owner, repoName, prNumber, token);
335
- const nextResult = identifyNextWorkflowStep(history, allSteps, params.headSha);
388
+ const history = await fetchStepHistoryWithRetry(owner, repoName, prNumber, token);
389
+ // The webhook payload carries no usable `mergeable`, so ask — but only for
390
+ // workflows that can act on the answer. GitHub computes it lazily, and a null
391
+ // simply means the conflict route is not offered on this event. Unlike the
392
+ // history read above this one stays best-effort: not knowing whether the PR
393
+ // conflicts only costs a deferred resolve, not a wrong review.
394
+ let mergeable = null;
395
+ if (allSteps.some(s => s.type === 'conflict-resolve')) {
396
+ try {
397
+ const { data: freshPR } = await lockOctokit.rest.pulls.get({ owner, repo: repoName, pull_number: prNumber });
398
+ mergeable = freshPR.mergeable;
399
+ }
400
+ catch { /* best-effort — unknown mergeability just skips the conflict route */ }
401
+ }
402
+ const nextResult = identifyNextWorkflowStep(history, allSteps, params.headSha, { mergeable });
336
403
  if (nextResult.step === null) {
337
404
  // Workflow already complete for this HEAD sha — release lock and skip.
338
405
  // Happens when a synchronize event fires after all steps are done.
339
406
  await releaseRemoteLock(lockOctokit, owner, repoName, params.headSha, 'success');
340
407
  releasePRLock(owner, repoName, prNumber, params.headSha);
341
- fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'workflow_complete', sha: params.headSha });
408
+ fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: nextResult.stopReason ?? 'workflow_complete', sha: params.headSha });
342
409
  return;
343
410
  }
344
411
  if (nextResult.hasExistingReview) {
345
- isRecheckRun = nextResult.step.type !== 'review';
412
+ // conflict-resolve is excluded: the run continues into the review step, and
413
+ // the merge commit it produces is new content owed a real review, not a
414
+ // recheck of findings the merge had nothing to do with.
415
+ isRecheckRun = nextResult.step.type !== 'review' && nextResult.step.type !== 'conflict-resolve';
346
416
  // auto_loop pre-computed the correct incremented round from prRoundCounts;
347
417
  // history detection returns the last recheck's round (unchanged across
348
418
  // iterations), so overwriting here would defeat the max_rounds cap.
@@ -391,15 +461,26 @@ export async function runWatch(opts = {}) {
391
461
  }
392
462
  }
393
463
  }
394
- catch {
395
- // best-effort fall back to session-based detection.
396
- // For auto_loop, force isRecheckRun=true so a transient history-fetch
397
- // failure does not restart the workflow from the review step.
398
- // A recheck-no-fix repo has no session state left to fall back to (the guard
399
- // above cleared it), so it re-reviews the new SHA the conservative direction,
400
- // since a fresh review never assumes findings that may already be resolved.
401
- if (params.action === 'auto_loop')
402
- isRecheckRun = true;
464
+ catch (err) {
465
+ // History still unreadable after the retry. Everything this function decides —
466
+ // whether HEAD is already approved, already reviewed, or mid-loop lives in
467
+ // that history, so running anyway risks a second review on an approved commit
468
+ // or a fix re-applied to code that already has one. Defer instead: release the
469
+ // locks and let the next event, `kickass`, or `crosscheck run` pick the PR up.
470
+ //
471
+ // auto_loop is the exception. It is a continuation of a run this process just
472
+ // completed, so the state is known from the session rather than from history,
473
+ // and deferring would strand the loop — nothing else re-triggers it.
474
+ if (params.action !== 'auto_loop') {
475
+ // `failure`, not `success` — see the review-only branch above: a green
476
+ // `crosscheck/review` on a HEAD nobody reviewed can satisfy branch
477
+ // protection.
478
+ await releaseRemoteLock(lockOctokit, owner, repoName, params.headSha, 'failure');
479
+ releasePRLock(owner, repoName, prNumber, params.headSha);
480
+ fileLog({ level: 'warn', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'history_unavailable', sha: params.headSha, error: err instanceof Error ? err.message : String(err) });
481
+ return;
482
+ }
483
+ isRecheckRun = true;
403
484
  }
404
485
  }
405
486
  const reviewStart = Date.now();
@@ -407,6 +488,32 @@ export async function runWatch(opts = {}) {
407
488
  let stopHeartbeat = () => { };
408
489
  let boardAdded = false;
409
490
  try {
491
+ // Board first, so any failure below is visible rather than swallowed by the
492
+ // enclosing catch (which only calls logError — a no-op when file logging is
493
+ // off). Then auth, then the clone: during an outage a long-running watcher
494
+ // would otherwise pay for a full clone on every event before rejecting it.
495
+ // Computed here rather than at the runWorkflow call so the auth gate below
496
+ // sees the workflow that will actually run. resolvedSteps alone is
497
+ // undefined for a plain event, and canWriteVerdict fails open on undefined
498
+ // — which let a fix-only daemon resolve credentials on every event.
499
+ const workflowStepsForRun = resolvedSteps
500
+ ?? (repoStepOverride ? filterStepsByTypes(loadWorkflow(process.cwd()), repoStepOverride) : loadWorkflow(process.cwd()));
501
+ board.addPR(key, prNumber, `${owner}/${repoName}`, params.headRef, round);
502
+ boardAdded = true;
503
+ // Only workflows that can write a verdict need an identity — the fix and
504
+ // resolve paths never call notifyLinear.
505
+ let linearAuth = null;
506
+ if (linearWritePossible(effectiveConfig.linear, workflowStepsForRun)) {
507
+ try {
508
+ linearAuth = await resolveLinearAuth(effectiveConfig.linear, getLinearCredentials(effectiveConfig.linear.auth));
509
+ }
510
+ catch (err) {
511
+ const message = err instanceof Error ? err.message : String(err);
512
+ board.failPR(key, `linear identity unavailable: ${message}`);
513
+ fileLog({ level: 'error', event: 'linear_auth_failed', repo: `${owner}/${repoName}`, pr: prNumber, reason: message });
514
+ throw err;
515
+ }
516
+ }
410
517
  await clonePRForReview({
411
518
  owner, repo: repoName, prNumber, baseRef: params.baseRef,
412
519
  tmpDir, token, protocol: config.clone_protocol,
@@ -442,18 +549,18 @@ export async function runWatch(opts = {}) {
442
549
  logError({ repo: `${owner}/${repoName}`, pr: prNumber, phase: 'no_diff_comment' }, err);
443
550
  }
444
551
  await releaseRemoteLock(lockOctokit, owner, repoName, params.headSha, 'success');
552
+ // The PR is on the board from before the clone, so close its slot rather
553
+ // than leaving a row that never resolves.
554
+ board.completePR(key, { elapsedMs: Date.now() - reviewStart, url: `https://github.com/${owner}/${repoName}/pull/${prNumber}` });
445
555
  return;
446
556
  }
447
- board.addPR(key, prNumber, `${owner}/${repoName}`, params.headRef, round);
448
- boardAdded = true;
449
557
  const prLoc = computePRLoc(tmpDir, params.baseRef);
450
558
  board.updatePR(key, { prLoc });
451
559
  stopHeartbeat = startRemoteLockHeartbeat(lockOctokit, owner, repoName, params.headSha);
452
- const workflowStepsForRun = resolvedSteps
453
- ?? (repoStepOverride ? filterStepsByTypes(loadWorkflow(process.cwd()), repoStepOverride) : undefined);
454
- const { verdict, fixAppliedCount } = await runWorkflow({
560
+ const { verdict, fixAppliedCount, strategySkipped } = await runWorkflow({
455
561
  owner, repoName, prNumber, pr,
456
562
  tmpDir, token, config: effectiveConfig, origin,
563
+ linearAuth,
457
564
  reviewStart,
458
565
  log: (msg) => bLog(`${chalk.dim(fmtTime())} ${msg}`),
459
566
  onPhaseChange: (label, data) => board.updatePR(key, { label, ...data }),
@@ -470,10 +577,11 @@ export async function runWatch(opts = {}) {
470
577
  round,
471
578
  trigger: params.action === 'backtrace' ? 'backtrace' : params.action === 'comment' ? 'comment' : 'watch',
472
579
  });
473
- void verdict;
474
- reviewedPRKeys.add(prKey);
475
- reviewedPRShaKeys.add(key); // key = "owner/repo#pr@sha"
476
- prRoundCounts.set(prKey, round);
580
+ // A strategy-skipped PR never ran a review, so it must not advance the
581
+ // auto-loop's round counter — that run posted nothing to count.
582
+ if (strategySkipped === undefined) {
583
+ prRoundCounts.set(prKey, round);
584
+ }
477
585
  // Recompute the diff hash AFTER runWorkflow — workflow steps such as
478
586
  // `conflict-resolve` or `fix` followed by `recheck` can mutate the checkout,
479
587
  // so the pre-workflow hash may not represent the content that was actually
@@ -493,7 +601,10 @@ export async function runWatch(opts = {}) {
493
601
  }
494
602
  catch { /* fall back — skip auto-loop this cycle */ }
495
603
  }
496
- if (newDiffHash) {
604
+ // Same gate as the session caches above: caching the diff of a PR that was
605
+ // never reviewed would make the first event that *should* review it get
606
+ // skipped as `no_diff_change`.
607
+ if (newDiffHash && strategySkipped === undefined) {
497
608
  // For non-commit delivery with an applied fix the checkout may be on a fix
498
609
  // branch; computeDiffHash would return the post-fix diff, not the original PR
499
610
  // diff. Caching that under prKey corrupts future no_diff_change checks for the
@@ -514,9 +625,15 @@ export async function runWatch(opts = {}) {
514
625
  }
515
626
  }
516
627
  }
628
+ // A class-skipped PR (e.g. lockfile-only) never ran a review, so say so
629
+ // rather than reporting it as a completed one.
630
+ if (strategySkipped) {
631
+ bLog(`${chalk.dim(fmtTime())} ${chalk.dim(`PR #${prNumber} skipped — ${strategySkipped} class, nothing to review`)}`);
632
+ }
517
633
  board.completePR(key, {
518
634
  elapsedMs: Date.now() - reviewStart,
519
635
  url: `github.com/${owner}/${repoName}/pull/${prNumber}`,
636
+ ...(strategySkipped !== undefined && { label: `skipped · ${strategySkipped}` }),
520
637
  });
521
638
  // Smart-switch recovery confirmation: if a restore attempt is pending and
522
639
  // this reviewer matches the previously-degraded vendor, announce full restoration.
@@ -649,6 +766,9 @@ export async function runWatch(opts = {}) {
649
766
  title: pr.title, body: pr.body, author: pr.user.login,
650
767
  headSha: pr.head.sha, headRef: pr.head.ref, headRepo: pr.head.repo?.full_name ?? null,
651
768
  baseRef: pr.base.ref, action: event.action,
769
+ ...(pr.labels !== undefined && { labels: pr.labels.map(l => l.name) }),
770
+ ...(pr.base.repo.default_branch !== undefined && { defaultBranch: pr.base.repo.default_branch }),
771
+ ...(pr.created_at !== undefined && { createdAt: pr.created_at }),
652
772
  });
653
773
  }, (msg) => bLog(chalk.dim(fmtTime()) + ' ' + msg), fileLog, async (event) => {
654
774
  const owner = event.repository.owner.login;
@@ -698,10 +818,11 @@ export async function runWatch(opts = {}) {
698
818
  fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'comment_stale_sha', sha: prData.head.sha, annotation_sha: annotationSha });
699
819
  return;
700
820
  }
701
- if (reviewedPRShaKeys.has(`${owner}/${repoName}#${prNumber}@${prData.head.sha}`)) {
702
- fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'comment_self_trigger', sha: prData.head.sha });
821
+ if (handledCommentIds.has(event.comment.id)) {
822
+ fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'comment_already_handled', comment_id: event.comment.id });
703
823
  return;
704
824
  }
825
+ handledCommentIds.add(event.comment.id);
705
826
  // GitHub can deliver issue_comment before ck run's finally block releases
706
827
  // the remote lock. Retry with backoff; re-fetch the PR head on each attempt
707
828
  // so we don't dispatch against a sha that advanced while we were waiting.
@@ -725,10 +846,6 @@ export async function runWatch(opts = {}) {
725
846
  break;
726
847
  }
727
848
  }
728
- if (reviewedPRShaKeys.has(`${owner}/${repoName}#${prNumber}@${prData.head.sha}`)) {
729
- fileLog({ level: 'info', event: 'pr_skipped', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'comment_self_trigger', sha: prData.head.sha });
730
- break;
731
- }
732
849
  if (await checkRemoteLock(octokit, owner, repoName, prData.head.sha).catch(() => false)) {
733
850
  fileLog({ level: 'info', event: 'comment_trigger_deferred', repo: `${owner}/${repoName}`, pr: prNumber, reason: 'in_progress_remote', attempt });
734
851
  continue;
@@ -743,6 +860,12 @@ export async function runWatch(opts = {}) {
743
860
  headRepo: prData.head.repo?.full_name ?? null,
744
861
  baseRef: prData.base.ref,
745
862
  action: 'comment',
863
+ // Same fields the webhook path forwards: without them the risky
864
+ // class's label and hotfix rules classify this PR differently
865
+ // depending on which trigger ran it.
866
+ ...(prData.labels !== undefined && { labels: prData.labels.map((l) => l.name) }),
867
+ ...(prData.base.repo?.default_branch !== undefined && { defaultBranch: prData.base.repo.default_branch }),
868
+ ...(prData.created_at !== undefined && { createdAt: prData.created_at }),
746
869
  });
747
870
  break;
748
871
  }
@@ -750,6 +873,29 @@ export async function runWatch(opts = {}) {
750
873
  catch (err) {
751
874
  logError({ repo: `${owner}/${repoName}`, pr: prNumber, phase: 'comment_trigger' }, err);
752
875
  }
876
+ }, async (event) => {
877
+ const owner = event.repository.owner.login;
878
+ const repoName = event.repository.name;
879
+ const prNumber = event.number;
880
+ try {
881
+ const octokit = createGithubClient(token);
882
+ const outcomes = await closeSupersededAutoFixPRs(octokit, owner, repoName, prNumber, event.pull_request.merge_commit_sha ?? null);
883
+ for (const outcome of outcomes) {
884
+ fileLog({
885
+ level: outcome.status === 'failed' ? 'warn' : 'info',
886
+ event: 'auto_fix_pr_superseded',
887
+ repo: `${owner}/${repoName}`, pr: prNumber,
888
+ fix_pr: outcome.prNumber, status: outcome.status,
889
+ ...(outcome.reason && { reason: outcome.reason }),
890
+ });
891
+ if (outcome.status === 'closed') {
892
+ bLog(chalk.dim(fmtTime()) + ` closed superseded auto-fix PR ${owner}/${repoName}#${outcome.prNumber} (${prNumber} merged)`);
893
+ }
894
+ }
895
+ }
896
+ catch (err) {
897
+ logError({ repo: `${owner}/${repoName}`, pr: prNumber, phase: 'auto_fix_supersede' }, err);
898
+ }
753
899
  });
754
900
  await new Promise((resolve, reject) => {
755
901
  server.on('error', (err) => {
@@ -1029,6 +1175,9 @@ export async function runWatch(opts = {}) {
1029
1175
  title: pr.title, body: pr.body, author: pr.author,
1030
1176
  headSha: pr.headSha, headRef: pr.headRef, headRepo: pr.headRepo,
1031
1177
  baseRef: pr.baseRef, action: 'backtrace',
1178
+ ...(pr.labels !== undefined && { labels: pr.labels }),
1179
+ ...(pr.defaultBranch !== undefined && { defaultBranch: pr.defaultBranch }),
1180
+ createdAt: pr.createdAt,
1032
1181
  })));
1033
1182
  }
1034
1183
  catch (err) {