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,321 @@
1
+ import { config } from '../../config.js';
2
+ // ---------------------------------------------------------------------------
3
+ // Security headers + cross-origin request guard.
4
+ //
5
+ // Deliberately hand-rolled rather than @fastify/helmet: helmet would be a new
6
+ // runtime dependency that must also be threaded through the curated release
7
+ // manifest (scripts/build-release.mjs) and the pinned lockfile, and its default
8
+ // CSP is wrong for this app in three ways anyway (it forbids the inline styles
9
+ // vis-timeline and React both emit, it forbids the `https:` images every GitHub
10
+ // markdown body embeds, and it knows nothing about the local/cloud split). The
11
+ // header set below is the same posture, sized to this app.
12
+ //
13
+ // Everything here is computed ONCE at module load — the per-request hook only
14
+ // does a couple of string comparisons and reply.header() calls.
15
+ // ---------------------------------------------------------------------------
16
+ // Google Tag Manager / GA4 endpoints. Analytics is CLOUD-ONLY (both analytics.ts
17
+ // callers gate on it) and now additionally consent-gated in the browser, so these
18
+ // only enter the CSP in cloud mode — a local install's CSP names no third party.
19
+ const GA_SCRIPT = 'https://www.googletagmanager.com';
20
+ const GA_CONNECT = [
21
+ 'https://www.google-analytics.com',
22
+ 'https://*.google-analytics.com',
23
+ 'https://*.analytics.google.com',
24
+ 'https://*.googletagmanager.com',
25
+ ].join(' ');
26
+ // `style-src 'unsafe-inline'` is unavoidable and accepted: React writes inline
27
+ // `style` attributes, vis-timeline positions every item by mutating element.style,
28
+ // and the landing's splash-caret keyframes are an inline <style>. CSS injection is
29
+ // a far weaker primitive than script injection, and script-src stays strict — no
30
+ // 'unsafe-inline', no 'unsafe-eval' (verified: neither vis-timeline, vis-data nor
31
+ // vis-util contains eval/new Function, so the timeline runs under this policy).
32
+ //
33
+ // `img-src https:` is required, not lax: PR/comment bodies are third-party markdown
34
+ // that legitimately embeds images from arbitrary hosts (user-content, camo, shields,
35
+ // gravatar). The markdown pipeline already strips javascript:/data: URLs via
36
+ // rehype-sanitize, so the residual risk is a tracking pixel, not code execution.
37
+ const SPA_CSP = [
38
+ "default-src 'self'",
39
+ `script-src 'self'${config.isCloud ? ` ${GA_SCRIPT}` : ''}`,
40
+ "style-src 'self' 'unsafe-inline'",
41
+ "font-src 'self'",
42
+ "img-src 'self' data: blob: https:",
43
+ "media-src 'self' https:",
44
+ `connect-src 'self'${config.isCloud ? ` ${GA_CONNECT}` : ''}`,
45
+ "worker-src 'self' blob:",
46
+ "frame-src 'none'",
47
+ "object-src 'none'",
48
+ "base-uri 'self'",
49
+ "form-action 'self'",
50
+ "frame-ancestors 'none'",
51
+ ].join('; ');
52
+ // The landing is a plain marketing page: no third-party images beyond its own
53
+ // assets, no embedded frames. Tighter than the SPA on purpose.
54
+ const LANDING_CSP = [
55
+ "default-src 'self'",
56
+ `script-src 'self' ${GA_SCRIPT}`,
57
+ "style-src 'self' 'unsafe-inline'",
58
+ "font-src 'self'",
59
+ "img-src 'self' data:",
60
+ `connect-src 'self' ${GA_CONNECT}`,
61
+ "object-src 'none'",
62
+ "base-uri 'self'",
63
+ "form-action 'self'",
64
+ "frame-ancestors 'none'",
65
+ ].join('; ');
66
+ // API responses are JSON and are never a document. `default-src 'none'` means that
67
+ // even if a response body were somehow rendered as HTML (a content-sniffing browser,
68
+ // a `view-source`-style navigation, an error page), nothing in it can load or run.
69
+ const API_CSP = [
70
+ "default-src 'none'",
71
+ "base-uri 'none'",
72
+ "form-action 'none'",
73
+ "frame-ancestors 'none'",
74
+ ].join('; ');
75
+ const PERMISSIONS_POLICY = [
76
+ 'accelerometer=()',
77
+ 'autoplay=()',
78
+ 'camera=()',
79
+ 'display-capture=()',
80
+ 'encrypted-media=()',
81
+ 'geolocation=()',
82
+ 'gyroscope=()',
83
+ 'magnetometer=()',
84
+ 'microphone=()',
85
+ 'midi=()',
86
+ 'payment=()',
87
+ 'usb=()',
88
+ 'xr-spatial-tracking=()',
89
+ ].join(', ');
90
+ /**
91
+ * Sets the response security headers on every request, plus (cloud only) HSTS and
92
+ * the www → apex canonical redirect. Registered FIRST in app.ts so the headers ride
93
+ * on every response including redirects, 404s and error bodies.
94
+ */
95
+ export function registerSecurityHeaders(app) {
96
+ // Cloud only: the canonical host, for the www redirect. hostname (not host) so a
97
+ // configured port never enters the comparison.
98
+ let canonicalHost = '';
99
+ if (config.isCloud) {
100
+ try {
101
+ canonicalHost = new URL(config.appBaseUrl).hostname.toLowerCase();
102
+ }
103
+ catch {
104
+ canonicalHost = '';
105
+ }
106
+ }
107
+ app.addHook('onRequest', async (req, reply) => {
108
+ const path = pathOf(req);
109
+ const isApi = path.startsWith('/api');
110
+ // ---- HSTS + canonical host (cloud only) ----
111
+ // Local mode runs on http://127.0.0.1, where HSTS would wrongly pin localhost
112
+ // to HTTPS and a www redirect is meaningless — so both stay cloud-gated.
113
+ if (config.isCloud) {
114
+ // Honored only over HTTPS (Railway terminates TLS); ignored on plain HTTP.
115
+ // `includeSubDomains` also covers www. No `preload` — it is hard to undo.
116
+ // HSTS_MAX_AGE=0 disables it.
117
+ if (config.hstsMaxAge > 0) {
118
+ reply.header('Strict-Transport-Security', `max-age=${config.hstsMaxAge}; includeSubDomains`);
119
+ }
120
+ // 301 www.<apex> → <apex> so the OAuth round-trip and the session cookie stay
121
+ // on a single origin (and crawlers see one canonical URL).
122
+ if (canonicalHost) {
123
+ const host = (req.headers.host ?? '').toLowerCase().split(':')[0] ?? '';
124
+ if (host !== canonicalHost &&
125
+ host.replace(/^www\./, '') === canonicalHost) {
126
+ return reply.redirect(`${config.appBaseUrl}${req.url}`, 301);
127
+ }
128
+ }
129
+ }
130
+ // ---- Content-Security-Policy, per surface ----
131
+ reply.header('Content-Security-Policy', isApi ? API_CSP : path === '/app' || path.startsWith('/app/') ? SPA_CSP : LANDING_CSP);
132
+ // ---- The rest, everywhere ----
133
+ // nosniff: stops a browser deciding a JSON error body is HTML and rendering it.
134
+ reply.header('X-Content-Type-Options', 'nosniff');
135
+ // Clickjacking. CSP frame-ancestors above is the modern control; X-Frame-Options
136
+ // is kept for older browsers that ignore it.
137
+ reply.header('X-Frame-Options', 'DENY');
138
+ // App URLs carry repo/PR ids in the query string — send the origin only, never
139
+ // the path, to any cross-origin destination.
140
+ reply.header('Referrer-Policy', 'strict-origin-when-cross-origin');
141
+ reply.header('Permissions-Policy', PERMISSIONS_POLICY);
142
+ // Isolate the browsing context from anything this app opens (and vice-versa).
143
+ // Safe here: OAuth is a top-level redirect, not a popup+postMessage handshake.
144
+ reply.header('Cross-Origin-Opener-Policy', 'same-origin');
145
+ if (isApi) {
146
+ // Block cross-origin no-cors embedding of API JSON (a <script src> / <img src>
147
+ // side-channel). Applied to /api ONLY — the landing's og-image.png must stay
148
+ // embeddable for link previews.
149
+ reply.header('Cross-Origin-Resource-Policy', 'same-origin');
150
+ // Never let PR data, feeds or account state land in a browser or proxy cache.
151
+ // Nothing in the app relies on HTTP caching (React Query + IndexedDB do their
152
+ // own caching at the application layer).
153
+ reply.header('Cache-Control', 'no-store');
154
+ }
155
+ });
156
+ }
157
+ // ---------------------------------------------------------------------------
158
+ // Cross-origin request guard
159
+ // ---------------------------------------------------------------------------
160
+ /** The path with any query string stripped. */
161
+ function pathOf(req) {
162
+ return req.url.split('?')[0] ?? req.url;
163
+ }
164
+ /**
165
+ * Origins allowed to make credentialed / state-changing calls to this instance.
166
+ *
167
+ * CLOUD: exactly the deployment's own origin.
168
+ *
169
+ * LOCAL: any loopback origin, on any port. This is deliberately a set of PATTERNS
170
+ * rather than a fixed list, because local mode is reached from several ports that
171
+ * are not knowable here — the Vite dev server (5173), the landing dev server (5174),
172
+ * the isolated demo stack (5273), the packaged CLI serving the SPA from the API port
173
+ * itself, and whatever PORT/BACKEND_PORT a user overrides. What it does NOT match is
174
+ * the case that matters: a page on a real website (https://evil.example) that the
175
+ * developer happens to have open while Pierre is running.
176
+ *
177
+ * ALLOWED_ORIGINS (comma-separated) adds extra origins for anything exotic.
178
+ */
179
+ const EXTRA_ORIGINS = (process.env.ALLOWED_ORIGINS ?? '')
180
+ .split(',')
181
+ .map((o) => o.trim().replace(/\/$/, ''))
182
+ .filter(Boolean);
183
+ const LOOPBACK_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);
184
+ function isLoopbackOrigin(origin) {
185
+ try {
186
+ const u = new URL(origin);
187
+ if (u.protocol !== 'http:' && u.protocol !== 'https:')
188
+ return false;
189
+ return LOOPBACK_HOSTNAMES.has(u.hostname.toLowerCase());
190
+ }
191
+ catch {
192
+ return false;
193
+ }
194
+ }
195
+ export function isAllowedOrigin(origin) {
196
+ const o = origin.replace(/\/$/, '');
197
+ if (EXTRA_ORIGINS.includes(o))
198
+ return true;
199
+ return config.isCloud ? o === config.appBaseUrl : isLoopbackOrigin(o);
200
+ }
201
+ /** The @fastify/cors `origin` option: an allowlist in BOTH modes (was `true` locally). */
202
+ export function corsOriginDelegate(origin, cb) {
203
+ // No Origin header = a same-origin navigation or a non-browser client (curl, the
204
+ // CLI, a webhook). CORS has nothing to decide; the guard below handles the
205
+ // state-changing case.
206
+ if (!origin)
207
+ return cb(null, true);
208
+ cb(null, isAllowedOrigin(origin));
209
+ }
210
+ // Server-to-server POSTs authenticated by an HMAC signature instead of an origin.
211
+ // GitHub and Stripe send no Origin and no Sec-Fetch-* headers, so they would pass
212
+ // the guard regardless — listed explicitly so that stays true by intent, not luck.
213
+ const GUARD_EXEMPT_PATHS = new Set([
214
+ '/api/webhooks/github',
215
+ '/api/billing/webhook',
216
+ ]);
217
+ // GET routes that change server state and so need the same protection as a POST.
218
+ // /api/auth/reconnect revokes the GitHub grant and drops the session; the frontend
219
+ // reaches it as a same-origin <a href> navigation, so guarding it is free.
220
+ // NOT guarded: /api/auth/login and /api/billing/checkout — those are legitimately
221
+ // entered as cross-site top-level navigations (a link from the README, a bookmark).
222
+ const GUARDED_GET_PATHS = new Set(['/api/auth/reconnect']);
223
+ /**
224
+ * Rejects state-changing /api requests that arrive from a foreign origin.
225
+ *
226
+ * This is NOT redundant with the CORS allowlist. CORS only governs whether the
227
+ * browser lets the *attacker's page* READ the response — a "simple" cross-origin
228
+ * POST (or a form submission, or a fetch with a non-preflighted content type) is
229
+ * still DELIVERED and still EXECUTES on the server. In local mode that matters a
230
+ * great deal, because there is no session cookie to miss: every request resolves to
231
+ * the single local account, so a drive-by POST from any page the developer has open
232
+ * would run as them — resolve review threads, trigger a paid AI run, add a repo.
233
+ * SameSite=Lax covers the cloud cookie case, but this closes the local hole and
234
+ * gives cloud a second, independent layer.
235
+ *
236
+ * Detection prefers `Sec-Fetch-Site`, which every current browser sends and no page
237
+ * can forge, and falls back to `Origin`. A request with neither (curl, a CI script,
238
+ * a webhook) is allowed: CSRF requires a browser, and blocking header-less clients
239
+ * would break the CLI and every integration.
240
+ */
241
+ export function registerCrossOriginGuard(app) {
242
+ app.addHook('onRequest', async (req, reply) => {
243
+ const path = pathOf(req);
244
+ if (!path.startsWith('/api/'))
245
+ return;
246
+ if (GUARD_EXEMPT_PATHS.has(path))
247
+ return;
248
+ const method = req.method.toUpperCase();
249
+ const mutating = method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS';
250
+ if (!mutating && !GUARDED_GET_PATHS.has(path))
251
+ return;
252
+ // `Sec-Fetch-Site` is set by the browser, unforgeable by page script:
253
+ // same-origin / same-site → our own UI
254
+ // none → user-initiated (address bar, bookmark)
255
+ // cross-site → another site caused this request
256
+ const site = req.headers['sec-fetch-site'];
257
+ if (typeof site === 'string') {
258
+ if (site === 'cross-site')
259
+ await deny(reply, req, 'sec-fetch-site');
260
+ return;
261
+ }
262
+ // Older/exotic browsers: fall back to Origin. Absent = not a cross-origin
263
+ // browser request, so allow.
264
+ const origin = req.headers.origin;
265
+ if (typeof origin === 'string' && origin !== 'null' && !isAllowedOrigin(origin)) {
266
+ await deny(reply, req, 'origin');
267
+ }
268
+ });
269
+ }
270
+ async function deny(reply, req, via) {
271
+ req.log.warn({ path: pathOf(req), method: req.method, origin: req.headers.origin, via }, 'blocked cross-origin state-changing request');
272
+ // 403, not 401: the caller may be perfectly well authenticated — the problem is
273
+ // where the request came from, and retrying with credentials will not help.
274
+ await reply.code(403).send({
275
+ error: 'Forbidden',
276
+ message: 'Cross-origin request blocked.',
277
+ });
278
+ }
279
+ // ---------------------------------------------------------------------------
280
+ // Host guard (local mode): DNS-rebinding defence
281
+ // ---------------------------------------------------------------------------
282
+ /**
283
+ * Local mode binds 127.0.0.1, so nothing on the network can reach it directly — but
284
+ * a hostname an attacker controls can be made to RESOLVE to 127.0.0.1 (DNS
285
+ * rebinding), at which point their page is same-origin with the app and every
286
+ * same-origin protection above evaporates. The defence is to check the Host header:
287
+ * a rebound request still carries `Host: evil.example`, never `localhost`.
288
+ *
289
+ * Only applied when the server is actually bound to a loopback address. If a user
290
+ * has deliberately set HOST to expose a local instance on their LAN, they need
291
+ * arbitrary Host values to work, so the check stands down (and ALLOWED_HOSTS lets
292
+ * them re-enable it for named hosts).
293
+ */
294
+ const EXTRA_HOSTS = (process.env.ALLOWED_HOSTS ?? '')
295
+ .split(',')
296
+ .map((h) => h.trim().toLowerCase())
297
+ .filter(Boolean);
298
+ export function registerHostGuard(app) {
299
+ if (config.isCloud)
300
+ return;
301
+ const boundToLoopback = LOOPBACK_HOSTNAMES.has(config.host.toLowerCase());
302
+ if (!boundToLoopback && EXTRA_HOSTS.length === 0)
303
+ return;
304
+ app.addHook('onRequest', async (req, reply) => {
305
+ // Host may carry a port, and an IPv6 literal is bracketed: [::1]:4000.
306
+ const raw = (req.headers.host ?? '').toLowerCase();
307
+ const hostname = raw.startsWith('[')
308
+ ? (raw.split(']')[0] ?? '') + ']'
309
+ : (raw.split(':')[0] ?? '');
310
+ if (LOOPBACK_HOSTNAMES.has(hostname) || EXTRA_HOSTS.includes(hostname))
311
+ return;
312
+ // An absent Host header (HTTP/1.0, a raw socket probe) is not a browser doing
313
+ // DNS rebinding — but it is also not something the app needs to serve.
314
+ req.log.warn({ host: raw, path: pathOf(req) }, 'blocked unexpected Host header');
315
+ await reply.code(421).send({
316
+ error: 'MisdirectedRequest',
317
+ message: 'This instance only serves requests addressed to localhost. Set ALLOWED_HOSTS to permit another hostname.',
318
+ });
319
+ });
320
+ }
321
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1,103 @@
1
+ import { getActivity, getConsolidatedFeed, listClaudeReviewsByRepo, markFeedSeen, resolveWorkspaceScope, } from '../../db/queries.js';
2
+ import { accountIdOf } from '../plugins/auth.js';
3
+ // Parse a comma-separated id list into an int array, or null when empty/absent (so
4
+ // `resolveWorkspaceScope` treats it as "no narrowing — every repo in the workspace").
5
+ //
6
+ // ⚠ ITS RESULT IS NEVER A SCOPE. It is the `narrow` argument to `resolveWorkspaceScope`, which
7
+ // INTERSECTS it with the workspace's membership; a handler that passed this list straight to a
8
+ // getter would let `?workspace=5&repoIds=<a repo of workspace 9>` render one workspace's console
9
+ // out of another's repos.
10
+ function parseIntList(raw) {
11
+ if (!raw)
12
+ return null;
13
+ const ids = raw
14
+ .split(',')
15
+ .map((s) => Number.parseInt(s.trim(), 10))
16
+ .filter((n) => Number.isFinite(n));
17
+ return ids.length > 0 ? ids : null;
18
+ }
19
+ // ---- WHY THESE TWO CONTENT ROUTES TAKE `?workspace=` ----
20
+ //
21
+ // They carried only `?repoIds=` for as long as `null` meant "every repo of the account", and
22
+ // that is exactly what broke under a workspace scope, two ways over:
23
+ //
24
+ // • a workspace with ZERO repos yields `repoIds: []`, every client query-string builder drops
25
+ // an empty array, and the server then answered with EVERY repo in the account — the precise
26
+ // opposite of "this workspace is empty";
27
+ // • two different workspaces both sitting on `repoIds = null` produced the SAME query string,
28
+ // so React Query served one workspace's data under the other's cache key with no refetch.
29
+ //
30
+ // `resolveWorkspaceScope` is the one resolver: absent / unparseable / another tenant's id all
31
+ // degrade to the account's DEFAULT workspace (never a 404 — every id yields the same response
32
+ // shape, so it is not an existence oracle), and the returned `repoIds` is ALWAYS
33
+ // `membership ∩ (?repoIds= ?? membership)`. A `[]` therefore reaches the getters as a concrete
34
+ // empty set and comes back empty, which is what an empty workspace must render.
35
+ export async function activityRoutes(app) {
36
+ // The Activity aggregate: per repo, current-state stats + thread totals +
37
+ // attention/unread flags + open PRs. Scoped to the active WORKSPACE — every repo in it, with
38
+ // no second visibility axis; `repoIds` + `userIds` narrow WITHIN that. Pure DB read — no
39
+ // GitHub sync, no AI.
40
+ //
41
+ // It takes the whole `BotScope`, not just the repo list: its acted-on bot stat needs the
42
+ // WORKSPACE to know who counts as a review bot, while `scope.repoIds` only narrows the data.
43
+ app.get('/api/activity', async (req) => {
44
+ const q = req.query;
45
+ const accountId = accountIdOf(req);
46
+ const scope = await resolveWorkspaceScope(accountId, q.workspace, parseIntList(q.repoIds));
47
+ return getActivity(accountId, scope, parseIntList(q.userIds));
48
+ });
49
+ // The consolidated Feed (the Activity "Feed" entry): one flat, chronological stream of real
50
+ // activity, each row flagged `isMyTurn` by participation. Scoped to the active WORKSPACE —
51
+ // every repo in it, with no second visibility axis; `repoIds` + `userIds` narrow WITHIN that.
52
+ // Pure DB read — no GitHub sync, no AI.
53
+ //
54
+ // `workspaceId` is passed SEPARATELY from `repoIds` and is required: the bots-only path
55
+ // resolves an automated-reviewer set, and that answer is a workspace fact. The two deliberately
56
+ // disagree on the single-PR isolation path (`prId`), which reaches a PR whose repo may be
57
+ // filtered out of `repoIds` — the workspace still owns "is this login a bot" there.
58
+ app.get('/api/activity/feed', async (req) => {
59
+ const q = req.query;
60
+ const accountId = accountIdOf(req);
61
+ const scope = await resolveWorkspaceScope(accountId, q.workspace, parseIntList(q.repoIds));
62
+ const limit = q.limit != null ? Number(q.limit) : null;
63
+ const offset = q.offset != null ? Number(q.offset) : 0;
64
+ const prId = q.prId != null ? Number(q.prId) : null;
65
+ // Bot-only feed window (days) — clamped to 1..90; only honored on the botsOnly path.
66
+ const botWindowDaysRaw = q.botWindowDays != null ? Number(q.botWindowDays) : null;
67
+ const botWindowDays = botWindowDaysRaw != null && Number.isFinite(botWindowDaysRaw)
68
+ ? Math.min(90, Math.max(1, Math.trunc(botWindowDaysRaw)))
69
+ : null;
70
+ return getConsolidatedFeed(accountId, {
71
+ workspaceId: scope.workspaceId,
72
+ repoIds: scope.repoIds,
73
+ userIds: parseIntList(q.userIds),
74
+ prId: prId != null && Number.isFinite(prId) ? prId : null,
75
+ limit: Number.isFinite(limit) && limit != null && limit > 0 ? limit : null,
76
+ offset: Number.isFinite(offset) && offset > 0 ? offset : 0,
77
+ excludeBots: q.excludeBots === 'true',
78
+ allowBotIds: parseIntList(q.allowBotIds),
79
+ botsOnly: q.botsOnly === 'true',
80
+ botWindowDays,
81
+ includeAllCommits: q.includeAllCommits === 'true',
82
+ // Opt-in CI-failure rows (PR heads + the default branch). OFF unless explicitly asked
83
+ // for, like includeAllCommits — a missing/garbage value means off, never on. Still a
84
+ // pure DB read over two indexed transition logs, so no new rate-limit tier is needed.
85
+ includeCiFailures: q.includeCiFailures === 'true',
86
+ });
87
+ });
88
+ // Mark the Activity Feed as seen (bumps the account's server-side "seen" marker to
89
+ // now). Called when the user views the feed; resets the "new My Turn since last here"
90
+ // count that drives the Welcome-back banner. Account-scoped; no body.
91
+ app.post('/api/activity/feed/mark-seen', async (req) => {
92
+ const at = await markFeedSeen(accountIdOf(req));
93
+ return { feedLastSeenAt: at.toISOString() };
94
+ });
95
+ // Repo-scoped Claude-review history for the Activity single-repo console. Ownership +
96
+ // feature-gating are handled inside the query (an unowned repo / disabled feature
97
+ // both return an empty list), so the caller never leaks another account's data.
98
+ app.get('/api/repos/:id/claude-reviews', async (req) => {
99
+ const { id } = req.params;
100
+ return listClaudeReviewsByRepo(Number.parseInt(id, 10), accountIdOf(req));
101
+ });
102
+ }
103
+ //# sourceMappingURL=activity.js.map
@@ -0,0 +1,67 @@
1
+ import { config } from '../../config.js';
2
+ import { getAiRuntimeStatus, installAiRuntime } from '../../ai/runtime.js';
3
+ // POST /api/ai/runtime/install — "Set up AI": download the pinned AI runtime (the Agent SDK and its
4
+ // three peers, ~110 MB) into `<dataDir>/ai-runtime`, streaming npm's progress as SSE.
5
+ // ai/runtime.ts owns the work; this route only relays it.
6
+ //
7
+ // LOCAL ONLY, STRUCTURALLY: registered only when `config.aiEnabled` (which is false in cloud and
8
+ // under LIMN_AI_DISABLED), and it refuses again on its own if ever reached in cloud.
9
+ //
10
+ // ⚠ IT IS A POST, SO THE DISCONNECT SIGNAL IS THE REPLY SOCKET (`reply.raw`), never
11
+ // `req.raw.on('close')` — a POST's request `close` fires as soon as the body is read (CLAUDE.md,
12
+ // Conventions). And a disconnect only UNSUBSCRIBES: the download is single-flight and keeps going,
13
+ // so a reload re-POSTs and joins the same run instead of starting a second one.
14
+ export async function aiRuntimeRoutes(app) {
15
+ app.post('/api/ai/runtime/install', async (_req, reply) => {
16
+ if (config.isCloud || !config.aiEnabled) {
17
+ return reply.code(404).send({ error: 'NotFound', message: 'Not available here.' });
18
+ }
19
+ const before = getAiRuntimeStatus();
20
+ reply.hijack();
21
+ const raw = reply.raw;
22
+ raw.writeHead(200, {
23
+ 'Content-Type': 'text/event-stream',
24
+ 'Cache-Control': 'no-cache, no-transform',
25
+ Connection: 'keep-alive',
26
+ 'X-Accel-Buffering': 'no',
27
+ });
28
+ let open = true;
29
+ const send = (e) => {
30
+ if (open && !raw.writableEnded)
31
+ raw.write(`data: ${JSON.stringify(e)}\n\n`);
32
+ };
33
+ const hb = setInterval(() => {
34
+ if (open && !raw.writableEnded)
35
+ raw.write(': hb\n\n');
36
+ }, 15_000);
37
+ const finish = () => {
38
+ if (!open)
39
+ return;
40
+ open = false;
41
+ clearInterval(hb);
42
+ if (!raw.writableEnded)
43
+ raw.end();
44
+ };
45
+ raw.on('close', () => {
46
+ open = false;
47
+ clearInterval(hb);
48
+ });
49
+ // Already there (a second tab, or the workspace's own SDK in dev): say so and stop.
50
+ if (before.runtime === 'ready') {
51
+ send({ type: 'done', runtime: 'ready' });
52
+ finish();
53
+ return;
54
+ }
55
+ const result = await installAiRuntime((p) => {
56
+ if (p.phase === 'done' || p.phase === 'failed')
57
+ return; // the terminal event is sent below
58
+ send({ type: 'progress', phase: p.phase, message: p.message });
59
+ });
60
+ if (result.ok)
61
+ send({ type: 'done', runtime: 'ready' });
62
+ else
63
+ send({ type: 'error', message: result.message });
64
+ finish();
65
+ });
66
+ }
67
+ //# sourceMappingURL=ai-runtime.js.map