@humanbased/crosscheck 0.14.0

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 (381) hide show
  1. package/AGENT.md +207 -0
  2. package/ISSUE.md +234 -0
  3. package/LICENSE +21 -0
  4. package/README.md +234 -0
  5. package/README.zh.md +169 -0
  6. package/assets/logo.png +0 -0
  7. package/assets/screenshot-watch-timing.png +0 -0
  8. package/assets/screenshot-watch-timing.svg +1 -0
  9. package/assets/screenshot-watch.png +0 -0
  10. package/crosscheck.config.example.yml +214 -0
  11. package/dist/__tests__/annotation.test.d.ts +2 -0
  12. package/dist/__tests__/annotation.test.d.ts.map +1 -0
  13. package/dist/__tests__/annotation.test.js +134 -0
  14. package/dist/__tests__/annotation.test.js.map +1 -0
  15. package/dist/__tests__/backtrace.test.d.ts +2 -0
  16. package/dist/__tests__/backtrace.test.d.ts.map +1 -0
  17. package/dist/__tests__/backtrace.test.js +280 -0
  18. package/dist/__tests__/backtrace.test.js.map +1 -0
  19. package/dist/__tests__/board.test.d.ts +2 -0
  20. package/dist/__tests__/board.test.d.ts.map +1 -0
  21. package/dist/__tests__/board.test.js +149 -0
  22. package/dist/__tests__/board.test.js.map +1 -0
  23. package/dist/__tests__/codex.test.d.ts +2 -0
  24. package/dist/__tests__/codex.test.d.ts.map +1 -0
  25. package/dist/__tests__/codex.test.js +92 -0
  26. package/dist/__tests__/codex.test.js.map +1 -0
  27. package/dist/__tests__/comment-bodies.test.d.ts +2 -0
  28. package/dist/__tests__/comment-bodies.test.d.ts.map +1 -0
  29. package/dist/__tests__/comment-bodies.test.js +75 -0
  30. package/dist/__tests__/comment-bodies.test.js.map +1 -0
  31. package/dist/__tests__/conflict-resolve.test.d.ts +2 -0
  32. package/dist/__tests__/conflict-resolve.test.d.ts.map +1 -0
  33. package/dist/__tests__/conflict-resolve.test.js +123 -0
  34. package/dist/__tests__/conflict-resolve.test.js.map +1 -0
  35. package/dist/__tests__/crosscheck-commit.test.d.ts +2 -0
  36. package/dist/__tests__/crosscheck-commit.test.d.ts.map +1 -0
  37. package/dist/__tests__/crosscheck-commit.test.js +13 -0
  38. package/dist/__tests__/crosscheck-commit.test.js.map +1 -0
  39. package/dist/__tests__/detector.test.d.ts +2 -0
  40. package/dist/__tests__/detector.test.d.ts.map +1 -0
  41. package/dist/__tests__/detector.test.js +112 -0
  42. package/dist/__tests__/detector.test.js.map +1 -0
  43. package/dist/__tests__/diagnose.test.d.ts +2 -0
  44. package/dist/__tests__/diagnose.test.d.ts.map +1 -0
  45. package/dist/__tests__/diagnose.test.js +164 -0
  46. package/dist/__tests__/diagnose.test.js.map +1 -0
  47. package/dist/__tests__/diff-hash.test.d.ts +2 -0
  48. package/dist/__tests__/diff-hash.test.d.ts.map +1 -0
  49. package/dist/__tests__/diff-hash.test.js +126 -0
  50. package/dist/__tests__/diff-hash.test.js.map +1 -0
  51. package/dist/__tests__/durations.test.d.ts +2 -0
  52. package/dist/__tests__/durations.test.d.ts.map +1 -0
  53. package/dist/__tests__/durations.test.js +26 -0
  54. package/dist/__tests__/durations.test.js.map +1 -0
  55. package/dist/__tests__/event-fields.test.d.ts +2 -0
  56. package/dist/__tests__/event-fields.test.d.ts.map +1 -0
  57. package/dist/__tests__/event-fields.test.js +50 -0
  58. package/dist/__tests__/event-fields.test.js.map +1 -0
  59. package/dist/__tests__/filter.test.d.ts +2 -0
  60. package/dist/__tests__/filter.test.d.ts.map +1 -0
  61. package/dist/__tests__/filter.test.js +21 -0
  62. package/dist/__tests__/filter.test.js.map +1 -0
  63. package/dist/__tests__/fix.test.d.ts +2 -0
  64. package/dist/__tests__/fix.test.d.ts.map +1 -0
  65. package/dist/__tests__/fix.test.js +124 -0
  66. package/dist/__tests__/fix.test.js.map +1 -0
  67. package/dist/__tests__/github-client.test.d.ts +2 -0
  68. package/dist/__tests__/github-client.test.d.ts.map +1 -0
  69. package/dist/__tests__/github-client.test.js +22 -0
  70. package/dist/__tests__/github-client.test.js.map +1 -0
  71. package/dist/__tests__/github-scan-client.test.d.ts +2 -0
  72. package/dist/__tests__/github-scan-client.test.d.ts.map +1 -0
  73. package/dist/__tests__/github-scan-client.test.js +100 -0
  74. package/dist/__tests__/github-scan-client.test.js.map +1 -0
  75. package/dist/__tests__/is-fresh-review-comment.test.d.ts +2 -0
  76. package/dist/__tests__/is-fresh-review-comment.test.d.ts.map +1 -0
  77. package/dist/__tests__/is-fresh-review-comment.test.js +86 -0
  78. package/dist/__tests__/is-fresh-review-comment.test.js.map +1 -0
  79. package/dist/__tests__/issue.test.d.ts +2 -0
  80. package/dist/__tests__/issue.test.d.ts.map +1 -0
  81. package/dist/__tests__/issue.test.js +259 -0
  82. package/dist/__tests__/issue.test.js.map +1 -0
  83. package/dist/__tests__/kickass.test.d.ts +2 -0
  84. package/dist/__tests__/kickass.test.d.ts.map +1 -0
  85. package/dist/__tests__/kickass.test.js +268 -0
  86. package/dist/__tests__/kickass.test.js.map +1 -0
  87. package/dist/__tests__/loader.test.d.ts +2 -0
  88. package/dist/__tests__/loader.test.d.ts.map +1 -0
  89. package/dist/__tests__/loader.test.js +180 -0
  90. package/dist/__tests__/loader.test.js.map +1 -0
  91. package/dist/__tests__/onboard-preservation.test.d.ts +2 -0
  92. package/dist/__tests__/onboard-preservation.test.d.ts.map +1 -0
  93. package/dist/__tests__/onboard-preservation.test.js +506 -0
  94. package/dist/__tests__/onboard-preservation.test.js.map +1 -0
  95. package/dist/__tests__/optimize.test.d.ts +2 -0
  96. package/dist/__tests__/optimize.test.d.ts.map +1 -0
  97. package/dist/__tests__/optimize.test.js +101 -0
  98. package/dist/__tests__/optimize.test.js.map +1 -0
  99. package/dist/__tests__/post-review-comment.test.d.ts +2 -0
  100. package/dist/__tests__/post-review-comment.test.d.ts.map +1 -0
  101. package/dist/__tests__/post-review-comment.test.js +44 -0
  102. package/dist/__tests__/post-review-comment.test.js.map +1 -0
  103. package/dist/__tests__/pr-lock.test.d.ts +2 -0
  104. package/dist/__tests__/pr-lock.test.d.ts.map +1 -0
  105. package/dist/__tests__/pr-lock.test.js +115 -0
  106. package/dist/__tests__/pr-lock.test.js.map +1 -0
  107. package/dist/__tests__/pr-picker.test.d.ts +2 -0
  108. package/dist/__tests__/pr-picker.test.d.ts.map +1 -0
  109. package/dist/__tests__/pr-picker.test.js +57 -0
  110. package/dist/__tests__/pr-picker.test.js.map +1 -0
  111. package/dist/__tests__/pr-status-scan.test.d.ts +2 -0
  112. package/dist/__tests__/pr-status-scan.test.d.ts.map +1 -0
  113. package/dist/__tests__/pr-status-scan.test.js +92 -0
  114. package/dist/__tests__/pr-status-scan.test.js.map +1 -0
  115. package/dist/__tests__/pr-status.test.d.ts +2 -0
  116. package/dist/__tests__/pr-status.test.d.ts.map +1 -0
  117. package/dist/__tests__/pr-status.test.js +346 -0
  118. package/dist/__tests__/pr-status.test.js.map +1 -0
  119. package/dist/__tests__/repo-picker.test.d.ts +2 -0
  120. package/dist/__tests__/repo-picker.test.d.ts.map +1 -0
  121. package/dist/__tests__/repo-picker.test.js +115 -0
  122. package/dist/__tests__/repo-picker.test.js.map +1 -0
  123. package/dist/__tests__/review-comment-body.test.d.ts +2 -0
  124. package/dist/__tests__/review-comment-body.test.d.ts.map +1 -0
  125. package/dist/__tests__/review-comment-body.test.js +54 -0
  126. package/dist/__tests__/review-comment-body.test.js.map +1 -0
  127. package/dist/__tests__/review-models.test.d.ts +2 -0
  128. package/dist/__tests__/review-models.test.d.ts.map +1 -0
  129. package/dist/__tests__/review-models.test.js +39 -0
  130. package/dist/__tests__/review-models.test.js.map +1 -0
  131. package/dist/__tests__/review-status.test.d.ts +2 -0
  132. package/dist/__tests__/review-status.test.d.ts.map +1 -0
  133. package/dist/__tests__/review-status.test.js +95 -0
  134. package/dist/__tests__/review-status.test.js.map +1 -0
  135. package/dist/__tests__/runner.test.d.ts +2 -0
  136. package/dist/__tests__/runner.test.d.ts.map +1 -0
  137. package/dist/__tests__/runner.test.js +204 -0
  138. package/dist/__tests__/runner.test.js.map +1 -0
  139. package/dist/__tests__/scan-cache.test.d.ts +2 -0
  140. package/dist/__tests__/scan-cache.test.d.ts.map +1 -0
  141. package/dist/__tests__/scan-cache.test.js +59 -0
  142. package/dist/__tests__/scan-cache.test.js.map +1 -0
  143. package/dist/__tests__/scan-client.test.d.ts +2 -0
  144. package/dist/__tests__/scan-client.test.d.ts.map +1 -0
  145. package/dist/__tests__/scan-client.test.js +30 -0
  146. package/dist/__tests__/scan-client.test.js.map +1 -0
  147. package/dist/__tests__/scan.test.d.ts +2 -0
  148. package/dist/__tests__/scan.test.d.ts.map +1 -0
  149. package/dist/__tests__/scan.test.js +115 -0
  150. package/dist/__tests__/scan.test.js.map +1 -0
  151. package/dist/__tests__/scopes.test.d.ts +2 -0
  152. package/dist/__tests__/scopes.test.d.ts.map +1 -0
  153. package/dist/__tests__/scopes.test.js +101 -0
  154. package/dist/__tests__/scopes.test.js.map +1 -0
  155. package/dist/__tests__/sha-cache.test.d.ts +2 -0
  156. package/dist/__tests__/sha-cache.test.d.ts.map +1 -0
  157. package/dist/__tests__/sha-cache.test.js +40 -0
  158. package/dist/__tests__/sha-cache.test.js.map +1 -0
  159. package/dist/__tests__/smart-switch.test.d.ts +2 -0
  160. package/dist/__tests__/smart-switch.test.d.ts.map +1 -0
  161. package/dist/__tests__/smart-switch.test.js +145 -0
  162. package/dist/__tests__/smart-switch.test.js.map +1 -0
  163. package/dist/ck.d.ts +3 -0
  164. package/dist/ck.d.ts.map +1 -0
  165. package/dist/ck.js +8 -0
  166. package/dist/ck.js.map +1 -0
  167. package/dist/cli.d.ts +3 -0
  168. package/dist/cli.d.ts.map +1 -0
  169. package/dist/cli.js +132 -0
  170. package/dist/cli.js.map +1 -0
  171. package/dist/commands/diagnose.d.ts +54 -0
  172. package/dist/commands/diagnose.d.ts.map +1 -0
  173. package/dist/commands/diagnose.js +294 -0
  174. package/dist/commands/diagnose.js.map +1 -0
  175. package/dist/commands/impact.d.ts +38 -0
  176. package/dist/commands/impact.d.ts.map +1 -0
  177. package/dist/commands/impact.js +210 -0
  178. package/dist/commands/impact.js.map +1 -0
  179. package/dist/commands/init.d.ts +12 -0
  180. package/dist/commands/init.d.ts.map +1 -0
  181. package/dist/commands/init.js +183 -0
  182. package/dist/commands/init.js.map +1 -0
  183. package/dist/commands/issue.d.ts +25 -0
  184. package/dist/commands/issue.d.ts.map +1 -0
  185. package/dist/commands/issue.js +445 -0
  186. package/dist/commands/issue.js.map +1 -0
  187. package/dist/commands/kickass.d.ts +59 -0
  188. package/dist/commands/kickass.d.ts.map +1 -0
  189. package/dist/commands/kickass.js +288 -0
  190. package/dist/commands/kickass.js.map +1 -0
  191. package/dist/commands/onboard.d.ts +70 -0
  192. package/dist/commands/onboard.d.ts.map +1 -0
  193. package/dist/commands/onboard.js +883 -0
  194. package/dist/commands/onboard.js.map +1 -0
  195. package/dist/commands/optimize.d.ts +16 -0
  196. package/dist/commands/optimize.d.ts.map +1 -0
  197. package/dist/commands/optimize.js +244 -0
  198. package/dist/commands/optimize.js.map +1 -0
  199. package/dist/commands/review.d.ts +2 -0
  200. package/dist/commands/review.d.ts.map +1 -0
  201. package/dist/commands/review.js +118 -0
  202. package/dist/commands/review.js.map +1 -0
  203. package/dist/commands/run.d.ts +13 -0
  204. package/dist/commands/run.d.ts.map +1 -0
  205. package/dist/commands/run.js +243 -0
  206. package/dist/commands/run.js.map +1 -0
  207. package/dist/commands/scan.d.ts +94 -0
  208. package/dist/commands/scan.d.ts.map +1 -0
  209. package/dist/commands/scan.js +276 -0
  210. package/dist/commands/scan.js.map +1 -0
  211. package/dist/commands/serve.d.ts +9 -0
  212. package/dist/commands/serve.d.ts.map +1 -0
  213. package/dist/commands/serve.js +402 -0
  214. package/dist/commands/serve.js.map +1 -0
  215. package/dist/commands/status.d.ts +2 -0
  216. package/dist/commands/status.d.ts.map +1 -0
  217. package/dist/commands/status.js +89 -0
  218. package/dist/commands/status.js.map +1 -0
  219. package/dist/commands/watch.d.ts +9 -0
  220. package/dist/commands/watch.d.ts.map +1 -0
  221. package/dist/commands/watch.js +902 -0
  222. package/dist/commands/watch.js.map +1 -0
  223. package/dist/config/loader.d.ts +47 -0
  224. package/dist/config/loader.d.ts.map +1 -0
  225. package/dist/config/loader.js +334 -0
  226. package/dist/config/loader.js.map +1 -0
  227. package/dist/config/schema.d.ts +814 -0
  228. package/dist/config/schema.d.ts.map +1 -0
  229. package/dist/config/schema.js +152 -0
  230. package/dist/config/schema.js.map +1 -0
  231. package/dist/github/client.d.ts +139 -0
  232. package/dist/github/client.d.ts.map +1 -0
  233. package/dist/github/client.js +711 -0
  234. package/dist/github/client.js.map +1 -0
  235. package/dist/github/detector.d.ts +12 -0
  236. package/dist/github/detector.d.ts.map +1 -0
  237. package/dist/github/detector.js +120 -0
  238. package/dist/github/detector.js.map +1 -0
  239. package/dist/github/merge.d.ts +9 -0
  240. package/dist/github/merge.d.ts.map +1 -0
  241. package/dist/github/merge.js +33 -0
  242. package/dist/github/merge.js.map +1 -0
  243. package/dist/github/review-status.d.ts +6 -0
  244. package/dist/github/review-status.d.ts.map +1 -0
  245. package/dist/github/review-status.js +51 -0
  246. package/dist/github/review-status.js.map +1 -0
  247. package/dist/github/webhook.d.ts +41 -0
  248. package/dist/github/webhook.d.ts.map +1 -0
  249. package/dist/github/webhook.js +50 -0
  250. package/dist/github/webhook.js.map +1 -0
  251. package/dist/lib/annotation.d.ts +23 -0
  252. package/dist/lib/annotation.d.ts.map +1 -0
  253. package/dist/lib/annotation.js +103 -0
  254. package/dist/lib/annotation.js.map +1 -0
  255. package/dist/lib/backtrace.d.ts +40 -0
  256. package/dist/lib/backtrace.d.ts.map +1 -0
  257. package/dist/lib/backtrace.js +169 -0
  258. package/dist/lib/backtrace.js.map +1 -0
  259. package/dist/lib/board.d.ts +74 -0
  260. package/dist/lib/board.d.ts.map +1 -0
  261. package/dist/lib/board.js +640 -0
  262. package/dist/lib/board.js.map +1 -0
  263. package/dist/lib/clone.d.ts +12 -0
  264. package/dist/lib/clone.d.ts.map +1 -0
  265. package/dist/lib/clone.js +30 -0
  266. package/dist/lib/clone.js.map +1 -0
  267. package/dist/lib/comment-bodies.d.ts +17 -0
  268. package/dist/lib/comment-bodies.d.ts.map +1 -0
  269. package/dist/lib/comment-bodies.js +51 -0
  270. package/dist/lib/comment-bodies.js.map +1 -0
  271. package/dist/lib/crosscheck-commit.d.ts +2 -0
  272. package/dist/lib/crosscheck-commit.d.ts.map +1 -0
  273. package/dist/lib/crosscheck-commit.js +4 -0
  274. package/dist/lib/crosscheck-commit.js.map +1 -0
  275. package/dist/lib/diff-hash.d.ts +16 -0
  276. package/dist/lib/diff-hash.d.ts.map +1 -0
  277. package/dist/lib/diff-hash.js +71 -0
  278. package/dist/lib/diff-hash.js.map +1 -0
  279. package/dist/lib/durations.d.ts +5 -0
  280. package/dist/lib/durations.d.ts.map +1 -0
  281. package/dist/lib/durations.js +39 -0
  282. package/dist/lib/durations.js.map +1 -0
  283. package/dist/lib/event-fields.d.ts +6 -0
  284. package/dist/lib/event-fields.d.ts.map +1 -0
  285. package/dist/lib/event-fields.js +20 -0
  286. package/dist/lib/event-fields.js.map +1 -0
  287. package/dist/lib/filter.d.ts +2 -0
  288. package/dist/lib/filter.d.ts.map +1 -0
  289. package/dist/lib/filter.js +4 -0
  290. package/dist/lib/filter.js.map +1 -0
  291. package/dist/lib/fortune.d.ts +2 -0
  292. package/dist/lib/fortune.d.ts.map +1 -0
  293. package/dist/lib/fortune.js +26 -0
  294. package/dist/lib/fortune.js.map +1 -0
  295. package/dist/lib/languages.d.ts +3 -0
  296. package/dist/lib/languages.d.ts.map +1 -0
  297. package/dist/lib/languages.js +26 -0
  298. package/dist/lib/languages.js.map +1 -0
  299. package/dist/lib/log-analysis.d.ts +17 -0
  300. package/dist/lib/log-analysis.d.ts.map +1 -0
  301. package/dist/lib/log-analysis.js +72 -0
  302. package/dist/lib/log-analysis.js.map +1 -0
  303. package/dist/lib/logger.d.ts +14 -0
  304. package/dist/lib/logger.d.ts.map +1 -0
  305. package/dist/lib/logger.js +84 -0
  306. package/dist/lib/logger.js.map +1 -0
  307. package/dist/lib/port.d.ts +2 -0
  308. package/dist/lib/port.d.ts.map +1 -0
  309. package/dist/lib/port.js +21 -0
  310. package/dist/lib/port.js.map +1 -0
  311. package/dist/lib/pr-lock.d.ts +4 -0
  312. package/dist/lib/pr-lock.d.ts.map +1 -0
  313. package/dist/lib/pr-lock.js +91 -0
  314. package/dist/lib/pr-lock.js.map +1 -0
  315. package/dist/lib/pr-picker.d.ts +10 -0
  316. package/dist/lib/pr-picker.d.ts.map +1 -0
  317. package/dist/lib/pr-picker.js +80 -0
  318. package/dist/lib/pr-picker.js.map +1 -0
  319. package/dist/lib/pr-status.d.ts +206 -0
  320. package/dist/lib/pr-status.d.ts.map +1 -0
  321. package/dist/lib/pr-status.js +613 -0
  322. package/dist/lib/pr-status.js.map +1 -0
  323. package/dist/lib/repo-picker.d.ts +23 -0
  324. package/dist/lib/repo-picker.d.ts.map +1 -0
  325. package/dist/lib/repo-picker.js +411 -0
  326. package/dist/lib/repo-picker.js.map +1 -0
  327. package/dist/lib/review-models.d.ts +7 -0
  328. package/dist/lib/review-models.d.ts.map +1 -0
  329. package/dist/lib/review-models.js +32 -0
  330. package/dist/lib/review-models.js.map +1 -0
  331. package/dist/lib/runner.d.ts +65 -0
  332. package/dist/lib/runner.d.ts.map +1 -0
  333. package/dist/lib/runner.js +710 -0
  334. package/dist/lib/runner.js.map +1 -0
  335. package/dist/lib/scan-cache.d.ts +31 -0
  336. package/dist/lib/scan-cache.d.ts.map +1 -0
  337. package/dist/lib/scan-cache.js +112 -0
  338. package/dist/lib/scan-cache.js.map +1 -0
  339. package/dist/lib/scopes.d.ts +16 -0
  340. package/dist/lib/scopes.d.ts.map +1 -0
  341. package/dist/lib/scopes.js +37 -0
  342. package/dist/lib/scopes.js.map +1 -0
  343. package/dist/lib/sha-cache.d.ts +7 -0
  344. package/dist/lib/sha-cache.d.ts.map +1 -0
  345. package/dist/lib/sha-cache.js +44 -0
  346. package/dist/lib/sha-cache.js.map +1 -0
  347. package/dist/lib/smart-switch.d.ts +44 -0
  348. package/dist/lib/smart-switch.d.ts.map +1 -0
  349. package/dist/lib/smart-switch.js +145 -0
  350. package/dist/lib/smart-switch.js.map +1 -0
  351. package/dist/lib/verdict.d.ts +9 -0
  352. package/dist/lib/verdict.d.ts.map +1 -0
  353. package/dist/lib/verdict.js +52 -0
  354. package/dist/lib/verdict.js.map +1 -0
  355. package/dist/lib/workflow.d.ts +85 -0
  356. package/dist/lib/workflow.d.ts.map +1 -0
  357. package/dist/lib/workflow.js +116 -0
  358. package/dist/lib/workflow.js.map +1 -0
  359. package/dist/reviewers/address.d.ts +5 -0
  360. package/dist/reviewers/address.d.ts.map +1 -0
  361. package/dist/reviewers/address.js +87 -0
  362. package/dist/reviewers/address.js.map +1 -0
  363. package/dist/reviewers/claude.d.ts +12 -0
  364. package/dist/reviewers/claude.d.ts.map +1 -0
  365. package/dist/reviewers/claude.js +78 -0
  366. package/dist/reviewers/claude.js.map +1 -0
  367. package/dist/reviewers/codex.d.ts +9 -0
  368. package/dist/reviewers/codex.d.ts.map +1 -0
  369. package/dist/reviewers/codex.js +121 -0
  370. package/dist/reviewers/codex.js.map +1 -0
  371. package/dist/reviewers/conflict-resolve.d.ts +15 -0
  372. package/dist/reviewers/conflict-resolve.d.ts.map +1 -0
  373. package/dist/reviewers/conflict-resolve.js +219 -0
  374. package/dist/reviewers/conflict-resolve.js.map +1 -0
  375. package/dist/reviewers/fix.d.ts +7 -0
  376. package/dist/reviewers/fix.d.ts.map +1 -0
  377. package/dist/reviewers/fix.js +197 -0
  378. package/dist/reviewers/fix.js.map +1 -0
  379. package/get-started.md +1271 -0
  380. package/get-started.zh.md +1208 -0
  381. package/package.json +75 -0
