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,254 @@
|
|
|
1
|
+
import { diffTokens } from '../conflict/diff.js';
|
|
2
|
+
import { ghRestGetFor } from './client.js';
|
|
3
|
+
// The Changes tab's three click-gated "load more" reads (docs/API.md § PR files):
|
|
4
|
+
//
|
|
5
|
+
// • `?page=` on GET /api/prs/:id/files — the next 100 changed files (`fetchPrFilesPage`).
|
|
6
|
+
// • GET /api/prs/:id/files/content — one file's raw text at the PR head or the merge base, so a
|
|
7
|
+
// gap marker can expand the unchanged lines GitHub's patch leaves out (`fetchFileAtRef`).
|
|
8
|
+
// • GET /api/prs/:id/files/diff — a file GitHub sends NO patch for (too large): both sides are
|
|
9
|
+
// fetched and diffed here with the resolver's Myers engine (`synthesizeUnifiedPatch`).
|
|
10
|
+
//
|
|
11
|
+
// ⚠ NOTHING HERE IS STORED. File bodies are live reads, cached client-side only; the merge base
|
|
12
|
+
// is cached in this process's memory per (account, PR, head) because it is the one value two
|
|
13
|
+
// different clicks both need and it cannot change while the head does not.
|
|
14
|
+
//
|
|
15
|
+
// ⚠ EVERY READ IS CLICK-GATED IN THE SPA — never on mount. A PR with 3,000 files would otherwise
|
|
16
|
+
// spend 30 calls to paint, and every gap in every file would be a call.
|
|
17
|
+
/** GitHub's REST ceiling for `GET /pulls/:n/files` — it lists at most 3,000 files. */
|
|
18
|
+
export const PR_FILES_CEILING = 3000;
|
|
19
|
+
/** Files per page (GitHub's own per_page cap for this endpoint). */
|
|
20
|
+
export const PR_FILES_PER_PAGE = 100;
|
|
21
|
+
/** Last page we will ask for: 30 × 100 = the 3,000-file ceiling. */
|
|
22
|
+
export const PR_FILES_MAX_PAGE = PR_FILES_CEILING / PR_FILES_PER_PAGE;
|
|
23
|
+
/**
|
|
24
|
+
* Per-side size limit for a file body (`/files/content` and `/files/diff`). Past this a file is
|
|
25
|
+
* not something a reader scrolls in a review pane, and the server-side diff of two such sides is
|
|
26
|
+
* the expensive part. Refused with a plain sentence; the GitHub link stays.
|
|
27
|
+
*/
|
|
28
|
+
export const MAX_FILE_CONTENT_BYTES = 1_000_000;
|
|
29
|
+
/** Myers edit-distance budget for a synthesised diff. Past it the file is one replacement hunk. */
|
|
30
|
+
const SYNTH_DIFF_MAX_D = 20_000;
|
|
31
|
+
/**
|
|
32
|
+
* ONE page of a PR's changed files. `nextPage` is set whenever the page came back FULL and the
|
|
33
|
+
* ceiling is not reached — a full page followed by an empty one costs one harmless extra click,
|
|
34
|
+
* where looking ahead would cost a call on every page. `ceilingReached` is true when this is the
|
|
35
|
+
* last page GitHub will ever serve and it was full: files past 3,000 exist but cannot be listed.
|
|
36
|
+
*/
|
|
37
|
+
export async function fetchPrFilesPage(token, owner, name, number, page) {
|
|
38
|
+
const p = Math.min(Math.max(1, Math.trunc(page)), PR_FILES_MAX_PAGE);
|
|
39
|
+
const files = await ghRestGetFor(token, `/repos/${owner}/${name}/pulls/${number}/files?per_page=${PR_FILES_PER_PAGE}&page=${p}`);
|
|
40
|
+
const full = files.length >= PR_FILES_PER_PAGE;
|
|
41
|
+
return {
|
|
42
|
+
files,
|
|
43
|
+
nextPage: full && p < PR_FILES_MAX_PAGE ? p + 1 : null,
|
|
44
|
+
ceilingReached: full && p >= PR_FILES_MAX_PAGE,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Find ONE file's REST patch anywhere in the PR's file list, paging until it turns up. The inline
|
|
49
|
+
* comment route needs this: with the Changes tab now paging past the first 100 files, a reader can
|
|
50
|
+
* comment on file 150, and a first-page-only lookup would refuse that line as "not in the diff".
|
|
51
|
+
*/
|
|
52
|
+
export async function findPrFileWithPatch(token, owner, name, number, path) {
|
|
53
|
+
for (let page = 1; page <= PR_FILES_MAX_PAGE; page += 1) {
|
|
54
|
+
const { files, nextPage } = await fetchPrFilesPage(token, owner, name, number, page);
|
|
55
|
+
const hit = files.find((f) => f.filename === path);
|
|
56
|
+
if (hit)
|
|
57
|
+
return hit;
|
|
58
|
+
if (nextPage == null)
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A repo-relative path the SPA may ask for. It only ever names a file inside the PR's own repo
|
|
65
|
+
* (owner/name come from the account-scoped PR row, never the request), so this is hygiene rather
|
|
66
|
+
* than the tenancy boundary — but a `..` segment or a leading slash has no business in a path
|
|
67
|
+
* GitHub listed, and a NUL never reaches a URL.
|
|
68
|
+
*/
|
|
69
|
+
export function isSafeRepoPath(path) {
|
|
70
|
+
if (typeof path !== 'string')
|
|
71
|
+
return false;
|
|
72
|
+
if (path === '' || path.length > 4096)
|
|
73
|
+
return false;
|
|
74
|
+
if (path.startsWith('/') || path.includes('\0') || path.includes('\\'))
|
|
75
|
+
return false;
|
|
76
|
+
return path.split('/').every((seg) => seg !== '' && seg !== '.' && seg !== '..');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* One file's raw bytes at `ref` (Accept: application/vnd.github.raw), capped at
|
|
80
|
+
* `MAX_FILE_CONTENT_BYTES` WHILE READING — a 90 MB generated file is refused after one megabyte
|
|
81
|
+
* crosses the wire, not after the whole thing is buffered. A NUL byte marks it binary (git's own
|
|
82
|
+
* test). Throws on any GitHub failure other than a 404, which is the ordinary "this side has no
|
|
83
|
+
* such file" (an added file's base, a deleted file's head).
|
|
84
|
+
*/
|
|
85
|
+
export async function fetchFileAtRef(token, owner, name, path, ref) {
|
|
86
|
+
const encoded = path.split('/').map(encodeURIComponent).join('/');
|
|
87
|
+
const res = await fetch(`https://api.github.com/repos/${owner}/${name}/contents/${encoded}?ref=${encodeURIComponent(ref)}`, {
|
|
88
|
+
method: 'GET',
|
|
89
|
+
headers: {
|
|
90
|
+
authorization: `token ${token}`,
|
|
91
|
+
accept: 'application/vnd.github.raw',
|
|
92
|
+
'x-github-api-version': '2022-11-28',
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
if (res.status === 404) {
|
|
96
|
+
await res.body?.cancel().catch(() => { });
|
|
97
|
+
return { kind: 'missing' };
|
|
98
|
+
}
|
|
99
|
+
if (!res.ok) {
|
|
100
|
+
const text = await res.text().catch(() => '');
|
|
101
|
+
throw Object.assign(new Error(`GitHub contents ${res.status}: ${text.slice(0, 200)}`), {
|
|
102
|
+
status: res.status,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const declared = Number(res.headers.get('content-length'));
|
|
106
|
+
if (Number.isFinite(declared) && declared > MAX_FILE_CONTENT_BYTES) {
|
|
107
|
+
await res.body?.cancel().catch(() => { });
|
|
108
|
+
return { kind: 'too_large', bytes: declared };
|
|
109
|
+
}
|
|
110
|
+
const chunks = [];
|
|
111
|
+
let total = 0;
|
|
112
|
+
const reader = res.body?.getReader();
|
|
113
|
+
if (reader) {
|
|
114
|
+
for (;;) {
|
|
115
|
+
const { done, value } = await reader.read();
|
|
116
|
+
if (done)
|
|
117
|
+
break;
|
|
118
|
+
total += value.byteLength;
|
|
119
|
+
if (total > MAX_FILE_CONTENT_BYTES) {
|
|
120
|
+
await reader.cancel().catch(() => { });
|
|
121
|
+
return { kind: 'too_large', bytes: null };
|
|
122
|
+
}
|
|
123
|
+
chunks.push(value);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const bytes = Buffer.concat(chunks.map((c) => Buffer.from(c)));
|
|
127
|
+
if (bytes.includes(0))
|
|
128
|
+
return { kind: 'binary' };
|
|
129
|
+
return { kind: 'text', text: bytes.toString('utf8') };
|
|
130
|
+
}
|
|
131
|
+
/** Bounded insertion-ordered cache; the oldest entry goes first. Keyed per account — the key
|
|
132
|
+
* carries the accountId so one tenant can never read another's resolution. */
|
|
133
|
+
const MERGE_BASE_CACHE_MAX = 500;
|
|
134
|
+
const mergeBaseCache = new Map();
|
|
135
|
+
/**
|
|
136
|
+
* The two commits the PR's diff is BETWEEN: the head the SPA's patch was read at (the stored head,
|
|
137
|
+
* else GitHub's live one) and the merge base GitHub's three-dot "Files changed" diff is taken
|
|
138
|
+
* against. The base is not stored anywhere (the sync never needed it), so it costs one `pulls`
|
|
139
|
+
* read plus one `compare` — once per (account, PR, head), then from memory.
|
|
140
|
+
*/
|
|
141
|
+
export async function resolvePrDiffRefs(token, accountId, prId, owner, name, number, storedHeadSha) {
|
|
142
|
+
// No stored head ⇒ no cache: the live head can move under a key that does not name it.
|
|
143
|
+
const key = storedHeadSha != null ? `${accountId}:${prId}:${storedHeadSha}` : null;
|
|
144
|
+
const hit = key != null ? mergeBaseCache.get(key) : undefined;
|
|
145
|
+
if (hit)
|
|
146
|
+
return hit;
|
|
147
|
+
const pull = await ghRestGetFor(token, `/repos/${owner}/${name}/pulls/${number}`);
|
|
148
|
+
const headSha = storedHeadSha ?? pull.head.sha;
|
|
149
|
+
// `per_page=1`: the compare payload also pages its commit list, which nothing here reads.
|
|
150
|
+
const cmp = await ghRestGetFor(token, `/repos/${owner}/${name}/compare/${pull.base.sha}...${headSha}?per_page=1`);
|
|
151
|
+
const mergeBase = cmp.merge_base_commit?.sha ?? pull.base.sha;
|
|
152
|
+
const value = { headSha, mergeBase };
|
|
153
|
+
if (key == null)
|
|
154
|
+
return value;
|
|
155
|
+
mergeBaseCache.set(key, value);
|
|
156
|
+
while (mergeBaseCache.size > MERGE_BASE_CACHE_MAX) {
|
|
157
|
+
const oldest = mergeBaseCache.keys().next().value;
|
|
158
|
+
if (oldest == null)
|
|
159
|
+
break;
|
|
160
|
+
mergeBaseCache.delete(oldest);
|
|
161
|
+
}
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
/** Test seam: forget every cached merge base. */
|
|
165
|
+
export function clearMergeBaseCache() {
|
|
166
|
+
mergeBaseCache.clear();
|
|
167
|
+
}
|
|
168
|
+
// ---- a unified patch from two whole files ----
|
|
169
|
+
function splitLines(text) {
|
|
170
|
+
if (text === '')
|
|
171
|
+
return [];
|
|
172
|
+
const lines = text.split('\n');
|
|
173
|
+
if (lines[lines.length - 1] === '')
|
|
174
|
+
lines.pop();
|
|
175
|
+
return lines.map((l) => (l.endsWith('\r') ? l.slice(0, -1) : l));
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* A header-less unified patch (starting at the first `@@`, exactly the shape GitHub's REST `patch`
|
|
179
|
+
* field has) from the two sides of one file, with `context` unchanged lines around each change.
|
|
180
|
+
* Lines are interned to integers and diffed by `diffTokens` — the conflict resolver's Myers, which
|
|
181
|
+
* is git's own default algorithm, so the hunks land where GitHub's would. A pair past the edit
|
|
182
|
+
* budget degrades to ONE whole-file replacement hunk: coarser, never wrong.
|
|
183
|
+
*
|
|
184
|
+
* Returns `{ patch: '' }` when the two sides are line-identical (a whitespace-at-EOF-only change).
|
|
185
|
+
*/
|
|
186
|
+
export function synthesizeUnifiedPatch(oldText, newText, context = 3) {
|
|
187
|
+
const a = splitLines(oldText);
|
|
188
|
+
const b = splitLines(newText);
|
|
189
|
+
const ids = new Map();
|
|
190
|
+
const intern = (l) => {
|
|
191
|
+
let id = ids.get(l);
|
|
192
|
+
if (id == null) {
|
|
193
|
+
id = ids.size;
|
|
194
|
+
ids.set(l, id);
|
|
195
|
+
}
|
|
196
|
+
return id;
|
|
197
|
+
};
|
|
198
|
+
const ai = a.map(intern);
|
|
199
|
+
const bi = b.map(intern);
|
|
200
|
+
const changes = diffTokens(ai, bi, SYNTH_DIFF_MAX_D) ?? [
|
|
201
|
+
{ aStart: 0, aEnd: a.length, bStart: 0, bEnd: b.length },
|
|
202
|
+
];
|
|
203
|
+
const real = changes.filter((c) => c.aEnd > c.aStart || c.bEnd > c.bStart);
|
|
204
|
+
if (real.length === 0)
|
|
205
|
+
return { patch: '', additions: 0, deletions: 0 };
|
|
206
|
+
// Group changes whose context windows touch into one hunk.
|
|
207
|
+
const groups = [];
|
|
208
|
+
for (const c of real) {
|
|
209
|
+
const last = groups[groups.length - 1];
|
|
210
|
+
const prev = last?.[last.length - 1];
|
|
211
|
+
if (last && prev && c.aStart - prev.aEnd <= context * 2)
|
|
212
|
+
last.push(c);
|
|
213
|
+
else
|
|
214
|
+
groups.push([c]);
|
|
215
|
+
}
|
|
216
|
+
const out = [];
|
|
217
|
+
let additions = 0;
|
|
218
|
+
let deletions = 0;
|
|
219
|
+
for (const g of groups) {
|
|
220
|
+
const first = g[0];
|
|
221
|
+
const lastC = g[g.length - 1];
|
|
222
|
+
const lead = Math.min(context, first.aStart);
|
|
223
|
+
const aFrom = first.aStart - lead;
|
|
224
|
+
const bFrom = first.bStart - lead;
|
|
225
|
+
const trail = Math.min(context, a.length - lastC.aEnd);
|
|
226
|
+
const aTo = lastC.aEnd + trail;
|
|
227
|
+
const bTo = lastC.bEnd + trail;
|
|
228
|
+
const body = [];
|
|
229
|
+
let ap = aFrom;
|
|
230
|
+
for (const c of g) {
|
|
231
|
+
for (; ap < c.aStart; ap += 1)
|
|
232
|
+
body.push(` ${a[ap]}`);
|
|
233
|
+
for (let k = c.aStart; k < c.aEnd; k += 1) {
|
|
234
|
+
body.push(`-${a[k]}`);
|
|
235
|
+
deletions += 1;
|
|
236
|
+
}
|
|
237
|
+
for (let k = c.bStart; k < c.bEnd; k += 1) {
|
|
238
|
+
body.push(`+${b[k]}`);
|
|
239
|
+
additions += 1;
|
|
240
|
+
}
|
|
241
|
+
ap = c.aEnd;
|
|
242
|
+
}
|
|
243
|
+
for (; ap < aTo; ap += 1)
|
|
244
|
+
body.push(` ${a[ap]}`);
|
|
245
|
+
const aCount = aTo - aFrom;
|
|
246
|
+
const bCount = bTo - bFrom;
|
|
247
|
+
// git's convention: an empty range names the line BEFORE it (0 for the top of the file).
|
|
248
|
+
const aStart = aCount === 0 ? aFrom : aFrom + 1;
|
|
249
|
+
const bStart = bCount === 0 ? bFrom : bFrom + 1;
|
|
250
|
+
out.push(`@@ -${aStart},${aCount} +${bStart},${bCount} @@`, ...body);
|
|
251
|
+
}
|
|
252
|
+
return { patch: out.join('\n'), additions, deletions };
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=pr-file-content.js.map
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { getGraphqlClientFor, graphqlTolerant, isRateLimitError, } from './client.js';
|
|
2
|
+
// IMPORTED, never re-spelled. `mergeQueueEntryStateFrom` returns null for a state GitHub adds
|
|
3
|
+
// later rather than storing the raw string, and both merge-queue write routes already stamp the
|
|
4
|
+
// column through it — a second copy of that table here is how this sweep and the fat walk start
|
|
5
|
+
// writing different values into one column from the same GitHub answer.
|
|
6
|
+
import { mergeQueueEntryStateFrom } from '../sync/upsert.js';
|
|
7
|
+
import { isLimited, noteBudget, noteLimited } from './rate-budget.js';
|
|
8
|
+
import { PR_LIVENESS_NODES_QUERY, } from './queries.js';
|
|
9
|
+
/**
|
|
10
|
+
* The CHEAP pass's batch. GitHub's `nodes(ids:)` accepts at most 100 ids; kept under it for the
|
|
11
|
+
* reason REACTION_NODE_BATCH is, and MEASURED comfortable at 90 (~1.4s, 1 point).
|
|
12
|
+
*/
|
|
13
|
+
export const PR_LIVENESS_NODE_BATCH = 90;
|
|
14
|
+
/**
|
|
15
|
+
* The EXPENSIVE pass's batch — the one that asks GitHub to compute mergeability.
|
|
16
|
+
*
|
|
17
|
+
* ⚠ 25 IS NOT A ROUND NUMBER PICKED FOR TIDINESS. Measured: 40 ids answered in 5.8s, 50 ids
|
|
18
|
+
* returned HTTP 502 after 10.8s, 90 ids 502'd after 11.2s. The failure is a gateway TIMEOUT and
|
|
19
|
+
* it returns no partial data — one over-large batch loses every PR in it, not the marginal one.
|
|
20
|
+
* 25 measured 3.9-5.5s across three disjoint slices of this account's real open PRs, which leaves
|
|
21
|
+
* roughly a 2× margin against the observed cliff on a resource whose latency is GitHub's to vary.
|
|
22
|
+
*/
|
|
23
|
+
export const PR_MERGE_STATE_NODE_BATCH = 25;
|
|
24
|
+
/** A `rateLimit.resetAt` we can actually use, or null. Never feeds an Invalid Date to the budget. */
|
|
25
|
+
function parseResetAt(raw) {
|
|
26
|
+
if (!raw)
|
|
27
|
+
return null;
|
|
28
|
+
const d = new Date(raw);
|
|
29
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
30
|
+
}
|
|
31
|
+
const STATE_FROM = {
|
|
32
|
+
OPEN: 'open',
|
|
33
|
+
CLOSED: 'closed',
|
|
34
|
+
MERGED: 'merged',
|
|
35
|
+
};
|
|
36
|
+
const MERGEABLE_FROM = {
|
|
37
|
+
MERGEABLE: 'mergeable',
|
|
38
|
+
CONFLICTING: 'conflicting',
|
|
39
|
+
UNKNOWN: 'unknown',
|
|
40
|
+
};
|
|
41
|
+
const MERGE_STATE_FROM = {
|
|
42
|
+
CLEAN: 'clean',
|
|
43
|
+
DIRTY: 'dirty',
|
|
44
|
+
UNSTABLE: 'unstable',
|
|
45
|
+
BLOCKED: 'blocked',
|
|
46
|
+
BEHIND: 'behind',
|
|
47
|
+
HAS_HOOKS: 'has_hooks',
|
|
48
|
+
UNKNOWN: 'unknown',
|
|
49
|
+
};
|
|
50
|
+
const REVIEW_DECISION_FROM = {
|
|
51
|
+
APPROVED: 'approved',
|
|
52
|
+
CHANGES_REQUESTED: 'changes_requested',
|
|
53
|
+
REVIEW_REQUIRED: 'review_required',
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Fold ONE node into an observation, or null when nothing usable arrived.
|
|
57
|
+
*
|
|
58
|
+
* ⚠ `state` IS THE PROOF THAT THE FRAGMENT LANDED. GitHub returns a non-null `state` for every
|
|
59
|
+
* PullRequest it will talk about, so a null/absent one means the id resolved to another type, the
|
|
60
|
+
* token cannot see it, or `graphqlTolerant` NULLED the selection after a partial error. In all
|
|
61
|
+
* three cases we know nothing and must say so — an "absent means unchanged" reading here would
|
|
62
|
+
* quietly stop refreshing exactly the PRs whose access just broke.
|
|
63
|
+
*/
|
|
64
|
+
export function foldLivenessNode(node) {
|
|
65
|
+
if (!node?.id)
|
|
66
|
+
return null;
|
|
67
|
+
const state = node.state ? STATE_FROM[node.state.toUpperCase()] : undefined;
|
|
68
|
+
if (!state)
|
|
69
|
+
return null;
|
|
70
|
+
const out = {
|
|
71
|
+
nodeId: node.id,
|
|
72
|
+
state,
|
|
73
|
+
isDraft: typeof node.isDraft === 'boolean' ? node.isDraft : null,
|
|
74
|
+
updatedAt: node.updatedAt ?? null,
|
|
75
|
+
mergedAt: node.mergedAt ?? null,
|
|
76
|
+
closedAt: node.closedAt ?? null,
|
|
77
|
+
// Null here is GitHub SAYING "no review decision" (the 92%-of-open-PRs case on real data:
|
|
78
|
+
// the repo requires no review), which is the same value persistPr stores from the fat walk.
|
|
79
|
+
// It reaches us only because `state` above proved the fragment landed.
|
|
80
|
+
reviewDecision: node.reviewDecision
|
|
81
|
+
? (REVIEW_DECISION_FROM[node.reviewDecision.toUpperCase()] ?? null)
|
|
82
|
+
: null,
|
|
83
|
+
};
|
|
84
|
+
// Left UNSET, never set to a fallback: the cheap pass does not ask for these, and an
|
|
85
|
+
// enum member we do not model is dropped rather than written through.
|
|
86
|
+
const m = node.mergeable ? MERGEABLE_FROM[node.mergeable.toUpperCase()] : undefined;
|
|
87
|
+
if (m)
|
|
88
|
+
out.mergeable = m;
|
|
89
|
+
const s = node.mergeStateStatus
|
|
90
|
+
? MERGE_STATE_FROM[node.mergeStateStatus.toUpperCase()]
|
|
91
|
+
: undefined;
|
|
92
|
+
if (s)
|
|
93
|
+
out.mergeStateStatus = s;
|
|
94
|
+
// Merge queue — the SAME three-state discriminator sync/upsert.ts uses, for the same reason.
|
|
95
|
+
// `isInMergeQueue` is `Boolean!` in GitHub's schema, so a null here can only be the field
|
|
96
|
+
// `graphqlTolerant` nulled after a partial error: nothing was said, so nothing is written. A
|
|
97
|
+
// positive `false` IS a statement and must reach the DB, or an ejected PR keeps claiming a
|
|
98
|
+
// queue place forever. Both fields are set together so the caller cannot write half an answer.
|
|
99
|
+
if (node.isInMergeQueue != null) {
|
|
100
|
+
out.inMergeQueue = node.isInMergeQueue;
|
|
101
|
+
// Not queued ⇒ no entry, whatever the (absent) object says.
|
|
102
|
+
out.mergeQueueEntryState = node.isInMergeQueue
|
|
103
|
+
? mergeQueueEntryStateFrom(node.mergeQueueEntry?.state)
|
|
104
|
+
: null;
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Read liveness for up to one BATCH of node ids in ONE GraphQL call (1 point — see the measured
|
|
110
|
+
* table in the query's header). The caller does the batching; this function does not loop.
|
|
111
|
+
*
|
|
112
|
+
* Nodes GitHub answered with `null`, nodes of a non-PullRequest type, and nodes whose selection
|
|
113
|
+
* came back NULLED by a partial error are simply ABSENT from the result — the caller leaves those
|
|
114
|
+
* rows exactly as they were, which is strictly better than inventing a state for them.
|
|
115
|
+
*
|
|
116
|
+
* An exhausted budget returns EMPTY rather than throwing, for reactions.ts's reason one surface
|
|
117
|
+
* over: liveness is a freshening pass over rows that are already renderable, so failing it must
|
|
118
|
+
* never turn a working board into an error. The caller reports the pause instead.
|
|
119
|
+
*/
|
|
120
|
+
export async function fetchPrLivenessForNodes(token, nodeIds, opts) {
|
|
121
|
+
if (nodeIds.length === 0)
|
|
122
|
+
return [];
|
|
123
|
+
const { accountId, withMergeState } = opts;
|
|
124
|
+
// Already known-limited (this token's sync walk paused, or any other consumer classified a
|
|
125
|
+
// limited error): do not spend a request to be told so again. The cheap-consumer contract —
|
|
126
|
+
// `isLimited`, deliberately NOT `gateBudget`: nobody is holding a board open waiting out an
|
|
127
|
+
// hourly window, and the floor exists to leave headroom for the user's own clicks.
|
|
128
|
+
if (isLimited(accountId)) {
|
|
129
|
+
opts.onRateLimited?.(null);
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
let res;
|
|
133
|
+
try {
|
|
134
|
+
const gql = getGraphqlClientFor(token);
|
|
135
|
+
res = await graphqlTolerant(gql, PR_LIVENESS_NODES_QUERY, { ids: nodeIds, withMergeState }, opts.onPartial);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
const rl = isRateLimitError(err);
|
|
139
|
+
if (!rl.limited)
|
|
140
|
+
throw err;
|
|
141
|
+
// Tell the SHARED budget so the walks and the other cheap consumers back off too — a limit
|
|
142
|
+
// discovered by a liveness probe is a fact about the token, not about liveness.
|
|
143
|
+
noteLimited(accountId, rl.resumeAt);
|
|
144
|
+
opts.onRateLimited?.(rl.resumeAt);
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
if (res.rateLimit) {
|
|
148
|
+
noteBudget(accountId, {
|
|
149
|
+
remaining: res.rateLimit.remaining ?? null,
|
|
150
|
+
resetAt: parseResetAt(res.rateLimit.resetAt),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const out = [];
|
|
154
|
+
for (const node of res.nodes ?? []) {
|
|
155
|
+
const obs = foldLivenessNode(node);
|
|
156
|
+
if (obs)
|
|
157
|
+
out.push(obs);
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=pr-liveness.js.map
|