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,127 @@
1
+ -- THE BOT OBJECT BECOMES PER REPO — the Postgres twin of the sqlite migration
2
+ -- 0042_repo_reviewers. Hand-written additive (like 0027/0028), idempotent. Migration 0030 then
3
+ -- splits the ACTOR grain back out; read the two together, they are one change in two steps:
4
+ --
5
+ -- 0029 RE-KEY (account, author) → (account, repo, author) columns unchanged
6
+ -- 0030 NORMALISE the actor-grain columns → their own table `account_reviewers`
7
+ --
8
+ -- `bot_review_classification` answered "is this login a bot, and what kind" once per ACCOUNT.
9
+ -- `repo_reviewers` answers it once per (account, REPO, author), and that row IS the bot object.
10
+ -- A bot is installed per repository; a team is a bag of repos someone can re-bag tomorrow. There
11
+ -- is no team key, no inheritance chain, no merge and NO DEDUPLICATION — a vendor running on six
12
+ -- repos is six rows, which is the intended display.
13
+ --
14
+ -- The old table is DROPPED at the bottom rather than left behind: a second, differently-keyed
15
+ -- answer to the same question sitting in every database is exactly how two surfaces disagree.
16
+ --
17
+ -- `kind` / `label` / `identity_source` ARE CREATED HERE AND REMOVED AGAIN IN 0030. This step is a
18
+ -- pure re-key — every column of the old row is carried across unchanged — so that the fan-out and
19
+ -- the normalisation can be read one at a time.
20
+ --
21
+ -- TENANCY IS STRUCTURAL. `repo_id` is the first column here that arrives in a REQUEST BODY
22
+ -- rather than from sync, and a plain `REFERENCES repos(id)` accepts (account 2, repo 10) where
23
+ -- repo 10 belongs to account 1. The FK is therefore COMPOSITE against `repos(id, account_id)`,
24
+ -- which the unique index below makes legal — Postgres accepts a plain UNIQUE INDEX as an FK
25
+ -- parent key (verified on 16.13; it does not require a named UNIQUE CONSTRAINT), and SQLite
26
+ -- requires exactly the same thing, so the twins stay identical. The cross-account insert now
27
+ -- fails in the database, in every code path, not in one handler's predicate.
28
+ --
29
+ -- THE CONSTRAINT IS NAMED, and the name is the one `schema.pg.ts` declares. Postgres quotes it in
30
+ -- the violation message ("violates foreign key constraint \"repo_reviewers_repo_account_fk\""),
31
+ -- so it is a real, greppable identifier. Previously the SQL named nothing and pg auto-named the
32
+ -- constraint `repo_reviewers_repo_id_account_id_fkey`, i.e. drizzle's `name:` field matched no
33
+ -- live constraint in any database. (SQLite stores the same clause but never reports it.)
34
+ --
35
+ -- THE BACKFILL IS ASYMMETRIC ON PURPOSE. MANUAL rows fan out to EVERY repo in the account — a
36
+ -- human judgement is the only thing here that cannot be regenerated, it was made account-wide,
37
+ -- and fanning it out reproduces that meaning with every repo row agreeing. AUTO rows land only on
38
+ -- repos where the actor actually has a FOOTPRINT (a review, an inline thread, or a PR comment
39
+ -- there): an auto verdict is re-derived on the next classification pass, and a row for a repo the
40
+ -- reviewer has never touched is not a bot object but a fabricated one. See the sqlite twin.
41
+ --
42
+ -- The footprint CTE is MATERIALIZED so the planner cannot inline it into the correlated EXISTS
43
+ -- and re-scan three tables per (row, repo) pair.
44
+ --
45
+ -- WHY THE BACKFILL IS WRAPPED IN A DO BLOCK: this file ends by dropping
46
+ -- `bot_review_classification`, so a hand-replay would otherwise fail on "relation does not exist"
47
+ -- rather than no-op. `to_regclass` is the guard; plpgsql does not resolve the branch's SQL until
48
+ -- it executes, so naming the dropped table inside it is safe. The sqlite twin reaches the same
49
+ -- end with a `CREATE TABLE IF NOT EXISTS` stub of the legacy table, which is the nearest thing
50
+ -- that dialect has to a conditional. Same intent, two dialects, stated rather than discovered.
51
+ -- Not for lookups (`id` is already the PK) — it exists solely as the composite FK's parent key.
52
+ CREATE UNIQUE INDEX IF NOT EXISTS "repos_id_account" ON "repos" USING btree ("id","account_id");--> statement-breakpoint
53
+ -- Unrelated to the FK, and the reason it rides in THIS migration: `review_threads
54
+ -- .original_commenter_id` had no index at all, and it is one of the three columns the footprint
55
+ -- CTE below unions on AND the column the per-repo footprint counts group by on every listing.
56
+ CREATE INDEX IF NOT EXISTS "thread_original_commenter_idx" ON "review_threads" USING btree ("original_commenter_id");--> statement-breakpoint
57
+ CREATE TABLE IF NOT EXISTS "repo_reviewers" (
58
+ "id" serial PRIMARY KEY NOT NULL,
59
+ "account_id" integer NOT NULL REFERENCES "accounts"("id") ON DELETE cascade,
60
+ "repo_id" integer NOT NULL,
61
+ "author_user_id" integer NOT NULL REFERENCES "users"("id"),
62
+ "automated" boolean NOT NULL,
63
+ "kind" text,
64
+ "label" text,
65
+ "identity_source" text DEFAULT 'auto' NOT NULL,
66
+ "role" text DEFAULT 'review' NOT NULL,
67
+ "confidence" text NOT NULL,
68
+ "source" text NOT NULL,
69
+ "reasons_json" jsonb,
70
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
71
+ CONSTRAINT "repo_reviewers_repo_account_fk" FOREIGN KEY ("repo_id","account_id") REFERENCES "repos"("id","account_id") ON DELETE cascade
72
+ );--> statement-breakpoint
73
+ CREATE UNIQUE INDEX IF NOT EXISTS "repo_reviewers_account_repo_author" ON "repo_reviewers" USING btree ("account_id","repo_id","author_user_id");--> statement-breakpoint
74
+ CREATE INDEX IF NOT EXISTS "repo_reviewers_account_repo_idx" ON "repo_reviewers" USING btree ("account_id","repo_id");--> statement-breakpoint
75
+ CREATE INDEX IF NOT EXISTS "repo_reviewers_account_author_idx" ON "repo_reviewers" USING btree ("account_id","author_user_id");--> statement-breakpoint
76
+ DO $$
77
+ BEGIN
78
+ IF to_regclass('public.bot_review_classification') IS NOT NULL THEN
79
+ WITH footprint AS MATERIALIZED (
80
+ SELECT DISTINCT p.repo_id AS repo_id, x.uid AS author_user_id
81
+ FROM pull_requests p
82
+ JOIN (
83
+ SELECT pr_id, author_id AS uid FROM reviews WHERE author_id IS NOT NULL
84
+ UNION ALL
85
+ SELECT pr_id, original_commenter_id AS uid FROM review_threads WHERE original_commenter_id IS NOT NULL
86
+ UNION ALL
87
+ SELECT pr_id, author_id AS uid FROM pr_comments WHERE author_id IS NOT NULL
88
+ ) x ON x.pr_id = p.id
89
+ )
90
+ INSERT INTO "repo_reviewers"
91
+ ("account_id", "repo_id", "author_user_id", "automated", "kind", "label",
92
+ "identity_source", "role", "confidence", "source", "reasons_json", "updated_at")
93
+ -- `identity_source` is derived from the old row's `source`: the old model had ONE provenance
94
+ -- field covering both grains, and a manual row's kind/label WERE a human's opinion. Import
95
+ -- them as 'auto' and the next classification pass silently reverts a hand-made correction.
96
+ SELECT b.account_id, r.id, b.author_user_id, b.automated, b.kind, b.label,
97
+ CASE WHEN b.source = 'manual' THEN 'manual' ELSE 'auto' END,
98
+ 'review', b.confidence, b.source, b.reasons_json, b.updated_at
99
+ FROM bot_review_classification b
100
+ JOIN repos r ON r.account_id = b.account_id
101
+ WHERE b.source = 'manual'
102
+ OR EXISTS (
103
+ SELECT 1 FROM footprint f
104
+ WHERE f.repo_id = r.id AND f.author_user_id = b.author_user_id
105
+ )
106
+ -- Never fires on a database this migration is meant for (the legacy table is unique on
107
+ -- (account_id, author_user_id)). It covers the state an unpushed rewrite can leave behind: a
108
+ -- machine that ran the deleted team-keyed draft has up to one row per (account, TEAM, author).
109
+ ON CONFLICT ("account_id", "repo_id", "author_user_id") DO NOTHING;
110
+ END IF;
111
+ END $$;--> statement-breakpoint
112
+ UPDATE "repo_reviewers"
113
+ SET "role" = 'quality_check'
114
+ WHERE "source" <> 'manual'
115
+ AND "author_user_id" IN (
116
+ SELECT "id" FROM "users" WHERE lower("github_login") IN (
117
+ 'sonarqubecloud', 'sonarqubecloud[bot]',
118
+ 'sonarcloud', 'sonarcloud[bot]',
119
+ 'codecov', 'codecov[bot]',
120
+ 'codeclimate', 'codeclimate[bot]',
121
+ 'codefactor-io', 'codefactor-io[bot]',
122
+ 'houndci-bot', 'houndci-bot[bot]',
123
+ 'coveralls', 'coveralls[bot]',
124
+ 'codacy-bot', 'codacy-bot[bot]'
125
+ )
126
+ );--> statement-breakpoint
127
+ DROP TABLE IF EXISTS "bot_review_classification";
@@ -0,0 +1,77 @@
1
+ -- NORMALISE THE ACTOR GRAIN OUT OF `repo_reviewers` — the Postgres twin of the sqlite migration
2
+ -- 0043_account_reviewers. Hand-written additive, idempotent. Second half of the change 0029
3
+ -- starts:
4
+ --
5
+ -- 0029 RE-KEY (account, author) → (account, repo, author) columns unchanged
6
+ -- 0030 NORMALISE the actor-grain columns → their own table `account_reviewers`
7
+ --
8
+ -- WHY. After 0029, `kind` / `label` / `identity_source` sit REPLICATED on every one of an actor's
9
+ -- repo rows, held consistent only by convention — the same hazard that giving cost its own
10
+ -- storage was meant to eliminate, left in place for three other columns. It carried three
11
+ -- standing obligations: a new repo row had to SEED all three from its siblings; persist() had to
12
+ -- gate on TWO provenance flags in one row; and the account-wide identity was "a straight read of
13
+ -- any one of them", which elects a winner the moment two rows disagree. One row per actor deletes
14
+ -- all three. Full argument in the sqlite twin.
15
+ --
16
+ -- COST IS FOLDED IN rather than given a third table: a price is just another actor-level
17
+ -- property, and a separate table on the same key would be this one with extra steps. One table
18
+ -- per grain.
19
+ --
20
+ -- `monthly_cents` IS THEREFORE NULLABLE, where a standalone cost table had it NOT NULL ("no
21
+ -- price" was "no row"). A row that also carries identity exists for reasons unrelated to money.
22
+ -- That is SAFE here in a way it was not before: the old bug class was nullable-means-INHERIT,
23
+ -- with a fallback chain behind it, where 0 ("free HERE") and NULL ("ask my parent") had to be
24
+ -- told apart with `??` rather than `||`. NOTHING INHERITS ANY MORE — NULL is "no price set", 0 is
25
+ -- "free", two states and no chain. Clearing a price is `SET monthly_cents = NULL`, not a DELETE.
26
+ --
27
+ -- `account_reviewers` is named for its KEY, exactly as `repo_reviewers` is, so the two names ARE
28
+ -- the statement of the model. Not `reviewer_identities` (which would make `monthly_cents` look
29
+ -- like a stray) and not `reviewer_costs` (which made `kind`/`label` look the same way).
30
+ --
31
+ -- ⚠ DRAFT-STATE HOUSEKEEPING — DELETE BEFORE THIS BRANCH MERGES. `reviewer_costs` was created by
32
+ -- an earlier draft of this migration while the branch was unpushed and never existed in any
33
+ -- released version. The guarded carry-over below exists ONLY so a machine that ran that draft
34
+ -- keeps the prices it typed; everywhere else `to_regclass` is NULL and it does nothing. No
35
+ -- Postgres deployment has ever run the draft — this is here for symmetry with the sqlite twin,
36
+ -- which needs it, and both go at the same time.
37
+ CREATE TABLE IF NOT EXISTS "account_reviewers" (
38
+ "id" serial PRIMARY KEY NOT NULL,
39
+ "account_id" integer NOT NULL REFERENCES "accounts"("id") ON DELETE cascade,
40
+ "author_user_id" integer NOT NULL REFERENCES "users"("id"),
41
+ "kind" text,
42
+ "label" text,
43
+ "identity_source" text DEFAULT 'auto' NOT NULL,
44
+ "monthly_cents" integer,
45
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
46
+ );--> statement-breakpoint
47
+ CREATE UNIQUE INDEX IF NOT EXISTS "account_reviewers_account_author" ON "account_reviewers" USING btree ("account_id","author_user_id");--> statement-breakpoint
48
+ -- ONE ROW PER ACTOR, LIFTED OFF WHICHEVER REPO ROW IS MOST AUTHORITATIVE. After 0029 an actor's
49
+ -- rows agree by construction, so any of them would do; the tie-break is stated because this is
50
+ -- exactly the "elect a winner" step the new shape abolishes, and doing it ONCE, here, with a
51
+ -- written rule is the honest way to abolish it: prefer a MANUAL identity, then the most recently
52
+ -- updated row. DISTINCT ON + ORDER BY is the same selection the sqlite twin spells with SQLite's
53
+ -- bare-columns-follow-the-max() rule.
54
+ INSERT INTO "account_reviewers"
55
+ ("account_id", "author_user_id", "kind", "label", "identity_source", "updated_at")
56
+ SELECT DISTINCT ON ("account_id", "author_user_id")
57
+ "account_id", "author_user_id", "kind", "label", "identity_source", "updated_at"
58
+ FROM "repo_reviewers"
59
+ ORDER BY "account_id", "author_user_id", ("identity_source" = 'manual') DESC, "updated_at" DESC
60
+ ON CONFLICT ("account_id", "author_user_id") DO NOTHING;--> statement-breakpoint
61
+ DO $$
62
+ BEGIN
63
+ IF to_regclass('public.reviewer_costs') IS NOT NULL THEN
64
+ UPDATE "account_reviewers" ar
65
+ SET "monthly_cents" = rc."monthly_cents"
66
+ FROM "reviewer_costs" rc
67
+ WHERE rc."account_id" = ar."account_id"
68
+ AND rc."author_user_id" = ar."author_user_id";
69
+ END IF;
70
+ END $$;--> statement-breakpoint
71
+ DROP TABLE IF EXISTS "reviewer_costs";--> statement-breakpoint
72
+ -- The point of the whole file: after this, a repo row can say nothing about WHO the actor is.
73
+ -- `RepoReviewer` on the wire exposes no identity field and `ReviewerIdentity` exposes no
74
+ -- judgement field, and now the storage says the same thing.
75
+ ALTER TABLE "repo_reviewers" DROP COLUMN IF EXISTS "kind";--> statement-breakpoint
76
+ ALTER TABLE "repo_reviewers" DROP COLUMN IF EXISTS "label";--> statement-breakpoint
77
+ ALTER TABLE "repo_reviewers" DROP COLUMN IF EXISTS "identity_source";
@@ -0,0 +1,131 @@
1
+ -- TEAMS BECOME WORKSPACES, AND A REPO GETS EXACTLY ONE. Postgres / cloud mode; the SQLite twin is
2
+ -- migrations/0044_workspaces.sql. Migration 0032 then collapses the two bot tables onto the
3
+ -- workspace key — read them together, they are one change in two steps:
4
+ --
5
+ -- 0031 RE-HOME repo grouping: teams (m2m) → workspaces (1:N), + a Default per account
6
+ -- 0032 COLLAPSE the bot object: repo_reviewers + account_reviewers → workspace_reviewers
7
+ --
8
+ -- WORKSPACE IDS ARE THE OLD TEAM IDS. Preserved deliberately: a URL, a bookmark, a persisted
9
+ -- filter and (after the plugin's own 0020) a cache row all carry the number, and renumbering
10
+ -- would silently repoint them at a different repo set.
11
+ --
12
+ -- ⚠ THE ONE THING THAT DIFFERS BETWEEN THE DIALECTS IS THE SEQUENCE. SQLite AUTOINCREMENT tracks
13
+ -- max(rowid) ever seen, so an explicit-id INSERT advances it and the Default rows below get fresh
14
+ -- ids for free. Postgres `serial` does NOT advance on an explicit-id INSERT, so this file MUST
15
+ -- setval() between step 1 and step 2 or the very next workspace collides with a preserved team id.
16
+ -- The failure is a LOUD BOOT FAILURE, not a later 500: step 2 takes nextval = 1, collides with
17
+ -- preserved team id 1, `duplicate key value violates unique constraint "workspaces_pkey"` aborts
18
+ -- the DO block, runMigrations() throws and cloud never boots.
19
+ --
20
+ -- ⚠ ONLY STEPS 1 AND 3 ARE GUARDED by `to_regclass`, because they are the only statements that
21
+ -- READ `teams` / `team_repos` (this file drops both at the bottom, so a replay would otherwise
22
+ -- fail on "relation does not exist" rather than no-op — the pg equivalent of the sqlite twin's
23
+ -- `CREATE TABLE IF NOT EXISTS` legacy stubs). Steps 2 (a Default per account) and 4 (unassigned
24
+ -- repos → Default) are PLAIN, UNGUARDED statements. Wrapping all four — the obvious reading of
25
+ -- "same five steps" — would make a Postgres database that no longer has `teams` (a fresh cloud
26
+ -- deploy, a replay after the DROP) create NO workspaces and NO memberships at all, while the
27
+ -- sqlite twin's stub tables leave steps 2 and 4 always running. The runtime repair
28
+ -- (ensureDefaultWorkspace / ensureRepoMemberships) hides it, so the two files would silently
29
+ -- implement two different algorithms.
30
+ CREATE TABLE IF NOT EXISTS "workspaces" (
31
+ "id" serial PRIMARY KEY NOT NULL,
32
+ "account_id" integer NOT NULL REFERENCES "accounts"("id") ON DELETE cascade,
33
+ "name" text NOT NULL,
34
+ "is_default" boolean DEFAULT false NOT NULL,
35
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
36
+ );--> statement-breakpoint
37
+ CREATE UNIQUE INDEX IF NOT EXISTS "workspaces_account_name" ON "workspaces" USING btree ("account_id","name");--> statement-breakpoint
38
+ CREATE INDEX IF NOT EXISTS "workspaces_account_idx" ON "workspaces" USING btree ("account_id");--> statement-breakpoint
39
+ -- Not for lookups (`id` is already the PK) — it is the PARENT KEY of the composite FKs on
40
+ -- workspace_repos and workspace_reviewers, exactly as `repos_id_account` (0029) is for those
41
+ -- tables' repo FKs. Postgres accepts a plain UNIQUE INDEX as an FK parent key; it does not require
42
+ -- a named UNIQUE CONSTRAINT. Drop it and both composite FKs become unexpressible.
43
+ CREATE UNIQUE INDEX IF NOT EXISTS "workspaces_id_account" ON "workspaces" USING btree ("id","account_id");--> statement-breakpoint
44
+ -- 1. One workspace per existing team, IDS PRESERVED.
45
+ DO $$
46
+ BEGIN
47
+ IF to_regclass('public.teams') IS NOT NULL THEN
48
+ INSERT INTO "workspaces" ("id", "account_id", "name", "is_default", "created_at")
49
+ SELECT "id", "account_id", "name", false, "created_at" FROM "teams"
50
+ ON CONFLICT ("id") DO NOTHING;
51
+ END IF;
52
+ END $$;--> statement-breakpoint
53
+ -- 1b. ADVANCE THE SEQUENCE PAST THE PRESERVED IDS. No sqlite counterpart — do not "harmonise it
54
+ -- away". `is_called` is false on an EMPTY table so the first id is 1, not 2: passing a bare
55
+ -- `true` makes a fresh cloud deployment's very first workspace id 2, which reads as a bug to
56
+ -- whoever finds it.
57
+ SELECT setval(
58
+ pg_get_serial_sequence('workspaces','id'),
59
+ GREATEST(COALESCE((SELECT MAX("id") FROM "workspaces"), 0), 1),
60
+ COALESCE((SELECT MAX("id") FROM "workspaces"), 0) > 0
61
+ );--> statement-breakpoint
62
+ -- 2. A Default workspace for EVERY account, including accounts that never had a team.
63
+ -- The three-level name CASE exists because `workspaces_account_name` is unique and a user may
64
+ -- already own a team called "Default". The third form embeds the account id, so it cannot
65
+ -- collide with the first two for the same account.
66
+ INSERT INTO "workspaces" ("account_id", "name", "is_default", "created_at")
67
+ SELECT a."id",
68
+ CASE
69
+ WHEN NOT EXISTS (SELECT 1 FROM "workspaces" w WHERE w."account_id" = a."id" AND w."name" = 'Default')
70
+ THEN 'Default'
71
+ WHEN NOT EXISTS (SELECT 1 FROM "workspaces" w WHERE w."account_id" = a."id" AND w."name" = 'Default workspace')
72
+ THEN 'Default workspace'
73
+ ELSE 'Default (workspace ' || a."id" || ')'
74
+ END,
75
+ true, now()
76
+ FROM "accounts" a
77
+ WHERE NOT EXISTS (SELECT 1 FROM "workspaces" w WHERE w."account_id" = a."id" AND w."is_default");--> statement-breakpoint
78
+ -- 2b. ONE DEFAULT PER ACCOUNT, AS A DATABASE FACT. Created after the backfill so a hand-corrupted
79
+ -- database fails on a statement whose meaning is obvious. This is what makes
80
+ -- ensureDefaultWorkspace's "INSERT … ON CONFLICT DO NOTHING then re-SELECT" race-safe: it runs
81
+ -- on effectively every request, and two concurrent calls for an account with no default would
82
+ -- otherwise both SELECT nothing and both INSERT.
83
+ CREATE UNIQUE INDEX IF NOT EXISTS "workspaces_one_default" ON "workspaces" USING btree ("account_id") WHERE "is_default";--> statement-breakpoint
84
+ CREATE TABLE IF NOT EXISTS "workspace_repos" (
85
+ "id" serial PRIMARY KEY NOT NULL,
86
+ "account_id" integer NOT NULL REFERENCES "accounts"("id") ON DELETE cascade,
87
+ "workspace_id" integer NOT NULL,
88
+ "repo_id" integer NOT NULL,
89
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
90
+ -- Tenancy as a constraint. `workspace_id` arrives in a REQUEST BODY, so a plain
91
+ -- `REFERENCES workspaces(id)` would accept (account 2, workspace 10) where workspace 10 belongs
92
+ -- to account 1 — both halves individually valid. NAMED so Postgres quotes it in the violation
93
+ -- message and a grep for the name finds a live constraint.
94
+ CONSTRAINT "workspace_repos_workspace_account_fk" FOREIGN KEY ("workspace_id","account_id") REFERENCES "workspaces"("id","account_id") ON DELETE cascade,
95
+ CONSTRAINT "workspace_repos_repo_account_fk" FOREIGN KEY ("repo_id","account_id") REFERENCES "repos"("id","account_id") ON DELETE cascade
96
+ );--> statement-breakpoint
97
+ -- EXACTLY ONE WORKSPACE PER REPO, AS A DATABASE FACT — and the upsert conflict target for
98
+ -- assignReposToWorkspace, so assigning a repo elsewhere is a MOVE and no code path can produce a
99
+ -- second membership row.
100
+ CREATE UNIQUE INDEX IF NOT EXISTS "workspace_repos_account_repo" ON "workspace_repos" USING btree ("account_id","repo_id");--> statement-breakpoint
101
+ CREATE INDEX IF NOT EXISTS "workspace_repos_account_workspace_idx" ON "workspace_repos" USING btree ("account_id","workspace_id");--> statement-breakpoint
102
+ CREATE INDEX IF NOT EXISTS "workspace_repos_repo_idx" ON "workspace_repos" USING btree ("repo_id");--> statement-breakpoint
103
+ -- 3. D4's TIE-BREAK: a repo in 2+ teams keeps its EARLIEST assignment (lowest team_repos.id).
104
+ -- Written as a correlated MIN() rather than DISTINCT ON, which SQLite does not have and which
105
+ -- would therefore make the two dialects' files structurally different for no reason. The
106
+ -- account predicate in the subquery is redundant (repos.id is a global PK, so repo_id already
107
+ -- implies the account) and is spelled anyway so tenancy is visible in the statement.
108
+ DO $$
109
+ BEGIN
110
+ IF to_regclass('public.team_repos') IS NOT NULL THEN
111
+ INSERT INTO "workspace_repos" ("account_id", "workspace_id", "repo_id", "created_at")
112
+ SELECT tr."account_id", tr."team_id", tr."repo_id", tr."created_at"
113
+ FROM "team_repos" tr
114
+ WHERE tr."id" = (
115
+ SELECT MIN(tr2."id") FROM "team_repos" tr2
116
+ WHERE tr2."repo_id" = tr."repo_id" AND tr2."account_id" = tr."account_id")
117
+ ON CONFLICT ("account_id", "repo_id") DO NOTHING;
118
+ END IF;
119
+ END $$;--> statement-breakpoint
120
+ -- 4. Every repo with no resulting row → that account's Default. Covers repos that were in no team
121
+ -- at all AND (on a replay) anything step 3 could not place. UNGUARDED — see the header.
122
+ INSERT INTO "workspace_repos" ("account_id", "workspace_id", "repo_id", "created_at")
123
+ SELECT r."account_id", w."id", r."id", now()
124
+ FROM "repos" r
125
+ JOIN "workspaces" w ON w."account_id" = r."account_id" AND w."is_default"
126
+ WHERE NOT EXISTS (SELECT 1 FROM "workspace_repos" wr WHERE wr."repo_id" = r."id")
127
+ ON CONFLICT ("account_id", "repo_id") DO NOTHING;--> statement-breakpoint
128
+ -- 5. Child first (team_repos FKs teams), then the parent. Leaving them behind would leave a
129
+ -- second, differently-keyed answer to "which repos am I looking at" in every database.
130
+ DROP TABLE IF EXISTS "team_repos";--> statement-breakpoint
131
+ DROP TABLE IF EXISTS "teams";
@@ -0,0 +1,165 @@
1
+ -- THE BOT OBJECT COLLAPSES ONTO THE WORKSPACE KEY. Postgres / cloud mode; the SQLite twin is
2
+ -- migrations/0045_workspace_reviewers.sql. Second half of the change 0031 starts.
3
+ --
4
+ -- `repo_reviewers` answered "is this login a bot" once per (account, repo). `account_reviewers`
5
+ -- answered "who is it and what does it cost" once per (account, actor). With one workspace as the
6
+ -- only scope, both questions have the same key, so both tables become one row per
7
+ -- (account, workspace, actor) — and the fold that reads six repo rows into one metric answer
8
+ -- disappears with them.
9
+ --
10
+ -- ⚠ WHAT MUST NOT BE LOST WITH THE MERGE: the two PROVENANCE FLAGS stay separate columns and are
11
+ -- honoured INDEPENDENTLY. `source` owns automated/role/confidence/reasons; `identity_source` owns
12
+ -- kind/label. `monthly_cents` is owned by nothing derived — exactly one writer (setReviewerCost)
13
+ -- names exactly one row, and the column appears in no other `set:` object anywhere. NULL = no
14
+ -- price set, 0 = "recorded as free"; nothing inherits, so there is no chain behind a `??`.
15
+ --
16
+ -- ── THE FOLD RULE ────────────────────────────────────────────────────────────────────────────
17
+ -- For each (account, workspace, actor) with at least one repo_reviewers row on a repo now in
18
+ -- that workspace:
19
+ -- automated = TRUE if ANY contributing row is automated (union — this half DOES
20
+ -- role = 'review' if ANY contributing row roles it 'review' reproduce resolveJudgements
21
+ -- else 'quality_check' exactly, for READS)
22
+ -- confidence = the highest confidence among the rows that carried the WINNING `automated` value
23
+ -- (folding the max across all rows lets an automated=false row hand `high` to an
24
+ -- automated=true verdict it disagreed with)
25
+ -- updated_at = MAX of the contributing rows
26
+ --
27
+ -- source = 'manual' if ANY contributing row is manual, regardless of which side won.
28
+ -- ⚠ THIS IS DELIBERATELY *NOT* THE READ-TIME UNION RULE, and the difference is the
29
+ -- whole point. For reads, `manualHuman` is only consulted when nothing in scope
30
+ -- calls the actor automated. But `source` is not only a read input: it is the WRITE
31
+ -- GATE in persist() and the flag behind the "Reset classification" affordance.
32
+ -- Folding it to 'auto' would let the next classification pass silently overwrite a
33
+ -- human's opinion, with no control offered to undo it and no trace in reasons_json.
34
+ -- A visible, resettable pin the user can see is strictly better than a judgement
35
+ -- that vanishes. ELSE the source of the winning evidence — carried, not invented:
36
+ -- the highest confidence row on the winning side of `automated`, ties broken by
37
+ -- updated_at DESC then repo_id ASC so both dialects pick the same row. ⚠ NEVER the
38
+ -- literal 'auto': that is the `identity_source` vocabulary. `ClassificationSource`
39
+ -- is 'manual'|'vendor_login'|'github_type'|'app_attribution'|'fingerprint'|
40
+ -- 'behavioral'|'ai_tiebreak' and an out-of-union value would never self-heal.
41
+ -- 'fingerprint' is the fallback if the subquery somehow finds nothing.
42
+ --
43
+ -- reasons_json = SYNTHESISED, not carried. One repo row's reason text does not describe a
44
+ -- workspace verdict, the field is advisory display evidence, and the next
45
+ -- classification pass overwrites it for every non-manual row. ⚠ A fold where a
46
+ -- manual row LOST gets an explicit conflict string, so the one case where a human's
47
+ -- judgement was superseded is visible on the card rather than inferred from a
48
+ -- mismatched provenance flag.
49
+ --
50
+ -- kind, label, identity_source = copied from the actor's account_reviewers row. Account-grain
51
+ -- today, so they REPLICATE into each workspace the actor appears in. That
52
+ -- replication is the accepted D5 consequence: from here on they are per-workspace
53
+ -- facts and may legitimately diverge.
54
+ -- monthly_cents = copied into every workspace row of that actor, on exactly the same footing.
55
+ -- This is the faithful carry-over of a value that WAS account-wide — a ONE-TIME
56
+ -- SEED, not an invariant. From here on price is a per-workspace fact like kind and
57
+ -- label: setReviewerCost writes ONE row, nothing fans out, nothing seeds a later
58
+ -- row, and the copies diverge freely as the user edits them.
59
+ --
60
+ -- Actors with an account_reviewers row but NO repo_reviewers row anywhere are DROPPED — the
61
+ -- listing has always been row-driven, and such an identity could never be displayed, edited or
62
+ -- cleared (the same rule the 404 on the old identity route enforced).
63
+ --
64
+ -- ⚠ DIALECT DIVERGENCES THAT MAY NOT BE "UNIFIED" (the sqlite twin is the reference for each):
65
+ -- • `bool_or(rr.automated)` — pg has no `max(boolean)`; `MAX()` would fail to plan.
66
+ -- • bare booleans (`rr.automated`, `f.aut`) where sqlite writes `= 1`.
67
+ -- • BUT `manual_aut` / `manual_any` / `any_review` are INTEGER `MAX(CASE … 1 ELSE 0 END)` in
68
+ -- BOTH dialects, so they must still be compared with `= 1` here. `CASE WHEN f.aut AND
69
+ -- f.manual_aut THEN …` is a HARD TYPE ERROR — Postgres has no implicit integer→boolean cast
70
+ -- and raises `argument of AND must be type boolean, not type integer`, aborting the whole
71
+ -- migration. (`rr2.automated = f.aut` is boolean = boolean here and integer = integer in
72
+ -- sqlite, so that one line is genuinely identical in both files.)
73
+ -- • `::jsonb` on the synthesised reasons — the pg column is jsonb, not text.
74
+ -- • the backfill is wrapped in a `to_regclass` guard rather than preceded by legacy-table stubs:
75
+ -- this file drops the tables its own backfill reads, and pg has conditional DDL while sqlite
76
+ -- does not. Same intent, two dialects, stated rather than discovered.
77
+ -- • no `WHERE true` before `ON CONFLICT` — that disambiguator is a sqlite parsing quirk only.
78
+ CREATE TABLE IF NOT EXISTS "workspace_reviewers" (
79
+ "id" serial PRIMARY KEY NOT NULL,
80
+ "account_id" integer NOT NULL REFERENCES "accounts"("id") ON DELETE cascade,
81
+ "workspace_id" integer NOT NULL,
82
+ -- No cascade: `users` is GLOBAL storage shared by every account and is never deleted.
83
+ "author_user_id" integer NOT NULL REFERENCES "users"("id"),
84
+ "automated" boolean NOT NULL,
85
+ "role" text DEFAULT 'review' NOT NULL,
86
+ "confidence" text NOT NULL,
87
+ "source" text NOT NULL,
88
+ "reasons_json" jsonb,
89
+ "kind" text,
90
+ "label" text,
91
+ "identity_source" text DEFAULT 'auto' NOT NULL,
92
+ "monthly_cents" integer,
93
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
94
+ CONSTRAINT "workspace_reviewers_workspace_account_fk" FOREIGN KEY ("workspace_id","account_id") REFERENCES "workspaces"("id","account_id") ON DELETE cascade
95
+ );--> statement-breakpoint
96
+ -- The upsert conflict target for EVERY writer. A stale target type-checks perfectly and raises
97
+ -- "no unique or exclusion constraint matching the ON CONFLICT specification" at RUNTIME, in both
98
+ -- dialects, only when a row is actually written.
99
+ CREATE UNIQUE INDEX IF NOT EXISTS "workspace_reviewers_account_workspace_author" ON "workspace_reviewers" USING btree ("account_id","workspace_id","author_user_id");--> statement-breakpoint
100
+ CREATE INDEX IF NOT EXISTS "workspace_reviewers_account_workspace_idx" ON "workspace_reviewers" USING btree ("account_id","workspace_id");--> statement-breakpoint
101
+ CREATE INDEX IF NOT EXISTS "workspace_reviewers_account_author_idx" ON "workspace_reviewers" USING btree ("account_id","author_user_id");--> statement-breakpoint
102
+ DO $$
103
+ BEGIN
104
+ IF to_regclass('public.repo_reviewers') IS NOT NULL THEN
105
+ INSERT INTO "workspace_reviewers"
106
+ ("account_id", "workspace_id", "author_user_id", "automated", "role", "confidence", "source",
107
+ "reasons_json", "kind", "label", "identity_source", "monthly_cents", "updated_at")
108
+ SELECT f."account_id", f."workspace_id", f."author_user_id",
109
+ f."aut",
110
+ CASE WHEN f."any_review" = 1 THEN 'review' ELSE 'quality_check' END,
111
+ CASE WHEN f."aut" THEN
112
+ CASE f."conf_aut" WHEN 3 THEN 'high' WHEN 2 THEN 'medium' ELSE 'low' END
113
+ ELSE
114
+ CASE f."conf_any" WHEN 3 THEN 'high' WHEN 2 THEN 'medium' ELSE 'low' END
115
+ END,
116
+ -- source: 'manual' if ANY contributing row was manual (the pin survives, visibly);
117
+ -- otherwise the winning row's OWN source — a real ClassificationSource member, never
118
+ -- 'auto'.
119
+ CASE WHEN f."manual_any" = 1 THEN 'manual' ELSE COALESCE((
120
+ SELECT rr2."source"
121
+ FROM "repo_reviewers" rr2
122
+ JOIN "workspace_repos" wr2
123
+ ON wr2."repo_id" = rr2."repo_id" AND wr2."account_id" = rr2."account_id"
124
+ WHERE rr2."account_id" = f."account_id"
125
+ AND wr2."workspace_id" = f."workspace_id"
126
+ AND rr2."author_user_id" = f."author_user_id"
127
+ AND rr2."automated" = f."aut"
128
+ ORDER BY CASE rr2."confidence" WHEN 'high' THEN 3 WHEN 'medium' THEN 2 ELSE 1 END DESC,
129
+ rr2."updated_at" DESC, rr2."repo_id" ASC
130
+ LIMIT 1), 'fingerprint') END,
131
+ CASE WHEN f."aut" AND f."manual_aut" = 1
132
+ THEN '["manually tagged as an automated reviewer"]'::jsonb
133
+ WHEN NOT f."aut" AND f."manual_any" = 1
134
+ THEN '["manually confirmed as a human"]'::jsonb
135
+ -- The one lossy case, named out loud on the card rather than left to be inferred.
136
+ WHEN f."manual_any" = 1
137
+ THEN '["migrated from the per-repo classification","⚠ conflicting per-repo judgements were merged — review this"]'::jsonb
138
+ ELSE '["migrated from the per-repo classification"]'::jsonb END,
139
+ ar."kind", ar."label", COALESCE(ar."identity_source", 'auto'), ar."monthly_cents",
140
+ f."upd"
141
+ FROM (
142
+ SELECT rr."account_id" AS "account_id",
143
+ wr."workspace_id" AS "workspace_id",
144
+ rr."author_user_id" AS "author_user_id",
145
+ bool_or(rr."automated") AS "aut",
146
+ MAX(CASE WHEN COALESCE(rr."role",'review') <> 'quality_check' THEN 1 ELSE 0 END) AS "any_review",
147
+ MAX(CASE WHEN rr."automated"
148
+ THEN CASE rr."confidence" WHEN 'high' THEN 3 WHEN 'medium' THEN 2 ELSE 1 END
149
+ ELSE 0 END) AS "conf_aut",
150
+ MAX(CASE rr."confidence" WHEN 'high' THEN 3 WHEN 'medium' THEN 2 ELSE 1 END) AS "conf_any",
151
+ MAX(CASE WHEN rr."source" = 'manual' AND rr."automated" THEN 1 ELSE 0 END) AS "manual_aut",
152
+ MAX(CASE WHEN rr."source" = 'manual' THEN 1 ELSE 0 END) AS "manual_any",
153
+ MAX(rr."updated_at") AS "upd"
154
+ FROM "repo_reviewers" rr
155
+ JOIN "workspace_repos" wr
156
+ ON wr."repo_id" = rr."repo_id" AND wr."account_id" = rr."account_id"
157
+ GROUP BY rr."account_id", wr."workspace_id", rr."author_user_id"
158
+ ) f
159
+ LEFT JOIN "account_reviewers" ar
160
+ ON ar."account_id" = f."account_id" AND ar."author_user_id" = f."author_user_id"
161
+ ON CONFLICT ("account_id", "workspace_id", "author_user_id") DO NOTHING;
162
+ END IF;
163
+ END $$;--> statement-breakpoint
164
+ DROP TABLE IF EXISTS "repo_reviewers";--> statement-breakpoint
165
+ DROP TABLE IF EXISTS "account_reviewers";
@@ -0,0 +1,14 @@
1
+ -- Drop the "watched" concept — the Postgres twin of the sqlite migration 0046_drop_inbox_watch.
2
+ --
3
+ -- `repos.inbox_watch` / `repos.inbox_watch_started_at` were a second visibility axis on top of
4
+ -- "is this repo added to the account", quietly narrowing the Feed, recent activity, My Turn and
5
+ -- the Pro digest collection to a watched subset. With Workspaces the Workspace IS the scope, so
6
+ -- every repo in a Workspace is now fully live.
7
+ --
8
+ -- The property that mattered — adding a repo with 400 open PRs must not dump them all into My
9
+ -- Turn on day one — moves to `repos.created_at` (when the repo was ADDED), which already exists
10
+ -- and is NOT NULL, so there is no backfill.
11
+ --
12
+ -- `IF EXISTS` on both, so a database that somehow never had them replays cleanly.
13
+ ALTER TABLE "repos" DROP COLUMN IF EXISTS "inbox_watch";--> statement-breakpoint
14
+ ALTER TABLE "repos" DROP COLUMN IF EXISTS "inbox_watch_started_at";
@@ -0,0 +1,33 @@
1
+ -- Postgres twin of migrations/0047_ml_comment_labels.sql — ML severity/category labels for
2
+ -- BOT-authored text (CORE, free tier, no LLM). HAND-WRITTEN ADDITIVE, like every pg migration
3
+ -- since 0023: never regenerate the baseline with `pnpm db:generate:pg`, which squashes it.
4
+ -- Index names are byte-identical to the sqlite twin (they are the drizzle index names).
5
+ CREATE TABLE IF NOT EXISTS "ml_comment_labels" (
6
+ "id" serial PRIMARY KEY NOT NULL,
7
+ "account_id" integer NOT NULL,
8
+ "repo_id" integer NOT NULL,
9
+ "pr_id" integer NOT NULL,
10
+ "target_kind" text NOT NULL,
11
+ "target_id" integer NOT NULL,
12
+ "author_user_id" integer NOT NULL,
13
+ "severity" text NOT NULL,
14
+ "severity_ord" integer NOT NULL,
15
+ "severity_prob" double precision NOT NULL,
16
+ "categories" jsonb NOT NULL,
17
+ "category_probs" jsonb NOT NULL,
18
+ "is_summary" boolean NOT NULL,
19
+ "backend" text NOT NULL,
20
+ "model_version" text NOT NULL,
21
+ "body_hash" text NOT NULL,
22
+ "target_created_at" timestamp with time zone NOT NULL,
23
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
24
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
25
+ );
26
+ --> statement-breakpoint
27
+ ALTER TABLE "ml_comment_labels" ADD CONSTRAINT "ml_comment_labels_account_id_accounts_id_fk" FOREIGN KEY ("account_id") REFERENCES "public"."accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
28
+ ALTER TABLE "ml_comment_labels" ADD CONSTRAINT "ml_comment_labels_repo_id_repos_id_fk" FOREIGN KEY ("repo_id") REFERENCES "public"."repos"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
29
+ ALTER TABLE "ml_comment_labels" ADD CONSTRAINT "ml_comment_labels_pr_id_pull_requests_id_fk" FOREIGN KEY ("pr_id") REFERENCES "public"."pull_requests"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
30
+ ALTER TABLE "ml_comment_labels" ADD CONSTRAINT "ml_comment_labels_author_user_id_users_id_fk" FOREIGN KEY ("author_user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
31
+ CREATE UNIQUE INDEX IF NOT EXISTS "mcl_account_target" ON "ml_comment_labels" USING btree ("account_id","target_kind","target_id");--> statement-breakpoint
32
+ CREATE INDEX IF NOT EXISTS "mcl_account_pr_idx" ON "ml_comment_labels" USING btree ("account_id","pr_id");--> statement-breakpoint
33
+ CREATE INDEX IF NOT EXISTS "mcl_account_repo_author_idx" ON "ml_comment_labels" USING btree ("account_id","repo_id","author_user_id");
@@ -0,0 +1,16 @@
1
+ -- The review bot's OWN declared severity — the Postgres twin of sqlite
2
+ -- 0048_ml_vendor_severity.sql. HAND-WRITTEN ADDITIVE, like every pg migration since 0023:
3
+ -- never regenerate the baseline with `pnpm db:generate:pg`, which squashes it.
4
+ --
5
+ -- See the sqlite twin for the full argument. In short: the severity-api's marker reader already
6
+ -- extracts the vendor's self-declared severity and we were discarding it, so it is stored beside
7
+ -- ours to be DISPLAYED next to it — never folded into it. On `gold_v2_sample` our model scores
8
+ -- 0.700 exact / 0.303 ordinal MAE against the vendor badge's 0.474 / 0.697, so the vendor's claim
9
+ -- is the less accurate of the two; nothing derives our severity from it.
10
+ --
11
+ -- Both columns are nullable: most comments carry no vendor badge, and an older severity-api
12
+ -- omits the fields entirely (the client reads them defensively → null). No index (read out of an
13
+ -- already-fetched row, never a predicate) and no backfill (labels are not re-scored; `pnpm
14
+ -- ml:enrich --reset` is the refresh gesture).
15
+ ALTER TABLE "ml_comment_labels" ADD COLUMN IF NOT EXISTS "vendor_severity" text;--> statement-breakpoint
16
+ ALTER TABLE "ml_comment_labels" ADD COLUMN IF NOT EXISTS "vendor_severity_confidence" text;
@@ -0,0 +1,12 @@
1
+ -- Per-seat bot pricing + repo descriptions — the Postgres twin of sqlite
2
+ -- 0049_seat_pricing_repo_description.sql. HAND-WRITTEN ADDITIVE, like every pg migration
3
+ -- since 0023: never regenerate the baseline with `pnpm db:generate:pg`, which squashes it.
4
+ --
5
+ -- See the sqlite twin for the full argument. In short: `cost_model` says how `monthly_cents`
6
+ -- is read ('flat' = whole-workspace subscription, the default so every existing row keeps its
7
+ -- meaning; 'per_seat' = unit price × the workspace's derived human seat count, product never
8
+ -- stored), with ownership identical to `monthly_cents` (one writer, `setReviewerCost`).
9
+ -- `repos.description` is the GitHub "About" text captured by the activity sync — grounding
10
+ -- for the workspace-purpose sprint-chat preset.
11
+ ALTER TABLE "repos" ADD COLUMN IF NOT EXISTS "description" text;--> statement-breakpoint
12
+ ALTER TABLE "workspace_reviewers" ADD COLUMN IF NOT EXISTS "cost_model" text DEFAULT 'flat' NOT NULL;
@@ -0,0 +1,9 @@
1
+ -- Bot Tuning Advisor discovery: capture the GitHub App slug behind a bot's comments — the
2
+ -- Postgres twin of sqlite 0050_users_app_slug.sql. HAND-WRITTEN ADDITIVE, like every pg
3
+ -- migration since 0023: never regenerate the baseline with `pnpm db:generate:pg`, which
4
+ -- squashes it.
5
+ --
6
+ -- See the sqlite twin for the full argument. In short: `users.app_slug` persists the
7
+ -- `performed_via_github_app.slug` the app-attribution probe already receives (global fact,
8
+ -- global table, nullable, never cleared by a later app-less comment).
9
+ ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "app_slug" text;
@@ -0,0 +1,10 @@
1
+ -- Auto-merge ("merge when ready") learns GitHub's native merge queue — the Postgres twin of
2
+ -- sqlite 0051_auto_merge_queue.sql. HAND-WRITTEN ADDITIVE, like every pg migration since
3
+ -- 0023: never regenerate the baseline with `pnpm db:generate:pg`, which squashes it.
4
+ --
5
+ -- See the sqlite twin for the full argument. In short: `via_merge_queue` (arm-time fact —
6
+ -- the watcher enqueues instead of direct-merging) + `enqueued_at` (the watcher's own
7
+ -- enqueue, the attribution record for 'merged' vs 'disarmed_blocked').
8
+ ALTER TABLE "auto_merge_requests" ADD COLUMN IF NOT EXISTS "via_merge_queue" boolean DEFAULT false NOT NULL;
9
+ --> statement-breakpoint
10
+ ALTER TABLE "auto_merge_requests" ADD COLUMN IF NOT EXISTS "enqueued_at" timestamp with time zone;