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,532 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { config } from '../config.js';
3
+ import { conflictFileEntries } from './model.js';
4
+ /** At most one live session per `(accountId, prId)`. */
5
+ const sessions = new Map();
6
+ const keyOf = (accountId, prId) => `${accountId}:${prId}`;
7
+ /**
8
+ * Drop everything past its TTL, on every map operation.
9
+ *
10
+ * A lazy sweep rather than an interval: the map holds at most a handful of entries, and an
11
+ * unref-less timer in a module every route file imports is a process that will not exit.
12
+ */
13
+ function sweep(now) {
14
+ for (const [key, rec] of sessions) {
15
+ if (rec.expiresAt > now)
16
+ continue;
17
+ // A job still running owns its record — reaping it would leave the job writing into a
18
+ // record nobody can read, and the SSE stream on the other end waiting forever.
19
+ if (rec.openRunning || rec.commitRunning)
20
+ continue;
21
+ endStream(rec);
22
+ sessions.delete(key);
23
+ }
24
+ }
25
+ /**
26
+ * How long a record may live NO MATTER HOW OFTEN IT IS READ.
27
+ *
28
+ * ⚠ `touch()` ALONE BOUNDS NOTHING. Every manifest read touches, and the SPA polls the manifest
29
+ * for as long as the overlay is mounted (`useConflictSession` — the poll is the recovery channel
30
+ * after a proxy cuts the stream, so it cannot simply stop at `ready`). A tab left open overnight
31
+ * therefore pushed `expiresAt` out forever: the record stayed resident, `liveSessionIds()` kept
32
+ * reporting it to the janitor, and its two fetch refs pinned objects against every repack.
33
+ *
34
+ * So the TTL measures IDLENESS and this measures AGE, and a session hits whichever comes first.
35
+ * Losing one is cheap by construction (the header): the model is rebuilt against the shas as they
36
+ * are now, and the reader's decisions are in the SPA's own store under the pinned key.
37
+ */
38
+ const MAX_SESSION_LIFETIME_MS = 4 * 60 * 60_000;
39
+ /** Push the expiry out. Called on every successful lookup: an open resolver in use is not idle. */
40
+ function touch(rec, now) {
41
+ rec.expiresAt = Math.min(now + config.conflictSessionTtlMs, rec.createdAt + MAX_SESSION_LIFETIME_MS);
42
+ }
43
+ /* ═════════════════════ how many jobs may run ═════════════════════ */
44
+ /**
45
+ * ⚠ TWO CAPS, BECAUSE THEY ANSWER TWO DIFFERENT QUESTIONS — and they refuse in two different
46
+ * sentences, because they are two different facts about the world.
47
+ *
48
+ * PER ACCOUNT — one at a time. A resolver is a clone, two fetches and a merge-tree; a person
49
+ * resolves one pull request at a time, and a second one from the same account is a second tab
50
+ * or a second click, not a second job. The reader is told about THEIR OWN work: "you're already
51
+ * resolving one".
52
+ *
53
+ * GLOBAL — the machine's ceiling. It protects disk and CPU, and it is the ONLY thing the
54
+ * per-account cap cannot bound once there are many accounts.
55
+ *
56
+ * ⚠ A TENANT IS NEVER TOLD ABOUT ANOTHER TENANT'S LOAD. The global refusal says the service is
57
+ * busy and stops; it names no count, no account and no pull request. The predecessor was a single
58
+ * process-global cap of 2 whose own comment reasoned from "the LOCAL mode's single account", so
59
+ * in cloud one tenant opening two resolvers told every other tenant that two pull requests were
60
+ * already being prepared — false about their work, and a disclosure about somebody else's.
61
+ */
62
+ const MAX_JOBS_PER_ACCOUNT = 1;
63
+ const MAX_CONCURRENT_JOBS = 4;
64
+ /**
65
+ * ⚠ AND TWO MORE CAPS ON THE RECORDS THEMSELVES, WHICH ARE NOT THE SAME THING AS THE JOB CAPS
66
+ * ABOVE. A job ends in seconds; the RECORD it leaves behind holds a whole `ConflictModel` — the
67
+ * base/ours/theirs text of every region, up to `config.conflictMaxTotalBytes` (8 MiB) of it — and
68
+ * lives until the TTL or the lifetime ceiling reaps it. `MAX_JOBS_PER_ACCOUNT` serialises the
69
+ * builds and refuses NONE of them, so one account scripting an open per conflicted pull request
70
+ * retained one model per PR: hundreds of megabytes of string data in a process every other tenant
71
+ * shares, bounded by nothing. This is the bound.
72
+ *
73
+ * The arithmetic is deliberately conservative and the worst case is the one to read: 24 × 8 MiB.
74
+ * Real models are orders of magnitude smaller (a conflict is a few hunks of a few files), so the
75
+ * ceiling is reached by an adversary long before it is reached by readers.
76
+ *
77
+ * ⚠ EVICTION, NOT REFUSAL, AND ONLY OF A SETTLED RECORD. A running job owns its record (the same
78
+ * rule `sweep` follows). What an evicted reader loses is what a restart loses — the model, never
79
+ * the decisions — so refusing the open instead would protect nothing and block the work.
80
+ */
81
+ const MAX_SESSIONS_PER_ACCOUNT = 3;
82
+ const MAX_TOTAL_SESSIONS = 24;
83
+ /** Running jobs, split into this account's and everyone's, in ONE pass over the map. */
84
+ function jobCounts(accountId) {
85
+ let mine = 0;
86
+ let total = 0;
87
+ for (const rec of sessions.values()) {
88
+ if (!rec.openRunning && !rec.commitRunning)
89
+ continue;
90
+ total += 1;
91
+ if (rec.accountId === accountId)
92
+ mine += 1;
93
+ }
94
+ return { mine, total };
95
+ }
96
+ /**
97
+ * Make room for one more record: drop a SETTLED session — unwatched before watched, and within
98
+ * each the one read longest ago — from this account's own share first and then the process's,
99
+ * until both ceilings have room.
100
+ *
101
+ * ⚠ SYNCHRONOUS, like everything else in the claim window. `expiresAt` is the touch order (every
102
+ * touch sets it from the same TTL), so the smallest is the one read longest ago.
103
+ */
104
+ function evictForNewSession(accountId) {
105
+ // ⚠ AN UNWATCHED RECORD GOES FIRST. A live subscriber is a tab with the overlay open on
106
+ // screen; an abandoned one is exactly what this cap exists to collect. Both are survivable
107
+ // (the client falls through to the poll, gets "no longer open" and offers Start again), but
108
+ // one of them interrupts somebody mid-resolution and the other does not.
109
+ const rank = (rec) => [
110
+ rec.subscribers.size > 0 ? 1 : 0,
111
+ rec.expiresAt,
112
+ ];
113
+ const evictOldest = (of) => {
114
+ let oldest = null;
115
+ for (const rec of of) {
116
+ if (rec.openRunning || rec.commitRunning)
117
+ continue;
118
+ if (oldest == null) {
119
+ oldest = rec;
120
+ continue;
121
+ }
122
+ const [w, e] = rank(rec);
123
+ const [ow, oe] = rank(oldest);
124
+ if (w < ow || (w === ow && e < oe))
125
+ oldest = rec;
126
+ }
127
+ if (oldest == null)
128
+ return false;
129
+ endStream(oldest);
130
+ sessions.delete(keyOf(oldest.accountId, oldest.prId));
131
+ return true;
132
+ };
133
+ // This account's share first — a hoarder must never evict somebody else's work to make room
134
+ // for its own fourth session.
135
+ for (;;) {
136
+ const mine = [...sessions.values()].filter((r) => r.accountId === accountId);
137
+ if (mine.length < MAX_SESSIONS_PER_ACCOUNT)
138
+ break;
139
+ if (!evictOldest(mine))
140
+ break;
141
+ }
142
+ for (;;) {
143
+ if (sessions.size < MAX_TOTAL_SESSIONS)
144
+ break;
145
+ if (!evictOldest([...sessions.values()]))
146
+ break;
147
+ }
148
+ }
149
+ /**
150
+ * Why this account may not start a job right now, or null.
151
+ *
152
+ * ⚠ CALLED INSIDE THE SYNCHRONOUS CLAIM WINDOW and it must stay synchronous: an `await` between
153
+ * this and the `openRunning = true` / `commitRunning = true` write is the gap in which two
154
+ * requests both pass the check (the AI in-flight-slot defect, one feature over).
155
+ */
156
+ function capacityRefusal(accountId) {
157
+ if (shuttingDown)
158
+ return 'shutdown';
159
+ const { mine, total } = jobCounts(accountId);
160
+ if (mine >= MAX_JOBS_PER_ACCOUNT)
161
+ return 'account';
162
+ if (total >= MAX_CONCURRENT_JOBS)
163
+ return 'capacity';
164
+ return null;
165
+ }
166
+ /* ═════════════════════ shutdown ═════════════════════ */
167
+ /**
168
+ * Set by the SIGTERM handler (index.ts). From here on no NEW job may be claimed, while the ones
169
+ * already running are left alone to finish — a commit mid-push is a `git push` GitHub may
170
+ * already have accepted, and killing it is how a reader ends up with a landed commit and a
171
+ * screen that says it failed.
172
+ *
173
+ * ⚠ IT DOES NOT PRESERVE SESSIONS, AND MUST NOT GROW INTO SOMETHING THAT DOES. A restart loses
174
+ * every session, deliberately and correctly (see the header): the model is pinned to
175
+ * `(headSha, baseSha, modelHash)` and the reader's decisions live in the SPA's own store. What
176
+ * this buys is the IN-FLIGHT PUSH, nothing else.
177
+ */
178
+ let shuttingDown = false;
179
+ export function beginShutdown() {
180
+ shuttingDown = true;
181
+ }
182
+ /** Jobs still running anywhere in the process — the SIGTERM handler's wait condition. */
183
+ export function runningJobCount() {
184
+ let n = 0;
185
+ for (const rec of sessions.values())
186
+ if (rec.openRunning || rec.commitRunning)
187
+ n += 1;
188
+ return n;
189
+ }
190
+ /**
191
+ * The session ids the janitor must NOT collect refs for.
192
+ *
193
+ * ⚠ LIVE MEANS PRESENT IN THIS MAP, not "running". A settled session sitting at `ready` still
194
+ * owns `refs/pierre/conflict/<id>/{head,base}`, and its commit re-reads those refs minutes
195
+ * later — deleting them mid-resolution is how a commit lands against objects that are no longer
196
+ * reachable.
197
+ *
198
+ * ⚠ IT SWEEPS FIRST, AND THAT IS NOT AN OPTIMISATION. Every other sweep is request-driven
199
+ * (claim/peek/get/drop), and the case the janitor exists for — a reader who opens the resolver
200
+ * and closes the TAB, so the deferred DELETE never lands — is by definition the case where no
201
+ * further request arrives. Without this the expired record sat in the map, reported itself live
202
+ * on every tick, and its two refs were counted in `refsKept` and kept forever: the janitor
203
+ * scanned, repacked, and collected exactly the leak it was written to collect.
204
+ */
205
+ export function liveSessionIds(now = Date.now()) {
206
+ sweep(now);
207
+ const ids = new Set();
208
+ for (const rec of sessions.values())
209
+ ids.add(rec.sessionId);
210
+ return ids;
211
+ }
212
+ /**
213
+ * Claim the open slot for one PR.
214
+ *
215
+ * ⚠ SYNCHRONOUS, START TO FINISH, and it must stay that way. The whole point is that two POSTs
216
+ * arriving in the same tick cannot both start a clone; an `await` anywhere between the lookup
217
+ * and the `openRunning = true` write re-opens exactly that gap (the `synthesis/routes.ts`
218
+ * in-flight-slot pattern).
219
+ *
220
+ * `restart` discards a settled session and builds against the CURRENT shas. It cannot discard a
221
+ * RUNNING one — there is nothing to cancel a build with, and abandoning it would leave a clone
222
+ * and two fetches running for a record nobody will read.
223
+ */
224
+ export function claimSession(accountId, prId, opts, now = Date.now()) {
225
+ sweep(now);
226
+ const key = keyOf(accountId, prId);
227
+ const live = sessions.get(key) ?? null;
228
+ if (live && (live.openRunning || live.commitRunning)) {
229
+ // Re-attaching to a build already in flight is the right answer for a second tab; a
230
+ // restart on top of one is not, and neither is a second commit.
231
+ if (!opts.restart && live.openRunning) {
232
+ touch(live, now);
233
+ return { kind: 'reused', session: live };
234
+ }
235
+ return { kind: 'busy', session: live, reason: 'pr' };
236
+ }
237
+ if (live && !opts.restart) {
238
+ touch(live, now);
239
+ return { kind: 'reused', session: live };
240
+ }
241
+ if (live) {
242
+ endStream(live);
243
+ sessions.delete(key);
244
+ }
245
+ const refusal = capacityRefusal(accountId);
246
+ if (refusal)
247
+ return { kind: 'busy', session: null, reason: refusal };
248
+ // AFTER the refusal, so a claim that is going to be refused never costs somebody their model.
249
+ evictForNewSession(accountId);
250
+ const rec = {
251
+ sessionId: randomUUID(),
252
+ accountId,
253
+ prId,
254
+ status: 'preparing',
255
+ phase: null,
256
+ error: null,
257
+ autoApply: opts.autoApply,
258
+ model: null,
259
+ modelHash: '',
260
+ commit: null,
261
+ suggestions: new Map(),
262
+ edits: new Map(),
263
+ createdAt: now,
264
+ expiresAt: now + config.conflictSessionTtlMs,
265
+ openRunning: true,
266
+ commitRunning: false,
267
+ commitAbort: null,
268
+ subscribers: new Set(),
269
+ };
270
+ sessions.set(key, rec);
271
+ return { kind: 'created', session: rec };
272
+ }
273
+ /** The live session for this PR, whatever its id. Used by the routes that carry `?session=`
274
+ * only to tell "expired" apart from "superseded" — both refuse, with different sentences. */
275
+ export function peekSession(accountId, prId, now = Date.now()) {
276
+ sweep(now);
277
+ return sessions.get(keyOf(accountId, prId)) ?? null;
278
+ }
279
+ /** The session the caller named, or null — expired, superseded and never-existed are one
280
+ * answer here on purpose: all three mean "take your decisions again". */
281
+ export function getSession(accountId, prId, sessionId, now = Date.now()) {
282
+ sweep(now);
283
+ const rec = sessions.get(keyOf(accountId, prId));
284
+ if (!rec || rec.sessionId !== sessionId)
285
+ return null;
286
+ touch(rec, now);
287
+ return rec;
288
+ }
289
+ /**
290
+ * Drop a session by id. Returns false when it was already gone or already superseded — the
291
+ * DELETE route answers 204 either way, because "it is not there" is what the caller asked for.
292
+ *
293
+ * A session with a job in flight is NOT dropped: the reader closing the overlay must not leave
294
+ * a push writing into a record nobody holds. It expires on its own.
295
+ */
296
+ export function dropSession(accountId, prId, sessionId, now = Date.now()) {
297
+ sweep(now);
298
+ const key = keyOf(accountId, prId);
299
+ const rec = sessions.get(key);
300
+ if (!rec || rec.sessionId !== sessionId)
301
+ return false;
302
+ if (rec.openRunning || rec.commitRunning)
303
+ return false;
304
+ endStream(rec);
305
+ sessions.delete(key);
306
+ return true;
307
+ }
308
+ /**
309
+ * Claim the commit slot. SYNCHRONOUS for the same reason `claimSession` is: two clicks on
310
+ * "Commit" a tick apart must not become two pushes.
311
+ */
312
+ export function claimCommitSlot(rec, now = Date.now()) {
313
+ if (rec.openRunning || rec.commitRunning)
314
+ return { ok: false, reason: 'pr' };
315
+ // `rec` holds no job (the line above proved it), so it contributes nothing to either count
316
+ // and needs no exclusion.
317
+ const refusal = capacityRefusal(rec.accountId);
318
+ if (refusal)
319
+ return { ok: false, reason: refusal };
320
+ const ac = new AbortController();
321
+ rec.commitRunning = true;
322
+ rec.commitAbort = ac;
323
+ rec.commit = { status: 'running', phase: 'preparing', result: null, error: null };
324
+ touch(rec, now);
325
+ return { ok: true, signal: ac.signal };
326
+ }
327
+ /* ═════════════════════════════ settling a session ═════════════════════════════ */
328
+ /** Prepare progress. Silently ignored once the session has settled, so a late phase callback
329
+ * from an abandoned build cannot walk a `ready` session backwards. */
330
+ export function setPreparePhase(rec, phase) {
331
+ if (rec.status !== 'preparing')
332
+ return;
333
+ rec.phase = phase;
334
+ emit(rec, { type: 'progress', session: sessionView(rec) });
335
+ }
336
+ export function settleReady(rec, model, modelHash, clean) {
337
+ rec.model = model;
338
+ rec.modelHash = modelHash;
339
+ rec.status = clean ? 'clean' : 'ready';
340
+ rec.phase = null;
341
+ rec.openRunning = false;
342
+ touch(rec, Date.now());
343
+ emit(rec, { type: 'ready', session: sessionView(rec) });
344
+ }
345
+ export function settleFailed(rec, code, message) {
346
+ rec.status = 'failed';
347
+ rec.phase = null;
348
+ rec.error = { code, message };
349
+ rec.openRunning = false;
350
+ touch(rec, Date.now());
351
+ emit(rec, { type: 'failed', session: sessionView(rec) });
352
+ }
353
+ export function setCommitPhase(rec, phase) {
354
+ if (rec.commit?.status !== 'running')
355
+ return;
356
+ rec.commit = { ...rec.commit, phase };
357
+ touch(rec, Date.now());
358
+ emit(rec, { type: 'commit_progress', session: sessionView(rec) });
359
+ }
360
+ export function settleCommitDone(rec, result) {
361
+ rec.commit = { status: 'done', phase: null, result, error: null };
362
+ rec.commitRunning = false;
363
+ rec.commitAbort = null;
364
+ touch(rec, Date.now());
365
+ emit(rec, { type: 'commit_done', session: sessionView(rec), result });
366
+ }
367
+ export function settleCommitFailed(rec, code, message) {
368
+ const error = { code, message };
369
+ rec.commit = { status: 'failed', phase: null, result: null, error };
370
+ rec.commitRunning = false;
371
+ rec.commitAbort = null;
372
+ touch(rec, Date.now());
373
+ emit(rec, { type: 'commit_failed', session: sessionView(rec), error });
374
+ }
375
+ /* ═════════════════════════════ the stream ═════════════════════════════ */
376
+ export function subscribe(rec, cb) {
377
+ rec.subscribers.add(cb);
378
+ return () => {
379
+ rec.subscribers.delete(cb);
380
+ };
381
+ }
382
+ /** ⚠ A subscriber that throws must not take the job down with it: the emitter is the build,
383
+ * and a broken socket is not a reason to abandon a push. */
384
+ function emit(rec, e) {
385
+ for (const cb of [...rec.subscribers]) {
386
+ try {
387
+ cb(e);
388
+ }
389
+ catch {
390
+ /* a dead socket — the stream's own close handler unsubscribes it */
391
+ }
392
+ }
393
+ }
394
+ /** Tell every live stream this session is gone, so the client stops waiting on a record that
395
+ * no longer exists. */
396
+ function endStream(rec) {
397
+ emit(rec, { type: 'done' });
398
+ rec.subscribers.clear();
399
+ }
400
+ /* ═════════════════════════════ Pro suggestions ═════════════════════════════ */
401
+ /**
402
+ * Store one validated suggestion and mint its handle.
403
+ *
404
+ * The HOST owns this, not the plugin: the plugin validates and hands back lines, and the id it
405
+ * would otherwise mint would be an id into a store the commit path cannot read. Valid for this
406
+ * session only — a new model is a new session, and a suggestion against the old bytes is
407
+ * `UnknownSuggestion` rather than a splice of text nobody looked at.
408
+ */
409
+ export function storeSuggestion(rec, suggestion) {
410
+ const id = randomUUID();
411
+ rec.suggestions.set(id, suggestion);
412
+ touch(rec, Date.now());
413
+ return id;
414
+ }
415
+ /* ═════════════════════════════ manual edits (CORE) ═════════════════════════════ */
416
+ /**
417
+ * ⚠ A THIRD MEMORY BOUND, FOR THE ONE THING IN THIS FILE A READER CAN GROW BY TYPING.
418
+ *
419
+ * The record caps above bound how many MODELS the process retains; this bounds how much typed
420
+ * text one of them may accumulate — and since nothing is ever evicted (see `storeEdit`), it is
421
+ * the WHOLE bound. Without it the ceiling would be one edit per decidable region per save:
422
+ * `CONFLICT_MAX_FILES` × `CONFLICT_MAX_REGIONS` is 4,800 regions, at up to
423
+ * `conflictSuggestMaxChars` (4,000) each, so ~19 MiB per session before anybody re-saves — more
424
+ * than the 8 MiB the whole model is capped at, in a process every tenant shares. Sixty-four
425
+ * full-size edits is far more hand-editing than any resolution contains and well under a
426
+ * megabyte, so the refusal is reachable by a script and not by a person.
427
+ *
428
+ * ⚠ IT IS TIED TO THE PER-EDIT CAP RATHER THAN SPELLED AS A NUMBER, so the two move together.
429
+ */
430
+ const MAX_EDIT_CHARS_PER_SESSION = 64 * config.conflictSuggestMaxChars;
431
+ const editChars = (edit) => {
432
+ let n = 0;
433
+ for (const line of edit.lines)
434
+ n += line.length + 1;
435
+ return n;
436
+ };
437
+ /**
438
+ * Store one validated edit and mint its handle, or null when this session is already holding
439
+ * as much typed text as it may.
440
+ *
441
+ * ⚠ A HANDLE IS NEVER EVICTED — `storeSuggestion`'s rule, and for the same reason. Re-saving a
442
+ * region used to DELETE its previous edit on the argument that "a region has one current text";
443
+ * true of the region, false of the handles pointing at it. The undo stack is the counter-example
444
+ * that shipped: it files `previousEditId` when a decision is replaced, so a second save followed
445
+ * by one Ctrl+Z restored an id the server had just destroyed. The pane could not tell — the
446
+ * client's own line map is append-only, so the region went on rendering applied-green and
447
+ * counting as decided — and the WHOLE commit then came back `UnknownEdit`, naming no file and no
448
+ * region, with every region on screen looking answered. A second tab saving the same region did
449
+ * it to the first tab's decisions from across the process. `MAX_EDIT_CHARS_PER_SESSION` is the
450
+ * bound now, and it is a real one.
451
+ *
452
+ * ⚠ AND THE BUDGET IS COMPUTED WITHOUT MUTATING, so a refusal is the no-op this route's contract
453
+ * promises ("a refusal mints nothing, so there is no handle to redeem"). The eviction ran BEFORE
454
+ * the sum, so a save that overran the cap destroyed the region's previous, accepted, already-
455
+ * decided edit on its way to answering `too_many_edits` — the same unlocatable `UnknownEdit` at
456
+ * commit time, reached by doing nothing wrong.
457
+ *
458
+ * ⚠ THE HOST MINTS THE ID, exactly as it does for a suggestion: an id minted anywhere else is
459
+ * an id into a store the commit path cannot read.
460
+ */
461
+ export function storeEdit(rec, edit) {
462
+ let held = 0;
463
+ for (const e of rec.edits.values())
464
+ held += editChars(e);
465
+ if (held + editChars(edit) > MAX_EDIT_CHARS_PER_SESSION)
466
+ return null;
467
+ const id = randomUUID();
468
+ rec.edits.set(id, edit);
469
+ touch(rec, Date.now());
470
+ return id;
471
+ }
472
+ /* ═════════════════════════════ the wire projection ═════════════════════════════ */
473
+ /**
474
+ * The manifest. ⚠ NO REGIONS — a thirty-file conflict carrying them inline is a multi-megabyte
475
+ * payload on every progress frame; the SPA fetches one file's regions on selection (route 4).
476
+ *
477
+ * While `preparing` there is no model yet, so the pins are empty strings. The SPA reads them
478
+ * only once `status === 'ready'` (they are the key its decision store is filed under), and the
479
+ * `clean` sentence reads `baseRef`, which a clean build DOES carry a model for.
480
+ */
481
+ export function sessionView(rec) {
482
+ const model = rec.model;
483
+ const files = model ? conflictFileEntries(model) : [];
484
+ return {
485
+ sessionId: rec.sessionId,
486
+ prId: rec.prId,
487
+ status: rec.status,
488
+ phase: rec.phase,
489
+ error: rec.error,
490
+ headSha: model?.headSha ?? '',
491
+ baseSha: model?.baseSha ?? '',
492
+ modelHash: rec.modelHash,
493
+ mergeBaseSha: model?.mergeBaseSha ?? null,
494
+ mergeBaseIsVirtual: model?.mergeBaseIsVirtual ?? false,
495
+ baseRef: model?.baseRef ?? '',
496
+ headRef: model?.headRef ?? '',
497
+ files,
498
+ unsupportedIndexes: files.filter((f) => f.unsupported !== null).map((f) => f.index),
499
+ // Every conflicted path merge-tree named is resolvable here: none refused, and the caps
500
+ // stopped us reaching none of them. `truncated` is the second half — a file we never
501
+ // extracted is a file the commit leaves conflicted just as surely as a binary one.
502
+ fullyResolvable: model !== null && !model.truncated && files.every((f) => f.unsupported === null),
503
+ autoApplied: rec.autoApply,
504
+ renameDetection: model?.renameDetection ?? 'on',
505
+ truncated: model?.truncated ?? false,
506
+ totalConflictedPaths: model?.totalConflictedPaths ?? 0,
507
+ strategies: model?.strategies ?? [],
508
+ rebaseUnavailableReason: model?.rebaseUnavailableReason ?? null,
509
+ // ⚠ `true` while there is no model, for the same reason the model itself defaults true: an
510
+ // absent fact is not a refusal, and the land route is what actually decides.
511
+ prBranchPushable: model?.prBranchPushable ?? true,
512
+ prBranchUnavailableReason: model?.prBranchUnavailableReason ?? null,
513
+ reservedBranchNames: model?.reservedBranchNames ?? [],
514
+ commit: rec.commit,
515
+ };
516
+ }
517
+ /** Test seam only. */
518
+ export const __testing = {
519
+ sessions,
520
+ MAX_CONCURRENT_JOBS,
521
+ MAX_JOBS_PER_ACCOUNT,
522
+ MAX_SESSIONS_PER_ACCOUNT,
523
+ MAX_TOTAL_SESSIONS,
524
+ MAX_EDIT_CHARS_PER_SESSION,
525
+ reset: () => {
526
+ for (const rec of sessions.values())
527
+ rec.subscribers.clear();
528
+ sessions.clear();
529
+ shuttingDown = false;
530
+ },
531
+ };
532
+ //# sourceMappingURL=session.js.map