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 { mkdir, writeFile } from 'node:fs/promises';
2
+ import { dirname } from 'node:path';
3
+ import { and, eq } from 'drizzle-orm';
4
+ import { getAccessToken, getAccountById } from '../auth/account.js';
5
+ import { cleanupCloneCache, prepWorktree, prepWorktreeAtRef, removeWorktreeLocked, } from '../review/clone-manager.js';
6
+ import { createPullRequest, fetchPrHeadInfo, } from '../github/mutations.js';
7
+ import { ghRestGetFor, ghRestGetText } from '../github/client.js';
8
+ import { db, schema } from '../db/client.js';
9
+ import { settlePrAfterWrite } from '../sync/resync-after-write.js';
10
+ import { syncOnePr } from '../sync/sync-one-pr.js';
11
+ import { applyPatchToWorktree, codedError, commitAll, pushRef, } from './git.js';
12
+ import { assertSafeTargetPath, resolveContainedTargetPath } from './safe-path.js';
13
+ /**
14
+ * The names a push for this PR must never land on: the repo's default branch and the PR's
15
+ * base ref, read from the LOCAL DB (no GitHub call).
16
+ *
17
+ * Returns [] when the push target repo is NOT the watched repo — a fork PR pushes to the
18
+ * FORK, where `main` is the contributor's own branch and protecting it would refuse a
19
+ * legitimate push.
20
+ */
21
+ export async function protectedRefsFor(accountId, owner, name, prNumber, pushOwner, pushName) {
22
+ if (pushOwner.toLowerCase() !== owner.toLowerCase() ||
23
+ pushName.toLowerCase() !== name.toLowerCase()) {
24
+ return [];
25
+ }
26
+ const { repos, pullRequests } = schema;
27
+ const [repoRow] = await db
28
+ .select({ id: repos.id, defaultBranch: repos.defaultBranch })
29
+ .from(repos)
30
+ .where(and(eq(repos.accountId, accountId), eq(repos.owner, owner), eq(repos.name, name)))
31
+ .execute();
32
+ if (!repoRow)
33
+ return [];
34
+ const [prRow] = await db
35
+ .select({ baseRefName: pullRequests.baseRefName })
36
+ .from(pullRequests)
37
+ .where(and(eq(pullRequests.repoId, repoRow.id), eq(pullRequests.number, prNumber)))
38
+ .execute();
39
+ const names = [repoRow.defaultBranch, prRow?.baseRefName];
40
+ return [...new Set(names.filter((n) => Boolean(n)))];
41
+ }
42
+ // The local repo id for (account, owner/name), or null. Account-scoped: an owner/name another
43
+ // tenant tracks resolves only to THIS account's row.
44
+ async function localRepoId(accountId, owner, name) {
45
+ const { repos } = schema;
46
+ const [repoRow] = await db
47
+ .select({ id: repos.id })
48
+ .from(repos)
49
+ .where(and(eq(repos.accountId, accountId), eq(repos.owner, owner), eq(repos.name, name)))
50
+ .execute();
51
+ return repoRow?.id ?? null;
52
+ }
53
+ // After a push onto an EXISTING PR's head: settle-and-verify that PR against the pushed commit.
54
+ // The PR number resolves ONLY within (accountId, repoId) — numbers are unique per repo. Never
55
+ // throws: the push has already landed.
56
+ async function settlePushedPr(accountId, owner, name, prNumber, commitSha) {
57
+ try {
58
+ const repoId = await localRepoId(accountId, owner, name);
59
+ if (repoId == null)
60
+ return;
61
+ const { pullRequests } = schema;
62
+ const [prRow] = await db
63
+ .select({ id: pullRequests.id })
64
+ .from(pullRequests)
65
+ .where(and(eq(pullRequests.accountId, accountId), eq(pullRequests.repoId, repoId), eq(pullRequests.number, prNumber)))
66
+ .execute();
67
+ if (!prRow)
68
+ return;
69
+ await settlePrAfterWrite({
70
+ accountId,
71
+ prId: prRow.id,
72
+ log: quietLogger,
73
+ expect: { headSha: commitSha },
74
+ });
75
+ }
76
+ catch {
77
+ /* the push is real; a failed confirmation only delays what the SPA sees */
78
+ }
79
+ }
80
+ // After a push that OPENED a new PR in (owner/name): targeted-sync it. Never throws.
81
+ async function syncNewPrInRepo(accountId, owner, name, prNumber) {
82
+ try {
83
+ const repoId = await localRepoId(accountId, owner, name);
84
+ if (repoId == null)
85
+ return;
86
+ await syncOnePr(repoId, prNumber, quietLogger, { waitForInFlight: true });
87
+ }
88
+ catch {
89
+ /* the PR is real; the next walk picks it up */
90
+ }
91
+ }
92
+ // The implementation behind ctx.coding.applyAndPush. STATELESS: it re-preps a fresh
93
+ // worktree at the patch's exact base commit and `git apply`s the stored patch, so a
94
+ // fix survives a restart and works on ephemeral cloud disk. Owns the head-moved guard
95
+ // (existing-branch only) and does all git writes (the agent never can).
96
+ export async function applyAndPush(args) {
97
+ const { accountId, owner, name, prNumber, baseSha, patch, commitMessage, target } = args;
98
+ const token = await getAccessToken(accountId);
99
+ const account = await getAccountById(accountId);
100
+ const authorName = account?.displayName || account?.githubLogin || 'pierre-review';
101
+ const authorEmail = account?.githubLogin
102
+ ? `${account.githubLogin}@users.noreply.github.com`
103
+ : 'pierre-review@users.noreply.github.com';
104
+ // Head-moved / un-pushable-fork guard applies ONLY to the existing-branch path
105
+ // (pushing onto the PR's own head). A new branch is self-contained off baseSha and
106
+ // is immune to the head having advanced.
107
+ //
108
+ // Whether the PR's head branch lives in THIS repo — read here, used by the post-push settle
109
+ // below. ⚠ A FORK PR's head lives in the fork, but the push below goes to `owner/name` (the
110
+ // base repo), so for a fork it writes a same-named branch the PR does not track. That is a
111
+ // latent defect of this path (the conflict resolver pushes to `headRepoFullName` instead) and
112
+ // it is NOT changed here; the settle is simply skipped for it, because the PR's head can
113
+ // never become the pushed commit and the ladder would run to exhaustion for nothing.
114
+ let pushedToPrHeadRepo = false;
115
+ if (target.kind === 'existing') {
116
+ const info = await fetchPrHeadInfo(token, owner, name, prNumber);
117
+ pushedToPrHeadRepo =
118
+ !info.isFork &&
119
+ info.headRepoFullName.toLowerCase() === `${owner}/${name}`.toLowerCase();
120
+ if (info.headSha !== baseSha) {
121
+ throw codedError('HEAD_MOVED', `the PR head advanced (now ${info.headSha.slice(0, 7)}, fix was built on ${baseSha.slice(0, 7)})`);
122
+ }
123
+ if (info.isFork && !info.maintainerCanModify) {
124
+ throw codedError('PUSH_DENIED', 'the PR head is a fork this account cannot push to — push to a new branch instead');
125
+ }
126
+ }
127
+ else {
128
+ // NEW branch, never force: a live ref under this name means someone (an earlier fix run,
129
+ // a person) already owns it — refuse rather than clobber. commitFilesAndOpenPr has always
130
+ // done this; this path used to discover the collision as a push rejection instead.
131
+ const existing = await ghRestGetText(token, `/repos/${owner}/${name}/git/ref/heads/${target.branch}`);
132
+ if (existing.ok) {
133
+ throw codedError('BRANCH_EXISTS', `branch ${target.branch} already exists on ${owner}/${name}`);
134
+ }
135
+ }
136
+ const protect = await protectedRefsFor(accountId, owner, name, prNumber, owner, name);
137
+ let repoCloneDir = null;
138
+ let worktreePath = null;
139
+ try {
140
+ ({ repoCloneDir, worktreePath } = await prepWorktree(owner, name, prNumber, baseSha, token));
141
+ await applyPatchToWorktree(worktreePath, patch);
142
+ const commitSha = await commitAll(worktreePath, {
143
+ message: commitMessage,
144
+ authorName,
145
+ authorEmail,
146
+ });
147
+ const push = { worktree: worktreePath, owner, name, token, committish: 'HEAD', protect };
148
+ if (target.kind === 'existing') {
149
+ await pushRef({ ...push, remoteBranch: target.headRef });
150
+ // ── The push LANDED: nothing below may throw (a retry would double-push). ──────────
151
+ // Settle-and-verify against the pushed commit (sync/resync-after-write.ts), so the stored
152
+ // head and conflict/CI verdict catch up; when they move, persistPr raises the SPA change
153
+ // signal and the SPA's post-write poll window refetches. Only when the pushed repo IS the
154
+ // PR's head repo — see `pushedToPrHeadRepo` above.
155
+ // ⚠ NOT AWAITED. The caller records the push (the plugin's `markFixPushed`) only AFTER this
156
+ // returns, and awaiting the settle's ~7s inline wait widened "pushed on GitHub but not
157
+ // recorded here" from milliseconds to seconds — a restart in that window left the fix row
158
+ // unmarked. `settlePushedPr` never rejects.
159
+ if (pushedToPrHeadRepo) {
160
+ void settlePushedPr(accountId, owner, name, prNumber, commitSha);
161
+ }
162
+ return { pushedBranch: target.headRef, commitSha };
163
+ }
164
+ // New branch → push then open a PR against the base.
165
+ await pushRef({ ...push, remoteBranch: target.branch });
166
+ const pr = await createPullRequest(token, {
167
+ owner,
168
+ name,
169
+ head: target.branch,
170
+ base: target.base,
171
+ title: target.title,
172
+ body: target.body,
173
+ });
174
+ // The new PR exists on GitHub: sync it so it becomes local. ⚠ NOT AWAITED, for the same
175
+ // reason as above: the caller records the new PR's number and URL only after this returns.
176
+ // The sync's first sighting raises the SPA change signal. `syncNewPrInRepo` never rejects.
177
+ void syncNewPrInRepo(accountId, owner, name, pr.number);
178
+ return {
179
+ pushedBranch: target.branch,
180
+ commitSha,
181
+ prNumber: pr.number,
182
+ prUrl: pr.url,
183
+ };
184
+ }
185
+ finally {
186
+ if (repoCloneDir && worktreePath) {
187
+ await removeWorktreeLocked(owner, name, repoCloneDir, worktreePath).catch(() => { });
188
+ }
189
+ if (repoCloneDir) {
190
+ setImmediate(() => {
191
+ try {
192
+ cleanupCloneCache();
193
+ }
194
+ catch {
195
+ /* advisory cleanup — never surface */
196
+ }
197
+ });
198
+ }
199
+ }
200
+ }
201
+ // ── commitFilesAndOpenPr — the advisor's config-PR primitive ────────────────────────────────
202
+ // The implementation behind ctx.coding.commitFilesAndOpenPr: worktree at the repo's DEFAULT
203
+ // branch → write LITERAL file contents (the adapter merged them against the fetched originals
204
+ // upstream — this function writes bytes, it never merges) → commit → push a NEW branch (a
205
+ // plain push, never force; an existing branch is a refusal, not an overwrite) → open the PR →
206
+ // the syncOnePr visibility tail. After GitHub 201s the PR, nothing here may throw — a failed
207
+ // confirming sync is `visible: false`, and the caller's copy contract ("it'll show up here
208
+ // shortly", never a retry) does the rest.
209
+ // The OAuth token has no `workflow` scope, so a push touching .github/workflows/* is rejected
210
+ // by GitHub AFTER the branch exists — refuse outright instead of half-failing. (The general
211
+ // path rules live in safe-path.ts; this one is about a token, not a filesystem.)
212
+ function assertNotWorkflowPath(path) {
213
+ const norm = path.replace(/\\/g, '/');
214
+ if (norm === '.github/workflows' || norm.startsWith('.github/workflows/')) {
215
+ throw codedError('PUSH_DENIED', `refusing to write ${path}: workflow files need the \`workflow\` OAuth scope, which this token does not carry`);
216
+ }
217
+ }
218
+ const quietLogger = {
219
+ info: () => { },
220
+ warn: (m, ...a) => console.warn(m, ...a),
221
+ error: (m, ...a) => console.error(m, ...a),
222
+ };
223
+ export async function commitFilesAndOpenPr(args) {
224
+ const { accountId, owner, name, files, branch, title, body } = args;
225
+ if (files.length === 0)
226
+ throw codedError('APPLY_FAILED', 'no files to commit');
227
+ for (const f of files) {
228
+ assertSafeTargetPath(f.path);
229
+ assertNotWorkflowPath(f.path);
230
+ }
231
+ // ⚠ STAYS HERE. This is a naming convention on a branch the ADVISOR INVENTS, not a push
232
+ // guard — hoisting it into pushRef would refuse real GitHub head refs (`feature/#123`,
233
+ // `user's-branch`, `a+b`, `ünicode/x`). The push guard is assertPushTarget in git.ts.
234
+ if (!/^[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(branch) || branch.includes('..')) {
235
+ throw codedError('APPLY_FAILED', `invalid branch name: ${branch}`);
236
+ }
237
+ const token = await getAccessToken(accountId);
238
+ const account = await getAccountById(accountId);
239
+ const authorName = account?.displayName || account?.githubLogin || 'pierre-review';
240
+ const authorEmail = account?.githubLogin
241
+ ? `${account.githubLogin}@users.noreply.github.com`
242
+ : 'pierre-review@users.noreply.github.com';
243
+ // NEW branch, never force: a live ref under this name means someone (possibly an earlier
244
+ // advisor run) already owns it — refuse rather than clobber or confuse two PRs.
245
+ const existing = await ghRestGetText(token, `/repos/${owner}/${name}/git/ref/heads/${branch}`);
246
+ if (existing.ok) {
247
+ throw codedError('BRANCH_EXISTS', `branch ${branch} already exists on ${owner}/${name}`);
248
+ }
249
+ // The DEFAULT branch is the base: the config that governs FUTURE reviews is the default
250
+ // branch's copy (repo grain — a head-branch config affects only that PR's reviews).
251
+ const repoMeta = await ghRestGetFor(token, `/repos/${owner}/${name}`);
252
+ const base = repoMeta.default_branch;
253
+ if (!base)
254
+ throw codedError('TRUNK_FETCH_FAILED', `no default branch for ${owner}/${name}`);
255
+ let repoCloneDir = null;
256
+ let worktreePath = null;
257
+ try {
258
+ ({ repoCloneDir, worktreePath } = await prepWorktreeAtRef(owner, name, base, token));
259
+ for (const f of files) {
260
+ // Containment is re-checked HERE, against the real checkout: a committed symlink is a
261
+ // real directory entry and the lexical pass above cannot see one.
262
+ const target = resolveContainedTargetPath(worktreePath, f.path);
263
+ await mkdir(dirname(target), { recursive: true });
264
+ await writeFile(target, f.content, 'utf8');
265
+ }
266
+ await commitAll(worktreePath, { message: title, authorName, authorEmail });
267
+ // The base here is the repo's DEFAULT branch, so it is exactly what must be protected.
268
+ await pushRef({
269
+ worktree: worktreePath,
270
+ owner,
271
+ name,
272
+ token,
273
+ committish: 'HEAD',
274
+ remoteBranch: branch,
275
+ protect: [base],
276
+ });
277
+ const pr = await createPullRequest(token, { owner, name, head: branch, base, title, body });
278
+ // ── After this point the PR EXISTS on GitHub: nothing below may throw. ──────────────
279
+ // Visibility tail (the resync-after-write contract): targeted-sync the new PR so the
280
+ // SPA's next read of the local DB sees it, and CONFIRM with a SELECT rather than trust
281
+ // the sync's return value.
282
+ let visible = false;
283
+ try {
284
+ const { repos, pullRequests } = schema;
285
+ const [repoRow] = await db
286
+ .select({ id: repos.id })
287
+ .from(repos)
288
+ .where(and(eq(repos.accountId, accountId), eq(repos.owner, owner), eq(repos.name, name)))
289
+ .execute();
290
+ if (repoRow) {
291
+ await syncOnePr(repoRow.id, pr.number, quietLogger, { waitForInFlight: true });
292
+ const [prRow] = await db
293
+ .select({ id: pullRequests.id })
294
+ .from(pullRequests)
295
+ .where(and(eq(pullRequests.repoId, repoRow.id), eq(pullRequests.number, pr.number)))
296
+ .execute();
297
+ visible = Boolean(prRow);
298
+ }
299
+ }
300
+ catch {
301
+ /* the PR is real; a failed confirmation is visible:false, never an error */
302
+ }
303
+ return { prNumber: pr.number, url: pr.url, visible };
304
+ }
305
+ finally {
306
+ if (repoCloneDir && worktreePath) {
307
+ await removeWorktreeLocked(owner, name, repoCloneDir, worktreePath).catch(() => { });
308
+ }
309
+ if (repoCloneDir) {
310
+ setImmediate(() => {
311
+ try {
312
+ cleanupCloneCache();
313
+ }
314
+ catch {
315
+ /* advisory cleanup — never surface */
316
+ }
317
+ });
318
+ }
319
+ }
320
+ }
321
+ //# sourceMappingURL=git-ops.js.map
@@ -0,0 +1,184 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { promisify } from 'node:util';
6
+ const execFileAsync = promisify(execFile);
7
+ // Low-level git helpers for the AI-Fix write path. Args are ALWAYS an array (never a
8
+ // shell string), so there's no interpolation/injection surface — the same discipline
9
+ // as clone-manager.ts. A tokenized push URL is passed per-op and never persisted.
10
+ /** An Error carrying a `.code` the push route maps to an HTTP status. */
11
+ export function codedError(code, message) {
12
+ const err = new Error(message);
13
+ err.code = code;
14
+ return err;
15
+ }
16
+ async function git(args, cwd) {
17
+ return execFileAsync('git', args, {
18
+ cwd,
19
+ timeout: 120_000,
20
+ maxBuffer: 128 * 1024 * 1024,
21
+ });
22
+ }
23
+ /**
24
+ * Stage everything (including NEW and deleted files) and capture the change as a
25
+ * binary-safe unified diff against HEAD, plus the changed-file list. `git add -A`
26
+ * before `git diff --cached` is what makes newly-created files round-trip — a plain
27
+ * `git diff` silently omits untracked files. `--binary` round-trips assets too.
28
+ */
29
+ export async function captureWorktreeDiff(worktree) {
30
+ await git(['add', '-A'], worktree);
31
+ const { stdout: patch } = await git(['diff', '--cached', '--binary'], worktree);
32
+ const { stdout: names } = await git(['diff', '--cached', '--name-only'], worktree);
33
+ const filesChanged = names
34
+ .split('\n')
35
+ .map((s) => s.trim())
36
+ .filter(Boolean);
37
+ return { patch, filesChanged };
38
+ }
39
+ /**
40
+ * Apply a stored unified-diff patch onto the worktree (checked out at the patch's
41
+ * exact base commit). `--3way` is a safety net if the tree isn't byte-identical.
42
+ * Throws a coded 'APPLY_FAILED' error if the patch doesn't apply.
43
+ */
44
+ export async function applyPatchToWorktree(worktree, patch) {
45
+ const tmp = mkdtempSync(join(tmpdir(), 'pierre-fix-'));
46
+ const patchFile = join(tmp, 'fix.patch');
47
+ try {
48
+ writeFileSync(patchFile, patch, 'utf-8');
49
+ await git(['apply', '--3way', patchFile], worktree);
50
+ }
51
+ catch (err) {
52
+ throw codedError('APPLY_FAILED', `patch did not apply cleanly: ${err instanceof Error ? err.message : String(err)}`);
53
+ }
54
+ finally {
55
+ try {
56
+ rmSync(tmp, { recursive: true, force: true });
57
+ }
58
+ catch {
59
+ /* advisory */
60
+ }
61
+ }
62
+ }
63
+ /**
64
+ * Stage everything and create a single commit with the given author identity. Returns
65
+ * the new commit SHA. `--no-verify` skips any inherited hooks (the fix is machine-made
66
+ * and pre-reviewed by the user). Uses `-c user.*` so we never touch global git config.
67
+ */
68
+ export async function commitAll(worktree, opts) {
69
+ await git(['add', '-A'], worktree);
70
+ await git([
71
+ '-c',
72
+ `user.name=${opts.authorName}`,
73
+ '-c',
74
+ `user.email=${opts.authorEmail}`,
75
+ 'commit',
76
+ '--no-verify',
77
+ '-m',
78
+ opts.message,
79
+ ], worktree);
80
+ const { stdout } = await git(['rev-parse', 'HEAD'], worktree);
81
+ return stdout.trim();
82
+ }
83
+ // ── The push guard ──────────────────────────────────────────────────────────────────────
84
+ // Everything that writes a ref on GitHub goes through pushRef / pushForceWithLease, and both
85
+ // go through assertPushTarget FIRST. The branch name reaching them is not always ours: it can
86
+ // be a PR's own head ref, and in the resolver it can be a name the user typed.
87
+ //
88
+ // ⚠ THE AUTHORITY IS `git check-ref-format`, NOT A REGEX. git-ops.ts's
89
+ // `/^[A-Za-z0-9][A-Za-z0-9._/-]*$/` is a stricter NAMING CONVENTION on branches the advisor
90
+ // INVENTS; it is not a general push guard and must not be hoisted here. `feature/#123`,
91
+ // `user's-branch`, `a+b` and `ünicode/x` are all valid GitHub head refs that regex rejects,
92
+ // and pushRef is already called with real head refs.
93
+ /**
94
+ * Names git's own format check accepts but that must never be a push target: they are
95
+ * pseudo-refs or a leading-dash argument git would read as an option. Compared
96
+ * case-INSENSITIVELY — on a case-folding filesystem `head` and `HEAD` are the same file.
97
+ */
98
+ const RESERVED_REF_NAMES = new Set([
99
+ 'HEAD',
100
+ 'FETCH_HEAD',
101
+ 'ORIG_HEAD',
102
+ 'MERGE_HEAD',
103
+ 'CHERRY_PICK_HEAD',
104
+ 'REVERT_HEAD',
105
+ 'BISECT_HEAD',
106
+ 'AUTO_MERGE',
107
+ ]);
108
+ /**
109
+ * Refuse a push target before any process is spawned: a malformed ref name, a reserved
110
+ * pseudo-ref, or a protected branch. Throws a coded 'PUSH_DENIED'.
111
+ * ⚠ Runs BEFORE the push, deliberately — a refusal that arrives as "git push failed" reads
112
+ * as a network or permission problem and sends the reader looking in the wrong place.
113
+ */
114
+ export async function assertPushTarget(remoteBranch, protect) {
115
+ const deny = (why) => {
116
+ throw codedError('PUSH_DENIED', `refusing to push to '${remoteBranch}': ${why}`);
117
+ };
118
+ if (!remoteBranch)
119
+ deny('the branch name is empty');
120
+ // check-ref-format accepts a leading `-`, which git would then read as an option, and it
121
+ // has nothing to say about the double / trailing slashes some git versions tolerate.
122
+ if (remoteBranch.startsWith('-'))
123
+ deny('a branch name may not start with a dash');
124
+ if (remoteBranch.startsWith('/') || remoteBranch.endsWith('/')) {
125
+ deny('a branch name may not start or end with a slash');
126
+ }
127
+ if (remoteBranch.includes('//'))
128
+ deny('a branch name may not contain an empty component');
129
+ if (RESERVED_REF_NAMES.has(remoteBranch.toUpperCase())) {
130
+ deny(`${remoteBranch} is one of git's own ref names`);
131
+ }
132
+ try {
133
+ await execFileAsync('git', ['check-ref-format', `refs/heads/${remoteBranch}`], {
134
+ timeout: 10_000,
135
+ maxBuffer: 1024 * 1024,
136
+ });
137
+ }
138
+ catch {
139
+ deny('git does not accept it as a branch name');
140
+ }
141
+ const lowered = remoteBranch.toLowerCase();
142
+ if (protect.some((p) => p.toLowerCase() === lowered)) {
143
+ deny('it is a protected branch for this pull request');
144
+ }
145
+ }
146
+ /** The tokenized push URL. Built per op and never persisted (clone-manager owns the same rule). */
147
+ function pushUrl(owner, name, token) {
148
+ return `https://x-access-token:${token}@github.com/${owner}/${name}.git`;
149
+ }
150
+ /**
151
+ * Push a local committish to a remote branch via an explicit tokenized URL (never the
152
+ * clone's persisted remote). Throws a coded 'PUSH_DENIED' error on failure (no write
153
+ * access, protected branch, non-fast-forward on an existing branch).
154
+ */
155
+ export async function pushRef(t) {
156
+ await assertPushTarget(t.remoteBranch, t.protect);
157
+ try {
158
+ await git(['push', pushUrl(t.owner, t.name, t.token), `${t.committish}:refs/heads/${t.remoteBranch}`], t.worktree);
159
+ }
160
+ catch (err) {
161
+ throw codedError('PUSH_DENIED', `git push failed (write access / protected branch?): ${err instanceof Error ? err.message : String(err)}`);
162
+ }
163
+ }
164
+ /**
165
+ * Force-push with a lease: the push aborts unless the remote branch is still at `leaseSha`,
166
+ * so a concurrent push is never blown away.
167
+ * ⚠ This is the DANGEROUS half and it lived in merge.ts, below every guard. It is here so
168
+ * that "a force push goes through assertPushTarget" is true by construction.
169
+ */
170
+ export async function pushForceWithLease(t) {
171
+ await assertPushTarget(t.remoteBranch, t.protect);
172
+ try {
173
+ await git([
174
+ 'push',
175
+ `--force-with-lease=refs/heads/${t.remoteBranch}:${t.leaseSha}`,
176
+ pushUrl(t.owner, t.name, t.token),
177
+ `${t.committish}:refs/heads/${t.remoteBranch}`,
178
+ ], t.worktree);
179
+ }
180
+ catch (err) {
181
+ throw codedError('PUSH_DENIED', `force-with-lease push failed (branch moved since / protected / no write): ${err instanceof Error ? err.message : String(err)}`);
182
+ }
183
+ }
184
+ //# sourceMappingURL=git.js.map