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,482 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { eq } from 'drizzle-orm';
3
+ import { compactMyTurnSettings } from '../shared/index.js';
4
+ import { db, schema } from '../db/client.js';
5
+ import { getGithubTokenAsync } from '../github/auth.js';
6
+ import { decryptToken } from './crypto.js';
7
+ const STALE_MS = 24 * 60 * 60 * 1000; // re-fetch the local identity once a day
8
+ // The synthesized local account is always id 1 (seeded by migration 0008).
9
+ export const LOCAL_ACCOUNT_ID = 1;
10
+ function fetchFromGh() {
11
+ try {
12
+ const out = execFileSync('gh', ['api', 'user'], { encoding: 'utf-8' });
13
+ const parsed = JSON.parse(out);
14
+ if (!parsed.login || !parsed.node_id)
15
+ return null;
16
+ return {
17
+ login: parsed.login,
18
+ node_id: parsed.node_id,
19
+ name: parsed.name ?? null,
20
+ avatar_url: parsed.avatar_url ?? null,
21
+ };
22
+ }
23
+ catch {
24
+ // gh missing / not authed / offline — non-fatal; my-turn just stays empty.
25
+ return null;
26
+ }
27
+ }
28
+ function rowToAccount(row) {
29
+ return {
30
+ id: row.id,
31
+ githubUserId: row.githubUserId,
32
+ githubLogin: row.githubLogin,
33
+ displayName: row.displayName,
34
+ avatarUrl: row.avatarUrl,
35
+ isLocal: row.isLocal,
36
+ plan: row.plan === 'pro' ? 'pro' : 'free',
37
+ stripeCustomerId: row.stripeCustomerId,
38
+ aiCreditAllowance: row.aiCreditAllowance ?? null,
39
+ benchmarkOptIn: row.benchmarkOptIn ?? false,
40
+ largePrCodeLocThreshold: row.largePrCodeLocThreshold ?? null,
41
+ blastRadiusConfig: row.blastRadiusConfig ?? null,
42
+ myTurnSettings: row.myTurnSettings ?? null,
43
+ };
44
+ }
45
+ // Module cache of the local account, set by ensureLocalAccount() at startup so
46
+ // the per-request auth hook can resolve it synchronously.
47
+ let cachedLocalAccount = null;
48
+ /**
49
+ * Guarantee the account's DEFAULT WORKSPACE exists before the account is handed to anyone.
50
+ *
51
+ * A workspace id is the only scope this app has: every scoped read resolves one, repos land in
52
+ * the Default on sync, and reviewer rows key on it. An account with no workspace therefore has
53
+ * no reachable state at all — not an empty board but a resolver with nothing to return — so both
54
+ * account-creation paths below close the gap at the moment the row is created. Migration 0044
55
+ * backfills a Default for every account that existed when it ran; this covers every account
56
+ * created afterwards, which in cloud is all of them.
57
+ *
58
+ * `db/queries.ts` STATICALLY imports `getAccountUserId` from this file, so the import here is
59
+ * DYNAMIC to avoid closing that cycle — the same shape `setBenchmarkConsent` below already uses
60
+ * for the same reason. It costs nothing: both callers are cold (process startup / an OAuth
61
+ * callback) and the module is cached after the first call.
62
+ *
63
+ * It is deliberately NOT swallowed. `ensureDefaultWorkspace` only throws when the partial unique
64
+ * index `workspaces_one_default` is missing, i.e. migration 0044 never ran — and both callers run
65
+ * `runMigrations()` first. Failing there names the real fault; swallowing it would instead have
66
+ * every later request resolve a scope that does not exist.
67
+ */
68
+ async function ensureAccountWorkspace(accountId) {
69
+ const { ensureDefaultWorkspace } = await import('../db/queries.js');
70
+ await ensureDefaultWorkspace(accountId);
71
+ }
72
+ /**
73
+ * Ensure the synthesized local account (id 1) reflects the locally-authenticated
74
+ * GitHub user, and that it owns a Default workspace. Refetches via `gh api user`
75
+ * on first run and once per day; otherwise returns the cached row.
76
+ *
77
+ * A missing/unauthenticated/offline `gh` is non-fatal — it leaves "you" unknown and
78
+ * triage degrades gracefully to empty. (A failing DB is not: see
79
+ * `ensureAccountWorkspace`.) Local mode only.
80
+ */
81
+ export async function ensureLocalAccount() {
82
+ const account = await resolveLocalAccount();
83
+ cachedLocalAccount = account;
84
+ // ONE call site covering all three resolution paths — the fresh cache hit included, since an
85
+ // account row that predates the workspace tables reaches this function by that path too.
86
+ if (account)
87
+ await ensureAccountWorkspace(account.id);
88
+ return account;
89
+ }
90
+ /** The `accounts` half of ensureLocalAccount: resolve/refresh the row, no workspace, no caching. */
91
+ async function resolveLocalAccount() {
92
+ const { accounts } = schema;
93
+ const existingRows = await db
94
+ .select()
95
+ .from(accounts)
96
+ .where(eq(accounts.id, LOCAL_ACCOUNT_ID))
97
+ .limit(1)
98
+ .execute();
99
+ const existing = existingRows[0] ?? null;
100
+ const fresh = existing &&
101
+ existing.githubUserId !== '' &&
102
+ existing.lastLoginAt != null &&
103
+ Date.now() - existing.lastLoginAt.getTime() < STALE_MS &&
104
+ // Backfill the display name on the first run after it was added (older rows have
105
+ // it NULL). A genuinely name-less GitHub user re-fetches each startup — cheap;
106
+ // the daily refresh would repopulate it anyway.
107
+ existing.displayName != null;
108
+ if (existing && fresh)
109
+ return rowToAccount(existing);
110
+ const gh = fetchFromGh();
111
+ if (!gh) {
112
+ // Fall back to whatever row exists (the migration seeds a placeholder).
113
+ return existing ? rowToAccount(existing) : null;
114
+ }
115
+ const updatedRows = await db
116
+ .insert(accounts)
117
+ .values({
118
+ id: LOCAL_ACCOUNT_ID,
119
+ githubUserId: gh.node_id,
120
+ githubLogin: gh.login,
121
+ displayName: gh.name,
122
+ avatarUrl: gh.avatar_url,
123
+ isLocal: true,
124
+ lastLoginAt: new Date(),
125
+ })
126
+ .onConflictDoUpdate({
127
+ target: accounts.id,
128
+ set: {
129
+ githubUserId: gh.node_id,
130
+ githubLogin: gh.login,
131
+ displayName: gh.name,
132
+ avatarUrl: gh.avatar_url,
133
+ isLocal: true,
134
+ lastLoginAt: new Date(),
135
+ },
136
+ })
137
+ .returning()
138
+ .execute();
139
+ return updatedRows[0] ? rowToAccount(updatedRows[0]) : null;
140
+ }
141
+ /** The local account from the module cache (no network / no DB). */
142
+ export function getLocalAccountCached() {
143
+ return cachedLocalAccount;
144
+ }
145
+ /**
146
+ * Re-read the LOCAL account's row into the module cache after a column write.
147
+ *
148
+ * ⚠ WITHOUT THIS, A PER-ACCOUNT SETTING SILENTLY DOES NOT SAVE IN LOCAL MODE. The cache is
149
+ * populated once at startup by `ensureLocalAccount` and the per-request hook serves `req.account`
150
+ * straight out of it, so `/api/me` keeps echoing the value the process booted with. The write
151
+ * route's own response is correct, so the SPA paints the new setting — and is then told the old
152
+ * one by the `['me']` refetch its mutation triggers, which reverts the control the user just used.
153
+ * Measured on the shipping large-PR threshold before this existed: POST 900 → 200 OK with
154
+ * `{threshold: 900}`, then `/api/me` → `{threshold: 1500, isDefault: true}`.
155
+ *
156
+ * A no-op in cloud (there is no cache: `registerAccountContext` reads the row per request) and a
157
+ * no-op for any account that is not the cached one.
158
+ */
159
+ export async function refreshLocalAccountCache(accountId) {
160
+ if (cachedLocalAccount == null || cachedLocalAccount.id !== accountId)
161
+ return;
162
+ const fresh = await getAccountById(accountId);
163
+ if (fresh)
164
+ cachedLocalAccount = fresh;
165
+ }
166
+ /**
167
+ * Upsert a cloud account from a completed OAuth sign-in (keyed on the GitHub
168
+ * user node id). Re-login refreshes the login/avatar, the encrypted token, and
169
+ * lastLoginAt, and guarantees the account's Default workspace. Returns the account.
170
+ *
171
+ * This is the ONLY path that creates a cloud account, so it is the only place a
172
+ * brand-new tenant can be given the one scope the app has — migration 0044's
173
+ * backfill can only reach accounts that already existed when it ran.
174
+ */
175
+ export async function upsertCloudAccount(input) {
176
+ const { accounts } = schema;
177
+ const now = new Date();
178
+ const rows = await db
179
+ .insert(accounts)
180
+ .values({
181
+ githubUserId: input.githubUserId,
182
+ githubLogin: input.githubLogin,
183
+ displayName: input.displayName,
184
+ avatarUrl: input.avatarUrl,
185
+ accessTokenEnc: input.accessTokenEnc,
186
+ isLocal: false,
187
+ lastLoginAt: now,
188
+ // Seed activity at sign-in so the user's repos are eligible on the very next
189
+ // scheduled sync tick (don't wait for the first heartbeat).
190
+ lastActiveAt: now,
191
+ })
192
+ .onConflictDoUpdate({
193
+ target: accounts.githubUserId,
194
+ set: {
195
+ githubLogin: input.githubLogin,
196
+ displayName: input.displayName,
197
+ avatarUrl: input.avatarUrl,
198
+ accessTokenEnc: input.accessTokenEnc,
199
+ lastLoginAt: now,
200
+ lastActiveAt: now,
201
+ },
202
+ })
203
+ .returning()
204
+ .execute();
205
+ const account = rowToAccount(rows[0]);
206
+ // Before returning: the callback redirects straight to /app, which immediately resolves a
207
+ // workspace scope. Creating it here (not lazily on that first request) means a sign-in that
208
+ // cannot produce a usable tenant fails at sign-in, where the error is attributable.
209
+ await ensureAccountWorkspace(account.id);
210
+ return account;
211
+ }
212
+ // In-memory throttle for the activity stamp below: accountId → last-stamp epoch ms.
213
+ // A loaded SPA is chatty (timeline, polls, heartbeat), so we only touch the DB at
214
+ // most once per window per account.
215
+ const lastActiveStampMs = new Map();
216
+ const ACTIVE_STAMP_THROTTLE_MS = 60_000;
217
+ /**
218
+ * Record that a loaded frontend for this account just talked to the backend
219
+ * (drives the scheduler's "only sync accounts with an open tab" gate). Throttled
220
+ * in-memory and fire-and-forget — a dropped stamp only means a slightly staler
221
+ * signal, and the next request re-stamps. Cloud-only in practice (the caller gates
222
+ * on isCloud; local has a single always-synced account).
223
+ */
224
+ export function stampAccountActive(accountId) {
225
+ const now = Date.now();
226
+ const last = lastActiveStampMs.get(accountId) ?? 0;
227
+ if (now - last < ACTIVE_STAMP_THROTTLE_MS)
228
+ return;
229
+ lastActiveStampMs.set(accountId, now);
230
+ const { accounts } = schema;
231
+ void db
232
+ .update(accounts)
233
+ .set({ lastActiveAt: new Date() })
234
+ .where(eq(accounts.id, accountId))
235
+ .execute()
236
+ .catch(() => {
237
+ // Best-effort: roll back the throttle so the next request retries the stamp.
238
+ lastActiveStampMs.delete(accountId);
239
+ });
240
+ }
241
+ /** Load an account by id. */
242
+ export async function getAccountById(id) {
243
+ const { accounts } = schema;
244
+ const rows = await db
245
+ .select()
246
+ .from(accounts)
247
+ .where(eq(accounts.id, id))
248
+ .limit(1)
249
+ .execute();
250
+ return rows[0] ? rowToAccount(rows[0]) : null;
251
+ }
252
+ /**
253
+ * Set an account's billing plan (and, when known, its Stripe customer id).
254
+ * Called only by the Stripe webhook handler (api/routes/billing.ts).
255
+ */
256
+ export async function setAccountPlan(accountId, plan, stripeCustomerId) {
257
+ const { accounts } = schema;
258
+ const set = { plan };
259
+ if (stripeCustomerId != null)
260
+ set.stripeCustomerId = stripeCustomerId;
261
+ await db.update(accounts).set(set).where(eq(accounts.id, accountId)).execute();
262
+ }
263
+ /**
264
+ * Set an account's cross-org benchmark consent (cloud-only feature). Withdrawing (optIn=false)
265
+ * DELETES the account's contributions — one-click, complete removal, honouring the consent
266
+ * promise. The caller (the /api/me/benchmark-consent route) seeds contributions on opt-in.
267
+ */
268
+ export async function setBenchmarkConsent(accountId, optIn) {
269
+ const { accounts } = schema;
270
+ await db
271
+ .update(accounts)
272
+ .set({ benchmarkOptIn: optIn })
273
+ .where(eq(accounts.id, accountId))
274
+ .execute();
275
+ if (!optIn) {
276
+ const { deleteBenchmarkContributions } = await import('../db/queries.js');
277
+ await deleteBenchmarkContributions(accountId);
278
+ }
279
+ }
280
+ /**
281
+ * Set (or clear) an account's LARGE-PR FLAG threshold, in lines of CODE churn.
282
+ *
283
+ * `null` CLEARS it — back to the two-state "no opinion → product default", which is what makes a
284
+ * later change to that default reach every account that never overrode it. It is a column write,
285
+ * never a delete of anything, and it is deliberately validated HERE as well as by the route's
286
+ * schema: a threshold of 0 or a fraction would flag every PR or none, silently.
287
+ */
288
+ export async function setLargePrCodeLocThreshold(accountId, threshold) {
289
+ const { accounts } = schema;
290
+ const value = threshold != null && Number.isInteger(threshold) && threshold > 0 ? threshold : null;
291
+ await db
292
+ .update(accounts)
293
+ .set({ largePrCodeLocThreshold: value })
294
+ .where(eq(accounts.id, accountId))
295
+ .execute();
296
+ // ⚠ REQUIRED, not tidiness — see refreshLocalAccountCache. Without it this setting appears not
297
+ // to save in local mode: the route 200s with the new number and the SPA's own `['me']` refetch
298
+ // immediately reports the old one.
299
+ await refreshLocalAccountCache(accountId);
300
+ }
301
+ // The runtime spellings of two `packages/shared` unions. ⚠ MIRRORED ON PURPOSE, and this is the
302
+ // small mirror the design chose over the big one: `shared` is types-only on this side
303
+ // (PACKAGING), so a route that validates a stored enum needs the members as VALUES. Ten strings
304
+ // and three, versus the 18-number BLAST_THRESHOLDS table the SPA resolves instead. If a member is
305
+ // added to either union in shared, add it here — `blast-radius-config.test.ts` pins the count so
306
+ // the omission fails rather than silently rejecting a valid write.
307
+ const BLAST_SURFACE_VALUES = [
308
+ 'db_migration',
309
+ 'db_schema',
310
+ 'sql',
311
+ 'public_types',
312
+ 'idl',
313
+ 'openapi',
314
+ 'infra',
315
+ 'auth',
316
+ 'ci',
317
+ 'deps',
318
+ ];
319
+ const BLAST_SENSITIVITY_VALUES = ['cautious', 'balanced', 'relaxed'];
320
+ const BLAST_THRESHOLD_KEYS = [
321
+ 'highCodeLoc',
322
+ 'highCodeFiles',
323
+ 'highDirs',
324
+ 'highSubsystems',
325
+ 'lowCodeLoc',
326
+ 'lowCodeFiles',
327
+ ];
328
+ /**
329
+ * Validate an untrusted blast-radius config into the shape the column may hold, or `null`.
330
+ *
331
+ * ⚠ RETURNS null FOR ANYTHING IT CANNOT FULLY VALIDATE, which is the same "no opinion" state a
332
+ * cleared setting has — never a partially-applied blob. An unknown surface string is DROPPED
333
+ * rather than failing the whole write, so an older backend reading a newer client's payload
334
+ * degrades to ignoring one opt-out instead of discarding the user's dial.
335
+ */
336
+ export function sanitizeBlastRadiusConfig(input) {
337
+ if (input == null || typeof input !== 'object' || Array.isArray(input))
338
+ return null;
339
+ const raw = input;
340
+ const sensitivity = BLAST_SENSITIVITY_VALUES.find((v) => v === raw.sensitivity);
341
+ if (sensitivity == null)
342
+ return null;
343
+ const surfacesOff = Array.isArray(raw.surfacesOff)
344
+ ? BLAST_SURFACE_VALUES.filter((v) => raw.surfacesOff.includes(v))
345
+ : [];
346
+ let overrides;
347
+ if (raw.overrides != null && typeof raw.overrides === 'object' && !Array.isArray(raw.overrides)) {
348
+ const src = raw.overrides;
349
+ const out = {};
350
+ for (const k of BLAST_THRESHOLD_KEYS) {
351
+ const v = src[k];
352
+ // Same rule as the large-PR threshold: a 0 or a fraction would bucket everything or
353
+ // nothing, silently. Only a positive integer is an opinion.
354
+ if (typeof v === 'number' && Number.isInteger(v) && v > 0)
355
+ out[k] = v;
356
+ }
357
+ if (Object.keys(out).length > 0)
358
+ overrides = out;
359
+ }
360
+ // ⚠ ONLY STORED WHEN IT IS `false`. `showImpactNote` defaults to SHOWN, so writing `true`
361
+ // would persist the product default as a choice — the same two-state rule the whole config
362
+ // follows, one field down. A blob whose only content was `{showImpactNote: true}` would also
363
+ // stop `isDefault` reading true in Settings for a user who changed nothing that matters.
364
+ const hide = raw.showImpactNote === false;
365
+ const base = { sensitivity, surfacesOff };
366
+ if (overrides)
367
+ base.overrides = overrides;
368
+ if (hide)
369
+ base.showImpactNote = false;
370
+ return base;
371
+ }
372
+ /**
373
+ * Set (or clear) an account's BLAST-RADIUS reading settings.
374
+ *
375
+ * `null` — and any payload that fails validation — CLEARS the column, back to the two-state
376
+ * "no opinion → product defaults". Exactly the rule `setLargePrCodeLocThreshold` follows, and for
377
+ * the same reason: a stored blob would freeze this account against a later change to those
378
+ * defaults.
379
+ */
380
+ export async function setBlastRadiusConfig(accountId, config) {
381
+ const { accounts } = schema;
382
+ const value = sanitizeBlastRadiusConfig(config);
383
+ await db
384
+ .update(accounts)
385
+ .set({ blastRadiusConfig: value })
386
+ .where(eq(accounts.id, accountId))
387
+ .execute();
388
+ // ⚠ Same rule as the threshold above, and the same failure without it.
389
+ await refreshLocalAccountCache(accountId);
390
+ return value;
391
+ }
392
+ /**
393
+ * Set (or clear) an account's MY TURN settings, and return what was STORED.
394
+ *
395
+ * The value is COMPACTED first (`compactMyTurnSettings`, the ONE definition of "an override" the
396
+ * Settings form builds its body with too), so a default is never frozen into the row — `null`, or
397
+ * anything that compacts to nothing, stores NULL and the account follows the product defaults
398
+ * again. Validate BEFORE calling: compaction drops a malformed part silently, which is right for a
399
+ * stored value and wrong for a request.
400
+ */
401
+ export async function setMyTurnSettings(accountId, settings) {
402
+ const { accounts } = schema;
403
+ const value = compactMyTurnSettings(settings);
404
+ await db
405
+ .update(accounts)
406
+ .set({ myTurnSettings: value })
407
+ .where(eq(accounts.id, accountId))
408
+ .execute();
409
+ // ⚠ Same rule as the two settings above, and the same failure without it: Settings would save,
410
+ // then `/api/me` would hand the old value back and revert the form.
411
+ await refreshLocalAccountCache(accountId);
412
+ return value;
413
+ }
414
+ /** Resolve an account by its Stripe customer id (subscription webhooks). */
415
+ export async function getAccountByStripeCustomerId(customerId) {
416
+ const { accounts } = schema;
417
+ const rows = await db
418
+ .select()
419
+ .from(accounts)
420
+ .where(eq(accounts.stripeCustomerId, customerId))
421
+ .limit(1)
422
+ .execute();
423
+ return rows[0] ? rowToAccount(rows[0]) : null;
424
+ }
425
+ /**
426
+ * Resolve an account's owner to a row in `users` (by login) if they've appeared
427
+ * in any synced repo. Returns null when "you" haven't authored/acted anywhere
428
+ * yet. This is the "who am I" used by all triage ("my turn").
429
+ */
430
+ export async function getAccountUserId(accountId) {
431
+ const account = await getAccountById(accountId);
432
+ if (!account || !account.githubLogin)
433
+ return null;
434
+ const { users } = schema;
435
+ const rows = await db
436
+ .select({ id: users.id })
437
+ .from(users)
438
+ .where(eq(users.githubLogin, account.githubLogin))
439
+ .limit(1)
440
+ .execute();
441
+ return rows[0]?.id ?? null;
442
+ }
443
+ /**
444
+ * Resolve the GitHub access token to use for an account's API calls.
445
+ * - Local account (isLocal): live `gh auth token` (no token stored).
446
+ * - Cloud account: decrypt the stored AES-256-GCM sealed token.
447
+ * Throws if the account is missing or a cloud account has no stored token.
448
+ */
449
+ export async function getAccessToken(accountId) {
450
+ const { accounts } = schema;
451
+ const rows = await db
452
+ .select()
453
+ .from(accounts)
454
+ .where(eq(accounts.id, accountId))
455
+ .limit(1)
456
+ .execute();
457
+ const row = rows[0];
458
+ if (!row)
459
+ throw new Error(`account ${accountId} not found`);
460
+ // The ASYNC form: this runs on request paths (PR-detail hydration, repo search, every write
461
+ // action), and the synchronous `gh auth token` it used to call blocked the event loop for
462
+ // 50–300ms per call while forking a child process. Both are now cached with a short TTL and
463
+ // share one in-flight invocation — see github/auth.ts.
464
+ if (row.isLocal)
465
+ return getGithubTokenAsync();
466
+ if (!row.accessTokenEnc) {
467
+ throw new Error(`account ${accountId} has no stored access token (re-auth needed)`);
468
+ }
469
+ return decryptToken(row.accessTokenEnc);
470
+ }
471
+ /** Shape an Account as the legacy LocalUser wire type for /api/me. */
472
+ export function accountToLocalUser(account) {
473
+ if (!account || !account.githubLogin)
474
+ return null;
475
+ return {
476
+ login: account.githubLogin,
477
+ githubId: account.githubUserId,
478
+ avatarUrl: account.avatarUrl,
479
+ displayName: account.displayName,
480
+ };
481
+ }
482
+ //# sourceMappingURL=account.js.map
@@ -0,0 +1,46 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
2
+ import { config } from '../config.js';
3
+ // AES-256-GCM sealing of per-account GitHub access tokens at rest. The key is
4
+ // ENCRYPTION_KEY (32 bytes as 64 hex chars; generate with `openssl rand -hex 32`).
5
+ // Stored form is `iv:tag:ciphertext`, each base64. Tokens are decrypted in-memory
6
+ // per call and NEVER logged.
7
+ const ALGO = 'aes-256-gcm';
8
+ function key() {
9
+ const k = Buffer.from(config.encryptionKey, 'hex');
10
+ if (k.length !== 32) {
11
+ throw new Error('ENCRYPTION_KEY must be 32 bytes as 64 hex chars (openssl rand -hex 32).');
12
+ }
13
+ return k;
14
+ }
15
+ export function encryptToken(plain) {
16
+ const iv = randomBytes(12);
17
+ const cipher = createCipheriv(ALGO, key(), iv);
18
+ const ciphertext = Buffer.concat([cipher.update(plain, 'utf8'), cipher.final()]);
19
+ const tag = cipher.getAuthTag();
20
+ return [
21
+ iv.toString('base64'),
22
+ tag.toString('base64'),
23
+ ciphertext.toString('base64'),
24
+ ].join(':');
25
+ }
26
+ export function decryptToken(enc) {
27
+ const parts = enc.split(':');
28
+ if (parts.length !== 3)
29
+ throw new Error('malformed encrypted token');
30
+ const [ivB64, tagB64, ctB64] = parts;
31
+ const decipher = createDecipheriv(ALGO, key(), Buffer.from(ivB64, 'base64'));
32
+ decipher.setAuthTag(Buffer.from(tagB64, 'base64'));
33
+ return Buffer.concat([
34
+ decipher.update(Buffer.from(ctB64, 'base64')),
35
+ decipher.final(),
36
+ ]).toString('utf8');
37
+ }
38
+ // Whether sealing is available on this process: ENCRYPTION_KEY is set and is 32 bytes. Always true
39
+ // in cloud (`assertCloudConfig` refuses to boot otherwise); true locally only when the operator set
40
+ // a key. The Pro plugin's optional `host.sealSecret` / `host.openSecret` seam is wired from this, so
41
+ // a plugin-stored secret (the per-workspace Jira token) is sealed wherever a key exists and stored
42
+ // plain only on a local install without one — the same trust as that machine's `gh` token.
43
+ export function sealingAvailable() {
44
+ return Buffer.from(config.encryptionKey, 'hex').length === 32;
45
+ }
46
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1,59 @@
1
+ import { getAccessToken } from '../auth/account.js';
2
+ import { setReviewThreadResolved } from '../github/mutations.js';
3
+ import { stampThreadResolved } from '../db/queries.js';
4
+ import { notePrChangedForPrs } from '../sync/pr-settle.js';
5
+ // Resolve a batch of already-eligible review threads on GitHub AND stamp them resolved
6
+ // locally. This is the ONE place the GitHub `resolveReviewThread` mutation is driven for
7
+ // bot-thread triage — shared verbatim by the manual per-PR `POST /api/prs/:id/resolve-bot-
8
+ // threads` route and the workspace-wide `POST /api/bot-threads/resolve`, so their behaviour
9
+ // can't drift. Both are strictly user-initiated + confirm-gated — there is no automatic/cron
10
+ // resolve.
11
+ //
12
+ // Callers own eligibility: `getResolvableBotThreads` (per-PR — the workspace comes from the PR's
13
+ // own repo, via `botScopeForPr`) / `getResolvableBotThreadsForScope` (workspace-wide — takes the
14
+ // `BotScope` the route built with `resolveWorkspaceScope`) return ONLY owned + automated-reviewer-
15
+ // originated + `likely_addressed` + unresolved threads (with a GitHub node id), so this helper
16
+ // never has to re-derive it.
17
+ //
18
+ // ⚠ THE CALLER'S RE-DERIVATION MUST RUN AT THE SAME WORKSPACE THE LISTING OFFERED FROM. "Is this
19
+ // login a bot" is answered once per workspace, so a listing resolved at workspace A and a resolve
20
+ // re-derived at workspace B disagree about which threads are bot threads — the listing offers a
21
+ // backlog and the resolve then finds nothing eligible, returning 0 with no error anywhere. That
22
+ // was a real, already-closed gap and it must not reopen: `ScopeResolveBotThreadsBody.workspaceId`
23
+ // is what makes the two sides structurally the same scope, and this helper cannot check it — by
24
+ // the time threads reach here the judgement has already been made.
25
+ //
26
+ // It NEVER merges — it only resolves. Sequential (not Promise.all) to stay gentle on the GraphQL
27
+ // rate limit (a bot backlog is a handful of threads); per-thread try/catch so one bad thread or a
28
+ // moved head never aborts the batch. The token is fetched once per batch from the OWNING account,
29
+ // so a bad/absent token throws BEFORE the loop — the caller (route or job) decides how to surface it.
30
+ //
31
+ // Each thread names its PR (`prId`, from the caller's own eligibility read) so the helper can raise
32
+ // the SPA change signal for every repo it stamped, ONCE, before the route replies — the stamps move
33
+ // the Pending thread cards, and no walk will report them (sync/pr-settle.ts `notePrChangedForPrs`).
34
+ export async function resolveThreadsOnGitHub(accountId, threads) {
35
+ const out = { resolved: 0, failed: 0, results: [] };
36
+ // Empty selection → a no-op (never even fetch a token) — matches the manual route's
37
+ // "stale client list / no eligible threads is a no-op, not an error" contract.
38
+ if (threads.length === 0)
39
+ return out;
40
+ const token = await getAccessToken(accountId);
41
+ const stampedPrIds = new Set();
42
+ for (const t of threads) {
43
+ try {
44
+ await setReviewThreadResolved(token, t.threadNodeId, true);
45
+ const derivedState = await stampThreadResolved(t.id, true, accountId);
46
+ out.results.push({ threadId: t.id, ok: true, derivedState: derivedState ?? 'resolved' });
47
+ out.resolved += 1;
48
+ stampedPrIds.add(t.prId);
49
+ }
50
+ catch {
51
+ out.results.push({ threadId: t.id, ok: false, derivedState: null });
52
+ out.failed += 1;
53
+ }
54
+ }
55
+ // Never throws, and a no-op when nothing was stamped.
56
+ await notePrChangedForPrs(accountId, [...stampedPrIds]);
57
+ return out;
58
+ }
59
+ //# sourceMappingURL=resolve.js.map