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,255 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm';
|
|
2
|
+
import { db, runTransaction, schema } from './client.js';
|
|
3
|
+
import { deleteRepo } from './queries.js';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Right to erasure (UK/EU GDPR Art. 17, CCPA "right to delete").
|
|
6
|
+
//
|
|
7
|
+
// A privacy policy that promises deletion and a codebase with no delete path is worse than
|
|
8
|
+
// having neither — so this is the machinery behind that promise, not a soft "deactivate".
|
|
9
|
+
// It removes the account row, its sealed GitHub token, every repository it added, every PR
|
|
10
|
+
// subtree under those repositories, and every account-keyed row in between.
|
|
11
|
+
//
|
|
12
|
+
// Two design rules:
|
|
13
|
+
//
|
|
14
|
+
// 1. REUSE deleteRepo per repository rather than hand-rolling a second cascade. Almost none
|
|
15
|
+
// of the core schema declares `ON DELETE CASCADE` (the workspace tables and
|
|
16
|
+
// auto_merge_requests are the exceptions), so the FK-safe delete order is intricate and has
|
|
17
|
+
// already been got wrong twice (ci_status_events was missing from both delete paths at one
|
|
18
|
+
// point, which 500'd repo deletion). One implementation, one place to fix. Where a cascade
|
|
19
|
+
// DOES exist, this file still deletes explicitly and child-first: SQLite enforces FKs only
|
|
20
|
+
// under `foreign_keys=ON` while Postgres enforces them immediately, and an erasure promise
|
|
21
|
+
// must not depend on which dialect is running.
|
|
22
|
+
//
|
|
23
|
+
// 2. Enumerate the account-level tables EXPLICITLY and keep this list next to the schema in
|
|
24
|
+
// review. A table added later that carries `accountId` and is not listed here becomes a
|
|
25
|
+
// silent erasure leak — data that survives a deletion the user was told was complete.
|
|
26
|
+
// `eraseAccountData` is covered by a test that asserts every accountId-bearing table is
|
|
27
|
+
// empty afterwards, so the omission fails CI rather than shipping.
|
|
28
|
+
//
|
|
29
|
+
// The `users` and `commitFiles` tables are deliberately NOT touched: they are global,
|
|
30
|
+
// deduplicated across tenants (a GitHub login is the same person for everyone, an immutable
|
|
31
|
+
// commit's file list is the same list), so deleting rows there would corrupt other accounts'
|
|
32
|
+
// data. `users` holds only what GitHub publishes on a public profile — login, display name,
|
|
33
|
+
// avatar — and never anything the erasing user contributed.
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
const { accounts, repos, aiUsage, workspaces, workspaceRepos, workspaceReviewers, benchmarkContributions, searchIndex, autoMergeRequests, branchCommits, trunkCiStatusEvents, mlCommentLabels, prMentions, pendingMutedRepos, myTurnDismissals, repoFileCoupling, } = schema;
|
|
36
|
+
const erasureHandlers = [];
|
|
37
|
+
export function registerAccountErasureHandler(h) {
|
|
38
|
+
erasureHandlers.push(h);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Irreversibly delete everything belonging to one account. Returns how many repositories
|
|
42
|
+
* were removed. Safe to call for an id that no longer exists (returns 0).
|
|
43
|
+
*/
|
|
44
|
+
export async function eraseAccountData(accountId) {
|
|
45
|
+
// 1. Per-repository subtrees first, via the single canonical cascade. Sequential rather
|
|
46
|
+
// than parallel: each call opens its own transaction, and on SQLite a second concurrent
|
|
47
|
+
// write transaction would contend on the database lock.
|
|
48
|
+
const repoRows = await db
|
|
49
|
+
.select({ id: repos.id })
|
|
50
|
+
.from(repos)
|
|
51
|
+
.where(eq(repos.accountId, accountId))
|
|
52
|
+
.execute();
|
|
53
|
+
let reposDeleted = 0;
|
|
54
|
+
for (const r of repoRows) {
|
|
55
|
+
if (await deleteRepo(r.id, accountId))
|
|
56
|
+
reposDeleted += 1;
|
|
57
|
+
}
|
|
58
|
+
// 2. Plugin-owned tables, BEFORE the account row disappears — a handler may want to read
|
|
59
|
+
// the account while resolving its own rows. A plugin failure must not abort the core
|
|
60
|
+
// erasure: the user asked to be deleted, and leaving them half-deleted because an
|
|
61
|
+
// optional plugin threw is the worse outcome. Logged by the caller.
|
|
62
|
+
for (const handler of erasureHandlers) {
|
|
63
|
+
await handler({ accountId });
|
|
64
|
+
}
|
|
65
|
+
// 3. Account-level core tables, then the account itself. One transaction so an account can
|
|
66
|
+
// never be left present-but-stripped (or absent-but-referenced) if this fails midway.
|
|
67
|
+
await runTransaction(async (tx) => {
|
|
68
|
+
// Workspaces and everything keyed to one, CHILD BEFORE PARENT. All three tables carry
|
|
69
|
+
// `accountId`, so each delete is a single indexed predicate and none of them depends on
|
|
70
|
+
// another's result set — but the ORDER still matters and is not belt-and-braces alone:
|
|
71
|
+
//
|
|
72
|
+
// workspace_reviewers ─┐
|
|
73
|
+
// ├─ composite FK (workspace_id, account_id) → workspaces(id, account_id)
|
|
74
|
+
// workspace_repos ─────┘ ON DELETE CASCADE
|
|
75
|
+
//
|
|
76
|
+
// Postgres checks FKs immediately, so deleting `workspaces` first would rely entirely on the
|
|
77
|
+
// cascade firing; SQLite only enforces them at all with `foreign_keys=ON`. Deleting the
|
|
78
|
+
// children explicitly, first, makes the erasure independent of both — the cascades then find
|
|
79
|
+
// nothing to do, which is exactly the property the checklist below asserts.
|
|
80
|
+
//
|
|
81
|
+
// `workspace_reviewers` is the one that must never be left to a cascade: it holds the manual
|
|
82
|
+
// judgements, the human-set vendor names and `monthly_cents` — a price the user typed — so a
|
|
83
|
+
// row surviving here survives an erasure the user was told was complete.
|
|
84
|
+
await tx
|
|
85
|
+
.delete(workspaceReviewers)
|
|
86
|
+
.where(eq(workspaceReviewers.accountId, accountId))
|
|
87
|
+
.execute();
|
|
88
|
+
// Membership rows for surviving repos are already gone with their repos (the repo half of
|
|
89
|
+
// the composite FK cascades), but a workspace whose repos were all deleted, and any row the
|
|
90
|
+
// repo loop above could not reach, still carries this account's id.
|
|
91
|
+
await tx.delete(workspaceRepos).where(eq(workspaceRepos.accountId, accountId)).execute();
|
|
92
|
+
// The REPO half of the Pending mute (migration 0058 / pg 0045). Deleted BEFORE `workspaces`
|
|
93
|
+
// only for tidiness — it has no FK to a workspace at all (a repo belongs to exactly one
|
|
94
|
+
// workspace already, so copying that fact onto the mute row would give the account two
|
|
95
|
+
// answers to it). Its composite FK to `repos` cascades and the repo loop above has normally
|
|
96
|
+
// taken it; explicit for the same reason as every other entry here — the guarantee must not
|
|
97
|
+
// depend on which dialect enforces FKs, nor on the repo loop having succeeded. The row is a
|
|
98
|
+
// preference the user expressed by hand.
|
|
99
|
+
await tx
|
|
100
|
+
.delete(pendingMutedRepos)
|
|
101
|
+
.where(eq(pendingMutedRepos.accountId, accountId))
|
|
102
|
+
.execute();
|
|
103
|
+
// My Turn dismissals (migration 0069 / pg 0056). Both composite FKs cascade and the repo loop
|
|
104
|
+
// has normally taken every row; explicit for the same dialect-agnostic reason.
|
|
105
|
+
await tx
|
|
106
|
+
.delete(myTurnDismissals)
|
|
107
|
+
.where(eq(myTurnDismissals.accountId, accountId))
|
|
108
|
+
.execute();
|
|
109
|
+
await tx.delete(workspaces).where(eq(workspaces.accountId, accountId)).execute();
|
|
110
|
+
// Claude Review chat messages (migration 0073 / pg 0060). The repo loop has normally taken them
|
|
111
|
+
// with their runs; explicit because each row is text this user typed or paid to have written.
|
|
112
|
+
await tx
|
|
113
|
+
.delete(schema.claudeReviewChatMessages)
|
|
114
|
+
.where(eq(schema.claudeReviewChatMessages.accountId, accountId))
|
|
115
|
+
.execute();
|
|
116
|
+
// AI Fix runs (patches of the user's code + the prompts they were built from). Core since
|
|
117
|
+
// migration 0074 / pg 0061 — the plugin's erasure hook used to own them.
|
|
118
|
+
await tx.delete(schema.aiFixes).where(eq(schema.aiFixes.accountId, accountId)).execute();
|
|
119
|
+
// The AI spend ledger (token/credit counts — no prompt text).
|
|
120
|
+
await tx.delete(aiUsage).where(eq(aiUsage.accountId, accountId)).execute();
|
|
121
|
+
// Any aggregate rows contributed to the cross-org benchmark. Consent was the basis for
|
|
122
|
+
// these, so withdrawal-by-deletion must remove them too.
|
|
123
|
+
await tx
|
|
124
|
+
.delete(benchmarkContributions)
|
|
125
|
+
.where(eq(benchmarkContributions.accountId, accountId))
|
|
126
|
+
.execute();
|
|
127
|
+
// Full-text search rows are keyed by (accountId, …) and are copies of comment/PR text.
|
|
128
|
+
await tx.delete(searchIndex).where(eq(searchIndex.accountId, accountId)).execute();
|
|
129
|
+
// ML severity/category labels. Their FKs cascade from repos/pull_requests, so the repo loop
|
|
130
|
+
// above has normally taken them already — explicit for the same reason as every other entry
|
|
131
|
+
// here: the guarantee must not depend on which dialect enforces FKs, or on the repo loop
|
|
132
|
+
// having succeeded. Each row is a machine judgement ABOUT this account's bot comments.
|
|
133
|
+
await tx
|
|
134
|
+
.delete(mlCommentLabels)
|
|
135
|
+
.where(eq(mlCommentLabels.accountId, accountId))
|
|
136
|
+
.execute();
|
|
137
|
+
// "@you" mention rows (migration 0056 / pg 0043). Same reasoning as the labels above — the
|
|
138
|
+
// repo loop and the cascading repo/PR FKs normally take them, and this makes the guarantee
|
|
139
|
+
// independent of both. Each row records that a named person typed THIS user's login on a
|
|
140
|
+
// specific PR, which is exactly the kind of trace an erasure promise covers.
|
|
141
|
+
await tx.delete(prMentions).where(eq(prMentions.accountId, accountId)).execute();
|
|
142
|
+
// Standing auto-merge intents. Their FKs cascade from pull_requests, so deleteRepo above
|
|
143
|
+
// has already taken most of them — but a row is only as safe as the repo loop that ran,
|
|
144
|
+
// and an intent naming a PR id is a record of what this user was about to ship. Explicit.
|
|
145
|
+
await tx
|
|
146
|
+
.delete(autoMergeRequests)
|
|
147
|
+
.where(eq(autoMergeRequests.accountId, accountId))
|
|
148
|
+
.execute();
|
|
149
|
+
// Default-branch commit snapshots (author names, commit subjects) — same reasoning: the
|
|
150
|
+
// repo cascade normally clears them, this makes the guarantee independent of it.
|
|
151
|
+
await tx.delete(branchCommits).where(eq(branchCommits.accountId, accountId)).execute();
|
|
152
|
+
// The trunk CI transition log (migration 0052 / pg 0039). Same reasoning again: the repo
|
|
153
|
+
// loop and the cascade normally clear it, and this makes the guarantee independent of both.
|
|
154
|
+
// Unlike `ci_status_events` it is NOT anchored to a PR, so the time-based retention sweep
|
|
155
|
+
// can never reach it either — the only bounds are its own per-repo trim and this delete.
|
|
156
|
+
await tx
|
|
157
|
+
.delete(trunkCiStatusEvents)
|
|
158
|
+
.where(eq(trunkCiStatusEvents.accountId, accountId))
|
|
159
|
+
.execute();
|
|
160
|
+
// Finally the account: identity + the AES-256-GCM sealed GitHub token.
|
|
161
|
+
await tx.delete(accounts).where(eq(accounts.id, accountId)).execute();
|
|
162
|
+
});
|
|
163
|
+
return { reposDeleted };
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Every core table that carries an `accountId`, with its column — the checklist
|
|
167
|
+
* `eraseAccountData` is tested against. Exported so the test iterates THIS list rather than a
|
|
168
|
+
* copy of it, which is what makes "a new accountId table was added and not erased" a test
|
|
169
|
+
* failure instead of a silent leak.
|
|
170
|
+
*/
|
|
171
|
+
export function accountScopedTables() {
|
|
172
|
+
const { pullRequests, events, claudeReviews } = schema;
|
|
173
|
+
const rows = [
|
|
174
|
+
{ name: 'accounts', col: accounts.id, table: accounts },
|
|
175
|
+
{ name: 'repos', col: repos.accountId, table: repos },
|
|
176
|
+
{ name: 'pullRequests', col: pullRequests.accountId, table: pullRequests },
|
|
177
|
+
{ name: 'events', col: events.accountId, table: events },
|
|
178
|
+
{ name: 'claudeReviews', col: claudeReviews.accountId, table: claudeReviews },
|
|
179
|
+
// Claude Review chat messages (migration 0073 / pg 0060) — the questions a person typed and the
|
|
180
|
+
// answers they paid for. deleteRepo takes them with their runs; erased explicitly below too.
|
|
181
|
+
{
|
|
182
|
+
name: 'claudeReviewChatMessages',
|
|
183
|
+
col: schema.claudeReviewChatMessages.accountId,
|
|
184
|
+
table: schema.claudeReviewChatMessages,
|
|
185
|
+
},
|
|
186
|
+
{ name: 'aiUsage', col: aiUsage.accountId, table: aiUsage },
|
|
187
|
+
// Adopted from the plugin (migration 0074 / pg 0061) with AI Fix's fixer. No FKs, so nothing
|
|
188
|
+
// cascades: the explicit delete above is the whole guarantee. (`reviewLearnings`, review
|
|
189
|
+
// memory's table, sat here until migration 0075 / pg 0062 DROPPED it.)
|
|
190
|
+
{ name: 'aiFixes', col: schema.aiFixes.accountId, table: schema.aiFixes },
|
|
191
|
+
// `myTurnDismissals` sat here until migration 0060 / pg 0047 DROPPED the table. It is named
|
|
192
|
+
// rather than silently absent because this list is a checklist, and a checklist that shortens
|
|
193
|
+
// with no explanation reads as an omission — the exact failure this function guards against.
|
|
194
|
+
// THE WORKSPACE TRIO (migrations 0044/0045). FOUR entries left here when they arrived —
|
|
195
|
+
// `repoReviewers`, `accountReviewers`, `teams`, `teamRepos` — and THREE replaced them. The net
|
|
196
|
+
// drop of one is correct and intended: the two bot tables collapsed onto a single
|
|
197
|
+
// (account, workspace, actor) row. It is written out because an off-by-one in a checklist is
|
|
198
|
+
// exactly how a table gets missed.
|
|
199
|
+
{ name: 'workspaces', col: workspaces.accountId, table: workspaces },
|
|
200
|
+
// workspaceRepos carries its OWN accountId (not only workspaceId/repoId) — it must be on the
|
|
201
|
+
// checklist, or a membership row could survive an erasure carrying this account's id.
|
|
202
|
+
{ name: 'workspaceRepos', col: workspaceRepos.accountId, table: workspaceRepos },
|
|
203
|
+
// The bot object, now one grain. It holds the human judgements, the human-set vendor names
|
|
204
|
+
// and `monthly_cents` — the one column no classifier can regenerate — so this is the entry
|
|
205
|
+
// whose omission would cost the user data they entered by hand.
|
|
206
|
+
{ name: 'workspaceReviewers', col: workspaceReviewers.accountId, table: workspaceReviewers },
|
|
207
|
+
{
|
|
208
|
+
name: 'benchmarkContributions',
|
|
209
|
+
col: benchmarkContributions.accountId,
|
|
210
|
+
table: benchmarkContributions,
|
|
211
|
+
},
|
|
212
|
+
{ name: 'searchIndex', col: searchIndex.accountId, table: searchIndex },
|
|
213
|
+
{
|
|
214
|
+
name: 'autoMergeRequests',
|
|
215
|
+
col: autoMergeRequests.accountId,
|
|
216
|
+
table: autoMergeRequests,
|
|
217
|
+
},
|
|
218
|
+
{ name: 'branchCommits', col: branchCommits.accountId, table: branchCommits },
|
|
219
|
+
// The trunk CI transition log. On the checklist rather than in the KNOWN_UNCHECKED
|
|
220
|
+
// exemption that `ci_status_events` sits in, because this one has NO parent PR: neither the
|
|
221
|
+
// retention sweep nor `deletePrSubtree` can ever reach it, so `deleteRepo` + this delete are
|
|
222
|
+
// the ONLY things that bound it.
|
|
223
|
+
{
|
|
224
|
+
name: 'trunkCiStatusEvents',
|
|
225
|
+
col: trunkCiStatusEvents.accountId,
|
|
226
|
+
table: trunkCiStatusEvents,
|
|
227
|
+
},
|
|
228
|
+
{ name: 'mlCommentLabels', col: mlCommentLabels.accountId, table: mlCommentLabels },
|
|
229
|
+
// "@you was mentioned on this PR" (migration 0056 / pg 0043).
|
|
230
|
+
{ name: 'prMentions', col: prMentions.accountId, table: prMentions },
|
|
231
|
+
// The REPO half of the Pending mute (migration 0058 / pg 0045). The WORKSPACE half is a
|
|
232
|
+
// COLUMN on `workspaces`, already on this checklist, so there is nothing separate to count
|
|
233
|
+
// for it — the row it lives on is deleted.
|
|
234
|
+
{ name: 'pendingMutedRepos', col: pendingMutedRepos.accountId, table: pendingMutedRepos },
|
|
235
|
+
// My Turn dismissals (migration 0069 / pg 0056) — one timestamp per dismissed PR or red branch.
|
|
236
|
+
{ name: 'myTurnDismissals', col: myTurnDismissals.accountId, table: myTurnDismissals },
|
|
237
|
+
// The blast-radius co-change index (migration 0063 / pg 0050). Purely DERIVED — erasing it
|
|
238
|
+
// loses nothing but a rebuild — and it is on the checklist anyway, because the rule is "does
|
|
239
|
+
// the table carry an accountId", not "is the data precious". A derived table left behind
|
|
240
|
+
// still holds this account's file PATHS.
|
|
241
|
+
{ name: 'repoFileCoupling', col: repoFileCoupling.accountId, table: repoFileCoupling },
|
|
242
|
+
];
|
|
243
|
+
return rows.map(({ name, col, table }) => ({
|
|
244
|
+
name,
|
|
245
|
+
count: async (accountId) => {
|
|
246
|
+
const found = await db
|
|
247
|
+
.select({ id: col })
|
|
248
|
+
.from(table)
|
|
249
|
+
.where(and(eq(col, accountId)))
|
|
250
|
+
.execute();
|
|
251
|
+
return found.length;
|
|
252
|
+
},
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=erase-account.js.map
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { eq, inArray } from 'drizzle-orm';
|
|
2
|
+
import { db, schema } from './client.js';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Right of access + data portability (UK/EU GDPR Arts. 15 and 20).
|
|
5
|
+
//
|
|
6
|
+
// One JSON document containing everything the server holds for one account, in a
|
|
7
|
+
// "commonly used, machine-readable format" as Art. 20 requires. Deliberately built from
|
|
8
|
+
// explicit column lists rather than `select()` so that:
|
|
9
|
+
//
|
|
10
|
+
// • the sealed GitHub token can NEVER appear in the output. `accounts.accessTokenEnc` is
|
|
11
|
+
// encrypted, but an export is a file a user emails around and drops in cloud storage —
|
|
12
|
+
// shipping even a sealed credential in it would be a self-inflicted key-exposure risk,
|
|
13
|
+
// and it is not personal data the subject needs. Same for `stripeCustomerId` beyond the
|
|
14
|
+
// fact a subscription exists.
|
|
15
|
+
// • adding a column to a table cannot silently widen the export.
|
|
16
|
+
//
|
|
17
|
+
// Scope note: the export includes the third-party GitHub activity synced into this account
|
|
18
|
+
// (other people's comments and logins). That is correct — it is the data held in connection
|
|
19
|
+
// with this account, and the subject is entitled to see what is there — but it means an
|
|
20
|
+
// export can contain other people's personal data, which is why the download is
|
|
21
|
+
// same-origin-only, behind the session, and rate-limited like every other route.
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
const { accounts, repos, pullRequests, reviews, reviewThreads, reviewComments, prComments, events, workspaces, workspaceRepos, workspaceReviewers, aiUsage, benchmarkContributions, autoMergeRequests, } = schema;
|
|
24
|
+
/** Cap per collection. An export must not be a way to ask the server to build a 2 GB string;
|
|
25
|
+
* the caps are far above a normal account and the response records where one bit. */
|
|
26
|
+
const EXPORT_ROW_CAP = 50_000;
|
|
27
|
+
/** Build the full export document for one account. Returns null if the account is gone. */
|
|
28
|
+
export async function exportAccountData(accountId) {
|
|
29
|
+
const truncated = [];
|
|
30
|
+
const capped = (collection, rows) => {
|
|
31
|
+
if (rows.length > EXPORT_ROW_CAP) {
|
|
32
|
+
truncated.push({ collection, cap: EXPORT_ROW_CAP });
|
|
33
|
+
return rows.slice(0, EXPORT_ROW_CAP);
|
|
34
|
+
}
|
|
35
|
+
return rows;
|
|
36
|
+
};
|
|
37
|
+
const accountRows = await db
|
|
38
|
+
.select({
|
|
39
|
+
id: accounts.id,
|
|
40
|
+
githubUserId: accounts.githubUserId,
|
|
41
|
+
githubLogin: accounts.githubLogin,
|
|
42
|
+
displayName: accounts.displayName,
|
|
43
|
+
avatarUrl: accounts.avatarUrl,
|
|
44
|
+
isLocal: accounts.isLocal,
|
|
45
|
+
plan: accounts.plan,
|
|
46
|
+
// Deliberately NOT exported: accessTokenEnc (a credential, even sealed) and
|
|
47
|
+
// stripeCustomerId (Stripe's own identifier — request billing data from Stripe).
|
|
48
|
+
hasStoredGithubToken: accounts.accessTokenEnc,
|
|
49
|
+
aiCreditAllowance: accounts.aiCreditAllowance,
|
|
50
|
+
benchmarkOptIn: accounts.benchmarkOptIn,
|
|
51
|
+
largePrCodeLocThreshold: accounts.largePrCodeLocThreshold,
|
|
52
|
+
blastRadiusConfig: accounts.blastRadiusConfig,
|
|
53
|
+
myTurnSettings: accounts.myTurnSettings,
|
|
54
|
+
lastLoginAt: accounts.lastLoginAt,
|
|
55
|
+
lastActiveAt: accounts.lastActiveAt,
|
|
56
|
+
createdAt: accounts.createdAt,
|
|
57
|
+
})
|
|
58
|
+
.from(accounts)
|
|
59
|
+
.where(eq(accounts.id, accountId))
|
|
60
|
+
.limit(1)
|
|
61
|
+
.execute();
|
|
62
|
+
const account = accountRows[0];
|
|
63
|
+
if (!account)
|
|
64
|
+
return null;
|
|
65
|
+
const repoRows = await db
|
|
66
|
+
.select()
|
|
67
|
+
.from(repos)
|
|
68
|
+
.where(eq(repos.accountId, accountId))
|
|
69
|
+
.execute();
|
|
70
|
+
const prRows = capped('pullRequests', await db.select().from(pullRequests).where(eq(pullRequests.accountId, accountId)).execute());
|
|
71
|
+
const prIds = prRows.map((p) => p.id);
|
|
72
|
+
// The child tables reach their account through prId. An account with no PRs short-circuits
|
|
73
|
+
// to empty arrays rather than issuing an `IN ()`.
|
|
74
|
+
const childRows = async (fn) => (prIds.length === 0 ? [] : fn(prIds));
|
|
75
|
+
const [reviewRows, threadRows, reviewCommentRows, prCommentRows, eventRows, workspaceRows, workspaceRepoRows, usageRows, workspaceReviewerRows, benchmarkRows, autoMergeRows,] = await Promise.all([
|
|
76
|
+
childRows((ids) => db.select().from(reviews).where(inArray(reviews.prId, ids)).execute()),
|
|
77
|
+
childRows((ids) => db.select().from(reviewThreads).where(inArray(reviewThreads.prId, ids)).execute()),
|
|
78
|
+
childRows((ids) => db.select().from(reviewComments).where(inArray(reviewComments.prId, ids)).execute()),
|
|
79
|
+
childRows((ids) => db.select().from(prComments).where(inArray(prComments.prId, ids)).execute()),
|
|
80
|
+
db.select().from(events).where(eq(events.accountId, accountId)).execute(),
|
|
81
|
+
// Workspaces + their memberships. Explicit column lists, per this file's rule: adding a
|
|
82
|
+
// column to either table must be a deliberate decision to export it, not a side effect.
|
|
83
|
+
db
|
|
84
|
+
.select({
|
|
85
|
+
id: workspaces.id,
|
|
86
|
+
accountId: workspaces.accountId,
|
|
87
|
+
name: workspaces.name,
|
|
88
|
+
isDefault: workspaces.isDefault,
|
|
89
|
+
createdAt: workspaces.createdAt,
|
|
90
|
+
// Chronology's working hours and budgets — settings the user TYPED, so Art. 15 material,
|
|
91
|
+
// exported deliberately (NULL = every default).
|
|
92
|
+
flowSettings: workspaces.flowSettings,
|
|
93
|
+
})
|
|
94
|
+
.from(workspaces)
|
|
95
|
+
.where(eq(workspaces.accountId, accountId))
|
|
96
|
+
.execute(),
|
|
97
|
+
// Scoped by accountId rather than by the repo id list: the membership table carries its own
|
|
98
|
+
// accountId, so this needs no `IN ()` guard and cannot be skewed by the pullRequests cap.
|
|
99
|
+
db
|
|
100
|
+
.select({ workspaceId: workspaceRepos.workspaceId, repoId: workspaceRepos.repoId })
|
|
101
|
+
.from(workspaceRepos)
|
|
102
|
+
.where(eq(workspaceRepos.accountId, accountId))
|
|
103
|
+
.execute(),
|
|
104
|
+
db.select().from(aiUsage).where(eq(aiUsage.accountId, accountId)).execute(),
|
|
105
|
+
// THE BOT OBJECT, one row per (workspace, reviewer). See the payload field's comment for why
|
|
106
|
+
// it is Art. 15 material: classification decisions, a vendor name and a price the user typed.
|
|
107
|
+
db
|
|
108
|
+
.select({
|
|
109
|
+
id: workspaceReviewers.id,
|
|
110
|
+
accountId: workspaceReviewers.accountId,
|
|
111
|
+
workspaceId: workspaceReviewers.workspaceId,
|
|
112
|
+
authorUserId: workspaceReviewers.authorUserId,
|
|
113
|
+
automated: workspaceReviewers.automated,
|
|
114
|
+
role: workspaceReviewers.role,
|
|
115
|
+
confidence: workspaceReviewers.confidence,
|
|
116
|
+
source: workspaceReviewers.source,
|
|
117
|
+
reasonsJson: workspaceReviewers.reasonsJson,
|
|
118
|
+
kind: workspaceReviewers.kind,
|
|
119
|
+
label: workspaceReviewers.label,
|
|
120
|
+
identitySource: workspaceReviewers.identitySource,
|
|
121
|
+
// ⚠ THE PRICE IS EXPORTED EVEN THOUGH THE PRODUCT SURFACES STRIP IT, and that is a
|
|
122
|
+
// decision, not an oversight. Reading a seat price on `GET /api/bot-reviewers` (and the
|
|
123
|
+
// three sibling routes that echo a reviewer row) needs the paid `botDepth` entitlement —
|
|
124
|
+
// `stripCost` in api/routes/bot-triage.ts. This export does not: Art. 15/20 are about the
|
|
125
|
+
// SUBJECT'S OWN DATA, and `monthly_cents` is a number this account typed in. A downgraded
|
|
126
|
+
// account can therefore still export prices it can no longer see in the UI, which is the
|
|
127
|
+
// correct answer for a portability endpoint and the wrong one for a feature tier. If that
|
|
128
|
+
// is ever reversed, it must be reversed HERE and recorded in bot-triage.ts's `stripCost`
|
|
129
|
+
// note, which names this file as the one exemption.
|
|
130
|
+
monthlyCents: workspaceReviewers.monthlyCents,
|
|
131
|
+
// The price's reading rule ('flat' | 'per_seat') — user-typed the same way the price is.
|
|
132
|
+
costModel: workspaceReviewers.costModel,
|
|
133
|
+
updatedAt: workspaceReviewers.updatedAt,
|
|
134
|
+
})
|
|
135
|
+
.from(workspaceReviewers)
|
|
136
|
+
.where(eq(workspaceReviewers.accountId, accountId))
|
|
137
|
+
.execute(),
|
|
138
|
+
db
|
|
139
|
+
.select()
|
|
140
|
+
.from(benchmarkContributions)
|
|
141
|
+
.where(eq(benchmarkContributions.accountId, accountId))
|
|
142
|
+
.execute(),
|
|
143
|
+
// "Merge when ready" intents. A record of an action the user asked the server to take on
|
|
144
|
+
// their behalf (and of merges it performed), so it belongs in an Art. 15 export even
|
|
145
|
+
// though the rows are small and short-lived.
|
|
146
|
+
db
|
|
147
|
+
.select()
|
|
148
|
+
.from(autoMergeRequests)
|
|
149
|
+
.where(eq(autoMergeRequests.accountId, accountId))
|
|
150
|
+
.execute(),
|
|
151
|
+
]);
|
|
152
|
+
return {
|
|
153
|
+
meta: {
|
|
154
|
+
generatedAt: new Date().toISOString(),
|
|
155
|
+
format: 'pierre-account-export',
|
|
156
|
+
formatVersion: 1,
|
|
157
|
+
truncated,
|
|
158
|
+
notes: [
|
|
159
|
+
'Your stored GitHub access token is deliberately excluded: it is a credential, not ' +
|
|
160
|
+
'personal data you need, and an export file is not a safe place for one.',
|
|
161
|
+
'Payment and card data is held by Stripe, never by this server — request it from Stripe.',
|
|
162
|
+
'This export includes GitHub activity by OTHER people (their logins and the comments ' +
|
|
163
|
+
'they wrote) because that is what was synced into your account. Handle it accordingly.',
|
|
164
|
+
'Repository content is a cache of GitHub data and is regenerable from GitHub at any time.',
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
account: {
|
|
168
|
+
...account,
|
|
169
|
+
// Report only the PRESENCE of a token, never the sealed value.
|
|
170
|
+
hasStoredGithubToken: Boolean(account.hasStoredGithubToken),
|
|
171
|
+
},
|
|
172
|
+
repositories: repoRows,
|
|
173
|
+
workspaces: workspaceRows.map((w) => ({
|
|
174
|
+
...w,
|
|
175
|
+
repoIds: workspaceRepoRows.filter((wr) => wr.workspaceId === w.id).map((wr) => wr.repoId),
|
|
176
|
+
})),
|
|
177
|
+
pullRequests: prRows,
|
|
178
|
+
reviews: capped('reviews', reviewRows),
|
|
179
|
+
reviewThreads: capped('reviewThreads', threadRows),
|
|
180
|
+
reviewComments: capped('reviewComments', reviewCommentRows),
|
|
181
|
+
prComments: capped('prComments', prCommentRows),
|
|
182
|
+
events: capped('events', eventRows),
|
|
183
|
+
aiUsage: usageRows,
|
|
184
|
+
workspaceReviewers: workspaceReviewerRows,
|
|
185
|
+
benchmarkContributions: benchmarkRows,
|
|
186
|
+
autoMergeRequests: autoMergeRows,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=export-account.js.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// ── WHICH CHECKS ARE FAILING — the Pending cards' names line (CORE, no AI, no GitHub call) ─────
|
|
2
|
+
//
|
|
3
|
+
// A red CI label on a Pending card says THAT the build failed; these readers say WHICH checks. Both
|
|
4
|
+
// read rows the sync already stores, so the board still fetches nothing on mount:
|
|
5
|
+
//
|
|
6
|
+
// • A PR — the NEWEST `ci_status_events` row whose `head_sha` is the PR's CURRENT head. That log
|
|
7
|
+
// is written in the same transaction as `pull_requests.ci_status` (sync/upsert.ts, not
|
|
8
|
+
// lean-gated), so the dot and the names come from one observation. ⚠ IT IS A TRANSITION LOG,
|
|
9
|
+
// NOT A SNAPSHOT: a row for an older head describes code that is no longer the PR, and a newer
|
|
10
|
+
// non-red row means the red set it once named is gone — both give NO names.
|
|
11
|
+
// • A default branch — the head commit's `branch_commits` row, through THE ONE READER
|
|
12
|
+
// `/api/branch-status` uses (`trunkHeadFailingChecks`, db/branch-queries.ts).
|
|
13
|
+
//
|
|
14
|
+
// ⚠ BARE NAMES, never workflow-prefixed: the PR side stores no workflow name, and one card
|
|
15
|
+
// vocabulary for both sides beats a richer trunk line (the Feed's CI items made the same call).
|
|
16
|
+
// ⚠ "NO NAMES" IS NOT "NOTHING FAILED". A cancelled-only failure is recorded as neutral
|
|
17
|
+
// (`checkContextState`), and a tolerant GitHub response that nulled the contexts writes `[]` — the
|
|
18
|
+
// card then shows its CI label alone, never "0 failing".
|
|
19
|
+
// ⚠ DISPLAY ONLY. These names must never reach a card's `detail`, a work-plan fact, or any hashed
|
|
20
|
+
// or model payload — they are third-party text and they move on every push.
|
|
21
|
+
import { and, eq, inArray } from 'drizzle-orm';
|
|
22
|
+
import { FAILING_CHECKS_SHOWN } from '../shared/index.js';
|
|
23
|
+
import { db, schema } from './client.js';
|
|
24
|
+
import { isRedCiStatus } from './pending-classify.js';
|
|
25
|
+
import { trunkHeadFailingChecks, trunkHeadKey } from './branch-queries.js';
|
|
26
|
+
const { ciStatusEvents } = schema;
|
|
27
|
+
/** The longest check name a card carries — the trunk writer's own slice (sync/branch-status.ts).
|
|
28
|
+
* Check names are chosen by CI vendors; the PR-side log stores them uncapped. */
|
|
29
|
+
export const MAX_CHECK_NAME_CHARS = 200;
|
|
30
|
+
/**
|
|
31
|
+
* Names → the card's pair: each sliced to `MAX_CHECK_NAME_CHARS`, blanks dropped, deduped,
|
|
32
|
+
* alphabetical, the first `cap` kept, the total counted over ALL of them. null when nothing
|
|
33
|
+
* remains — an empty list is "no names", which the card renders as nothing.
|
|
34
|
+
*/
|
|
35
|
+
export function summariseFailingChecks(names, cap = FAILING_CHECKS_SHOWN) {
|
|
36
|
+
const unique = [
|
|
37
|
+
...new Set(names.flatMap((n) => {
|
|
38
|
+
const t = (n ?? '').trim().slice(0, MAX_CHECK_NAME_CHARS);
|
|
39
|
+
return t.length > 0 ? [t] : [];
|
|
40
|
+
})),
|
|
41
|
+
].sort((a, b) => a.localeCompare(b, 'en') || (a < b ? -1 : a > b ? 1 : 0));
|
|
42
|
+
if (unique.length === 0)
|
|
43
|
+
return null;
|
|
44
|
+
return { failingChecks: unique.slice(0, cap), failingCheckTotal: unique.length };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The failing checks of each RED PR's CURRENT head, keyed by PR id.
|
|
48
|
+
*
|
|
49
|
+
* Only PRs whose `ciStatus` is red and whose `headSha` is known are asked about. One account-scoped
|
|
50
|
+
* select; the `prId IN × headSha IN` predicate over-matches (PR 1's row for a sha only PR 2 asked
|
|
51
|
+
* about), so rows are kept only for a REQUESTED `(prId, headSha)` pair. The newest row per PR wins
|
|
52
|
+
* by `observedAt`, then `id` (the CI-history backfill synthesizes rows at commit time, so two rows
|
|
53
|
+
* can share a second). That row is used only when ITS status is red.
|
|
54
|
+
*/
|
|
55
|
+
export async function prFailingChecks(accountId, prs) {
|
|
56
|
+
const out = new Map();
|
|
57
|
+
const asked = new Map();
|
|
58
|
+
for (const p of prs) {
|
|
59
|
+
if (p.headSha != null && isRedCiStatus(p.ciStatus))
|
|
60
|
+
asked.set(p.id, p.headSha);
|
|
61
|
+
}
|
|
62
|
+
if (asked.size === 0)
|
|
63
|
+
return out;
|
|
64
|
+
const rows = await db
|
|
65
|
+
.select({
|
|
66
|
+
id: ciStatusEvents.id,
|
|
67
|
+
prId: ciStatusEvents.prId,
|
|
68
|
+
headSha: ciStatusEvents.headSha,
|
|
69
|
+
status: ciStatusEvents.status,
|
|
70
|
+
failingChecks: ciStatusEvents.failingChecks,
|
|
71
|
+
observedAt: ciStatusEvents.observedAt,
|
|
72
|
+
})
|
|
73
|
+
.from(ciStatusEvents)
|
|
74
|
+
.where(and(eq(ciStatusEvents.accountId, accountId), inArray(ciStatusEvents.prId, [...asked.keys()]), inArray(ciStatusEvents.headSha, [...new Set(asked.values())])))
|
|
75
|
+
.execute();
|
|
76
|
+
const newest = new Map();
|
|
77
|
+
for (const r of rows) {
|
|
78
|
+
if (asked.get(r.prId) !== r.headSha)
|
|
79
|
+
continue;
|
|
80
|
+
const cur = newest.get(r.prId);
|
|
81
|
+
if (cur == null ||
|
|
82
|
+
r.observedAt.getTime() > cur.observedAt.getTime() ||
|
|
83
|
+
(r.observedAt.getTime() === cur.observedAt.getTime() && r.id > cur.id)) {
|
|
84
|
+
newest.set(r.prId, r);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const [prId, r] of newest) {
|
|
88
|
+
if (!isRedCiStatus(r.status))
|
|
89
|
+
continue;
|
|
90
|
+
const summary = summariseFailingChecks(r.failingChecks ?? []);
|
|
91
|
+
if (summary != null)
|
|
92
|
+
out.set(prId, { headSha: r.headSha, ...summary });
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The failing checks of each red default-branch HEAD, keyed by `trunkHeadKey(repoId, sha)` —
|
|
98
|
+
* through the one reader `/api/branch-status` uses, mapped to bare names. Independent of whether a
|
|
99
|
+
* landing PR resolved: a direct push to trunk has failing checks too.
|
|
100
|
+
*/
|
|
101
|
+
export async function trunkFailingChecks(accountId, heads) {
|
|
102
|
+
const out = new Map();
|
|
103
|
+
const pairs = heads.flatMap((h) => (h.sha != null ? [{ repoId: h.repoId, sha: h.sha }] : []));
|
|
104
|
+
if (pairs.length === 0)
|
|
105
|
+
return out;
|
|
106
|
+
for (const [key, runs] of await trunkHeadFailingChecks(accountId, pairs)) {
|
|
107
|
+
const summary = summariseFailingChecks(runs.map((r) => r.name));
|
|
108
|
+
if (summary != null)
|
|
109
|
+
out.set(key, summary);
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
/** The REQUIRED pair on `CiFailingCard` / `MyTurnTrunkCard`: the names, or null for both when
|
|
114
|
+
* none are known (never an empty list, never a 0). */
|
|
115
|
+
export function failingChecksFields(s) {
|
|
116
|
+
return s != null
|
|
117
|
+
? { failingChecks: s.failingChecks, failingCheckTotal: s.failingCheckTotal }
|
|
118
|
+
: { failingChecks: null, failingCheckTotal: null };
|
|
119
|
+
}
|
|
120
|
+
/** The trunk map's lookup for one head — the `trunkHeadKey` spelling, never re-typed. */
|
|
121
|
+
export function trunkFailingFor(map, repoId, sha) {
|
|
122
|
+
return sha != null ? map.get(trunkHeadKey(repoId, sha)) : undefined;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=failing-checks.js.map
|