@@ -0,0 +1,711 @@
1
+ import { Octokit } from 'octokit';
2
+ import { createHmac, timingSafeEqual } from 'crypto';
3
+ import { buildAnnotation, parseAnnotation, parseAnnotationFields } from '../lib/annotation.js';
4
+ import { modelDisplayName } from '../lib/review-models.js';
5
+ export function createGithubClient(token) {
6
+ return new Octokit({ auth: token });
7
+ }
8
+ async function readGithubErrorMessage(res) {
9
+ try {
10
+ const body = await res.json();
11
+ if (typeof body.message === 'string' && body.message.trim().length > 0)
12
+ return body.message;
13
+ }
14
+ catch {
15
+ // Fall back to status text when GitHub returns a non-JSON error body.
16
+ }
17
+ return res.statusText || `HTTP ${res.status}`;
18
+ }
19
+ async function throwGithubRequestError(res, context) {
20
+ const message = await readGithubErrorMessage(res);
21
+ throw new Error(`GitHub API request failed (${context}) [${res.status}]: ${message}`);
22
+ }
23
+ function repoPath(owner, repo) {
24
+ return `${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;
25
+ }
26
+ export function verifyWebhookSignature(payload, signature, secret) {
27
+ const expected = `sha256=${createHmac('sha256', secret).update(payload).digest('hex')}`;
28
+ try {
29
+ return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ export async function getPRDiff(octokit, owner, repo, pullNumber) {
36
+ const { data } = await octokit.rest.pulls.get({
37
+ owner,
38
+ repo,
39
+ pull_number: pullNumber,
40
+ mediaType: { format: 'diff' },
41
+ });
42
+ return data;
43
+ }
44
+ export async function registerRepoWebhook(owner, repo, webhookUrl, secret, token) {
45
+ const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/hooks`, {
46
+ method: 'POST',
47
+ headers: {
48
+ Authorization: `Bearer ${token}`,
49
+ Accept: 'application/vnd.github+json',
50
+ 'Content-Type': 'application/json',
51
+ },
52
+ body: JSON.stringify({
53
+ name: 'web',
54
+ active: true,
55
+ events: ['pull_request'],
56
+ config: { url: webhookUrl, content_type: 'json', secret },
57
+ }),
58
+ });
59
+ if (!res.ok) {
60
+ const err = await res.json();
61
+ throw new Error(`Failed to register repo webhook [${res.status}]: ${err.message ?? res.statusText}`);
62
+ }
63
+ const data = await res.json();
64
+ return data.id;
65
+ }
66
+ export async function deleteRepoWebhook(owner, repo, hookId, token) {
67
+ await fetch(`https://api.github.com/repos/${owner}/${repo}/hooks/${hookId}`, {
68
+ method: 'DELETE',
69
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' },
70
+ });
71
+ }
72
+ export async function registerOrgWebhook(org, webhookUrl, secret, token) {
73
+ const res = await fetch(`https://api.github.com/orgs/${org}/hooks`, {
74
+ method: 'POST',
75
+ headers: {
76
+ Authorization: `Bearer ${token}`,
77
+ Accept: 'application/vnd.github+json',
78
+ 'Content-Type': 'application/json',
79
+ },
80
+ body: JSON.stringify({
81
+ name: 'web',
82
+ active: true,
83
+ events: ['pull_request'],
84
+ config: { url: webhookUrl, content_type: 'json', secret },
85
+ }),
86
+ });
87
+ if (!res.ok) {
88
+ const err = await res.json();
89
+ throw new Error(`Failed to register org webhook [${res.status}]: ${err.message ?? res.statusText}`);
90
+ }
91
+ const data = await res.json();
92
+ return data.id;
93
+ }
94
+ export async function deleteOrgWebhook(org, hookId, token) {
95
+ await fetch(`https://api.github.com/orgs/${org}/hooks/${hookId}`, {
96
+ method: 'DELETE',
97
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' },
98
+ });
99
+ }
100
+ export async function listUserOrgs(token) {
101
+ const results = [];
102
+ let page = 1;
103
+ while (true) {
104
+ const res = await fetch(`https://api.github.com/user/memberships/orgs?state=active&per_page=100&page=${page}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
105
+ if (!res.ok)
106
+ break;
107
+ const data = await res.json();
108
+ if (data.length === 0)
109
+ break;
110
+ for (const m of data)
111
+ results.push(m.organization.login);
112
+ if (data.length < 100)
113
+ break;
114
+ page++;
115
+ }
116
+ return results;
117
+ }
118
+ export async function checkRepoAccessible(owner, repo, token) {
119
+ const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
120
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' },
121
+ });
122
+ return res.ok;
123
+ }
124
+ export async function listUserRepos(username, token, isSelf = false) {
125
+ const results = [];
126
+ let page = 1;
127
+ while (true) {
128
+ const url = isSelf
129
+ ? `https://api.github.com/user/repos?affiliation=owner&visibility=all&per_page=100&page=${page}`
130
+ : `https://api.github.com/users/${encodeURIComponent(username)}/repos?per_page=100&page=${page}&type=owner`;
131
+ const res = await fetch(url, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
132
+ if (res.status === 404)
133
+ return results;
134
+ if (!res.ok)
135
+ await throwGithubRequestError(res, `list user repos for ${username}`);
136
+ const data = await res.json();
137
+ if (data.length === 0)
138
+ break;
139
+ for (const repo of data) {
140
+ if (!repo.archived && repo.owner.login === username)
141
+ results.push({ owner: repo.owner.login, name: repo.name });
142
+ }
143
+ if (data.length < 100)
144
+ break;
145
+ page++;
146
+ }
147
+ return results;
148
+ }
149
+ export async function listOrgRepos(org, token) {
150
+ const results = [];
151
+ let page = 1;
152
+ while (true) {
153
+ const res = await fetch(`https://api.github.com/orgs/${encodeURIComponent(org)}/repos?per_page=100&page=${page}&sort=pushed&type=all`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
154
+ if (res.status === 404)
155
+ return results;
156
+ if (!res.ok)
157
+ throw new Error(`Failed to list org repos [${res.status}]: ${res.statusText} (GitHub API request failed)`);
158
+ const data = await res.json();
159
+ if (data.length === 0)
160
+ break;
161
+ for (const repo of data) {
162
+ if (!repo.archived) {
163
+ results.push({ owner: org, name: repo.name, pushedAt: repo.pushed_at ? new Date(repo.pushed_at) : null });
164
+ }
165
+ }
166
+ if (data.length < 100)
167
+ break;
168
+ page++;
169
+ }
170
+ return results;
171
+ }
172
+ export async function listOpenPRs(owner, repo, token) {
173
+ const results = [];
174
+ let page = 1;
175
+ while (true) {
176
+ const res = await fetch(`https://api.github.com/repos/${repoPath(owner, repo)}/pulls?state=open&per_page=100&page=${page}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
177
+ if (res.status === 404)
178
+ return results;
179
+ if (!res.ok) {
180
+ const message = await readGithubErrorMessage(res);
181
+ throw new Error(`Failed to list open PRs for ${owner}/${repo} page ${page} [${res.status}]: ${message}`);
182
+ }
183
+ const data = await res.json();
184
+ if (data.length === 0)
185
+ break;
186
+ for (const pr of data) {
187
+ results.push({
188
+ number: pr.number,
189
+ title: pr.title,
190
+ author: pr.user?.login ?? 'ghost',
191
+ headSha: pr.head.sha,
192
+ headRef: pr.head.ref,
193
+ headRepo: pr.head.repo?.full_name ?? null,
194
+ baseRef: pr.base.ref,
195
+ body: pr.body,
196
+ createdAt: pr.created_at,
197
+ updatedAt: pr.updated_at,
198
+ url: pr.html_url,
199
+ });
200
+ }
201
+ if (data.length < 100)
202
+ break;
203
+ page++;
204
+ }
205
+ return results;
206
+ }
207
+ async function githubJson(url, token, label) {
208
+ const res = await fetch(url, {
209
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' },
210
+ });
211
+ if (!res.ok) {
212
+ let message = res.statusText;
213
+ try {
214
+ const data = await res.json();
215
+ message = data.message ?? message;
216
+ }
217
+ catch { /* keep status text */ }
218
+ const context = res.status === 403 || res.status === 429
219
+ ? 'GitHub rate limit or secondary rate limit'
220
+ : 'GitHub API request';
221
+ throw new Error(`${label} failed [${res.status}]: ${context}: ${message}`);
222
+ }
223
+ return await res.json();
224
+ }
225
+ export async function listOrgReposForScan(org, token) {
226
+ const results = [];
227
+ let page = 1;
228
+ while (true) {
229
+ const data = await githubJson(`https://api.github.com/orgs/${encodeURIComponent(org)}/repos?per_page=100&page=${page}&sort=pushed&type=all`, token, `List repos for org ${org}`);
230
+ if (data.length === 0)
231
+ break;
232
+ for (const repo of data) {
233
+ if (!repo.archived)
234
+ results.push({ owner: org, name: repo.name });
235
+ }
236
+ if (data.length < 100)
237
+ break;
238
+ page++;
239
+ }
240
+ return results;
241
+ }
242
+ export async function listUserReposForScan(username, token, isSelf) {
243
+ const results = [];
244
+ let page = 1;
245
+ while (true) {
246
+ const url = isSelf
247
+ ? `https://api.github.com/user/repos?affiliation=owner&visibility=all&per_page=100&page=${page}`
248
+ : `https://api.github.com/users/${encodeURIComponent(username)}/repos?per_page=100&page=${page}&type=owner`;
249
+ const data = await githubJson(url, token, `List repos for user ${username}`);
250
+ if (data.length === 0)
251
+ break;
252
+ for (const repo of data) {
253
+ if (!repo.archived && repo.owner.login.toLowerCase() === username.toLowerCase()) {
254
+ results.push({ owner: repo.owner.login, name: repo.name });
255
+ }
256
+ }
257
+ if (data.length < 100)
258
+ break;
259
+ page++;
260
+ }
261
+ return results;
262
+ }
263
+ export async function listOpenPRsForScan(owner, repo, token) {
264
+ const results = [];
265
+ let page = 1;
266
+ while (true) {
267
+ const data = await githubJson(`https://api.github.com/repos/${repoPath(owner, repo)}/pulls?state=open&per_page=100&page=${page}`, token, `List open PRs for ${owner}/${repo}`);
268
+ if (data.length === 0)
269
+ break;
270
+ for (const pr of data) {
271
+ results.push({
272
+ number: pr.number,
273
+ title: pr.title,
274
+ author: pr.user?.login ?? 'unknown',
275
+ headSha: pr.head.sha,
276
+ headRef: pr.head.ref,
277
+ headRepo: pr.head.repo?.full_name ?? null,
278
+ baseRef: pr.base.ref,
279
+ createdAt: pr.created_at,
280
+ updatedAt: pr.updated_at,
281
+ url: pr.html_url,
282
+ });
283
+ }
284
+ if (data.length < 100)
285
+ break;
286
+ page++;
287
+ }
288
+ return results;
289
+ }
290
+ export async function listIssueCommentsForScan(owner, repo, issueNumber, token) {
291
+ const results = [];
292
+ let page = 1;
293
+ while (true) {
294
+ const data = await githubJson(`https://api.github.com/repos/${repoPath(owner, repo)}/issues/${issueNumber}/comments?per_page=100&page=${page}&sort=created&direction=asc`, token, `List comments for ${owner}/${repo}#${issueNumber}`);
295
+ if (data.length === 0)
296
+ break;
297
+ for (const comment of data) {
298
+ results.push({
299
+ id: comment.id,
300
+ author: comment.user?.login ?? 'unknown',
301
+ body: comment.body ?? '',
302
+ createdAt: comment.created_at,
303
+ updatedAt: comment.updated_at,
304
+ });
305
+ }
306
+ if (data.length < 100)
307
+ break;
308
+ page++;
309
+ }
310
+ return results;
311
+ }
312
+ export async function listPRComments(owner, repo, prNumber, token) {
313
+ const results = [];
314
+ let page = 1;
315
+ while (true) {
316
+ const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/issues/${prNumber}/comments?per_page=100&page=${page}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
317
+ if (!res.ok)
318
+ throw new Error(`Failed to list PR comments [${res.status}]: ${res.statusText}`);
319
+ const data = await res.json();
320
+ if (data.length === 0)
321
+ break;
322
+ for (const comment of data) {
323
+ results.push({
324
+ id: comment.id,
325
+ body: comment.body,
326
+ createdAt: comment.created_at,
327
+ updatedAt: comment.updated_at,
328
+ });
329
+ }
330
+ if (data.length < 100)
331
+ break;
332
+ page++;
333
+ }
334
+ return results;
335
+ }
336
+ export async function listPRCommitsDetailed(owner, repo, prNumber, token) {
337
+ const results = [];
338
+ let page = 1;
339
+ while (true) {
340
+ const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/commits?per_page=100&page=${page}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
341
+ if (!res.ok)
342
+ throw new Error(`Failed to list PR commits [${res.status}]: ${res.statusText}`);
343
+ const data = await res.json();
344
+ if (data.length === 0)
345
+ break;
346
+ for (const commit of data) {
347
+ const committedAt = commit.commit.committer.date ?? commit.commit.author.date;
348
+ if (committedAt)
349
+ results.push({ sha: commit.sha, committedAt });
350
+ }
351
+ if (data.length < 100)
352
+ break;
353
+ page++;
354
+ }
355
+ return results;
356
+ }
357
+ export async function listCommitStatuses(owner, repo, sha, token) {
358
+ const results = [];
359
+ let page = 1;
360
+ while (true) {
361
+ const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/commits/${sha}/statuses?per_page=100&page=${page}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
362
+ if (!res.ok) {
363
+ if (page === 1)
364
+ throw new Error(`Failed to list commit statuses [${res.status}]: ${res.statusText}`);
365
+ break;
366
+ }
367
+ const data = await res.json();
368
+ if (data.length === 0)
369
+ break;
370
+ for (const status of data) {
371
+ results.push({ context: status.context, state: status.state, updatedAt: status.updated_at });
372
+ }
373
+ if (data.length < 100)
374
+ break;
375
+ page++;
376
+ }
377
+ return results;
378
+ }
379
+ export async function listIssueComments(owner, repo, issueNumber, token) {
380
+ const results = [];
381
+ let page = 1;
382
+ while (true) {
383
+ const res = await fetch(`https://api.github.com/repos/${repoPath(owner, repo)}/issues/${issueNumber}/comments?per_page=100&page=${page}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
384
+ if (res.status === 404)
385
+ return results;
386
+ if (!res.ok)
387
+ await throwGithubRequestError(res, `list issue comments for ${owner}/${repo}#${issueNumber}`);
388
+ const data = await res.json();
389
+ if (data.length === 0)
390
+ break;
391
+ for (const comment of data) {
392
+ results.push({ body: comment.body, createdAt: comment.created_at, updatedAt: comment.updated_at });
393
+ }
394
+ if (data.length < 100)
395
+ break;
396
+ page++;
397
+ }
398
+ return results;
399
+ }
400
+ export async function listPRReviewComments(owner, repo, pullNumber, token) {
401
+ const results = [];
402
+ let page = 1;
403
+ while (true) {
404
+ const res = await fetch(`https://api.github.com/repos/${repoPath(owner, repo)}/pulls/${pullNumber}/comments?per_page=100&page=${page}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
405
+ if (res.status === 404)
406
+ return results;
407
+ if (!res.ok)
408
+ await throwGithubRequestError(res, `list PR review comments for ${owner}/${repo}#${pullNumber}`);
409
+ const data = await res.json();
410
+ if (data.length === 0)
411
+ break;
412
+ for (const comment of data) {
413
+ results.push({ body: comment.body, createdAt: comment.created_at, updatedAt: comment.updated_at });
414
+ }
415
+ if (data.length < 100)
416
+ break;
417
+ page++;
418
+ }
419
+ return results;
420
+ }
421
+ export async function listPRCommitActivity(owner, repo, pullNumber, token) {
422
+ const results = [];
423
+ let page = 1;
424
+ while (true) {
425
+ const res = await fetch(`https://api.github.com/repos/${repoPath(owner, repo)}/pulls/${pullNumber}/commits?per_page=100&page=${page}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
426
+ if (res.status === 404)
427
+ return results;
428
+ if (!res.ok)
429
+ await throwGithubRequestError(res, `list PR commits for ${owner}/${repo}#${pullNumber}`);
430
+ const data = await res.json();
431
+ if (data.length === 0)
432
+ break;
433
+ for (const commit of data) {
434
+ const committedAt = commit.commit.committer?.date ?? commit.commit.author?.date;
435
+ if (committedAt)
436
+ results.push({ sha: commit.sha, committedAt });
437
+ }
438
+ if (data.length < 100)
439
+ break;
440
+ page++;
441
+ }
442
+ return results;
443
+ }
444
+ export async function listCheckRuns(owner, repo, ref, token) {
445
+ const results = [];
446
+ let page = 1;
447
+ while (true) {
448
+ const res = await fetch(`https://api.github.com/repos/${repoPath(owner, repo)}/commits/${encodeURIComponent(ref)}/check-runs?per_page=100&page=${page}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
449
+ if (res.status === 404)
450
+ return results;
451
+ if (!res.ok)
452
+ await throwGithubRequestError(res, `list check runs for ${owner}/${repo}@${ref}`);
453
+ const data = await res.json();
454
+ if (data.check_runs.length === 0)
455
+ break;
456
+ for (const run of data.check_runs) {
457
+ const updatedAt = run.completed_at ?? run.started_at;
458
+ if (updatedAt)
459
+ results.push({ name: run.name, status: run.status, conclusion: run.conclusion, updatedAt });
460
+ }
461
+ if (data.check_runs.length < 100)
462
+ break;
463
+ page++;
464
+ }
465
+ return results;
466
+ }
467
+ export async function listTimelineEvents(owner, repo, issueNumber, token) {
468
+ const results = [];
469
+ let page = 1;
470
+ while (true) {
471
+ const res = await fetch(`https://api.github.com/repos/${repoPath(owner, repo)}/issues/${issueNumber}/timeline?per_page=100&page=${page}`, {
472
+ headers: {
473
+ Authorization: `Bearer ${token}`,
474
+ Accept: 'application/vnd.github+json',
475
+ },
476
+ });
477
+ if (res.status === 404)
478
+ return results;
479
+ if (!res.ok)
480
+ await throwGithubRequestError(res, `list timeline events for ${owner}/${repo}#${issueNumber}`);
481
+ const data = await res.json();
482
+ if (data.length === 0)
483
+ break;
484
+ for (const event of data) {
485
+ if (event.created_at)
486
+ results.push({ name: event.event, updatedAt: event.created_at });
487
+ }
488
+ if (data.length < 100)
489
+ break;
490
+ page++;
491
+ }
492
+ return results;
493
+ }
494
+ // Returns true if any comment on the PR contains '[crosscheck]' — meaning it
495
+ // has already been reviewed by this tool.
496
+ export async function prHasCrossCheckComment(owner, repo, prNumber, token) {
497
+ let page = 1;
498
+ while (true) {
499
+ const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/issues/${prNumber}/comments?per_page=100&page=${page}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
500
+ if (!res.ok)
501
+ return false;
502
+ const data = await res.json();
503
+ if (data.length === 0)
504
+ break;
505
+ for (const comment of data) {
506
+ if (comment.body.includes('[crosscheck]'))
507
+ return true;
508
+ }
509
+ if (data.length < 100)
510
+ break;
511
+ page++;
512
+ }
513
+ return false;
514
+ }
515
+ // True iff `body` is a fresh review comment that a recheck should link back to.
516
+ // Classification cascade:
517
+ // 1. Contract annotation parsed by annotation.ts with explicit type=review
518
+ // → review. Other explicit types are not reviews.
519
+ // 2. Legacy annotations without type= fall through to the header check so
520
+ // pre-type rechecks are still excluded by the "> Recheck of" prefix.
521
+ // 3. Bare summary/notification markers (`fix_failed`, `fix_applied`,
522
+ // `conflict_resolved`, `no_diff_change`, future bare markers) are not reviews.
523
+ // 4. No annotation → legacy pre-annotation comment. Identify reviews by the
524
+ // canonical "### Code Review by" header, exclude rechecks by the
525
+ // "> Recheck of" prefix.
526
+ export function isFreshReviewComment(body) {
527
+ const fields = parseAnnotationFields(body);
528
+ const parsed = parseAnnotation(body);
529
+ if (parsed && fields?.has('type')) {
530
+ // Explicit type= field present — trust it directly.
531
+ return parsed.type === 'review';
532
+ }
533
+ if (parsed && !fields?.has('type')) {
534
+ // Pre-type-era annotation: type was defaulted to 'review', not stated.
535
+ // Fall through to the header/prefix check so legacy rechecks are excluded.
536
+ }
537
+ else if (fields && !parsed) {
538
+ // Has an annotation tag but no origin+reviewer — bare summary marker.
539
+ return false;
540
+ }
541
+ // No annotation (or pre-type fallthrough): use header + recheck-prefix heuristic.
542
+ return body.includes('### Code Review by') && !body.startsWith('> Recheck of');
543
+ }
544
+ // Returns the GitHub comment ID of the most recent crosscheck REVIEW comment.
545
+ // Used by recheck steps to link back to the original review that raised issues.
546
+ // Classification is delegated to `isFreshReviewComment` — annotation is the
547
+ // source of truth, with a header-based fallback for pre-annotation comments.
548
+ export async function getLastCrossCheckCommentId(owner, repo, prNumber, token) {
549
+ return (await getLastCrossCheckReviewComment(owner, repo, prNumber, token))?.id;
550
+ }
551
+ export async function getLastCrossCheckReviewComment(owner, repo, prNumber, token) {
552
+ let page = 1;
553
+ let lastComment;
554
+ while (true) {
555
+ const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/issues/${prNumber}/comments?per_page=100&page=${page}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
556
+ if (!res.ok)
557
+ break;
558
+ const data = await res.json();
559
+ if (data.length === 0)
560
+ break;
561
+ for (const comment of data) {
562
+ if (isFreshReviewComment(comment.body)) {
563
+ lastComment = { id: comment.id, body: comment.body };
564
+ }
565
+ }
566
+ if (data.length < 100)
567
+ break;
568
+ page++;
569
+ }
570
+ return lastComment;
571
+ }
572
+ export async function getPRCommits(owner, repo, prNumber, token) {
573
+ const results = [];
574
+ let page = 1;
575
+ while (true) {
576
+ const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/commits?per_page=100&page=${page}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
577
+ if (!res.ok)
578
+ break;
579
+ const data = await res.json();
580
+ if (data.length === 0)
581
+ break;
582
+ for (const c of data)
583
+ results.push(c.commit.message);
584
+ if (data.length < 100)
585
+ break;
586
+ page++;
587
+ }
588
+ return results;
589
+ }
590
+ export async function getCommitMessage(owner, repo, sha, token) {
591
+ const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/commits/${sha}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
592
+ if (!res.ok)
593
+ return null;
594
+ const data = await res.json();
595
+ return data.commit?.message ?? null;
596
+ }
597
+ export async function findOrgWebhook(org, url, token) {
598
+ const res = await fetch(`https://api.github.com/orgs/${org}/hooks?per_page=100`, {
599
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' },
600
+ });
601
+ if (!res.ok)
602
+ return null;
603
+ const hooks = await res.json();
604
+ return hooks.find(h => h.config.url === url)?.id ?? null;
605
+ }
606
+ export async function findRepoWebhook(owner, repo, url, token) {
607
+ const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/hooks?per_page=100`, {
608
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' },
609
+ });
610
+ if (!res.ok)
611
+ return null;
612
+ const hooks = await res.json();
613
+ return hooks.find(h => h.config.url === url)?.id ?? null;
614
+ }
615
+ export async function fetchActiveRepos(login, token) {
616
+ const results = [];
617
+ const now = Date.now();
618
+ const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
619
+ const ninetyDaysMs = 90 * 24 * 60 * 60 * 1000;
620
+ let page = 1;
621
+ while (true) {
622
+ const res = await fetch(`https://api.github.com/user/repos?affiliation=owner&visibility=all&sort=pushed&per_page=100&page=${page}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' } });
623
+ if (!res.ok)
624
+ break;
625
+ const data = await res.json();
626
+ if (data.length === 0)
627
+ break;
628
+ for (const r of data) {
629
+ if (r.archived || r.owner.login !== login)
630
+ continue;
631
+ const createdAt = new Date(r.created_at);
632
+ const pushedAt = r.pushed_at ? new Date(r.pushed_at) : createdAt;
633
+ const createdAgo = now - createdAt.getTime();
634
+ const pushedAgo = now - pushedAt.getTime();
635
+ const tier = createdAgo < sevenDaysMs ? 1 : pushedAgo < ninetyDaysMs ? 2 : 3;
636
+ results.push({ tier, fullName: `${login}/${r.name}`, pushedAt, createdAt });
637
+ }
638
+ if (data.length < 100)
639
+ break;
640
+ page++;
641
+ }
642
+ // Tier 1 first (newest created), within each tier sort by pushedAt desc
643
+ results.sort((a, b) => a.tier !== b.tier ? a.tier - b.tier : b.pushedAt.getTime() - a.pushedAt.getTime());
644
+ return results;
645
+ }
646
+ export function buildReviewCommentBody(input) {
647
+ const reviewer = input.reviewer;
648
+ const brand = input.brand ?? {};
649
+ const model = input.model ?? 'default';
650
+ const round = input.round ?? 1;
651
+ const stepType = input.stepType ?? (input.isRecheck ? 'recheck' : 'review');
652
+ const serviceName = brand.service_name || 'crosscheck';
653
+ const isClaude = reviewer === 'claude';
654
+ const vendorLabel = isClaude ? '🤖 Claude Code' : '⚡ Codex';
655
+ const modelDisplay = modelDisplayName(model);
656
+ const serviceSegment = serviceName !== 'crosscheck' ? ` · ${serviceName}` : '';
657
+ const modelSegment = modelDisplay ? ` · ${modelDisplay}` : '';
658
+ const header = `### ${stepVerb(stepType)} by ${vendorLabel}${modelSegment}${serviceSegment}\n\n`;
659
+ const defaultAttribution = isClaude
660
+ ? '_Reviewed with [Claude Code](https://claude.ai/code)_'
661
+ : '_Reviewed with [OpenAI Codex](https://openai.com/codex)_';
662
+ const attribution = brand.reviewer_attribution || defaultAttribution;
663
+ const footer = `\n\n---\n${attribution}`;
664
+ const customHeader = brand.comment_header ? `${brand.comment_header}\n\n` : '';
665
+ const customFooter = brand.comment_footer ? `\n\n${brand.comment_footer}` : '';
666
+ const annotationTag = `\n\n${buildAnnotation({
667
+ origin: input.origin ?? 'human',
668
+ reviewer,
669
+ model,
670
+ type: stepType,
671
+ round,
672
+ verdict: input.verdict ?? 'UNKNOWN',
673
+ service: serviceName,
674
+ ...(input.sha && { sha: input.sha }),
675
+ })}`;
676
+ const replyPrefix = input.replyToCommentId
677
+ ? `> Recheck of [original review](#issuecomment-${input.replyToCommentId})\n\n`
678
+ : '';
679
+ return customHeader + replyPrefix + header + input.body + footer + customFooter + annotationTag;
680
+ }
681
+ export async function postReviewComment(octokit, owner, repo, pullNumber, body, reviewer, brand = {}, origin = 'human', verdict, replyToCommentId, isRecheck, model = 'default', stepType, round = 1, sha) {
682
+ const { data: comment } = await octokit.rest.issues.createComment({
683
+ owner,
684
+ repo,
685
+ issue_number: pullNumber,
686
+ body: buildReviewCommentBody({
687
+ body,
688
+ reviewer,
689
+ brand,
690
+ origin,
691
+ verdict,
692
+ replyToCommentId,
693
+ isRecheck,
694
+ model,
695
+ stepType,
696
+ round,
697
+ sha,
698
+ }),
699
+ });
700
+ return comment.id;
701
+ }
702
+ function stepVerb(stepType) {
703
+ if (stepType === 'recheck')
704
+ return 'Recheck';
705
+ if (stepType === 'fix')
706
+ return 'Fixes';
707
+ if (stepType === 'conflict-resolve')
708
+ return 'Conflict resolution';
709
+ return 'Code Review';
710
+ }
711
+ //# sourceMappingURL=client.js.map