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,320 @@
1
+ import { inArray } from 'drizzle-orm';
2
+ import { db, schema } from './client.js';
3
+ import { getAccountUserId } from '../auth/account.js';
4
+ const { reviewRequests, prViews, events, reviews } = schema;
5
+ // The merge-state values that mean "GitHub would let this land right now". Mirrors the
6
+ // `canMerge` half of the frontend's `mergeVerdict()` resolver (lib/ui.ts) — the two must
7
+ // agree, or a PR reads "approved & ready" in the triage queue and "blocked" on the PR itself.
8
+ export const READY_MERGE_STATES = new Set([
9
+ 'clean',
10
+ 'has_hooks',
11
+ 'unstable',
12
+ ]);
13
+ function emptyNew() {
14
+ return { commits: 0, comments: 0, reviews: 0 };
15
+ }
16
+ /** Tier of a review state for the standing pick — LOWER WINS, regardless of timestamp.
17
+ *
18
+ * Tier 0 is the VERDICT tier and is the ONLY tier the approval count reads, which is what makes
19
+ * this rule a strict superset of its predecessor (below). Tiers 1 and 2 exist so a reviewer who
20
+ * never filed a verdict still appears on the card with an honest label instead of vanishing.
21
+ * A dismissal outranks a bare comment: it is a verdict-shaped fact about the review, and a
22
+ * reviewer who was dismissed and then commented has not gone back to saying nothing. */
23
+ function standingTier(state) {
24
+ if (state === 'approved' || state === 'changes_requested')
25
+ return 0;
26
+ if (state === 'dismissed')
27
+ return 1;
28
+ if (state === 'commented')
29
+ return 2;
30
+ // 'pending' — a review draft that was never submitted. Not a review; it says nothing about
31
+ // where the reviewer stands, and showing it as one would name someone who has not spoken.
32
+ return null;
33
+ }
34
+ /**
35
+ * Per-PR reviewer standings: ONE indexed read of `reviews` over the given PR ids, grouped per
36
+ * (pr, author) in memory.
37
+ *
38
+ * THE RULE — a reviewer's standing is their latest VERDICT (approved / changes_requested) if they
39
+ * ever filed one; failing that their latest dismissal; failing that their latest comment.
40
+ *
41
+ * ⚠ THIS IS A STRICT SUPERSET OF THE OLD APPROVAL FOLD, BY CONSTRUCTION, AND HAS TO STAY ONE.
42
+ * `approvals` is hashed into stored Pro work plans (`db/work-plan.ts` → `payloadHashFor`), so a
43
+ * count that moves flips every stored plan on an affected workspace permanently `stale` and
44
+ * re-bills it. The predecessor picked the latest row whose state was `approved` or
45
+ * `changes_requested` and ignored every other row; tier 0 is that pick, unchanged, and tiers 1-2
46
+ * can only add reviewers the old rule dropped entirely — reviewers who count for nothing. Pinned
47
+ * in `triage-reviewers.test.ts`.
48
+ *
49
+ * ⚠ KNOWN DIVERGENCE FROM THE STANDING A HUMAN WOULD READ, KEPT DELIBERATELY: a reviewer who
50
+ * approved and was LATER DISMISSED (a fresh review node whose state is DISMISSED, which is how
51
+ * GitHub records a revoked approval) still reads `approved`, because tier 0 outranks tier 1
52
+ * whatever the clock says. Demoting them would be the honest label — and would also drop the
53
+ * approval, moving the hashed count. MEASURED on this repo's real data: 2 reviewer-PR pairs
54
+ * account-wide, 1 of them on an open PR (sourcery-ai). So it is a decision to take with the hash
55
+ * consequence in hand, not a patch: change the tier of `dismissed` to 0 and every stored plan on
56
+ * an affected workspace re-bills.
57
+ *
58
+ * ⚠ A NULL `authorId` IS ONE PSEUDO-REVIEWER PER PR, AND IT COUNTS. `reviews.authorId` is
59
+ * nullable (a deleted GitHub account), the old key was the string `${prId}:${authorId}`, so every
60
+ * ghost review on a PR collapsed into one slot and its verdict counted toward `approvals`. Both
61
+ * are preserved exactly. Zero such rows exist today, which is precisely why dropping them here
62
+ * would look safe and would silently move a count the day one appears.
63
+ *
64
+ * COST: one indexed scan (`rv_pr_idx` on `pr_id`), the same query the predecessor ran with `id`
65
+ * added to the select list — no second query, no per-PR fetch. Measured on real data: the 50
66
+ * most-recently-updated open PRs carry 147 review rows and 45 reviewers (max 4 on one PR); the 50
67
+ * BUSIEST open PRs carry 1,362 rows and 233 reviewers (max 10). The rows were always read; what
68
+ * changed is that they are no longer thrown away.
69
+ */
70
+ export async function computeReviewStandingsByPr(prIds) {
71
+ const out = new Map();
72
+ if (prIds.length === 0)
73
+ return out;
74
+ const rows = await db
75
+ .select({
76
+ id: reviews.id,
77
+ prId: reviews.prId,
78
+ authorId: reviews.authorId,
79
+ state: reviews.state,
80
+ submittedAt: reviews.submittedAt,
81
+ })
82
+ .from(reviews)
83
+ .where(inArray(reviews.prId, prIds))
84
+ .execute();
85
+ // One winner per (pr, author). ⚠ The tiebreak is EXPLICIT — a later `submittedAt`, then the
86
+ // higher `id` — because rows arrive in heap order, which on Postgres flips after any UPDATE to
87
+ // the table. The predecessor's bare `>` handed a same-second tie to whichever row the heap
88
+ // offered first, so the same PR could resolve two ways between two identical reads. Measured:
89
+ // 35 (pr, author, submitted_at) ties exist in real data and NOT ONE disagrees on state, so
90
+ // making this deterministic moves no count today — it just stops the day one would.
91
+ const best = new Map();
92
+ for (const r of rows) {
93
+ const tier = standingTier(r.state);
94
+ if (tier == null)
95
+ continue;
96
+ const key = `${r.prId}:${r.authorId}`;
97
+ const prev = best.get(key);
98
+ if (prev != null) {
99
+ if (tier > prev.tier)
100
+ continue;
101
+ if (tier === prev.tier) {
102
+ const dt = r.submittedAt.getTime() - prev.at.getTime();
103
+ if (dt < 0 || (dt === 0 && r.id <= prev.id))
104
+ continue;
105
+ }
106
+ }
107
+ best.set(key, {
108
+ prId: r.prId,
109
+ userId: r.authorId,
110
+ tier,
111
+ state: r.state,
112
+ at: r.submittedAt,
113
+ id: r.id,
114
+ });
115
+ }
116
+ const byPr = new Map();
117
+ for (const v of best.values()) {
118
+ const list = byPr.get(v.prId) ?? [];
119
+ list.push({
120
+ userId: v.userId,
121
+ standing: v.state,
122
+ standingAt: v.at,
123
+ });
124
+ byPr.set(v.prId, list);
125
+ }
126
+ for (const [prId, list] of byPr) {
127
+ // Deterministic for the same reason the tiebreak is: a card's chips must not reshuffle
128
+ // because Postgres handed back the same rows in a different order. Newest standing first is
129
+ // also the order a caller capping to three wants.
130
+ list.sort((a, b) => b.standingAt.getTime() - a.standingAt.getTime() || (a.userId ?? 0) - (b.userId ?? 0));
131
+ out.set(prId, { reviewers: list, total: list.length });
132
+ }
133
+ return out;
134
+ }
135
+ /**
136
+ * The approval standing of a PR, folded from its reviewer list — the ONE place the counts come
137
+ * from, so the chips on a card and the "approved by N" beside them cannot disagree. `dismissed`
138
+ * and `commented` count as nothing, exactly as the predecessor's skipped rows did.
139
+ */
140
+ export function approvalInfoFromStandings(standings) {
141
+ let approvals = 0;
142
+ let blocks = 0;
143
+ let latestApprovalAt = null;
144
+ for (const r of standings.reviewers) {
145
+ if (r.standing === 'approved') {
146
+ approvals += 1;
147
+ if (!latestApprovalAt || r.standingAt.getTime() > latestApprovalAt.getTime()) {
148
+ latestApprovalAt = r.standingAt;
149
+ }
150
+ }
151
+ else if (r.standing === 'changes_requested') {
152
+ blocks += 1;
153
+ }
154
+ }
155
+ return {
156
+ approved: approvals > 0 && blocks === 0,
157
+ changesRequested: blocks > 0,
158
+ approvals,
159
+ latestApprovalAt,
160
+ };
161
+ }
162
+ /**
163
+ * Per-PR approval standing: at least one reviewer's standing decision is approved and none is
164
+ * changes_requested. Drives the `approved_ready` reason tag, the green/red review outline on
165
+ * timeline bars, the "your PR was approved" My Turn section and the Pro work plan's `approvals`.
166
+ *
167
+ * Now a projection of `computeReviewStandingsByPr` rather than a second fold over the same rows —
168
+ * a caller wanting the reviewers as well should call that once and pass the result through
169
+ * `approvalInfoFromStandings`, not run both.
170
+ */
171
+ export async function computeApprovalInfoByPr(prIds) {
172
+ const out = new Map();
173
+ for (const [prId, standings] of await computeReviewStandingsByPr(prIds)) {
174
+ const info = approvalInfoFromStandings(standings);
175
+ // ⚠ THE EMISSION SET IS PRESERVED, not just the values. The predecessor built its map from
176
+ // the decisive picks alone, so a PR whose only reviews are comments had NO ENTRY — not a
177
+ // zeroed one. Every caller today reads `.get(id)?.field ?? default` and cannot tell, but a
178
+ // future `.has()` could, and this fold is the one whose output is hashed.
179
+ if (info.approvals === 0 && !info.changesRequested)
180
+ continue;
181
+ out.set(prId, info);
182
+ }
183
+ return out;
184
+ }
185
+ /**
186
+ * Compute triage fields (reason tag, review-requested-from-me, new-since
187
+ * counts) for a batch of PRs. All supporting data is loaded in a handful of
188
+ * batched queries — safe to call on the hot timeline path.
189
+ */
190
+ export async function computeTriage(prs, accountId) {
191
+ const out = new Map();
192
+ const prIds = prs.map((p) => p.id);
193
+ const localUserId = await getAccountUserId(accountId);
194
+ // ---- review requests (user-type) by PR ----
195
+ const reqByPr = new Map();
196
+ if (prIds.length > 0) {
197
+ const rows = await db
198
+ .select({ prId: reviewRequests.prId, userId: reviewRequests.userId })
199
+ .from(reviewRequests)
200
+ .where(inArray(reviewRequests.prId, prIds))
201
+ .execute();
202
+ for (const r of rows) {
203
+ // A null userId means the request went to a GITHUB team — a group of people on GitHub,
204
+ // NOT one of this app's workspaces — so it names no single person and can't be "me".
205
+ if (r.userId == null)
206
+ continue;
207
+ const entry = reqByPr.get(r.prId) ?? { mine: false, others: 0 };
208
+ if (localUserId != null && r.userId === localUserId)
209
+ entry.mine = true;
210
+ else
211
+ entry.others += 1;
212
+ reqByPr.set(r.prId, entry);
213
+ }
214
+ }
215
+ // ---- last-viewed per PR ----
216
+ const viewedAtByPr = new Map();
217
+ if (prIds.length > 0) {
218
+ const rows = await db
219
+ .select({ prId: prViews.prId, lastViewedAt: prViews.lastViewedAt })
220
+ .from(prViews)
221
+ .where(inArray(prViews.prId, prIds))
222
+ .execute();
223
+ for (const r of rows)
224
+ viewedAtByPr.set(r.prId, r.lastViewedAt.getTime());
225
+ }
226
+ // ---- events per PR (for new-since counts) ----
227
+ const newByPr = new Map();
228
+ if (prIds.length > 0 && viewedAtByPr.size > 0) {
229
+ const rows = await db
230
+ .select({
231
+ prId: events.prId,
232
+ type: events.type,
233
+ occurredAt: events.occurredAt,
234
+ })
235
+ .from(events)
236
+ .where(inArray(events.prId, prIds))
237
+ .execute();
238
+ for (const r of rows) {
239
+ if (r.prId == null)
240
+ continue;
241
+ const threshold = viewedAtByPr.get(r.prId);
242
+ if (threshold == null)
243
+ continue;
244
+ if (r.occurredAt.getTime() <= threshold)
245
+ continue;
246
+ const n = newByPr.get(r.prId) ?? emptyNew();
247
+ if (r.type === 'commit_pushed')
248
+ n.commits += 1;
249
+ else if (r.type === 'pr_comment' || r.type === 'review_comment')
250
+ n.comments += 1;
251
+ else if (r.type === 'review_submitted')
252
+ n.reviews += 1;
253
+ newByPr.set(r.prId, n);
254
+ }
255
+ }
256
+ const approvalByPr = await computeApprovalInfoByPr(prIds);
257
+ for (const pr of prs) {
258
+ const req = reqByPr.get(pr.id);
259
+ const reviewRequestedFromMe = req?.mine ?? false;
260
+ const otherReviewersRequested = req?.others ?? 0;
261
+ const approval = approvalByPr.get(pr.id);
262
+ // No "new" badges on closed/merged PRs — done is done.
263
+ const newSinceLastViewed = pr.state === 'open' && viewedAtByPr.has(pr.id)
264
+ ? (newByPr.get(pr.id) ?? emptyNew())
265
+ : null;
266
+ out.set(pr.id, {
267
+ reviewRequestedFromMe,
268
+ otherReviewersRequested,
269
+ isApproved: approval?.approved ?? false,
270
+ isChangesRequested: approval?.changesRequested ?? false,
271
+ newSinceLastViewed,
272
+ reasonTag: deriveReasonTag(pr, {
273
+ reviewRequestedFromMe,
274
+ localUserId,
275
+ newComments: newSinceLastViewed?.comments ?? 0,
276
+ approvedReady: approval?.approved ?? false,
277
+ }),
278
+ });
279
+ }
280
+ return out;
281
+ }
282
+ function deriveReasonTag(pr, ctx) {
283
+ // The cascade only really applies to open PRs; closed/merged fall through to
284
+ // the default.
285
+ if (pr.state === 'open') {
286
+ // Actionable-by-you reasons win — "awaiting your review" beats CI failing
287
+ // because only you can clear it.
288
+ if (ctx.reviewRequestedFromMe)
289
+ return 'awaiting_your_review';
290
+ if (ctx.localUserId != null &&
291
+ pr.authorId === ctx.localUserId &&
292
+ ctx.newComments > 0)
293
+ return 'your_pr_new_comments';
294
+ if (pr.ciStatus === 'failure' || pr.ciStatus === 'error')
295
+ return 'ci_failing';
296
+ if (pr.mergeable === 'conflicting' || pr.mergeStateStatus === 'dirty')
297
+ return 'merge_conflicts';
298
+ // CI is known-not-failing here (failing CI returned above).
299
+ //
300
+ // `mergeable` ONLY reports merge-CONFLICT state (MERGEABLE / CONFLICTING / UNKNOWN) — it
301
+ // says nothing about branch protection. Testing it alone tagged PRs "approved & ready"
302
+ // while their REQUIRED checks were red or a second required review was outstanding, which
303
+ // is the same blindness the merge surfaces had. `mergeStateStatus` is the protection-aware
304
+ // field, so gate on it too:
305
+ // clean — mergeable and passing
306
+ // has_hooks — mergeable, just has a pre-receive hook to run
307
+ // unstable — NON-required checks are red; GitHub will still merge it, so it IS ready
308
+ // Everything else (blocked / behind / dirty / unknown) is not ready by definition.
309
+ if (ctx.approvedReady &&
310
+ pr.mergeable === 'mergeable' &&
311
+ READY_MERGE_STATES.has(pr.mergeStateStatus))
312
+ return 'approved_ready';
313
+ if (pr.isStalled)
314
+ return 'stalled';
315
+ if (pr.threadCounts.untouched > 0)
316
+ return 'untouched_threads';
317
+ }
318
+ return 'in_progress';
319
+ }
320
+ //# sourceMappingURL=triage.js.map
@@ -0,0 +1,63 @@
1
+ import { and, eq, gte } from 'drizzle-orm';
2
+ import { db, schema } from './client.js';
3
+ const { aiUsage } = schema;
4
+ // Append one usage row. Best-effort + non-fatal: a ledger write must never break the
5
+ // feature that spent the money, so callers `.catch()` or await-in-try. Drops rows with
6
+ // no real cost (a $0 ambient-session completion isn't worth a row).
7
+ export async function recordAiUsage(row) {
8
+ if (!Number.isFinite(row.costUsd) || row.costUsd <= 0)
9
+ return;
10
+ await db
11
+ .insert(aiUsage)
12
+ .values({
13
+ accountId: row.accountId,
14
+ seam: row.seam,
15
+ feature: row.feature,
16
+ model: row.model,
17
+ costUsd: row.costUsd,
18
+ inputTokens: row.inputTokens ?? null,
19
+ outputTokens: row.outputTokens ?? null,
20
+ prId: row.prId ?? null,
21
+ repoId: row.repoId ?? null,
22
+ occurredAt: new Date(),
23
+ })
24
+ .execute();
25
+ }
26
+ // Sum recorded AI spend for an account since `sinceMs`, split by seam. Rows are small
27
+ // (per account, per month) so we fetch + reduce in JS — portable across both dialects.
28
+ export async function getAiUsageSummary(accountId, sinceMs) {
29
+ const rows = await db
30
+ .select({ seam: aiUsage.seam, costUsd: aiUsage.costUsd })
31
+ .from(aiUsage)
32
+ .where(and(eq(aiUsage.accountId, accountId), gte(aiUsage.occurredAt, new Date(sinceMs))))
33
+ .execute();
34
+ let summaryUsd = 0;
35
+ let agentUsd = 0;
36
+ for (const r of rows) {
37
+ if (r.seam === 'agent')
38
+ agentUsd += r.costUsd;
39
+ else
40
+ summaryUsd += r.costUsd;
41
+ }
42
+ return { summaryUsd, agentUsd, totalUsd: summaryUsd + agentUsd };
43
+ }
44
+ // Auxiliary summary sub-calls that belong to a SINGLE user-initiated turn and must NOT count as
45
+ // their own turn — e.g. the insights-chat CHART is a second Haiku call rendering the SAME
46
+ // question, so one charted question is still one turn. Their $ cost stays on the ledger (COGS
47
+ // visibility); only the turn TALLY excludes them.
48
+ const NON_TURN_SUMMARY_FEATURES = new Set(['sprint_chat_chart']);
49
+ // Count the SUMMARY-seam turns an account has spent since `sinceMs` (month-to-date). One
50
+ // recorded ledger row = one turn, EXCEPT auxiliary sub-calls (above); a $0 cache hit /
51
+ // ambient-session completion isn't recorded, so it's free and doesn't count (recordAiUsage
52
+ // drops non-positive-cost rows). A summary turn limit caps this. Portable across both dialects
53
+ // (fetch + reduce in JS; the row set is bounded by ~the monthly limit — same pattern as
54
+ // getAiUsageSummary).
55
+ export async function getSummaryTurnCount(accountId, sinceMs) {
56
+ const rows = await db
57
+ .select({ feature: aiUsage.feature })
58
+ .from(aiUsage)
59
+ .where(and(eq(aiUsage.accountId, accountId), eq(aiUsage.seam, 'summary'), gte(aiUsage.occurredAt, new Date(sinceMs))))
60
+ .execute();
61
+ return rows.filter((r) => !NON_TURN_SUMMARY_FEATURES.has(r.feature)).length;
62
+ }
63
+ //# sourceMappingURL=usage.js.map