limn-review 0.1.123

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 (474) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +133 -0
  3. package/dist/ai/runtime.js +535 -0
  4. package/dist/api/plugins/auth.js +169 -0
  5. package/dist/api/plugins/error-handler.js +114 -0
  6. package/dist/api/plugins/rate-limit.js +855 -0
  7. package/dist/api/plugins/security.js +321 -0
  8. package/dist/api/routes/activity.js +103 -0
  9. package/dist/api/routes/ai-runtime.js +67 -0
  10. package/dist/api/routes/auth.js +194 -0
  11. package/dist/api/routes/billing.js +209 -0
  12. package/dist/api/routes/bot-triage.js +1289 -0
  13. package/dist/api/routes/conflicts.js +730 -0
  14. package/dist/api/routes/contact.js +318 -0
  15. package/dist/api/routes/daily-brief.js +58 -0
  16. package/dist/api/routes/flow.js +71 -0
  17. package/dist/api/routes/health.js +7 -0
  18. package/dist/api/routes/insights.js +200 -0
  19. package/dist/api/routes/me.js +458 -0
  20. package/dist/api/routes/mentions.js +22 -0
  21. package/dist/api/routes/mergers.js +8 -0
  22. package/dist/api/routes/ml-labels.js +109 -0
  23. package/dist/api/routes/open-prs.js +35 -0
  24. package/dist/api/routes/prs.js +1633 -0
  25. package/dist/api/routes/reactions.js +217 -0
  26. package/dist/api/routes/repos.js +563 -0
  27. package/dist/api/routes/search.js +41 -0
  28. package/dist/api/routes/threads.js +118 -0
  29. package/dist/api/routes/timeline.js +150 -0
  30. package/dist/api/routes/users.js +67 -0
  31. package/dist/api/routes/webhooks.js +181 -0
  32. package/dist/api/routes/workspaces.js +322 -0
  33. package/dist/app.js +274 -0
  34. package/dist/ascii.js +19 -0
  35. package/dist/auth/account.js +482 -0
  36. package/dist/auth/crypto.js +46 -0
  37. package/dist/bot-triage/resolve.js +59 -0
  38. package/dist/cli.js +388 -0
  39. package/dist/coding/activity.js +80 -0
  40. package/dist/coding/agent.js +288 -0
  41. package/dist/coding/ai-fix/auto-fix.js +212 -0
  42. package/dist/coding/ai-fix/manager.js +370 -0
  43. package/dist/coding/ai-fix/persist.js +213 -0
  44. package/dist/coding/ai-fix/pr-context.js +58 -0
  45. package/dist/coding/ai-fix/prompts.js +138 -0
  46. package/dist/coding/ai-fix/review-seed.js +357 -0
  47. package/dist/coding/ai-fix/routes.js +425 -0
  48. package/dist/coding/git-ops.js +321 -0
  49. package/dist/coding/git.js +184 -0
  50. package/dist/coding/merge.js +262 -0
  51. package/dist/coding/safe-path.js +75 -0
  52. package/dist/coding/schema.js +43 -0
  53. package/dist/config.js +648 -0
  54. package/dist/conflict/diff.js +304 -0
  55. package/dist/conflict/git.js +118 -0
  56. package/dist/conflict/hash.js +73 -0
  57. package/dist/conflict/janitor.js +218 -0
  58. package/dist/conflict/land.js +675 -0
  59. package/dist/conflict/model-types.js +2 -0
  60. package/dist/conflict/model.js +1081 -0
  61. package/dist/conflict/parse.js +142 -0
  62. package/dist/conflict/seam.js +136 -0
  63. package/dist/conflict/session.js +532 -0
  64. package/dist/conflict/suggestion.js +398 -0
  65. package/dist/conflict/tokens.js +72 -0
  66. package/dist/data-dir.js +112 -0
  67. package/dist/db/actor-lanes.js +213 -0
  68. package/dist/db/automation-ids.js +97 -0
  69. package/dist/db/automation-output.js +349 -0
  70. package/dist/db/blast-radius-query.js +36 -0
  71. package/dist/db/blast-radius.js +226 -0
  72. package/dist/db/bot-dormancy.js +115 -0
  73. package/dist/db/bot-overlap.js +579 -0
  74. package/dist/db/bot-volume.js +722 -0
  75. package/dist/db/bot-window.js +35 -0
  76. package/dist/db/branch-queries.js +310 -0
  77. package/dist/db/change-shape.js +243 -0
  78. package/dist/db/changepoint.js +75 -0
  79. package/dist/db/cleanup.js +36 -0
  80. package/dist/db/client.js +102 -0
  81. package/dist/db/code-loc.js +388 -0
  82. package/dist/db/credits.js +79 -0
  83. package/dist/db/daily-brief.js +406 -0
  84. package/dist/db/dependency-cards.js +145 -0
  85. package/dist/db/erase-account.js +255 -0
  86. package/dist/db/export-account.js +189 -0
  87. package/dist/db/failing-checks.js +124 -0
  88. package/dist/db/file-coupling.js +197 -0
  89. package/dist/db/flow-detail.js +661 -0
  90. package/dist/db/flow-pointers.js +197 -0
  91. package/dist/db/flow-settings.js +67 -0
  92. package/dist/db/forecast.js +114 -0
  93. package/dist/db/line-overlap.js +81 -0
  94. package/dist/db/merge-queue.js +189 -0
  95. package/dist/db/migrate.js +17 -0
  96. package/dist/db/migrations/0000_purple_ben_grimm.sql +155 -0
  97. package/dist/db/migrations/0001_colorful_ozymandias.sql +31 -0
  98. package/dist/db/migrations/0002_famous_sersi.sql +9 -0
  99. package/dist/db/migrations/0003_clever_shinobi_shaw.sql +3 -0
  100. package/dist/db/migrations/0004_pale_scalphunter.sql +1 -0
  101. package/dist/db/migrations/0005_daffy_guardian.sql +2 -0
  102. package/dist/db/migrations/0006_cute_violations.sql +45 -0
  103. package/dist/db/migrations/0007_dark_anthem.sql +2 -0
  104. package/dist/db/migrations/0008_multitenant_accounts.sql +44 -0
  105. package/dist/db/migrations/0009_lean_bodies.sql +13 -0
  106. package/dist/db/migrations/0010_lean_bodies_nullable.sql +56 -0
  107. package/dist/db/migrations/0011_pr_diff_size.sql +10 -0
  108. package/dist/db/migrations/0012_findings_included_default.sql +7 -0
  109. package/dist/db/migrations/0013_review_routing.sql +9 -0
  110. package/dist/db/migrations/0014_account_last_active.sql +6 -0
  111. package/dist/db/migrations/0015_repos_inbox_watch.sql +9 -0
  112. package/dist/db/migrations/0016_repo_viewer_permission.sql +7 -0
  113. package/dist/db/migrations/0017_account_display_name.sql +7 -0
  114. package/dist/db/migrations/0018_finding_comment_kind.sql +7 -0
  115. package/dist/db/migrations/0019_finding_file_in_diff.sql +7 -0
  116. package/dist/db/migrations/0020_claude_review_cost_telemetry.sql +11 -0
  117. package/dist/db/migrations/0021_account_feed_last_seen.sql +7 -0
  118. package/dist/db/migrations/0022_ci_status_events.sql +21 -0
  119. package/dist/db/migrations/0023_ai_usage.sql +21 -0
  120. package/dist/db/migrations/0024_pr_head_ref_name.sql +6 -0
  121. package/dist/db/migrations/0025_account_billing.sql +7 -0
  122. package/dist/db/migrations/0026_ai_credit_allowance.sql +5 -0
  123. package/dist/db/migrations/0027_user_github_type.sql +6 -0
  124. package/dist/db/migrations/0028_bot_review_classification.sql +21 -0
  125. package/dist/db/migrations/0029_bot_mute_rules.sql +19 -0
  126. package/dist/db/migrations/0030_teams.sql +32 -0
  127. package/dist/db/migrations/0031_events_pr_idx.sql +7 -0
  128. package/dist/db/migrations/0032_benchmark_contributions.sql +29 -0
  129. package/dist/db/migrations/0033_thread_addressed_confidence.sql +9 -0
  130. package/dist/db/migrations/0034_thread_resolved_at.sql +6 -0
  131. package/dist/db/migrations/0035_pr_first_review_requested_at.sql +5 -0
  132. package/dist/db/migrations/0036_search_index.sql +25 -0
  133. package/dist/db/migrations/0037_author_indexes.sql +15 -0
  134. package/dist/db/migrations/0038_auto_merge_requests.sql +37 -0
  135. package/dist/db/migrations/0039_branch_status.sql +44 -0
  136. package/dist/db/migrations/0040_pr_review_decision.sql +10 -0
  137. package/dist/db/migrations/0041_branch_commit_detail.sql +38 -0
  138. package/dist/db/migrations/0042_repo_reviewers.sql +176 -0
  139. package/dist/db/migrations/0043_account_reviewers.sql +131 -0
  140. package/dist/db/migrations/0044_workspaces.sql +138 -0
  141. package/dist/db/migrations/0045_workspace_reviewers.sql +180 -0
  142. package/dist/db/migrations/0046_drop_inbox_watch.sql +25 -0
  143. package/dist/db/migrations/0047_ml_comment_labels.sql +40 -0
  144. package/dist/db/migrations/0048_ml_vendor_severity.sql +31 -0
  145. package/dist/db/migrations/0049_seat_pricing_repo_description.sql +21 -0
  146. package/dist/db/migrations/0050_users_app_slug.sql +12 -0
  147. package/dist/db/migrations/0051_auto_merge_queue.sql +17 -0
  148. package/dist/db/migrations/0052_trunk_ci_status_events.sql +43 -0
  149. package/dist/db/migrations/0053_automation_roles.sql +79 -0
  150. package/dist/db/migrations/0054_automation_vendor_kinds.sql +514 -0
  151. package/dist/db/migrations/0055_auto_merge_phase.sql +12 -0
  152. package/dist/db/migrations/0056_pr_mentions.sql +22 -0
  153. package/dist/db/migrations/0057_large_pr_threshold.sql +14 -0
  154. package/dist/db/migrations/0058_pending_mute.sql +53 -0
  155. package/dist/db/migrations/0059_merge_queue_state.sql +33 -0
  156. package/dist/db/migrations/0060_drop_my_turn_dismissals.sql +33 -0
  157. package/dist/db/migrations/0061_auto_merge_durable_anchors.sql +27 -0
  158. package/dist/db/migrations/0062_blast_radius_config.sql +21 -0
  159. package/dist/db/migrations/0063_repo_file_coupling.sql +33 -0
  160. package/dist/db/migrations/0064_pr_content_kind.sql +21 -0
  161. package/dist/db/migrations/0065_workspace_flow_settings.sql +13 -0
  162. package/dist/db/migrations/0066_review_request_events.sql +31 -0
  163. package/dist/db/migrations/0067_pr_security_signals.sql +86 -0
  164. package/dist/db/migrations/0068_my_turn_settings.sql +18 -0
  165. package/dist/db/migrations/0069_my_turn_dismissals.sql +41 -0
  166. package/dist/db/migrations/0070_claude_review_follow_up_ticket.sql +22 -0
  167. package/dist/db/migrations/0071_claude_review_trigger.sql +11 -0
  168. package/dist/db/migrations/0072_commit_message_headline.sql +13 -0
  169. package/dist/db/migrations/0073_claude_review_chat.sql +31 -0
  170. package/dist/db/migrations/0074_adopt_agentic_tables.sql +98 -0
  171. package/dist/db/migrations/0075_review_lens_drop_learnings.sql +15 -0
  172. package/dist/db/migrations/0076_claude_review_threads.sql +17 -0
  173. package/dist/db/migrations/0077_claude_review_ci_failures.sql +10 -0
  174. package/dist/db/migrations/0078_ai_fix_change_report.sql +17 -0
  175. package/dist/db/migrations/0079_claude_finding_story.sql +16 -0
  176. package/dist/db/migrations/meta/0000_snapshot.json +1116 -0
  177. package/dist/db/migrations/meta/0001_snapshot.json +1321 -0
  178. package/dist/db/migrations/meta/0002_snapshot.json +1375 -0
  179. package/dist/db/migrations/meta/0003_snapshot.json +1396 -0
  180. package/dist/db/migrations/meta/0004_snapshot.json +1416 -0
  181. package/dist/db/migrations/meta/0005_snapshot.json +1430 -0
  182. package/dist/db/migrations/meta/0006_snapshot.json +1745 -0
  183. package/dist/db/migrations/meta/0007_snapshot.json +1759 -0
  184. package/dist/db/migrations/meta/_journal.json +566 -0
  185. package/dist/db/migrations-pg/0000_tired_alex_power.sql +268 -0
  186. package/dist/db/migrations-pg/0001_dizzy_mockingbird.sql +3 -0
  187. package/dist/db/migrations-pg/0002_glorious_ikaris.sql +4 -0
  188. package/dist/db/migrations-pg/0003_melted_secret_warriors.sql +2 -0
  189. package/dist/db/migrations-pg/0004_numerous_triton.sql +1 -0
  190. package/dist/db/migrations-pg/0005_thick_blockbuster.sql +2 -0
  191. package/dist/db/migrations-pg/0006_majestic_dracula.sql +1 -0
  192. package/dist/db/migrations-pg/0007_same_human_torch.sql +2 -0
  193. package/dist/db/migrations-pg/0008_slimy_toxin.sql +1 -0
  194. package/dist/db/migrations-pg/0009_fuzzy_jubilee.sql +4 -0
  195. package/dist/db/migrations-pg/0010_late_hammerhead.sql +1 -0
  196. package/dist/db/migrations-pg/0011_happy_leech.sql +16 -0
  197. package/dist/db/migrations-pg/0012_woozy_captain_midlands.sql +16 -0
  198. package/dist/db/migrations-pg/0013_yielding_nicolaos.sql +1 -0
  199. package/dist/db/migrations-pg/0014_wakeful_bill_hollister.sql +2 -0
  200. package/dist/db/migrations-pg/0015_aspiring_black_tom.sql +1 -0
  201. package/dist/db/migrations-pg/0016_cute_ghost_rider.sql +30 -0
  202. package/dist/db/migrations-pg/0017_far_changeling.sql +24 -0
  203. package/dist/db/migrations-pg/0018_fancy_miss_america.sql +1 -0
  204. package/dist/db/migrations-pg/0019_benchmark_contributions.sql +22 -0
  205. package/dist/db/migrations-pg/0020_majestic_edwin_jarvis.sql +3 -0
  206. package/dist/db/migrations-pg/0021_green_salo.sql +1 -0
  207. package/dist/db/migrations-pg/0022_wise_baron_strucker.sql +1 -0
  208. package/dist/db/migrations-pg/0023_search_index.sql +19 -0
  209. package/dist/db/migrations-pg/0024_author_indexes.sql +9 -0
  210. package/dist/db/migrations-pg/0025_auto_merge_requests.sql +26 -0
  211. package/dist/db/migrations-pg/0026_branch_status.sql +32 -0
  212. package/dist/db/migrations-pg/0027_pr_review_decision.sql +5 -0
  213. package/dist/db/migrations-pg/0028_branch_commit_detail.sql +9 -0
  214. package/dist/db/migrations-pg/0029_repo_reviewers.sql +127 -0
  215. package/dist/db/migrations-pg/0030_account_reviewers.sql +77 -0
  216. package/dist/db/migrations-pg/0031_workspaces.sql +131 -0
  217. package/dist/db/migrations-pg/0032_workspace_reviewers.sql +165 -0
  218. package/dist/db/migrations-pg/0033_drop_inbox_watch.sql +14 -0
  219. package/dist/db/migrations-pg/0034_ml_comment_labels.sql +33 -0
  220. package/dist/db/migrations-pg/0035_ml_vendor_severity.sql +16 -0
  221. package/dist/db/migrations-pg/0036_seat_pricing_repo_description.sql +12 -0
  222. package/dist/db/migrations-pg/0037_users_app_slug.sql +9 -0
  223. package/dist/db/migrations-pg/0038_auto_merge_queue.sql +10 -0
  224. package/dist/db/migrations-pg/0039_trunk_ci_status_events.sql +27 -0
  225. package/dist/db/migrations-pg/0040_automation_roles.sql +63 -0
  226. package/dist/db/migrations-pg/0041_automation_vendor_kinds.sql +488 -0
  227. package/dist/db/migrations-pg/0042_auto_merge_phase.sql +8 -0
  228. package/dist/db/migrations-pg/0043_pr_mentions.sql +19 -0
  229. package/dist/db/migrations-pg/0044_large_pr_threshold.sql +6 -0
  230. package/dist/db/migrations-pg/0045_pending_mute.sql +26 -0
  231. package/dist/db/migrations-pg/0046_merge_queue_state.sql +18 -0
  232. package/dist/db/migrations-pg/0047_drop_my_turn_dismissals.sql +21 -0
  233. package/dist/db/migrations-pg/0048_auto_merge_durable_anchors.sql +27 -0
  234. package/dist/db/migrations-pg/0049_blast_radius_config.sql +8 -0
  235. package/dist/db/migrations-pg/0050_repo_file_coupling.sql +13 -0
  236. package/dist/db/migrations-pg/0051_pr_content_kind.sql +6 -0
  237. package/dist/db/migrations-pg/0052_workspace_flow_settings.sql +4 -0
  238. package/dist/db/migrations-pg/0053_review_request_events.sql +18 -0
  239. package/dist/db/migrations-pg/0054_pr_security_signals.sql +59 -0
  240. package/dist/db/migrations-pg/0055_my_turn_settings.sql +6 -0
  241. package/dist/db/migrations-pg/0056_my_turn_dismissals.sql +21 -0
  242. package/dist/db/migrations-pg/0057_claude_review_follow_up_ticket.sql +8 -0
  243. package/dist/db/migrations-pg/0058_claude_review_trigger.sql +4 -0
  244. package/dist/db/migrations-pg/0059_commit_message_headline.sql +3 -0
  245. package/dist/db/migrations-pg/0060_claude_review_chat.sql +24 -0
  246. package/dist/db/migrations-pg/0061_adopt_agentic_tables.sql +79 -0
  247. package/dist/db/migrations-pg/0062_review_lens_drop_learnings.sql +5 -0
  248. package/dist/db/migrations-pg/0063_claude_review_threads.sql +7 -0
  249. package/dist/db/migrations-pg/0064_claude_review_ci_failures.sql +4 -0
  250. package/dist/db/migrations-pg/0065_ai_fix_change_report.sql +9 -0
  251. package/dist/db/migrations-pg/0066_claude_finding_story.sql +7 -0
  252. package/dist/db/migrations-pg/meta/0000_snapshot.json +2137 -0
  253. package/dist/db/migrations-pg/meta/0001_snapshot.json +2143 -0
  254. package/dist/db/migrations-pg/meta/0002_snapshot.json +2170 -0
  255. package/dist/db/migrations-pg/meta/0003_snapshot.json +2182 -0
  256. package/dist/db/migrations-pg/meta/0004_snapshot.json +2188 -0
  257. package/dist/db/migrations-pg/meta/0005_snapshot.json +2201 -0
  258. package/dist/db/migrations-pg/meta/0006_snapshot.json +2207 -0
  259. package/dist/db/migrations-pg/meta/0007_snapshot.json +2219 -0
  260. package/dist/db/migrations-pg/meta/0008_snapshot.json +2226 -0
  261. package/dist/db/migrations-pg/meta/0009_snapshot.json +2250 -0
  262. package/dist/db/migrations-pg/meta/0010_snapshot.json +2256 -0
  263. package/dist/db/migrations-pg/meta/0011_snapshot.json +2412 -0
  264. package/dist/db/migrations-pg/meta/0012_snapshot.json +2528 -0
  265. package/dist/db/migrations-pg/meta/0013_snapshot.json +2534 -0
  266. package/dist/db/migrations-pg/meta/0014_snapshot.json +2547 -0
  267. package/dist/db/migrations-pg/meta/0015_snapshot.json +2553 -0
  268. package/dist/db/migrations-pg/meta/0016_snapshot.json +2774 -0
  269. package/dist/db/migrations-pg/meta/0017_snapshot.json +2999 -0
  270. package/dist/db/migrations-pg/meta/0018_snapshot.json +3020 -0
  271. package/dist/db/migrations-pg/meta/0020_snapshot.json +3228 -0
  272. package/dist/db/migrations-pg/meta/0021_snapshot.json +3234 -0
  273. package/dist/db/migrations-pg/meta/0022_snapshot.json +3240 -0
  274. package/dist/db/migrations-pg/meta/_journal.json +475 -0
  275. package/dist/db/ml-labels.js +1666 -0
  276. package/dist/db/my-turn-dismissals.js +343 -0
  277. package/dist/db/my-turn-settings.js +34 -0
  278. package/dist/db/pending-classify.js +46 -0
  279. package/dist/db/pending-mute.js +171 -0
  280. package/dist/db/pending-tabs.js +238 -0
  281. package/dist/db/period-metrics.js +820 -0
  282. package/dist/db/person-period.js +763 -0
  283. package/dist/db/pr-intervals.js +930 -0
  284. package/dist/db/pr-liveness.js +240 -0
  285. package/dist/db/pr-mentions.js +279 -0
  286. package/dist/db/pr-merge-queue-stamp.js +91 -0
  287. package/dist/db/queries.js +14631 -0
  288. package/dist/db/reaction-targets.js +94 -0
  289. package/dist/db/repo-activity.js +171 -0
  290. package/dist/db/retention.js +138 -0
  291. package/dist/db/review-threads-for-review.js +240 -0
  292. package/dist/db/run-migrations.js +24 -0
  293. package/dist/db/schema.pg.js +1220 -0
  294. package/dist/db/schema.sqlite.js +1801 -0
  295. package/dist/db/search.js +317 -0
  296. package/dist/db/security-alerts.js +218 -0
  297. package/dist/db/synthesis-input.js +565 -0
  298. package/dist/db/triage.js +320 -0
  299. package/dist/db/usage.js +63 -0
  300. package/dist/db/work-plan.js +1086 -0
  301. package/dist/db/working-hours.js +161 -0
  302. package/dist/feed/my-turn.js +122 -0
  303. package/dist/github/actions-logs.js +397 -0
  304. package/dist/github/auth.js +86 -0
  305. package/dist/github/branch-queries.js +229 -0
  306. package/dist/github/client.js +454 -0
  307. package/dist/github/codeowners.js +221 -0
  308. package/dist/github/commit-checks.js +105 -0
  309. package/dist/github/compare.js +110 -0
  310. package/dist/github/diff-anchor.js +227 -0
  311. package/dist/github/mutations.js +706 -0
  312. package/dist/github/pr-file-content.js +254 -0
  313. package/dist/github/pr-liveness.js +161 -0
  314. package/dist/github/queries.js +875 -0
  315. package/dist/github/rate-budget.js +129 -0
  316. package/dist/github/reactions.js +161 -0
  317. package/dist/github/reviewer-suggest.js +104 -0
  318. package/dist/github/team-reviewers.js +96 -0
  319. package/dist/index.js +184 -0
  320. package/dist/merge/auto-merge-runner.js +960 -0
  321. package/dist/ml/severity-client.js +218 -0
  322. package/dist/pr/detail-enricher.js +26 -0
  323. package/dist/pro/bind.js +302 -0
  324. package/dist/pro/contract.js +37 -0
  325. package/dist/pro/migrate.js +64 -0
  326. package/dist/review/agent-context.js +127 -0
  327. package/dist/review/agent.js +385 -0
  328. package/dist/review/agentic.js +59 -0
  329. package/dist/review/auth.js +94 -0
  330. package/dist/review/chat-agent.js +179 -0
  331. package/dist/review/claude-review/ai-ready.js +16 -0
  332. package/dist/review/claude-review/auto-settings.js +139 -0
  333. package/dist/review/claude-review/auto.js +244 -0
  334. package/dist/review/claude-review/chat.js +489 -0
  335. package/dist/review/claude-review/ci-failures.js +350 -0
  336. package/dist/review/claude-review/follow-up.js +310 -0
  337. package/dist/review/claude-review/manager.js +686 -0
  338. package/dist/review/claude-review/persist.js +996 -0
  339. package/dist/review/claude-review/prompts.js +357 -0
  340. package/dist/review/claude-review/routes.js +518 -0
  341. package/dist/review/claude-review/routing.js +61 -0
  342. package/dist/review/claude-review/specialists.js +190 -0
  343. package/dist/review/claude-review/threads.js +265 -0
  344. package/dist/review/claude-review/ticket.js +282 -0
  345. package/dist/review/clone-hygiene.js +380 -0
  346. package/dist/review/clone-manager.js +367 -0
  347. package/dist/review/llm.js +113 -0
  348. package/dist/review/model-options.js +48 -0
  349. package/dist/review/plugin-providers.js +12 -0
  350. package/dist/review/post-review.js +216 -0
  351. package/dist/review/post-seam.js +161 -0
  352. package/dist/review/prepare.js +168 -0
  353. package/dist/review/pricing.js +17 -0
  354. package/dist/review/schema.js +156 -0
  355. package/dist/review/submit-map.js +41 -0
  356. package/dist/review/usage.js +90 -0
  357. package/dist/shared/claude-review.js +335 -0
  358. package/dist/shared/conflict-fold.js +151 -0
  359. package/dist/shared/conflicts.js +30 -0
  360. package/dist/shared/flow-settings.js +191 -0
  361. package/dist/shared/index.js +8 -0
  362. package/dist/shared/my-turn-settings.js +238 -0
  363. package/dist/shared/pending-rules.js +281 -0
  364. package/dist/shared/product.js +54 -0
  365. package/dist/shared/types.js +896 -0
  366. package/dist/status.js +386 -0
  367. package/dist/sync/adaptive.js +185 -0
  368. package/dist/sync/app-attribution.js +66 -0
  369. package/dist/sync/auth-notices.js +36 -0
  370. package/dist/sync/backfill-ci-history.js +315 -0
  371. package/dist/sync/backfill-commit-headlines.js +136 -0
  372. package/dist/sync/backfill-pr-security.js +175 -0
  373. package/dist/sync/backfill-review-requests.js +97 -0
  374. package/dist/sync/benchmark-rollup.js +50 -0
  375. package/dist/sync/bot-detection.js +309 -0
  376. package/dist/sync/bot-resolution-markers.js +30 -0
  377. package/dist/sync/branch-status.js +634 -0
  378. package/dist/sync/classify-change-shape.js +150 -0
  379. package/dist/sync/commit-files.js +74 -0
  380. package/dist/sync/derive-thread-state.js +99 -0
  381. package/dist/sync/drain-review-threads.js +212 -0
  382. package/dist/sync/hydrate-detail.js +482 -0
  383. package/dist/sync/mention-scan.js +89 -0
  384. package/dist/sync/ml-enrichment.js +478 -0
  385. package/dist/sync/pr-change-signal.js +96 -0
  386. package/dist/sync/pr-liveness-sweep.js +139 -0
  387. package/dist/sync/pr-settle.js +381 -0
  388. package/dist/sync/refresh-pr.js +163 -0
  389. package/dist/sync/reparse-vendor-badges.js +495 -0
  390. package/dist/sync/resync-after-write.js +280 -0
  391. package/dist/sync/review-fingerprint.js +117 -0
  392. package/dist/sync/reviewer-behavior.js +103 -0
  393. package/dist/sync/reviewer-classify.js +503 -0
  394. package/dist/sync/routing-files.js +97 -0
  395. package/dist/sync/scheduled-jobs.js +12 -0
  396. package/dist/sync/scheduler.js +159 -0
  397. package/dist/sync/security-detect.js +521 -0
  398. package/dist/sync/sync-manager.js +750 -0
  399. package/dist/sync/sync-one-pr.js +205 -0
  400. package/dist/sync/sync-repo.js +400 -0
  401. package/dist/sync/unsettled-prs.js +506 -0
  402. package/dist/sync/upsert.js +1190 -0
  403. package/package.json +56 -0
  404. package/public/apple-touch-icon.png +0 -0
  405. package/public/assets/AiFixTab-CQP6PT5u.js +2 -0
  406. package/public/assets/ClaudeReviewTab-DQJ-58ZA.js +8 -0
  407. package/public/assets/PrBotBehaviourTab-CCnFrWbC.js +1 -0
  408. package/public/assets/archivo-latin-600-BN-JkNrl.woff2 +0 -0
  409. package/public/assets/highlight-BEHUn5zE.css +10 -0
  410. package/public/assets/highlight-CIIZLa6Z.js +12 -0
  411. package/public/assets/index-BDnYs6zg.js +73 -0
  412. package/public/assets/index-mCuI--RC.css +1 -0
  413. package/public/assets/markdown-I_JLLVAZ.js +32 -0
  414. package/public/assets/perfProbe-Da13EhTE.js +1 -0
  415. package/public/assets/tanstack-5qsxxaLe.js +17 -0
  416. package/public/assets/vis-C3BCkP5U.js +1280 -0
  417. package/public/assets/vis-ziCVehMy.css +1 -0
  418. package/public/favicon-16.png +0 -0
  419. package/public/favicon-32.png +0 -0
  420. package/public/favicon-48.png +0 -0
  421. package/public/favicon.svg +5 -0
  422. package/public/index.html +41 -0
  423. package/public-landing/apple-touch-icon.png +0 -0
  424. package/public-landing/assets/archivo-latin-500-BWmvx2Mg.woff2 +0 -0
  425. package/public-landing/assets/archivo-latin-600-BN-JkNrl.woff2 +0 -0
  426. package/public-landing/assets/index-CA3eEGXO.js +40 -0
  427. package/public-landing/assets/index-naQrQ6Fe.css +1 -0
  428. package/public-landing/assets/jetbrains-mono-latin-400-RruCX7RW.woff2 +0 -0
  429. package/public-landing/assets/newsreader-latin-400-DExrRRrN.woff2 +0 -0
  430. package/public-landing/assets/newsreader-latin-400-italic-b0I-AwbV.woff2 +0 -0
  431. package/public-landing/bots/index.html +156 -0
  432. package/public-landing/contact/index.html +156 -0
  433. package/public-landing/cookies/index.html +156 -0
  434. package/public-landing/demo/limn-walkthrough-poster.jpg +0 -0
  435. package/public-landing/demo/limn-walkthrough.cues.json +44 -0
  436. package/public-landing/demo/limn-walkthrough.mp4 +0 -0
  437. package/public-landing/favicon-16.png +0 -0
  438. package/public-landing/favicon-32.png +0 -0
  439. package/public-landing/favicon-48.png +0 -0
  440. package/public-landing/favicon.svg +5 -0
  441. package/public-landing/features/index.html +156 -0
  442. package/public-landing/for-developers/index.html +156 -0
  443. package/public-landing/for-managers/index.html +156 -0
  444. package/public-landing/how-it-works/index.html +156 -0
  445. package/public-landing/how-we-measure/index.html +156 -0
  446. package/public-landing/icon-192.png +0 -0
  447. package/public-landing/icon-512.png +0 -0
  448. package/public-landing/index.html +156 -0
  449. package/public-landing/insights/index.html +156 -0
  450. package/public-landing/og-image.png +0 -0
  451. package/public-landing/pricing/index.html +156 -0
  452. package/public-landing/privacy/index.html +156 -0
  453. package/public-landing/pro/index.html +156 -0
  454. package/public-landing/reviews/index.html +156 -0
  455. package/public-landing/robots.txt +8 -0
  456. package/public-landing/shots/benchmark.png +0 -0
  457. package/public-landing/shots/bot-roi.png +0 -0
  458. package/public-landing/shots/bot-settings.png +0 -0
  459. package/public-landing/shots/chronology.png +0 -0
  460. package/public-landing/shots/feed.png +0 -0
  461. package/public-landing/shots/flow-metrics.png +0 -0
  462. package/public-landing/shots/free-reports.png +0 -0
  463. package/public-landing/shots/pending-board.png +0 -0
  464. package/public-landing/shots/pending-card.png +0 -0
  465. package/public-landing/shots/period-report.png +0 -0
  466. package/public-landing/shots/pr-detail.png +0 -0
  467. package/public-landing/shots/pr-threads.png +0 -0
  468. package/public-landing/shots/reach.png +0 -0
  469. package/public-landing/shots/repo-rows.png +0 -0
  470. package/public-landing/shots/severity-findings.png +0 -0
  471. package/public-landing/shots/severity-strip.png +0 -0
  472. package/public-landing/site.webmanifest +14 -0
  473. package/public-landing/sitemap.xml +51 -0
  474. package/public-landing/terms/index.html +156 -0
