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,343 @@
|
|
|
1
|
+
// MY TURN DISMISSALS + ONE CARD PER PR (CORE, free, both modes, no AI).
|
|
2
|
+
//
|
|
3
|
+
// Two rules the My Turn fold (`getMyTurn`) applies, kept here so they can be read — and tested —
|
|
4
|
+
// apart from the 700-line fold that calls them.
|
|
5
|
+
//
|
|
6
|
+
// ── 1. A DISMISSAL SETS ONE ITEM DOWN UNTIL SOMETHING NEW HAPPENS ────────────────────────────
|
|
7
|
+
//
|
|
8
|
+
// The reader can take a subject — a pull request, or a repository for a red default branch — off
|
|
9
|
+
// their plate. ⚠ This is NOT the "Done" button migration 0060 deleted, and the difference is the
|
|
10
|
+
// whole design. That one stored "I dealt with this" and never expired, so a PR whose ball came back
|
|
11
|
+
// stayed hidden behind it for weeks. This one stores a TIMESTAMP and lets the fold decide:
|
|
12
|
+
//
|
|
13
|
+
// • an item on a dismissed subject is hidden only while its OWN clock (the moment the thing that
|
|
14
|
+
// needs you happened — `MyTurnPr.since`, a thread's `lastReplyAt`, …) is at or before the
|
|
15
|
+
// dismissal. Anything LATER shows again, and only that item: a new reply comes back as a reply,
|
|
16
|
+
// not as the review request you set down;
|
|
17
|
+
// • a subject with NO My Turn item at all — you acted, it closed, the request was withdrawn —
|
|
18
|
+
// DISCHARGES its row (`dischargeable`), so the next summons starts fresh rather than arriving
|
|
19
|
+
// already dismissed. That is what stops a dismissal outliving the thing it dismissed.
|
|
20
|
+
//
|
|
21
|
+
// It is applied INSIDE `getMyTurn`, so the board, the brief's counts, the badges, the browser
|
|
22
|
+
// notification and the CLI all stop showing a dismissed item together — the Settings-gate rule.
|
|
23
|
+
//
|
|
24
|
+
// ── 2. ONE CARD PER PR ON THE BOARD (`onePerPr`) ─────────────────────────────────────────────
|
|
25
|
+
//
|
|
26
|
+
// A PR can hold several My Turn jobs at once (a review requested of you AND a reply in your thread
|
|
27
|
+
// AND your own unanswered thread). The My turn tab shows ONE: the job highest in the READER'S type
|
|
28
|
+
// order (Settings → My Turn — the tab's own grouping), and within one type the one that has waited
|
|
29
|
+
// longest (the scorer's pick too: same PR, same type ⇒ same proximity and relevance, so the older
|
|
30
|
+
// clock scores higher and wins `compareScored`'s age tie-break). Ties beyond that break on the
|
|
31
|
+
// item's own id, so two reads of unchanged data keep the same card.
|
|
32
|
+
//
|
|
33
|
+
// ⚠ IT IS A BOARD RULE, NOT A NOTIFICATION RULE. `getWorkspaceInsights` asks for it; the
|
|
34
|
+
// account-wide `GET /api/my-turn` does not, because the browser-notification watcher diffs item
|
|
35
|
+
// ids — a deduplicated list would flip the winner whenever the top job cleared and announce the
|
|
36
|
+
// runner-up as "new". Red default branches are repo-grained and never collide with a PR.
|
|
37
|
+
import { and, eq, inArray } from 'drizzle-orm';
|
|
38
|
+
import { db, schema } from './client.js';
|
|
39
|
+
const { myTurnDismissals, pullRequests, repos } = schema;
|
|
40
|
+
export const subjectKey = (t) => `${t.kind}:${t.id}`;
|
|
41
|
+
/** Every dismissal the account holds. One indexed select plus a join for the PR's repo/state. */
|
|
42
|
+
export async function readMyTurnDismissals(accountId) {
|
|
43
|
+
const rows = await db
|
|
44
|
+
.select({
|
|
45
|
+
prId: myTurnDismissals.prId,
|
|
46
|
+
repoId: myTurnDismissals.repoId,
|
|
47
|
+
dismissedAt: myTurnDismissals.dismissedAt,
|
|
48
|
+
prRepoId: pullRequests.repoId,
|
|
49
|
+
prState: pullRequests.state,
|
|
50
|
+
})
|
|
51
|
+
.from(myTurnDismissals)
|
|
52
|
+
.leftJoin(pullRequests, eq(pullRequests.id, myTurnDismissals.prId))
|
|
53
|
+
.where(eq(myTurnDismissals.accountId, accountId))
|
|
54
|
+
.execute();
|
|
55
|
+
const out = [];
|
|
56
|
+
for (const r of rows) {
|
|
57
|
+
if (r.prId != null && r.prRepoId != null) {
|
|
58
|
+
out.push({
|
|
59
|
+
target: { kind: 'pr', id: r.prId },
|
|
60
|
+
dismissedAtMs: r.dismissedAt.getTime(),
|
|
61
|
+
repoId: r.prRepoId,
|
|
62
|
+
closed: r.prState !== 'open',
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else if (r.repoId != null) {
|
|
66
|
+
out.push({
|
|
67
|
+
target: { kind: 'repo', id: r.repoId },
|
|
68
|
+
dismissedAtMs: r.dismissedAt.getTime(),
|
|
69
|
+
repoId: r.repoId,
|
|
70
|
+
closed: false,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The dismissal's timestamp, ROUNDED UP to the whole second. SQLite stores `mode: 'timestamp'` in
|
|
78
|
+
* whole seconds, so an unrounded write would land up to 999ms EARLY and an item that happened just
|
|
79
|
+
* before the press would compare as "after" it and stay on screen. Rounded the same way in both
|
|
80
|
+
* dialects, so the rule is one rule.
|
|
81
|
+
*/
|
|
82
|
+
export function dismissalInstant(nowMs) {
|
|
83
|
+
return new Date(Math.ceil(nowMs / 1000) * 1000);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Dismiss one subject. Null when the subject is not this account's — the route answers 404, so
|
|
87
|
+
* the family is not an existence oracle. ⚠ The composite FK would refuse a cross-account pair
|
|
88
|
+
* anyway; this read exists to answer with a 404 rather than a constraint error.
|
|
89
|
+
*
|
|
90
|
+
* Re-dismissing moves the timestamp forward: "and whatever arrived since, too".
|
|
91
|
+
*/
|
|
92
|
+
export async function dismissMyTurn(accountId, target, nowMs = Date.now()) {
|
|
93
|
+
const at = dismissalInstant(nowMs);
|
|
94
|
+
if (target.kind === 'pr') {
|
|
95
|
+
const owned = await db
|
|
96
|
+
.select({ id: pullRequests.id })
|
|
97
|
+
.from(pullRequests)
|
|
98
|
+
.where(and(eq(pullRequests.id, target.id), eq(pullRequests.accountId, accountId)))
|
|
99
|
+
.limit(1)
|
|
100
|
+
.execute();
|
|
101
|
+
if (owned[0] == null)
|
|
102
|
+
return null;
|
|
103
|
+
await db
|
|
104
|
+
.insert(myTurnDismissals)
|
|
105
|
+
.values({ accountId, prId: target.id, repoId: null, dismissedAt: at })
|
|
106
|
+
.onConflictDoUpdate({
|
|
107
|
+
target: [myTurnDismissals.accountId, myTurnDismissals.prId],
|
|
108
|
+
set: { dismissedAt: at },
|
|
109
|
+
})
|
|
110
|
+
.execute();
|
|
111
|
+
return at;
|
|
112
|
+
}
|
|
113
|
+
const owned = await db
|
|
114
|
+
.select({ id: repos.id })
|
|
115
|
+
.from(repos)
|
|
116
|
+
.where(and(eq(repos.id, target.id), eq(repos.accountId, accountId)))
|
|
117
|
+
.limit(1)
|
|
118
|
+
.execute();
|
|
119
|
+
if (owned[0] == null)
|
|
120
|
+
return null;
|
|
121
|
+
await db
|
|
122
|
+
.insert(myTurnDismissals)
|
|
123
|
+
.values({ accountId, prId: null, repoId: target.id, dismissedAt: at })
|
|
124
|
+
.onConflictDoUpdate({
|
|
125
|
+
target: [myTurnDismissals.accountId, myTurnDismissals.repoId],
|
|
126
|
+
set: { dismissedAt: at },
|
|
127
|
+
})
|
|
128
|
+
.execute();
|
|
129
|
+
return at;
|
|
130
|
+
}
|
|
131
|
+
/** Bring a subject back. True when a dismissal existed. Scoped by account on the row itself. */
|
|
132
|
+
export async function restoreMyTurn(accountId, target) {
|
|
133
|
+
const col = target.kind === 'pr' ? myTurnDismissals.prId : myTurnDismissals.repoId;
|
|
134
|
+
const deleted = await db
|
|
135
|
+
.delete(myTurnDismissals)
|
|
136
|
+
.where(and(eq(myTurnDismissals.accountId, accountId), eq(col, target.id)))
|
|
137
|
+
.returning({ id: myTurnDismissals.id })
|
|
138
|
+
.execute();
|
|
139
|
+
return deleted.length > 0;
|
|
140
|
+
}
|
|
141
|
+
/** Drop the rows the fold found nothing left for — see `MyTurnDismissalFilter.dischargeable`. */
|
|
142
|
+
export async function dischargeMyTurnDismissals(accountId, targets) {
|
|
143
|
+
const prIds = targets.filter((t) => t.kind === 'pr').map((t) => t.id);
|
|
144
|
+
const repoIds = targets.filter((t) => t.kind === 'repo').map((t) => t.id);
|
|
145
|
+
if (prIds.length > 0) {
|
|
146
|
+
await db
|
|
147
|
+
.delete(myTurnDismissals)
|
|
148
|
+
.where(and(eq(myTurnDismissals.accountId, accountId), inArray(myTurnDismissals.prId, prIds)))
|
|
149
|
+
.execute();
|
|
150
|
+
}
|
|
151
|
+
if (repoIds.length > 0) {
|
|
152
|
+
await db
|
|
153
|
+
.delete(myTurnDismissals)
|
|
154
|
+
.where(and(eq(myTurnDismissals.accountId, accountId), inArray(myTurnDismissals.repoId, repoIds)))
|
|
155
|
+
.execute();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The per-fold filter. `keep` is called once per candidate item, in any order; it records which
|
|
160
|
+
* subjects the fold saw at all, which it showed and which it hid, so the three questions after the
|
|
161
|
+
* fold — what is dismissed, what may be discharged — are answered off what actually happened.
|
|
162
|
+
*/
|
|
163
|
+
export class MyTurnDismissalFilter {
|
|
164
|
+
byKey;
|
|
165
|
+
orderIdx;
|
|
166
|
+
present = new Set();
|
|
167
|
+
shown = new Set();
|
|
168
|
+
hidden = new Map();
|
|
169
|
+
constructor(rows, order) {
|
|
170
|
+
this.byKey = new Map(rows.map((r) => [subjectKey(r.target), r]));
|
|
171
|
+
this.orderIdx = new Map(order.map((r, i) => [r, i]));
|
|
172
|
+
}
|
|
173
|
+
/** True ⇒ the item stays. `clock` is the item's own ISO clock; null ⇒ it has none, and an
|
|
174
|
+
* item with no clock cannot prove it is newer than the dismissal, so it stays hidden. */
|
|
175
|
+
keep(target, clock, reason, describe) {
|
|
176
|
+
const key = subjectKey(target);
|
|
177
|
+
this.present.add(key);
|
|
178
|
+
const d = this.byKey.get(key);
|
|
179
|
+
if (d == null)
|
|
180
|
+
return true;
|
|
181
|
+
const ms = clock == null ? Number.NaN : Date.parse(clock);
|
|
182
|
+
if (Number.isFinite(ms) && ms > d.dismissedAtMs) {
|
|
183
|
+
this.shown.add(key);
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
const prev = this.hidden.get(key);
|
|
187
|
+
if (prev == null || this.rank(reason) < this.rank(prev.reason)) {
|
|
188
|
+
this.hidden.set(key, { reason, describe });
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
rank(r) {
|
|
193
|
+
return this.orderIdx.get(r) ?? Number.MAX_SAFE_INTEGER;
|
|
194
|
+
}
|
|
195
|
+
/** Subjects wholly hidden by a live dismissal — the tab's "Dismissed" list, newest first. A
|
|
196
|
+
* subject with a newer item showing is back on the plate and is not listed. */
|
|
197
|
+
dismissed() {
|
|
198
|
+
const out = [];
|
|
199
|
+
for (const [key, h] of this.hidden) {
|
|
200
|
+
if (this.shown.has(key))
|
|
201
|
+
continue;
|
|
202
|
+
const d = this.byKey.get(key);
|
|
203
|
+
out.push({
|
|
204
|
+
target: d.target,
|
|
205
|
+
...h.describe(),
|
|
206
|
+
reason: h.reason,
|
|
207
|
+
dismissedAt: new Date(d.dismissedAtMs).toISOString(),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return out.sort((a, b) => b.dismissedAt.localeCompare(a.dismissedAt));
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Rows with nothing left to dismiss: the fold saw NO item on the subject. ⚠ ONLY WHERE THE FOLD
|
|
214
|
+
* COULD HAVE SEEN ONE — a workspace-scoped fold has not looked at another workspace's repos, so
|
|
215
|
+
* `inScope` says which subjects this read covered. A closed PR is dead wherever it lives.
|
|
216
|
+
*/
|
|
217
|
+
dischargeable(inScope) {
|
|
218
|
+
const out = [];
|
|
219
|
+
for (const [key, d] of this.byKey) {
|
|
220
|
+
if (this.present.has(key))
|
|
221
|
+
continue;
|
|
222
|
+
if (d.closed || inScope(d.repoId))
|
|
223
|
+
out.push(d.target);
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const clockMsOf = (iso) => {
|
|
229
|
+
const ms = iso == null ? Number.NaN : Date.parse(iso);
|
|
230
|
+
// No clock ⇒ the oldest possible: the board's scorer dates such a card off the PR's open time,
|
|
231
|
+
// which is the longest wait it has.
|
|
232
|
+
return Number.isFinite(ms) ? ms : Number.NEGATIVE_INFINITY;
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* Keep ONE item per pull request across every PR-grained section — see the header's rule 2.
|
|
236
|
+
* Pure. Sections keep their own order; only losers are removed.
|
|
237
|
+
*/
|
|
238
|
+
export function onePerPr(mt, order) {
|
|
239
|
+
const orderIdx = new Map(order.map((r, i) => [r, i]));
|
|
240
|
+
const rank = (r) => orderIdx.get(r) ?? Number.MAX_SAFE_INTEGER;
|
|
241
|
+
const better = (a, b) => rank(a.reason) - rank(b.reason) < 0 ||
|
|
242
|
+
(rank(a.reason) === rank(b.reason) &&
|
|
243
|
+
(a.clockMs < b.clockMs || (a.clockMs === b.clockMs && `${a.reason}:${a.ref}` < `${b.reason}:${b.ref}`)));
|
|
244
|
+
const best = new Map();
|
|
245
|
+
const offer = (c) => {
|
|
246
|
+
const cur = best.get(c.prId);
|
|
247
|
+
if (cur == null || better(c, cur))
|
|
248
|
+
best.set(c.prId, c);
|
|
249
|
+
};
|
|
250
|
+
const winnerIs = (c) => {
|
|
251
|
+
const w = best.get(c.prId);
|
|
252
|
+
return w.reason === c.reason && w.ref === c.ref;
|
|
253
|
+
};
|
|
254
|
+
// Each section, described once: its reason, and how to read an item's PR, clock and id.
|
|
255
|
+
const pr = (reason) => ({
|
|
256
|
+
of: (i) => ({ prId: i.prId, reason, clockMs: clockMsOf(i.since), ref: `${i.prId}` }),
|
|
257
|
+
});
|
|
258
|
+
const S = {
|
|
259
|
+
awaitingReview: pr('review_request'),
|
|
260
|
+
mentions: pr('mention'),
|
|
261
|
+
commentReplies: pr('comment_reply'),
|
|
262
|
+
pushedSince: pr('pushed_since'),
|
|
263
|
+
ownCiRed: pr('own_ci_red'),
|
|
264
|
+
ownConflicts: pr('own_conflicts'),
|
|
265
|
+
approvedPrs: pr('pr_approved'),
|
|
266
|
+
ownReady: pr('own_ready'),
|
|
267
|
+
yourPrs: pr('your_pr'),
|
|
268
|
+
watchedRepoPrs: pr('watched_repo_pr'),
|
|
269
|
+
};
|
|
270
|
+
const thread = (reason) => ({
|
|
271
|
+
of: (i) => ({
|
|
272
|
+
prId: i.prId,
|
|
273
|
+
reason,
|
|
274
|
+
clockMs: clockMsOf(i.lastReplyAt),
|
|
275
|
+
ref: `${i.threadId}`,
|
|
276
|
+
}),
|
|
277
|
+
});
|
|
278
|
+
const threadsAwaiting = thread('thread');
|
|
279
|
+
const threadReplies = thread('thread_reply');
|
|
280
|
+
const ownThreads = {
|
|
281
|
+
of: (i) => ({
|
|
282
|
+
prId: i.prId,
|
|
283
|
+
reason: 'own_thread',
|
|
284
|
+
clockMs: clockMsOf(i.since),
|
|
285
|
+
ref: `${i.threadId}`,
|
|
286
|
+
}),
|
|
287
|
+
};
|
|
288
|
+
const claude = {
|
|
289
|
+
of: (i) => ({
|
|
290
|
+
prId: i.prId,
|
|
291
|
+
reason: 'claude_review',
|
|
292
|
+
clockMs: clockMsOf(i.finishedAt),
|
|
293
|
+
ref: `${i.reviewId}`,
|
|
294
|
+
}),
|
|
295
|
+
};
|
|
296
|
+
for (const i of mt.awaitingReview)
|
|
297
|
+
offer(S.awaitingReview.of(i));
|
|
298
|
+
for (const i of mt.mentions)
|
|
299
|
+
offer(S.mentions.of(i));
|
|
300
|
+
for (const i of mt.commentReplies)
|
|
301
|
+
offer(S.commentReplies.of(i));
|
|
302
|
+
for (const i of mt.pushedSince)
|
|
303
|
+
offer(S.pushedSince.of(i));
|
|
304
|
+
for (const i of mt.ownCiRed)
|
|
305
|
+
offer(S.ownCiRed.of(i));
|
|
306
|
+
for (const i of mt.ownConflicts)
|
|
307
|
+
offer(S.ownConflicts.of(i));
|
|
308
|
+
for (const i of mt.approvedPrs)
|
|
309
|
+
offer(S.approvedPrs.of(i));
|
|
310
|
+
for (const i of mt.ownReady)
|
|
311
|
+
offer(S.ownReady.of(i));
|
|
312
|
+
for (const i of mt.yourPrs)
|
|
313
|
+
offer(S.yourPrs.of(i));
|
|
314
|
+
for (const i of mt.watchedRepoPrs)
|
|
315
|
+
offer(S.watchedRepoPrs.of(i));
|
|
316
|
+
for (const i of mt.threadsAwaiting)
|
|
317
|
+
offer(threadsAwaiting.of(i));
|
|
318
|
+
for (const i of mt.threadReplies)
|
|
319
|
+
offer(threadReplies.of(i));
|
|
320
|
+
for (const i of mt.ownThreads)
|
|
321
|
+
offer(ownThreads.of(i));
|
|
322
|
+
for (const i of mt.claudeReviewsToAction)
|
|
323
|
+
offer(claude.of(i));
|
|
324
|
+
return {
|
|
325
|
+
awaitingReview: mt.awaitingReview.filter((i) => winnerIs(S.awaitingReview.of(i))),
|
|
326
|
+
mentions: mt.mentions.filter((i) => winnerIs(S.mentions.of(i))),
|
|
327
|
+
commentReplies: mt.commentReplies.filter((i) => winnerIs(S.commentReplies.of(i))),
|
|
328
|
+
pushedSince: mt.pushedSince.filter((i) => winnerIs(S.pushedSince.of(i))),
|
|
329
|
+
ownCiRed: mt.ownCiRed.filter((i) => winnerIs(S.ownCiRed.of(i))),
|
|
330
|
+
ownConflicts: mt.ownConflicts.filter((i) => winnerIs(S.ownConflicts.of(i))),
|
|
331
|
+
approvedPrs: mt.approvedPrs.filter((i) => winnerIs(S.approvedPrs.of(i))),
|
|
332
|
+
ownReady: mt.ownReady.filter((i) => winnerIs(S.ownReady.of(i))),
|
|
333
|
+
yourPrs: mt.yourPrs.filter((i) => winnerIs(S.yourPrs.of(i))),
|
|
334
|
+
watchedRepoPrs: mt.watchedRepoPrs.filter((i) => winnerIs(S.watchedRepoPrs.of(i))),
|
|
335
|
+
threadsAwaiting: mt.threadsAwaiting.filter((i) => winnerIs(threadsAwaiting.of(i))),
|
|
336
|
+
threadReplies: mt.threadReplies.filter((i) => winnerIs(threadReplies.of(i))),
|
|
337
|
+
ownThreads: mt.ownThreads.filter((i) => winnerIs(ownThreads.of(i))),
|
|
338
|
+
claudeReviewsToAction: mt.claudeReviewsToAction.filter((i) => winnerIs(claude.of(i))),
|
|
339
|
+
// Repo-grained: a branch is not a pull request, and never shares a key with one.
|
|
340
|
+
redTrunks: mt.redTrunks,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
//# sourceMappingURL=my-turn-dismissals.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// ── MY TURN SETTINGS, READ SERVER-SIDE (CORE, free, no AI) ─────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// The account's stored overrides (`accounts.my_turn_settings`), resolved through the ONE shared
|
|
4
|
+
// resolver (`resolveMyTurnSettings`, packages/shared/src/my-turn-settings.ts). Every server reader
|
|
5
|
+
// comes through here: `getMyTurn` (which types are shown), the Pending ranker (the type order and
|
|
6
|
+
// the Do next weights) and the Pro work plan's scorer (the weights).
|
|
7
|
+
//
|
|
8
|
+
// ⚠ READ STRAIGHT FROM `accounts`, NEVER FROM THE LOCAL-ACCOUNT CACHE. The cache is the per-request
|
|
9
|
+
// hook's copy (auth/account.ts); a server fold that read it would see a save only if the setter
|
|
10
|
+
// remembered to refresh it. One indexed read by primary key costs nothing beside the folds it gates.
|
|
11
|
+
import { eq } from 'drizzle-orm';
|
|
12
|
+
import { resolveMyTurnSettings, } from '../shared/index.js';
|
|
13
|
+
import { db, schema } from './client.js';
|
|
14
|
+
/** The account's My Turn settings, resolved (the product defaults where nothing is stored). */
|
|
15
|
+
export async function getMyTurnSettings(accountId) {
|
|
16
|
+
const rows = await db
|
|
17
|
+
.select({ settings: schema.accounts.myTurnSettings })
|
|
18
|
+
.from(schema.accounts)
|
|
19
|
+
.where(eq(schema.accounts.id, accountId))
|
|
20
|
+
.limit(1)
|
|
21
|
+
.execute();
|
|
22
|
+
return resolveMyTurnSettings(rows[0]?.settings ?? null);
|
|
23
|
+
}
|
|
24
|
+
/** The ranking an `/api/attention` response was built with — what every explanation on the board
|
|
25
|
+
* must print, rather than the product constants. */
|
|
26
|
+
export function rankRulesOf(s) {
|
|
27
|
+
return {
|
|
28
|
+
weights: s.weights,
|
|
29
|
+
preset: s.preset,
|
|
30
|
+
myTurnOrder: [...s.order],
|
|
31
|
+
myTurnOff: [...s.off],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=my-turn-settings.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IS THIS BUILD RED? — and RED IS ALWAYS THE PAIR `failure` | `error`, never one of them.
|
|
3
|
+
*
|
|
4
|
+
* GitHub reports an infrastructure/permissions problem as `error` and a genuine check failure as
|
|
5
|
+
* `failure`, and every layer of this app that asks "is it red" has to accept both: db/triage.ts's
|
|
6
|
+
* reason tags, getWorkspaceMetricsDetail's own local `isRed`, and the SPA's lib/ui.ts. A fold that
|
|
7
|
+
* tested only 'failure' would silently drop every errored build — which is the half that most
|
|
8
|
+
* often needs a human.
|
|
9
|
+
*/
|
|
10
|
+
export const RED_CI_STATUSES = ['failure', 'error'];
|
|
11
|
+
export function isRedCiStatus(ci) {
|
|
12
|
+
return RED_CI_STATUSES.includes(ci ?? '');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* DOES THE HEAD CONFLICT WITH THE BASE? An OR over two columns, because they are two views of one
|
|
16
|
+
* fact: `mergeStateStatus = 'dirty'` is GitHub's protection-aware verdict, `mergeable =
|
|
17
|
+
* 'conflicting'` its conflict-only one, and they agree on every real row we have measured.
|
|
18
|
+
*
|
|
19
|
+
* ⚠ NULL IN EITHER IS NOT OBSERVED, never a conflict — and never "fine" either. That is why this is
|
|
20
|
+
* a JS fold and not a SQL predicate: `mergeable <> 'conflicting'` in SQL silently drops the NULL
|
|
21
|
+
* rows, which are exactly the ones we have not asked about yet.
|
|
22
|
+
*/
|
|
23
|
+
export function isConflicting(p) {
|
|
24
|
+
return p.mergeStateStatus === 'dirty' || p.mergeable === 'conflicting';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* WHICH FORWARD CARD IS THIS PR? — `'update_branch'` when GitHub wants the branch updated first,
|
|
28
|
+
* `'merge'` when it will merge now, else null. The Ready to land tab's two kinds, spelled ONCE for
|
|
29
|
+
* the merge-candidate loop in `getWorkspaceInsights` and for My Turn's `own_ready` promotion, so a
|
|
30
|
+
* PR promoted into My turn is classified by the very test its home card was.
|
|
31
|
+
*
|
|
32
|
+
* ⚠ CONFLICTS OUTRANK 'behind' — GitHub's own `canUpdateBranch` is "behind AND NOT conflicting",
|
|
33
|
+
* so an Update-branch button on a conflicting PR is a button GitHub refuses.
|
|
34
|
+
* ⚠ `readyToMerge` is `READY_MERGE_STATES.has(mergeStateStatus)`, evaluated by the CALLER: that set
|
|
35
|
+
* lives in db/triage.ts, which opens the DB client at load, and this module stays pure.
|
|
36
|
+
*/
|
|
37
|
+
export function forwardKindOf(p, readyToMerge) {
|
|
38
|
+
if (p.mergeStateStatus == null)
|
|
39
|
+
return null;
|
|
40
|
+
if (isConflicting(p))
|
|
41
|
+
return null;
|
|
42
|
+
if (p.mergeStateStatus === 'behind')
|
|
43
|
+
return 'update_branch';
|
|
44
|
+
return readyToMerge ? 'merge' : null;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=pending-classify.js.map
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// THE PENDING MUTE (CORE, free, no AI) — "stop these Pending items claiming my turn".
|
|
2
|
+
//
|
|
3
|
+
// ── WHAT A MUTE IS ───────────────────────────────────────────────────────────────────────────
|
|
4
|
+
// A muted repo's "your turn"-shaped rows are downgraded to `relevance: 'none'` (hence
|
|
5
|
+
// `personal: false`) inside `getMyTurn`, at the ONE place `personal` is folded from `relevance`.
|
|
6
|
+
// From that single write, and with no second predicate anywhere:
|
|
7
|
+
//
|
|
8
|
+
// • the Pending card relabels from "Your turn" / "In your repos" to the neutral
|
|
9
|
+
// "Review or reply" (`cardKindLabel`, which reads `relevance`);
|
|
10
|
+
// • the browser notification stops firing for it (`useMyTurnNotifications`, which reads
|
|
11
|
+
// `personal`);
|
|
12
|
+
// • the welcome-back banner, the workspace badges, the "Elsewhere" rows and the
|
|
13
|
+
// "N need your attention" line stop counting it (`myTurnPersonal`, folded off `relevance`);
|
|
14
|
+
// • it moves into the broad "N need review or reply" population (`myTurnOther`);
|
|
15
|
+
// • it ranks lower in the "Do next" head (`RELEVANCE_WEIGHT` in db/work-plan.ts).
|
|
16
|
+
//
|
|
17
|
+
// ⚠ THE CARD NEVER LEAVES THE BOARD, and the broad `myTurn` count never moves. "That work is
|
|
18
|
+
// real, it is just not yours, and hiding it would delete work rather than route it" is the rule
|
|
19
|
+
// the Pending board is built on; a mute is a ROUTING gesture inside it, not a filter.
|
|
20
|
+
//
|
|
21
|
+
// ── WHAT IT IS NOT ───────────────────────────────────────────────────────────────────────────
|
|
22
|
+
// ⚠ IT IS NOT THE `repos.inbox_watch` AXIS MIGRATION 0046 DROPPED, and `api/routes/workspaces.ts`
|
|
23
|
+
// still carries the rule that killed it ("do not reintroduce a per-repo visibility flag here: the
|
|
24
|
+
// workspace IS the scope"). That column was a SECOND VISIBILITY SCOPE on top of membership — it
|
|
25
|
+
// decided whether a repo's work appeared AT ALL, so every screen had two axes and no way to say
|
|
26
|
+
// which it was obeying. Nothing here changes any screen's population: a muted repo is fully live
|
|
27
|
+
// on Feed, Timeline, Activity, Bots and the Pending board. What changes is whether its rows may
|
|
28
|
+
// CLAIM THE READER'S TURN and interrupt them. Membership remains the only visibility axis.
|
|
29
|
+
//
|
|
30
|
+
// ⚠ IT IS NOT THE ORPHANED `bot_mute_rules` TABLE (migration 0029) either. That backed a removed
|
|
31
|
+
// feature that HID review threads and RESOLVED them on a timer. This hides nothing and resolves
|
|
32
|
+
// nothing.
|
|
33
|
+
//
|
|
34
|
+
// ── TWO INDEPENDENT FACTS, OR-ed. NEVER A CHAIN. ─────────────────────────────────────────────
|
|
35
|
+
//
|
|
36
|
+
// muted(repo) == its workspace's `workspaces.pending_muted` OR a `pending_muted_repos` row
|
|
37
|
+
//
|
|
38
|
+
// Workspace grain alone does not answer the ask (one noisy repo inside a useful workspace);
|
|
39
|
+
// repo grain alone makes silencing a 20-repo workspace twenty clicks. `null`-means-inherit is a
|
|
40
|
+
// named bug class in this codebase (`workspace_reviewers.monthly_cents`, the Slack target, the
|
|
41
|
+
// sprint cadence): it needs a resolver, and "which grain am I reading?" then has to be answered
|
|
42
|
+
// at every call site. There is no resolver — there is ONE union, computed here, and clearing
|
|
43
|
+
// either half neither reveals nor overwrites the other. It is the `hiddenBotUserIds` shape.
|
|
44
|
+
import { and, eq, inArray } from 'drizzle-orm';
|
|
45
|
+
import { db, runTransaction, schema } from './client.js';
|
|
46
|
+
const { pendingMutedRepos, workspaceRepos, workspaces } = schema;
|
|
47
|
+
/**
|
|
48
|
+
* THE ONE READ. Every repo id in this account whose Pending items are muted, by EITHER fact.
|
|
49
|
+
*
|
|
50
|
+
* Account-scoped, never workspace-scoped, and that is what makes the ACCOUNT-WIDE
|
|
51
|
+
* `GET /api/my-turn` — the one the browser-notification watcher reads, deliberately with no
|
|
52
|
+
* `?workspace=` — obey a per-WORKSPACE mute without gaining a scope parameter: a repo belongs to
|
|
53
|
+
* exactly one workspace, so a workspace-grained mute is fully resolvable per repo id.
|
|
54
|
+
*
|
|
55
|
+
* Two indexed selects, unioned in JS rather than in SQL: an `OR` across a join and a second table
|
|
56
|
+
* is a portable-dialect liability for no gain here (both sides are tiny — one row per muted repo,
|
|
57
|
+
* one flag per workspace), and the union is the thing the comment above has to keep saying.
|
|
58
|
+
*/
|
|
59
|
+
export async function getMutedPendingRepoIds(accountId) {
|
|
60
|
+
const [wsRows, repoRows] = await Promise.all([
|
|
61
|
+
// The WORKSPACE half: every repo whose workspace carries the flag. The join is on BOTH
|
|
62
|
+
// columns because `workspace_repos` carries its own `account_id` — the same pairing the
|
|
63
|
+
// composite FK enforces, so the predicate and the constraint say the same thing.
|
|
64
|
+
db
|
|
65
|
+
.select({ repoId: workspaceRepos.repoId })
|
|
66
|
+
.from(workspaceRepos)
|
|
67
|
+
.innerJoin(workspaces, and(eq(workspaces.id, workspaceRepos.workspaceId), eq(workspaces.accountId, workspaceRepos.accountId)))
|
|
68
|
+
.where(and(eq(workspaceRepos.accountId, accountId), eq(workspaces.pendingMuted, true)))
|
|
69
|
+
.execute(),
|
|
70
|
+
// The REPO half: presence is the fact.
|
|
71
|
+
db
|
|
72
|
+
.select({ repoId: pendingMutedRepos.repoId })
|
|
73
|
+
.from(pendingMutedRepos)
|
|
74
|
+
.where(eq(pendingMutedRepos.accountId, accountId))
|
|
75
|
+
.execute(),
|
|
76
|
+
]);
|
|
77
|
+
const out = new Set();
|
|
78
|
+
for (const r of wsRows)
|
|
79
|
+
out.add(r.repoId);
|
|
80
|
+
for (const r of repoRows)
|
|
81
|
+
out.add(r.repoId);
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The REPO HALF ALONE, account-scoped, for the settings listing — deliberately NOT the union
|
|
86
|
+
* above. `listWorkspaces` intersects it with each workspace's membership to build
|
|
87
|
+
* `Workspace.mutedRepoIds`, and the settings screen has to render the two switches SEPARATELY:
|
|
88
|
+
* folding the workspace flag in here would hand it one merged set it could not un-mix, and
|
|
89
|
+
* un-ticking a repository would then appear to do nothing. The row carries no workspace id (a
|
|
90
|
+
* repo belongs to exactly one workspace already, and a fact lives at exactly one grain).
|
|
91
|
+
*/
|
|
92
|
+
export async function repoGrainedPendingMutes(accountId) {
|
|
93
|
+
const rows = await db
|
|
94
|
+
.select({ repoId: pendingMutedRepos.repoId })
|
|
95
|
+
.from(pendingMutedRepos)
|
|
96
|
+
.where(eq(pendingMutedRepos.accountId, accountId))
|
|
97
|
+
.execute();
|
|
98
|
+
return new Set(rows.map((r) => r.repoId));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* THE WRITE, for `PUT /api/workspaces/:id/pending-mute`. Each half is applied ONLY when the
|
|
102
|
+
* caller supplied it (`undefined` = untouched), so one Save can carry either grain or both
|
|
103
|
+
* without a no-op write inventing a value for the other.
|
|
104
|
+
*
|
|
105
|
+
* ⚠ THE REPO SET IS SCOPED TO THIS WORKSPACE'S MEMBERSHIP, AND THAT IS A CORRECTNESS RULE, NOT A
|
|
106
|
+
* VALIDATION NICETY. `mutedRepoIds` is the EXACT muted set within the named workspace: ids in it
|
|
107
|
+
* that the workspace does not hold are IGNORED (never written), and stored rows for repos OUTSIDE
|
|
108
|
+
* this workspace are LEFT ALONE (never deleted). Without the second half, saving workspace A's
|
|
109
|
+
* screen would silently clear every mute the reader had set in workspaces B and C — the "an edit
|
|
110
|
+
* meant for one team travels to every team" failure this modal's grain split exists to prevent.
|
|
111
|
+
*
|
|
112
|
+
* Tenancy is ALSO structural: `repo_id` arrives in a request body, and the composite FK
|
|
113
|
+
* `(repo_id, account_id) → repos(id, account_id)` means a cross-account pair fails in the
|
|
114
|
+
* DATABASE. The membership intersection below is the scope guard; the FK is the tenant guard.
|
|
115
|
+
* Neither substitutes for the other.
|
|
116
|
+
*
|
|
117
|
+
* Returns false when the workspace is not this account's (the route 404s), so ownership is
|
|
118
|
+
* decided here rather than by whichever handler remembered to check.
|
|
119
|
+
*/
|
|
120
|
+
export async function setWorkspacePendingMute(accountId, workspaceId, patch) {
|
|
121
|
+
const owned = (await db
|
|
122
|
+
.select({ id: workspaces.id })
|
|
123
|
+
.from(workspaces)
|
|
124
|
+
.where(and(eq(workspaces.id, workspaceId), eq(workspaces.accountId, accountId)))
|
|
125
|
+
.limit(1)
|
|
126
|
+
.execute())[0];
|
|
127
|
+
if (!owned)
|
|
128
|
+
return false;
|
|
129
|
+
// The workspace's membership — the domain the repo half is allowed to write inside.
|
|
130
|
+
const memberRows = patch.mutedRepoIds === undefined
|
|
131
|
+
? []
|
|
132
|
+
: await db
|
|
133
|
+
.select({ repoId: workspaceRepos.repoId })
|
|
134
|
+
.from(workspaceRepos)
|
|
135
|
+
.where(and(eq(workspaceRepos.accountId, accountId), eq(workspaceRepos.workspaceId, workspaceId)))
|
|
136
|
+
.execute();
|
|
137
|
+
const membership = new Set(memberRows.map((r) => r.repoId));
|
|
138
|
+
const wanted = new Set((patch.mutedRepoIds ?? []).filter((id) => membership.has(id)));
|
|
139
|
+
const toClear = [...membership].filter((id) => !wanted.has(id));
|
|
140
|
+
await runTransaction(async (tx) => {
|
|
141
|
+
if (patch.muted !== undefined) {
|
|
142
|
+
await tx
|
|
143
|
+
.update(workspaces)
|
|
144
|
+
.set({ pendingMuted: patch.muted })
|
|
145
|
+
.where(and(eq(workspaces.id, workspaceId), eq(workspaces.accountId, accountId)))
|
|
146
|
+
.execute();
|
|
147
|
+
}
|
|
148
|
+
if (patch.mutedRepoIds !== undefined) {
|
|
149
|
+
if (toClear.length > 0) {
|
|
150
|
+
await tx
|
|
151
|
+
.delete(pendingMutedRepos)
|
|
152
|
+
.where(and(eq(pendingMutedRepos.accountId, accountId), inArray(pendingMutedRepos.repoId, toClear)))
|
|
153
|
+
.execute();
|
|
154
|
+
}
|
|
155
|
+
for (const repoId of wanted) {
|
|
156
|
+
// The conflict target is the table's ONE unique, (account_id, repo_id). Presence is the
|
|
157
|
+
// fact, so there is nothing to update — DO NOTHING keeps `created_at` as "when the reader
|
|
158
|
+
// muted it" rather than "when they last pressed Save".
|
|
159
|
+
await tx
|
|
160
|
+
.insert(pendingMutedRepos)
|
|
161
|
+
.values({ accountId, repoId })
|
|
162
|
+
.onConflictDoNothing({
|
|
163
|
+
target: [pendingMutedRepos.accountId, pendingMutedRepos.repoId],
|
|
164
|
+
})
|
|
165
|
+
.execute();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=pending-mute.js.map
|