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,240 @@
|
|
|
1
|
+
// Pending-board liveness — the DATABASE half (target resolution + the narrow write-back).
|
|
2
|
+
//
|
|
3
|
+
// The GitHub half is github/pr-liveness.ts; the route that composes the two is
|
|
4
|
+
// `POST /api/attention/liveness` in api/routes/insights.ts. This module owns two things:
|
|
5
|
+
//
|
|
6
|
+
// 1. RESOLVING a client-supplied list of local PR ids to GitHub node ids, under the account
|
|
7
|
+
// AND the resolved workspace's membership. That resolve IS the isolation guarantee for a
|
|
8
|
+
// route whose ids arrive in a request body: an id belonging to another tenant, to another
|
|
9
|
+
// workspace, or to nothing at all simply does not come back, so it can never reach GitHub
|
|
10
|
+
// and can never be written to. (scripts/verify-isolation.ts exercises it directly.)
|
|
11
|
+
// 2. APPLYING what GitHub said, one narrow UPDATE per genuinely-moved row.
|
|
12
|
+
//
|
|
13
|
+
// It lives beside queries.ts rather than in it, on the db/repo-activity.ts precedent set earlier
|
|
14
|
+
// in this batch: queries.ts is already 9k lines and carries literal NUL bytes that make `rg`/
|
|
15
|
+
// `grep` under-report against it, so new folds go in a file a search can actually find.
|
|
16
|
+
import { and, eq, inArray } from 'drizzle-orm';
|
|
17
|
+
import { db, schema } from './client.js';
|
|
18
|
+
import { READY_MERGE_STATES } from './triage.js';
|
|
19
|
+
const { pullRequests } = schema;
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the caller's PR ids to liveness targets, scoped to the account AND the workspace's
|
|
22
|
+
* repo membership.
|
|
23
|
+
*
|
|
24
|
+
* ⚠ BOTH PREDICATES, AND NEITHER IS REDUNDANT. `accountId` is the tenancy boundary; `repoIds` is
|
|
25
|
+
* the scope the route echoes back, and without it a caller could name their OWN PRs from another
|
|
26
|
+
* workspace and have this route refresh (and bill GitHub quota for) rows outside the board they
|
|
27
|
+
* claimed to be looking at. `repoIds` comes from `resolveWorkspaceScope`, so `[]` — an empty
|
|
28
|
+
* workspace — correctly resolves nothing rather than widening to the account.
|
|
29
|
+
*/
|
|
30
|
+
export async function getPrLivenessTargets(accountId, repoIds, prIds) {
|
|
31
|
+
if (prIds.length === 0 || repoIds.length === 0)
|
|
32
|
+
return [];
|
|
33
|
+
const rows = await db
|
|
34
|
+
.select({
|
|
35
|
+
prId: pullRequests.id,
|
|
36
|
+
repoId: pullRequests.repoId,
|
|
37
|
+
githubNodeId: pullRequests.githubNodeId,
|
|
38
|
+
state: pullRequests.state,
|
|
39
|
+
isDraft: pullRequests.isDraft,
|
|
40
|
+
mergeable: pullRequests.mergeable,
|
|
41
|
+
mergeStateStatus: pullRequests.mergeStateStatus,
|
|
42
|
+
reviewDecision: pullRequests.reviewDecision,
|
|
43
|
+
inMergeQueue: pullRequests.inMergeQueue,
|
|
44
|
+
mergeQueueEntryState: pullRequests.mergeQueueEntryState,
|
|
45
|
+
updatedAt: pullRequests.updatedAt,
|
|
46
|
+
})
|
|
47
|
+
.from(pullRequests)
|
|
48
|
+
.where(and(eq(pullRequests.accountId, accountId), inArray(pullRequests.repoId, repoIds), inArray(pullRequests.id, prIds)))
|
|
49
|
+
.execute();
|
|
50
|
+
return rows.map((r) => ({
|
|
51
|
+
prId: r.prId,
|
|
52
|
+
repoId: r.repoId,
|
|
53
|
+
githubNodeId: r.githubNodeId,
|
|
54
|
+
state: r.state,
|
|
55
|
+
isDraft: r.isDraft,
|
|
56
|
+
mergeable: r.mergeable ?? null,
|
|
57
|
+
mergeStateStatus: r.mergeStateStatus ?? null,
|
|
58
|
+
reviewDecision: r.reviewDecision ?? null,
|
|
59
|
+
inMergeQueue: r.inMergeQueue ?? null,
|
|
60
|
+
mergeQueueEntryState: r.mergeQueueEntryState ?? null,
|
|
61
|
+
updatedAt: r.updatedAt,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* WHICH TARGETS EARN THE EXPENSIVE SECOND PASS, in priority order.
|
|
66
|
+
*
|
|
67
|
+
* The mergeability selection costs ~5s for 25 PRs and 502s the gateway at 50 (see the measured
|
|
68
|
+
* table in PR_LIVENESS_NODES_QUERY), so only a bounded subset can ever have it. The ordering is
|
|
69
|
+
* the whole design decision:
|
|
70
|
+
*
|
|
71
|
+
* 1. PRs whose STORED merge state already puts them on the board as a FORWARD card
|
|
72
|
+
* (READY_MERGE_STATES ∪ {'behind'}). These are the rows offering a Merge / Update-branch
|
|
73
|
+
* button, and a stale `mergeStateStatus` there is a button that 405s — the most expensive
|
|
74
|
+
* kind of wrong this board can be. The board caps forward cards at 15 per kind, so this
|
|
75
|
+
* group is at most 30 PRs and usually far fewer.
|
|
76
|
+
* 2. Everything else still open, most-recently-updated first — the rotation that lets a
|
|
77
|
+
* `blocked → clean` flip (somebody else's approval satisfying branch protection) be noticed
|
|
78
|
+
* at all. It is best-effort by construction: on a board with more than ~25 PRs the tail of
|
|
79
|
+
* this list simply waits for the next adaptive walk, which is exactly what it did before.
|
|
80
|
+
*
|
|
81
|
+
* ⚠ `'dirty'` IS DELIBERATELY NOT IN GROUP 1, even though the `conflicts` card kind is built off
|
|
82
|
+
* exactly this column. Group 1 exists because a stale merge state there is a BUTTON THAT 405s; a
|
|
83
|
+
* conflicts card carries no button, so a stale one costs the reader a wasted row, not a refused
|
|
84
|
+
* merge. It rides the group-2 rotation with everything else, and the SPA's own ranking (which
|
|
85
|
+
* decides which ids reach this function at all) puts conflicts cards first — so the question gets
|
|
86
|
+
* asked; how often it is answered stays best-effort, by design.
|
|
87
|
+
*
|
|
88
|
+
* Already-merged/closed targets are excluded outright: their card is leaving on this very refetch
|
|
89
|
+
* and GitHub will not be recomputing mergeability for them.
|
|
90
|
+
*/
|
|
91
|
+
export function rankForMergeStatePass(targets, cap) {
|
|
92
|
+
const forward = (t) => t.mergeStateStatus != null &&
|
|
93
|
+
(t.mergeStateStatus === 'behind' || READY_MERGE_STATES.has(t.mergeStateStatus));
|
|
94
|
+
return targets
|
|
95
|
+
.filter((t) => t.state === 'open')
|
|
96
|
+
.sort((a, b) => Number(forward(b)) - Number(forward(a)) ||
|
|
97
|
+
b.updatedAt.getTime() - a.updatedAt.getTime() ||
|
|
98
|
+
a.prId - b.prId)
|
|
99
|
+
.slice(0, cap);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Write back ONE observation, and only the columns GitHub positively spoke about.
|
|
103
|
+
*
|
|
104
|
+
* ⚠ A COLUMN MAY BE CLEARED ONLY ON A POSITIVE STATEMENT FROM GITHUB. `graphqlTolerant` returns
|
|
105
|
+
* partial data with forbidden fields NULLED, so an absent `mergeable` is indistinguishable from
|
|
106
|
+
* "GitHub says unknown" at the JSON level. `foldLivenessNode` has already collapsed that: it
|
|
107
|
+
* refuses the whole node unless `state` arrived (proving the fragment landed), and it leaves
|
|
108
|
+
* `mergeable`/`mergeStateStatus` UNSET rather than null when they were not asked for. So here,
|
|
109
|
+
* `undefined` means omit the key and anything else is written — including `reviewDecision: null`,
|
|
110
|
+
* which on this path is GitHub saying "this repo requires no review", the same value the fat sync
|
|
111
|
+
* walk stores from the same field, and including `inMergeQueue: false`, which is what a landing or
|
|
112
|
+
* an ejection from the merge queue looks like.
|
|
113
|
+
*
|
|
114
|
+
* ⚠ IT DOES NOT TOUCH `mergedById`. That is the maintainer-inference input and it is not in this
|
|
115
|
+
* selection; inventing one (or clearing it) from a probe that never asked would corrupt a
|
|
116
|
+
* different feature. The next `persistPr` fills it in.
|
|
117
|
+
*
|
|
118
|
+
* Returns null when nothing in the row actually moved — the common case, and what lets the route
|
|
119
|
+
* report an honest `changed` count instead of inviting a refetch on every tick.
|
|
120
|
+
*/
|
|
121
|
+
export async function applyPrLiveness(accountId, target, obs) {
|
|
122
|
+
const set = {};
|
|
123
|
+
// `dirty` = the row needs writing at all. `movedOnBoard` = a card can change because of it.
|
|
124
|
+
// Two flags, deliberately: see PrLivenessDiff.movedOnBoard.
|
|
125
|
+
let dirty = false;
|
|
126
|
+
let movedOnBoard = false;
|
|
127
|
+
const change = (onBoard) => {
|
|
128
|
+
dirty = true;
|
|
129
|
+
if (onBoard)
|
|
130
|
+
movedOnBoard = true;
|
|
131
|
+
};
|
|
132
|
+
if (obs.state !== target.state) {
|
|
133
|
+
set.state = obs.state;
|
|
134
|
+
change(true);
|
|
135
|
+
}
|
|
136
|
+
if (obs.isDraft != null && obs.isDraft !== target.isDraft) {
|
|
137
|
+
set.isDraft = obs.isDraft;
|
|
138
|
+
change(true);
|
|
139
|
+
}
|
|
140
|
+
// Timestamps that only ever ARRIVE (GitHub never un-merges), so they carry no comparison and
|
|
141
|
+
// no movement of their own — they ride along so a card leaving the board leaves with a real
|
|
142
|
+
// merged/closed clock instead of a null the next walk has to backfill.
|
|
143
|
+
if (obs.mergedAt != null && target.state !== 'merged') {
|
|
144
|
+
set.mergedAt = new Date(obs.mergedAt);
|
|
145
|
+
dirty = true;
|
|
146
|
+
}
|
|
147
|
+
if (obs.closedAt != null && target.state === 'open') {
|
|
148
|
+
set.closedAt = new Date(obs.closedAt);
|
|
149
|
+
dirty = true;
|
|
150
|
+
}
|
|
151
|
+
if (obs.updatedAt != null) {
|
|
152
|
+
const next = new Date(obs.updatedAt);
|
|
153
|
+
if (next.getTime() !== target.updatedAt.getTime()) {
|
|
154
|
+
set.updatedAt = next;
|
|
155
|
+
change(true);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Written whenever it DIFFERS, null included — on this path a null is GitHub saying "this repo
|
|
159
|
+
// requires no review", the same value persistPr stores from the same field, and it reaches us
|
|
160
|
+
// only because `state` proved the fragment landed.
|
|
161
|
+
//
|
|
162
|
+
// ⚠ BOARD MOVEMENT, since the Pending card renders review standing. It was `change(false)` while
|
|
163
|
+
// no card showed the decision, on the argument that 92% of open PRs carry a null one restated
|
|
164
|
+
// every tick. That argument was always about the WRONG branch: the test above is a DIFF, so a
|
|
165
|
+
// restatement — null → null included — never gets here at all. What actually reaches this line is
|
|
166
|
+
// the approval that just landed, which is the single most useful thing the board can learn
|
|
167
|
+
// between walks and the reason someone leaves the tab open.
|
|
168
|
+
if (obs.reviewDecision !== target.reviewDecision) {
|
|
169
|
+
set.reviewDecision = obs.reviewDecision;
|
|
170
|
+
change(true);
|
|
171
|
+
}
|
|
172
|
+
// ── Merge queue — membership and entry state, written as ONE fact ─────────────────────────
|
|
173
|
+
//
|
|
174
|
+
// `undefined` = GitHub said nothing (see PrLivenessObservation): omit both keys, learn nothing.
|
|
175
|
+
// An observed value is written whenever it differs, `false` INCLUDED — a false is GitHub saying
|
|
176
|
+
// the PR is not in the queue, which is what a landing or an EJECTION looks like, and refusing to
|
|
177
|
+
// write it would leave a card claiming a queue place the PR no longer holds.
|
|
178
|
+
//
|
|
179
|
+
// Board movement both ways: the card shows queue membership by default, and `unmergeable` is the
|
|
180
|
+
// state worth interrupting someone for — GitHub is dropping the PR out of the queue.
|
|
181
|
+
//
|
|
182
|
+
// ⚠ THE ENTRY STATE IS WRITTEN WHENEVER MEMBERSHIP WAS OBSERVED, even to null. Not queued ⇒ no
|
|
183
|
+
// entry; queued in a state this codebase does not model ⇒ also null (see mergeQueueEntryStateFrom
|
|
184
|
+
// for why an unmodelled state is not stored raw). That is exactly what persistPr does with the
|
|
185
|
+
// same GitHub answer, and the two writers must agree or one column flip-flops between the walk
|
|
186
|
+
// and this sweep every minute. The unknown-never-demotes-known guard above does NOT apply here:
|
|
187
|
+
// it exists because GitHub answers `mergeable` with UNKNOWN while it computes one, and there is
|
|
188
|
+
// no such in-progress value on the queue fields — they are stored state.
|
|
189
|
+
if (obs.inMergeQueue !== undefined) {
|
|
190
|
+
const nextState = obs.mergeQueueEntryState ?? null;
|
|
191
|
+
if (obs.inMergeQueue !== target.inMergeQueue) {
|
|
192
|
+
set.inMergeQueue = obs.inMergeQueue;
|
|
193
|
+
change(true);
|
|
194
|
+
}
|
|
195
|
+
if (nextState !== target.mergeQueueEntryState) {
|
|
196
|
+
set.mergeQueueEntryState = nextState;
|
|
197
|
+
change(true);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// ── The two computed fields, and the one rule that keeps this sweep CONVERGENT ────────────
|
|
201
|
+
//
|
|
202
|
+
// `undefined` = the cheap pass never asked. Never conflated with GitHub's own 'unknown'.
|
|
203
|
+
//
|
|
204
|
+
// ⚠ AN OBSERVED 'unknown' DOES NOT OVERWRITE A KNOWN STORED VALUE, and this is a DELIBERATE
|
|
205
|
+
// divergence from `persistPr`, which writes it. GitHub does not store mergeability: asking for
|
|
206
|
+
// it STARTS a background trial merge, and the answer while that runs is UNKNOWN. `persistPr`
|
|
207
|
+
// runs on the adaptive cadence (2-15 min) so it rarely catches that window; this sweep runs
|
|
208
|
+
// every 60s and would sit in it — downgrading `clean` to `unknown`, taking the card's Merge
|
|
209
|
+
// button away, then restoring it a minute later. Every one of those transitions is a `changed`,
|
|
210
|
+
// so the board would refetch on a fixed timer forever with a GitHub call in front of it, which
|
|
211
|
+
// is precisely the churn `changed` exists to prevent.
|
|
212
|
+
//
|
|
213
|
+
// Cost of the guard: a PR whose merge state genuinely became unknowable (a force-push mid-
|
|
214
|
+
// computation) keeps its previous state until the next full walk. That is the same staleness
|
|
215
|
+
// every card had before this route existed, on a narrow slice of PRs, for a couple of minutes.
|
|
216
|
+
const authoritative = (next, stored, unknown) => next !== undefined && next !== stored && !(next === unknown && stored != null);
|
|
217
|
+
if (authoritative(obs.mergeStateStatus, target.mergeStateStatus, 'unknown')) {
|
|
218
|
+
set.mergeStateStatus = obs.mergeStateStatus;
|
|
219
|
+
change(true);
|
|
220
|
+
}
|
|
221
|
+
if (authoritative(obs.mergeable, target.mergeable, 'unknown')) {
|
|
222
|
+
set.mergeable = obs.mergeable;
|
|
223
|
+
// The board's merge GATE reads it (`card.mergeable ?? 'unknown'` → mergeVerdict), so a
|
|
224
|
+
// conflict appearing or clearing really can change a button.
|
|
225
|
+
change(true);
|
|
226
|
+
}
|
|
227
|
+
if (!dirty)
|
|
228
|
+
return null;
|
|
229
|
+
await db
|
|
230
|
+
.update(pullRequests)
|
|
231
|
+
.set(set)
|
|
232
|
+
.where(and(eq(pullRequests.id, target.prId), eq(pullRequests.accountId, accountId)))
|
|
233
|
+
.execute();
|
|
234
|
+
return {
|
|
235
|
+
prId: target.prId,
|
|
236
|
+
movedOnBoard,
|
|
237
|
+
leftOpenSet: target.state === 'open' && obs.state !== 'open',
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=pr-liveness.js.map
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// Query layer for "@you was mentioned on this PR" (CORE, free, no AI, no GitHub quota).
|
|
2
|
+
//
|
|
3
|
+
// WHAT THIS ANSWERS. My Turn's `mention` type (S6 of the ball rule, `getMyTurn`): a PERSON typed
|
|
4
|
+
// your login on an open PR, and you have not acted on that PR since. A mention is a summons EVEN
|
|
5
|
+
// IN A REPO YOU ONLY READ — that is the whole point of it, and why it is its own card type rather
|
|
6
|
+
// than a property of the repo. The card clears when you act AFTER the mention, so each row carries
|
|
7
|
+
// the moment of the NEWEST qualifying mention (`mentioned_at`) and who made it.
|
|
8
|
+
//
|
|
9
|
+
// TWO JOBS, and nothing else:
|
|
10
|
+
// 1. MATCH — the pure `@login` word-boundary predicate (exported and unit-pinned)
|
|
11
|
+
// 2. DERIVE — the full "which of this account's PRs mention the viewer, newest when and by whom"
|
|
12
|
+
// set (the scanner's worklist; see sync/mention-scan.ts), and the diff that makes
|
|
13
|
+
// the stored rows equal it. The READ is a join inside `getMyTurn`.
|
|
14
|
+
//
|
|
15
|
+
// WHO CAN SUMMON YOU. A mention by the viewer themself is not a summons, and neither is one by
|
|
16
|
+
// automation — the GLOBAL automation set (`globalAutomationUserIds`), the SAME set the ball rule
|
|
17
|
+
// resolves bot-ness with, so a mention this scanner stamps is always one `getMyTurn` would honour.
|
|
18
|
+
//
|
|
19
|
+
// WHY THE DERIVE IS A FULL RE-SCAN AND NOT AN INCREMENTAL CURSOR. A cursor over the three comment
|
|
20
|
+
// tables would have to be right about four different ways the corpus changes, and each wrong
|
|
21
|
+
// answer is silent: a 90-day BACKFILL inserts rows whose `created_at` predates any time-based
|
|
22
|
+
// watermark; a body EDIT changes neither a row's id nor its created_at; a deleted comment must
|
|
23
|
+
// REMOVE a mention; and Postgres hands out sequence values out of commit order, so an id-based
|
|
24
|
+
// watermark can skip a row permanently. Re-deriving the whole set and diffing it against what is
|
|
25
|
+
// stored is correct under all four with no state to keep, and it is affordable because the
|
|
26
|
+
// expensive half is bounded by the MATCHES, not by the corpus: on this repo's own dev database
|
|
27
|
+
// (65k comment/review bodies, 8.5k PRs) the three scans below run in ~0.19s and return 12 rows.
|
|
28
|
+
// The cost that does scale is the LIKE scan itself, which is why this is a background worker on a
|
|
29
|
+
// multi-minute cron and NEVER a per-request read — `getWorkspaceInsights` runs on every Feed
|
|
30
|
+
// landing.
|
|
31
|
+
import { and, desc, eq, inArray, isNotNull, sql } from 'drizzle-orm';
|
|
32
|
+
import { db, schema } from './client.js';
|
|
33
|
+
import { globalAutomationUserIds } from './automation-ids.js';
|
|
34
|
+
const { prMentions, prComments, pullRequests, reviewComments, reviews, users } = schema;
|
|
35
|
+
// A cap on how many MATCHING rows one account's scan will look at, per source table. Not a
|
|
36
|
+
// correctness bound — it is a guard against a pathological corpus (a bot that @-mentions the
|
|
37
|
+
// account owner on every PR) turning one tick into an unbounded allocation. Each scan orders
|
|
38
|
+
// NEWEST FIRST before the cap, so if it ever bites it drops the OLDEST matches — the ones least
|
|
39
|
+
// likely to be a PR's newest mention, which is the only one a row keeps.
|
|
40
|
+
const MENTION_SCAN_CAP = 20_000;
|
|
41
|
+
// Chunk size for the `IN (…)` lookups. SQLite's default bound-variable limit is 999.
|
|
42
|
+
const ID_CHUNK = 500;
|
|
43
|
+
/**
|
|
44
|
+
* THE MATCH RULE. `@login` as a whole word, case-insensitive.
|
|
45
|
+
*
|
|
46
|
+
* The two failure directions this exists to prevent, both silent:
|
|
47
|
+
* • `@alex` must NOT match "@alexwakeman" and `@alexwakeman` must not match "@alex" — the
|
|
48
|
+
* trailing class rejects a login that merely STARTS with ours (GitHub logins are
|
|
49
|
+
* `[A-Za-z0-9-]`; `_` is in the class as well because a reader cannot tell "@alex_wakeman"
|
|
50
|
+
* apart from a login we do not know, and under-notifying is the safe direction here).
|
|
51
|
+
* • "bob@alexwakeman.com" and "docs/@alexwakeman/notes.md" must not count — the leading class
|
|
52
|
+
* rejects an `@` glued to a word character, a path separator or a dot, which is what an
|
|
53
|
+
* email local part and a path segment look like.
|
|
54
|
+
*
|
|
55
|
+
* Everything else — start of string, whitespace, `(`, `[`, `>`, a backtick — is a real mention
|
|
56
|
+
* position and matches. (`deriveMentionedPrs` drops QUOTED lines before it asks: see
|
|
57
|
+
* `withoutQuotedLines`.)
|
|
58
|
+
*
|
|
59
|
+
* Deliberately NOT a `\b` boundary: `\b` is symmetric about word characters and `@` is not a word
|
|
60
|
+
* character, so `\b@login\b` would happily match the email case above.
|
|
61
|
+
*/
|
|
62
|
+
export function mentionPattern(login) {
|
|
63
|
+
// The login is DATA (it comes from `accounts.github_login`, ultimately from GitHub). Escaping
|
|
64
|
+
// it keeps a hyphen or a dot from being read as regex syntax.
|
|
65
|
+
const escaped = login.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
66
|
+
return new RegExp(`(?<![A-Za-z0-9_@./-])@${escaped}(?![A-Za-z0-9_-])`, 'i');
|
|
67
|
+
}
|
|
68
|
+
/** The body without its markdown blockquote lines — what its author TYPED. A quote-reply repeats
|
|
69
|
+
* somebody else's words: "> @you can you look?" is not the quoter summoning you, and counting it
|
|
70
|
+
* would move the mention clock past an action you already took and credit the wrong person. */
|
|
71
|
+
export function withoutQuotedLines(body) {
|
|
72
|
+
return body.replace(/^[ \t]*>.*$/gm, '');
|
|
73
|
+
}
|
|
74
|
+
/** True when `text` @-mentions `login` as a whole word. Empty login never matches. */
|
|
75
|
+
export function mentionsLogin(text, login) {
|
|
76
|
+
if (!text || !login)
|
|
77
|
+
return false;
|
|
78
|
+
return mentionPattern(login).test(text);
|
|
79
|
+
}
|
|
80
|
+
// A `%@login%` LIKE pattern with the LIKE metacharacters escaped, paired with `ESCAPE '\'` below
|
|
81
|
+
// (the db/search.ts idiom). This is a PREFILTER ONLY — it is substring, not word-boundary, so
|
|
82
|
+
// `mentionsLogin` above is the authority on every row it returns.
|
|
83
|
+
function likePattern(login) {
|
|
84
|
+
return `%@${login.toLowerCase().replace(/([\\%_])/g, '\\$1')}%`;
|
|
85
|
+
}
|
|
86
|
+
function likeCol(col, pattern) {
|
|
87
|
+
return sql `lower(${col}) like ${pattern} escape '\\'`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* THE FULL DERIVATION: every PR of this account on which a PERSON other than the viewer
|
|
91
|
+
* @-mentions `login`, with that PR's NEWEST such mention.
|
|
92
|
+
*
|
|
93
|
+
* All three body-bearing tables, because a mention is a mention wherever it was typed: an
|
|
94
|
+
* issue-level PR comment, a review body, an inline review-thread comment. PR TITLES AND
|
|
95
|
+
* DESCRIPTIONS ARE DELIBERATELY OUT — descriptions are not persisted under lean storage
|
|
96
|
+
* (docs/BACKEND.md), so including them would make the answer depend on whether PERSIST_BODIES
|
|
97
|
+
* happened to be on, which is the one thing a derived fact must never do. Comment and review
|
|
98
|
+
* bodies are ALWAYS persisted, so this needs no GitHub fetch and no hydration.
|
|
99
|
+
*
|
|
100
|
+
* ⚠ A SKIPPED ROW IS SKIPPED BEFORE "NEWEST" IS DECIDED. A bot that echoes your login after a
|
|
101
|
+
* colleague's mention must not become the PR's mention (its time would move the card's clock, and
|
|
102
|
+
* the next bot echo would re-summon you after you acted); the colleague's mention stays the newest.
|
|
103
|
+
*/
|
|
104
|
+
export async function deriveMentionedPrs(accountId, login) {
|
|
105
|
+
if (!login)
|
|
106
|
+
return [];
|
|
107
|
+
const pat = likePattern(login);
|
|
108
|
+
// Resolved once per scan: who "you" are (a self-mention is not a summons) and who is automation.
|
|
109
|
+
const [viewerRows, bots] = await Promise.all([
|
|
110
|
+
db
|
|
111
|
+
.select({ id: users.id })
|
|
112
|
+
.from(users)
|
|
113
|
+
.where(sql `lower(${users.githubLogin}) = ${login.toLowerCase()}`)
|
|
114
|
+
.limit(1)
|
|
115
|
+
.execute(),
|
|
116
|
+
globalAutomationUserIds(),
|
|
117
|
+
]);
|
|
118
|
+
const viewerUserId = viewerRows[0]?.id ?? null;
|
|
119
|
+
// Three near-identical queries rather than one parameterised helper: drizzle's column types are
|
|
120
|
+
// per-table, and the generic version only type-checks by widening the columns to `any` — which
|
|
121
|
+
// is exactly the seam that would let a wrong `prId` through unnoticed.
|
|
122
|
+
const [pc, rc, rv] = await Promise.all([
|
|
123
|
+
db
|
|
124
|
+
.select({
|
|
125
|
+
prId: prComments.prId,
|
|
126
|
+
repoId: pullRequests.repoId,
|
|
127
|
+
body: prComments.body,
|
|
128
|
+
authorId: prComments.authorId,
|
|
129
|
+
at: prComments.createdAt,
|
|
130
|
+
})
|
|
131
|
+
.from(prComments)
|
|
132
|
+
.innerJoin(pullRequests, eq(pullRequests.id, prComments.prId))
|
|
133
|
+
.where(and(eq(pullRequests.accountId, accountId), isNotNull(prComments.body), likeCol(prComments.body, pat)))
|
|
134
|
+
.orderBy(desc(prComments.createdAt))
|
|
135
|
+
.limit(MENTION_SCAN_CAP)
|
|
136
|
+
.execute(),
|
|
137
|
+
db
|
|
138
|
+
.select({
|
|
139
|
+
prId: reviewComments.prId,
|
|
140
|
+
repoId: pullRequests.repoId,
|
|
141
|
+
body: reviewComments.body,
|
|
142
|
+
authorId: reviewComments.authorId,
|
|
143
|
+
at: reviewComments.createdAt,
|
|
144
|
+
})
|
|
145
|
+
.from(reviewComments)
|
|
146
|
+
.innerJoin(pullRequests, eq(pullRequests.id, reviewComments.prId))
|
|
147
|
+
.where(and(eq(pullRequests.accountId, accountId), isNotNull(reviewComments.body), likeCol(reviewComments.body, pat)))
|
|
148
|
+
.orderBy(desc(reviewComments.createdAt))
|
|
149
|
+
.limit(MENTION_SCAN_CAP)
|
|
150
|
+
.execute(),
|
|
151
|
+
db
|
|
152
|
+
.select({
|
|
153
|
+
prId: reviews.prId,
|
|
154
|
+
repoId: pullRequests.repoId,
|
|
155
|
+
body: reviews.body,
|
|
156
|
+
authorId: reviews.authorId,
|
|
157
|
+
at: reviews.submittedAt,
|
|
158
|
+
})
|
|
159
|
+
.from(reviews)
|
|
160
|
+
.innerJoin(pullRequests, eq(pullRequests.id, reviews.prId))
|
|
161
|
+
.where(and(eq(pullRequests.accountId, accountId), isNotNull(reviews.body), likeCol(reviews.body, pat)))
|
|
162
|
+
.orderBy(desc(reviews.submittedAt))
|
|
163
|
+
.limit(MENTION_SCAN_CAP)
|
|
164
|
+
.execute(),
|
|
165
|
+
]);
|
|
166
|
+
const out = new Map();
|
|
167
|
+
for (const rows of [pc, rc, rv]) {
|
|
168
|
+
for (const r of rows) {
|
|
169
|
+
// Nobody we can name (a deleted account), yourself, or automation: not a summons.
|
|
170
|
+
if (r.authorId == null || r.authorId === viewerUserId || bots.has(r.authorId))
|
|
171
|
+
continue;
|
|
172
|
+
// ⚠ THE SQL IS A PREFILTER, THE REGEX IS THE ANSWER. `lower(body) LIKE '%@alex%'` matches
|
|
173
|
+
// "@alexwakeman" and "bob@alex.com" too; dropping this line is how "@alex" starts claiming
|
|
174
|
+
// every PR that mentions a colleague with a longer login. And only TYPED lines count — a
|
|
175
|
+
// quoted mention skipped here, before "newest" is decided, cannot restamp the clock.
|
|
176
|
+
if (r.body == null || !mentionsLogin(withoutQuotedLines(r.body), login))
|
|
177
|
+
continue;
|
|
178
|
+
const prev = out.get(r.prId);
|
|
179
|
+
if (prev != null && prev.mentionedAt.getTime() >= r.at.getTime())
|
|
180
|
+
continue;
|
|
181
|
+
out.set(r.prId, {
|
|
182
|
+
prId: r.prId,
|
|
183
|
+
repoId: r.repoId,
|
|
184
|
+
mentionedAt: r.at,
|
|
185
|
+
mentionedById: r.authorId,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return [...out.values()];
|
|
190
|
+
}
|
|
191
|
+
/** What is currently stored for this account: each PR, the login it was derived under, and the
|
|
192
|
+
* mention clock last stamped on it (NULL until the first scan after migration 0068). */
|
|
193
|
+
export async function listStoredMentions(accountId) {
|
|
194
|
+
return db
|
|
195
|
+
.select({
|
|
196
|
+
id: prMentions.id,
|
|
197
|
+
prId: prMentions.prId,
|
|
198
|
+
login: prMentions.login,
|
|
199
|
+
mentionedAt: prMentions.mentionedAt,
|
|
200
|
+
mentionedByUserId: prMentions.mentionedByUserId,
|
|
201
|
+
})
|
|
202
|
+
.from(prMentions)
|
|
203
|
+
.where(eq(prMentions.accountId, accountId))
|
|
204
|
+
.execute();
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Make the stored set EQUAL the derived set for one account — the scanner's only writer.
|
|
208
|
+
*
|
|
209
|
+
* A diff rather than an upsert sweep, because the delete half is load-bearing: a mention edited
|
|
210
|
+
* out of a comment, a deleted comment, a PR that lost its mention when a review was dismissed,
|
|
211
|
+
* and every row derived under a login this account no longer has must all STOP summoning you.
|
|
212
|
+
* An insert-only writer would make the mention card a ratchet that only ever widens.
|
|
213
|
+
*
|
|
214
|
+
* ⚠ AND THE KEPT HALF IS RESTAMPED. A row is "unchanged" only when its login, its mention time and
|
|
215
|
+
* its author all match what was derived; a NEWER mention on the same PR must move `mentioned_at`,
|
|
216
|
+
* or you would act, the card would clear, and the colleague's second "@you?" would never return
|
|
217
|
+
* it.
|
|
218
|
+
*
|
|
219
|
+
* `login` is stored lowercased so the reader's equality test does not depend on how GitHub
|
|
220
|
+
* spelled the login on the day the row was written.
|
|
221
|
+
*/
|
|
222
|
+
export async function syncAccountMentions(accountId, login, derived) {
|
|
223
|
+
const canonical = login.toLowerCase();
|
|
224
|
+
const stored = await listStoredMentions(accountId);
|
|
225
|
+
const storedByPr = new Map(stored.map((r) => [r.prId, r]));
|
|
226
|
+
const wanted = new Map(derived.map((d) => [d.prId, d]));
|
|
227
|
+
// Stale = no longer derived, OR derived under a different login (an account rename). Both are
|
|
228
|
+
// "this row no longer states a true fact", so both leave by the same path.
|
|
229
|
+
const staleIds = stored
|
|
230
|
+
.filter((r) => !wanted.has(r.prId) || r.login !== canonical)
|
|
231
|
+
.map((r) => r.id);
|
|
232
|
+
for (let i = 0; i < staleIds.length; i += ID_CHUNK) {
|
|
233
|
+
await db
|
|
234
|
+
.delete(prMentions)
|
|
235
|
+
.where(inArray(prMentions.id, staleIds.slice(i, i + ID_CHUNK)))
|
|
236
|
+
.execute();
|
|
237
|
+
}
|
|
238
|
+
let added = 0;
|
|
239
|
+
let updated = 0;
|
|
240
|
+
for (const d of derived) {
|
|
241
|
+
const existing = storedByPr.get(d.prId);
|
|
242
|
+
const kept = existing != null && existing.login === canonical;
|
|
243
|
+
if (kept &&
|
|
244
|
+
existing.mentionedAt?.getTime() === d.mentionedAt.getTime() &&
|
|
245
|
+
existing.mentionedByUserId === d.mentionedById) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
// An UPSERT on the table's one unique, `(account_id, pr_id)` — the `prm_account_pr` target
|
|
249
|
+
// every writer of this table uses — so a concurrent (or retried) tick cannot turn a duplicate
|
|
250
|
+
// into a thrown scan, and a kept row is restamped in the same statement.
|
|
251
|
+
const res = await db
|
|
252
|
+
.insert(prMentions)
|
|
253
|
+
.values({
|
|
254
|
+
accountId,
|
|
255
|
+
repoId: d.repoId,
|
|
256
|
+
prId: d.prId,
|
|
257
|
+
login: canonical,
|
|
258
|
+
mentionedAt: d.mentionedAt,
|
|
259
|
+
mentionedByUserId: d.mentionedById,
|
|
260
|
+
})
|
|
261
|
+
.onConflictDoUpdate({
|
|
262
|
+
target: [prMentions.accountId, prMentions.prId],
|
|
263
|
+
set: {
|
|
264
|
+
login: canonical,
|
|
265
|
+
repoId: d.repoId,
|
|
266
|
+
mentionedAt: d.mentionedAt,
|
|
267
|
+
mentionedByUserId: d.mentionedById,
|
|
268
|
+
},
|
|
269
|
+
})
|
|
270
|
+
.returning({ id: prMentions.id })
|
|
271
|
+
.execute();
|
|
272
|
+
if (kept)
|
|
273
|
+
updated += res.length;
|
|
274
|
+
else
|
|
275
|
+
added += res.length;
|
|
276
|
+
}
|
|
277
|
+
return { added, updated, removed: staleIds.length };
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=pr-mentions.js.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// A pull request's GitHub MERGE-QUEUE membership, stamped from a live observation.
|
|
2
|
+
//
|
|
3
|
+
// ⚠ NOT `db/merge-queue.ts`. That module is Limn's OWN per-repo landing order for armed intents
|
|
4
|
+
// (`queued_local`); this one is GitHub's native merge queue, as synced onto `pull_requests`
|
|
5
|
+
// (`in_merge_queue` + `merge_queue_entry_state`). Two queues, two files, never one name.
|
|
6
|
+
//
|
|
7
|
+
// WHY A STAMP AT ALL. A GitHub write is not done when GitHub 200s: the SPA re-reads from the local
|
|
8
|
+
// DB, so an enqueue nobody stamped is invisible until the next adaptive walk — up to fifteen
|
|
9
|
+
// minutes on a cold repo — and the Pending board is forbidden from fetching to find out. So EVERY
|
|
10
|
+
// path that holds a positive answer about membership writes it here:
|
|
11
|
+
//
|
|
12
|
+
// - POST / DELETE /api/prs/:id/merge-queue (the two verbs, and the DELETE's own probe)
|
|
13
|
+
// - GET /api/prs/:id/merge-options, when its queue probe answered
|
|
14
|
+
// - the arm route's AlreadyQueued 409 (it just proved the PR is queued)
|
|
15
|
+
// - the disarm route's dequeue of a watcher-made entry
|
|
16
|
+
// - the auto-merge runner's enqueue, and its per-tick live probe
|
|
17
|
+
//
|
|
18
|
+
// ⚠ POSITIVE ANSWERS ONLY — the partial-response rule ("a column may be CLEARED only on a positive
|
|
19
|
+
// statement from GitHub"). Callers pass a NON-NULL `fetchMergeQueueState` result (non-tolerant
|
|
20
|
+
// GraphQL with `isInMergeQueue: Boolean!`, so a result is a statement) or the outcome of a mutation
|
|
21
|
+
// GitHub accepted. Never the `.catch(() => null)` null of a best-effort probe: that is "we never
|
|
22
|
+
// received it", and writing `false` from it would tell the board a queued PR is not queued.
|
|
23
|
+
//
|
|
24
|
+
// ⚠ THE SYNC'S OWN THREE-STATE FOLD (sync/upsert.ts) DOES NOT COME THROUGH HERE, and must not: a
|
|
25
|
+
// walk that did not carry the selection OMITS the keys, and an unconditional setter cannot say
|
|
26
|
+
// "omit". This helper is correct precisely because every caller holds an answer.
|
|
27
|
+
//
|
|
28
|
+
// ⚠ A STAMP THAT CHANGES THE STORED VALUE RAISES THE CHANGE SIGNAL (`notePrChanged`). The board's
|
|
29
|
+
// liveness sweep compares GitHub against the ROW, so once a route has written the new value the
|
|
30
|
+
// sweep sees no difference and never repaints the card; without the signal a stamp made by the
|
|
31
|
+
// runner or by a GET would silence the one mechanism that used to catch it. A write that changed
|
|
32
|
+
// nothing raises nothing — a signal costs the SPA a cascade of refetches.
|
|
33
|
+
import { and, eq } from 'drizzle-orm';
|
|
34
|
+
import { db, schema } from './client.js';
|
|
35
|
+
import { mergeQueueEntryStateFrom } from '../sync/upsert.js';
|
|
36
|
+
import { notePrChanged } from '../sync/pr-change-signal.js';
|
|
37
|
+
/**
|
|
38
|
+
* Write what GitHub just said about this PR's merge-queue membership. Account-scoped (a foreign
|
|
39
|
+
* id matches no row and writes nothing). Returns true when the stored value CHANGED.
|
|
40
|
+
*
|
|
41
|
+
* `rawEntryState` is GitHub's `MergeQueueEntryState` as it arrived (`QUEUED`, `AWAITING_CHECKS`,
|
|
42
|
+
* …), normalised by the ONE normaliser the sync uses. Out of the queue means no entry — a stale
|
|
43
|
+
* `queued` is never carried past a dequeue. In the queue with no modelled state stores "queued,
|
|
44
|
+
* state unknown", never "not queued".
|
|
45
|
+
*/
|
|
46
|
+
export async function stampPrMergeQueueState(prId, accountId, inQueue, rawEntryState) {
|
|
47
|
+
const { pullRequests } = schema;
|
|
48
|
+
const next = {
|
|
49
|
+
inMergeQueue: inQueue,
|
|
50
|
+
mergeQueueEntryState: inQueue ? mergeQueueEntryStateFrom(rawEntryState) : null,
|
|
51
|
+
};
|
|
52
|
+
const owned = and(eq(pullRequests.id, prId), eq(pullRequests.accountId, accountId));
|
|
53
|
+
const rows = await db
|
|
54
|
+
.select({
|
|
55
|
+
repoId: pullRequests.repoId,
|
|
56
|
+
inMergeQueue: pullRequests.inMergeQueue,
|
|
57
|
+
mergeQueueEntryState: pullRequests.mergeQueueEntryState,
|
|
58
|
+
})
|
|
59
|
+
.from(pullRequests)
|
|
60
|
+
.where(owned)
|
|
61
|
+
.execute();
|
|
62
|
+
const row = rows[0];
|
|
63
|
+
if (row == null)
|
|
64
|
+
return false;
|
|
65
|
+
if (row.inMergeQueue === next.inMergeQueue &&
|
|
66
|
+
(row.mergeQueueEntryState ?? null) === next.mergeQueueEntryState) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
await db.update(pullRequests).set(next).where(owned).execute();
|
|
70
|
+
notePrChanged(accountId, row.repoId);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The same write, NEVER FATAL — what every ROUTE calls.
|
|
75
|
+
*
|
|
76
|
+
* ⚠ A STAMP IS A LOCAL COPY OF SOMETHING GITHUB ALREADY SAID, so failing to write it is a stale
|
|
77
|
+
* screen, never a failed request. Most call sites sit inside a try whose catch answers
|
|
78
|
+
* `502 GitHubError`: a DB hiccup there would make GET merge-options read "Couldn't load merge
|
|
79
|
+
* status", refuse an arm, or — after GitHub has ACCEPTED an enqueue or a dequeue — tell the reader
|
|
80
|
+
* the write failed when it landed (and invite a retry). "Once GitHub has 201'd the route may not
|
|
81
|
+
* fail." So the failure is logged and swallowed here, once, rather than at each call site.
|
|
82
|
+
*/
|
|
83
|
+
export async function stampPrMergeQueueStateNonFatal(prId, accountId, inQueue, rawEntryState, log) {
|
|
84
|
+
try {
|
|
85
|
+
await stampPrMergeQueueState(prId, accountId, inQueue, rawEntryState);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
log.warn({ err, prId }, 'merge queue: could not record the queue state on the PR row');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=pr-merge-queue-stamp.js.map
|