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,343 @@
1
+ // MY TURN DISMISSALS + ONE CARD PER PR (CORE, free, both modes, no AI).
2
+ //
3
+ // Two rules the My Turn fold (`getMyTurn`) applies, kept here so they can be read — and tested —
4
+ // apart from the 700-line fold that calls them.
5
+ //
6
+ // ── 1. A DISMISSAL SETS ONE ITEM DOWN UNTIL SOMETHING NEW HAPPENS ────────────────────────────
7
+ //
8
+ // The reader can take a subject — a pull request, or a repository for a red default branch — off
9
+ // their plate. ⚠ This is NOT the "Done" button migration 0060 deleted, and the difference is the
10
+ // whole design. That one stored "I dealt with this" and never expired, so a PR whose ball came back
11
+ // stayed hidden behind it for weeks. This one stores a TIMESTAMP and lets the fold decide:
12
+ //
13
+ // • an item on a dismissed subject is hidden only while its OWN clock (the moment the thing that
14
+ // needs you happened — `MyTurnPr.since`, a thread's `lastReplyAt`, …) is at or before the
15
+ // dismissal. Anything LATER shows again, and only that item: a new reply comes back as a reply,
16
+ // not as the review request you set down;
17
+ // • a subject with NO My Turn item at all — you acted, it closed, the request was withdrawn —
18
+ // DISCHARGES its row (`dischargeable`), so the next summons starts fresh rather than arriving
19
+ // already dismissed. That is what stops a dismissal outliving the thing it dismissed.
20
+ //
21
+ // It is applied INSIDE `getMyTurn`, so the board, the brief's counts, the badges, the browser
22
+ // notification and the CLI all stop showing a dismissed item together — the Settings-gate rule.
23
+ //
24
+ // ── 2. ONE CARD PER PR ON THE BOARD (`onePerPr`) ─────────────────────────────────────────────
25
+ //
26
+ // A PR can hold several My Turn jobs at once (a review requested of you AND a reply in your thread
27
+ // AND your own unanswered thread). The My turn tab shows ONE: the job highest in the READER'S type
28
+ // order (Settings → My Turn — the tab's own grouping), and within one type the one that has waited
29
+ // longest (the scorer's pick too: same PR, same type ⇒ same proximity and relevance, so the older
30
+ // clock scores higher and wins `compareScored`'s age tie-break). Ties beyond that break on the
31
+ // item's own id, so two reads of unchanged data keep the same card.
32
+ //
33
+ // ⚠ IT IS A BOARD RULE, NOT A NOTIFICATION RULE. `getWorkspaceInsights` asks for it; the
34
+ // account-wide `GET /api/my-turn` does not, because the browser-notification watcher diffs item
35
+ // ids — a deduplicated list would flip the winner whenever the top job cleared and announce the
36
+ // runner-up as "new". Red default branches are repo-grained and never collide with a PR.
37
+ import { and, eq, inArray } from 'drizzle-orm';
38
+ import { db, schema } from './client.js';
39
+ const { myTurnDismissals, pullRequests, repos } = schema;
40
+ export const subjectKey = (t) => `${t.kind}:${t.id}`;
41
+ /** Every dismissal the account holds. One indexed select plus a join for the PR's repo/state. */
42
+ export async function readMyTurnDismissals(accountId) {
43
+ const rows = await db
44
+ .select({
45
+ prId: myTurnDismissals.prId,
46
+ repoId: myTurnDismissals.repoId,
47
+ dismissedAt: myTurnDismissals.dismissedAt,
48
+ prRepoId: pullRequests.repoId,
49
+ prState: pullRequests.state,
50
+ })
51
+ .from(myTurnDismissals)
52
+ .leftJoin(pullRequests, eq(pullRequests.id, myTurnDismissals.prId))
53
+ .where(eq(myTurnDismissals.accountId, accountId))
54
+ .execute();
55
+ const out = [];
56
+ for (const r of rows) {
57
+ if (r.prId != null && r.prRepoId != null) {
58
+ out.push({
59
+ target: { kind: 'pr', id: r.prId },
60
+ dismissedAtMs: r.dismissedAt.getTime(),
61
+ repoId: r.prRepoId,
62
+ closed: r.prState !== 'open',
63
+ });
64
+ }
65
+ else if (r.repoId != null) {
66
+ out.push({
67
+ target: { kind: 'repo', id: r.repoId },
68
+ dismissedAtMs: r.dismissedAt.getTime(),
69
+ repoId: r.repoId,
70
+ closed: false,
71
+ });
72
+ }
73
+ }
74
+ return out;
75
+ }
76
+ /**
77
+ * The dismissal's timestamp, ROUNDED UP to the whole second. SQLite stores `mode: 'timestamp'` in
78
+ * whole seconds, so an unrounded write would land up to 999ms EARLY and an item that happened just
79
+ * before the press would compare as "after" it and stay on screen. Rounded the same way in both
80
+ * dialects, so the rule is one rule.
81
+ */
82
+ export function dismissalInstant(nowMs) {
83
+ return new Date(Math.ceil(nowMs / 1000) * 1000);
84
+ }
85
+ /**
86
+ * Dismiss one subject. Null when the subject is not this account's — the route answers 404, so
87
+ * the family is not an existence oracle. ⚠ The composite FK would refuse a cross-account pair
88
+ * anyway; this read exists to answer with a 404 rather than a constraint error.
89
+ *
90
+ * Re-dismissing moves the timestamp forward: "and whatever arrived since, too".
91
+ */
92
+ export async function dismissMyTurn(accountId, target, nowMs = Date.now()) {
93
+ const at = dismissalInstant(nowMs);
94
+ if (target.kind === 'pr') {
95
+ const owned = await db
96
+ .select({ id: pullRequests.id })
97
+ .from(pullRequests)
98
+ .where(and(eq(pullRequests.id, target.id), eq(pullRequests.accountId, accountId)))
99
+ .limit(1)
100
+ .execute();
101
+ if (owned[0] == null)
102
+ return null;
103
+ await db
104
+ .insert(myTurnDismissals)
105
+ .values({ accountId, prId: target.id, repoId: null, dismissedAt: at })
106
+ .onConflictDoUpdate({
107
+ target: [myTurnDismissals.accountId, myTurnDismissals.prId],
108
+ set: { dismissedAt: at },
109
+ })
110
+ .execute();
111
+ return at;
112
+ }
113
+ const owned = await db
114
+ .select({ id: repos.id })
115
+ .from(repos)
116
+ .where(and(eq(repos.id, target.id), eq(repos.accountId, accountId)))
117
+ .limit(1)
118
+ .execute();
119
+ if (owned[0] == null)
120
+ return null;
121
+ await db
122
+ .insert(myTurnDismissals)
123
+ .values({ accountId, prId: null, repoId: target.id, dismissedAt: at })
124
+ .onConflictDoUpdate({
125
+ target: [myTurnDismissals.accountId, myTurnDismissals.repoId],
126
+ set: { dismissedAt: at },
127
+ })
128
+ .execute();
129
+ return at;
130
+ }
131
+ /** Bring a subject back. True when a dismissal existed. Scoped by account on the row itself. */
132
+ export async function restoreMyTurn(accountId, target) {
133
+ const col = target.kind === 'pr' ? myTurnDismissals.prId : myTurnDismissals.repoId;
134
+ const deleted = await db
135
+ .delete(myTurnDismissals)
136
+ .where(and(eq(myTurnDismissals.accountId, accountId), eq(col, target.id)))
137
+ .returning({ id: myTurnDismissals.id })
138
+ .execute();
139
+ return deleted.length > 0;
140
+ }
141
+ /** Drop the rows the fold found nothing left for — see `MyTurnDismissalFilter.dischargeable`. */
142
+ export async function dischargeMyTurnDismissals(accountId, targets) {
143
+ const prIds = targets.filter((t) => t.kind === 'pr').map((t) => t.id);
144
+ const repoIds = targets.filter((t) => t.kind === 'repo').map((t) => t.id);
145
+ if (prIds.length > 0) {
146
+ await db
147
+ .delete(myTurnDismissals)
148
+ .where(and(eq(myTurnDismissals.accountId, accountId), inArray(myTurnDismissals.prId, prIds)))
149
+ .execute();
150
+ }
151
+ if (repoIds.length > 0) {
152
+ await db
153
+ .delete(myTurnDismissals)
154
+ .where(and(eq(myTurnDismissals.accountId, accountId), inArray(myTurnDismissals.repoId, repoIds)))
155
+ .execute();
156
+ }
157
+ }
158
+ /**
159
+ * The per-fold filter. `keep` is called once per candidate item, in any order; it records which
160
+ * subjects the fold saw at all, which it showed and which it hid, so the three questions after the
161
+ * fold — what is dismissed, what may be discharged — are answered off what actually happened.
162
+ */
163
+ export class MyTurnDismissalFilter {
164
+ byKey;
165
+ orderIdx;
166
+ present = new Set();
167
+ shown = new Set();
168
+ hidden = new Map();
169
+ constructor(rows, order) {
170
+ this.byKey = new Map(rows.map((r) => [subjectKey(r.target), r]));
171
+ this.orderIdx = new Map(order.map((r, i) => [r, i]));
172
+ }
173
+ /** True ⇒ the item stays. `clock` is the item's own ISO clock; null ⇒ it has none, and an
174
+ * item with no clock cannot prove it is newer than the dismissal, so it stays hidden. */
175
+ keep(target, clock, reason, describe) {
176
+ const key = subjectKey(target);
177
+ this.present.add(key);
178
+ const d = this.byKey.get(key);
179
+ if (d == null)
180
+ return true;
181
+ const ms = clock == null ? Number.NaN : Date.parse(clock);
182
+ if (Number.isFinite(ms) && ms > d.dismissedAtMs) {
183
+ this.shown.add(key);
184
+ return true;
185
+ }
186
+ const prev = this.hidden.get(key);
187
+ if (prev == null || this.rank(reason) < this.rank(prev.reason)) {
188
+ this.hidden.set(key, { reason, describe });
189
+ }
190
+ return false;
191
+ }
192
+ rank(r) {
193
+ return this.orderIdx.get(r) ?? Number.MAX_SAFE_INTEGER;
194
+ }
195
+ /** Subjects wholly hidden by a live dismissal — the tab's "Dismissed" list, newest first. A
196
+ * subject with a newer item showing is back on the plate and is not listed. */
197
+ dismissed() {
198
+ const out = [];
199
+ for (const [key, h] of this.hidden) {
200
+ if (this.shown.has(key))
201
+ continue;
202
+ const d = this.byKey.get(key);
203
+ out.push({
204
+ target: d.target,
205
+ ...h.describe(),
206
+ reason: h.reason,
207
+ dismissedAt: new Date(d.dismissedAtMs).toISOString(),
208
+ });
209
+ }
210
+ return out.sort((a, b) => b.dismissedAt.localeCompare(a.dismissedAt));
211
+ }
212
+ /**
213
+ * Rows with nothing left to dismiss: the fold saw NO item on the subject. ⚠ ONLY WHERE THE FOLD
214
+ * COULD HAVE SEEN ONE — a workspace-scoped fold has not looked at another workspace's repos, so
215
+ * `inScope` says which subjects this read covered. A closed PR is dead wherever it lives.
216
+ */
217
+ dischargeable(inScope) {
218
+ const out = [];
219
+ for (const [key, d] of this.byKey) {
220
+ if (this.present.has(key))
221
+ continue;
222
+ if (d.closed || inScope(d.repoId))
223
+ out.push(d.target);
224
+ }
225
+ return out;
226
+ }
227
+ }
228
+ const clockMsOf = (iso) => {
229
+ const ms = iso == null ? Number.NaN : Date.parse(iso);
230
+ // No clock ⇒ the oldest possible: the board's scorer dates such a card off the PR's open time,
231
+ // which is the longest wait it has.
232
+ return Number.isFinite(ms) ? ms : Number.NEGATIVE_INFINITY;
233
+ };
234
+ /**
235
+ * Keep ONE item per pull request across every PR-grained section — see the header's rule 2.
236
+ * Pure. Sections keep their own order; only losers are removed.
237
+ */
238
+ export function onePerPr(mt, order) {
239
+ const orderIdx = new Map(order.map((r, i) => [r, i]));
240
+ const rank = (r) => orderIdx.get(r) ?? Number.MAX_SAFE_INTEGER;
241
+ const better = (a, b) => rank(a.reason) - rank(b.reason) < 0 ||
242
+ (rank(a.reason) === rank(b.reason) &&
243
+ (a.clockMs < b.clockMs || (a.clockMs === b.clockMs && `${a.reason}:${a.ref}` < `${b.reason}:${b.ref}`)));
244
+ const best = new Map();
245
+ const offer = (c) => {
246
+ const cur = best.get(c.prId);
247
+ if (cur == null || better(c, cur))
248
+ best.set(c.prId, c);
249
+ };
250
+ const winnerIs = (c) => {
251
+ const w = best.get(c.prId);
252
+ return w.reason === c.reason && w.ref === c.ref;
253
+ };
254
+ // Each section, described once: its reason, and how to read an item's PR, clock and id.
255
+ const pr = (reason) => ({
256
+ of: (i) => ({ prId: i.prId, reason, clockMs: clockMsOf(i.since), ref: `${i.prId}` }),
257
+ });
258
+ const S = {
259
+ awaitingReview: pr('review_request'),
260
+ mentions: pr('mention'),
261
+ commentReplies: pr('comment_reply'),
262
+ pushedSince: pr('pushed_since'),
263
+ ownCiRed: pr('own_ci_red'),
264
+ ownConflicts: pr('own_conflicts'),
265
+ approvedPrs: pr('pr_approved'),
266
+ ownReady: pr('own_ready'),
267
+ yourPrs: pr('your_pr'),
268
+ watchedRepoPrs: pr('watched_repo_pr'),
269
+ };
270
+ const thread = (reason) => ({
271
+ of: (i) => ({
272
+ prId: i.prId,
273
+ reason,
274
+ clockMs: clockMsOf(i.lastReplyAt),
275
+ ref: `${i.threadId}`,
276
+ }),
277
+ });
278
+ const threadsAwaiting = thread('thread');
279
+ const threadReplies = thread('thread_reply');
280
+ const ownThreads = {
281
+ of: (i) => ({
282
+ prId: i.prId,
283
+ reason: 'own_thread',
284
+ clockMs: clockMsOf(i.since),
285
+ ref: `${i.threadId}`,
286
+ }),
287
+ };
288
+ const claude = {
289
+ of: (i) => ({
290
+ prId: i.prId,
291
+ reason: 'claude_review',
292
+ clockMs: clockMsOf(i.finishedAt),
293
+ ref: `${i.reviewId}`,
294
+ }),
295
+ };
296
+ for (const i of mt.awaitingReview)
297
+ offer(S.awaitingReview.of(i));
298
+ for (const i of mt.mentions)
299
+ offer(S.mentions.of(i));
300
+ for (const i of mt.commentReplies)
301
+ offer(S.commentReplies.of(i));
302
+ for (const i of mt.pushedSince)
303
+ offer(S.pushedSince.of(i));
304
+ for (const i of mt.ownCiRed)
305
+ offer(S.ownCiRed.of(i));
306
+ for (const i of mt.ownConflicts)
307
+ offer(S.ownConflicts.of(i));
308
+ for (const i of mt.approvedPrs)
309
+ offer(S.approvedPrs.of(i));
310
+ for (const i of mt.ownReady)
311
+ offer(S.ownReady.of(i));
312
+ for (const i of mt.yourPrs)
313
+ offer(S.yourPrs.of(i));
314
+ for (const i of mt.watchedRepoPrs)
315
+ offer(S.watchedRepoPrs.of(i));
316
+ for (const i of mt.threadsAwaiting)
317
+ offer(threadsAwaiting.of(i));
318
+ for (const i of mt.threadReplies)
319
+ offer(threadReplies.of(i));
320
+ for (const i of mt.ownThreads)
321
+ offer(ownThreads.of(i));
322
+ for (const i of mt.claudeReviewsToAction)
323
+ offer(claude.of(i));
324
+ return {
325
+ awaitingReview: mt.awaitingReview.filter((i) => winnerIs(S.awaitingReview.of(i))),
326
+ mentions: mt.mentions.filter((i) => winnerIs(S.mentions.of(i))),
327
+ commentReplies: mt.commentReplies.filter((i) => winnerIs(S.commentReplies.of(i))),
328
+ pushedSince: mt.pushedSince.filter((i) => winnerIs(S.pushedSince.of(i))),
329
+ ownCiRed: mt.ownCiRed.filter((i) => winnerIs(S.ownCiRed.of(i))),
330
+ ownConflicts: mt.ownConflicts.filter((i) => winnerIs(S.ownConflicts.of(i))),
331
+ approvedPrs: mt.approvedPrs.filter((i) => winnerIs(S.approvedPrs.of(i))),
332
+ ownReady: mt.ownReady.filter((i) => winnerIs(S.ownReady.of(i))),
333
+ yourPrs: mt.yourPrs.filter((i) => winnerIs(S.yourPrs.of(i))),
334
+ watchedRepoPrs: mt.watchedRepoPrs.filter((i) => winnerIs(S.watchedRepoPrs.of(i))),
335
+ threadsAwaiting: mt.threadsAwaiting.filter((i) => winnerIs(threadsAwaiting.of(i))),
336
+ threadReplies: mt.threadReplies.filter((i) => winnerIs(threadReplies.of(i))),
337
+ ownThreads: mt.ownThreads.filter((i) => winnerIs(ownThreads.of(i))),
338
+ claudeReviewsToAction: mt.claudeReviewsToAction.filter((i) => winnerIs(claude.of(i))),
339
+ // Repo-grained: a branch is not a pull request, and never shares a key with one.
340
+ redTrunks: mt.redTrunks,
341
+ };
342
+ }
343
+ //# sourceMappingURL=my-turn-dismissals.js.map
@@ -0,0 +1,34 @@
1
+ // ── MY TURN SETTINGS, READ SERVER-SIDE (CORE, free, no AI) ─────────────────────────────────
2
+ //
3
+ // The account's stored overrides (`accounts.my_turn_settings`), resolved through the ONE shared
4
+ // resolver (`resolveMyTurnSettings`, packages/shared/src/my-turn-settings.ts). Every server reader
5
+ // comes through here: `getMyTurn` (which types are shown), the Pending ranker (the type order and
6
+ // the Do next weights) and the Pro work plan's scorer (the weights).
7
+ //
8
+ // ⚠ READ STRAIGHT FROM `accounts`, NEVER FROM THE LOCAL-ACCOUNT CACHE. The cache is the per-request
9
+ // hook's copy (auth/account.ts); a server fold that read it would see a save only if the setter
10
+ // remembered to refresh it. One indexed read by primary key costs nothing beside the folds it gates.
11
+ import { eq } from 'drizzle-orm';
12
+ import { resolveMyTurnSettings, } from '../shared/index.js';
13
+ import { db, schema } from './client.js';
14
+ /** The account's My Turn settings, resolved (the product defaults where nothing is stored). */
15
+ export async function getMyTurnSettings(accountId) {
16
+ const rows = await db
17
+ .select({ settings: schema.accounts.myTurnSettings })
18
+ .from(schema.accounts)
19
+ .where(eq(schema.accounts.id, accountId))
20
+ .limit(1)
21
+ .execute();
22
+ return resolveMyTurnSettings(rows[0]?.settings ?? null);
23
+ }
24
+ /** The ranking an `/api/attention` response was built with — what every explanation on the board
25
+ * must print, rather than the product constants. */
26
+ export function rankRulesOf(s) {
27
+ return {
28
+ weights: s.weights,
29
+ preset: s.preset,
30
+ myTurnOrder: [...s.order],
31
+ myTurnOff: [...s.off],
32
+ };
33
+ }
34
+ //# sourceMappingURL=my-turn-settings.js.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * IS THIS BUILD RED? — and RED IS ALWAYS THE PAIR `failure` | `error`, never one of them.
3
+ *
4
+ * GitHub reports an infrastructure/permissions problem as `error` and a genuine check failure as
5
+ * `failure`, and every layer of this app that asks "is it red" has to accept both: db/triage.ts's
6
+ * reason tags, getWorkspaceMetricsDetail's own local `isRed`, and the SPA's lib/ui.ts. A fold that
7
+ * tested only 'failure' would silently drop every errored build — which is the half that most
8
+ * often needs a human.
9
+ */
10
+ export const RED_CI_STATUSES = ['failure', 'error'];
11
+ export function isRedCiStatus(ci) {
12
+ return RED_CI_STATUSES.includes(ci ?? '');
13
+ }
14
+ /**
15
+ * DOES THE HEAD CONFLICT WITH THE BASE? An OR over two columns, because they are two views of one
16
+ * fact: `mergeStateStatus = 'dirty'` is GitHub's protection-aware verdict, `mergeable =
17
+ * 'conflicting'` its conflict-only one, and they agree on every real row we have measured.
18
+ *
19
+ * ⚠ NULL IN EITHER IS NOT OBSERVED, never a conflict — and never "fine" either. That is why this is
20
+ * a JS fold and not a SQL predicate: `mergeable <> 'conflicting'` in SQL silently drops the NULL
21
+ * rows, which are exactly the ones we have not asked about yet.
22
+ */
23
+ export function isConflicting(p) {
24
+ return p.mergeStateStatus === 'dirty' || p.mergeable === 'conflicting';
25
+ }
26
+ /**
27
+ * WHICH FORWARD CARD IS THIS PR? — `'update_branch'` when GitHub wants the branch updated first,
28
+ * `'merge'` when it will merge now, else null. The Ready to land tab's two kinds, spelled ONCE for
29
+ * the merge-candidate loop in `getWorkspaceInsights` and for My Turn's `own_ready` promotion, so a
30
+ * PR promoted into My turn is classified by the very test its home card was.
31
+ *
32
+ * ⚠ CONFLICTS OUTRANK 'behind' — GitHub's own `canUpdateBranch` is "behind AND NOT conflicting",
33
+ * so an Update-branch button on a conflicting PR is a button GitHub refuses.
34
+ * ⚠ `readyToMerge` is `READY_MERGE_STATES.has(mergeStateStatus)`, evaluated by the CALLER: that set
35
+ * lives in db/triage.ts, which opens the DB client at load, and this module stays pure.
36
+ */
37
+ export function forwardKindOf(p, readyToMerge) {
38
+ if (p.mergeStateStatus == null)
39
+ return null;
40
+ if (isConflicting(p))
41
+ return null;
42
+ if (p.mergeStateStatus === 'behind')
43
+ return 'update_branch';
44
+ return readyToMerge ? 'merge' : null;
45
+ }
46
+ //# sourceMappingURL=pending-classify.js.map
@@ -0,0 +1,171 @@
1
+ // THE PENDING MUTE (CORE, free, no AI) — "stop these Pending items claiming my turn".
2
+ //
3
+ // ── WHAT A MUTE IS ───────────────────────────────────────────────────────────────────────────
4
+ // A muted repo's "your turn"-shaped rows are downgraded to `relevance: 'none'` (hence
5
+ // `personal: false`) inside `getMyTurn`, at the ONE place `personal` is folded from `relevance`.
6
+ // From that single write, and with no second predicate anywhere:
7
+ //
8
+ // • the Pending card relabels from "Your turn" / "In your repos" to the neutral
9
+ // "Review or reply" (`cardKindLabel`, which reads `relevance`);
10
+ // • the browser notification stops firing for it (`useMyTurnNotifications`, which reads
11
+ // `personal`);
12
+ // • the welcome-back banner, the workspace badges, the "Elsewhere" rows and the
13
+ // "N need your attention" line stop counting it (`myTurnPersonal`, folded off `relevance`);
14
+ // • it moves into the broad "N need review or reply" population (`myTurnOther`);
15
+ // • it ranks lower in the "Do next" head (`RELEVANCE_WEIGHT` in db/work-plan.ts).
16
+ //
17
+ // ⚠ THE CARD NEVER LEAVES THE BOARD, and the broad `myTurn` count never moves. "That work is
18
+ // real, it is just not yours, and hiding it would delete work rather than route it" is the rule
19
+ // the Pending board is built on; a mute is a ROUTING gesture inside it, not a filter.
20
+ //
21
+ // ── WHAT IT IS NOT ───────────────────────────────────────────────────────────────────────────
22
+ // ⚠ IT IS NOT THE `repos.inbox_watch` AXIS MIGRATION 0046 DROPPED, and `api/routes/workspaces.ts`
23
+ // still carries the rule that killed it ("do not reintroduce a per-repo visibility flag here: the
24
+ // workspace IS the scope"). That column was a SECOND VISIBILITY SCOPE on top of membership — it
25
+ // decided whether a repo's work appeared AT ALL, so every screen had two axes and no way to say
26
+ // which it was obeying. Nothing here changes any screen's population: a muted repo is fully live
27
+ // on Feed, Timeline, Activity, Bots and the Pending board. What changes is whether its rows may
28
+ // CLAIM THE READER'S TURN and interrupt them. Membership remains the only visibility axis.
29
+ //
30
+ // ⚠ IT IS NOT THE ORPHANED `bot_mute_rules` TABLE (migration 0029) either. That backed a removed
31
+ // feature that HID review threads and RESOLVED them on a timer. This hides nothing and resolves
32
+ // nothing.
33
+ //
34
+ // ── TWO INDEPENDENT FACTS, OR-ed. NEVER A CHAIN. ─────────────────────────────────────────────
35
+ //
36
+ // muted(repo) == its workspace's `workspaces.pending_muted` OR a `pending_muted_repos` row
37
+ //
38
+ // Workspace grain alone does not answer the ask (one noisy repo inside a useful workspace);
39
+ // repo grain alone makes silencing a 20-repo workspace twenty clicks. `null`-means-inherit is a
40
+ // named bug class in this codebase (`workspace_reviewers.monthly_cents`, the Slack target, the
41
+ // sprint cadence): it needs a resolver, and "which grain am I reading?" then has to be answered
42
+ // at every call site. There is no resolver — there is ONE union, computed here, and clearing
43
+ // either half neither reveals nor overwrites the other. It is the `hiddenBotUserIds` shape.
44
+ import { and, eq, inArray } from 'drizzle-orm';
45
+ import { db, runTransaction, schema } from './client.js';
46
+ const { pendingMutedRepos, workspaceRepos, workspaces } = schema;
47
+ /**
48
+ * THE ONE READ. Every repo id in this account whose Pending items are muted, by EITHER fact.
49
+ *
50
+ * Account-scoped, never workspace-scoped, and that is what makes the ACCOUNT-WIDE
51
+ * `GET /api/my-turn` — the one the browser-notification watcher reads, deliberately with no
52
+ * `?workspace=` — obey a per-WORKSPACE mute without gaining a scope parameter: a repo belongs to
53
+ * exactly one workspace, so a workspace-grained mute is fully resolvable per repo id.
54
+ *
55
+ * Two indexed selects, unioned in JS rather than in SQL: an `OR` across a join and a second table
56
+ * is a portable-dialect liability for no gain here (both sides are tiny — one row per muted repo,
57
+ * one flag per workspace), and the union is the thing the comment above has to keep saying.
58
+ */
59
+ export async function getMutedPendingRepoIds(accountId) {
60
+ const [wsRows, repoRows] = await Promise.all([
61
+ // The WORKSPACE half: every repo whose workspace carries the flag. The join is on BOTH
62
+ // columns because `workspace_repos` carries its own `account_id` — the same pairing the
63
+ // composite FK enforces, so the predicate and the constraint say the same thing.
64
+ db
65
+ .select({ repoId: workspaceRepos.repoId })
66
+ .from(workspaceRepos)
67
+ .innerJoin(workspaces, and(eq(workspaces.id, workspaceRepos.workspaceId), eq(workspaces.accountId, workspaceRepos.accountId)))
68
+ .where(and(eq(workspaceRepos.accountId, accountId), eq(workspaces.pendingMuted, true)))
69
+ .execute(),
70
+ // The REPO half: presence is the fact.
71
+ db
72
+ .select({ repoId: pendingMutedRepos.repoId })
73
+ .from(pendingMutedRepos)
74
+ .where(eq(pendingMutedRepos.accountId, accountId))
75
+ .execute(),
76
+ ]);
77
+ const out = new Set();
78
+ for (const r of wsRows)
79
+ out.add(r.repoId);
80
+ for (const r of repoRows)
81
+ out.add(r.repoId);
82
+ return out;
83
+ }
84
+ /**
85
+ * The REPO HALF ALONE, account-scoped, for the settings listing — deliberately NOT the union
86
+ * above. `listWorkspaces` intersects it with each workspace's membership to build
87
+ * `Workspace.mutedRepoIds`, and the settings screen has to render the two switches SEPARATELY:
88
+ * folding the workspace flag in here would hand it one merged set it could not un-mix, and
89
+ * un-ticking a repository would then appear to do nothing. The row carries no workspace id (a
90
+ * repo belongs to exactly one workspace already, and a fact lives at exactly one grain).
91
+ */
92
+ export async function repoGrainedPendingMutes(accountId) {
93
+ const rows = await db
94
+ .select({ repoId: pendingMutedRepos.repoId })
95
+ .from(pendingMutedRepos)
96
+ .where(eq(pendingMutedRepos.accountId, accountId))
97
+ .execute();
98
+ return new Set(rows.map((r) => r.repoId));
99
+ }
100
+ /**
101
+ * THE WRITE, for `PUT /api/workspaces/:id/pending-mute`. Each half is applied ONLY when the
102
+ * caller supplied it (`undefined` = untouched), so one Save can carry either grain or both
103
+ * without a no-op write inventing a value for the other.
104
+ *
105
+ * ⚠ THE REPO SET IS SCOPED TO THIS WORKSPACE'S MEMBERSHIP, AND THAT IS A CORRECTNESS RULE, NOT A
106
+ * VALIDATION NICETY. `mutedRepoIds` is the EXACT muted set within the named workspace: ids in it
107
+ * that the workspace does not hold are IGNORED (never written), and stored rows for repos OUTSIDE
108
+ * this workspace are LEFT ALONE (never deleted). Without the second half, saving workspace A's
109
+ * screen would silently clear every mute the reader had set in workspaces B and C — the "an edit
110
+ * meant for one team travels to every team" failure this modal's grain split exists to prevent.
111
+ *
112
+ * Tenancy is ALSO structural: `repo_id` arrives in a request body, and the composite FK
113
+ * `(repo_id, account_id) → repos(id, account_id)` means a cross-account pair fails in the
114
+ * DATABASE. The membership intersection below is the scope guard; the FK is the tenant guard.
115
+ * Neither substitutes for the other.
116
+ *
117
+ * Returns false when the workspace is not this account's (the route 404s), so ownership is
118
+ * decided here rather than by whichever handler remembered to check.
119
+ */
120
+ export async function setWorkspacePendingMute(accountId, workspaceId, patch) {
121
+ const owned = (await db
122
+ .select({ id: workspaces.id })
123
+ .from(workspaces)
124
+ .where(and(eq(workspaces.id, workspaceId), eq(workspaces.accountId, accountId)))
125
+ .limit(1)
126
+ .execute())[0];
127
+ if (!owned)
128
+ return false;
129
+ // The workspace's membership — the domain the repo half is allowed to write inside.
130
+ const memberRows = patch.mutedRepoIds === undefined
131
+ ? []
132
+ : await db
133
+ .select({ repoId: workspaceRepos.repoId })
134
+ .from(workspaceRepos)
135
+ .where(and(eq(workspaceRepos.accountId, accountId), eq(workspaceRepos.workspaceId, workspaceId)))
136
+ .execute();
137
+ const membership = new Set(memberRows.map((r) => r.repoId));
138
+ const wanted = new Set((patch.mutedRepoIds ?? []).filter((id) => membership.has(id)));
139
+ const toClear = [...membership].filter((id) => !wanted.has(id));
140
+ await runTransaction(async (tx) => {
141
+ if (patch.muted !== undefined) {
142
+ await tx
143
+ .update(workspaces)
144
+ .set({ pendingMuted: patch.muted })
145
+ .where(and(eq(workspaces.id, workspaceId), eq(workspaces.accountId, accountId)))
146
+ .execute();
147
+ }
148
+ if (patch.mutedRepoIds !== undefined) {
149
+ if (toClear.length > 0) {
150
+ await tx
151
+ .delete(pendingMutedRepos)
152
+ .where(and(eq(pendingMutedRepos.accountId, accountId), inArray(pendingMutedRepos.repoId, toClear)))
153
+ .execute();
154
+ }
155
+ for (const repoId of wanted) {
156
+ // The conflict target is the table's ONE unique, (account_id, repo_id). Presence is the
157
+ // fact, so there is nothing to update — DO NOTHING keeps `created_at` as "when the reader
158
+ // muted it" rather than "when they last pressed Save".
159
+ await tx
160
+ .insert(pendingMutedRepos)
161
+ .values({ accountId, repoId })
162
+ .onConflictDoNothing({
163
+ target: [pendingMutedRepos.accountId, pendingMutedRepos.repoId],
164
+ })
165
+ .execute();
166
+ }
167
+ }
168
+ });
169
+ return true;
170
+ }
171
+ //# sourceMappingURL=pending-mute.js.map