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,127 @@
|
|
|
1
|
+
-- THE BOT OBJECT BECOMES PER REPO — the Postgres twin of the sqlite migration
|
|
2
|
+
-- 0042_repo_reviewers. Hand-written additive (like 0027/0028), idempotent. Migration 0030 then
|
|
3
|
+
-- splits the ACTOR grain back out; read the two together, they are one change in two steps:
|
|
4
|
+
--
|
|
5
|
+
-- 0029 RE-KEY (account, author) → (account, repo, author) columns unchanged
|
|
6
|
+
-- 0030 NORMALISE the actor-grain columns → their own table `account_reviewers`
|
|
7
|
+
--
|
|
8
|
+
-- `bot_review_classification` answered "is this login a bot, and what kind" once per ACCOUNT.
|
|
9
|
+
-- `repo_reviewers` answers it once per (account, REPO, author), and that row IS the bot object.
|
|
10
|
+
-- A bot is installed per repository; a team is a bag of repos someone can re-bag tomorrow. There
|
|
11
|
+
-- is no team key, no inheritance chain, no merge and NO DEDUPLICATION — a vendor running on six
|
|
12
|
+
-- repos is six rows, which is the intended display.
|
|
13
|
+
--
|
|
14
|
+
-- The old table is DROPPED at the bottom rather than left behind: a second, differently-keyed
|
|
15
|
+
-- answer to the same question sitting in every database is exactly how two surfaces disagree.
|
|
16
|
+
--
|
|
17
|
+
-- `kind` / `label` / `identity_source` ARE CREATED HERE AND REMOVED AGAIN IN 0030. This step is a
|
|
18
|
+
-- pure re-key — every column of the old row is carried across unchanged — so that the fan-out and
|
|
19
|
+
-- the normalisation can be read one at a time.
|
|
20
|
+
--
|
|
21
|
+
-- TENANCY IS STRUCTURAL. `repo_id` is the first column here that arrives in a REQUEST BODY
|
|
22
|
+
-- rather than from sync, and a plain `REFERENCES repos(id)` accepts (account 2, repo 10) where
|
|
23
|
+
-- repo 10 belongs to account 1. The FK is therefore COMPOSITE against `repos(id, account_id)`,
|
|
24
|
+
-- which the unique index below makes legal — Postgres accepts a plain UNIQUE INDEX as an FK
|
|
25
|
+
-- parent key (verified on 16.13; it does not require a named UNIQUE CONSTRAINT), and SQLite
|
|
26
|
+
-- requires exactly the same thing, so the twins stay identical. The cross-account insert now
|
|
27
|
+
-- fails in the database, in every code path, not in one handler's predicate.
|
|
28
|
+
--
|
|
29
|
+
-- THE CONSTRAINT IS NAMED, and the name is the one `schema.pg.ts` declares. Postgres quotes it in
|
|
30
|
+
-- the violation message ("violates foreign key constraint \"repo_reviewers_repo_account_fk\""),
|
|
31
|
+
-- so it is a real, greppable identifier. Previously the SQL named nothing and pg auto-named the
|
|
32
|
+
-- constraint `repo_reviewers_repo_id_account_id_fkey`, i.e. drizzle's `name:` field matched no
|
|
33
|
+
-- live constraint in any database. (SQLite stores the same clause but never reports it.)
|
|
34
|
+
--
|
|
35
|
+
-- THE BACKFILL IS ASYMMETRIC ON PURPOSE. MANUAL rows fan out to EVERY repo in the account — a
|
|
36
|
+
-- human judgement is the only thing here that cannot be regenerated, it was made account-wide,
|
|
37
|
+
-- and fanning it out reproduces that meaning with every repo row agreeing. AUTO rows land only on
|
|
38
|
+
-- repos where the actor actually has a FOOTPRINT (a review, an inline thread, or a PR comment
|
|
39
|
+
-- there): an auto verdict is re-derived on the next classification pass, and a row for a repo the
|
|
40
|
+
-- reviewer has never touched is not a bot object but a fabricated one. See the sqlite twin.
|
|
41
|
+
--
|
|
42
|
+
-- The footprint CTE is MATERIALIZED so the planner cannot inline it into the correlated EXISTS
|
|
43
|
+
-- and re-scan three tables per (row, repo) pair.
|
|
44
|
+
--
|
|
45
|
+
-- WHY THE BACKFILL IS WRAPPED IN A DO BLOCK: this file ends by dropping
|
|
46
|
+
-- `bot_review_classification`, so a hand-replay would otherwise fail on "relation does not exist"
|
|
47
|
+
-- rather than no-op. `to_regclass` is the guard; plpgsql does not resolve the branch's SQL until
|
|
48
|
+
-- it executes, so naming the dropped table inside it is safe. The sqlite twin reaches the same
|
|
49
|
+
-- end with a `CREATE TABLE IF NOT EXISTS` stub of the legacy table, which is the nearest thing
|
|
50
|
+
-- that dialect has to a conditional. Same intent, two dialects, stated rather than discovered.
|
|
51
|
+
-- Not for lookups (`id` is already the PK) — it exists solely as the composite FK's parent key.
|
|
52
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "repos_id_account" ON "repos" USING btree ("id","account_id");--> statement-breakpoint
|
|
53
|
+
-- Unrelated to the FK, and the reason it rides in THIS migration: `review_threads
|
|
54
|
+
-- .original_commenter_id` had no index at all, and it is one of the three columns the footprint
|
|
55
|
+
-- CTE below unions on AND the column the per-repo footprint counts group by on every listing.
|
|
56
|
+
CREATE INDEX IF NOT EXISTS "thread_original_commenter_idx" ON "review_threads" USING btree ("original_commenter_id");--> statement-breakpoint
|
|
57
|
+
CREATE TABLE IF NOT EXISTS "repo_reviewers" (
|
|
58
|
+
"id" serial PRIMARY KEY NOT NULL,
|
|
59
|
+
"account_id" integer NOT NULL REFERENCES "accounts"("id") ON DELETE cascade,
|
|
60
|
+
"repo_id" integer NOT NULL,
|
|
61
|
+
"author_user_id" integer NOT NULL REFERENCES "users"("id"),
|
|
62
|
+
"automated" boolean NOT NULL,
|
|
63
|
+
"kind" text,
|
|
64
|
+
"label" text,
|
|
65
|
+
"identity_source" text DEFAULT 'auto' NOT NULL,
|
|
66
|
+
"role" text DEFAULT 'review' NOT NULL,
|
|
67
|
+
"confidence" text NOT NULL,
|
|
68
|
+
"source" text NOT NULL,
|
|
69
|
+
"reasons_json" jsonb,
|
|
70
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
71
|
+
CONSTRAINT "repo_reviewers_repo_account_fk" FOREIGN KEY ("repo_id","account_id") REFERENCES "repos"("id","account_id") ON DELETE cascade
|
|
72
|
+
);--> statement-breakpoint
|
|
73
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "repo_reviewers_account_repo_author" ON "repo_reviewers" USING btree ("account_id","repo_id","author_user_id");--> statement-breakpoint
|
|
74
|
+
CREATE INDEX IF NOT EXISTS "repo_reviewers_account_repo_idx" ON "repo_reviewers" USING btree ("account_id","repo_id");--> statement-breakpoint
|
|
75
|
+
CREATE INDEX IF NOT EXISTS "repo_reviewers_account_author_idx" ON "repo_reviewers" USING btree ("account_id","author_user_id");--> statement-breakpoint
|
|
76
|
+
DO $$
|
|
77
|
+
BEGIN
|
|
78
|
+
IF to_regclass('public.bot_review_classification') IS NOT NULL THEN
|
|
79
|
+
WITH footprint AS MATERIALIZED (
|
|
80
|
+
SELECT DISTINCT p.repo_id AS repo_id, x.uid AS author_user_id
|
|
81
|
+
FROM pull_requests p
|
|
82
|
+
JOIN (
|
|
83
|
+
SELECT pr_id, author_id AS uid FROM reviews WHERE author_id IS NOT NULL
|
|
84
|
+
UNION ALL
|
|
85
|
+
SELECT pr_id, original_commenter_id AS uid FROM review_threads WHERE original_commenter_id IS NOT NULL
|
|
86
|
+
UNION ALL
|
|
87
|
+
SELECT pr_id, author_id AS uid FROM pr_comments WHERE author_id IS NOT NULL
|
|
88
|
+
) x ON x.pr_id = p.id
|
|
89
|
+
)
|
|
90
|
+
INSERT INTO "repo_reviewers"
|
|
91
|
+
("account_id", "repo_id", "author_user_id", "automated", "kind", "label",
|
|
92
|
+
"identity_source", "role", "confidence", "source", "reasons_json", "updated_at")
|
|
93
|
+
-- `identity_source` is derived from the old row's `source`: the old model had ONE provenance
|
|
94
|
+
-- field covering both grains, and a manual row's kind/label WERE a human's opinion. Import
|
|
95
|
+
-- them as 'auto' and the next classification pass silently reverts a hand-made correction.
|
|
96
|
+
SELECT b.account_id, r.id, b.author_user_id, b.automated, b.kind, b.label,
|
|
97
|
+
CASE WHEN b.source = 'manual' THEN 'manual' ELSE 'auto' END,
|
|
98
|
+
'review', b.confidence, b.source, b.reasons_json, b.updated_at
|
|
99
|
+
FROM bot_review_classification b
|
|
100
|
+
JOIN repos r ON r.account_id = b.account_id
|
|
101
|
+
WHERE b.source = 'manual'
|
|
102
|
+
OR EXISTS (
|
|
103
|
+
SELECT 1 FROM footprint f
|
|
104
|
+
WHERE f.repo_id = r.id AND f.author_user_id = b.author_user_id
|
|
105
|
+
)
|
|
106
|
+
-- Never fires on a database this migration is meant for (the legacy table is unique on
|
|
107
|
+
-- (account_id, author_user_id)). It covers the state an unpushed rewrite can leave behind: a
|
|
108
|
+
-- machine that ran the deleted team-keyed draft has up to one row per (account, TEAM, author).
|
|
109
|
+
ON CONFLICT ("account_id", "repo_id", "author_user_id") DO NOTHING;
|
|
110
|
+
END IF;
|
|
111
|
+
END $$;--> statement-breakpoint
|
|
112
|
+
UPDATE "repo_reviewers"
|
|
113
|
+
SET "role" = 'quality_check'
|
|
114
|
+
WHERE "source" <> 'manual'
|
|
115
|
+
AND "author_user_id" IN (
|
|
116
|
+
SELECT "id" FROM "users" WHERE lower("github_login") IN (
|
|
117
|
+
'sonarqubecloud', 'sonarqubecloud[bot]',
|
|
118
|
+
'sonarcloud', 'sonarcloud[bot]',
|
|
119
|
+
'codecov', 'codecov[bot]',
|
|
120
|
+
'codeclimate', 'codeclimate[bot]',
|
|
121
|
+
'codefactor-io', 'codefactor-io[bot]',
|
|
122
|
+
'houndci-bot', 'houndci-bot[bot]',
|
|
123
|
+
'coveralls', 'coveralls[bot]',
|
|
124
|
+
'codacy-bot', 'codacy-bot[bot]'
|
|
125
|
+
)
|
|
126
|
+
);--> statement-breakpoint
|
|
127
|
+
DROP TABLE IF EXISTS "bot_review_classification";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
-- NORMALISE THE ACTOR GRAIN OUT OF `repo_reviewers` — the Postgres twin of the sqlite migration
|
|
2
|
+
-- 0043_account_reviewers. Hand-written additive, idempotent. Second half of the change 0029
|
|
3
|
+
-- starts:
|
|
4
|
+
--
|
|
5
|
+
-- 0029 RE-KEY (account, author) → (account, repo, author) columns unchanged
|
|
6
|
+
-- 0030 NORMALISE the actor-grain columns → their own table `account_reviewers`
|
|
7
|
+
--
|
|
8
|
+
-- WHY. After 0029, `kind` / `label` / `identity_source` sit REPLICATED on every one of an actor's
|
|
9
|
+
-- repo rows, held consistent only by convention — the same hazard that giving cost its own
|
|
10
|
+
-- storage was meant to eliminate, left in place for three other columns. It carried three
|
|
11
|
+
-- standing obligations: a new repo row had to SEED all three from its siblings; persist() had to
|
|
12
|
+
-- gate on TWO provenance flags in one row; and the account-wide identity was "a straight read of
|
|
13
|
+
-- any one of them", which elects a winner the moment two rows disagree. One row per actor deletes
|
|
14
|
+
-- all three. Full argument in the sqlite twin.
|
|
15
|
+
--
|
|
16
|
+
-- COST IS FOLDED IN rather than given a third table: a price is just another actor-level
|
|
17
|
+
-- property, and a separate table on the same key would be this one with extra steps. One table
|
|
18
|
+
-- per grain.
|
|
19
|
+
--
|
|
20
|
+
-- `monthly_cents` IS THEREFORE NULLABLE, where a standalone cost table had it NOT NULL ("no
|
|
21
|
+
-- price" was "no row"). A row that also carries identity exists for reasons unrelated to money.
|
|
22
|
+
-- That is SAFE here in a way it was not before: the old bug class was nullable-means-INHERIT,
|
|
23
|
+
-- with a fallback chain behind it, where 0 ("free HERE") and NULL ("ask my parent") had to be
|
|
24
|
+
-- told apart with `??` rather than `||`. NOTHING INHERITS ANY MORE — NULL is "no price set", 0 is
|
|
25
|
+
-- "free", two states and no chain. Clearing a price is `SET monthly_cents = NULL`, not a DELETE.
|
|
26
|
+
--
|
|
27
|
+
-- `account_reviewers` is named for its KEY, exactly as `repo_reviewers` is, so the two names ARE
|
|
28
|
+
-- the statement of the model. Not `reviewer_identities` (which would make `monthly_cents` look
|
|
29
|
+
-- like a stray) and not `reviewer_costs` (which made `kind`/`label` look the same way).
|
|
30
|
+
--
|
|
31
|
+
-- ⚠ DRAFT-STATE HOUSEKEEPING — DELETE BEFORE THIS BRANCH MERGES. `reviewer_costs` was created by
|
|
32
|
+
-- an earlier draft of this migration while the branch was unpushed and never existed in any
|
|
33
|
+
-- released version. The guarded carry-over below exists ONLY so a machine that ran that draft
|
|
34
|
+
-- keeps the prices it typed; everywhere else `to_regclass` is NULL and it does nothing. No
|
|
35
|
+
-- Postgres deployment has ever run the draft — this is here for symmetry with the sqlite twin,
|
|
36
|
+
-- which needs it, and both go at the same time.
|
|
37
|
+
CREATE TABLE IF NOT EXISTS "account_reviewers" (
|
|
38
|
+
"id" serial PRIMARY KEY NOT NULL,
|
|
39
|
+
"account_id" integer NOT NULL REFERENCES "accounts"("id") ON DELETE cascade,
|
|
40
|
+
"author_user_id" integer NOT NULL REFERENCES "users"("id"),
|
|
41
|
+
"kind" text,
|
|
42
|
+
"label" text,
|
|
43
|
+
"identity_source" text DEFAULT 'auto' NOT NULL,
|
|
44
|
+
"monthly_cents" integer,
|
|
45
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
46
|
+
);--> statement-breakpoint
|
|
47
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "account_reviewers_account_author" ON "account_reviewers" USING btree ("account_id","author_user_id");--> statement-breakpoint
|
|
48
|
+
-- ONE ROW PER ACTOR, LIFTED OFF WHICHEVER REPO ROW IS MOST AUTHORITATIVE. After 0029 an actor's
|
|
49
|
+
-- rows agree by construction, so any of them would do; the tie-break is stated because this is
|
|
50
|
+
-- exactly the "elect a winner" step the new shape abolishes, and doing it ONCE, here, with a
|
|
51
|
+
-- written rule is the honest way to abolish it: prefer a MANUAL identity, then the most recently
|
|
52
|
+
-- updated row. DISTINCT ON + ORDER BY is the same selection the sqlite twin spells with SQLite's
|
|
53
|
+
-- bare-columns-follow-the-max() rule.
|
|
54
|
+
INSERT INTO "account_reviewers"
|
|
55
|
+
("account_id", "author_user_id", "kind", "label", "identity_source", "updated_at")
|
|
56
|
+
SELECT DISTINCT ON ("account_id", "author_user_id")
|
|
57
|
+
"account_id", "author_user_id", "kind", "label", "identity_source", "updated_at"
|
|
58
|
+
FROM "repo_reviewers"
|
|
59
|
+
ORDER BY "account_id", "author_user_id", ("identity_source" = 'manual') DESC, "updated_at" DESC
|
|
60
|
+
ON CONFLICT ("account_id", "author_user_id") DO NOTHING;--> statement-breakpoint
|
|
61
|
+
DO $$
|
|
62
|
+
BEGIN
|
|
63
|
+
IF to_regclass('public.reviewer_costs') IS NOT NULL THEN
|
|
64
|
+
UPDATE "account_reviewers" ar
|
|
65
|
+
SET "monthly_cents" = rc."monthly_cents"
|
|
66
|
+
FROM "reviewer_costs" rc
|
|
67
|
+
WHERE rc."account_id" = ar."account_id"
|
|
68
|
+
AND rc."author_user_id" = ar."author_user_id";
|
|
69
|
+
END IF;
|
|
70
|
+
END $$;--> statement-breakpoint
|
|
71
|
+
DROP TABLE IF EXISTS "reviewer_costs";--> statement-breakpoint
|
|
72
|
+
-- The point of the whole file: after this, a repo row can say nothing about WHO the actor is.
|
|
73
|
+
-- `RepoReviewer` on the wire exposes no identity field and `ReviewerIdentity` exposes no
|
|
74
|
+
-- judgement field, and now the storage says the same thing.
|
|
75
|
+
ALTER TABLE "repo_reviewers" DROP COLUMN IF EXISTS "kind";--> statement-breakpoint
|
|
76
|
+
ALTER TABLE "repo_reviewers" DROP COLUMN IF EXISTS "label";--> statement-breakpoint
|
|
77
|
+
ALTER TABLE "repo_reviewers" DROP COLUMN IF EXISTS "identity_source";
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
-- TEAMS BECOME WORKSPACES, AND A REPO GETS EXACTLY ONE. Postgres / cloud mode; the SQLite twin is
|
|
2
|
+
-- migrations/0044_workspaces.sql. Migration 0032 then collapses the two bot tables onto the
|
|
3
|
+
-- workspace key — read them together, they are one change in two steps:
|
|
4
|
+
--
|
|
5
|
+
-- 0031 RE-HOME repo grouping: teams (m2m) → workspaces (1:N), + a Default per account
|
|
6
|
+
-- 0032 COLLAPSE the bot object: repo_reviewers + account_reviewers → workspace_reviewers
|
|
7
|
+
--
|
|
8
|
+
-- WORKSPACE IDS ARE THE OLD TEAM IDS. Preserved deliberately: a URL, a bookmark, a persisted
|
|
9
|
+
-- filter and (after the plugin's own 0020) a cache row all carry the number, and renumbering
|
|
10
|
+
-- would silently repoint them at a different repo set.
|
|
11
|
+
--
|
|
12
|
+
-- ⚠ THE ONE THING THAT DIFFERS BETWEEN THE DIALECTS IS THE SEQUENCE. SQLite AUTOINCREMENT tracks
|
|
13
|
+
-- max(rowid) ever seen, so an explicit-id INSERT advances it and the Default rows below get fresh
|
|
14
|
+
-- ids for free. Postgres `serial` does NOT advance on an explicit-id INSERT, so this file MUST
|
|
15
|
+
-- setval() between step 1 and step 2 or the very next workspace collides with a preserved team id.
|
|
16
|
+
-- The failure is a LOUD BOOT FAILURE, not a later 500: step 2 takes nextval = 1, collides with
|
|
17
|
+
-- preserved team id 1, `duplicate key value violates unique constraint "workspaces_pkey"` aborts
|
|
18
|
+
-- the DO block, runMigrations() throws and cloud never boots.
|
|
19
|
+
--
|
|
20
|
+
-- ⚠ ONLY STEPS 1 AND 3 ARE GUARDED by `to_regclass`, because they are the only statements that
|
|
21
|
+
-- READ `teams` / `team_repos` (this file drops both at the bottom, so a replay would otherwise
|
|
22
|
+
-- fail on "relation does not exist" rather than no-op — the pg equivalent of the sqlite twin's
|
|
23
|
+
-- `CREATE TABLE IF NOT EXISTS` legacy stubs). Steps 2 (a Default per account) and 4 (unassigned
|
|
24
|
+
-- repos → Default) are PLAIN, UNGUARDED statements. Wrapping all four — the obvious reading of
|
|
25
|
+
-- "same five steps" — would make a Postgres database that no longer has `teams` (a fresh cloud
|
|
26
|
+
-- deploy, a replay after the DROP) create NO workspaces and NO memberships at all, while the
|
|
27
|
+
-- sqlite twin's stub tables leave steps 2 and 4 always running. The runtime repair
|
|
28
|
+
-- (ensureDefaultWorkspace / ensureRepoMemberships) hides it, so the two files would silently
|
|
29
|
+
-- implement two different algorithms.
|
|
30
|
+
CREATE TABLE IF NOT EXISTS "workspaces" (
|
|
31
|
+
"id" serial PRIMARY KEY NOT NULL,
|
|
32
|
+
"account_id" integer NOT NULL REFERENCES "accounts"("id") ON DELETE cascade,
|
|
33
|
+
"name" text NOT NULL,
|
|
34
|
+
"is_default" boolean DEFAULT false NOT NULL,
|
|
35
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
36
|
+
);--> statement-breakpoint
|
|
37
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "workspaces_account_name" ON "workspaces" USING btree ("account_id","name");--> statement-breakpoint
|
|
38
|
+
CREATE INDEX IF NOT EXISTS "workspaces_account_idx" ON "workspaces" USING btree ("account_id");--> statement-breakpoint
|
|
39
|
+
-- Not for lookups (`id` is already the PK) — it is the PARENT KEY of the composite FKs on
|
|
40
|
+
-- workspace_repos and workspace_reviewers, exactly as `repos_id_account` (0029) is for those
|
|
41
|
+
-- tables' repo FKs. Postgres accepts a plain UNIQUE INDEX as an FK parent key; it does not require
|
|
42
|
+
-- a named UNIQUE CONSTRAINT. Drop it and both composite FKs become unexpressible.
|
|
43
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "workspaces_id_account" ON "workspaces" USING btree ("id","account_id");--> statement-breakpoint
|
|
44
|
+
-- 1. One workspace per existing team, IDS PRESERVED.
|
|
45
|
+
DO $$
|
|
46
|
+
BEGIN
|
|
47
|
+
IF to_regclass('public.teams') IS NOT NULL THEN
|
|
48
|
+
INSERT INTO "workspaces" ("id", "account_id", "name", "is_default", "created_at")
|
|
49
|
+
SELECT "id", "account_id", "name", false, "created_at" FROM "teams"
|
|
50
|
+
ON CONFLICT ("id") DO NOTHING;
|
|
51
|
+
END IF;
|
|
52
|
+
END $$;--> statement-breakpoint
|
|
53
|
+
-- 1b. ADVANCE THE SEQUENCE PAST THE PRESERVED IDS. No sqlite counterpart — do not "harmonise it
|
|
54
|
+
-- away". `is_called` is false on an EMPTY table so the first id is 1, not 2: passing a bare
|
|
55
|
+
-- `true` makes a fresh cloud deployment's very first workspace id 2, which reads as a bug to
|
|
56
|
+
-- whoever finds it.
|
|
57
|
+
SELECT setval(
|
|
58
|
+
pg_get_serial_sequence('workspaces','id'),
|
|
59
|
+
GREATEST(COALESCE((SELECT MAX("id") FROM "workspaces"), 0), 1),
|
|
60
|
+
COALESCE((SELECT MAX("id") FROM "workspaces"), 0) > 0
|
|
61
|
+
);--> statement-breakpoint
|
|
62
|
+
-- 2. A Default workspace for EVERY account, including accounts that never had a team.
|
|
63
|
+
-- The three-level name CASE exists because `workspaces_account_name` is unique and a user may
|
|
64
|
+
-- already own a team called "Default". The third form embeds the account id, so it cannot
|
|
65
|
+
-- collide with the first two for the same account.
|
|
66
|
+
INSERT INTO "workspaces" ("account_id", "name", "is_default", "created_at")
|
|
67
|
+
SELECT a."id",
|
|
68
|
+
CASE
|
|
69
|
+
WHEN NOT EXISTS (SELECT 1 FROM "workspaces" w WHERE w."account_id" = a."id" AND w."name" = 'Default')
|
|
70
|
+
THEN 'Default'
|
|
71
|
+
WHEN NOT EXISTS (SELECT 1 FROM "workspaces" w WHERE w."account_id" = a."id" AND w."name" = 'Default workspace')
|
|
72
|
+
THEN 'Default workspace'
|
|
73
|
+
ELSE 'Default (workspace ' || a."id" || ')'
|
|
74
|
+
END,
|
|
75
|
+
true, now()
|
|
76
|
+
FROM "accounts" a
|
|
77
|
+
WHERE NOT EXISTS (SELECT 1 FROM "workspaces" w WHERE w."account_id" = a."id" AND w."is_default");--> statement-breakpoint
|
|
78
|
+
-- 2b. ONE DEFAULT PER ACCOUNT, AS A DATABASE FACT. Created after the backfill so a hand-corrupted
|
|
79
|
+
-- database fails on a statement whose meaning is obvious. This is what makes
|
|
80
|
+
-- ensureDefaultWorkspace's "INSERT … ON CONFLICT DO NOTHING then re-SELECT" race-safe: it runs
|
|
81
|
+
-- on effectively every request, and two concurrent calls for an account with no default would
|
|
82
|
+
-- otherwise both SELECT nothing and both INSERT.
|
|
83
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "workspaces_one_default" ON "workspaces" USING btree ("account_id") WHERE "is_default";--> statement-breakpoint
|
|
84
|
+
CREATE TABLE IF NOT EXISTS "workspace_repos" (
|
|
85
|
+
"id" serial PRIMARY KEY NOT NULL,
|
|
86
|
+
"account_id" integer NOT NULL REFERENCES "accounts"("id") ON DELETE cascade,
|
|
87
|
+
"workspace_id" integer NOT NULL,
|
|
88
|
+
"repo_id" integer NOT NULL,
|
|
89
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
90
|
+
-- Tenancy as a constraint. `workspace_id` arrives in a REQUEST BODY, so a plain
|
|
91
|
+
-- `REFERENCES workspaces(id)` would accept (account 2, workspace 10) where workspace 10 belongs
|
|
92
|
+
-- to account 1 — both halves individually valid. NAMED so Postgres quotes it in the violation
|
|
93
|
+
-- message and a grep for the name finds a live constraint.
|
|
94
|
+
CONSTRAINT "workspace_repos_workspace_account_fk" FOREIGN KEY ("workspace_id","account_id") REFERENCES "workspaces"("id","account_id") ON DELETE cascade,
|
|
95
|
+
CONSTRAINT "workspace_repos_repo_account_fk" FOREIGN KEY ("repo_id","account_id") REFERENCES "repos"("id","account_id") ON DELETE cascade
|
|
96
|
+
);--> statement-breakpoint
|
|
97
|
+
-- EXACTLY ONE WORKSPACE PER REPO, AS A DATABASE FACT — and the upsert conflict target for
|
|
98
|
+
-- assignReposToWorkspace, so assigning a repo elsewhere is a MOVE and no code path can produce a
|
|
99
|
+
-- second membership row.
|
|
100
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "workspace_repos_account_repo" ON "workspace_repos" USING btree ("account_id","repo_id");--> statement-breakpoint
|
|
101
|
+
CREATE INDEX IF NOT EXISTS "workspace_repos_account_workspace_idx" ON "workspace_repos" USING btree ("account_id","workspace_id");--> statement-breakpoint
|
|
102
|
+
CREATE INDEX IF NOT EXISTS "workspace_repos_repo_idx" ON "workspace_repos" USING btree ("repo_id");--> statement-breakpoint
|
|
103
|
+
-- 3. D4's TIE-BREAK: a repo in 2+ teams keeps its EARLIEST assignment (lowest team_repos.id).
|
|
104
|
+
-- Written as a correlated MIN() rather than DISTINCT ON, which SQLite does not have and which
|
|
105
|
+
-- would therefore make the two dialects' files structurally different for no reason. The
|
|
106
|
+
-- account predicate in the subquery is redundant (repos.id is a global PK, so repo_id already
|
|
107
|
+
-- implies the account) and is spelled anyway so tenancy is visible in the statement.
|
|
108
|
+
DO $$
|
|
109
|
+
BEGIN
|
|
110
|
+
IF to_regclass('public.team_repos') IS NOT NULL THEN
|
|
111
|
+
INSERT INTO "workspace_repos" ("account_id", "workspace_id", "repo_id", "created_at")
|
|
112
|
+
SELECT tr."account_id", tr."team_id", tr."repo_id", tr."created_at"
|
|
113
|
+
FROM "team_repos" tr
|
|
114
|
+
WHERE tr."id" = (
|
|
115
|
+
SELECT MIN(tr2."id") FROM "team_repos" tr2
|
|
116
|
+
WHERE tr2."repo_id" = tr."repo_id" AND tr2."account_id" = tr."account_id")
|
|
117
|
+
ON CONFLICT ("account_id", "repo_id") DO NOTHING;
|
|
118
|
+
END IF;
|
|
119
|
+
END $$;--> statement-breakpoint
|
|
120
|
+
-- 4. Every repo with no resulting row → that account's Default. Covers repos that were in no team
|
|
121
|
+
-- at all AND (on a replay) anything step 3 could not place. UNGUARDED — see the header.
|
|
122
|
+
INSERT INTO "workspace_repos" ("account_id", "workspace_id", "repo_id", "created_at")
|
|
123
|
+
SELECT r."account_id", w."id", r."id", now()
|
|
124
|
+
FROM "repos" r
|
|
125
|
+
JOIN "workspaces" w ON w."account_id" = r."account_id" AND w."is_default"
|
|
126
|
+
WHERE NOT EXISTS (SELECT 1 FROM "workspace_repos" wr WHERE wr."repo_id" = r."id")
|
|
127
|
+
ON CONFLICT ("account_id", "repo_id") DO NOTHING;--> statement-breakpoint
|
|
128
|
+
-- 5. Child first (team_repos FKs teams), then the parent. Leaving them behind would leave a
|
|
129
|
+
-- second, differently-keyed answer to "which repos am I looking at" in every database.
|
|
130
|
+
DROP TABLE IF EXISTS "team_repos";--> statement-breakpoint
|
|
131
|
+
DROP TABLE IF EXISTS "teams";
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
-- THE BOT OBJECT COLLAPSES ONTO THE WORKSPACE KEY. Postgres / cloud mode; the SQLite twin is
|
|
2
|
+
-- migrations/0045_workspace_reviewers.sql. Second half of the change 0031 starts.
|
|
3
|
+
--
|
|
4
|
+
-- `repo_reviewers` answered "is this login a bot" once per (account, repo). `account_reviewers`
|
|
5
|
+
-- answered "who is it and what does it cost" once per (account, actor). With one workspace as the
|
|
6
|
+
-- only scope, both questions have the same key, so both tables become one row per
|
|
7
|
+
-- (account, workspace, actor) — and the fold that reads six repo rows into one metric answer
|
|
8
|
+
-- disappears with them.
|
|
9
|
+
--
|
|
10
|
+
-- ⚠ WHAT MUST NOT BE LOST WITH THE MERGE: the two PROVENANCE FLAGS stay separate columns and are
|
|
11
|
+
-- honoured INDEPENDENTLY. `source` owns automated/role/confidence/reasons; `identity_source` owns
|
|
12
|
+
-- kind/label. `monthly_cents` is owned by nothing derived — exactly one writer (setReviewerCost)
|
|
13
|
+
-- names exactly one row, and the column appears in no other `set:` object anywhere. NULL = no
|
|
14
|
+
-- price set, 0 = "recorded as free"; nothing inherits, so there is no chain behind a `??`.
|
|
15
|
+
--
|
|
16
|
+
-- ── THE FOLD RULE ────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
-- For each (account, workspace, actor) with at least one repo_reviewers row on a repo now in
|
|
18
|
+
-- that workspace:
|
|
19
|
+
-- automated = TRUE if ANY contributing row is automated (union — this half DOES
|
|
20
|
+
-- role = 'review' if ANY contributing row roles it 'review' reproduce resolveJudgements
|
|
21
|
+
-- else 'quality_check' exactly, for READS)
|
|
22
|
+
-- confidence = the highest confidence among the rows that carried the WINNING `automated` value
|
|
23
|
+
-- (folding the max across all rows lets an automated=false row hand `high` to an
|
|
24
|
+
-- automated=true verdict it disagreed with)
|
|
25
|
+
-- updated_at = MAX of the contributing rows
|
|
26
|
+
--
|
|
27
|
+
-- source = 'manual' if ANY contributing row is manual, regardless of which side won.
|
|
28
|
+
-- ⚠ THIS IS DELIBERATELY *NOT* THE READ-TIME UNION RULE, and the difference is the
|
|
29
|
+
-- whole point. For reads, `manualHuman` is only consulted when nothing in scope
|
|
30
|
+
-- calls the actor automated. But `source` is not only a read input: it is the WRITE
|
|
31
|
+
-- GATE in persist() and the flag behind the "Reset classification" affordance.
|
|
32
|
+
-- Folding it to 'auto' would let the next classification pass silently overwrite a
|
|
33
|
+
-- human's opinion, with no control offered to undo it and no trace in reasons_json.
|
|
34
|
+
-- A visible, resettable pin the user can see is strictly better than a judgement
|
|
35
|
+
-- that vanishes. ELSE the source of the winning evidence — carried, not invented:
|
|
36
|
+
-- the highest confidence row on the winning side of `automated`, ties broken by
|
|
37
|
+
-- updated_at DESC then repo_id ASC so both dialects pick the same row. ⚠ NEVER the
|
|
38
|
+
-- literal 'auto': that is the `identity_source` vocabulary. `ClassificationSource`
|
|
39
|
+
-- is 'manual'|'vendor_login'|'github_type'|'app_attribution'|'fingerprint'|
|
|
40
|
+
-- 'behavioral'|'ai_tiebreak' and an out-of-union value would never self-heal.
|
|
41
|
+
-- 'fingerprint' is the fallback if the subquery somehow finds nothing.
|
|
42
|
+
--
|
|
43
|
+
-- reasons_json = SYNTHESISED, not carried. One repo row's reason text does not describe a
|
|
44
|
+
-- workspace verdict, the field is advisory display evidence, and the next
|
|
45
|
+
-- classification pass overwrites it for every non-manual row. ⚠ A fold where a
|
|
46
|
+
-- manual row LOST gets an explicit conflict string, so the one case where a human's
|
|
47
|
+
-- judgement was superseded is visible on the card rather than inferred from a
|
|
48
|
+
-- mismatched provenance flag.
|
|
49
|
+
--
|
|
50
|
+
-- kind, label, identity_source = copied from the actor's account_reviewers row. Account-grain
|
|
51
|
+
-- today, so they REPLICATE into each workspace the actor appears in. That
|
|
52
|
+
-- replication is the accepted D5 consequence: from here on they are per-workspace
|
|
53
|
+
-- facts and may legitimately diverge.
|
|
54
|
+
-- monthly_cents = copied into every workspace row of that actor, on exactly the same footing.
|
|
55
|
+
-- This is the faithful carry-over of a value that WAS account-wide — a ONE-TIME
|
|
56
|
+
-- SEED, not an invariant. From here on price is a per-workspace fact like kind and
|
|
57
|
+
-- label: setReviewerCost writes ONE row, nothing fans out, nothing seeds a later
|
|
58
|
+
-- row, and the copies diverge freely as the user edits them.
|
|
59
|
+
--
|
|
60
|
+
-- Actors with an account_reviewers row but NO repo_reviewers row anywhere are DROPPED — the
|
|
61
|
+
-- listing has always been row-driven, and such an identity could never be displayed, edited or
|
|
62
|
+
-- cleared (the same rule the 404 on the old identity route enforced).
|
|
63
|
+
--
|
|
64
|
+
-- ⚠ DIALECT DIVERGENCES THAT MAY NOT BE "UNIFIED" (the sqlite twin is the reference for each):
|
|
65
|
+
-- • `bool_or(rr.automated)` — pg has no `max(boolean)`; `MAX()` would fail to plan.
|
|
66
|
+
-- • bare booleans (`rr.automated`, `f.aut`) where sqlite writes `= 1`.
|
|
67
|
+
-- • BUT `manual_aut` / `manual_any` / `any_review` are INTEGER `MAX(CASE … 1 ELSE 0 END)` in
|
|
68
|
+
-- BOTH dialects, so they must still be compared with `= 1` here. `CASE WHEN f.aut AND
|
|
69
|
+
-- f.manual_aut THEN …` is a HARD TYPE ERROR — Postgres has no implicit integer→boolean cast
|
|
70
|
+
-- and raises `argument of AND must be type boolean, not type integer`, aborting the whole
|
|
71
|
+
-- migration. (`rr2.automated = f.aut` is boolean = boolean here and integer = integer in
|
|
72
|
+
-- sqlite, so that one line is genuinely identical in both files.)
|
|
73
|
+
-- • `::jsonb` on the synthesised reasons — the pg column is jsonb, not text.
|
|
74
|
+
-- • the backfill is wrapped in a `to_regclass` guard rather than preceded by legacy-table stubs:
|
|
75
|
+
-- this file drops the tables its own backfill reads, and pg has conditional DDL while sqlite
|
|
76
|
+
-- does not. Same intent, two dialects, stated rather than discovered.
|
|
77
|
+
-- • no `WHERE true` before `ON CONFLICT` — that disambiguator is a sqlite parsing quirk only.
|
|
78
|
+
CREATE TABLE IF NOT EXISTS "workspace_reviewers" (
|
|
79
|
+
"id" serial PRIMARY KEY NOT NULL,
|
|
80
|
+
"account_id" integer NOT NULL REFERENCES "accounts"("id") ON DELETE cascade,
|
|
81
|
+
"workspace_id" integer NOT NULL,
|
|
82
|
+
-- No cascade: `users` is GLOBAL storage shared by every account and is never deleted.
|
|
83
|
+
"author_user_id" integer NOT NULL REFERENCES "users"("id"),
|
|
84
|
+
"automated" boolean NOT NULL,
|
|
85
|
+
"role" text DEFAULT 'review' NOT NULL,
|
|
86
|
+
"confidence" text NOT NULL,
|
|
87
|
+
"source" text NOT NULL,
|
|
88
|
+
"reasons_json" jsonb,
|
|
89
|
+
"kind" text,
|
|
90
|
+
"label" text,
|
|
91
|
+
"identity_source" text DEFAULT 'auto' NOT NULL,
|
|
92
|
+
"monthly_cents" integer,
|
|
93
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
94
|
+
CONSTRAINT "workspace_reviewers_workspace_account_fk" FOREIGN KEY ("workspace_id","account_id") REFERENCES "workspaces"("id","account_id") ON DELETE cascade
|
|
95
|
+
);--> statement-breakpoint
|
|
96
|
+
-- The upsert conflict target for EVERY writer. A stale target type-checks perfectly and raises
|
|
97
|
+
-- "no unique or exclusion constraint matching the ON CONFLICT specification" at RUNTIME, in both
|
|
98
|
+
-- dialects, only when a row is actually written.
|
|
99
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "workspace_reviewers_account_workspace_author" ON "workspace_reviewers" USING btree ("account_id","workspace_id","author_user_id");--> statement-breakpoint
|
|
100
|
+
CREATE INDEX IF NOT EXISTS "workspace_reviewers_account_workspace_idx" ON "workspace_reviewers" USING btree ("account_id","workspace_id");--> statement-breakpoint
|
|
101
|
+
CREATE INDEX IF NOT EXISTS "workspace_reviewers_account_author_idx" ON "workspace_reviewers" USING btree ("account_id","author_user_id");--> statement-breakpoint
|
|
102
|
+
DO $$
|
|
103
|
+
BEGIN
|
|
104
|
+
IF to_regclass('public.repo_reviewers') IS NOT NULL THEN
|
|
105
|
+
INSERT INTO "workspace_reviewers"
|
|
106
|
+
("account_id", "workspace_id", "author_user_id", "automated", "role", "confidence", "source",
|
|
107
|
+
"reasons_json", "kind", "label", "identity_source", "monthly_cents", "updated_at")
|
|
108
|
+
SELECT f."account_id", f."workspace_id", f."author_user_id",
|
|
109
|
+
f."aut",
|
|
110
|
+
CASE WHEN f."any_review" = 1 THEN 'review' ELSE 'quality_check' END,
|
|
111
|
+
CASE WHEN f."aut" THEN
|
|
112
|
+
CASE f."conf_aut" WHEN 3 THEN 'high' WHEN 2 THEN 'medium' ELSE 'low' END
|
|
113
|
+
ELSE
|
|
114
|
+
CASE f."conf_any" WHEN 3 THEN 'high' WHEN 2 THEN 'medium' ELSE 'low' END
|
|
115
|
+
END,
|
|
116
|
+
-- source: 'manual' if ANY contributing row was manual (the pin survives, visibly);
|
|
117
|
+
-- otherwise the winning row's OWN source — a real ClassificationSource member, never
|
|
118
|
+
-- 'auto'.
|
|
119
|
+
CASE WHEN f."manual_any" = 1 THEN 'manual' ELSE COALESCE((
|
|
120
|
+
SELECT rr2."source"
|
|
121
|
+
FROM "repo_reviewers" rr2
|
|
122
|
+
JOIN "workspace_repos" wr2
|
|
123
|
+
ON wr2."repo_id" = rr2."repo_id" AND wr2."account_id" = rr2."account_id"
|
|
124
|
+
WHERE rr2."account_id" = f."account_id"
|
|
125
|
+
AND wr2."workspace_id" = f."workspace_id"
|
|
126
|
+
AND rr2."author_user_id" = f."author_user_id"
|
|
127
|
+
AND rr2."automated" = f."aut"
|
|
128
|
+
ORDER BY CASE rr2."confidence" WHEN 'high' THEN 3 WHEN 'medium' THEN 2 ELSE 1 END DESC,
|
|
129
|
+
rr2."updated_at" DESC, rr2."repo_id" ASC
|
|
130
|
+
LIMIT 1), 'fingerprint') END,
|
|
131
|
+
CASE WHEN f."aut" AND f."manual_aut" = 1
|
|
132
|
+
THEN '["manually tagged as an automated reviewer"]'::jsonb
|
|
133
|
+
WHEN NOT f."aut" AND f."manual_any" = 1
|
|
134
|
+
THEN '["manually confirmed as a human"]'::jsonb
|
|
135
|
+
-- The one lossy case, named out loud on the card rather than left to be inferred.
|
|
136
|
+
WHEN f."manual_any" = 1
|
|
137
|
+
THEN '["migrated from the per-repo classification","⚠ conflicting per-repo judgements were merged — review this"]'::jsonb
|
|
138
|
+
ELSE '["migrated from the per-repo classification"]'::jsonb END,
|
|
139
|
+
ar."kind", ar."label", COALESCE(ar."identity_source", 'auto'), ar."monthly_cents",
|
|
140
|
+
f."upd"
|
|
141
|
+
FROM (
|
|
142
|
+
SELECT rr."account_id" AS "account_id",
|
|
143
|
+
wr."workspace_id" AS "workspace_id",
|
|
144
|
+
rr."author_user_id" AS "author_user_id",
|
|
145
|
+
bool_or(rr."automated") AS "aut",
|
|
146
|
+
MAX(CASE WHEN COALESCE(rr."role",'review') <> 'quality_check' THEN 1 ELSE 0 END) AS "any_review",
|
|
147
|
+
MAX(CASE WHEN rr."automated"
|
|
148
|
+
THEN CASE rr."confidence" WHEN 'high' THEN 3 WHEN 'medium' THEN 2 ELSE 1 END
|
|
149
|
+
ELSE 0 END) AS "conf_aut",
|
|
150
|
+
MAX(CASE rr."confidence" WHEN 'high' THEN 3 WHEN 'medium' THEN 2 ELSE 1 END) AS "conf_any",
|
|
151
|
+
MAX(CASE WHEN rr."source" = 'manual' AND rr."automated" THEN 1 ELSE 0 END) AS "manual_aut",
|
|
152
|
+
MAX(CASE WHEN rr."source" = 'manual' THEN 1 ELSE 0 END) AS "manual_any",
|
|
153
|
+
MAX(rr."updated_at") AS "upd"
|
|
154
|
+
FROM "repo_reviewers" rr
|
|
155
|
+
JOIN "workspace_repos" wr
|
|
156
|
+
ON wr."repo_id" = rr."repo_id" AND wr."account_id" = rr."account_id"
|
|
157
|
+
GROUP BY rr."account_id", wr."workspace_id", rr."author_user_id"
|
|
158
|
+
) f
|
|
159
|
+
LEFT JOIN "account_reviewers" ar
|
|
160
|
+
ON ar."account_id" = f."account_id" AND ar."author_user_id" = f."author_user_id"
|
|
161
|
+
ON CONFLICT ("account_id", "workspace_id", "author_user_id") DO NOTHING;
|
|
162
|
+
END IF;
|
|
163
|
+
END $$;--> statement-breakpoint
|
|
164
|
+
DROP TABLE IF EXISTS "repo_reviewers";--> statement-breakpoint
|
|
165
|
+
DROP TABLE IF EXISTS "account_reviewers";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- Drop the "watched" concept — the Postgres twin of the sqlite migration 0046_drop_inbox_watch.
|
|
2
|
+
--
|
|
3
|
+
-- `repos.inbox_watch` / `repos.inbox_watch_started_at` were a second visibility axis on top of
|
|
4
|
+
-- "is this repo added to the account", quietly narrowing the Feed, recent activity, My Turn and
|
|
5
|
+
-- the Pro digest collection to a watched subset. With Workspaces the Workspace IS the scope, so
|
|
6
|
+
-- every repo in a Workspace is now fully live.
|
|
7
|
+
--
|
|
8
|
+
-- The property that mattered — adding a repo with 400 open PRs must not dump them all into My
|
|
9
|
+
-- Turn on day one — moves to `repos.created_at` (when the repo was ADDED), which already exists
|
|
10
|
+
-- and is NOT NULL, so there is no backfill.
|
|
11
|
+
--
|
|
12
|
+
-- `IF EXISTS` on both, so a database that somehow never had them replays cleanly.
|
|
13
|
+
ALTER TABLE "repos" DROP COLUMN IF EXISTS "inbox_watch";--> statement-breakpoint
|
|
14
|
+
ALTER TABLE "repos" DROP COLUMN IF EXISTS "inbox_watch_started_at";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-- Postgres twin of migrations/0047_ml_comment_labels.sql — ML severity/category labels for
|
|
2
|
+
-- BOT-authored text (CORE, free tier, no LLM). HAND-WRITTEN ADDITIVE, like every pg migration
|
|
3
|
+
-- since 0023: never regenerate the baseline with `pnpm db:generate:pg`, which squashes it.
|
|
4
|
+
-- Index names are byte-identical to the sqlite twin (they are the drizzle index names).
|
|
5
|
+
CREATE TABLE IF NOT EXISTS "ml_comment_labels" (
|
|
6
|
+
"id" serial PRIMARY KEY NOT NULL,
|
|
7
|
+
"account_id" integer NOT NULL,
|
|
8
|
+
"repo_id" integer NOT NULL,
|
|
9
|
+
"pr_id" integer NOT NULL,
|
|
10
|
+
"target_kind" text NOT NULL,
|
|
11
|
+
"target_id" integer NOT NULL,
|
|
12
|
+
"author_user_id" integer NOT NULL,
|
|
13
|
+
"severity" text NOT NULL,
|
|
14
|
+
"severity_ord" integer NOT NULL,
|
|
15
|
+
"severity_prob" double precision NOT NULL,
|
|
16
|
+
"categories" jsonb NOT NULL,
|
|
17
|
+
"category_probs" jsonb NOT NULL,
|
|
18
|
+
"is_summary" boolean NOT NULL,
|
|
19
|
+
"backend" text NOT NULL,
|
|
20
|
+
"model_version" text NOT NULL,
|
|
21
|
+
"body_hash" text NOT NULL,
|
|
22
|
+
"target_created_at" timestamp with time zone NOT NULL,
|
|
23
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
24
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
25
|
+
);
|
|
26
|
+
--> statement-breakpoint
|
|
27
|
+
ALTER TABLE "ml_comment_labels" ADD CONSTRAINT "ml_comment_labels_account_id_accounts_id_fk" FOREIGN KEY ("account_id") REFERENCES "public"."accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
28
|
+
ALTER TABLE "ml_comment_labels" ADD CONSTRAINT "ml_comment_labels_repo_id_repos_id_fk" FOREIGN KEY ("repo_id") REFERENCES "public"."repos"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
29
|
+
ALTER TABLE "ml_comment_labels" ADD CONSTRAINT "ml_comment_labels_pr_id_pull_requests_id_fk" FOREIGN KEY ("pr_id") REFERENCES "public"."pull_requests"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
30
|
+
ALTER TABLE "ml_comment_labels" ADD CONSTRAINT "ml_comment_labels_author_user_id_users_id_fk" FOREIGN KEY ("author_user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
31
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "mcl_account_target" ON "ml_comment_labels" USING btree ("account_id","target_kind","target_id");--> statement-breakpoint
|
|
32
|
+
CREATE INDEX IF NOT EXISTS "mcl_account_pr_idx" ON "ml_comment_labels" USING btree ("account_id","pr_id");--> statement-breakpoint
|
|
33
|
+
CREATE INDEX IF NOT EXISTS "mcl_account_repo_author_idx" ON "ml_comment_labels" USING btree ("account_id","repo_id","author_user_id");
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- The review bot's OWN declared severity — the Postgres twin of sqlite
|
|
2
|
+
-- 0048_ml_vendor_severity.sql. HAND-WRITTEN ADDITIVE, like every pg migration since 0023:
|
|
3
|
+
-- never regenerate the baseline with `pnpm db:generate:pg`, which squashes it.
|
|
4
|
+
--
|
|
5
|
+
-- See the sqlite twin for the full argument. In short: the severity-api's marker reader already
|
|
6
|
+
-- extracts the vendor's self-declared severity and we were discarding it, so it is stored beside
|
|
7
|
+
-- ours to be DISPLAYED next to it — never folded into it. On `gold_v2_sample` our model scores
|
|
8
|
+
-- 0.700 exact / 0.303 ordinal MAE against the vendor badge's 0.474 / 0.697, so the vendor's claim
|
|
9
|
+
-- is the less accurate of the two; nothing derives our severity from it.
|
|
10
|
+
--
|
|
11
|
+
-- Both columns are nullable: most comments carry no vendor badge, and an older severity-api
|
|
12
|
+
-- omits the fields entirely (the client reads them defensively → null). No index (read out of an
|
|
13
|
+
-- already-fetched row, never a predicate) and no backfill (labels are not re-scored; `pnpm
|
|
14
|
+
-- ml:enrich --reset` is the refresh gesture).
|
|
15
|
+
ALTER TABLE "ml_comment_labels" ADD COLUMN IF NOT EXISTS "vendor_severity" text;--> statement-breakpoint
|
|
16
|
+
ALTER TABLE "ml_comment_labels" ADD COLUMN IF NOT EXISTS "vendor_severity_confidence" text;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- Per-seat bot pricing + repo descriptions — the Postgres twin of sqlite
|
|
2
|
+
-- 0049_seat_pricing_repo_description.sql. HAND-WRITTEN ADDITIVE, like every pg migration
|
|
3
|
+
-- since 0023: never regenerate the baseline with `pnpm db:generate:pg`, which squashes it.
|
|
4
|
+
--
|
|
5
|
+
-- See the sqlite twin for the full argument. In short: `cost_model` says how `monthly_cents`
|
|
6
|
+
-- is read ('flat' = whole-workspace subscription, the default so every existing row keeps its
|
|
7
|
+
-- meaning; 'per_seat' = unit price × the workspace's derived human seat count, product never
|
|
8
|
+
-- stored), with ownership identical to `monthly_cents` (one writer, `setReviewerCost`).
|
|
9
|
+
-- `repos.description` is the GitHub "About" text captured by the activity sync — grounding
|
|
10
|
+
-- for the workspace-purpose sprint-chat preset.
|
|
11
|
+
ALTER TABLE "repos" ADD COLUMN IF NOT EXISTS "description" text;--> statement-breakpoint
|
|
12
|
+
ALTER TABLE "workspace_reviewers" ADD COLUMN IF NOT EXISTS "cost_model" text DEFAULT 'flat' NOT NULL;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- Bot Tuning Advisor discovery: capture the GitHub App slug behind a bot's comments — the
|
|
2
|
+
-- Postgres twin of sqlite 0050_users_app_slug.sql. HAND-WRITTEN ADDITIVE, like every pg
|
|
3
|
+
-- migration since 0023: never regenerate the baseline with `pnpm db:generate:pg`, which
|
|
4
|
+
-- squashes it.
|
|
5
|
+
--
|
|
6
|
+
-- See the sqlite twin for the full argument. In short: `users.app_slug` persists the
|
|
7
|
+
-- `performed_via_github_app.slug` the app-attribution probe already receives (global fact,
|
|
8
|
+
-- global table, nullable, never cleared by a later app-less comment).
|
|
9
|
+
ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "app_slug" text;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Auto-merge ("merge when ready") learns GitHub's native merge queue — the Postgres twin of
|
|
2
|
+
-- sqlite 0051_auto_merge_queue.sql. HAND-WRITTEN ADDITIVE, like every pg migration since
|
|
3
|
+
-- 0023: never regenerate the baseline with `pnpm db:generate:pg`, which squashes it.
|
|
4
|
+
--
|
|
5
|
+
-- See the sqlite twin for the full argument. In short: `via_merge_queue` (arm-time fact —
|
|
6
|
+
-- the watcher enqueues instead of direct-merging) + `enqueued_at` (the watcher's own
|
|
7
|
+
-- enqueue, the attribution record for 'merged' vs 'disarmed_blocked').
|
|
8
|
+
ALTER TABLE "auto_merge_requests" ADD COLUMN IF NOT EXISTS "via_merge_queue" boolean DEFAULT false NOT NULL;
|
|
9
|
+
--> statement-breakpoint
|
|
10
|
+
ALTER TABLE "auto_merge_requests" ADD COLUMN IF NOT EXISTS "enqueued_at" timestamp with time zone;
|