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,706 @@
|
|
|
1
|
+
// Per-account GitHub WRITE actions — the data-plane for the PR write features
|
|
2
|
+
// (reply / resolve / comment / approve / inline comment / fetch files). Every
|
|
3
|
+
// function takes the owning account's token as its FIRST argument and goes
|
|
4
|
+
// through the per-account client factories (getGraphqlClientFor / ghRestGetFor /
|
|
5
|
+
// ghRestPostFor) — NEVER the gh-CLI wrappers, so these work in cloud too.
|
|
6
|
+
//
|
|
7
|
+
// GraphQL note: @octokit/graphql RESERVES the variable name `query`. The
|
|
8
|
+
// operation-variable here is named `input` / etc., never `query`.
|
|
9
|
+
import { getGraphqlClientFor, ghRestGetDiffStatus, ghRestGetFor, ghRestPatchStatus, ghRestPostFor, ghRestPostNoContent, ghRestPutStatus, } from './client.js';
|
|
10
|
+
import { summariseReactionGroups, toGithubReactionContent } from './reactions.js';
|
|
11
|
+
const ADD_REPLY_MUTATION = /* GraphQL */ `
|
|
12
|
+
mutation AddThreadReply($input: AddPullRequestReviewThreadReplyInput!) {
|
|
13
|
+
addPullRequestReviewThreadReply(input: $input) {
|
|
14
|
+
comment {
|
|
15
|
+
id
|
|
16
|
+
databaseId
|
|
17
|
+
body
|
|
18
|
+
createdAt
|
|
19
|
+
url
|
|
20
|
+
author {
|
|
21
|
+
login
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
// Reply to an existing review thread (GraphQL). `threadNodeId` is the thread's
|
|
28
|
+
// GitHub node id (reviewThreads.githubNodeId).
|
|
29
|
+
export async function addReviewThreadReply(token, threadNodeId, body) {
|
|
30
|
+
const gql = getGraphqlClientFor(token);
|
|
31
|
+
const res = await gql(ADD_REPLY_MUTATION, {
|
|
32
|
+
input: { pullRequestReviewThreadId: threadNodeId, body },
|
|
33
|
+
});
|
|
34
|
+
const c = res.addPullRequestReviewThreadReply.comment;
|
|
35
|
+
if (!c) {
|
|
36
|
+
throw new Error('GitHub returned no comment for the thread reply (the reply may still have posted)');
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
nodeId: c.id,
|
|
40
|
+
databaseId: c.databaseId,
|
|
41
|
+
body: c.body,
|
|
42
|
+
createdAt: c.createdAt,
|
|
43
|
+
url: c.url,
|
|
44
|
+
authorLogin: c.author?.login ?? null,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const RESOLVE_MUTATION = /* GraphQL */ `
|
|
48
|
+
mutation ResolveThread($input: ResolveReviewThreadInput!) {
|
|
49
|
+
resolveReviewThread(input: $input) {
|
|
50
|
+
thread {
|
|
51
|
+
isResolved
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
const UNRESOLVE_MUTATION = /* GraphQL */ `
|
|
57
|
+
mutation UnresolveThread($input: UnresolveReviewThreadInput!) {
|
|
58
|
+
unresolveReviewThread(input: $input) {
|
|
59
|
+
thread {
|
|
60
|
+
isResolved
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
// Resolve (resolved=true) or unresolve (resolved=false) a review thread.
|
|
66
|
+
export async function setReviewThreadResolved(token, threadNodeId, resolved) {
|
|
67
|
+
const gql = getGraphqlClientFor(token);
|
|
68
|
+
if (resolved) {
|
|
69
|
+
const res = await gql(RESOLVE_MUTATION, {
|
|
70
|
+
input: { threadId: threadNodeId },
|
|
71
|
+
});
|
|
72
|
+
const t = res.resolveReviewThread.thread;
|
|
73
|
+
if (!t)
|
|
74
|
+
throw new Error('GitHub returned no thread for resolve');
|
|
75
|
+
return { isResolved: t.isResolved };
|
|
76
|
+
}
|
|
77
|
+
const res = await gql(UNRESOLVE_MUTATION, {
|
|
78
|
+
input: { threadId: threadNodeId },
|
|
79
|
+
});
|
|
80
|
+
const t = res.unresolveReviewThread.thread;
|
|
81
|
+
if (!t)
|
|
82
|
+
throw new Error('GitHub returned no thread for unresolve');
|
|
83
|
+
return { isResolved: t.isResolved };
|
|
84
|
+
}
|
|
85
|
+
// Post a new issue-level (general PR) comment. A PR is an issue for the comments
|
|
86
|
+
// endpoint, so `number` is the PR number.
|
|
87
|
+
export async function addIssueComment(token, owner, name, number, body) {
|
|
88
|
+
const res = await ghRestPostFor(token, `/repos/${owner}/${name}/issues/${number}/comments`, { body });
|
|
89
|
+
return {
|
|
90
|
+
nodeId: res.node_id,
|
|
91
|
+
databaseId: res.id,
|
|
92
|
+
body: res.body,
|
|
93
|
+
createdAt: res.created_at,
|
|
94
|
+
url: res.html_url,
|
|
95
|
+
authorLogin: res.user?.login ?? null,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Submit ONE PR review (inline comments + body + verdict) in a single call. The
|
|
99
|
+
// REST reviews endpoint is required for inline line comments. `commitId` pins the
|
|
100
|
+
// review to a head SHA so it 409s/422s if the head moved. `comments` are inline
|
|
101
|
+
// line comments folded into this one review.
|
|
102
|
+
export async function submitPrReview(token, owner, name, number, opts) {
|
|
103
|
+
const payload = { event: opts.event };
|
|
104
|
+
if (opts.body !== undefined)
|
|
105
|
+
payload.body = opts.body;
|
|
106
|
+
if (opts.commitId !== undefined)
|
|
107
|
+
payload.commit_id = opts.commitId;
|
|
108
|
+
if (opts.comments && opts.comments.length > 0) {
|
|
109
|
+
payload.comments = opts.comments.map((c) => ({
|
|
110
|
+
path: c.path,
|
|
111
|
+
line: c.line,
|
|
112
|
+
side: c.side,
|
|
113
|
+
body: c.body,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
const res = await ghRestPostFor(token, `/repos/${owner}/${name}/pulls/${number}/reviews`, payload);
|
|
117
|
+
return {
|
|
118
|
+
databaseId: res.id,
|
|
119
|
+
nodeId: res.node_id,
|
|
120
|
+
state: res.state,
|
|
121
|
+
body: res.body,
|
|
122
|
+
submittedAt: res.submitted_at ?? new Date().toISOString(),
|
|
123
|
+
url: res.html_url,
|
|
124
|
+
authorLogin: res.user?.login ?? null,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// Post a SINGLE standalone inline review comment (not part of a review draft),
|
|
128
|
+
// committed immediately. `commitId` pins it to the head SHA; the line must be
|
|
129
|
+
// addable on that side.
|
|
130
|
+
export async function postInlineComment(token, owner, name, number, opts) {
|
|
131
|
+
const res = await ghRestPostFor(token, `/repos/${owner}/${name}/pulls/${number}/comments`, {
|
|
132
|
+
body: opts.body,
|
|
133
|
+
commit_id: opts.commitId,
|
|
134
|
+
path: opts.path,
|
|
135
|
+
line: opts.line,
|
|
136
|
+
side: opts.side,
|
|
137
|
+
});
|
|
138
|
+
return { databaseId: res.id, nodeId: res.node_id, url: res.html_url };
|
|
139
|
+
}
|
|
140
|
+
// Fetch the PR's per-file diff patches, paginating by hand (per_page=100). Models
|
|
141
|
+
// the hand-paginated loop on sync/commit-files.ts. Stops once a page returns < 100
|
|
142
|
+
// files (definitively the last page) or once we've collected more than `cap`
|
|
143
|
+
// files. `truncated` is true iff there are more files than we return — either
|
|
144
|
+
// because a fetched page pushed us past `cap`, or because the last full page was
|
|
145
|
+
// exactly at `cap` AND a subsequent page returned more (handled naturally: we
|
|
146
|
+
// only break on a short page or on overflow, so a trailing full page is always
|
|
147
|
+
// followed by another fetch).
|
|
148
|
+
export async function fetchPrFilesWithPatch(token, owner, name, number, cap = 100) {
|
|
149
|
+
const files = [];
|
|
150
|
+
let truncated = false;
|
|
151
|
+
let page = 1;
|
|
152
|
+
// GitHub caps per_page at 100 for this endpoint.
|
|
153
|
+
const perPage = 100;
|
|
154
|
+
for (;;) {
|
|
155
|
+
const pageFiles = await ghRestGetFor(token, `/repos/${owner}/${name}/pulls/${number}/files?per_page=${perPage}&page=${page}`);
|
|
156
|
+
files.push(...pageFiles);
|
|
157
|
+
if (files.length > cap) {
|
|
158
|
+
// We have strictly more than the cap → there's at least one extra file.
|
|
159
|
+
truncated = true;
|
|
160
|
+
files.length = cap;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
if (pageFiles.length < perPage) {
|
|
164
|
+
// Short page: this is the last page, nothing beyond it.
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
// A full page came back and we're still at/under the cap. Keep going; the
|
|
168
|
+
// next page's result decides whether more files exist beyond the cap.
|
|
169
|
+
page += 1;
|
|
170
|
+
}
|
|
171
|
+
return { files, truncated };
|
|
172
|
+
}
|
|
173
|
+
// The PR's current head SHA (to pin a posted review/comment to, and to detect a
|
|
174
|
+
// head-moved race).
|
|
175
|
+
export async function fetchHeadShaFor(token, owner, name, number) {
|
|
176
|
+
const pull = await ghRestGetFor(token, `/repos/${owner}/${name}/pulls/${number}`);
|
|
177
|
+
return pull.head.sha;
|
|
178
|
+
}
|
|
179
|
+
// Full head/fork metadata for a PR. `isFork` is true when the head repo differs from
|
|
180
|
+
// the base repo; combined with `maintainer_can_modify` it tells the fixer whether the
|
|
181
|
+
// PR's own head branch can be pushed to (else it must open a new branch in the base
|
|
182
|
+
// repo). head.repo can be null when a fork was deleted — treated as a fork.
|
|
183
|
+
export async function fetchPrHeadInfo(token, owner, name, number) {
|
|
184
|
+
const pull = await ghRestGetFor(token, `/repos/${owner}/${name}/pulls/${number}`);
|
|
185
|
+
const baseRepoFullName = pull.base.repo.full_name;
|
|
186
|
+
const headRepoFullName = pull.head.repo?.full_name ?? baseRepoFullName;
|
|
187
|
+
return {
|
|
188
|
+
headSha: pull.head.sha,
|
|
189
|
+
headRef: pull.head.ref,
|
|
190
|
+
headRepoFullName,
|
|
191
|
+
isFork: pull.head.repo == null || headRepoFullName !== baseRepoFullName,
|
|
192
|
+
maintainerCanModify: pull.maintainer_can_modify,
|
|
193
|
+
baseRef: pull.base.ref,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
// GitHub's OWN mergeability for a PR — a NEAR-INSTANT trunk-conflict signal (one, or
|
|
197
|
+
// two, REST calls), read without cloning by the PR merge / update-branch routes
|
|
198
|
+
// (api/routes/prs.ts). GitHub computes `mergeable` asynchronously and can briefly return
|
|
199
|
+
// null; we retry once. The `compare` call adds behind/ahead counts + the trunk tip.
|
|
200
|
+
export async function fetchMergeability(token, owner, name, number) {
|
|
201
|
+
let pull = await ghRestGetFor(token, `/repos/${owner}/${name}/pulls/${number}`);
|
|
202
|
+
if (pull.mergeable === null) {
|
|
203
|
+
await new Promise((r) => setTimeout(r, 600));
|
|
204
|
+
pull = await ghRestGetFor(token, `/repos/${owner}/${name}/pulls/${number}`);
|
|
205
|
+
}
|
|
206
|
+
const cmp = await compareBaseToHead(token, owner, name, pull);
|
|
207
|
+
return {
|
|
208
|
+
mergeable: pull.mergeable,
|
|
209
|
+
mergeableState: pull.mergeable_state,
|
|
210
|
+
baseRef: pull.base.ref,
|
|
211
|
+
baseSha: cmp.baseSha ?? pull.base.sha,
|
|
212
|
+
behindBy: cmp.behindBy,
|
|
213
|
+
aheadBy: cmp.aheadBy,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
// The `GET /compare/base...head` half of the mergeability read, shared by `fetchMergeability`
|
|
217
|
+
// and `fetchPrMergeSnapshot` so the fork-qualification rule can't drift between them.
|
|
218
|
+
async function compareBaseToHead(token, owner, name, pull) {
|
|
219
|
+
try {
|
|
220
|
+
// For a fork the head must be qualified `owner:branch` (head.label); same-repo uses
|
|
221
|
+
// the plain ref. Branch names with slashes are fine literal in the compare path.
|
|
222
|
+
const headRef = pull.head.repo ? pull.head.label : pull.head.ref;
|
|
223
|
+
const cmp = await ghRestGetFor(token, `/repos/${owner}/${name}/compare/${pull.base.ref}...${headRef}`);
|
|
224
|
+
return {
|
|
225
|
+
behindBy: cmp.behind_by ?? 0,
|
|
226
|
+
aheadBy: cmp.ahead_by ?? 0,
|
|
227
|
+
baseSha: cmp.base_commit?.sha ?? null,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
/* compare is best-effort — the mergeable flag is the load-bearing part */
|
|
232
|
+
return { behindBy: 0, aheadBy: 0, baseSha: null };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Head info AND mergeability from ONE `GET /pulls/{n}`. `fetchPrHeadInfo` and
|
|
236
|
+
// `fetchMergeability` read strictly non-overlapping fields of the SAME payload, so a caller
|
|
237
|
+
// that needs both (the auto-merge watcher, on every intent on every tick) was paying two
|
|
238
|
+
// identical GETs — 750 wasted calls/hour at 25 intents against a 5k/hour budget. Both of the
|
|
239
|
+
// originals stay for their single-purpose callers.
|
|
240
|
+
export async function fetchPrMergeSnapshot(token, owner, name, number) {
|
|
241
|
+
const path = `/repos/${owner}/${name}/pulls/${number}`;
|
|
242
|
+
let pull = await ghRestGetFor(token, path);
|
|
243
|
+
// GitHub computes `mergeable` asynchronously and can briefly return null; retry once,
|
|
244
|
+
// exactly as fetchMergeability does.
|
|
245
|
+
if (pull.mergeable === null) {
|
|
246
|
+
await new Promise((r) => setTimeout(r, 600));
|
|
247
|
+
pull = await ghRestGetFor(token, path);
|
|
248
|
+
}
|
|
249
|
+
const cmp = await compareBaseToHead(token, owner, name, pull);
|
|
250
|
+
const baseRepoFullName = pull.base.repo.full_name;
|
|
251
|
+
const headRepoFullName = pull.head.repo?.full_name ?? baseRepoFullName;
|
|
252
|
+
return {
|
|
253
|
+
headSha: pull.head.sha,
|
|
254
|
+
headRef: pull.head.ref,
|
|
255
|
+
headRepoFullName,
|
|
256
|
+
isFork: pull.head.repo == null || headRepoFullName !== baseRepoFullName,
|
|
257
|
+
maintainerCanModify: pull.maintainer_can_modify,
|
|
258
|
+
mergeable: pull.mergeable,
|
|
259
|
+
mergeableState: pull.mergeable_state,
|
|
260
|
+
baseRef: pull.base.ref,
|
|
261
|
+
baseSha: cmp.baseSha ?? pull.base.sha,
|
|
262
|
+
behindBy: cmp.behindBy,
|
|
263
|
+
aheadBy: cmp.aheadBy,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
// One commit's parent SHAs (`GET /commits/{sha}`), in order — parent 0 is the first parent.
|
|
267
|
+
// Used to prove that a head move is the base-into-head merge WE asked for rather than a
|
|
268
|
+
// human push: only the former is a two-parent commit whose first parent is the old head.
|
|
269
|
+
export async function fetchCommitParents(token, owner, name, sha) {
|
|
270
|
+
const commit = await ghRestGetFor(token, `/repos/${owner}/${name}/commits/${sha}`);
|
|
271
|
+
return (commit.parents ?? []).map((p) => p.sha);
|
|
272
|
+
}
|
|
273
|
+
// Is `sha` reachable from `ref`? `GET /compare/{ref}...{sha}` reports 'identical' or 'behind'
|
|
274
|
+
// exactly when it is. Returns null when the comparison can't be made (deleted ref, no access)
|
|
275
|
+
// — the caller must treat "don't know" as "no consent", never as a yes.
|
|
276
|
+
export async function isCommitContainedInRef(token, owner, name, ref, sha) {
|
|
277
|
+
try {
|
|
278
|
+
const cmp = await ghRestGetFor(token, `/repos/${owner}/${name}/compare/${ref}...${sha}`);
|
|
279
|
+
return cmp.status === 'identical' || cmp.status === 'behind';
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const PR_CHECK_ROLLUP_QUERY = /* GraphQL */ `
|
|
286
|
+
query PrCheckRollup($owner: String!, $name: String!, $number: Int!) {
|
|
287
|
+
repository(owner: $owner, name: $name) {
|
|
288
|
+
pullRequest(number: $number) {
|
|
289
|
+
commits(last: 1) {
|
|
290
|
+
nodes {
|
|
291
|
+
commit {
|
|
292
|
+
statusCheckRollup {
|
|
293
|
+
state
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
`;
|
|
302
|
+
/**
|
|
303
|
+
* The LIVE rollup state of every check on a PR's head commit (SUCCESS / FAILURE / PENDING /
|
|
304
|
+
* ERROR / EXPECTED), or null.
|
|
305
|
+
*
|
|
306
|
+
* GraphQL rather than REST because `statusCheckRollup` is the ONE field that spans both check
|
|
307
|
+
* runs and legacy commit statuses; REST needs `/check-runs` and `/status` separately and still
|
|
308
|
+
* makes the caller fold them. One point, one call.
|
|
309
|
+
*
|
|
310
|
+
* ⚠ NULL MEANS "WE DON'T KNOW", never "green". A deleted PR, a token that can't read checks
|
|
311
|
+
* (`statusCheckRollup` is one of the sub-fields GitHub NULLS on a partial 200 — see
|
|
312
|
+
* github/client.ts), or a commit no CI ever ran on all land here, so every caller must treat
|
|
313
|
+
* null as "unproven" and take the conservative branch. Errors are swallowed for the same
|
|
314
|
+
* reason: the auto-merge watcher reads this only to decide whether to let a LATER PR go first,
|
|
315
|
+
* and a network blip must not turn into a reordering — or into a failure strike.
|
|
316
|
+
*/
|
|
317
|
+
export async function fetchPrHeadCheckRollup(token, owner, name, number) {
|
|
318
|
+
try {
|
|
319
|
+
const gql = getGraphqlClientFor(token);
|
|
320
|
+
const res = await gql(PR_CHECK_ROLLUP_QUERY, { owner, name, number });
|
|
321
|
+
const nodes = res.repository?.pullRequest?.commits?.nodes ?? [];
|
|
322
|
+
return nodes[0]?.commit?.statusCheckRollup?.state ?? null;
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// ---- PR merge + update-from-trunk (CORE / free tier) ----
|
|
329
|
+
// The repo's enabled merge methods + default branch (GET /repos/{o}/{n}). Not synced — a
|
|
330
|
+
// rarely-changing repo setting, fetched live only when the merge control opens. Missing flags
|
|
331
|
+
// default true (GitHub's own default for a new repo).
|
|
332
|
+
export async function fetchRepoMergeConfig(token, owner, name) {
|
|
333
|
+
const repo = await ghRestGetFor(token, `/repos/${owner}/${name}`);
|
|
334
|
+
return {
|
|
335
|
+
allowMergeCommit: repo.allow_merge_commit ?? true,
|
|
336
|
+
allowSquashMerge: repo.allow_squash_merge ?? true,
|
|
337
|
+
allowRebaseMerge: repo.allow_rebase_merge ?? true,
|
|
338
|
+
defaultBranch: repo.default_branch,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
// Merge the PR via GitHub's native endpoint (PUT .../merge). Pins `sha` to the expected head so
|
|
342
|
+
// GitHub 409s if the branch moved. Cloud-safe, clone-free. The caller pre-checks conflicts.
|
|
343
|
+
export async function mergePullRequest(token, owner, name, number, opts) {
|
|
344
|
+
const body = { merge_method: opts.method };
|
|
345
|
+
if (opts.expectedHeadSha)
|
|
346
|
+
body.sha = opts.expectedHeadSha;
|
|
347
|
+
const res = await ghRestPutStatus(token, `/repos/${owner}/${name}/pulls/${number}/merge`, body);
|
|
348
|
+
const j = (res.json ?? {});
|
|
349
|
+
if (res.ok && j.merged)
|
|
350
|
+
return { ok: true, sha: j.sha ?? '' };
|
|
351
|
+
const message = j.message ?? res.text.slice(0, 300);
|
|
352
|
+
const reason = res.status === 409
|
|
353
|
+
? 'head_moved'
|
|
354
|
+
: res.status === 422
|
|
355
|
+
? 'method_disallowed'
|
|
356
|
+
: res.status === 405
|
|
357
|
+
? 'not_mergeable'
|
|
358
|
+
: 'error';
|
|
359
|
+
return { ok: false, reason, message };
|
|
360
|
+
}
|
|
361
|
+
// Close an open PR (REST PATCH `{ state: 'closed' }`) — WITHOUT merging. Reversible on
|
|
362
|
+
// GitHub (it can be reopened), so no head-SHA pin is needed. Permission is re-checked by the
|
|
363
|
+
// caller (author OR write+). A 404 usually means the token can't see the PR (private/SSO).
|
|
364
|
+
export async function closePullRequest(token, owner, name, number) {
|
|
365
|
+
const res = await ghRestPatchStatus(token, `/repos/${owner}/${name}/pulls/${number}`, {
|
|
366
|
+
state: 'closed',
|
|
367
|
+
});
|
|
368
|
+
if (res.ok)
|
|
369
|
+
return { ok: true };
|
|
370
|
+
const j = (res.json ?? {});
|
|
371
|
+
const message = j.message ?? res.text.slice(0, 300);
|
|
372
|
+
return { ok: false, reason: res.status === 404 ? 'not_found' : 'error', message };
|
|
373
|
+
}
|
|
374
|
+
// Reopen a closed, unmerged PR (REST PATCH `{ state: 'open' }`) — the exact inverse of
|
|
375
|
+
// closePullRequest, same endpoint, same permission rule (author OR write+), re-checked by the
|
|
376
|
+
// caller. No head-SHA pin: reopening asserts nothing about the code.
|
|
377
|
+
//
|
|
378
|
+
// ⚠ 422 IS A REAL, EXPECTED ANSWER AND ITS USEFUL TEXT IS NESTED. GitHub refuses to reopen a PR
|
|
379
|
+
// whose HEAD BRANCH has been deleted (the ordinary aftermath of a close), and refuses on an
|
|
380
|
+
// archived repo. The top-level `message` on those is "Validation Failed" — useless on screen —
|
|
381
|
+
// while `errors[0].message` is the actual sentence ("state cannot be changed. …"). Prefer the
|
|
382
|
+
// nested one so the UI can state the fact instead of narrating a shrug. Reported as
|
|
383
|
+
// `not_reopenable` so the route can answer 409 rather than 502: nothing is broken, the reopen is
|
|
384
|
+
// simply not available.
|
|
385
|
+
export async function reopenPullRequest(token, owner, name, number) {
|
|
386
|
+
const res = await ghRestPatchStatus(token, `/repos/${owner}/${name}/pulls/${number}`, {
|
|
387
|
+
state: 'open',
|
|
388
|
+
});
|
|
389
|
+
if (res.ok)
|
|
390
|
+
return { ok: true };
|
|
391
|
+
const j = (res.json ?? {});
|
|
392
|
+
const message = j.errors?.find((e) => e.message)?.message ?? j.message ?? res.text.slice(0, 300);
|
|
393
|
+
const reason = res.status === 404
|
|
394
|
+
? 'not_found'
|
|
395
|
+
: res.status === 422 || res.status === 403
|
|
396
|
+
? 'not_reopenable'
|
|
397
|
+
: 'error';
|
|
398
|
+
return { ok: false, reason, message };
|
|
399
|
+
}
|
|
400
|
+
// GitHub's NATIVE "update branch from base" (PUT .../update-branch) — a merge commit of the base
|
|
401
|
+
// into the head. Cloud-safe, clone-free (the cloud path for update-from-trunk). Merge-only: there
|
|
402
|
+
// is no native rebase. 202 = accepted; 422 = can't update — which GitHub uses BOTH for a stale
|
|
403
|
+
// expected_head_sha (a head-moved race — its message mentions the expected head sha) AND for a
|
|
404
|
+
// genuine can't-merge, so we disambiguate on the message.
|
|
405
|
+
export async function updatePullRequestBranch(token, owner, name, number, expectedHeadSha) {
|
|
406
|
+
const body = expectedHeadSha ? { expected_head_sha: expectedHeadSha } : undefined;
|
|
407
|
+
const res = await ghRestPutStatus(token, `/repos/${owner}/${name}/pulls/${number}/update-branch`, body);
|
|
408
|
+
if (res.status === 202)
|
|
409
|
+
return { ok: true };
|
|
410
|
+
const j = (res.json ?? {});
|
|
411
|
+
const message = j.message ?? res.text.slice(0, 300);
|
|
412
|
+
const headMoved = res.status === 409 || /expected head sha/i.test(message);
|
|
413
|
+
const reason = headMoved ? 'head_moved' : res.status === 422 ? 'conflicts' : 'error';
|
|
414
|
+
return { ok: false, reason, message };
|
|
415
|
+
}
|
|
416
|
+
// The PR's unified diff via the REST API (Accept: application/vnd.github.diff) —
|
|
417
|
+
// cloud-ready, per-account. GitHub caps this media type at 20,000 lines and returns 406
|
|
418
|
+
// past it (a large PR — e.g. bulk data/generated files). In that case we fall back to the
|
|
419
|
+
// per-file endpoint, which has no total-lines cap, and synthesise a unified diff from each
|
|
420
|
+
// file's `patch`. Without this fallback the diff came back empty and the AI summary / CI
|
|
421
|
+
// analysis / fixer would (correctly, but uselessly) report "the diff is empty".
|
|
422
|
+
export async function fetchPrUnifiedDiff(token, owner, name, number) {
|
|
423
|
+
const path = `/repos/${owner}/${name}/pulls/${number}`;
|
|
424
|
+
const res = await ghRestGetDiffStatus(token, path);
|
|
425
|
+
if (res.ok)
|
|
426
|
+
return res.text;
|
|
427
|
+
if (res.status === 406) {
|
|
428
|
+
return reconstructUnifiedDiffFromFiles(token, owner, name, number);
|
|
429
|
+
}
|
|
430
|
+
throw new Error(`GitHub REST GET(diff) ${path} -> ${res.status}: ${res.text.slice(0, 300)}`);
|
|
431
|
+
}
|
|
432
|
+
// Synthesise a unified diff from GitHub's per-file `patch`es — the fallback when the
|
|
433
|
+
// whole-PR .diff 406s (too large). The /files endpoint has no total-lines cap (up to 3000
|
|
434
|
+
// files); a file's `patch` is omitted when it's binary or a single file's own diff is too
|
|
435
|
+
// large, so we name those with their churn/status. The synthetic `diff --git a/… b/…`
|
|
436
|
+
// headers keep the result splittable by capDiff's per-file budgeter (so a summary still
|
|
437
|
+
// stays within the prompt's char budget, grounded in the files that DO have patches).
|
|
438
|
+
export function filesToUnifiedDiff(files) {
|
|
439
|
+
const parts = [];
|
|
440
|
+
for (const f of files) {
|
|
441
|
+
const aPath = f.previous_filename ?? f.filename;
|
|
442
|
+
parts.push(`diff --git a/${aPath} b/${f.filename}`);
|
|
443
|
+
if (f.patch) {
|
|
444
|
+
parts.push(`--- a/${aPath}`, `+++ b/${f.filename}`, f.patch);
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
// GitHub omits `patch` for binary files, a single file whose own diff is too large,
|
|
448
|
+
// and some no-content changes (pure rename / mode-only). Lead with the authoritative
|
|
449
|
+
// status + churn (the real signal) and hedge the reason so a rename isn't mislabelled.
|
|
450
|
+
parts.push(`(diff not shown — status=${f.status}, +${f.additions}/-${f.deletions}; likely binary or too large)`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return parts.join('\n');
|
|
454
|
+
}
|
|
455
|
+
async function reconstructUnifiedDiffFromFiles(token, owner, name, number) {
|
|
456
|
+
// 300 files is plenty for a grounded summary and bounds the paging to ~3 requests; the
|
|
457
|
+
// downstream capDiff trims further to the prompt's char budget.
|
|
458
|
+
const { files } = await fetchPrFilesWithPatch(token, owner, name, number, 300);
|
|
459
|
+
return filesToUnifiedDiff(files);
|
|
460
|
+
}
|
|
461
|
+
// Open a pull request (per-account). `head` is the branch to merge FROM (in the base
|
|
462
|
+
// repo — the fixer only ever creates branches in the base repo, so no `owner:branch`
|
|
463
|
+
// cross-fork form is needed); `base` the branch to merge INTO.
|
|
464
|
+
export async function createPullRequest(token, args) {
|
|
465
|
+
const res = await ghRestPostFor(token, `/repos/${args.owner}/${args.name}/pulls`, {
|
|
466
|
+
title: args.title,
|
|
467
|
+
head: args.head,
|
|
468
|
+
base: args.base,
|
|
469
|
+
body: args.body,
|
|
470
|
+
});
|
|
471
|
+
return { number: res.number, url: res.html_url };
|
|
472
|
+
}
|
|
473
|
+
// File an issue (per-account) — the advisor's "send the brief to the bot's own repo"
|
|
474
|
+
// output. Issues are NOT synced (this app tracks PR activity), so the caller stores the
|
|
475
|
+
// returned URL itself; there is no visibility tail to run.
|
|
476
|
+
export async function createIssue(token, owner, name, title, body) {
|
|
477
|
+
const res = await ghRestPostFor(token, `/repos/${owner}/${name}/issues`, { title, body });
|
|
478
|
+
return { number: res.number, url: res.html_url };
|
|
479
|
+
}
|
|
480
|
+
// ---- Request reviewers on a PR (REST) ----
|
|
481
|
+
// Request one or more reviewers on a PR (POST .../pulls/:n/requested_reviewers with
|
|
482
|
+
// { reviewers: [login…], team_reviewers: [slug…] }). Needs a token with write/triage
|
|
483
|
+
// access; GitHub 422s if a login isn't a collaborator or is the PR author (the caller
|
|
484
|
+
// filters the author out). `teamSlugs` requests a whole team (a CODEOWNERS `@org/team`)
|
|
485
|
+
// WITHOUT expanding its membership — GitHub resolves the slug against the repo's org.
|
|
486
|
+
// Returns 201 with the updated PR body, which we don't need — the refreshed request
|
|
487
|
+
// state arrives on the next sync (reviewRequests are re-derived each sync).
|
|
488
|
+
export async function requestReviewers(token, owner, name, number, logins, teamSlugs = []) {
|
|
489
|
+
const body = { reviewers: logins };
|
|
490
|
+
if (teamSlugs.length > 0)
|
|
491
|
+
body.team_reviewers = teamSlugs;
|
|
492
|
+
await ghRestPostFor(token, `/repos/${owner}/${name}/pulls/${number}/requested_reviewers`, body);
|
|
493
|
+
}
|
|
494
|
+
// ---- Re-trigger a GitHub Actions workflow run ----
|
|
495
|
+
// Re-run a workflow run (per-account). `mode: 'failed'` reruns only the failed jobs
|
|
496
|
+
// (POST .../runs/:runId/rerun-failed-jobs); `mode: 'all'` reruns the whole run
|
|
497
|
+
// (POST .../runs/:runId/rerun). GitHub returns 201 with an EMPTY body and queues the
|
|
498
|
+
// run asynchronously; the caller has no run id to poll (the refreshed status arrives
|
|
499
|
+
// on the next sync). Uses the no-content POST helper so the empty success body isn't
|
|
500
|
+
// JSON-parsed. Needs a token with actions:write — fails loud (throws) otherwise,
|
|
501
|
+
// matching the other write helpers.
|
|
502
|
+
export async function rerunWorkflowRun(token, owner, name, runId, mode) {
|
|
503
|
+
const suffix = mode === 'failed' ? 'rerun-failed-jobs' : 'rerun';
|
|
504
|
+
await ghRestPostNoContent(token, `/repos/${owner}/${name}/actions/runs/${runId}/${suffix}`);
|
|
505
|
+
}
|
|
506
|
+
// ---- GitHub's native merge QUEUE (GraphQL only) -----------------------------------------
|
|
507
|
+
//
|
|
508
|
+
// House style in this file is REST-first. The merge queue forks to GraphQL because it has to:
|
|
509
|
+
// `enqueuePullRequest` / `dequeuePullRequest` are GA GraphQL mutations with NO REST
|
|
510
|
+
// equivalent, and queue presence is not inferable from anything REST returns — in particular
|
|
511
|
+
// `MergeStateStatus` has no QUEUED value, so a queued PR looks like any other (queued PRs here
|
|
512
|
+
// have read 'clean' and 'unknown').
|
|
513
|
+
//
|
|
514
|
+
// MEMBERSHIP AND ENTRY STATE ARE SYNCED (`pull_requests.in_merge_queue` +
|
|
515
|
+
// `merge_queue_entry_state`): the sync walk writes them, and every caller of the three functions
|
|
516
|
+
// below that holds a positive answer stamps them through `db/pr-merge-queue-stamp.ts`. POSITION
|
|
517
|
+
// and ETA are not: they change minute to minute and only the merge control renders them, so they
|
|
518
|
+
// are fetched live in GET /api/prs/:id/merge-options and never stored.
|
|
519
|
+
// `MergeQueueEntry.estimatedTimeToMerge` is an Int of SECONDS in the GitHub schema; the wire
|
|
520
|
+
// type (PrMergeQueueInfo.estimatedTimeToMergeMs) is milliseconds, so it is scaled here — the
|
|
521
|
+
// one place that assumption lives.
|
|
522
|
+
const SECONDS_TO_MS = 1000;
|
|
523
|
+
const MERGE_QUEUE_STATE_QUERY = /* GraphQL */ `
|
|
524
|
+
query MergeQueueState($owner: String!, $name: String!, $number: Int!) {
|
|
525
|
+
repository(owner: $owner, name: $name) {
|
|
526
|
+
pullRequest(number: $number) {
|
|
527
|
+
state
|
|
528
|
+
reviewDecision
|
|
529
|
+
isMergeQueueEnabled
|
|
530
|
+
isInMergeQueue
|
|
531
|
+
mergeQueueEntry {
|
|
532
|
+
position
|
|
533
|
+
state
|
|
534
|
+
estimatedTimeToMerge
|
|
535
|
+
enqueuedAt
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
`;
|
|
541
|
+
// Live merge-queue state for one PR. Returns null when the PR can't be read (deleted, or the
|
|
542
|
+
// token can't see it) — the caller renders "no merge queue" rather than failing the whole
|
|
543
|
+
// merge-options fetch.
|
|
544
|
+
export async function fetchMergeQueueState(token, owner, name, number) {
|
|
545
|
+
const gql = getGraphqlClientFor(token);
|
|
546
|
+
const res = await gql(MERGE_QUEUE_STATE_QUERY, {
|
|
547
|
+
owner,
|
|
548
|
+
name,
|
|
549
|
+
number,
|
|
550
|
+
});
|
|
551
|
+
const pr = res.repository?.pullRequest;
|
|
552
|
+
if (!pr)
|
|
553
|
+
return null;
|
|
554
|
+
const entry = pr.mergeQueueEntry;
|
|
555
|
+
return {
|
|
556
|
+
enabled: pr.isMergeQueueEnabled,
|
|
557
|
+
inQueue: pr.isInMergeQueue,
|
|
558
|
+
position: entry?.position ?? null,
|
|
559
|
+
state: entry?.state ?? null,
|
|
560
|
+
estimatedTimeToMergeMs: entry?.estimatedTimeToMerge != null ? entry.estimatedTimeToMerge * SECONDS_TO_MS : null,
|
|
561
|
+
enqueuedAt: entry?.enqueuedAt ?? null,
|
|
562
|
+
prState: pr.state,
|
|
563
|
+
reviewDecision: pr.reviewDecision,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
const ENQUEUE_MUTATION = /* GraphQL */ `
|
|
567
|
+
mutation EnqueuePr($input: EnqueuePullRequestInput!) {
|
|
568
|
+
enqueuePullRequest(input: $input) {
|
|
569
|
+
mergeQueueEntry {
|
|
570
|
+
position
|
|
571
|
+
state
|
|
572
|
+
estimatedTimeToMerge
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
`;
|
|
577
|
+
const DEQUEUE_MUTATION = /* GraphQL */ `
|
|
578
|
+
mutation DequeuePr($input: DequeuePullRequestInput!) {
|
|
579
|
+
dequeuePullRequest(input: $input) {
|
|
580
|
+
mergeQueueEntry {
|
|
581
|
+
position
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
`;
|
|
586
|
+
/**
|
|
587
|
+
* Add the PR to its base branch's merge queue. `expectedHeadOid` pins the enqueue to the head
|
|
588
|
+
* the caller saw — GitHub rejects the mutation if the branch moved, the same consent anchor
|
|
589
|
+
* the direct merge uses (`sha` on PUT .../merge).
|
|
590
|
+
*
|
|
591
|
+
* `prNodeId` is the PR's GraphQL node id (pullRequests.githubNodeId), NOT its number.
|
|
592
|
+
*/
|
|
593
|
+
/**
|
|
594
|
+
* Is this enqueue refusal GitHub saying "not yet" rather than "no"?
|
|
595
|
+
*
|
|
596
|
+
* ⚠ THIS EXISTS BECAUSE THE OPPOSITE ASSUMPTION KILLED REAL INTENTS. `enqueueWhenReady`'s
|
|
597
|
+
* comment asserted that checks do not gate entry ("AWAITING_CHECKS is a normal entry state; the
|
|
598
|
+
* queue runs them itself"), which is true of a queue configured to run its own checks and FALSE
|
|
599
|
+
* of a branch whose protection requires them before entry. On the reporting account 3 of 21
|
|
600
|
+
* armed intents died this way, and two of them were refused purely because CI had not finished:
|
|
601
|
+
*
|
|
602
|
+
* "Pull request 2 of 3 required status checks are in progress."
|
|
603
|
+
* "2 of 2 required status checks have not succeeded: 1 expected."
|
|
604
|
+
*
|
|
605
|
+
* A refusal like that is the exact condition arming exists to wait out. Treated as an error it
|
|
606
|
+
* burned a strike, and three strikes at a two-minute tick killed the intent about six minutes
|
|
607
|
+
* after the user armed it — with CI still running.
|
|
608
|
+
*
|
|
609
|
+
* ⚠ MATCHES THE CONDITION, NOT THE WORD "check". A refusal naming a rule the watcher can never
|
|
610
|
+
* satisfy on its own (a missing review, a draft, a protected-branch restriction) must still fail
|
|
611
|
+
* — waiting forever on those is its own bug. Failing statuses are deliberately INCLUDED as
|
|
612
|
+
* waitable: a red check is routinely re-run or fixed by a push, and a push disarms us anyway
|
|
613
|
+
* through the head pin, which is the honest exit.
|
|
614
|
+
*/
|
|
615
|
+
export function isWaitableEnqueueRefusal(message) {
|
|
616
|
+
const m = message.toLowerCase();
|
|
617
|
+
if (!m.includes('status check') && !m.includes('required statuses'))
|
|
618
|
+
return false;
|
|
619
|
+
return (m.includes('in progress') ||
|
|
620
|
+
m.includes('have not succeeded') ||
|
|
621
|
+
m.includes('has not succeeded') ||
|
|
622
|
+
m.includes('expected') ||
|
|
623
|
+
m.includes('pending') ||
|
|
624
|
+
m.includes('queued') ||
|
|
625
|
+
m.includes('failing'));
|
|
626
|
+
}
|
|
627
|
+
export async function enqueuePullRequestOnQueue(token, prNodeId, expectedHeadOid) {
|
|
628
|
+
const gql = getGraphqlClientFor(token);
|
|
629
|
+
const input = { pullRequestId: prNodeId };
|
|
630
|
+
if (expectedHeadOid)
|
|
631
|
+
input.expectedHeadOid = expectedHeadOid;
|
|
632
|
+
const res = await gql(ENQUEUE_MUTATION, { input });
|
|
633
|
+
const entry = res.enqueuePullRequest?.mergeQueueEntry ?? null;
|
|
634
|
+
return {
|
|
635
|
+
position: entry?.position ?? null,
|
|
636
|
+
state: entry?.state ?? null,
|
|
637
|
+
estimatedTimeToMergeMs: entry?.estimatedTimeToMerge != null ? entry.estimatedTimeToMerge * SECONDS_TO_MS : null,
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
/** Remove the PR from the merge queue. `DequeuePullRequestInput.id` is the PR's node id. */
|
|
641
|
+
export async function dequeuePullRequestFromQueue(token, prNodeId) {
|
|
642
|
+
const gql = getGraphqlClientFor(token);
|
|
643
|
+
await gql(DEQUEUE_MUTATION, { input: { id: prNodeId } });
|
|
644
|
+
}
|
|
645
|
+
// ⚠ `reactors` takes NO `first:` — see github/queries.ts for the measured 44× cost cliff.
|
|
646
|
+
const REACTION_SUBJECT_FIELDS = /* GraphQL */ `
|
|
647
|
+
subject {
|
|
648
|
+
id
|
|
649
|
+
... on Reactable {
|
|
650
|
+
viewerCanReact
|
|
651
|
+
reactionGroups {
|
|
652
|
+
content
|
|
653
|
+
viewerHasReacted
|
|
654
|
+
reactors {
|
|
655
|
+
totalCount
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
`;
|
|
661
|
+
const ADD_REACTION_MUTATION = /* GraphQL */ `
|
|
662
|
+
mutation AddReaction($input: AddReactionInput!) {
|
|
663
|
+
addReaction(input: $input) {
|
|
664
|
+
${REACTION_SUBJECT_FIELDS}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
`;
|
|
668
|
+
const REMOVE_REACTION_MUTATION = /* GraphQL */ `
|
|
669
|
+
mutation RemoveReaction($input: RemoveReactionInput!) {
|
|
670
|
+
removeReaction(input: $input) {
|
|
671
|
+
${REACTION_SUBJECT_FIELDS}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
`;
|
|
675
|
+
/**
|
|
676
|
+
* Toggle one reaction on one subject. `subjectNodeId` is the comment / review body's GitHub
|
|
677
|
+
* node id; `content` is the lowercase wire value, mapped to GitHub's enum here.
|
|
678
|
+
*
|
|
679
|
+
* Returns the subject's post-write group set. Adding a reaction the viewer already has (or
|
|
680
|
+
* removing one they never had) is idempotent on GitHub's side, so a double-click cannot
|
|
681
|
+
* desynchronise the count.
|
|
682
|
+
*
|
|
683
|
+
* ⚠ ASYMMETRY WITH THE READ PATH, and it is GitHub's, not ours. `fetchReactionsForNodes`
|
|
684
|
+
* feeds `noteBudget` from the `rateLimit` block it selects; this cannot, because `rateLimit`
|
|
685
|
+
* is a field on the `Query` root ONLY — GitHub's `Mutation` type does not expose it
|
|
686
|
+
* (verified by introspection), so there is no block to select here. Reading it back would
|
|
687
|
+
* mean plumbing the HTTP response headers out of the graphql client, which is not worth one
|
|
688
|
+
* budget observation per user click. The PROTECTIVE half is covered regardless: the caller
|
|
689
|
+
* classifies a failure through `isRateLimitError` and calls `noteLimited`, so a toggle that
|
|
690
|
+
* discovers the limit still teaches the shared budget and the next lookup skips itself.
|
|
691
|
+
*/
|
|
692
|
+
export async function setReaction(token, subjectNodeId, content, add) {
|
|
693
|
+
const gql = getGraphqlClientFor(token);
|
|
694
|
+
const input = { subjectId: subjectNodeId, content: toGithubReactionContent(content) };
|
|
695
|
+
const res = await gql(add ? ADD_REACTION_MUTATION : REMOVE_REACTION_MUTATION, { input });
|
|
696
|
+
const payload = add ? res.addReaction : res.removeReaction;
|
|
697
|
+
const subject = payload?.subject ?? null;
|
|
698
|
+
if (!subject) {
|
|
699
|
+
throw new Error(`GitHub returned no subject for the reaction ${add ? 'add' : 'remove'} (it may still have applied)`);
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
groups: summariseReactionGroups(subject.reactionGroups),
|
|
703
|
+
viewerCanReact: subject.viewerCanReact === true,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
//# sourceMappingURL=mutations.js.map
|