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.
- package/LICENSE +110 -0
- package/README.md +133 -0
- package/dist/ai/runtime.js +535 -0
- package/dist/api/plugins/auth.js +169 -0
- package/dist/api/plugins/error-handler.js +114 -0
- package/dist/api/plugins/rate-limit.js +855 -0
- package/dist/api/plugins/security.js +321 -0
- package/dist/api/routes/activity.js +103 -0
- package/dist/api/routes/ai-runtime.js +67 -0
- package/dist/api/routes/auth.js +194 -0
- package/dist/api/routes/billing.js +209 -0
- package/dist/api/routes/bot-triage.js +1289 -0
- package/dist/api/routes/conflicts.js +730 -0
- package/dist/api/routes/contact.js +318 -0
- package/dist/api/routes/daily-brief.js +58 -0
- package/dist/api/routes/flow.js +71 -0
- package/dist/api/routes/health.js +7 -0
- package/dist/api/routes/insights.js +200 -0
- package/dist/api/routes/me.js +458 -0
- package/dist/api/routes/mentions.js +22 -0
- package/dist/api/routes/mergers.js +8 -0
- package/dist/api/routes/ml-labels.js +109 -0
- package/dist/api/routes/open-prs.js +35 -0
- package/dist/api/routes/prs.js +1633 -0
- package/dist/api/routes/reactions.js +217 -0
- package/dist/api/routes/repos.js +563 -0
- package/dist/api/routes/search.js +41 -0
- package/dist/api/routes/threads.js +118 -0
- package/dist/api/routes/timeline.js +150 -0
- package/dist/api/routes/users.js +67 -0
- package/dist/api/routes/webhooks.js +181 -0
- package/dist/api/routes/workspaces.js +322 -0
- package/dist/app.js +274 -0
- package/dist/ascii.js +19 -0
- package/dist/auth/account.js +482 -0
- package/dist/auth/crypto.js +46 -0
- package/dist/bot-triage/resolve.js +59 -0
- package/dist/cli.js +388 -0
- package/dist/coding/activity.js +80 -0
- package/dist/coding/agent.js +288 -0
- package/dist/coding/ai-fix/auto-fix.js +212 -0
- package/dist/coding/ai-fix/manager.js +370 -0
- package/dist/coding/ai-fix/persist.js +213 -0
- package/dist/coding/ai-fix/pr-context.js +58 -0
- package/dist/coding/ai-fix/prompts.js +138 -0
- package/dist/coding/ai-fix/review-seed.js +357 -0
- package/dist/coding/ai-fix/routes.js +425 -0
- package/dist/coding/git-ops.js +321 -0
- package/dist/coding/git.js +184 -0
- package/dist/coding/merge.js +262 -0
- package/dist/coding/safe-path.js +75 -0
- package/dist/coding/schema.js +43 -0
- package/dist/config.js +648 -0
- package/dist/conflict/diff.js +304 -0
- package/dist/conflict/git.js +118 -0
- package/dist/conflict/hash.js +73 -0
- package/dist/conflict/janitor.js +218 -0
- package/dist/conflict/land.js +675 -0
- package/dist/conflict/model-types.js +2 -0
- package/dist/conflict/model.js +1081 -0
- package/dist/conflict/parse.js +142 -0
- package/dist/conflict/seam.js +136 -0
- package/dist/conflict/session.js +532 -0
- package/dist/conflict/suggestion.js +398 -0
- package/dist/conflict/tokens.js +72 -0
- package/dist/data-dir.js +112 -0
- package/dist/db/actor-lanes.js +213 -0
- package/dist/db/automation-ids.js +97 -0
- package/dist/db/automation-output.js +349 -0
- package/dist/db/blast-radius-query.js +36 -0
- package/dist/db/blast-radius.js +226 -0
- package/dist/db/bot-dormancy.js +115 -0
- package/dist/db/bot-overlap.js +579 -0
- package/dist/db/bot-volume.js +722 -0
- package/dist/db/bot-window.js +35 -0
- package/dist/db/branch-queries.js +310 -0
- package/dist/db/change-shape.js +243 -0
- package/dist/db/changepoint.js +75 -0
- package/dist/db/cleanup.js +36 -0
- package/dist/db/client.js +102 -0
- package/dist/db/code-loc.js +388 -0
- package/dist/db/credits.js +79 -0
- package/dist/db/daily-brief.js +406 -0
- package/dist/db/dependency-cards.js +145 -0
- package/dist/db/erase-account.js +255 -0
- package/dist/db/export-account.js +189 -0
- package/dist/db/failing-checks.js +124 -0
- package/dist/db/file-coupling.js +197 -0
- package/dist/db/flow-detail.js +661 -0
- package/dist/db/flow-pointers.js +197 -0
- package/dist/db/flow-settings.js +67 -0
- package/dist/db/forecast.js +114 -0
- package/dist/db/line-overlap.js +81 -0
- package/dist/db/merge-queue.js +189 -0
- package/dist/db/migrate.js +17 -0
- package/dist/db/migrations/0000_purple_ben_grimm.sql +155 -0
- package/dist/db/migrations/0001_colorful_ozymandias.sql +31 -0
- package/dist/db/migrations/0002_famous_sersi.sql +9 -0
- package/dist/db/migrations/0003_clever_shinobi_shaw.sql +3 -0
- package/dist/db/migrations/0004_pale_scalphunter.sql +1 -0
- package/dist/db/migrations/0005_daffy_guardian.sql +2 -0
- package/dist/db/migrations/0006_cute_violations.sql +45 -0
- package/dist/db/migrations/0007_dark_anthem.sql +2 -0
- package/dist/db/migrations/0008_multitenant_accounts.sql +44 -0
- package/dist/db/migrations/0009_lean_bodies.sql +13 -0
- package/dist/db/migrations/0010_lean_bodies_nullable.sql +56 -0
- package/dist/db/migrations/0011_pr_diff_size.sql +10 -0
- package/dist/db/migrations/0012_findings_included_default.sql +7 -0
- package/dist/db/migrations/0013_review_routing.sql +9 -0
- package/dist/db/migrations/0014_account_last_active.sql +6 -0
- package/dist/db/migrations/0015_repos_inbox_watch.sql +9 -0
- package/dist/db/migrations/0016_repo_viewer_permission.sql +7 -0
- package/dist/db/migrations/0017_account_display_name.sql +7 -0
- package/dist/db/migrations/0018_finding_comment_kind.sql +7 -0
- package/dist/db/migrations/0019_finding_file_in_diff.sql +7 -0
- package/dist/db/migrations/0020_claude_review_cost_telemetry.sql +11 -0
- package/dist/db/migrations/0021_account_feed_last_seen.sql +7 -0
- package/dist/db/migrations/0022_ci_status_events.sql +21 -0
- package/dist/db/migrations/0023_ai_usage.sql +21 -0
- package/dist/db/migrations/0024_pr_head_ref_name.sql +6 -0
- package/dist/db/migrations/0025_account_billing.sql +7 -0
- package/dist/db/migrations/0026_ai_credit_allowance.sql +5 -0
- package/dist/db/migrations/0027_user_github_type.sql +6 -0
- package/dist/db/migrations/0028_bot_review_classification.sql +21 -0
- package/dist/db/migrations/0029_bot_mute_rules.sql +19 -0
- package/dist/db/migrations/0030_teams.sql +32 -0
- package/dist/db/migrations/0031_events_pr_idx.sql +7 -0
- package/dist/db/migrations/0032_benchmark_contributions.sql +29 -0
- package/dist/db/migrations/0033_thread_addressed_confidence.sql +9 -0
- package/dist/db/migrations/0034_thread_resolved_at.sql +6 -0
- package/dist/db/migrations/0035_pr_first_review_requested_at.sql +5 -0
- package/dist/db/migrations/0036_search_index.sql +25 -0
- package/dist/db/migrations/0037_author_indexes.sql +15 -0
- package/dist/db/migrations/0038_auto_merge_requests.sql +37 -0
- package/dist/db/migrations/0039_branch_status.sql +44 -0
- package/dist/db/migrations/0040_pr_review_decision.sql +10 -0
- package/dist/db/migrations/0041_branch_commit_detail.sql +38 -0
- package/dist/db/migrations/0042_repo_reviewers.sql +176 -0
- package/dist/db/migrations/0043_account_reviewers.sql +131 -0
- package/dist/db/migrations/0044_workspaces.sql +138 -0
- package/dist/db/migrations/0045_workspace_reviewers.sql +180 -0
- package/dist/db/migrations/0046_drop_inbox_watch.sql +25 -0
- package/dist/db/migrations/0047_ml_comment_labels.sql +40 -0
- package/dist/db/migrations/0048_ml_vendor_severity.sql +31 -0
- package/dist/db/migrations/0049_seat_pricing_repo_description.sql +21 -0
- package/dist/db/migrations/0050_users_app_slug.sql +12 -0
- package/dist/db/migrations/0051_auto_merge_queue.sql +17 -0
- package/dist/db/migrations/0052_trunk_ci_status_events.sql +43 -0
- package/dist/db/migrations/0053_automation_roles.sql +79 -0
- package/dist/db/migrations/0054_automation_vendor_kinds.sql +514 -0
- package/dist/db/migrations/0055_auto_merge_phase.sql +12 -0
- package/dist/db/migrations/0056_pr_mentions.sql +22 -0
- package/dist/db/migrations/0057_large_pr_threshold.sql +14 -0
- package/dist/db/migrations/0058_pending_mute.sql +53 -0
- package/dist/db/migrations/0059_merge_queue_state.sql +33 -0
- package/dist/db/migrations/0060_drop_my_turn_dismissals.sql +33 -0
- package/dist/db/migrations/0061_auto_merge_durable_anchors.sql +27 -0
- package/dist/db/migrations/0062_blast_radius_config.sql +21 -0
- package/dist/db/migrations/0063_repo_file_coupling.sql +33 -0
- package/dist/db/migrations/0064_pr_content_kind.sql +21 -0
- package/dist/db/migrations/0065_workspace_flow_settings.sql +13 -0
- package/dist/db/migrations/0066_review_request_events.sql +31 -0
- package/dist/db/migrations/0067_pr_security_signals.sql +86 -0
- package/dist/db/migrations/0068_my_turn_settings.sql +18 -0
- package/dist/db/migrations/0069_my_turn_dismissals.sql +41 -0
- package/dist/db/migrations/0070_claude_review_follow_up_ticket.sql +22 -0
- package/dist/db/migrations/0071_claude_review_trigger.sql +11 -0
- package/dist/db/migrations/0072_commit_message_headline.sql +13 -0
- package/dist/db/migrations/0073_claude_review_chat.sql +31 -0
- package/dist/db/migrations/0074_adopt_agentic_tables.sql +98 -0
- package/dist/db/migrations/0075_review_lens_drop_learnings.sql +15 -0
- package/dist/db/migrations/0076_claude_review_threads.sql +17 -0
- package/dist/db/migrations/0077_claude_review_ci_failures.sql +10 -0
- package/dist/db/migrations/0078_ai_fix_change_report.sql +17 -0
- package/dist/db/migrations/0079_claude_finding_story.sql +16 -0
- package/dist/db/migrations/meta/0000_snapshot.json +1116 -0
- package/dist/db/migrations/meta/0001_snapshot.json +1321 -0
- package/dist/db/migrations/meta/0002_snapshot.json +1375 -0
- package/dist/db/migrations/meta/0003_snapshot.json +1396 -0
- package/dist/db/migrations/meta/0004_snapshot.json +1416 -0
- package/dist/db/migrations/meta/0005_snapshot.json +1430 -0
- package/dist/db/migrations/meta/0006_snapshot.json +1745 -0
- package/dist/db/migrations/meta/0007_snapshot.json +1759 -0
- package/dist/db/migrations/meta/_journal.json +566 -0
- package/dist/db/migrations-pg/0000_tired_alex_power.sql +268 -0
- package/dist/db/migrations-pg/0001_dizzy_mockingbird.sql +3 -0
- package/dist/db/migrations-pg/0002_glorious_ikaris.sql +4 -0
- package/dist/db/migrations-pg/0003_melted_secret_warriors.sql +2 -0
- package/dist/db/migrations-pg/0004_numerous_triton.sql +1 -0
- package/dist/db/migrations-pg/0005_thick_blockbuster.sql +2 -0
- package/dist/db/migrations-pg/0006_majestic_dracula.sql +1 -0
- package/dist/db/migrations-pg/0007_same_human_torch.sql +2 -0
- package/dist/db/migrations-pg/0008_slimy_toxin.sql +1 -0
- package/dist/db/migrations-pg/0009_fuzzy_jubilee.sql +4 -0
- package/dist/db/migrations-pg/0010_late_hammerhead.sql +1 -0
- package/dist/db/migrations-pg/0011_happy_leech.sql +16 -0
- package/dist/db/migrations-pg/0012_woozy_captain_midlands.sql +16 -0
- package/dist/db/migrations-pg/0013_yielding_nicolaos.sql +1 -0
- package/dist/db/migrations-pg/0014_wakeful_bill_hollister.sql +2 -0
- package/dist/db/migrations-pg/0015_aspiring_black_tom.sql +1 -0
- package/dist/db/migrations-pg/0016_cute_ghost_rider.sql +30 -0
- package/dist/db/migrations-pg/0017_far_changeling.sql +24 -0
- package/dist/db/migrations-pg/0018_fancy_miss_america.sql +1 -0
- package/dist/db/migrations-pg/0019_benchmark_contributions.sql +22 -0
- package/dist/db/migrations-pg/0020_majestic_edwin_jarvis.sql +3 -0
- package/dist/db/migrations-pg/0021_green_salo.sql +1 -0
- package/dist/db/migrations-pg/0022_wise_baron_strucker.sql +1 -0
- package/dist/db/migrations-pg/0023_search_index.sql +19 -0
- package/dist/db/migrations-pg/0024_author_indexes.sql +9 -0
- package/dist/db/migrations-pg/0025_auto_merge_requests.sql +26 -0
- package/dist/db/migrations-pg/0026_branch_status.sql +32 -0
- package/dist/db/migrations-pg/0027_pr_review_decision.sql +5 -0
- package/dist/db/migrations-pg/0028_branch_commit_detail.sql +9 -0
- package/dist/db/migrations-pg/0029_repo_reviewers.sql +127 -0
- package/dist/db/migrations-pg/0030_account_reviewers.sql +77 -0
- package/dist/db/migrations-pg/0031_workspaces.sql +131 -0
- package/dist/db/migrations-pg/0032_workspace_reviewers.sql +165 -0
- package/dist/db/migrations-pg/0033_drop_inbox_watch.sql +14 -0
- package/dist/db/migrations-pg/0034_ml_comment_labels.sql +33 -0
- package/dist/db/migrations-pg/0035_ml_vendor_severity.sql +16 -0
- package/dist/db/migrations-pg/0036_seat_pricing_repo_description.sql +12 -0
- package/dist/db/migrations-pg/0037_users_app_slug.sql +9 -0
- package/dist/db/migrations-pg/0038_auto_merge_queue.sql +10 -0
- package/dist/db/migrations-pg/0039_trunk_ci_status_events.sql +27 -0
- package/dist/db/migrations-pg/0040_automation_roles.sql +63 -0
- package/dist/db/migrations-pg/0041_automation_vendor_kinds.sql +488 -0
- package/dist/db/migrations-pg/0042_auto_merge_phase.sql +8 -0
- package/dist/db/migrations-pg/0043_pr_mentions.sql +19 -0
- package/dist/db/migrations-pg/0044_large_pr_threshold.sql +6 -0
- package/dist/db/migrations-pg/0045_pending_mute.sql +26 -0
- package/dist/db/migrations-pg/0046_merge_queue_state.sql +18 -0
- package/dist/db/migrations-pg/0047_drop_my_turn_dismissals.sql +21 -0
- package/dist/db/migrations-pg/0048_auto_merge_durable_anchors.sql +27 -0
- package/dist/db/migrations-pg/0049_blast_radius_config.sql +8 -0
- package/dist/db/migrations-pg/0050_repo_file_coupling.sql +13 -0
- package/dist/db/migrations-pg/0051_pr_content_kind.sql +6 -0
- package/dist/db/migrations-pg/0052_workspace_flow_settings.sql +4 -0
- package/dist/db/migrations-pg/0053_review_request_events.sql +18 -0
- package/dist/db/migrations-pg/0054_pr_security_signals.sql +59 -0
- package/dist/db/migrations-pg/0055_my_turn_settings.sql +6 -0
- package/dist/db/migrations-pg/0056_my_turn_dismissals.sql +21 -0
- package/dist/db/migrations-pg/0057_claude_review_follow_up_ticket.sql +8 -0
- package/dist/db/migrations-pg/0058_claude_review_trigger.sql +4 -0
- package/dist/db/migrations-pg/0059_commit_message_headline.sql +3 -0
- package/dist/db/migrations-pg/0060_claude_review_chat.sql +24 -0
- package/dist/db/migrations-pg/0061_adopt_agentic_tables.sql +79 -0
- package/dist/db/migrations-pg/0062_review_lens_drop_learnings.sql +5 -0
- package/dist/db/migrations-pg/0063_claude_review_threads.sql +7 -0
- package/dist/db/migrations-pg/0064_claude_review_ci_failures.sql +4 -0
- package/dist/db/migrations-pg/0065_ai_fix_change_report.sql +9 -0
- package/dist/db/migrations-pg/0066_claude_finding_story.sql +7 -0
- package/dist/db/migrations-pg/meta/0000_snapshot.json +2137 -0
- package/dist/db/migrations-pg/meta/0001_snapshot.json +2143 -0
- package/dist/db/migrations-pg/meta/0002_snapshot.json +2170 -0
- package/dist/db/migrations-pg/meta/0003_snapshot.json +2182 -0
- package/dist/db/migrations-pg/meta/0004_snapshot.json +2188 -0
- package/dist/db/migrations-pg/meta/0005_snapshot.json +2201 -0
- package/dist/db/migrations-pg/meta/0006_snapshot.json +2207 -0
- package/dist/db/migrations-pg/meta/0007_snapshot.json +2219 -0
- package/dist/db/migrations-pg/meta/0008_snapshot.json +2226 -0
- package/dist/db/migrations-pg/meta/0009_snapshot.json +2250 -0
- package/dist/db/migrations-pg/meta/0010_snapshot.json +2256 -0
- package/dist/db/migrations-pg/meta/0011_snapshot.json +2412 -0
- package/dist/db/migrations-pg/meta/0012_snapshot.json +2528 -0
- package/dist/db/migrations-pg/meta/0013_snapshot.json +2534 -0
- package/dist/db/migrations-pg/meta/0014_snapshot.json +2547 -0
- package/dist/db/migrations-pg/meta/0015_snapshot.json +2553 -0
- package/dist/db/migrations-pg/meta/0016_snapshot.json +2774 -0
- package/dist/db/migrations-pg/meta/0017_snapshot.json +2999 -0
- package/dist/db/migrations-pg/meta/0018_snapshot.json +3020 -0
- package/dist/db/migrations-pg/meta/0020_snapshot.json +3228 -0
- package/dist/db/migrations-pg/meta/0021_snapshot.json +3234 -0
- package/dist/db/migrations-pg/meta/0022_snapshot.json +3240 -0
- package/dist/db/migrations-pg/meta/_journal.json +475 -0
- package/dist/db/ml-labels.js +1666 -0
- package/dist/db/my-turn-dismissals.js +343 -0
- package/dist/db/my-turn-settings.js +34 -0
- package/dist/db/pending-classify.js +46 -0
- package/dist/db/pending-mute.js +171 -0
- package/dist/db/pending-tabs.js +238 -0
- package/dist/db/period-metrics.js +820 -0
- package/dist/db/person-period.js +763 -0
- package/dist/db/pr-intervals.js +930 -0
- package/dist/db/pr-liveness.js +240 -0
- package/dist/db/pr-mentions.js +279 -0
- package/dist/db/pr-merge-queue-stamp.js +91 -0
- package/dist/db/queries.js +14631 -0
- package/dist/db/reaction-targets.js +94 -0
- package/dist/db/repo-activity.js +171 -0
- package/dist/db/retention.js +138 -0
- package/dist/db/review-threads-for-review.js +240 -0
- package/dist/db/run-migrations.js +24 -0
- package/dist/db/schema.pg.js +1220 -0
- package/dist/db/schema.sqlite.js +1801 -0
- package/dist/db/search.js +317 -0
- package/dist/db/security-alerts.js +218 -0
- package/dist/db/synthesis-input.js +565 -0
- package/dist/db/triage.js +320 -0
- package/dist/db/usage.js +63 -0
- package/dist/db/work-plan.js +1086 -0
- package/dist/db/working-hours.js +161 -0
- package/dist/feed/my-turn.js +122 -0
- package/dist/github/actions-logs.js +397 -0
- package/dist/github/auth.js +86 -0
- package/dist/github/branch-queries.js +229 -0
- package/dist/github/client.js +454 -0
- package/dist/github/codeowners.js +221 -0
- package/dist/github/commit-checks.js +105 -0
- package/dist/github/compare.js +110 -0
- package/dist/github/diff-anchor.js +227 -0
- package/dist/github/mutations.js +706 -0
- package/dist/github/pr-file-content.js +254 -0
- package/dist/github/pr-liveness.js +161 -0
- package/dist/github/queries.js +875 -0
- package/dist/github/rate-budget.js +129 -0
- package/dist/github/reactions.js +161 -0
- package/dist/github/reviewer-suggest.js +104 -0
- package/dist/github/team-reviewers.js +96 -0
- package/dist/index.js +184 -0
- package/dist/merge/auto-merge-runner.js +960 -0
- package/dist/ml/severity-client.js +218 -0
- package/dist/pr/detail-enricher.js +26 -0
- package/dist/pro/bind.js +302 -0
- package/dist/pro/contract.js +37 -0
- package/dist/pro/migrate.js +64 -0
- package/dist/review/agent-context.js +127 -0
- package/dist/review/agent.js +385 -0
- package/dist/review/agentic.js +59 -0
- package/dist/review/auth.js +94 -0
- package/dist/review/chat-agent.js +179 -0
- package/dist/review/claude-review/ai-ready.js +16 -0
- package/dist/review/claude-review/auto-settings.js +139 -0
- package/dist/review/claude-review/auto.js +244 -0
- package/dist/review/claude-review/chat.js +489 -0
- package/dist/review/claude-review/ci-failures.js +350 -0
- package/dist/review/claude-review/follow-up.js +310 -0
- package/dist/review/claude-review/manager.js +686 -0
- package/dist/review/claude-review/persist.js +996 -0
- package/dist/review/claude-review/prompts.js +357 -0
- package/dist/review/claude-review/routes.js +518 -0
- package/dist/review/claude-review/routing.js +61 -0
- package/dist/review/claude-review/specialists.js +190 -0
- package/dist/review/claude-review/threads.js +265 -0
- package/dist/review/claude-review/ticket.js +282 -0
- package/dist/review/clone-hygiene.js +380 -0
- package/dist/review/clone-manager.js +367 -0
- package/dist/review/llm.js +113 -0
- package/dist/review/model-options.js +48 -0
- package/dist/review/plugin-providers.js +12 -0
- package/dist/review/post-review.js +216 -0
- package/dist/review/post-seam.js +161 -0
- package/dist/review/prepare.js +168 -0
- package/dist/review/pricing.js +17 -0
- package/dist/review/schema.js +156 -0
- package/dist/review/submit-map.js +41 -0
- package/dist/review/usage.js +90 -0
- package/dist/shared/claude-review.js +335 -0
- package/dist/shared/conflict-fold.js +151 -0
- package/dist/shared/conflicts.js +30 -0
- package/dist/shared/flow-settings.js +191 -0
- package/dist/shared/index.js +8 -0
- package/dist/shared/my-turn-settings.js +238 -0
- package/dist/shared/pending-rules.js +281 -0
- package/dist/shared/product.js +54 -0
- package/dist/shared/types.js +896 -0
- package/dist/status.js +386 -0
- package/dist/sync/adaptive.js +185 -0
- package/dist/sync/app-attribution.js +66 -0
- package/dist/sync/auth-notices.js +36 -0
- package/dist/sync/backfill-ci-history.js +315 -0
- package/dist/sync/backfill-commit-headlines.js +136 -0
- package/dist/sync/backfill-pr-security.js +175 -0
- package/dist/sync/backfill-review-requests.js +97 -0
- package/dist/sync/benchmark-rollup.js +50 -0
- package/dist/sync/bot-detection.js +309 -0
- package/dist/sync/bot-resolution-markers.js +30 -0
- package/dist/sync/branch-status.js +634 -0
- package/dist/sync/classify-change-shape.js +150 -0
- package/dist/sync/commit-files.js +74 -0
- package/dist/sync/derive-thread-state.js +99 -0
- package/dist/sync/drain-review-threads.js +212 -0
- package/dist/sync/hydrate-detail.js +482 -0
- package/dist/sync/mention-scan.js +89 -0
- package/dist/sync/ml-enrichment.js +478 -0
- package/dist/sync/pr-change-signal.js +96 -0
- package/dist/sync/pr-liveness-sweep.js +139 -0
- package/dist/sync/pr-settle.js +381 -0
- package/dist/sync/refresh-pr.js +163 -0
- package/dist/sync/reparse-vendor-badges.js +495 -0
- package/dist/sync/resync-after-write.js +280 -0
- package/dist/sync/review-fingerprint.js +117 -0
- package/dist/sync/reviewer-behavior.js +103 -0
- package/dist/sync/reviewer-classify.js +503 -0
- package/dist/sync/routing-files.js +97 -0
- package/dist/sync/scheduled-jobs.js +12 -0
- package/dist/sync/scheduler.js +159 -0
- package/dist/sync/security-detect.js +521 -0
- package/dist/sync/sync-manager.js +750 -0
- package/dist/sync/sync-one-pr.js +205 -0
- package/dist/sync/sync-repo.js +400 -0
- package/dist/sync/unsettled-prs.js +506 -0
- package/dist/sync/upsert.js +1190 -0
- package/package.json +56 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/assets/AiFixTab-CQP6PT5u.js +2 -0
- package/public/assets/ClaudeReviewTab-DQJ-58ZA.js +8 -0
- package/public/assets/PrBotBehaviourTab-CCnFrWbC.js +1 -0
- package/public/assets/archivo-latin-600-BN-JkNrl.woff2 +0 -0
- package/public/assets/highlight-BEHUn5zE.css +10 -0
- package/public/assets/highlight-CIIZLa6Z.js +12 -0
- package/public/assets/index-BDnYs6zg.js +73 -0
- package/public/assets/index-mCuI--RC.css +1 -0
- package/public/assets/markdown-I_JLLVAZ.js +32 -0
- package/public/assets/perfProbe-Da13EhTE.js +1 -0
- package/public/assets/tanstack-5qsxxaLe.js +17 -0
- package/public/assets/vis-C3BCkP5U.js +1280 -0
- package/public/assets/vis-ziCVehMy.css +1 -0
- package/public/favicon-16.png +0 -0
- package/public/favicon-32.png +0 -0
- package/public/favicon-48.png +0 -0
- package/public/favicon.svg +5 -0
- package/public/index.html +41 -0
- package/public-landing/apple-touch-icon.png +0 -0
- package/public-landing/assets/archivo-latin-500-BWmvx2Mg.woff2 +0 -0
- package/public-landing/assets/archivo-latin-600-BN-JkNrl.woff2 +0 -0
- package/public-landing/assets/index-CA3eEGXO.js +40 -0
- package/public-landing/assets/index-naQrQ6Fe.css +1 -0
- package/public-landing/assets/jetbrains-mono-latin-400-RruCX7RW.woff2 +0 -0
- package/public-landing/assets/newsreader-latin-400-DExrRRrN.woff2 +0 -0
- package/public-landing/assets/newsreader-latin-400-italic-b0I-AwbV.woff2 +0 -0
- package/public-landing/bots/index.html +156 -0
- package/public-landing/contact/index.html +156 -0
- package/public-landing/cookies/index.html +156 -0
- package/public-landing/demo/limn-walkthrough-poster.jpg +0 -0
- package/public-landing/demo/limn-walkthrough.cues.json +44 -0
- package/public-landing/demo/limn-walkthrough.mp4 +0 -0
- package/public-landing/favicon-16.png +0 -0
- package/public-landing/favicon-32.png +0 -0
- package/public-landing/favicon-48.png +0 -0
- package/public-landing/favicon.svg +5 -0
- package/public-landing/features/index.html +156 -0
- package/public-landing/for-developers/index.html +156 -0
- package/public-landing/for-managers/index.html +156 -0
- package/public-landing/how-it-works/index.html +156 -0
- package/public-landing/how-we-measure/index.html +156 -0
- package/public-landing/icon-192.png +0 -0
- package/public-landing/icon-512.png +0 -0
- package/public-landing/index.html +156 -0
- package/public-landing/insights/index.html +156 -0
- package/public-landing/og-image.png +0 -0
- package/public-landing/pricing/index.html +156 -0
- package/public-landing/privacy/index.html +156 -0
- package/public-landing/pro/index.html +156 -0
- package/public-landing/reviews/index.html +156 -0
- package/public-landing/robots.txt +8 -0
- package/public-landing/shots/benchmark.png +0 -0
- package/public-landing/shots/bot-roi.png +0 -0
- package/public-landing/shots/bot-settings.png +0 -0
- package/public-landing/shots/chronology.png +0 -0
- package/public-landing/shots/feed.png +0 -0
- package/public-landing/shots/flow-metrics.png +0 -0
- package/public-landing/shots/free-reports.png +0 -0
- package/public-landing/shots/pending-board.png +0 -0
- package/public-landing/shots/pending-card.png +0 -0
- package/public-landing/shots/period-report.png +0 -0
- package/public-landing/shots/pr-detail.png +0 -0
- package/public-landing/shots/pr-threads.png +0 -0
- package/public-landing/shots/reach.png +0 -0
- package/public-landing/shots/repo-rows.png +0 -0
- package/public-landing/shots/severity-findings.png +0 -0
- package/public-landing/shots/severity-strip.png +0 -0
- package/public-landing/site.webmanifest +14 -0
- package/public-landing/sitemap.xml +51 -0
- 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
|