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,7 @@
1
+ -- Correlated "does this PR have an event since <cutoff>" EXISTS lookups filter events by
2
+ -- pr_id (getTeamInsights open-PR staleness, getOpenPrs, new-since checks, feed joins). With
3
+ -- no pr_id index they fall back to events_account_idx and scan every account event per PR
4
+ -- (O(open PRs × events)) — the getTeamInsights Overview took ~9s on 14 watched repos. This
5
+ -- composite (pr_id, occurred_at) makes the lookup ~450× faster (also resolves the time bound
6
+ -- inside the index).
7
+ CREATE INDEX IF NOT EXISTS `events_pr_idx` ON `events` (`pr_id`, `occurred_at`);
@@ -0,0 +1,29 @@
1
+ -- Cross-org benchmark network (CORE, cloud-only, opt-in), Phase 0: the opt-in flag on
2
+ -- accounts + the aggregate-only weekly contributions table. See schema.sqlite.ts for the
3
+ -- full rationale (no PII, in_house/pierre excluded, written only by the firewalled rollup).
4
+ -- The Postgres baseline is maintained separately (migrations-pg/0019_*).
5
+ ALTER TABLE `accounts` ADD `benchmark_opt_in` integer DEFAULT false NOT NULL;
6
+ --> statement-breakpoint
7
+ CREATE TABLE `benchmark_contributions` (
8
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
9
+ `account_id` integer NOT NULL,
10
+ `vendor_kind` text NOT NULL,
11
+ `week_start` integer NOT NULL,
12
+ `threads` integer DEFAULT 0 NOT NULL,
13
+ `comments` integer DEFAULT 0 NOT NULL,
14
+ `acted_on` integer DEFAULT 0 NOT NULL,
15
+ `untouched` integer DEFAULT 0 NOT NULL,
16
+ `human_follow` integer DEFAULT 0 NOT NULL,
17
+ `oldest_untouched_days` integer,
18
+ `org_size_bucket` text NOT NULL,
19
+ `ml_metrics` text,
20
+ `schema_version` integer DEFAULT 1 NOT NULL,
21
+ `created_at` integer DEFAULT (unixepoch()) NOT NULL,
22
+ FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON UPDATE no action ON DELETE cascade
23
+ );
24
+ --> statement-breakpoint
25
+ CREATE UNIQUE INDEX `bench_contrib_uniq` ON `benchmark_contributions` (`account_id`,`vendor_kind`,`week_start`);
26
+ --> statement-breakpoint
27
+ CREATE INDEX `bench_contrib_account_idx` ON `benchmark_contributions` (`account_id`);
28
+ --> statement-breakpoint
29
+ CREATE INDEX `bench_contrib_cohort_idx` ON `benchmark_contributions` (`vendor_kind`,`week_start`);
@@ -0,0 +1,9 @@
1
+ -- Part A: deterministic "was this thread addressed?" upgrade. Store a graded addressed-confidence
2
+ -- + a machine reason tag + who resolved the thread, alongside the existing derivedState. All
3
+ -- additive (defaulted / nullable) so the backfill is a no-op — the next sync recomputes them.
4
+ -- The Postgres baseline is regenerated separately via `pnpm db:generate:pg`.
5
+ ALTER TABLE `review_threads` ADD `addressed_confidence` text DEFAULT 'none' NOT NULL;
6
+ --> statement-breakpoint
7
+ ALTER TABLE `review_threads` ADD `addressed_reason` text;
8
+ --> statement-breakpoint
9
+ ALTER TABLE `review_threads` ADD `resolved_by_login` text;
@@ -0,0 +1,6 @@
1
+ -- Resolution-latency support: record WHEN a review thread was first observed resolved. Nullable
2
+ -- (unknown for threads already resolved at first sight); stamped by sync only on a witnessed
3
+ -- unresolved→resolved transition. Additive — the backfill is a no-op (existing resolved threads
4
+ -- keep a null resolvedAt; the metric only counts resolves observed going forward).
5
+ -- The Postgres baseline is regenerated separately via `pnpm db:generate:pg`.
6
+ ALTER TABLE `review_threads` ADD `resolved_at` integer;
@@ -0,0 +1,5 @@
1
+ -- Review-pickup support: store the earliest review-request time (first ReviewRequestedEvent) so
2
+ -- we can trend request→first-review latency. Nullable (null = never requested / pre-existing PRs
3
+ -- until the next sync backfills it from the timeline). Additive.
4
+ -- The Postgres baseline is regenerated separately via `pnpm db:generate:pg`.
5
+ ALTER TABLE `pull_requests` ADD `first_review_requested_at` integer;
@@ -0,0 +1,25 @@
1
+ -- Cross-team full-text search index (CORE, no AI). One row per searchable text unit — a PR
2
+ -- (title + description), a review body, a review-comment (carrying its thread_id for deep-linking),
3
+ -- or a PR-comment. Populated inside persistPr() (delete-by-pr_id then insert) and backfilled from
4
+ -- already-stored data at startup. Denormalized account_id is the tenant anchor; matching is portable
5
+ -- case-insensitive substring (lower(body) LIKE …). FKs cascade so a repo/PR delete cleans up.
6
+ -- The Postgres baseline is maintained separately (migrations-pg/).
7
+ CREATE TABLE `search_index` (
8
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
9
+ `account_id` integer NOT NULL,
10
+ `repo_id` integer NOT NULL,
11
+ `pr_id` integer NOT NULL,
12
+ `kind` text NOT NULL,
13
+ `ref_id` integer NOT NULL,
14
+ `thread_id` integer,
15
+ `author_id` integer,
16
+ `body` text NOT NULL,
17
+ `created_at` integer NOT NULL,
18
+ FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON UPDATE no action ON DELETE cascade,
19
+ FOREIGN KEY (`repo_id`) REFERENCES `repos`(`id`) ON UPDATE no action ON DELETE cascade,
20
+ FOREIGN KEY (`pr_id`) REFERENCES `pull_requests`(`id`) ON UPDATE no action ON DELETE cascade,
21
+ FOREIGN KEY (`author_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
22
+ );
23
+ --> statement-breakpoint
24
+ CREATE INDEX `search_account_repo_idx` ON `search_index` (`account_id`,`repo_id`);--> statement-breakpoint
25
+ CREATE INDEX `search_pr_idx` ON `search_index` (`pr_id`);
@@ -0,0 +1,15 @@
1
+ -- Author indexes for the contributor popover (`getUserStats`).
2
+ --
3
+ -- Its four counts all drive from `pull_requests` (the only side carrying `account_id`) and
4
+ -- probe each child table by `pr_id`, so the work scaled with the ACCOUNT's PR count rather
5
+ -- than with the person being asked about: measured ~55ms on a 6k-PR local DB for a busy
6
+ -- reviewer AND for one with zero reviews. better-sqlite3 is synchronous, so that is 55ms of
7
+ -- blocked event loop per popover open, and it grows with the tenant.
8
+ --
9
+ -- No table had an `author_id` index before this. Indexing the selective side lets the planner
10
+ -- drive from the user instead. Purely additive — no column or constraint changes.
11
+ -- The Postgres baseline is regenerated separately via `pnpm db:generate:pg`.
12
+ CREATE INDEX IF NOT EXISTS `pr_author_idx` ON `pull_requests` (`author_id`);--> statement-breakpoint
13
+ CREATE INDEX IF NOT EXISTS `rv_author_idx` ON `reviews` (`author_id`);--> statement-breakpoint
14
+ CREATE INDEX IF NOT EXISTS `prc_author_idx` ON `pr_comments` (`author_id`);--> statement-breakpoint
15
+ CREATE INDEX IF NOT EXISTS `rc_author_idx` ON `review_comments` (`author_id`);
@@ -0,0 +1,37 @@
1
+ -- Auto-merge intents ("arm it and walk away"), SQLite / local mode.
2
+ --
3
+ -- One row per (account, PR) recording a STANDING INTENT to merge once the blockers clear.
4
+ -- This is Pierre-side, not GitHub's native auto-merge, so it works on repos that don't enable
5
+ -- it and can offer a rebase-from-trunk step GitHub can't.
6
+ --
7
+ -- The safety property is `expected_head_oid`: arming is consent to merge THAT code. A new push
8
+ -- moves the head, the watcher sees the mismatch and disarms ('disarmed_head_moved') rather than
9
+ -- merging commits nobody looked at. `expires_at` is the second backstop, so an intent that never
10
+ -- becomes mergeable dies instead of lingering for weeks.
11
+ --
12
+ -- FKs CASCADE (unlike the rest of the core schema, which has none) so a repo/PR delete and an
13
+ -- account erasure clean this up without touching the intricate hand-ordered delete in
14
+ -- `deleteRepo`. The Postgres twin is migrations-pg/0025_auto_merge_requests.sql.
15
+ CREATE TABLE IF NOT EXISTS `auto_merge_requests` (
16
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
17
+ `account_id` integer NOT NULL REFERENCES `accounts`(`id`) ON DELETE cascade,
18
+ `pr_id` integer NOT NULL REFERENCES `pull_requests`(`id`) ON DELETE cascade,
19
+ `merge_method` text NOT NULL,
20
+ `update_strategy` text NOT NULL,
21
+ `expected_head_oid` text NOT NULL,
22
+ `state` text NOT NULL,
23
+ `armed_at` integer NOT NULL,
24
+ `expires_at` integer NOT NULL,
25
+ `last_checked_at` integer,
26
+ `last_reason` text,
27
+ `created_at` integer NOT NULL DEFAULT (unixepoch()),
28
+ `updated_at` integer NOT NULL DEFAULT (unixepoch())
29
+ );
30
+ --> statement-breakpoint
31
+ -- One armed request per PR per tenant — the upsert conflict target. Re-arming OVERWRITES.
32
+ CREATE UNIQUE INDEX IF NOT EXISTS `amr_account_pr` ON `auto_merge_requests` (`account_id`,`pr_id`);
33
+ --> statement-breakpoint
34
+ CREATE INDEX IF NOT EXISTS `amr_account_idx` ON `auto_merge_requests` (`account_id`);
35
+ --> statement-breakpoint
36
+ -- The watcher's scan: "every still-armed row".
37
+ CREATE INDEX IF NOT EXISTS `amr_state_idx` ON `auto_merge_requests` (`state`);
@@ -0,0 +1,44 @@
1
+ -- Default-branch status ("is trunk green?"), SQLite / local mode.
2
+ --
3
+ -- Everything else in this app is PR-shaped, so the one thing that invalidates EVERY open PR's
4
+ -- CI at once — a broken default branch — had nowhere to live. Two additive pieces:
5
+ --
6
+ -- 1. Four nullable columns on `repos`: a snapshot of the default branch head. Kept SEPARATE
7
+ -- from the existing `default_branch` column (written by the activity sync from GraphQL
8
+ -- defaultBranchRef.name for the maintainer inference) so the two syncs can't silently
9
+ -- clobber each other's freshness expectations.
10
+ -- 2. `branch_commits`: the recent commits ON that branch with their observed CI state. This
11
+ -- is NOT derivable from the existing `commits` table, which is PR-scoped — a squash-merged
12
+ -- PR never appears there under its trunk SHA.
13
+ --
14
+ -- `branch_commits` FKs CASCADE so a repo delete / account erasure cleans up automatically.
15
+ -- Postgres twin: migrations-pg/0026_branch_status.sql.
16
+ ALTER TABLE `repos` ADD `default_branch_name` text;
17
+ --> statement-breakpoint
18
+ ALTER TABLE `repos` ADD `default_branch_head_sha` text;
19
+ --> statement-breakpoint
20
+ ALTER TABLE `repos` ADD `default_branch_ci_status` text;
21
+ --> statement-breakpoint
22
+ ALTER TABLE `repos` ADD `default_branch_updated_at` integer;
23
+ --> statement-breakpoint
24
+ CREATE TABLE IF NOT EXISTS `branch_commits` (
25
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
26
+ `account_id` integer NOT NULL REFERENCES `accounts`(`id`) ON DELETE cascade,
27
+ `repo_id` integer NOT NULL REFERENCES `repos`(`id`) ON DELETE cascade,
28
+ `sha` text NOT NULL,
29
+ `message_headline` text NOT NULL,
30
+ `author_user_id` integer REFERENCES `users`(`id`),
31
+ `author_name` text,
32
+ `author_avatar_url` text,
33
+ `committed_at` integer NOT NULL,
34
+ `ci_status` text,
35
+ `created_at` integer NOT NULL DEFAULT (unixepoch())
36
+ );
37
+ --> statement-breakpoint
38
+ -- Idempotent upsert target — a re-sync of the same window updates CI state in place.
39
+ CREATE UNIQUE INDEX IF NOT EXISTS `bc_account_repo_sha` ON `branch_commits` (`account_id`,`repo_id`,`sha`);
40
+ --> statement-breakpoint
41
+ -- The read: one repo's window, newest first.
42
+ CREATE INDEX IF NOT EXISTS `bc_account_repo_time` ON `branch_commits` (`account_id`,`repo_id`,`committed_at`);
43
+ --> statement-breakpoint
44
+ CREATE INDEX IF NOT EXISTS `bc_account_idx` ON `branch_commits` (`account_id`);
@@ -0,0 +1,10 @@
1
+ -- PR review decision, SQLite / local mode.
2
+ --
3
+ -- `merge_state_status = 'blocked'` says branch protection is unmet but never WHY, so every
4
+ -- merge surface could only render a shrug. GitHub's `PullRequest.reviewDecision` names the
5
+ -- review half of it ('review_required' / 'changes_requested' / 'approved'), which is what lets
6
+ -- the collapsed merge verdict say something actionable.
7
+ --
8
+ -- Additive + nullable, so the backfill is a no-op: the next sync populates it. Stored lowercased
9
+ -- to match this schema's enum convention. Postgres twin: migrations-pg/0027_pr_review_decision.sql.
10
+ ALTER TABLE `pull_requests` ADD `review_decision` text;
@@ -0,0 +1,38 @@
1
+ -- Default-branch commit DETAIL, SQLite / local mode. Two additive nullable columns on
2
+ -- `branch_commits` plus one index on `pull_requests`.
3
+ --
4
+ -- The strip added in 0039 answers "is trunk green?" with a dot per commit. Both halves here
5
+ -- answer the two questions a red dot immediately raises:
6
+ --
7
+ -- 1. `failing_checks` — WHICH checks were failing on that commit (name, state, url, workflow
8
+ -- name). Failures ONLY, never the passing contexts, so a green commit stores NULL and a
9
+ -- healthy repo's rows are unchanged in size. Deliberately NOT a second copy on `repos`: the
10
+ -- head commit is the first node of the same history walk, so it already has a row here, and
11
+ -- the read derives the repo-level summary by matching `repos.default_branch_head_sha`. One
12
+ -- writer, one reader, one invariant.
13
+ --
14
+ -- NOT lean-gated (unlike `pull_requests.check_runs`): names-only metadata, capped per commit
15
+ -- by the writer, and a trunk commit belongs to no PR so there is no hydrate-on-demand path
16
+ -- to be lazy INTO. Gating it would delete the feature in cloud. Same reasoning (and same
17
+ -- column name, though a different shape) as `ci_status_events.failing_checks`.
18
+ --
19
+ -- 2. `pr_number` — the PR the commit landed from (GraphQL Commit.associatedPullRequests), or
20
+ -- NULL for a direct push to trunk. A plain number, NOT a `pull_requests` FK: the PR is often
21
+ -- not synced when the commit is observed (squash-merged before the backfill window, or the
22
+ -- repo was added later), a stored id would go stale on the next PR re-sync, and a real FK
23
+ -- would drag this table into both delete paths. The read layer resolves number → local id
24
+ -- per request, scoped by (account_id, repo_id), because a PR number is unique only within a
25
+ -- repo.
26
+ --
27
+ -- No index on either new column — both are read out of an already-fetched row, never used as a
28
+ -- predicate. The `pull_requests` index IS needed: the number→id resolution runs on a route the
29
+ -- Activity console hits on every mount, and without it the planner narrows to the repo via
30
+ -- `pr_repo_idx` and then scans that repo's entire PR set.
31
+ --
32
+ -- No backfill: the branch sync re-fetches the same commit window every tick and upserts on
33
+ -- (account_id, repo_id, sha), so existing rows gain both values on the next sync. Until then the
34
+ -- read resolves NULL → [] / null and the strip renders exactly as it does today.
35
+ -- Postgres twin: migrations-pg/0028_branch_commit_detail.sql.
36
+ ALTER TABLE `branch_commits` ADD `failing_checks` text;--> statement-breakpoint
37
+ ALTER TABLE `branch_commits` ADD `pr_number` integer;--> statement-breakpoint
38
+ CREATE INDEX IF NOT EXISTS `pr_account_repo_number_idx` ON `pull_requests` (`account_id`,`repo_id`,`number`);
@@ -0,0 +1,176 @@
1
+ -- THE BOT OBJECT BECOMES PER REPO. SQLite / local mode; Postgres twin:
2
+ -- migrations-pg/0029_repo_reviewers.sql. Migration 0043 then splits the ACTOR grain back out of
3
+ -- the table this one creates — read the two together, they are one change in two steps:
4
+ --
5
+ -- 0042 RE-KEY (account, author) → (account, repo, author) columns unchanged
6
+ -- 0043 NORMALISE the actor-grain columns → their own table `account_reviewers`
7
+ --
8
+ -- Keeping them separate is deliberate: the re-key is a fan-out with a footprint rule and a
9
+ -- careful backfill, the normalisation is a lift of three columns and a price. Fused into one
10
+ -- file, a reader cannot tell which statement is doing which job.
11
+ --
12
+ -- `bot_review_classification` answered "is this login a bot, and what kind" once per ACCOUNT.
13
+ -- `repo_reviewers` answers it once per (account, REPO, author), and that row IS the bot object.
14
+ --
15
+ -- WHY THE REPO IS THE KEY. A bot is installed per repository — GitHub Apps are installed on
16
+ -- repos, CI configs live in repos — while a team is a bag of repos someone can re-bag tomorrow.
17
+ -- Keying the judgement on a team made the answer move when team membership was edited and forced
18
+ -- an inheritance chain (team row → default row → auto-detect) whose null-means-inherit rules
19
+ -- leaked into every read. Keying on the repo removes the chain: one row per repo, nothing to fall
20
+ -- back to, nothing to merge, and NO DEDUPLICATION anywhere — a vendor on six repos is six rows,
21
+ -- which is the intended display.
22
+ --
23
+ -- IT REPLACES THE OLD TABLE RATHER THAN EXTENDING IT. `bot_review_classification` is DROPPED at
24
+ -- the bottom: leaving it would leave a second, differently-keyed answer to the same question
25
+ -- sitting in every database, which is exactly how two surfaces come to disagree.
26
+ --
27
+ -- ── THE BACKFILL, AND WHY IT IS ASYMMETRIC ──────────────────────────────────────────────────
28
+ -- MANUAL rows fan out to EVERY repo in the account. A human judgement is the only thing here
29
+ -- that cannot be regenerated, it was made account-wide, and fanning it out reproduces that
30
+ -- meaning exactly — every repo row agrees, which is the invariant ("only a human edit should ever
31
+ -- make two of an actor's rows disagree") stated as data.
32
+ --
33
+ -- AUTO rows land only on repos where the actor ACTUALLY HAS A FOOTPRINT — submitted a review,
34
+ -- opened an inline thread, or left a PR comment there. Two reasons: an auto verdict is cheap and
35
+ -- re-derived on the next classification pass, so nothing is lost if this misses; and a row for a
36
+ -- repo the reviewer has never touched is not a bot object, it is a fabricated one. On a real
37
+ -- account that is the difference between a few hundred rows and actors × repos of noise.
38
+ --
39
+ -- The footprint CTE is MATERIALIZED on purpose: without it SQLite may re-evaluate the union for
40
+ -- every (row, repo) pair, turning a one-off migration into minutes of nested scans.
41
+ --
42
+ -- The role seed at the bottom is the one judgement this migration adds. Static analysis /
43
+ -- coverage / lint (SonarQube, Codecov, Hound) posts review comments and IS automated, but it is
44
+ -- not reviewing, and counting it as a reviewer is what makes the Bot-ROI numbers lie. AUTO rows
45
+ -- for those logins are re-roled in place so an account that already classified SonarQube (today
46
+ -- it resolves to `in_house` via the githubType step — the exact miscount `role` fixes) reads
47
+ -- correctly on first load. MANUAL rows are never touched.
48
+ --
49
+ -- ── TENANCY IS STRUCTURAL HERE, NOT A ROUTE CHECK ───────────────────────────────────────────
50
+ -- `repo_id` is the FIRST column in this schema that arrives in a REQUEST BODY rather than from
51
+ -- sync (`RepoReviewerJudgementBody.repoId` — the row IS the object, so a judgement names one).
52
+ -- A plain `REFERENCES repos(id)` would happily accept (account 2, repo 10) where repo 10 belongs
53
+ -- to account 1: both halves are individually valid, and the only thing standing between that and
54
+ -- a row written into another tenant's repo would be one hand-written predicate in one handler.
55
+ -- So the FK is COMPOSITE — `(repo_id, account_id) REFERENCES repos(id, account_id)` — which the
56
+ -- unique index below exists to make legal (SQLite requires a UNIQUE index over the parent key
57
+ -- columns; Postgres accepts the same plain unique index, verified on 16.13). The cross-account
58
+ -- insert then fails with "FOREIGN KEY constraint failed" in BOTH dialects, in every code path,
59
+ -- including a hand-written one. It replaces the single-column repo FK rather than joining it:
60
+ -- the composite subsumes it, and two overlapping FKs is two things to keep in step.
61
+ --
62
+ -- THE CONSTRAINT IS NAMED, and the name is the one `schema.sqlite.ts` declares. SQLite parses and
63
+ -- stores a `CONSTRAINT <name> FOREIGN KEY` clause but never reports it (its violation message is
64
+ -- the bare "FOREIGN KEY constraint failed" and `PRAGMA foreign_key_list` has no name column), so
65
+ -- here it is documentation that at least matches the stored DDL; in Postgres the same name is
66
+ -- what the violation message quotes. Previously neither file named it, so drizzle's `name:` field
67
+ -- pointed at nothing: pg auto-named the constraint `repo_reviewers_repo_id_account_id_fkey`.
68
+ --
69
+ -- ── ON RE-RUNNING THIS FILE ─────────────────────────────────────────────────────────────────
70
+ -- Every statement is written IF NOT EXISTS / ON CONFLICT DO NOTHING, and the legacy table is
71
+ -- stubbed below, so a hand-replay is a no-op rather than a crash. That is the SQLite answer to
72
+ -- the pg twin's `to_regclass` guard, which exists for the same reason: this file ends by dropping
73
+ -- the table its own backfill reads.
74
+ -- The parent key for the composite FK, and nothing else — `repos.id` is already the PK, so this
75
+ -- index is redundant for lookups and exists purely so the FK is expressible.
76
+ CREATE UNIQUE INDEX IF NOT EXISTS `repos_id_account` ON `repos` (`id`,`account_id`);--> statement-breakpoint
77
+ -- Unrelated to the FK, and here because THIS migration is where it starts costing: `review_threads
78
+ -- .original_commenter_id` has never had an index, and it is one of the three columns the footprint
79
+ -- CTE below unions on (a full scan of `review_threads`) AND the column the per-repo footprint
80
+ -- counts (`RepoReviewerFootprint.threads`) group by on every bot listing.
81
+ CREATE INDEX IF NOT EXISTS `thread_original_commenter_idx` ON `review_threads` (`original_commenter_id`);--> statement-breakpoint
82
+ -- THE LEGACY SOURCE, STUBBED. On every database this migration is meant for, the real table is
83
+ -- already there and this is a no-op. It exists so the backfill below is still a legal, empty
84
+ -- statement on a database where the table has already gone — a hand-replay of this file, or a
85
+ -- machine that ran an earlier draft of it while this branch was unpushed. The stub is dropped at
86
+ -- the bottom along with the real thing, so it leaves nothing behind either way.
87
+ CREATE TABLE IF NOT EXISTS `bot_review_classification` (
88
+ `account_id` integer,
89
+ `author_user_id` integer,
90
+ `automated` integer,
91
+ `kind` text,
92
+ `label` text,
93
+ `confidence` text,
94
+ `source` text,
95
+ `reasons_json` text,
96
+ `updated_at` integer
97
+ );--> statement-breakpoint
98
+ -- `kind` / `label` / `identity_source` ARE CREATED HERE AND REMOVED AGAIN IN 0043. This step is a
99
+ -- pure re-key: every column of the old row is carried across unchanged so the two migrations can
100
+ -- be read (and reasoned about) one at a time. 0043 is where they become one row per actor.
101
+ CREATE TABLE IF NOT EXISTS `repo_reviewers` (
102
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
103
+ `account_id` integer NOT NULL,
104
+ `repo_id` integer NOT NULL,
105
+ `author_user_id` integer NOT NULL,
106
+ `automated` integer NOT NULL,
107
+ `kind` text,
108
+ `label` text,
109
+ `identity_source` text DEFAULT 'auto' NOT NULL,
110
+ `role` text DEFAULT 'review' NOT NULL,
111
+ `confidence` text NOT NULL,
112
+ `source` text NOT NULL,
113
+ `reasons_json` text,
114
+ `updated_at` integer DEFAULT (unixepoch()) NOT NULL,
115
+ FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON UPDATE no action ON DELETE cascade,
116
+ CONSTRAINT `repo_reviewers_repo_account_fk` FOREIGN KEY (`repo_id`,`account_id`) REFERENCES `repos`(`id`,`account_id`) ON UPDATE no action ON DELETE cascade,
117
+ FOREIGN KEY (`author_user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
118
+ );--> statement-breakpoint
119
+ CREATE UNIQUE INDEX IF NOT EXISTS `repo_reviewers_account_repo_author` ON `repo_reviewers` (`account_id`,`repo_id`,`author_user_id`);--> statement-breakpoint
120
+ CREATE INDEX IF NOT EXISTS `repo_reviewers_account_repo_idx` ON `repo_reviewers` (`account_id`,`repo_id`);--> statement-breakpoint
121
+ CREATE INDEX IF NOT EXISTS `repo_reviewers_account_author_idx` ON `repo_reviewers` (`account_id`,`author_user_id`);--> statement-breakpoint
122
+ WITH footprint AS MATERIALIZED (
123
+ SELECT DISTINCT p.repo_id AS repo_id, x.uid AS author_user_id
124
+ FROM pull_requests p
125
+ JOIN (
126
+ SELECT pr_id, author_id AS uid FROM reviews WHERE author_id IS NOT NULL
127
+ UNION ALL
128
+ SELECT pr_id, original_commenter_id AS uid FROM review_threads WHERE original_commenter_id IS NOT NULL
129
+ UNION ALL
130
+ SELECT pr_id, author_id AS uid FROM pr_comments WHERE author_id IS NOT NULL
131
+ ) x ON x.pr_id = p.id
132
+ )
133
+ INSERT INTO `repo_reviewers`
134
+ (`account_id`, `repo_id`, `author_user_id`, `automated`, `kind`, `label`, `identity_source`,
135
+ `role`, `confidence`, `source`, `reasons_json`, `updated_at`)
136
+ -- `identity_source` is derived from the old row's `source` because the old model had only ONE
137
+ -- provenance field covering both grains: a manual row's `kind`/`label` WERE a human's opinion,
138
+ -- and importing them as 'auto' would let the next classification pass silently revert a vendor
139
+ -- correction someone made by hand.
140
+ SELECT b.account_id, r.id, b.author_user_id, b.automated, b.kind, b.label,
141
+ CASE WHEN b.source = 'manual' THEN 'manual' ELSE 'auto' END,
142
+ 'review', b.confidence, b.source, b.reasons_json, b.updated_at
143
+ FROM bot_review_classification b
144
+ JOIN repos r ON r.account_id = b.account_id
145
+ WHERE (b.source = 'manual'
146
+ OR EXISTS (
147
+ SELECT 1 FROM footprint f
148
+ WHERE f.repo_id = r.id AND f.author_user_id = b.author_user_id
149
+ ))
150
+ -- The `WHERE true` disambiguator SQLite needs before ON CONFLICT in an `INSERT … SELECT … FROM`
151
+ -- is already satisfied by the real WHERE above (sqlite.org/lang_upsert.html §"Parsing Ambiguity");
152
+ -- Postgres has no such ambiguity, which is why the twin's clause reads identically without it.
153
+ --
154
+ -- ON CONFLICT DO NOTHING never fires on a database this migration is meant for: the legacy table
155
+ -- is unique on (account_id, author_user_id), so one row per (account, repo, author) comes out. It
156
+ -- is here for the state an unpushed rewrite can leave behind — a machine that ran the deleted
157
+ -- team-keyed draft has up to one row per (account, TEAM, author), which collides. The winner is
158
+ -- then arbitrary, which is the right answer for a draft artifact and the wrong answer to reach
159
+ -- for anywhere else.
160
+ ON CONFLICT (`account_id`, `repo_id`, `author_user_id`) DO NOTHING;--> statement-breakpoint
161
+ UPDATE `repo_reviewers`
162
+ SET `role` = 'quality_check'
163
+ WHERE `source` <> 'manual'
164
+ AND `author_user_id` IN (
165
+ SELECT `id` FROM `users` WHERE lower(`github_login`) IN (
166
+ 'sonarqubecloud', 'sonarqubecloud[bot]',
167
+ 'sonarcloud', 'sonarcloud[bot]',
168
+ 'codecov', 'codecov[bot]',
169
+ 'codeclimate', 'codeclimate[bot]',
170
+ 'codefactor-io', 'codefactor-io[bot]',
171
+ 'houndci-bot', 'houndci-bot[bot]',
172
+ 'coveralls', 'coveralls[bot]',
173
+ 'codacy-bot', 'codacy-bot[bot]'
174
+ )
175
+ );--> statement-breakpoint
176
+ DROP TABLE IF EXISTS `bot_review_classification`;
@@ -0,0 +1,131 @@
1
+ -- NORMALISE THE ACTOR GRAIN OUT OF `repo_reviewers`. SQLite / local mode; Postgres twin:
2
+ -- migrations-pg/0030_account_reviewers.sql. Second half of the change 0042 starts:
3
+ --
4
+ -- 0042 RE-KEY (account, author) → (account, repo, author) columns unchanged
5
+ -- 0043 NORMALISE the actor-grain columns → their own table `account_reviewers`
6
+ --
7
+ -- ── WHAT WAS WRONG WITH LEAVING THEM ON THE REPO ROW ────────────────────────────────────────
8
+ -- After 0042, `kind`, `label` and `identity_source` sit on every one of an actor's repo rows,
9
+ -- REPLICATED, held consistent by convention with no constraint anywhere. That is precisely the
10
+ -- hazard that giving cost its own storage was meant to eliminate — left in place for three other
11
+ -- columns — and it was the source of three standing obligations on code that has not been written
12
+ -- yet:
13
+ -- 1. creating a NEW repo row for an actor had to SEED all three from its siblings, or the
14
+ -- newest repo rendered the vendor differently from the rest;
15
+ -- 2. persist() had to gate on TWO different provenance flags sitting side by side in one row
16
+ -- (`identity_source` for kind/label, `source` for automated/role), and confusing them
17
+ -- reverts a human's vendor correction or freezes auto-detection account-wide;
18
+ -- 3. the account-wide identity the wire serves was documented as "a straight read of any one of
19
+ -- them" — which silently ELECTS A WINNER the moment two rows disagree, and no tie-break can
20
+ -- make the losing rows editable or even visible.
21
+ -- One row per actor deletes all three. There is no seeding (nothing to seed from), no second
22
+ -- provenance flag in the same row (they are on different tables now), and no election (there is
23
+ -- one row).
24
+ --
25
+ -- ── COST IS FOLDED IN, NOT GIVEN A THIRD TABLE ──────────────────────────────────────────────
26
+ -- A price is just another actor-level property. A separate `reviewer_costs` keyed on the same two
27
+ -- columns would be joined at every call site — this table with extra steps. ONE TABLE PER GRAIN
28
+ -- is the clearest possible statement of the model, and it is the same argument that moves
29
+ -- kind/label here.
30
+ --
31
+ -- `monthly_cents` IS THEREFORE NULLABLE, where a standalone cost table had it NOT NULL. That was
32
+ -- the point there: "no price" was "no row", so clearing a price was a DELETE and there was no
33
+ -- third state. A row that also carries identity exists for reasons that have nothing to do with
34
+ -- money, so the column must be nullable.
35
+ --
36
+ -- DO NOT REINTRODUCE THE OLD FEAR. The bug class that made nullable money dangerous was
37
+ -- nullable-means-INHERIT: NULL meant "fall through to the team-0 row", so 0 ("free HERE") and
38
+ -- NULL ("ask my parent") had to be told apart with `??` and never `||` — one character from a
39
+ -- silently wrong price. THERE IS NO INHERITANCE ANYWHERE ANY MORE. NULL means "no price set" and
40
+ -- 0 means "free": two states, no chain, no fallback, nothing to resolve. Clearing a price is
41
+ -- `SET monthly_cents = NULL`, not a DELETE — deleting the row would take the identity with it.
42
+ --
43
+ -- ── WHY `account_reviewers` ─────────────────────────────────────────────────────────────────
44
+ -- Named for its KEY, exactly as `repo_reviewers` is, so the two table names ARE the statement of
45
+ -- the model: one row per (account, author) here, one per (account, repo, author) there. It is
46
+ -- deliberately not named after any single fact it holds — `reviewer_identities` would make
47
+ -- `monthly_cents` look like a stray column, and `reviewer_costs` made `kind`/`label` look the
48
+ -- same way. The table is defined by its grain, so the next actor-level fact (a plan tier, a
49
+ -- contract end date) lands here without renaming anything.
50
+ --
51
+ -- ── REPLAY ──────────────────────────────────────────────────────────────────────────────────
52
+ -- Everything down to the DROP is IF NOT EXISTS / ON CONFLICT DO NOTHING. The three DROP COLUMNs
53
+ -- are NOT re-runnable: SQLite has no `DROP COLUMN IF EXISTS` (the pg twin uses one). drizzle's
54
+ -- journal never re-runs an applied migration, so that is a hand-replay limitation only, and it is
55
+ -- stated rather than hidden.
56
+ --
57
+ -- ⚠ WHAT THE COMMITTED DRAFT'S OWN COLUMNS DO *NOT* CARRY, and why the omission is deliberate.
58
+ -- The superseded 0042/0043 on this branch added `team_id`, `role` and `cost_monthly_cents` to
59
+ -- `bot_review_classification`. A database at the 0041 state — every fresh clone, CI, and every
60
+ -- peer — has NONE of those columns, and a single .sql file cannot reference a column that may or
61
+ -- may not exist (SQLite has no conditional DDL and would fail to PARSE the statement, not skip
62
+ -- it). So 0042 reads only the pre-draft column set and writes `role` as its default.
63
+ -- Consequence, scoped honestly: on a machine that ran the superseded draft, a per-team `role`
64
+ -- override and a price typed INLINE are not carried. Prices that came from the legacy
65
+ -- `pro_settings.bot_cost_json` blob are unaffected — pro 0019 re-imports them, and the blob is
66
+ -- never dropped. That leaves only values typed into the one-day-old inline editor, on unpushed
67
+ -- dev machines, which is a set of two on the author's box and empty everywhere else; both were
68
+ -- preserved by hand. Contorting this file for a state that exists on one machine and can never
69
+ -- occur again would make it wrong for every machine that matters.
70
+ --
71
+ -- ⚠ DRAFT-STATE HOUSEKEEPING — DELETE BEFORE THIS BRANCH MERGES. `reviewer_costs` was created by
72
+ -- an earlier draft of 0043 while this branch was unpushed and never existed in any released
73
+ -- version. The stub + carry-over + drop below exist ONLY so a machine that ran that draft keeps
74
+ -- the prices it typed. On every other database the stub is created empty, carries nothing, and is
75
+ -- dropped again. Once no such machine is left, delete these three statements.
76
+ CREATE TABLE IF NOT EXISTS `reviewer_costs` (
77
+ `account_id` integer,
78
+ `author_user_id` integer,
79
+ `monthly_cents` integer
80
+ );--> statement-breakpoint
81
+ CREATE TABLE IF NOT EXISTS `account_reviewers` (
82
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
83
+ `account_id` integer NOT NULL,
84
+ `author_user_id` integer NOT NULL,
85
+ `kind` text,
86
+ `label` text,
87
+ `identity_source` text DEFAULT 'auto' NOT NULL,
88
+ `monthly_cents` integer,
89
+ `updated_at` integer DEFAULT (unixepoch()) NOT NULL,
90
+ FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON UPDATE no action ON DELETE cascade,
91
+ FOREIGN KEY (`author_user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
92
+ );--> statement-breakpoint
93
+ CREATE UNIQUE INDEX IF NOT EXISTS `account_reviewers_account_author` ON `account_reviewers` (`account_id`,`author_user_id`);--> statement-breakpoint
94
+ -- ONE ROW PER ACTOR, LIFTED OFF WHICHEVER REPO ROW IS MOST AUTHORITATIVE. After 0042 an actor's
95
+ -- rows agree by construction, so on a freshly migrated database any of them would do; the
96
+ -- tie-break exists because this is exactly the "elect a winner" step the new shape abolishes, and
97
+ -- doing it ONCE, here, with a stated rule is the honest way to abolish it: prefer a MANUAL
98
+ -- identity (a human named this thing), then the most recently updated row.
99
+ --
100
+ -- The `max(...)` in the subquery is not decoration. SQLite's documented bare-columns rule — when
101
+ -- a query has exactly one min()/max() aggregate, the other bare columns are taken FROM THE ROW
102
+ -- THAT MATCHED — is what makes kind/label/identity_source/updated_at come from one single row
103
+ -- rather than four independent aggregates. The pg twin spells the same rule as DISTINCT ON with
104
+ -- an ORDER BY; different syntax, identical selection.
105
+ INSERT INTO `account_reviewers`
106
+ (`account_id`, `author_user_id`, `kind`, `label`, `identity_source`, `updated_at`)
107
+ SELECT `account_id`, `author_user_id`, `kind`, `label`, `identity_source`, `updated_at`
108
+ FROM (
109
+ SELECT `account_id`, `author_user_id`, `kind`, `label`, `identity_source`, `updated_at`,
110
+ max((`identity_source` = 'manual') * 100000000000 + `updated_at`) AS pick
111
+ FROM `repo_reviewers`
112
+ GROUP BY `account_id`, `author_user_id`
113
+ )
114
+ WHERE true
115
+ ON CONFLICT (`account_id`, `author_user_id`) DO NOTHING;--> statement-breakpoint
116
+ UPDATE `account_reviewers`
117
+ SET `monthly_cents` = (
118
+ SELECT rc.`monthly_cents` FROM `reviewer_costs` rc
119
+ WHERE rc.`account_id` = `account_reviewers`.`account_id`
120
+ AND rc.`author_user_id` = `account_reviewers`.`author_user_id`)
121
+ WHERE EXISTS (
122
+ SELECT 1 FROM `reviewer_costs` rc
123
+ WHERE rc.`account_id` = `account_reviewers`.`account_id`
124
+ AND rc.`author_user_id` = `account_reviewers`.`author_user_id`);--> statement-breakpoint
125
+ DROP TABLE IF EXISTS `reviewer_costs`;--> statement-breakpoint
126
+ -- The point of the whole file: after this, a repo row can say nothing about WHO the actor is.
127
+ -- `RepoReviewer` on the wire exposes no identity field and `ReviewerIdentity` exposes no
128
+ -- judgement field, and now the storage says the same thing.
129
+ ALTER TABLE `repo_reviewers` DROP COLUMN `kind`;--> statement-breakpoint
130
+ ALTER TABLE `repo_reviewers` DROP COLUMN `label`;--> statement-breakpoint
131
+ ALTER TABLE `repo_reviewers` DROP COLUMN `identity_source`;