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,503 @@
|
|
|
1
|
+
// WS1c — the layered reviewer classifier (CORE). Resolution order (first HARD hit wins; soft
|
|
2
|
+
// signals only accumulate into the MEDIUM band):
|
|
3
|
+
//
|
|
4
|
+
// 1. Known vendor login (reviewBotKind) → vendor kind. HIGH
|
|
5
|
+
// 2. githubType==='Bot' OR opt-in app attribution → fingerprint tool ?? in_house. HIGH
|
|
6
|
+
// 3. Branded/marked fingerprint → fingerprint tool ?? in_house. HIGH
|
|
7
|
+
// 4. Behavioral score (+ service-account login promotion) → in_house. MEDIUM (never auto-badges)
|
|
8
|
+
// otherwise → human (automated:false).
|
|
9
|
+
//
|
|
10
|
+
// ⚠ THERE IS NO LLM LEG IN HERE ANY MORE. A fifth step used to sit under the MEDIUM band — one
|
|
11
|
+
// `cheapComplete` call over 3 sample comments — behind the plugin setting `bots.aiTiebreak`. It
|
|
12
|
+
// was UNREACHABLE: the setting lived in the plugin's `pro_settings`, which CORE cannot read, and
|
|
13
|
+
// not one of the four `classifyReviewer` call sites ever passed the flag. It went with the setting,
|
|
14
|
+
// along with the per-account login allowlist (same story), which is why this module no longer
|
|
15
|
+
// imports `review/llm.js`. The `ClassificationSource` union still carries 'ai_tiebreak' for rows
|
|
16
|
+
// stored by an older build. Re-adding an LLM leg here means re-tiering three `read`-bucket routes
|
|
17
|
+
// (see api/plugins/rate-limit.ts) in the SAME change.
|
|
18
|
+
//
|
|
19
|
+
// ── IT DERIVES ONCE PER ACTOR AND WRITES THAT VERDICT TO EVERY ONE OF THE NAMED WORKSPACE ROWS ─
|
|
20
|
+
// A bot is a PER-WORKSPACE object, but the SIGNALS are not: a vendor login, `users.githubType`,
|
|
21
|
+
// app attribution and the branded-marker fingerprint are all properties of the ACTOR and give the
|
|
22
|
+
// same answer in every workspace. Deriving per workspace would multiply the work — and the BILLED
|
|
23
|
+
// Haiku tie-break — for an identical result, and would weaken the behavioural score by computing
|
|
24
|
+
// it on a thin slice. So `classifyReviewer` takes the actor's TARGET WORKSPACE LIST and `persist`
|
|
25
|
+
// fans one verdict out across it.
|
|
26
|
+
//
|
|
27
|
+
// ── IT GATES ON TWO PROVENANCE FLAGS THAT NOW SHARE ONE ROW ───────────────────────────────────
|
|
28
|
+
// `workspace_reviewers.identity_source = 'manual'` ⇒ leave kind/label alone (a human named this
|
|
29
|
+
// vendor; re-deriving reverts the correction)
|
|
30
|
+
// `workspace_reviewers.source = 'manual'` ⇒ leave automated/role/confidence/reasons
|
|
31
|
+
// alone FOR THAT WORKSPACE (a human judged
|
|
32
|
+
// this row; the OTHER workspaces still update)
|
|
33
|
+
//
|
|
34
|
+
// ⚠ THEY USED TO LIVE ON TWO TABLES, AND THAT TABLE BOUNDARY WAS THE ENFORCEMENT. It is gone: both
|
|
35
|
+
// facts are columns of one `workspace_reviewers` row now, so the separation is CODE DISCIPLINE —
|
|
36
|
+
// a `set:` object narrowed per half, built fresh per workspace, honouring each flag independently.
|
|
37
|
+
// The failure it prevents is unchanged: gate identity on the judgement flag and a "not a bot" here
|
|
38
|
+
// blanks the vendor's brand colour; gate the judgement on the identity flag and naming a vendor
|
|
39
|
+
// freezes auto-detection. See §4.6 / §9.9 of the workspace contract, and the paired tests.
|
|
40
|
+
//
|
|
41
|
+
// ⚠ THE SHARED-`values`-OBJECT PATTERN IS GONE ON PURPOSE. One object used as both the INSERT
|
|
42
|
+
// values and the ON CONFLICT `set:` is correct only while a table holds ONE grain. With judgement,
|
|
43
|
+
// identity and price in a single row it would overwrite a human's vendor correction on every auto
|
|
44
|
+
// pass — the exact bug the two-table split was built to kill, reintroduced inside one row.
|
|
45
|
+
//
|
|
46
|
+
// ⚠ Shared is no longer `import type` only here: `GENERIC_REVIEWER_LABELS` is a VALUE import.
|
|
47
|
+
// That is safe now — build-release.mjs compiles the package, vendors it at release/dist/shared
|
|
48
|
+
// and rewrites the specifier to a relative path (CLAUDE.md § Packaging). The big login→kind
|
|
49
|
+
// tables in sync/bot-detection.ts are still hand-copied, for the drift-test reason stated there.
|
|
50
|
+
import { and, eq, inArray } from 'drizzle-orm';
|
|
51
|
+
import { GENERIC_REVIEWER_LABELS } from '../shared/index.js';
|
|
52
|
+
import { db, schema } from '../db/client.js';
|
|
53
|
+
import { automationVendorFor, matchesAutomatedLoginPattern, reviewBotKind, roleForBotLogin, } from './bot-detection.js';
|
|
54
|
+
const { workspaceReviewers } = schema;
|
|
55
|
+
// Fallback display labels for the vendor kinds. The frontend's BOT_VENDOR_META is the
|
|
56
|
+
// source of truth for rendering; this is what lands in the persisted `label` column.
|
|
57
|
+
//
|
|
58
|
+
// ⚠ TWO SIBLINGS, HAND-KEPT: `BOT_VENDOR_META` (apps/frontend/src/lib/ui.ts) and the Pro
|
|
59
|
+
// plugin's Slack `BOT_LABELS` (packages/pro/src/slack/report.ts). Change one branded label,
|
|
60
|
+
// change all three. The three UNBRANDED kinds are no longer restated here — `labelFor` reads
|
|
61
|
+
// them from `GENERIC_REVIEWER_LABELS` in shared, because those were exactly the keys the three
|
|
62
|
+
// copies had drifted apart on.
|
|
63
|
+
//
|
|
64
|
+
// ⚠ `Record<AutomatedReviewerKind, …>` minus the three generic kinds, so a vendor added to the
|
|
65
|
+
// union without a label here is a COMPILE error rather than a card rendering the raw slug
|
|
66
|
+
// (`deepsource_autofix`) at a user. `labelFor` handles the generics separately because they are
|
|
67
|
+
// deliberately NOT branded — they render by login.
|
|
68
|
+
const VENDOR_LABELS = {
|
|
69
|
+
coderabbit: 'CodeRabbit',
|
|
70
|
+
greptile: 'Greptile',
|
|
71
|
+
copilot: 'Copilot',
|
|
72
|
+
qodo: 'Qodo',
|
|
73
|
+
sourcery: 'Sourcery',
|
|
74
|
+
bito: 'Bito',
|
|
75
|
+
ellipsis: 'Ellipsis',
|
|
76
|
+
korbit: 'Korbit',
|
|
77
|
+
baz: 'Baz',
|
|
78
|
+
graphite: 'Graphite',
|
|
79
|
+
cursor: 'Cursor',
|
|
80
|
+
devin: 'Devin',
|
|
81
|
+
entelligence: 'Entelligence',
|
|
82
|
+
deepsource: 'DeepSource',
|
|
83
|
+
github_code_quality: 'GitHub Code Quality',
|
|
84
|
+
github_advanced_security: 'GitHub Advanced Security',
|
|
85
|
+
codex: 'Codex',
|
|
86
|
+
// Quality gates, scanners and CI
|
|
87
|
+
sonarqube: 'SonarQube',
|
|
88
|
+
codecov: 'Codecov',
|
|
89
|
+
codeclimate: 'Code Climate',
|
|
90
|
+
codefactor: 'CodeFactor',
|
|
91
|
+
hound: 'Hound',
|
|
92
|
+
coveralls: 'Coveralls',
|
|
93
|
+
codacy: 'Codacy',
|
|
94
|
+
github_actions: 'GitHub Actions',
|
|
95
|
+
jit: 'Jit',
|
|
96
|
+
socket: 'Socket',
|
|
97
|
+
gitguardian: 'GitGuardian',
|
|
98
|
+
semgrep: 'Semgrep',
|
|
99
|
+
trunk: 'Trunk',
|
|
100
|
+
endor: 'Endor Labs',
|
|
101
|
+
// Dependency & version bumps
|
|
102
|
+
dependabot: 'Dependabot',
|
|
103
|
+
renovate: 'Renovate',
|
|
104
|
+
snyk: 'Snyk',
|
|
105
|
+
pyup: 'PyUp',
|
|
106
|
+
greenkeeper: 'Greenkeeper',
|
|
107
|
+
depfu: 'Depfu',
|
|
108
|
+
aikido: 'Aikido',
|
|
109
|
+
mend: 'Mend',
|
|
110
|
+
frogbot: 'Frogbot',
|
|
111
|
+
checkmarx: 'Checkmarx',
|
|
112
|
+
// Code agents — automation that writes code
|
|
113
|
+
sweep: 'Sweep',
|
|
114
|
+
codegen: 'Codegen',
|
|
115
|
+
deepsource_autofix: 'DeepSource Autofix',
|
|
116
|
+
pre_commit_ci: 'pre-commit.ci',
|
|
117
|
+
restyled: 'Restyled',
|
|
118
|
+
imgbot: 'ImgBot',
|
|
119
|
+
transifex: 'Transifex',
|
|
120
|
+
crowdin: 'Crowdin',
|
|
121
|
+
mintlify: 'Mintlify',
|
|
122
|
+
allstar: 'Allstar',
|
|
123
|
+
step_security: 'StepSecurity',
|
|
124
|
+
orbisai: 'OrbisAI',
|
|
125
|
+
// Release & merge automation
|
|
126
|
+
mergify: 'Mergify',
|
|
127
|
+
kodiak: 'Kodiak',
|
|
128
|
+
bulldozer: 'Bulldozer',
|
|
129
|
+
release_please: 'Release Please',
|
|
130
|
+
semantic_release: 'semantic-release',
|
|
131
|
+
release_drafter: 'Release Drafter',
|
|
132
|
+
changesets: 'Changesets',
|
|
133
|
+
backport: 'Backport bot',
|
|
134
|
+
// Housekeeping — process, compliance, metadata
|
|
135
|
+
cla_assistant: 'CLA Assistant',
|
|
136
|
+
google_cla: 'Google CLA',
|
|
137
|
+
meta_cla: 'Meta CLA',
|
|
138
|
+
dco: 'DCO',
|
|
139
|
+
stale_bot: 'Stale bot',
|
|
140
|
+
welcome_bot: 'Welcome bot',
|
|
141
|
+
lock_bot: 'Lock bot',
|
|
142
|
+
allcontributors: 'All Contributors',
|
|
143
|
+
semantic_pr: 'Semantic PR',
|
|
144
|
+
sizebot: 'Size bot',
|
|
145
|
+
codesandbox: 'CodeSandbox',
|
|
146
|
+
netlify: 'Netlify',
|
|
147
|
+
vercel: 'Vercel',
|
|
148
|
+
gitpod: 'Gitpod',
|
|
149
|
+
};
|
|
150
|
+
// What to call each role in a human-readable `reasons` line. "…is a known Dependabot dependency
|
|
151
|
+
// bot" reads as a classification; the old "…is a known Dependabot review bot" read as a mistake,
|
|
152
|
+
// because it was one.
|
|
153
|
+
const ROLE_NOUN = {
|
|
154
|
+
review: 'review bot',
|
|
155
|
+
quality_check: 'quality check',
|
|
156
|
+
dependency: 'dependency bot',
|
|
157
|
+
code_agent: 'code agent',
|
|
158
|
+
release: 'release automation',
|
|
159
|
+
housekeeping: 'housekeeping bot',
|
|
160
|
+
};
|
|
161
|
+
// Exported so the query layer (db/queries.ts) labels analytics/dedup groupings from the
|
|
162
|
+
// same source of truth as the persisted classification `label`.
|
|
163
|
+
export function labelFor(kind) {
|
|
164
|
+
// The three unbranded kinds come from shared: 'pierre' composes the product name, and
|
|
165
|
+
// 'in_house' is "In-house / custom" rather than "In-house AI" for a reason recorded there.
|
|
166
|
+
// Both spellings used to be restated here, in the SPA and in the Slack digest, and the three
|
|
167
|
+
// had drifted.
|
|
168
|
+
if (kind === 'pierre' || kind === 'in_house' || kind === 'vendor') {
|
|
169
|
+
return GENERIC_REVIEWER_LABELS[kind];
|
|
170
|
+
}
|
|
171
|
+
return VENDOR_LABELS[kind] ?? kind;
|
|
172
|
+
}
|
|
173
|
+
function describeMarkers(fp) {
|
|
174
|
+
if (fp.markers.length === 0)
|
|
175
|
+
return ['matched an automated-review fingerprint'];
|
|
176
|
+
const tool = fp.tool ?? 'in-house';
|
|
177
|
+
return [`matched ${tool} fingerprint: ${fp.markers.join(', ')}`];
|
|
178
|
+
}
|
|
179
|
+
function behavioralVerdict(sig, allowlistMatch) {
|
|
180
|
+
// Require account-level evidence (≥3 reviews) before the behavioral band engages.
|
|
181
|
+
if (!sig || sig.reviews < 3)
|
|
182
|
+
return { medium: false, reasons: [] };
|
|
183
|
+
const reasons = [];
|
|
184
|
+
let score = 0;
|
|
185
|
+
if (sig.medianPushToReviewMins != null && sig.medianPushToReviewMins < 2) {
|
|
186
|
+
score++;
|
|
187
|
+
reasons.push('reviews land within ~2 min of a push');
|
|
188
|
+
}
|
|
189
|
+
if (sig.reviewsPerPr != null && sig.reviewsPerPr <= 1.2) {
|
|
190
|
+
score++;
|
|
191
|
+
reasons.push('~1 review per PR');
|
|
192
|
+
}
|
|
193
|
+
if (sig.replyRate != null && sig.replyRate < 0.05) {
|
|
194
|
+
score++;
|
|
195
|
+
reasons.push('almost never replies in threads');
|
|
196
|
+
}
|
|
197
|
+
if (sig.commentsPerReview != null && sig.commentsPerReview >= 3) {
|
|
198
|
+
score++;
|
|
199
|
+
reasons.push('many inline comments per review');
|
|
200
|
+
}
|
|
201
|
+
if (allowlistMatch) {
|
|
202
|
+
score++;
|
|
203
|
+
reasons.push('login matches a service-account pattern');
|
|
204
|
+
}
|
|
205
|
+
return { medium: score >= 3, reasons };
|
|
206
|
+
}
|
|
207
|
+
// NOTE: the old `rowToClassification(row, login)` helper is GONE and is NOT to be reinstated now
|
|
208
|
+
// that one row does hold both halves. It built a `ReviewerClassification` out of one row, which
|
|
209
|
+
// reads as harmless again — and is exactly how a caller starts treating a row as a single
|
|
210
|
+
// undifferentiated verdict, losing the two provenance flags that make the halves independent. A
|
|
211
|
+
// caller that wants both facts reads both columns and states which flag governs the one it uses;
|
|
212
|
+
// the query layer does exactly that.
|
|
213
|
+
// The DEFAULT role for a login nobody has classified by hand: whichever of the five automation
|
|
214
|
+
// vocabularies claims it, else 'review'.
|
|
215
|
+
//
|
|
216
|
+
// ⚠ THE `'review'` FALLBACK IS NOT A CLAIM THAT AN UNKNOWN BOT REVIEWS — it is the historical
|
|
217
|
+
// default, kept so an unrecognised vendor keeps its row in the ROI table where a user can see and
|
|
218
|
+
// reclassify it. Defaulting an unknown automation to a non-review role would make it VANISH from
|
|
219
|
+
// the one panel that exists to correct it. (`resolveActorLanes` makes the opposite choice for the
|
|
220
|
+
// same input, and the asymmetry is deliberate: it declines to CREDIT an unknown as a reviewer,
|
|
221
|
+
// which costs nothing, whereas hiding it here would cost the user the fix.)
|
|
222
|
+
//
|
|
223
|
+
// This is DERIVED, never carried in from the caller, and that is deliberate — see the landmine
|
|
224
|
+
// on persist() below.
|
|
225
|
+
export function defaultRoleFor(login) {
|
|
226
|
+
return roleForBotLogin(login) ?? 'review';
|
|
227
|
+
}
|
|
228
|
+
// Write ONE derived verdict to each of `workspaceIds`' rows. Returns the classification as it now
|
|
229
|
+
// applies (with the derived role).
|
|
230
|
+
//
|
|
231
|
+
// LANDMINE 1 — ONE STATEMENT PER WORKSPACE, WITH A `set:` NARROWED PER HALF. The loop is not an
|
|
232
|
+
// oversight to be batched away: an account has few workspaces, and two workspaces may legitimately
|
|
233
|
+
// hold DIFFERENT provenance flags for the same actor (manual "not a bot" in one, untouched auto in
|
|
234
|
+
// the next). A single batched insert carries ONE `set:` object and physically cannot express that.
|
|
235
|
+
//
|
|
236
|
+
// LANDMINE 2 — the gate is a READ-THEN-NARROW, not an `onConflictDoUpdate … WHERE`. drizzle
|
|
237
|
+
// spells the conditional-update clause differently per dialect, and `db` is typed as
|
|
238
|
+
// node-postgres, so a `setWhere` here would compile and then behave differently (or not at all)
|
|
239
|
+
// on SQLite. Reading the two provenance flags first and simply not naming the columns a human
|
|
240
|
+
// owns is dialect-free.
|
|
241
|
+
//
|
|
242
|
+
// LANDMINE 3 — `role` is DERIVED from the local quality-check list rather than round-tripped
|
|
243
|
+
// through the ReviewerClassification the caller built. Anything in the `set:` OVERWRITES the
|
|
244
|
+
// stored column on every auto pass, so copying a stale in-memory role would re-write migration
|
|
245
|
+
// 0045's role fold from a stale default and put SonarQube straight back into the review-bot
|
|
246
|
+
// metrics. Deriving keeps the write idempotent AND self-healing for a login added to the list
|
|
247
|
+
// later. (The parameter is role-LESS on purpose, so a future step in the resolution order
|
|
248
|
+
// physically cannot hand one in.)
|
|
249
|
+
//
|
|
250
|
+
// LANDMINE 4 — THE PRICE COLUMN IS NAMED NOWHERE IN THIS FILE: not in a `set:`, not as a derived
|
|
251
|
+
// INSERT value, not as a seed read off a sibling workspace. Price is PER WORKSPACE, so there is no
|
|
252
|
+
// sibling that could be authoritative; a row this pass creates simply has no price until someone
|
|
253
|
+
// sets one, and because the column is never in the `set:`, a conflict leaves a stored price
|
|
254
|
+
// untouched. `ReviewerClassification` carries no cost field either, which makes the omission
|
|
255
|
+
// structural rather than a rule to remember. `setReviewerCost` is the one writer.
|
|
256
|
+
//
|
|
257
|
+
// LANDMINE 5 — the conflict target is the 3-COLUMN tuple
|
|
258
|
+
// `workspace_reviewers_account_workspace_author`. A stale target raises "no unique or exclusion
|
|
259
|
+
// constraint matching the ON CONFLICT specification" at RUNTIME, in both dialects, only when a row
|
|
260
|
+
// is actually written; it type-checks perfectly.
|
|
261
|
+
async function persist(accountId, workspaceIds, c, opts = {}) {
|
|
262
|
+
const now = new Date();
|
|
263
|
+
const role = defaultRoleFor(c.login);
|
|
264
|
+
for (const workspaceId of workspaceIds) {
|
|
265
|
+
const existing = (await db
|
|
266
|
+
.select({
|
|
267
|
+
source: workspaceReviewers.source,
|
|
268
|
+
identitySource: workspaceReviewers.identitySource,
|
|
269
|
+
})
|
|
270
|
+
.from(workspaceReviewers)
|
|
271
|
+
.where(and(eq(workspaceReviewers.accountId, accountId), eq(workspaceReviewers.workspaceId, workspaceId), eq(workspaceReviewers.authorUserId, c.userId)))
|
|
272
|
+
.limit(1)
|
|
273
|
+
.execute())[0];
|
|
274
|
+
const set = { updatedAt: now };
|
|
275
|
+
if (opts.only !== 'identity' && existing?.source !== 'manual') {
|
|
276
|
+
set.automated = c.automated;
|
|
277
|
+
set.role = role;
|
|
278
|
+
set.confidence = c.confidence;
|
|
279
|
+
set.source = c.source;
|
|
280
|
+
set.reasonsJson = c.reasons;
|
|
281
|
+
}
|
|
282
|
+
if (opts.only !== 'judgement' && existing?.identitySource !== 'manual') {
|
|
283
|
+
set.kind = c.kind;
|
|
284
|
+
set.label = c.label;
|
|
285
|
+
}
|
|
286
|
+
// Nothing this pass is allowed to write — emit NO statement at all rather than an UPDATE that
|
|
287
|
+
// only bumps `updated_at`, which would make a skipped row look freshly re-derived.
|
|
288
|
+
if (Object.keys(set).length === 1)
|
|
289
|
+
continue;
|
|
290
|
+
await db
|
|
291
|
+
.insert(workspaceReviewers)
|
|
292
|
+
.values({
|
|
293
|
+
accountId,
|
|
294
|
+
workspaceId,
|
|
295
|
+
authorUserId: c.userId,
|
|
296
|
+
// The INSERT carries the FULL derived verdict regardless of `only`: `automated`,
|
|
297
|
+
// `confidence` and `source` are NOT NULL, so a brand-new row needs them, and a new row has
|
|
298
|
+
// no human opinion on either half to preserve. The price column is omitted ⇒ NULL.
|
|
299
|
+
// `identity_source` is omitted too, taking its 'auto' default — on the UPDATE path it can
|
|
300
|
+
// only be 'auto' already (a 'manual' identity never reaches the kind/label branch), so
|
|
301
|
+
// writing it would be noise that also reads as if this path may set provenance.
|
|
302
|
+
automated: c.automated,
|
|
303
|
+
role,
|
|
304
|
+
confidence: c.confidence,
|
|
305
|
+
source: c.source,
|
|
306
|
+
reasonsJson: c.reasons,
|
|
307
|
+
kind: c.kind,
|
|
308
|
+
label: c.label,
|
|
309
|
+
updatedAt: now,
|
|
310
|
+
})
|
|
311
|
+
.onConflictDoUpdate({
|
|
312
|
+
target: [
|
|
313
|
+
workspaceReviewers.accountId,
|
|
314
|
+
workspaceReviewers.workspaceId,
|
|
315
|
+
workspaceReviewers.authorUserId,
|
|
316
|
+
],
|
|
317
|
+
set,
|
|
318
|
+
})
|
|
319
|
+
.execute();
|
|
320
|
+
}
|
|
321
|
+
return { ...c, role };
|
|
322
|
+
}
|
|
323
|
+
// Persist the "this is an ordinary human" judgement for an actor that only ever left issue
|
|
324
|
+
// comments — the population `classifyReviewer` is deliberately not run for (there are no reviews
|
|
325
|
+
// to score, so every signal it could read is absent).
|
|
326
|
+
//
|
|
327
|
+
// It exists because THE ROW IS THE BOT OBJECT: a (workspace, actor) pair with no row cannot be
|
|
328
|
+
// listed, and therefore cannot be corrected by hand either. Writing a low-confidence non-manual
|
|
329
|
+
// row makes the pair visible and leaves it fully re-derivable — the moment that person submits a
|
|
330
|
+
// real review, the next classification pass overwrites this.
|
|
331
|
+
//
|
|
332
|
+
// ⚠ TWO PROPERTIES THAT WERE EMERGENT UNDER TWO TABLES AND ARE NOW REQUIREMENTS:
|
|
333
|
+
// (a) the values object contains NO `kind`/`label` AT ALL, so a human judgement can never rename
|
|
334
|
+
// the vendor as a side effect. Under the merged row that omission is the ONLY thing stopping
|
|
335
|
+
// it — there is no longer a table boundary doing the job. (A new row therefore comes up with
|
|
336
|
+
// kind/label NULL, which is correct: a human has no vendor kind, and stamping one is a
|
|
337
|
+
// claim.) The price column is likewise absent, so a price already recorded survives.
|
|
338
|
+
// (b) it still narrows to the workspaces no human owns (`source = 'manual'`) by READING them
|
|
339
|
+
// first, not by a `setWhere` — drizzle spells that clause differently per dialect while `db`
|
|
340
|
+
// is pg-typed.
|
|
341
|
+
// Because the shared values object here names ONLY judgement columns and the manual rows are
|
|
342
|
+
// already filtered out, one batched statement is safe: every writable row takes identical values.
|
|
343
|
+
export async function persistHumanJudgement(accountId, workspaceIds, userId, login) {
|
|
344
|
+
if (workspaceIds.length === 0)
|
|
345
|
+
return;
|
|
346
|
+
const manualRows = await db
|
|
347
|
+
.select({ workspaceId: workspaceReviewers.workspaceId })
|
|
348
|
+
.from(workspaceReviewers)
|
|
349
|
+
.where(and(eq(workspaceReviewers.accountId, accountId), eq(workspaceReviewers.authorUserId, userId), eq(workspaceReviewers.source, 'manual'), inArray(workspaceReviewers.workspaceId, workspaceIds)))
|
|
350
|
+
.execute();
|
|
351
|
+
const manual = new Set(manualRows.map((r) => r.workspaceId));
|
|
352
|
+
const writable = workspaceIds.filter((id) => !manual.has(id));
|
|
353
|
+
if (writable.length === 0)
|
|
354
|
+
return;
|
|
355
|
+
const values = {
|
|
356
|
+
automated: false,
|
|
357
|
+
role: defaultRoleFor(login),
|
|
358
|
+
confidence: 'low',
|
|
359
|
+
source: 'fingerprint',
|
|
360
|
+
reasonsJson: ['commented on PRs but has not submitted a review'],
|
|
361
|
+
updatedAt: new Date(),
|
|
362
|
+
};
|
|
363
|
+
await db
|
|
364
|
+
.insert(workspaceReviewers)
|
|
365
|
+
.values(writable.map((workspaceId) => ({ accountId, workspaceId, authorUserId: userId, ...values })))
|
|
366
|
+
.onConflictDoUpdate({
|
|
367
|
+
target: [
|
|
368
|
+
workspaceReviewers.accountId,
|
|
369
|
+
workspaceReviewers.workspaceId,
|
|
370
|
+
workspaceReviewers.authorUserId,
|
|
371
|
+
],
|
|
372
|
+
set: values,
|
|
373
|
+
})
|
|
374
|
+
.execute();
|
|
375
|
+
}
|
|
376
|
+
// `workspaceIds` is the set of WORKSPACE ROWS this verdict should be written to. It is REQUIRED
|
|
377
|
+
// and positional so no caller can "just classify" without saying where the answer lands: the row
|
|
378
|
+
// IS the bot object, and a verdict with no row is a verdict nobody can see or correct.
|
|
379
|
+
//
|
|
380
|
+
// ⚠ AN EMPTY LIST IS A NO-OP WITH NO CALLER, NOT A MECHANISM. It used to be the way to write the
|
|
381
|
+
// identity half alone (two statements, two tables, only the judgement gated on the list being
|
|
382
|
+
// non-empty). Under one row and one per-workspace loop it writes NOTHING, so the two reset paths
|
|
383
|
+
// pass a real workspace id plus `opts.only` instead — see PersistOpts.
|
|
384
|
+
//
|
|
385
|
+
// THERE IS NO "MANUAL OVERRIDE WINS" EARLY RETURN, and its absence is the point. A manual flag is
|
|
386
|
+
// per WORKSPACE and per HALF, so the derivation always runs and `persist` declines exactly the
|
|
387
|
+
// columns a human owns — which is what lets a manual "not a bot" in one workspace coexist with a
|
|
388
|
+
// fresh auto verdict in the next, and a manual vendor name coexist with a re-derived judgement in
|
|
389
|
+
// the same row. The value this function RETURNS is therefore the DERIVED verdict, not necessarily
|
|
390
|
+
// what is stored for any given workspace; read the row back if you need that.
|
|
391
|
+
export async function classifyReviewer(accountId, user, evidence, workspaceIds, opts = {}) {
|
|
392
|
+
const login = user.githubLogin;
|
|
393
|
+
const fp = evidence.fingerprint;
|
|
394
|
+
// 1. Known vendor login — an AI REVIEWER.
|
|
395
|
+
const vendor = reviewBotKind(login);
|
|
396
|
+
if (vendor) {
|
|
397
|
+
return persist(accountId, workspaceIds, {
|
|
398
|
+
userId: user.id,
|
|
399
|
+
login,
|
|
400
|
+
automated: true,
|
|
401
|
+
kind: vendor,
|
|
402
|
+
label: labelFor(vendor),
|
|
403
|
+
confidence: 'high',
|
|
404
|
+
source: 'vendor_login',
|
|
405
|
+
reasons: [`login "${login}" is a known ${labelFor(vendor)} review bot`],
|
|
406
|
+
}, opts);
|
|
407
|
+
}
|
|
408
|
+
// 1b. Known vendor login — any OTHER automation family (quality gate, dependency bot, code
|
|
409
|
+
// agent, release or housekeeping automation).
|
|
410
|
+
//
|
|
411
|
+
// ⚠ THIS STEP MUST SIT ABOVE STEP 2, and its absence is why every non-reviewer integration used
|
|
412
|
+
// to render as "In-house AI". Step 2 fires on `githubType === 'Bot'`, which is true of all of
|
|
413
|
+
// these, and assigns `fp?.tool ?? 'in_house'` — so SonarQube, Dependabot, github-actions,
|
|
414
|
+
// GitGuardian, Socket, Google CLA and Jit all landed in the bucket literally labelled with
|
|
415
|
+
// another vendor's job description (25 of 37 such rows on the dev corpus). The user could not
|
|
416
|
+
// tell them apart on the one screen that exists to classify them.
|
|
417
|
+
//
|
|
418
|
+
// The `reasons` string says what the actor DOES rather than calling it a review bot — the
|
|
419
|
+
// difference between a classification a user trusts and one they immediately correct.
|
|
420
|
+
const automation = automationVendorFor(login);
|
|
421
|
+
if (automation) {
|
|
422
|
+
return persist(accountId, workspaceIds, {
|
|
423
|
+
userId: user.id,
|
|
424
|
+
login,
|
|
425
|
+
automated: true,
|
|
426
|
+
kind: automation.kind,
|
|
427
|
+
label: labelFor(automation.kind),
|
|
428
|
+
confidence: 'high',
|
|
429
|
+
source: 'vendor_login',
|
|
430
|
+
reasons: [
|
|
431
|
+
`login "${login}" is a known ${labelFor(automation.kind)} ` +
|
|
432
|
+
`${ROLE_NOUN[automation.role]}`,
|
|
433
|
+
],
|
|
434
|
+
}, opts);
|
|
435
|
+
}
|
|
436
|
+
// 2. GitHub App / Bot typename, or opt-in app attribution.
|
|
437
|
+
if (user.githubType === 'Bot' || evidence.appAttributed) {
|
|
438
|
+
const kind = fp?.tool ?? 'in_house';
|
|
439
|
+
const isBotType = user.githubType === 'Bot';
|
|
440
|
+
const source = isBotType ? 'github_type' : 'app_attribution';
|
|
441
|
+
const reasons = [
|
|
442
|
+
isBotType ? 'GitHub reports this account as a Bot' : 'posted via a GitHub App',
|
|
443
|
+
];
|
|
444
|
+
if (fp?.marked)
|
|
445
|
+
reasons.push(...describeMarkers(fp));
|
|
446
|
+
return persist(accountId, workspaceIds, {
|
|
447
|
+
userId: user.id,
|
|
448
|
+
login,
|
|
449
|
+
automated: true,
|
|
450
|
+
kind,
|
|
451
|
+
label: kind === 'in_house' ? login : labelFor(kind),
|
|
452
|
+
confidence: 'high',
|
|
453
|
+
source,
|
|
454
|
+
reasons,
|
|
455
|
+
}, opts);
|
|
456
|
+
}
|
|
457
|
+
// 3. Branded/marked fingerprint.
|
|
458
|
+
if (fp?.marked) {
|
|
459
|
+
const kind = fp.tool ?? 'in_house';
|
|
460
|
+
return persist(accountId, workspaceIds, {
|
|
461
|
+
userId: user.id,
|
|
462
|
+
login,
|
|
463
|
+
automated: true,
|
|
464
|
+
kind,
|
|
465
|
+
label: labelFor(kind),
|
|
466
|
+
confidence: 'high',
|
|
467
|
+
source: 'fingerprint',
|
|
468
|
+
reasons: describeMarkers(fp),
|
|
469
|
+
}, opts);
|
|
470
|
+
}
|
|
471
|
+
// 4. Behavioral band (MEDIUM — never auto-badges; the UI asks "confirm?"). This is the LAST
|
|
472
|
+
// step: the opt-in Haiku tie-break that used to follow it is gone (see the file header).
|
|
473
|
+
const allowlistMatch = matchesAutomatedLoginPattern(login);
|
|
474
|
+
const beh = behavioralVerdict(evidence.behavioral, allowlistMatch);
|
|
475
|
+
if (beh.medium) {
|
|
476
|
+
return persist(accountId, workspaceIds, {
|
|
477
|
+
userId: user.id,
|
|
478
|
+
login,
|
|
479
|
+
automated: true,
|
|
480
|
+
kind: 'in_house',
|
|
481
|
+
label: allowlistMatch ? login : 'In-house / custom',
|
|
482
|
+
confidence: 'medium',
|
|
483
|
+
source: 'behavioral',
|
|
484
|
+
reasons: beh.reasons,
|
|
485
|
+
}, opts);
|
|
486
|
+
}
|
|
487
|
+
// Otherwise → human. Strong (HIGH) when we had enough behavioral evidence to look and
|
|
488
|
+
// it didn't trip; LOW when there was nothing to go on.
|
|
489
|
+
const hadEvidence = (evidence.behavioral?.reviews ?? 0) >= 3;
|
|
490
|
+
return persist(accountId, workspaceIds, {
|
|
491
|
+
userId: user.id,
|
|
492
|
+
login,
|
|
493
|
+
automated: false,
|
|
494
|
+
kind: null,
|
|
495
|
+
label: login,
|
|
496
|
+
confidence: hadEvidence ? 'high' : 'low',
|
|
497
|
+
source: evidence.behavioral ? 'behavioral' : 'fingerprint',
|
|
498
|
+
reasons: hadEvidence
|
|
499
|
+
? ['no automation markers; reviewing behaviour looks human']
|
|
500
|
+
: ['no automated-reviewer signals'],
|
|
501
|
+
}, opts);
|
|
502
|
+
}
|
|
503
|
+
//# sourceMappingURL=reviewer-classify.js.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { and, eq, inArray, isNull } from 'drizzle-orm';
|
|
2
|
+
import { getAccessToken } from '../auth/account.js';
|
|
3
|
+
import { db, schema } from '../db/client.js';
|
|
4
|
+
import { fetchPrFilesWithPatch } from '../github/mutations.js';
|
|
5
|
+
const { pullRequests, repos } = schema;
|
|
6
|
+
// Best-effort, BOUNDED backfill of `pull_requests.files` for reviewer-routing
|
|
7
|
+
// candidates that predate the files column (old open PRs never re-synced). Only PRs
|
|
8
|
+
// whose `files` IS NULL are fetched; the result — even `[]` on failure — is persisted
|
|
9
|
+
// so the same PR isn't refetched on every insights refresh (an [] sentinel means "we
|
|
10
|
+
// tried"). The sprint window's PRs already carry files from sync, so this only ever
|
|
11
|
+
// touches a handful of stale orphans. Returns the resolved paths by prId.
|
|
12
|
+
export async function ensureRoutingPrFiles(accountId, prIds) {
|
|
13
|
+
const out = new Map();
|
|
14
|
+
if (prIds.length === 0)
|
|
15
|
+
return out;
|
|
16
|
+
const rows = await db
|
|
17
|
+
.select({
|
|
18
|
+
id: pullRequests.id,
|
|
19
|
+
number: pullRequests.number,
|
|
20
|
+
owner: repos.owner,
|
|
21
|
+
name: repos.name,
|
|
22
|
+
})
|
|
23
|
+
.from(pullRequests)
|
|
24
|
+
.innerJoin(repos, eq(repos.id, pullRequests.repoId))
|
|
25
|
+
.where(and(eq(pullRequests.accountId, accountId), inArray(pullRequests.id, prIds), isNull(pullRequests.files)))
|
|
26
|
+
.execute();
|
|
27
|
+
if (rows.length === 0)
|
|
28
|
+
return out;
|
|
29
|
+
let token;
|
|
30
|
+
try {
|
|
31
|
+
token = await getAccessToken(accountId);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return out; // no token (e.g. offline) → leave the fallback rationale in place
|
|
35
|
+
}
|
|
36
|
+
for (const r of rows) {
|
|
37
|
+
let stored = [];
|
|
38
|
+
try {
|
|
39
|
+
const { files } = await fetchPrFilesWithPatch(token, r.owner, r.name, r.number, 100);
|
|
40
|
+
stored = files.map((f) => ({
|
|
41
|
+
path: f.filename,
|
|
42
|
+
additions: f.additions,
|
|
43
|
+
deletions: f.deletions,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
stored = []; // sentinel — persist [] so we don't refetch this PR every refresh
|
|
48
|
+
}
|
|
49
|
+
await db
|
|
50
|
+
.update(pullRequests)
|
|
51
|
+
.set({ files: stored })
|
|
52
|
+
.where(and(eq(pullRequests.id, r.id), eq(pullRequests.accountId, accountId)))
|
|
53
|
+
.execute();
|
|
54
|
+
out.set(r.id, stored.map((s) => s.path));
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
/** How many missing file lists one repo may backfill in a single sync. */
|
|
59
|
+
const FILES_BACKFILL_PER_RUN = 25;
|
|
60
|
+
/**
|
|
61
|
+
* Backfill `pull_requests.files` for this repo's OPEN pull requests that have none.
|
|
62
|
+
*
|
|
63
|
+
* ---- WHY THIS EXISTS SEPARATELY FROM THE SYNC ITSELF ------------------------
|
|
64
|
+
*
|
|
65
|
+
* The walk stores `files` for the pull requests it touches, but a repo carries older open ones it
|
|
66
|
+
* never revisits — and a pull request with no stored file list has NO BLAST RADIUS AT ALL:
|
|
67
|
+
* `codeLocFor` returns null, `blastSignalsFor` returns null, and every surface correctly renders
|
|
68
|
+
* nothing. Measured on a real corpus: **154 of 1,564 open pull requests, 9.8%**, showed no chip
|
|
69
|
+
* for this reason alone.
|
|
70
|
+
*
|
|
71
|
+
* ⚠ THIS IS A DIFFERENT FETCH FROM THE CHANGE-SHAPE READ, AND IT IS THE CHEAPER, MORE VALUABLE
|
|
72
|
+
* ONE. `sync/classify-change-shape.ts` reads a DIFF to refine a level that already exists; this
|
|
73
|
+
* fetches the FILE LIST so a level can exist at all. Fetching the diff for these pull requests
|
|
74
|
+
* would not help — with no `files` there is nothing for the diff to refine.
|
|
75
|
+
*
|
|
76
|
+
* Bounded exactly like its sibling: capped per run, once per pull request (the `[]`-on-failure
|
|
77
|
+
* sentinel `ensureRoutingPrFiles` already writes means a pull request GitHub will not serve is
|
|
78
|
+
* never retried), and strictly non-fatal.
|
|
79
|
+
*/
|
|
80
|
+
export async function backfillMissingPrFiles(accountId, repoId, log) {
|
|
81
|
+
const rows = await db
|
|
82
|
+
.select({ id: pullRequests.id })
|
|
83
|
+
.from(pullRequests)
|
|
84
|
+
.where(and(eq(pullRequests.accountId, accountId), eq(pullRequests.repoId, repoId), eq(pullRequests.state, 'open'),
|
|
85
|
+
// ⚠ `IS NULL` only. A row already holding `[]` is the "we tried and GitHub refused"
|
|
86
|
+
// sentinel; re-querying it every sync forever is the cost this predicate avoids.
|
|
87
|
+
isNull(pullRequests.files)))
|
|
88
|
+
.limit(FILES_BACKFILL_PER_RUN)
|
|
89
|
+
.execute();
|
|
90
|
+
if (rows.length === 0)
|
|
91
|
+
return 0;
|
|
92
|
+
const resolved = await ensureRoutingPrFiles(accountId, rows.map((r) => r.id));
|
|
93
|
+
if (resolved.size > 0)
|
|
94
|
+
log?.info(`files backfill: filled ${resolved.size} pull request(s)`);
|
|
95
|
+
return resolved.size;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=routing-files.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const jobs = [];
|
|
2
|
+
export function registerScheduledJob(cron, handler, label = 'pro-job') {
|
|
3
|
+
jobs.push({ cron, label, handler });
|
|
4
|
+
}
|
|
5
|
+
export function getScheduledJobs() {
|
|
6
|
+
return jobs;
|
|
7
|
+
}
|
|
8
|
+
// Test/reset hook — registration is process-lifetime, but tests may want a clean slate.
|
|
9
|
+
export function clearScheduledJobs() {
|
|
10
|
+
jobs.length = 0;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=scheduled-jobs.js.map
|