@@ -0,0 +1,706 @@
1
+ // Per-account GitHub WRITE actions — the data-plane for the PR write features
2
+ // (reply / resolve / comment / approve / inline comment / fetch files). Every
3
+ // function takes the owning account's token as its FIRST argument and goes
4
+ // through the per-account client factories (getGraphqlClientFor / ghRestGetFor /
5
+ // ghRestPostFor) — NEVER the gh-CLI wrappers, so these work in cloud too.
6
+ //
7
+ // GraphQL note: @octokit/graphql RESERVES the variable name `query`. The
8
+ // operation-variable here is named `input` / etc., never `query`.
9
+ import { getGraphqlClientFor, ghRestGetDiffStatus, ghRestGetFor, ghRestPatchStatus, ghRestPostFor, ghRestPostNoContent, ghRestPutStatus, } from './client.js';
10
+ import { summariseReactionGroups, toGithubReactionContent } from './reactions.js';
11
+ const ADD_REPLY_MUTATION = /* GraphQL */ `
12
+ mutation AddThreadReply($input: AddPullRequestReviewThreadReplyInput!) {
13
+ addPullRequestReviewThreadReply(input: $input) {
14
+ comment {
15
+ id
16
+ databaseId
17
+ body
18
+ createdAt
19
+ url
20
+ author {
21
+ login
22
+ }
23
+ }
24
+ }
25
+ }
26
+ `;
27
+ // Reply to an existing review thread (GraphQL). `threadNodeId` is the thread's
28
+ // GitHub node id (reviewThreads.githubNodeId).
29
+ export async function addReviewThreadReply(token, threadNodeId, body) {
30
+ const gql = getGraphqlClientFor(token);
31
+ const res = await gql(ADD_REPLY_MUTATION, {
32
+ input: { pullRequestReviewThreadId: threadNodeId, body },
33
+ });
34
+ const c = res.addPullRequestReviewThreadReply.comment;
35
+ if (!c) {
36
+ throw new Error('GitHub returned no comment for the thread reply (the reply may still have posted)');
37
+ }
38
+ return {
39
+ nodeId: c.id,
40
+ databaseId: c.databaseId,
41
+ body: c.body,
42
+ createdAt: c.createdAt,
43
+ url: c.url,
44
+ authorLogin: c.author?.login ?? null,
45
+ };
46
+ }
47
+ const RESOLVE_MUTATION = /* GraphQL */ `
48
+ mutation ResolveThread($input: ResolveReviewThreadInput!) {
49
+ resolveReviewThread(input: $input) {
50
+ thread {
51
+ isResolved
52
+ }
53
+ }
54
+ }
55
+ `;
56
+ const UNRESOLVE_MUTATION = /* GraphQL */ `
57
+ mutation UnresolveThread($input: UnresolveReviewThreadInput!) {
58
+ unresolveReviewThread(input: $input) {
59
+ thread {
60
+ isResolved
61
+ }
62
+ }
63
+ }
64
+ `;
65
+ // Resolve (resolved=true) or unresolve (resolved=false) a review thread.
66
+ export async function setReviewThreadResolved(token, threadNodeId, resolved) {
67
+ const gql = getGraphqlClientFor(token);
68
+ if (resolved) {
69
+ const res = await gql(RESOLVE_MUTATION, {
70
+ input: { threadId: threadNodeId },
71
+ });
72
+ const t = res.resolveReviewThread.thread;
73
+ if (!t)
74
+ throw new Error('GitHub returned no thread for resolve');
75
+ return { isResolved: t.isResolved };
76
+ }
77
+ const res = await gql(UNRESOLVE_MUTATION, {
78
+ input: { threadId: threadNodeId },
79
+ });
80
+ const t = res.unresolveReviewThread.thread;
81
+ if (!t)
82
+ throw new Error('GitHub returned no thread for unresolve');
83
+ return { isResolved: t.isResolved };
84
+ }
85
+ // Post a new issue-level (general PR) comment. A PR is an issue for the comments
86
+ // endpoint, so `number` is the PR number.
87
+ export async function addIssueComment(token, owner, name, number, body) {
88
+ const res = await ghRestPostFor(token, `/repos/${owner}/${name}/issues/${number}/comments`, { body });
89
+ return {
90
+ nodeId: res.node_id,
91
+ databaseId: res.id,
92
+ body: res.body,
93
+ createdAt: res.created_at,
94
+ url: res.html_url,
95
+ authorLogin: res.user?.login ?? null,
96
+ };
97
+ }
98
+ // Submit ONE PR review (inline comments + body + verdict) in a single call. The
99
+ // REST reviews endpoint is required for inline line comments. `commitId` pins the
100
+ // review to a head SHA so it 409s/422s if the head moved. `comments` are inline
101
+ // line comments folded into this one review.
102
+ export async function submitPrReview(token, owner, name, number, opts) {
103
+ const payload = { event: opts.event };
104
+ if (opts.body !== undefined)
105
+ payload.body = opts.body;
106
+ if (opts.commitId !== undefined)
107
+ payload.commit_id = opts.commitId;
108
+ if (opts.comments && opts.comments.length > 0) {
109
+ payload.comments = opts.comments.map((c) => ({
110
+ path: c.path,
111
+ line: c.line,
112
+ side: c.side,
113
+ body: c.body,
114
+ }));
115
+ }
116
+ const res = await ghRestPostFor(token, `/repos/${owner}/${name}/pulls/${number}/reviews`, payload);
117
+ return {
118
+ databaseId: res.id,
119
+ nodeId: res.node_id,
120
+ state: res.state,
121
+ body: res.body,
122
+ submittedAt: res.submitted_at ?? new Date().toISOString(),
123
+ url: res.html_url,
124
+ authorLogin: res.user?.login ?? null,
125
+ };
126
+ }
127
+ // Post a SINGLE standalone inline review comment (not part of a review draft),
128
+ // committed immediately. `commitId` pins it to the head SHA; the line must be
129
+ // addable on that side.
130
+ export async function postInlineComment(token, owner, name, number, opts) {
131
+ const res = await ghRestPostFor(token, `/repos/${owner}/${name}/pulls/${number}/comments`, {
132
+ body: opts.body,
133
+ commit_id: opts.commitId,
134
+ path: opts.path,
135
+ line: opts.line,
136
+ side: opts.side,
137
+ });
138
+ return { databaseId: res.id, nodeId: res.node_id, url: res.html_url };
139
+ }
140
+ // Fetch the PR's per-file diff patches, paginating by hand (per_page=100). Models
141
+ // the hand-paginated loop on sync/commit-files.ts. Stops once a page returns < 100
142
+ // files (definitively the last page) or once we've collected more than `cap`
143
+ // files. `truncated` is true iff there are more files than we return — either
144
+ // because a fetched page pushed us past `cap`, or because the last full page was
145
+ // exactly at `cap` AND a subsequent page returned more (handled naturally: we
146
+ // only break on a short page or on overflow, so a trailing full page is always
147
+ // followed by another fetch).
148
+ export async function fetchPrFilesWithPatch(token, owner, name, number, cap = 100) {
149
+ const files = [];
150
+ let truncated = false;
151
+ let page = 1;
152
+ // GitHub caps per_page at 100 for this endpoint.
153
+ const perPage = 100;
154
+ for (;;) {
155
+ const pageFiles = await ghRestGetFor(token, `/repos/${owner}/${name}/pulls/${number}/files?per_page=${perPage}&page=${page}`);
156
+ files.push(...pageFiles);
157
+ if (files.length > cap) {
158
+ // We have strictly more than the cap → there's at least one extra file.
159
+ truncated = true;
160
+ files.length = cap;
161
+ break;
162
+ }
163
+ if (pageFiles.length < perPage) {
164
+ // Short page: this is the last page, nothing beyond it.
165
+ break;
166
+ }
167
+ // A full page came back and we're still at/under the cap. Keep going; the
168
+ // next page's result decides whether more files exist beyond the cap.
169
+ page += 1;
170
+ }
171
+ return { files, truncated };
172
+ }
173
+ // The PR's current head SHA (to pin a posted review/comment to, and to detect a
174
+ // head-moved race).
175
+ export async function fetchHeadShaFor(token, owner, name, number) {
176
+ const pull = await ghRestGetFor(token, `/repos/${owner}/${name}/pulls/${number}`);
177
+ return pull.head.sha;
178
+ }
179
+ // Full head/fork metadata for a PR. `isFork` is true when the head repo differs from
180
+ // the base repo; combined with `maintainer_can_modify` it tells the fixer whether the
181
+ // PR's own head branch can be pushed to (else it must open a new branch in the base
182
+ // repo). head.repo can be null when a fork was deleted — treated as a fork.
183
+ export async function fetchPrHeadInfo(token, owner, name, number) {
184
+ const pull = await ghRestGetFor(token, `/repos/${owner}/${name}/pulls/${number}`);
185
+ const baseRepoFullName = pull.base.repo.full_name;
186
+ const headRepoFullName = pull.head.repo?.full_name ?? baseRepoFullName;
187
+ return {
188
+ headSha: pull.head.sha,
189
+ headRef: pull.head.ref,
190
+ headRepoFullName,
191
+ isFork: pull.head.repo == null || headRepoFullName !== baseRepoFullName,
192
+ maintainerCanModify: pull.maintainer_can_modify,
193
+ baseRef: pull.base.ref,
194
+ };
195
+ }
196
+ // GitHub's OWN mergeability for a PR — a NEAR-INSTANT trunk-conflict signal (one, or
197
+ // two, REST calls), read without cloning by the PR merge / update-branch routes
198
+ // (api/routes/prs.ts). GitHub computes `mergeable` asynchronously and can briefly return
199
+ // null; we retry once. The `compare` call adds behind/ahead counts + the trunk tip.
200
+ export async function fetchMergeability(token, owner, name, number) {
201
+ let pull = await ghRestGetFor(token, `/repos/${owner}/${name}/pulls/${number}`);
202
+ if (pull.mergeable === null) {
203
+ await new Promise((r) => setTimeout(r, 600));
204
+ pull = await ghRestGetFor(token, `/repos/${owner}/${name}/pulls/${number}`);
205
+ }
206
+ const cmp = await compareBaseToHead(token, owner, name, pull);
207
+ return {
208
+ mergeable: pull.mergeable,
209
+ mergeableState: pull.mergeable_state,
210
+ baseRef: pull.base.ref,
211
+ baseSha: cmp.baseSha ?? pull.base.sha,
212
+ behindBy: cmp.behindBy,
213
+ aheadBy: cmp.aheadBy,
214
+ };
215
+ }
216
+ // The `GET /compare/base...head` half of the mergeability read, shared by `fetchMergeability`
217
+ // and `fetchPrMergeSnapshot` so the fork-qualification rule can't drift between them.
218
+ async function compareBaseToHead(token, owner, name, pull) {
219
+ try {
220
+ // For a fork the head must be qualified `owner:branch` (head.label); same-repo uses
221
+ // the plain ref. Branch names with slashes are fine literal in the compare path.
222
+ const headRef = pull.head.repo ? pull.head.label : pull.head.ref;
223
+ const cmp = await ghRestGetFor(token, `/repos/${owner}/${name}/compare/${pull.base.ref}...${headRef}`);
224
+ return {
225
+ behindBy: cmp.behind_by ?? 0,
226
+ aheadBy: cmp.ahead_by ?? 0,
227
+ baseSha: cmp.base_commit?.sha ?? null,
228
+ };
229
+ }
230
+ catch {
231
+ /* compare is best-effort — the mergeable flag is the load-bearing part */
232
+ return { behindBy: 0, aheadBy: 0, baseSha: null };
233
+ }
234
+ }
235
+ // Head info AND mergeability from ONE `GET /pulls/{n}`. `fetchPrHeadInfo` and
236
+ // `fetchMergeability` read strictly non-overlapping fields of the SAME payload, so a caller
237
+ // that needs both (the auto-merge watcher, on every intent on every tick) was paying two
238
+ // identical GETs — 750 wasted calls/hour at 25 intents against a 5k/hour budget. Both of the
239
+ // originals stay for their single-purpose callers.
240
+ export async function fetchPrMergeSnapshot(token, owner, name, number) {
241
+ const path = `/repos/${owner}/${name}/pulls/${number}`;
242
+ let pull = await ghRestGetFor(token, path);
243
+ // GitHub computes `mergeable` asynchronously and can briefly return null; retry once,
244
+ // exactly as fetchMergeability does.
245
+ if (pull.mergeable === null) {
246
+ await new Promise((r) => setTimeout(r, 600));
247
+ pull = await ghRestGetFor(token, path);
248
+ }
249
+ const cmp = await compareBaseToHead(token, owner, name, pull);
250
+ const baseRepoFullName = pull.base.repo.full_name;
251
+ const headRepoFullName = pull.head.repo?.full_name ?? baseRepoFullName;
252
+ return {
253
+ headSha: pull.head.sha,
254
+ headRef: pull.head.ref,
255
+ headRepoFullName,
256
+ isFork: pull.head.repo == null || headRepoFullName !== baseRepoFullName,
257
+ maintainerCanModify: pull.maintainer_can_modify,
258
+ mergeable: pull.mergeable,
259
+ mergeableState: pull.mergeable_state,
260
+ baseRef: pull.base.ref,
261
+ baseSha: cmp.baseSha ?? pull.base.sha,
262
+ behindBy: cmp.behindBy,
263
+ aheadBy: cmp.aheadBy,
264
+ };
265
+ }
266
+ // One commit's parent SHAs (`GET /commits/{sha}`), in order — parent 0 is the first parent.
267
+ // Used to prove that a head move is the base-into-head merge WE asked for rather than a
268
+ // human push: only the former is a two-parent commit whose first parent is the old head.
269
+ export async function fetchCommitParents(token, owner, name, sha) {
270
+ const commit = await ghRestGetFor(token, `/repos/${owner}/${name}/commits/${sha}`);
271
+ return (commit.parents ?? []).map((p) => p.sha);
272
+ }
273
+ // Is `sha` reachable from `ref`? `GET /compare/{ref}...{sha}` reports 'identical' or 'behind'
274
+ // exactly when it is. Returns null when the comparison can't be made (deleted ref, no access)
275
+ // — the caller must treat "don't know" as "no consent", never as a yes.
276
+ export async function isCommitContainedInRef(token, owner, name, ref, sha) {
277
+ try {
278
+ const cmp = await ghRestGetFor(token, `/repos/${owner}/${name}/compare/${ref}...${sha}`);
279
+ return cmp.status === 'identical' || cmp.status === 'behind';
280
+ }
281
+ catch {
282
+ return null;
283
+ }
284
+ }
285
+ const PR_CHECK_ROLLUP_QUERY = /* GraphQL */ `
286
+ query PrCheckRollup($owner: String!, $name: String!, $number: Int!) {
287
+ repository(owner: $owner, name: $name) {
288
+ pullRequest(number: $number) {
289
+ commits(last: 1) {
290
+ nodes {
291
+ commit {
292
+ statusCheckRollup {
293
+ state
294
+ }
295
+ }
296
+ }
297
+ }
298
+ }
299
+ }
300
+ }
301
+ `;
302
+ /**
303
+ * The LIVE rollup state of every check on a PR's head commit (SUCCESS / FAILURE / PENDING /
304
+ * ERROR / EXPECTED), or null.
305
+ *
306
+ * GraphQL rather than REST because `statusCheckRollup` is the ONE field that spans both check
307
+ * runs and legacy commit statuses; REST needs `/check-runs` and `/status` separately and still
308
+ * makes the caller fold them. One point, one call.
309
+ *
310
+ * ⚠ NULL MEANS "WE DON'T KNOW", never "green". A deleted PR, a token that can't read checks
311
+ * (`statusCheckRollup` is one of the sub-fields GitHub NULLS on a partial 200 — see
312
+ * github/client.ts), or a commit no CI ever ran on all land here, so every caller must treat
313
+ * null as "unproven" and take the conservative branch. Errors are swallowed for the same
314
+ * reason: the auto-merge watcher reads this only to decide whether to let a LATER PR go first,
315
+ * and a network blip must not turn into a reordering — or into a failure strike.
316
+ */
317
+ export async function fetchPrHeadCheckRollup(token, owner, name, number) {
318
+ try {
319
+ const gql = getGraphqlClientFor(token);
320
+ const res = await gql(PR_CHECK_ROLLUP_QUERY, { owner, name, number });
321
+ const nodes = res.repository?.pullRequest?.commits?.nodes ?? [];
322
+ return nodes[0]?.commit?.statusCheckRollup?.state ?? null;
323
+ }
324
+ catch {
325
+ return null;
326
+ }
327
+ }
328
+ // ---- PR merge + update-from-trunk (CORE / free tier) ----
329
+ // The repo's enabled merge methods + default branch (GET /repos/{o}/{n}). Not synced — a
330
+ // rarely-changing repo setting, fetched live only when the merge control opens. Missing flags
331
+ // default true (GitHub's own default for a new repo).
332
+ export async function fetchRepoMergeConfig(token, owner, name) {
333
+ const repo = await ghRestGetFor(token, `/repos/${owner}/${name}`);
334
+ return {
335
+ allowMergeCommit: repo.allow_merge_commit ?? true,
336
+ allowSquashMerge: repo.allow_squash_merge ?? true,
337
+ allowRebaseMerge: repo.allow_rebase_merge ?? true,
338
+ defaultBranch: repo.default_branch,
339
+ };
340
+ }
341
+ // Merge the PR via GitHub's native endpoint (PUT .../merge). Pins `sha` to the expected head so
342
+ // GitHub 409s if the branch moved. Cloud-safe, clone-free. The caller pre-checks conflicts.
343
+ export async function mergePullRequest(token, owner, name, number, opts) {
344
+ const body = { merge_method: opts.method };
345
+ if (opts.expectedHeadSha)
346
+ body.sha = opts.expectedHeadSha;
347
+ const res = await ghRestPutStatus(token, `/repos/${owner}/${name}/pulls/${number}/merge`, body);
348
+ const j = (res.json ?? {});
349
+ if (res.ok && j.merged)
350
+ return { ok: true, sha: j.sha ?? '' };
351
+ const message = j.message ?? res.text.slice(0, 300);
352
+ const reason = res.status === 409
353
+ ? 'head_moved'
354
+ : res.status === 422
355
+ ? 'method_disallowed'
356
+ : res.status === 405
357
+ ? 'not_mergeable'
358
+ : 'error';
359
+ return { ok: false, reason, message };
360
+ }
361
+ // Close an open PR (REST PATCH `{ state: 'closed' }`) — WITHOUT merging. Reversible on
362
+ // GitHub (it can be reopened), so no head-SHA pin is needed. Permission is re-checked by the
363
+ // caller (author OR write+). A 404 usually means the token can't see the PR (private/SSO).
364
+ export async function closePullRequest(token, owner, name, number) {
365
+ const res = await ghRestPatchStatus(token, `/repos/${owner}/${name}/pulls/${number}`, {
366
+ state: 'closed',
367
+ });
368
+ if (res.ok)
369
+ return { ok: true };
370
+ const j = (res.json ?? {});
371
+ const message = j.message ?? res.text.slice(0, 300);
372
+ return { ok: false, reason: res.status === 404 ? 'not_found' : 'error', message };
373
+ }
374
+ // Reopen a closed, unmerged PR (REST PATCH `{ state: 'open' }`) — the exact inverse of
375
+ // closePullRequest, same endpoint, same permission rule (author OR write+), re-checked by the
376
+ // caller. No head-SHA pin: reopening asserts nothing about the code.
377
+ //
378
+ // ⚠ 422 IS A REAL, EXPECTED ANSWER AND ITS USEFUL TEXT IS NESTED. GitHub refuses to reopen a PR
379
+ // whose HEAD BRANCH has been deleted (the ordinary aftermath of a close), and refuses on an
380
+ // archived repo. The top-level `message` on those is "Validation Failed" — useless on screen —
381
+ // while `errors[0].message` is the actual sentence ("state cannot be changed. …"). Prefer the
382
+ // nested one so the UI can state the fact instead of narrating a shrug. Reported as
383
+ // `not_reopenable` so the route can answer 409 rather than 502: nothing is broken, the reopen is
384
+ // simply not available.
385
+ export async function reopenPullRequest(token, owner, name, number) {
386
+ const res = await ghRestPatchStatus(token, `/repos/${owner}/${name}/pulls/${number}`, {
387
+ state: 'open',
388
+ });
389
+ if (res.ok)
390
+ return { ok: true };
391
+ const j = (res.json ?? {});
392
+ const message = j.errors?.find((e) => e.message)?.message ?? j.message ?? res.text.slice(0, 300);
393
+ const reason = res.status === 404
394
+ ? 'not_found'
395
+ : res.status === 422 || res.status === 403
396
+ ? 'not_reopenable'
397
+ : 'error';
398
+ return { ok: false, reason, message };
399
+ }
400
+ // GitHub's NATIVE "update branch from base" (PUT .../update-branch) — a merge commit of the base
401
+ // into the head. Cloud-safe, clone-free (the cloud path for update-from-trunk). Merge-only: there
402
+ // is no native rebase. 202 = accepted; 422 = can't update — which GitHub uses BOTH for a stale
403
+ // expected_head_sha (a head-moved race — its message mentions the expected head sha) AND for a
404
+ // genuine can't-merge, so we disambiguate on the message.
405
+ export async function updatePullRequestBranch(token, owner, name, number, expectedHeadSha) {
406
+ const body = expectedHeadSha ? { expected_head_sha: expectedHeadSha } : undefined;
407
+ const res = await ghRestPutStatus(token, `/repos/${owner}/${name}/pulls/${number}/update-branch`, body);
408
+ if (res.status === 202)
409
+ return { ok: true };
410
+ const j = (res.json ?? {});
411
+ const message = j.message ?? res.text.slice(0, 300);
412
+ const headMoved = res.status === 409 || /expected head sha/i.test(message);
413
+ const reason = headMoved ? 'head_moved' : res.status === 422 ? 'conflicts' : 'error';
414
+ return { ok: false, reason, message };
415
+ }
416
+ // The PR's unified diff via the REST API (Accept: application/vnd.github.diff) —
417
+ // cloud-ready, per-account. GitHub caps this media type at 20,000 lines and returns 406
418
+ // past it (a large PR — e.g. bulk data/generated files). In that case we fall back to the
419
+ // per-file endpoint, which has no total-lines cap, and synthesise a unified diff from each
420
+ // file's `patch`. Without this fallback the diff came back empty and the AI summary / CI
421
+ // analysis / fixer would (correctly, but uselessly) report "the diff is empty".
422
+ export async function fetchPrUnifiedDiff(token, owner, name, number) {
423
+ const path = `/repos/${owner}/${name}/pulls/${number}`;
424
+ const res = await ghRestGetDiffStatus(token, path);
425
+ if (res.ok)
426
+ return res.text;
427
+ if (res.status === 406) {
428
+ return reconstructUnifiedDiffFromFiles(token, owner, name, number);
429
+ }
430
+ throw new Error(`GitHub REST GET(diff) ${path} -> ${res.status}: ${res.text.slice(0, 300)}`);
431
+ }
432
+ // Synthesise a unified diff from GitHub's per-file `patch`es — the fallback when the
433
+ // whole-PR .diff 406s (too large). The /files endpoint has no total-lines cap (up to 3000
434
+ // files); a file's `patch` is omitted when it's binary or a single file's own diff is too
435
+ // large, so we name those with their churn/status. The synthetic `diff --git a/… b/…`
436
+ // headers keep the result splittable by capDiff's per-file budgeter (so a summary still
437
+ // stays within the prompt's char budget, grounded in the files that DO have patches).
438
+ export function filesToUnifiedDiff(files) {
439
+ const parts = [];
440
+ for (const f of files) {
441
+ const aPath = f.previous_filename ?? f.filename;
442
+ parts.push(`diff --git a/${aPath} b/${f.filename}`);
443
+ if (f.patch) {
444
+ parts.push(`--- a/${aPath}`, `+++ b/${f.filename}`, f.patch);
445
+ }
446
+ else {
447
+ // GitHub omits `patch` for binary files, a single file whose own diff is too large,
448
+ // and some no-content changes (pure rename / mode-only). Lead with the authoritative
449
+ // status + churn (the real signal) and hedge the reason so a rename isn't mislabelled.
450
+ parts.push(`(diff not shown — status=${f.status}, +${f.additions}/-${f.deletions}; likely binary or too large)`);
451
+ }
452
+ }
453
+ return parts.join('\n');
454
+ }
455
+ async function reconstructUnifiedDiffFromFiles(token, owner, name, number) {
456
+ // 300 files is plenty for a grounded summary and bounds the paging to ~3 requests; the
457
+ // downstream capDiff trims further to the prompt's char budget.
458
+ const { files } = await fetchPrFilesWithPatch(token, owner, name, number, 300);
459
+ return filesToUnifiedDiff(files);
460
+ }
461
+ // Open a pull request (per-account). `head` is the branch to merge FROM (in the base
462
+ // repo — the fixer only ever creates branches in the base repo, so no `owner:branch`
463
+ // cross-fork form is needed); `base` the branch to merge INTO.
464
+ export async function createPullRequest(token, args) {
465
+ const res = await ghRestPostFor(token, `/repos/${args.owner}/${args.name}/pulls`, {
466
+ title: args.title,
467
+ head: args.head,
468
+ base: args.base,
469
+ body: args.body,
470
+ });
471
+ return { number: res.number, url: res.html_url };
472
+ }
473
+ // File an issue (per-account) — the advisor's "send the brief to the bot's own repo"
474
+ // output. Issues are NOT synced (this app tracks PR activity), so the caller stores the
475
+ // returned URL itself; there is no visibility tail to run.
476
+ export async function createIssue(token, owner, name, title, body) {
477
+ const res = await ghRestPostFor(token, `/repos/${owner}/${name}/issues`, { title, body });
478
+ return { number: res.number, url: res.html_url };
479
+ }
480
+ // ---- Request reviewers on a PR (REST) ----
481
+ // Request one or more reviewers on a PR (POST .../pulls/:n/requested_reviewers with
482
+ // { reviewers: [login…], team_reviewers: [slug…] }). Needs a token with write/triage
483
+ // access; GitHub 422s if a login isn't a collaborator or is the PR author (the caller
484
+ // filters the author out). `teamSlugs` requests a whole team (a CODEOWNERS `@org/team`)
485
+ // WITHOUT expanding its membership — GitHub resolves the slug against the repo's org.
486
+ // Returns 201 with the updated PR body, which we don't need — the refreshed request
487
+ // state arrives on the next sync (reviewRequests are re-derived each sync).
488
+ export async function requestReviewers(token, owner, name, number, logins, teamSlugs = []) {
489
+ const body = { reviewers: logins };
490
+ if (teamSlugs.length > 0)
491
+ body.team_reviewers = teamSlugs;
492
+ await ghRestPostFor(token, `/repos/${owner}/${name}/pulls/${number}/requested_reviewers`, body);
493
+ }
494
+ // ---- Re-trigger a GitHub Actions workflow run ----
495
+ // Re-run a workflow run (per-account). `mode: 'failed'` reruns only the failed jobs
496
+ // (POST .../runs/:runId/rerun-failed-jobs); `mode: 'all'` reruns the whole run
497
+ // (POST .../runs/:runId/rerun). GitHub returns 201 with an EMPTY body and queues the
498
+ // run asynchronously; the caller has no run id to poll (the refreshed status arrives
499
+ // on the next sync). Uses the no-content POST helper so the empty success body isn't
500
+ // JSON-parsed. Needs a token with actions:write — fails loud (throws) otherwise,
501
+ // matching the other write helpers.
502
+ export async function rerunWorkflowRun(token, owner, name, runId, mode) {
503
+ const suffix = mode === 'failed' ? 'rerun-failed-jobs' : 'rerun';
504
+ await ghRestPostNoContent(token, `/repos/${owner}/${name}/actions/runs/${runId}/${suffix}`);
505
+ }
506
+ // ---- GitHub's native merge QUEUE (GraphQL only) -----------------------------------------
507
+ //
508
+ // House style in this file is REST-first. The merge queue forks to GraphQL because it has to:
509
+ // `enqueuePullRequest` / `dequeuePullRequest` are GA GraphQL mutations with NO REST
510
+ // equivalent, and queue presence is not inferable from anything REST returns — in particular
511
+ // `MergeStateStatus` has no QUEUED value, so a queued PR looks like any other (queued PRs here
512
+ // have read 'clean' and 'unknown').
513
+ //
514
+ // MEMBERSHIP AND ENTRY STATE ARE SYNCED (`pull_requests.in_merge_queue` +
515
+ // `merge_queue_entry_state`): the sync walk writes them, and every caller of the three functions
516
+ // below that holds a positive answer stamps them through `db/pr-merge-queue-stamp.ts`. POSITION
517
+ // and ETA are not: they change minute to minute and only the merge control renders them, so they
518
+ // are fetched live in GET /api/prs/:id/merge-options and never stored.
519
+ // `MergeQueueEntry.estimatedTimeToMerge` is an Int of SECONDS in the GitHub schema; the wire
520
+ // type (PrMergeQueueInfo.estimatedTimeToMergeMs) is milliseconds, so it is scaled here — the
521
+ // one place that assumption lives.
522
+ const SECONDS_TO_MS = 1000;
523
+ const MERGE_QUEUE_STATE_QUERY = /* GraphQL */ `
524
+ query MergeQueueState($owner: String!, $name: String!, $number: Int!) {
525
+ repository(owner: $owner, name: $name) {
526
+ pullRequest(number: $number) {
527
+ state
528
+ reviewDecision
529
+ isMergeQueueEnabled
530
+ isInMergeQueue
531
+ mergeQueueEntry {
532
+ position
533
+ state
534
+ estimatedTimeToMerge
535
+ enqueuedAt
536
+ }
537
+ }
538
+ }
539
+ }
540
+ `;
541
+ // Live merge-queue state for one PR. Returns null when the PR can't be read (deleted, or the
542
+ // token can't see it) — the caller renders "no merge queue" rather than failing the whole
543
+ // merge-options fetch.
544
+ export async function fetchMergeQueueState(token, owner, name, number) {
545
+ const gql = getGraphqlClientFor(token);
546
+ const res = await gql(MERGE_QUEUE_STATE_QUERY, {
547
+ owner,
548
+ name,
549
+ number,
550
+ });
551
+ const pr = res.repository?.pullRequest;
552
+ if (!pr)
553
+ return null;
554
+ const entry = pr.mergeQueueEntry;
555
+ return {
556
+ enabled: pr.isMergeQueueEnabled,
557
+ inQueue: pr.isInMergeQueue,
558
+ position: entry?.position ?? null,
559
+ state: entry?.state ?? null,
560
+ estimatedTimeToMergeMs: entry?.estimatedTimeToMerge != null ? entry.estimatedTimeToMerge * SECONDS_TO_MS : null,
561
+ enqueuedAt: entry?.enqueuedAt ?? null,
562
+ prState: pr.state,
563
+ reviewDecision: pr.reviewDecision,
564
+ };
565
+ }
566
+ const ENQUEUE_MUTATION = /* GraphQL */ `
567
+ mutation EnqueuePr($input: EnqueuePullRequestInput!) {
568
+ enqueuePullRequest(input: $input) {
569
+ mergeQueueEntry {
570
+ position
571
+ state
572
+ estimatedTimeToMerge
573
+ }
574
+ }
575
+ }
576
+ `;
577
+ const DEQUEUE_MUTATION = /* GraphQL */ `
578
+ mutation DequeuePr($input: DequeuePullRequestInput!) {
579
+ dequeuePullRequest(input: $input) {
580
+ mergeQueueEntry {
581
+ position
582
+ }
583
+ }
584
+ }
585
+ `;
586
+ /**
587
+ * Add the PR to its base branch's merge queue. `expectedHeadOid` pins the enqueue to the head
588
+ * the caller saw — GitHub rejects the mutation if the branch moved, the same consent anchor
589
+ * the direct merge uses (`sha` on PUT .../merge).
590
+ *
591
+ * `prNodeId` is the PR's GraphQL node id (pullRequests.githubNodeId), NOT its number.
592
+ */
593
+ /**
594
+ * Is this enqueue refusal GitHub saying "not yet" rather than "no"?
595
+ *
596
+ * ⚠ THIS EXISTS BECAUSE THE OPPOSITE ASSUMPTION KILLED REAL INTENTS. `enqueueWhenReady`'s
597
+ * comment asserted that checks do not gate entry ("AWAITING_CHECKS is a normal entry state; the
598
+ * queue runs them itself"), which is true of a queue configured to run its own checks and FALSE
599
+ * of a branch whose protection requires them before entry. On the reporting account 3 of 21
600
+ * armed intents died this way, and two of them were refused purely because CI had not finished:
601
+ *
602
+ * "Pull request 2 of 3 required status checks are in progress."
603
+ * "2 of 2 required status checks have not succeeded: 1 expected."
604
+ *
605
+ * A refusal like that is the exact condition arming exists to wait out. Treated as an error it
606
+ * burned a strike, and three strikes at a two-minute tick killed the intent about six minutes
607
+ * after the user armed it — with CI still running.
608
+ *
609
+ * ⚠ MATCHES THE CONDITION, NOT THE WORD "check". A refusal naming a rule the watcher can never
610
+ * satisfy on its own (a missing review, a draft, a protected-branch restriction) must still fail
611
+ * — waiting forever on those is its own bug. Failing statuses are deliberately INCLUDED as
612
+ * waitable: a red check is routinely re-run or fixed by a push, and a push disarms us anyway
613
+ * through the head pin, which is the honest exit.
614
+ */
615
+ export function isWaitableEnqueueRefusal(message) {
616
+ const m = message.toLowerCase();
617
+ if (!m.includes('status check') && !m.includes('required statuses'))
618
+ return false;
619
+ return (m.includes('in progress') ||
620
+ m.includes('have not succeeded') ||
621
+ m.includes('has not succeeded') ||
622
+ m.includes('expected') ||
623
+ m.includes('pending') ||
624
+ m.includes('queued') ||
625
+ m.includes('failing'));
626
+ }
627
+ export async function enqueuePullRequestOnQueue(token, prNodeId, expectedHeadOid) {
628
+ const gql = getGraphqlClientFor(token);
629
+ const input = { pullRequestId: prNodeId };
630
+ if (expectedHeadOid)
631
+ input.expectedHeadOid = expectedHeadOid;
632
+ const res = await gql(ENQUEUE_MUTATION, { input });
633
+ const entry = res.enqueuePullRequest?.mergeQueueEntry ?? null;
634
+ return {
635
+ position: entry?.position ?? null,
636
+ state: entry?.state ?? null,
637
+ estimatedTimeToMergeMs: entry?.estimatedTimeToMerge != null ? entry.estimatedTimeToMerge * SECONDS_TO_MS : null,
638
+ };
639
+ }
640
+ /** Remove the PR from the merge queue. `DequeuePullRequestInput.id` is the PR's node id. */
641
+ export async function dequeuePullRequestFromQueue(token, prNodeId) {
642
+ const gql = getGraphqlClientFor(token);
643
+ await gql(DEQUEUE_MUTATION, { input: { id: prNodeId } });
644
+ }
645
+ // ⚠ `reactors` takes NO `first:` — see github/queries.ts for the measured 44× cost cliff.
646
+ const REACTION_SUBJECT_FIELDS = /* GraphQL */ `
647
+ subject {
648
+ id
649
+ ... on Reactable {
650
+ viewerCanReact
651
+ reactionGroups {
652
+ content
653
+ viewerHasReacted
654
+ reactors {
655
+ totalCount
656
+ }
657
+ }
658
+ }
659
+ }
660
+ `;
661
+ const ADD_REACTION_MUTATION = /* GraphQL */ `
662
+ mutation AddReaction($input: AddReactionInput!) {
663
+ addReaction(input: $input) {
664
+ ${REACTION_SUBJECT_FIELDS}
665
+ }
666
+ }
667
+ `;
668
+ const REMOVE_REACTION_MUTATION = /* GraphQL */ `
669
+ mutation RemoveReaction($input: RemoveReactionInput!) {
670
+ removeReaction(input: $input) {
671
+ ${REACTION_SUBJECT_FIELDS}
672
+ }
673
+ }
674
+ `;
675
+ /**
676
+ * Toggle one reaction on one subject. `subjectNodeId` is the comment / review body's GitHub
677
+ * node id; `content` is the lowercase wire value, mapped to GitHub's enum here.
678
+ *
679
+ * Returns the subject's post-write group set. Adding a reaction the viewer already has (or
680
+ * removing one they never had) is idempotent on GitHub's side, so a double-click cannot
681
+ * desynchronise the count.
682
+ *
683
+ * ⚠ ASYMMETRY WITH THE READ PATH, and it is GitHub's, not ours. `fetchReactionsForNodes`
684
+ * feeds `noteBudget` from the `rateLimit` block it selects; this cannot, because `rateLimit`
685
+ * is a field on the `Query` root ONLY — GitHub's `Mutation` type does not expose it
686
+ * (verified by introspection), so there is no block to select here. Reading it back would
687
+ * mean plumbing the HTTP response headers out of the graphql client, which is not worth one
688
+ * budget observation per user click. The PROTECTIVE half is covered regardless: the caller
689
+ * classifies a failure through `isRateLimitError` and calls `noteLimited`, so a toggle that
690
+ * discovers the limit still teaches the shared budget and the next lookup skips itself.
691
+ */
692
+ export async function setReaction(token, subjectNodeId, content, add) {
693
+ const gql = getGraphqlClientFor(token);
694
+ const input = { subjectId: subjectNodeId, content: toGithubReactionContent(content) };
695
+ const res = await gql(add ? ADD_REACTION_MUTATION : REMOVE_REACTION_MUTATION, { input });
696
+ const payload = add ? res.addReaction : res.removeReaction;
697
+ const subject = payload?.subject ?? null;
698
+ if (!subject) {
699
+ throw new Error(`GitHub returned no subject for the reaction ${add ? 'add' : 'remove'} (it may still have applied)`);
700
+ }
701
+ return {
702
+ groups: summariseReactionGroups(subject.reactionGroups),
703
+ viewerCanReact: subject.viewerCanReact === true,
704
+ };
705
+ }
706
+ //# sourceMappingURL=mutations.js.map