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,317 @@
1
+ import { and, desc, eq, inArray, or, sql } from 'drizzle-orm';
2
+ import { db, schema, runTransaction } from './client.js';
3
+ // Cross-repo text search over the local `search_index` (CORE, no AI), scoped to ONE workspace's
4
+ // repos. The match is a PORTABLE case-insensitive SUBSTRING (`lower(col) LIKE pattern ESCAPE '\'`,
5
+ // term lowercased in JS) so the SAME query runs on SQLite (local) and Postgres (cloud) — no dialect
6
+ // fork, no FTS operator. Substring is the right semantics for the user's goal ("pinpoint where
7
+ // certain text exists"). Every query filters on the denormalized `accountId`, so cross-account
8
+ // isolation is a single indexed predicate. A Postgres `pg_trgm` GIN index (see schema.pg.ts) is a
9
+ // drop-in accelerator.
10
+ //
11
+ // ⚠ NOTHING HERE PERSISTS A SCOPE. `search_index` stores `accountId` + `repoId` and the scope is
12
+ // applied at QUERY time from the caller's resolved repo list, so the workspace refactor needs no
13
+ // data migration on this table — unlike the plugin's report caches, which persist a `scope_key`.
14
+ //
15
+ // Case-folding caveat: SQL `lower()` folds ASCII on both dialects, but only Postgres folds non-ASCII
16
+ // (accents, Cyrillic, …) — better-sqlite3 has no ICU. So a lowercase-accented query for
17
+ // upper-accented content ("café" ⇢ "CAFÉ") matches on cloud but not locally. Acceptable: the corpus
18
+ // (code, PR text) is ASCII-dominant, and closing it would need a normalized column + migration for a
19
+ // rare case. ASCII search — the overwhelming majority — is identical on both.
20
+ const { searchIndex, pullRequests, repos, users } = schema;
21
+ const { reviews: reviewsT, reviewComments: rcT, prComments: pcT } = schema;
22
+ const MAX_TERMS = 6;
23
+ const SNIPPET_RADIUS = 90;
24
+ const asDate = (d) => d instanceof Date ? d : new Date(d == null ? 0 : Number(d) * (Number(d) > 1e12 ? 1 : 1000));
25
+ // Collapse whitespace + hard-cap the indexed text (shared with the persist path in upsert.ts). A
26
+ // review/description can be arbitrarily long; the index only needs enough to match + snippet.
27
+ export const SEARCH_TEXT_MAX = 4000;
28
+ export function searchText(...parts) {
29
+ const joined = parts
30
+ .map((p) => (p ?? '').trim())
31
+ .filter((p) => p.length > 0)
32
+ .join('\n')
33
+ .replace(/\s+/g, ' ')
34
+ .trim();
35
+ return joined.length > SEARCH_TEXT_MAX ? joined.slice(0, SEARCH_TEXT_MAX) : joined;
36
+ }
37
+ // Split a raw query into up to MAX_TERMS lowercased terms; a "quoted phrase" is one term. The
38
+ // terms are ANDed for hits (all must appear) and ORed for people (any name fragment matches).
39
+ function parseTerms(query) {
40
+ const terms = [];
41
+ const re = /"([^"]+)"|(\S+)/g;
42
+ let m;
43
+ while ((m = re.exec(query)) != null && terms.length < MAX_TERMS) {
44
+ const t = (m[1] ?? m[2] ?? '').trim().toLowerCase();
45
+ if (t)
46
+ terms.push(t);
47
+ }
48
+ return terms;
49
+ }
50
+ // A `%term%` LIKE pattern with the LIKE metacharacters (%, _, \) escaped so the term matches
51
+ // literally (paired with `ESCAPE '\'` in likeCol).
52
+ function likePattern(term) {
53
+ return `%${term.replace(/([\\%_])/g, '\\$1')}%`;
54
+ }
55
+ // `lower(col) LIKE pattern ESCAPE '\'` — the portable substring predicate. `pattern` is bound as a
56
+ // parameter (no injection); the ESCAPE clause is literal SQL identical on both dialects.
57
+ function likeCol(col, pattern) {
58
+ return sql `lower(${col}) like ${pattern} escape '\\'`;
59
+ }
60
+ // A short excerpt of the matched text, centred on the earliest matched term (so the reader sees WHY
61
+ // it matched). Falls back to the head of the text when only the author matched.
62
+ function makeSnippet(body, terms) {
63
+ const lower = body.toLowerCase();
64
+ let idx = -1;
65
+ for (const t of terms) {
66
+ const i = lower.indexOf(t);
67
+ if (i >= 0 && (idx < 0 || i < idx))
68
+ idx = i;
69
+ }
70
+ if (idx < 0) {
71
+ return body.length > SNIPPET_RADIUS * 2 ? `${body.slice(0, SNIPPET_RADIUS * 2)}…` : body;
72
+ }
73
+ const start = Math.max(0, idx - SNIPPET_RADIUS);
74
+ const end = Math.min(body.length, idx + SNIPPET_RADIUS);
75
+ return `${start > 0 ? '…' : ''}${body.slice(start, end).trim()}${end < body.length ? '…' : ''}`;
76
+ }
77
+ const toIso = (d) => {
78
+ if (d == null)
79
+ return '';
80
+ const ms = d instanceof Date ? d.getTime() : Number(d) * (Number(d) > 1e12 ? 1 : 1000);
81
+ return new Date(ms).toISOString();
82
+ };
83
+ export async function searchPrs(accountId, opts) {
84
+ const query = opts.query.trim();
85
+ const terms = parseTerms(query);
86
+ // No terms, or an empty workspace → nothing to match.
87
+ if (terms.length === 0 || opts.repoIds.length === 0) {
88
+ return { query, hits: [], people: [], total: 0 };
89
+ }
90
+ // Each term must match the body OR the author's login/display name (ANDed across terms).
91
+ const termMatch = (term) => {
92
+ const pat = likePattern(term);
93
+ return or(likeCol(searchIndex.body, pat), likeCol(users.githubLogin, pat), likeCol(users.displayName, pat));
94
+ };
95
+ const repoFilter = inArray(searchIndex.repoId, opts.repoIds);
96
+ const kindFilter = opts.kinds && opts.kinds.length > 0 ? inArray(searchIndex.kind, opts.kinds) : undefined;
97
+ const where = and(eq(searchIndex.accountId, accountId), repoFilter, kindFilter, ...terms.map(termMatch));
98
+ const [rows, totalRows, people] = await Promise.all([
99
+ db
100
+ .select({
101
+ kind: searchIndex.kind,
102
+ prId: searchIndex.prId,
103
+ refId: searchIndex.refId,
104
+ threadId: searchIndex.threadId,
105
+ authorId: searchIndex.authorId,
106
+ body: searchIndex.body,
107
+ createdAt: searchIndex.createdAt,
108
+ prNumber: pullRequests.number,
109
+ prTitle: pullRequests.title,
110
+ prState: pullRequests.state,
111
+ repoId: repos.id,
112
+ repoOwner: repos.owner,
113
+ repoName: repos.name,
114
+ authorLogin: users.githubLogin,
115
+ authorAvatar: users.avatarUrl,
116
+ })
117
+ .from(searchIndex)
118
+ .innerJoin(pullRequests, eq(searchIndex.prId, pullRequests.id))
119
+ .innerJoin(repos, eq(searchIndex.repoId, repos.id))
120
+ .leftJoin(users, eq(searchIndex.authorId, users.id))
121
+ .where(where)
122
+ .orderBy(desc(searchIndex.createdAt))
123
+ .limit(opts.limit)
124
+ .offset(opts.offset)
125
+ .execute(),
126
+ db
127
+ .select({ n: sql `count(*)` })
128
+ // Only the users join is needed for the WHERE (author match); pullRequests/repos joins are
129
+ // display-only, so the count skips them.
130
+ .from(searchIndex)
131
+ .leftJoin(users, eq(searchIndex.authorId, users.id))
132
+ .where(where)
133
+ .execute(),
134
+ findPeople(accountId, opts.repoIds, terms),
135
+ ]);
136
+ const hits = rows.map((r) => ({
137
+ kind: r.kind,
138
+ prId: r.prId,
139
+ prNumber: r.prNumber,
140
+ prTitle: r.prTitle,
141
+ prState: r.prState,
142
+ repoId: r.repoId,
143
+ repoFullName: `${r.repoOwner}/${r.repoName}`,
144
+ refId: r.refId,
145
+ threadId: r.threadId,
146
+ authorId: r.authorId,
147
+ authorLogin: r.authorLogin ?? null,
148
+ authorAvatarUrl: r.authorAvatar ?? null,
149
+ snippet: makeSnippet(r.body, terms),
150
+ createdAt: toIso(r.createdAt),
151
+ }));
152
+ return { query, hits, people, total: Number(totalRows[0]?.n ?? 0) };
153
+ }
154
+ // The "People" facet: authors whose login/display name matches ANY term AND who have indexed
155
+ // activity in the searched repos (so the person is real + relevant), ranked by how much they've
156
+ // authored. `repoIds` is the same concrete workspace-bounded list `searchPrs` matched on — the
157
+ // caller has already returned empty for `[]`, so the facet can never widen past the hits.
158
+ async function findPeople(accountId, repoIds, terms) {
159
+ const nameMatch = or(...terms.map((t) => {
160
+ const pat = likePattern(t);
161
+ return or(likeCol(users.githubLogin, pat), likeCol(users.displayName, pat));
162
+ }));
163
+ const repoFilter = inArray(searchIndex.repoId, repoIds);
164
+ const n = sql `count(*)`;
165
+ const rows = await db
166
+ .select({
167
+ id: users.id,
168
+ login: users.githubLogin,
169
+ displayName: users.displayName,
170
+ avatarUrl: users.avatarUrl,
171
+ n,
172
+ })
173
+ .from(searchIndex)
174
+ .innerJoin(users, eq(searchIndex.authorId, users.id))
175
+ .where(and(eq(searchIndex.accountId, accountId), repoFilter, nameMatch))
176
+ .groupBy(users.id, users.githubLogin, users.displayName, users.avatarUrl)
177
+ .orderBy(desc(n))
178
+ .limit(10)
179
+ .execute();
180
+ return rows.map((r) => ({
181
+ id: r.id,
182
+ login: r.login,
183
+ displayName: r.displayName ?? null,
184
+ avatarUrl: r.avatarUrl ?? null,
185
+ matchCount: Number(r.n),
186
+ }));
187
+ }
188
+ // ---- backfill (startup) ------------------------------------------------------------------------
189
+ // Index PRs that predate the search feature from ALREADY-STORED data (title + review/comment
190
+ // bodies; the description only when pullRequests.body was persisted, i.e. non-lean — lean PRs get
191
+ // their description indexed on the next sync via bodyText). Idempotent: only touches PRs that have
192
+ // no search rows yet, in bounded batches, so a second run after a full pass is a cheap NOT-EXISTS
193
+ // scan. Safe to fire-and-forget at startup.
194
+ export async function backfillSearchIndex(batchSize = 200, maxBatches = 2000) {
195
+ let indexed = 0;
196
+ for (let b = 0; b < maxBatches; b++) {
197
+ const prRows = (await db
198
+ .select({
199
+ id: pullRequests.id,
200
+ accountId: pullRequests.accountId,
201
+ repoId: pullRequests.repoId,
202
+ title: pullRequests.title,
203
+ body: pullRequests.body,
204
+ authorId: pullRequests.authorId,
205
+ openedAt: pullRequests.openedAt,
206
+ })
207
+ .from(pullRequests)
208
+ .where(sql `not exists (select 1 from ${searchIndex} where ${searchIndex.prId} = ${pullRequests.id})`)
209
+ .limit(batchSize)
210
+ .execute());
211
+ if (prRows.length === 0)
212
+ break;
213
+ const prIds = prRows.map((p) => p.id);
214
+ const acctOf = new Map(prRows.map((p) => [p.id, p.accountId]));
215
+ const repoOf = new Map(prRows.map((p) => [p.id, p.repoId]));
216
+ const [revRows, rcRows, pcRows] = await Promise.all([
217
+ db
218
+ .select({ id: reviewsT.id, prId: reviewsT.prId, authorId: reviewsT.authorId, body: reviewsT.body, at: reviewsT.submittedAt })
219
+ .from(reviewsT)
220
+ .where(inArray(reviewsT.prId, prIds))
221
+ .execute(),
222
+ db
223
+ .select({ id: rcT.id, prId: rcT.prId, threadId: rcT.threadId, authorId: rcT.authorId, body: rcT.body, at: rcT.createdAt })
224
+ .from(rcT)
225
+ .where(inArray(rcT.prId, prIds))
226
+ .execute(),
227
+ db
228
+ .select({ id: pcT.id, prId: pcT.prId, authorId: pcT.authorId, body: pcT.body, at: pcT.createdAt })
229
+ .from(pcT)
230
+ .where(inArray(pcT.prId, prIds))
231
+ .execute(),
232
+ ]);
233
+ const rows = [];
234
+ for (const p of prRows) {
235
+ const text = searchText(p.title, p.body);
236
+ if (text)
237
+ rows.push({
238
+ accountId: p.accountId,
239
+ repoId: p.repoId,
240
+ prId: p.id,
241
+ kind: 'pr',
242
+ refId: p.id,
243
+ threadId: null,
244
+ authorId: p.authorId,
245
+ body: text,
246
+ createdAt: asDate(p.openedAt),
247
+ });
248
+ }
249
+ for (const r of revRows) {
250
+ const text = searchText(r.body);
251
+ if (text)
252
+ rows.push({
253
+ accountId: acctOf.get(r.prId),
254
+ repoId: repoOf.get(r.prId),
255
+ prId: r.prId,
256
+ kind: 'review',
257
+ refId: r.id,
258
+ threadId: null,
259
+ authorId: r.authorId,
260
+ body: text,
261
+ createdAt: asDate(r.at),
262
+ });
263
+ }
264
+ for (const c of rcRows) {
265
+ const text = searchText(c.body);
266
+ if (text)
267
+ rows.push({
268
+ accountId: acctOf.get(c.prId),
269
+ repoId: repoOf.get(c.prId),
270
+ prId: c.prId,
271
+ kind: 'review_comment',
272
+ refId: c.id,
273
+ threadId: c.threadId,
274
+ authorId: c.authorId,
275
+ body: text,
276
+ createdAt: asDate(c.at),
277
+ });
278
+ }
279
+ for (const c of pcRows) {
280
+ const text = searchText(c.body);
281
+ if (text)
282
+ rows.push({
283
+ accountId: acctOf.get(c.prId),
284
+ repoId: repoOf.get(c.prId),
285
+ prId: c.prId,
286
+ kind: 'pr_comment',
287
+ refId: c.id,
288
+ threadId: null,
289
+ authorId: c.authorId,
290
+ body: text,
291
+ createdAt: asDate(c.at),
292
+ });
293
+ }
294
+ // One transaction per batch. A concurrent live persistPr may have populated some of these PRs
295
+ // (with the richer, lean-safe rows from bodyText) BETWEEN this batch's un-indexed SELECT and
296
+ // here. Re-check inside the tx and INSERT only for PRs that STILL have no rows — never delete —
297
+ // so the backfill can neither clobber those fresh rows with its staler snapshot nor duplicate
298
+ // them. Chunked under SQLite's bound-variable limit.
299
+ await runTransaction(async (tx) => {
300
+ const already = (await tx
301
+ .select({ prId: searchIndex.prId })
302
+ .from(searchIndex)
303
+ .where(inArray(searchIndex.prId, prIds))
304
+ .execute());
305
+ const populated = new Set(already.map((r) => r.prId));
306
+ const fresh = rows.filter((r) => !populated.has(r.prId));
307
+ for (let i = 0; i < fresh.length; i += 100) {
308
+ await tx.insert(searchIndex).values(fresh.slice(i, i + 100)).execute();
309
+ }
310
+ });
311
+ indexed += prRows.length;
312
+ if (prRows.length < batchSize)
313
+ break; // last partial batch
314
+ }
315
+ return indexed;
316
+ }
317
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1,218 @@
1
+ // ── LIVE AUTOMATION SECURITY ALERTS, DERIVED ON READ (CORE, deterministic, no AI) ──────────────
2
+ //
3
+ // The Dependencies tab's second source of `security` cards: a security TOOL's own comment on a PR
4
+ // naming a KNOWN ADVISORY (a CVE / GHSA / … id) — Socket's "Critical CVE" report, a Dependency
5
+ // Review failure, an AI reviewer's own thread finding. Nothing is stored: every read re-derives the
6
+ // alert from the comment rows the sync already persists (comment and review bodies are ALWAYS
7
+ // persisted), so an alert clears itself by STATE — the tool's latest comment stops naming one, or
8
+ // the review thread is resolved / likely addressed — with no tombstone to forget.
9
+ //
10
+ // The rules are the detector's (sync/security-detect.ts `evaluateSecurityAlerts`); this file only
11
+ // reads the candidate rows. ⚠ The SQL pre-filter is built straight from `SECURITY_ALERT_PREFILTER`
12
+ // and may be LOOSER than the evaluator, never stricter: a tool's "all clear" (Socket's "All alerts
13
+ // resolved") names no advisory, and it must still reach the evaluator, because it is the latest row
14
+ // that decides. SQLite's case-blind `LIKE` and an `_` in a literal both widen it; the evaluator
15
+ // re-applies every literal exact-case, so both dialects alert on the same rows.
16
+ //
17
+ // ⚠ COMMENTS AND REVIEWS ARE NARROWED TO THE TOOLS' OWN ACCOUNTS, and are still exactly as loose:
18
+ // on those two surfaces an author-gated rule reads only its own tool's login, an author-free rule
19
+ // (Frogbot, Checkmarx — they post through github-actions or a person's token) reads anyone's row
20
+ // that carries its marker, and the `reviewer` fallback never reads them at all. THREADS are not
21
+ // narrowed: that fallback reads every automated root carrying any literal, which on Postgres (a
22
+ // case-sensitive `LIKE`) no narrower SQL can promise to keep. COST, measured on a copy of the dev DB
23
+ // over Erxes' 83 active PRs, whose 5.2 MB of text is 99.8% automation's own (CodeRabbit and co.):
24
+ // all 18 literals over every comment and review took ~65 ms of a ~400 ms fold; the narrowed reads
25
+ // take ~10 ms. Threads stay at ~60 ms.
26
+ import { and, asc, eq, inArray, like, ne, or, sql } from 'drizzle-orm';
27
+ import { normalizeBotLogin } from '../shared/index.js';
28
+ import { db, schema } from './client.js';
29
+ import { evaluateSecurityAlerts, SECURITY_ALERT_PREFILTER, SECURITY_ALERT_RULES, } from '../sync/security-detect.js';
30
+ const { prComments, reviews, reviewComments, reviewThreads, users } = schema;
31
+ /** Each AUTHOR-FREE rule's marker (`author: null`): a `SECURITY_ALERT_PREFILTER` literal that every
32
+ * body the rule `identifies` contains, so a person's row carrying it still reaches the evaluator.
33
+ * pending-deps.test.ts holds this to the rules. */
34
+ export const AUTHOR_FREE_ALERT_MARKERS = {
35
+ frogbot: 'Frogbot',
36
+ checkmarx: 'Checkmarx One',
37
+ };
38
+ /** `body LIKE '%s1%' OR body LIKE '%s2%' OR …` over the given literals. */
39
+ function anyLike(body, literals) {
40
+ return or(...literals.map((s) => like(body, `%${s}%`)));
41
+ }
42
+ /** The rows of a COMMENT or REVIEW surface that may reach the evaluator — every row a rule reading
43
+ * that surface could use. `null` = no rule reads it from anyone here: skip the read.
44
+ * `toolIds` = the automated authors whose login an author-gated rule on this surface names. */
45
+ function surfaceCandidates(surface, authorId, body, toolIds) {
46
+ const parts = [];
47
+ if (toolIds.length > 0) {
48
+ parts.push(and(inArray(authorId, [...toolIds]), anyLike(body, SECURITY_ALERT_PREFILTER)));
49
+ }
50
+ for (const rule of SECURITY_ALERT_RULES) {
51
+ if (rule.author !== null || !rule.surfaces.includes(surface))
52
+ continue;
53
+ const marker = AUTHOR_FREE_ALERT_MARKERS[rule.source];
54
+ // An author-free rule with no marker here reads the whole pre-filter, from anyone.
55
+ parts.push(marker != null ? like(body, `%${marker}%`) : anyLike(body, SECURITY_ALERT_PREFILTER));
56
+ }
57
+ return parts.length > 0 ? or(...parts) : null;
58
+ }
59
+ /**
60
+ * Live automation ALERTS naming a known advisory, for the given open PRs. DB-only, no GitHub call.
61
+ * Every live alert per PR, newest first, uncapped here — the card builder slices what it shows and
62
+ * carries the full count beside it.
63
+ *
64
+ * ⚠ TENANCY: `pr_comments` / `reviews` / `review_comments` carry no account_id. Every read here is
65
+ * keyed by `pr_id IN (prIds)`, and the ONLY caller passes the fold's account-scoped `openPrIds`.
66
+ * Never export a variant that takes ids from a request.
67
+ */
68
+ export async function deriveSecurityAlerts(prIds, automatedIds, kindOf) {
69
+ if (prIds.length === 0)
70
+ return new Map();
71
+ const ids = [...prIds];
72
+ const threadBody = sql `coalesce(${reviewComments.body}, ${reviewComments.excerpt})`;
73
+ // The tools' own accounts among the automated authors. `users` is GLOBAL — read by id only.
74
+ const automatedLogins = automatedIds.size > 0
75
+ ? await db
76
+ .select({ id: users.id, login: users.githubLogin })
77
+ .from(users)
78
+ .where(inArray(users.id, [...automatedIds]))
79
+ .execute()
80
+ : [];
81
+ const toolIdsFor = (surface) => automatedLogins
82
+ .filter((u) => SECURITY_ALERT_RULES.some((r) => r.author !== null && r.surfaces.includes(surface) && r.author(normalizeBotLogin(u.login))))
83
+ .map((u) => u.id);
84
+ const commentWhere = surfaceCandidates('comment', prComments.authorId, prComments.body, toolIdsFor('comment'));
85
+ const reviewWhere = surfaceCandidates('review', reviews.authorId, reviews.body, toolIdsFor('review'));
86
+ const [commentRows, reviewRows, threadRows] = await Promise.all([
87
+ commentWhere == null
88
+ ? []
89
+ : db
90
+ .select({
91
+ id: prComments.id,
92
+ prId: prComments.prId,
93
+ authorId: prComments.authorId,
94
+ body: prComments.body,
95
+ createdAt: prComments.createdAt,
96
+ })
97
+ .from(prComments)
98
+ .where(and(inArray(prComments.prId, ids), commentWhere))
99
+ .execute(),
100
+ reviewWhere == null
101
+ ? []
102
+ : db
103
+ .select({
104
+ id: reviews.id,
105
+ prId: reviews.prId,
106
+ authorId: reviews.authorId,
107
+ body: reviews.body,
108
+ createdAt: reviews.submittedAt,
109
+ })
110
+ .from(reviews)
111
+ // A 'pending' review is an unsubmitted draft — it has said nothing yet.
112
+ .where(and(inArray(reviews.prId, ids), ne(reviews.state, 'pending'), reviewWhere))
113
+ .execute(),
114
+ db
115
+ .select({
116
+ id: reviewComments.id,
117
+ prId: reviewComments.prId,
118
+ authorId: reviewComments.authorId,
119
+ body: threadBody,
120
+ createdAt: reviewComments.createdAt,
121
+ threadId: reviewComments.threadId,
122
+ isResolved: reviewThreads.isResolved,
123
+ derivedState: reviewThreads.derivedState,
124
+ })
125
+ .from(reviewComments)
126
+ .innerJoin(reviewThreads, eq(reviewThreads.id, reviewComments.threadId))
127
+ .where(and(inArray(reviewComments.prId, ids), anyLike(threadBody, SECURITY_ALERT_PREFILTER)))
128
+ .execute(),
129
+ ]);
130
+ // WHICH candidate is its thread's ROOT — the earliest (created_at, id) of the WHOLE thread, not
131
+ // of the candidates (a reply can pass the pre-filter while the root does not). One extra read over
132
+ // the candidate threads, on `rc_thread_idx`; no correlated subquery, so it stays portable.
133
+ const threadIds = [...new Set(threadRows.map((r) => r.threadId))];
134
+ const rootIdOf = new Map();
135
+ if (threadIds.length > 0) {
136
+ const all = await db
137
+ .select({ id: reviewComments.id, threadId: reviewComments.threadId })
138
+ .from(reviewComments)
139
+ .where(inArray(reviewComments.threadId, threadIds))
140
+ .orderBy(asc(reviewComments.createdAt), asc(reviewComments.id))
141
+ .execute();
142
+ // Ordered (created_at, id), so the first row seen per thread is its root.
143
+ for (const r of all)
144
+ if (!rootIdOf.has(r.threadId))
145
+ rootIdOf.set(r.threadId, r.id);
146
+ }
147
+ // Logins, for the rules that name their tool's account. `users` is GLOBAL — read by id only.
148
+ const authorIds = [
149
+ ...new Set([...commentRows, ...reviewRows, ...threadRows]
150
+ .map((r) => r.authorId)
151
+ .filter((id) => id != null)),
152
+ ];
153
+ const loginOf = new Map();
154
+ if (authorIds.length > 0) {
155
+ for (const u of await db
156
+ .select({ id: users.id, login: users.githubLogin })
157
+ .from(users)
158
+ .where(inArray(users.id, authorIds))
159
+ .execute())
160
+ loginOf.set(u.id, u.login);
161
+ }
162
+ const login = (id) => (id != null ? loginOf.get(id) ?? null : null);
163
+ const rows = [
164
+ ...commentRows.map((r) => ({
165
+ surface: 'comment',
166
+ rowId: r.id,
167
+ prId: r.prId,
168
+ authorId: r.authorId,
169
+ authorLogin: login(r.authorId),
170
+ body: r.body ?? '',
171
+ createdAt: r.createdAt,
172
+ threadId: null,
173
+ isRoot: false,
174
+ threadResolved: false,
175
+ threadState: null,
176
+ })),
177
+ ...reviewRows.map((r) => ({
178
+ surface: 'review',
179
+ rowId: r.id,
180
+ prId: r.prId,
181
+ authorId: r.authorId,
182
+ authorLogin: login(r.authorId),
183
+ body: r.body ?? '',
184
+ createdAt: r.createdAt,
185
+ threadId: null,
186
+ isRoot: false,
187
+ threadResolved: false,
188
+ threadState: null,
189
+ })),
190
+ ...threadRows.map((r) => ({
191
+ surface: 'thread',
192
+ rowId: r.id,
193
+ prId: r.prId,
194
+ authorId: r.authorId,
195
+ authorLogin: login(r.authorId),
196
+ body: r.body ?? '',
197
+ createdAt: r.createdAt,
198
+ threadId: r.threadId,
199
+ isRoot: rootIdOf.get(r.threadId) === r.id,
200
+ threadResolved: r.isResolved,
201
+ threadState: r.derivedState,
202
+ })),
203
+ ];
204
+ const out = new Map();
205
+ for (const [prId, alerts] of evaluateSecurityAlerts(rows, automatedIds)) {
206
+ out.set(prId, alerts.map((a) => ({
207
+ source: a.source,
208
+ authorId: a.authorId,
209
+ vendorKind: kindOf.get(a.authorId) ?? null,
210
+ surface: a.surface,
211
+ threadId: a.threadId,
212
+ advisoryIds: a.advisoryIds,
213
+ at: a.at.toISOString(),
214
+ })));
215
+ }
216
+ return out;
217
+ }
218
+ //# sourceMappingURL=security-alerts.js.map