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,406 @@
|
|
|
1
|
+
// ── The daily brief (plan P3.1 / N1): ONE deterministic "what needs me" fold per workspace ──
|
|
2
|
+
//
|
|
3
|
+
// Computed on read, stored nowhere (D5 — zero core migrations). Every line REUSES the exact fold
|
|
4
|
+
// of the surface its strip line deep-links to, so a number and the view it opens cannot disagree
|
|
5
|
+
// (the plan's ⚠: "never a re-derivation that can disagree with the surface it links to"):
|
|
6
|
+
//
|
|
7
|
+
// myTurn /
|
|
8
|
+
// ciFailing /
|
|
9
|
+
// security /
|
|
10
|
+
// stalled /
|
|
11
|
+
// untouchedThreads /
|
|
12
|
+
// needsReviewer → getWorkspaceInsights card counts — the same cards GET /api/attention
|
|
13
|
+
// serves (one my_turn card = one thing on your plate; one security card =
|
|
14
|
+
// one PR with a known-advisory fix or alert; one stalled_review card = one
|
|
15
|
+
// PR; one untouched_thread card = one thread; one reviewer_routing card =
|
|
16
|
+
// one PR needing a reviewer).
|
|
17
|
+
// ⚠ `myTurn` USED TO be getConsolidatedFeed(...).counts.myTurn — a count of
|
|
18
|
+
// feed EVENTS in a rolling 14 days, which corresponded to no clickable list
|
|
19
|
+
// ("54 items" the user could not open). It is now literally the number of
|
|
20
|
+
// my_turn cards the board emitted, which are in turn one-per-row of the
|
|
21
|
+
// `getMyTurn(accountId, scope)` fold GET /api/my-turn serves — so the strip's
|
|
22
|
+
// number, the board's list and the My Turn view are one population.
|
|
23
|
+
// ⚠ Those cards are CAPPED (MY_TURN_CARD_CAP = 50) and the cap is DISCLOSED,
|
|
24
|
+
// not hidden: `myTurnTotal` carries the uncapped population so the line can
|
|
25
|
+
// read "50 of 148". The displayed figure stays `myTurn` — the card count —
|
|
26
|
+
// because that is the list the click opens. See the shared type's doc.
|
|
27
|
+
// resolveBacklog → getResolvableBotThreadPrs(...).totalThreads — the review-&-resolve tab's
|
|
28
|
+
// own number (same predicate the resolve route re-derives).
|
|
29
|
+
// trunkRed → the repos DEFAULT-BRANCH head snapshot columns (branch-status's source of
|
|
30
|
+
// truth) — red = 'failure' | 'error'; pending/unknown are not red.
|
|
31
|
+
// ⚠ NOT the same population as `ciFailing` above, and neither replaces the
|
|
32
|
+
// other: this names EVERY red trunk in the workspace and opens that repo's
|
|
33
|
+
// console; `ciFailing` counts the ones you MAINTAIN plus your own red PRs and
|
|
34
|
+
// opens the attention board. Two figures, two lists, two destinations.
|
|
35
|
+
// botAnomalies → a NARROW volume-only self-baseline (below) — deliberately NOT the full
|
|
36
|
+
// getBotBehaviourAnalytics compute (trends+heatmaps+overlap+ML fold over
|
|
37
|
+
// every bot), which is priced for an explicit Pro tab open, not an
|
|
38
|
+
// every-morning free strip.
|
|
39
|
+
//
|
|
40
|
+
// ── The anomaly slice (the cost decision, spelled out) ──
|
|
41
|
+
// Weekly bot-activity counts (review_comment | pr_comment | review_submitted events) per
|
|
42
|
+
// role-'review' automated reviewer over the last 84 days — one indexed scan of `events`, no
|
|
43
|
+
// thread/ML/heatmap work — folded through the SAME exported `weeklyAnomalies` (median ± MAD,
|
|
44
|
+
// z ≥ 3) the behaviour tab uses, with the SAME volume discipline: zero weeks are null (a
|
|
45
|
+
// "typical volume" means "how much when it's working"; going dark is the silence detector's
|
|
46
|
+
// question and this slice deliberately does not ask it), direction 'both', minScale 2. A bot is
|
|
47
|
+
// flagged when its CURRENT trailing-7-day bucket is the anomalous one — the brief is about this
|
|
48
|
+
// week, not history.
|
|
49
|
+
//
|
|
50
|
+
// ── TWO caches, and the line between them (⚠ do not merge them back into one) ──
|
|
51
|
+
// The brief USED to sit behind a single 5-minute per-(accountId, workspaceId) TTL covering every
|
|
52
|
+
// slice. That made the strip's headline a snapshot of a fold whose LIST is live: `myTurn` is the
|
|
53
|
+
// my_turn card count, the click opens GET /api/attention, and that route recomputes the very same
|
|
54
|
+
// getWorkspaceInsights fold on every request with no cache at all. Dismiss two cards (or open two
|
|
55
|
+
// of your PRs — `pr_views` moves the fold too) and the strip kept saying 5 over a board of 3 for
|
|
56
|
+
// up to five more minutes. A count that a single click disproves is not "≤5 min stale", it is
|
|
57
|
+
// wrong; disclosing the staleness would not have made it right.
|
|
58
|
+
//
|
|
59
|
+
// So the split is by WHETHER THE NUMBER SITS ABOVE A LIVE LIST:
|
|
60
|
+
//
|
|
61
|
+
// COUNTS (the four card counts + resolveBacklog + trunkRed) — computed FRESH on every read of
|
|
62
|
+
// `getDailyBriefEntry`, the ACTIVE workspace's line. Each one is the same fold the surface it
|
|
63
|
+
// deep-links to serves on demand, so it must be evaluated at the same moment the user looks
|
|
64
|
+
// at it. This is affordable precisely BECAUSE the anomaly slice below is not in it.
|
|
65
|
+
// botAnomalies — keeps its OWN 5-min TTL (`anomalyCache`). It is an 84-day `events` scan, and
|
|
66
|
+
// it backs NO clickable list: its line reads "unusual volume this week" and opens a bot tab,
|
|
67
|
+
// so nothing a click renders can contradict a five-minute-old flag.
|
|
68
|
+
// The ROLL-UP + the Pro narration inputs — keep a whole-counts TTL (`countsCache`, reached
|
|
69
|
+
// through `getDailyBriefCounts`). The "Elsewhere" lines describe OTHER workspaces: clicking
|
|
70
|
+
// one switches workspace, which then fetches that workspace's own FRESH brief, so the number
|
|
71
|
+
// is re-derived before any list can disagree with it. Freshening the loop instead would
|
|
72
|
+
// multiply getWorkspaceInsights by workspace count on every Feed mount — the cost this route
|
|
73
|
+
// is on the `search` tier for. A My Turn settings save drops the account's entries
|
|
74
|
+
// (`clearDailyBriefCountsFor`), because it changes WHICH cards exist, not just how many.
|
|
75
|
+
// So does the SPA change signal (sync/pr-change-signal.ts `onPrChanged`, registered below):
|
|
76
|
+
// every write route, settle re-read, webhook sync and liveness sweep that moves a
|
|
77
|
+
// board-visible column raises it, and the SPA refetches the strip on it — a roll-up line
|
|
78
|
+
// still answering from before the change would then disagree with the Pending tabs it
|
|
79
|
+
// counts for up to COUNTS_TTL_MS. It drops the whole ACCOUNT (the signal names a repo, and
|
|
80
|
+
// mapping it to its workspace would be a query inside a synchronous listener). ⚠ This does
|
|
81
|
+
// NOT weaken the narration's cost gate below: the TTL still absorbs READ churn, and a
|
|
82
|
+
// narration regenerates only on a click (POST /api/pro/synthesis) whose payload hash really
|
|
83
|
+
// did move — a figure that changed is exactly what a regeneration is for.
|
|
84
|
+
//
|
|
85
|
+
// ⚠ THEREFORE `generatedAt` DESCRIBES TWO COMPUTATION TIMES, and the honest one is the tighter:
|
|
86
|
+
// it stamps the COUNTS (now == the request), while `botAnomalies` inside the same object may be
|
|
87
|
+
// up to ANOMALY_TTL_MS older. That asymmetry is the feature, not an oversight — collapsing it
|
|
88
|
+
// back into one cache to make one timestamp true again reintroduces the bug above.
|
|
89
|
+
//
|
|
90
|
+
// Both caches coalesce concurrent computes by storing the promise, and evict a REJECTED one so an
|
|
91
|
+
// error never sticks for the whole window.
|
|
92
|
+
import { and, eq, gte, inArray } from 'drizzle-orm';
|
|
93
|
+
import { PENDING_LIMITS } from '../shared/index.js';
|
|
94
|
+
import { db, schema } from './client.js';
|
|
95
|
+
import { onPrChanged } from '../sync/pr-change-signal.js';
|
|
96
|
+
import { automatedReviewerUserIds, classificationKindForUser, classificationLabelMap, getResolvableBotThreadPrs, getWorkspaceInsights, resolveWorkspaceScope, weeklyAnomalies, } from './queries.js';
|
|
97
|
+
const WEEK_MS = 7 * 86_400_000;
|
|
98
|
+
const SPAN_WEEKS = 12; // 84 days — the behaviour tab's own baseline span
|
|
99
|
+
/** Defensive caps: the strip is a one-line-per-item surface, not a listing. */
|
|
100
|
+
const ANOMALY_CAP = 5;
|
|
101
|
+
const TRUNK_CAP = 8;
|
|
102
|
+
/** The 84-day anomaly scan's own window (see the header — it backs no clickable list). */
|
|
103
|
+
const ANOMALY_TTL_MS = 5 * 60_000;
|
|
104
|
+
/** The roll-up / narration window — OTHER workspaces' lines, never the active one's. */
|
|
105
|
+
const COUNTS_TTL_MS = 5 * 60_000;
|
|
106
|
+
const anomalyCache = new Map();
|
|
107
|
+
const countsCache = new Map();
|
|
108
|
+
/** The one TTL read both caches share, so their eviction rules cannot drift apart. */
|
|
109
|
+
function cached(map, key, ttlMs, compute) {
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
const hit = map.get(key);
|
|
112
|
+
if (hit && now - hit.at < ttlMs)
|
|
113
|
+
return hit.promise;
|
|
114
|
+
const promise = compute();
|
|
115
|
+
map.set(key, { at: now, promise });
|
|
116
|
+
// An errored compute must not stick for the whole window.
|
|
117
|
+
promise.catch(() => {
|
|
118
|
+
if (map.get(key)?.promise === promise)
|
|
119
|
+
map.delete(key);
|
|
120
|
+
});
|
|
121
|
+
return promise;
|
|
122
|
+
}
|
|
123
|
+
/** Test seam: drop every cached brief slice (the module-level-cache convention). */
|
|
124
|
+
export function clearDailyBriefCache() {
|
|
125
|
+
anomalyCache.clear();
|
|
126
|
+
countsCache.clear();
|
|
127
|
+
}
|
|
128
|
+
/** Drop ONE account's roll-up counts, so a My Turn settings save cannot leave "Elsewhere" lines
|
|
129
|
+
* counting a type the reader just switched off for the rest of the five-minute window. The anomaly
|
|
130
|
+
* slice is untouched: settings do not move it. Called by PUT /api/me/my-turn-settings, and by
|
|
131
|
+
* the PR change signal's listener below. */
|
|
132
|
+
export function clearDailyBriefCountsFor(accountId) {
|
|
133
|
+
for (const k of [...countsCache.keys()]) {
|
|
134
|
+
if (k.startsWith(`${accountId}:`))
|
|
135
|
+
countsCache.delete(k);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// A board-visible PR change drops the account's roll-up counts too (see the header). Registered at
|
|
139
|
+
// module load, so the cache and its invalidation exist together or not at all.
|
|
140
|
+
onPrChanged((accountId) => clearDailyBriefCountsFor(accountId));
|
|
141
|
+
// The narrow volume-only anomaly slice (see the module header for why it is NOT the behaviour
|
|
142
|
+
// compute). Returns at most ANOMALY_CAP bots, most-anomalous first.
|
|
143
|
+
async function briefBotAnomalies(accountId, scope) {
|
|
144
|
+
if (scope.repoIds.length === 0)
|
|
145
|
+
return [];
|
|
146
|
+
const botIds = await automatedReviewerUserIds(accountId, scope.workspaceId, 'review');
|
|
147
|
+
if (botIds.length === 0)
|
|
148
|
+
return [];
|
|
149
|
+
const { events } = schema;
|
|
150
|
+
const nowMs = Date.now();
|
|
151
|
+
const since = new Date(nowMs - SPAN_WEEKS * WEEK_MS);
|
|
152
|
+
const rows = await db
|
|
153
|
+
.select({ actorId: events.actorId, occurredAt: events.occurredAt })
|
|
154
|
+
.from(events)
|
|
155
|
+
.where(and(eq(events.accountId, accountId), inArray(events.repoId, scope.repoIds), inArray(events.actorId, botIds), inArray(events.type, ['review_comment', 'pr_comment', 'review_submitted']), gte(events.occurredAt, since)))
|
|
156
|
+
.execute();
|
|
157
|
+
if (rows.length === 0)
|
|
158
|
+
return [];
|
|
159
|
+
// Rolling 7-day buckets ENDING NOW, oldest first — index 11 is the trailing week the brief
|
|
160
|
+
// reports on. (Calendar weeks would make Monday mornings compare a 1-day bucket to full ones.)
|
|
161
|
+
const byBot = new Map();
|
|
162
|
+
for (const r of rows) {
|
|
163
|
+
if (r.actorId == null)
|
|
164
|
+
continue;
|
|
165
|
+
const age = nowMs - r.occurredAt.getTime();
|
|
166
|
+
const bucket = SPAN_WEEKS - 1 - Math.min(SPAN_WEEKS - 1, Math.floor(age / WEEK_MS));
|
|
167
|
+
let counts = byBot.get(r.actorId);
|
|
168
|
+
if (!counts) {
|
|
169
|
+
counts = Array.from({ length: SPAN_WEEKS }, () => 0);
|
|
170
|
+
byBot.set(r.actorId, counts);
|
|
171
|
+
}
|
|
172
|
+
counts[bucket] = (counts[bucket] ?? 0) + 1;
|
|
173
|
+
}
|
|
174
|
+
const flagged = [];
|
|
175
|
+
for (const [userId, counts] of byBot) {
|
|
176
|
+
// Active-weeks-only baseline (zero weeks → null), the behaviour tab's volume discipline.
|
|
177
|
+
const series = counts.map((v) => (v > 0 ? v : null));
|
|
178
|
+
const anoms = weeklyAnomalies(series, { direction: 'both', minScale: 2 });
|
|
179
|
+
const current = anoms[SPAN_WEEKS - 1];
|
|
180
|
+
if (current != null)
|
|
181
|
+
flagged.push({ userId, z: current.z });
|
|
182
|
+
}
|
|
183
|
+
if (flagged.length === 0)
|
|
184
|
+
return [];
|
|
185
|
+
flagged.sort((a, b) => b.z - a.z);
|
|
186
|
+
const top = flagged.slice(0, ANOMALY_CAP);
|
|
187
|
+
const [labels, kinds] = await Promise.all([
|
|
188
|
+
classificationLabelMap(accountId, scope.workspaceId),
|
|
189
|
+
classificationKindForUser(accountId, scope.workspaceId),
|
|
190
|
+
]);
|
|
191
|
+
const { users } = schema;
|
|
192
|
+
const userRows = await db
|
|
193
|
+
.select({ id: users.id, login: users.githubLogin })
|
|
194
|
+
.from(users)
|
|
195
|
+
.where(inArray(users.id, top.map((f) => f.userId)))
|
|
196
|
+
.execute();
|
|
197
|
+
const loginById = new Map(userRows.map((u) => [u.id, u.login]));
|
|
198
|
+
return top.map((f) => {
|
|
199
|
+
const login = loginById.get(f.userId) ?? null;
|
|
200
|
+
return {
|
|
201
|
+
userId: f.userId,
|
|
202
|
+
label: labels.get(f.userId) ?? login ?? `#${f.userId}`,
|
|
203
|
+
login,
|
|
204
|
+
kind: kinds.get(f.userId) ?? null,
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
// Every red default branch in the workspace, sorted, UNCAPPED — `computeBriefCounts` removes the
|
|
209
|
+
// ones the reader promoted into My Turn first and only then applies TRUNK_CAP, so a promoted trunk
|
|
210
|
+
// never takes a slot on this line and is counted once, in My Turn.
|
|
211
|
+
async function trunkRedRepos(accountId, scope) {
|
|
212
|
+
if (scope.repoIds.length === 0)
|
|
213
|
+
return [];
|
|
214
|
+
const { repos } = schema;
|
|
215
|
+
const rows = await db
|
|
216
|
+
.select({ id: repos.id, name: repos.name, ci: repos.defaultBranchCiStatus })
|
|
217
|
+
.from(repos)
|
|
218
|
+
.where(and(eq(repos.accountId, accountId), inArray(repos.id, scope.repoIds)))
|
|
219
|
+
.execute();
|
|
220
|
+
return rows
|
|
221
|
+
.filter((r) => r.ci === 'failure' || r.ci === 'error')
|
|
222
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
223
|
+
.map((r) => ({ repoId: r.id, name: r.name }));
|
|
224
|
+
}
|
|
225
|
+
async function computeBriefCounts(accountId, workspaceId) {
|
|
226
|
+
// Number in → the resolver still owns membership (a foreign/dead id degrades to Default,
|
|
227
|
+
// exactly the scoped-route posture; the caller already resolved real requests).
|
|
228
|
+
const scope = await resolveWorkspaceScope(accountId, workspaceId);
|
|
229
|
+
const [insights, backlog, botAnomalies, allRedTrunks] = await Promise.all([
|
|
230
|
+
// The /api/attention fold verbatim (getWorkspaceInsights with the default window); the two
|
|
231
|
+
// bot cards it filters out are not counted here either. ⚠ UNCACHED, and that is the point:
|
|
232
|
+
// /api/attention re-runs this exact call on every request, so a cached copy here is a number
|
|
233
|
+
// the very next click disproves.
|
|
234
|
+
getWorkspaceInsights(accountId, undefined, scope),
|
|
235
|
+
getResolvableBotThreadPrs(accountId, scope),
|
|
236
|
+
// The ONE cached slice — the 84-day scan, keyed on the RESOLVED workspace (the raw argument
|
|
237
|
+
// may have degraded to Default). Membership changes reach it within ANOMALY_TTL_MS, which is
|
|
238
|
+
// fine here and only here: no list contradicts a bot-volume flag.
|
|
239
|
+
cached(anomalyCache, `${accountId}:${scope.workspaceId}`, ANOMALY_TTL_MS, () => briefBotAnomalies(accountId, scope)),
|
|
240
|
+
trunkRedRepos(accountId, scope),
|
|
241
|
+
]);
|
|
242
|
+
// ⚠ A RED TRUNK THE READER PROMOTED INTO MY TURN IS COUNTED THERE, NOT HERE — it is a my_turn
|
|
243
|
+
// card (and in `myTurn` below), so listing it on this line too would count one red branch twice.
|
|
244
|
+
// Filtered after the fold resolves, then capped.
|
|
245
|
+
const promotedTrunks = new Set(insights.myTurnTrunkRepoIds ?? []);
|
|
246
|
+
const trunkRed = allRedTrunks.filter((t) => !promotedTrunks.has(t.repoId)).slice(0, TRUNK_CAP);
|
|
247
|
+
let myTurn = 0;
|
|
248
|
+
// The `MyTurnCard.personal` subset of the same cards — the figure the NOTIFICATION surfaces
|
|
249
|
+
// display, where the board keeps displaying `myTurn`. Counted off the cards for exactly the
|
|
250
|
+
// reason `myTurn` is: it is the number of PERSONAL cards the board actually paints, so its
|
|
251
|
+
// total (`myTurnPersonalTotal`, the pre-cap fold) is the only thing that may qualify it.
|
|
252
|
+
let myTurnPersonal = 0;
|
|
253
|
+
// The THREE-WAY split of those same cards, by `MyTurnCard.relevance`. The brief renders two
|
|
254
|
+
// MUTUALLY EXCLUSIVE lines off them — "N need your attention" (direct + maintained, i.e.
|
|
255
|
+
// `myTurnPersonal`, unchanged) and "M need review or reply" (`myTurnOther`) — and each line
|
|
256
|
+
// opens a board filtered to its own number, so each needs its own count AND its own total.
|
|
257
|
+
//
|
|
258
|
+
// ⚠ `myTurnOther` IS NOT `myTurn - myTurnPersonal`. The arithmetic agrees; the disclosure does
|
|
259
|
+
// not. `capFor` prints "of N" only when the displayed figure equals the count it qualifies, so
|
|
260
|
+
// a subtracted figure has no total it may legally borrow and the "of N" vanishes on every
|
|
261
|
+
// capped workspace — the pair-narrow-with-narrow rule, one level down.
|
|
262
|
+
let myTurnDirect = 0;
|
|
263
|
+
let myTurnMaintained = 0;
|
|
264
|
+
let myTurnOther = 0;
|
|
265
|
+
// Red builds the viewer is on the hook for — their own open PR, or trunk in a repo they
|
|
266
|
+
// maintain. Counted off the CARDS like every figure above, so the line and the board agree.
|
|
267
|
+
//
|
|
268
|
+
// ⚠ IT OVERLAPS `trunkRed` BELOW WITHOUT REPLACING IT, and the two lines are not interchangeable:
|
|
269
|
+
// `trunkRed` names EVERY red trunk in the workspace (maintained or not) and each of its lines
|
|
270
|
+
// opens that repo's console; this counts the subset that is YOURS plus your own red PRs, and its
|
|
271
|
+
// line opens the attention board isolated to `ci_failing`. Folding either into the other hands
|
|
272
|
+
// one of them a list its number does not match — the defect this whole strip exists to prevent.
|
|
273
|
+
let ciFailing = 0;
|
|
274
|
+
let stalled = 0;
|
|
275
|
+
let untouchedThreads = 0;
|
|
276
|
+
let needsReviewer = 0;
|
|
277
|
+
// ⚠ FOUR KINDS REACH THIS LOOP AND ARE COUNTED BY NO LINE: `merge`, `update_branch`,
|
|
278
|
+
// `conflicts` and `dependency_bump`. The first two are opportunities and the strip counts what is
|
|
279
|
+
// WAITING ON YOU. The third is a genuine problem, but 3 of every 4 are somebody else's PR in a
|
|
280
|
+
// repo you can merely push to — a "waiting on you" number over that population would be a false
|
|
281
|
+
// claim, and it would stop the strip self-hiding on a workspace where nothing is actually owed.
|
|
282
|
+
// The fourth is housekeeping — an update, not something waiting on you. (`security` IS counted,
|
|
283
|
+
// below, off `kindTotals` like the other survey lines.) This allow-list and the route's deny-list
|
|
284
|
+
// are the two hand-maintained spellings of "which kinds count"; daily-brief.test.ts compares them
|
|
285
|
+
// per kind, so an omission here is a CI failure, not a silence.
|
|
286
|
+
for (const c of insights.cards) {
|
|
287
|
+
if (c.kind === 'ci_failing')
|
|
288
|
+
ciFailing += 1;
|
|
289
|
+
else if (c.kind === 'my_turn') {
|
|
290
|
+
myTurn += 1;
|
|
291
|
+
if (c.personal)
|
|
292
|
+
myTurnPersonal += 1;
|
|
293
|
+
// Absent ⇒ fall back to the boolean, which cannot express 'maintained' (see
|
|
294
|
+
// `MyTurnRelevance`). getWorkspaceInsights above always sets it; this is wire tolerance.
|
|
295
|
+
const rel = c.relevance ?? (c.personal ? 'direct' : 'none');
|
|
296
|
+
if (rel === 'direct')
|
|
297
|
+
myTurnDirect += 1;
|
|
298
|
+
else if (rel === 'maintained')
|
|
299
|
+
myTurnMaintained += 1;
|
|
300
|
+
else
|
|
301
|
+
myTurnOther += 1;
|
|
302
|
+
}
|
|
303
|
+
else if (c.kind === 'stalled_review')
|
|
304
|
+
stalled += 1;
|
|
305
|
+
else if (c.kind === 'untouched_thread')
|
|
306
|
+
untouchedThreads += 1;
|
|
307
|
+
else if (c.kind === 'reviewer_routing')
|
|
308
|
+
needsReviewer += 1;
|
|
309
|
+
}
|
|
310
|
+
// ⚠ THE DISPLAYED FIGURES ARE WHAT THE PENDING BOARD LISTS. The board ranks the UNCAPPED
|
|
311
|
+
// population by score and lists the top `boardListCap` of each view — My turn, "Only yours", its
|
|
312
|
+
// complement, CI failing — so each line's figure is min(its total, that cap), paired with the
|
|
313
|
+
// total for the "of N" disclosure. Counting the cards of THIS capped fold instead would count a
|
|
314
|
+
// different 50 (the newest, by colour) and could disagree with the list the line opens.
|
|
315
|
+
const listed = (total, fallback) => total == null ? fallback : Math.min(total, PENDING_LIMITS.boardListCap);
|
|
316
|
+
myTurn = listed(insights.myTurnTotal, myTurn);
|
|
317
|
+
myTurnPersonal = listed(insights.myTurnPersonalTotal, myTurnPersonal);
|
|
318
|
+
myTurnOther = listed(insights.myTurnOtherTotal, myTurnOther);
|
|
319
|
+
ciFailing = listed(insights.ciFailingTotal, ciFailing);
|
|
320
|
+
// ⚠ THE DIRECT / MAINTAINED HALVES MUST ADD UP TO `myTurnPersonal`, because the Workspace badges
|
|
321
|
+
// print them beside it. They do exactly when every personal item fits on the list — then the
|
|
322
|
+
// listed set IS the whole population and the halves are its two totals. Past the cap, which of
|
|
323
|
+
// the two the top-scoring items are is the board's call, not this fold's, so the halves are left
|
|
324
|
+
// OUT and the badge shows the total alone (its existing "both or neither" refusal).
|
|
325
|
+
const personalFits = insights.myTurnPersonalTotal == null ||
|
|
326
|
+
insights.myTurnPersonalTotal <= PENDING_LIMITS.boardListCap;
|
|
327
|
+
const split = personalFits
|
|
328
|
+
? {
|
|
329
|
+
myTurnDirect: insights.myTurnDirectTotal ?? myTurnDirect,
|
|
330
|
+
myTurnMaintained: insights.myTurnMaintainedTotal ?? myTurnMaintained,
|
|
331
|
+
}
|
|
332
|
+
: {};
|
|
333
|
+
return {
|
|
334
|
+
myTurn,
|
|
335
|
+
// The cap DISCLOSURE, passed straight through from the same getWorkspaceInsights call above
|
|
336
|
+
// (same fold, same scope, same default window) — so the strip's "of N" and the board's "of N"
|
|
337
|
+
// are one number, not two derivations that can drift. ⚠ `myTurn` above stays the figure the
|
|
338
|
+
// strip DISPLAYS: it is the number of cards the board will paint, and a strip that announced
|
|
339
|
+
// 148 over a board of 50 is exactly the "number you can't open" this brief replaced.
|
|
340
|
+
myTurnTotal: insights.myTurnTotal,
|
|
341
|
+
myTurnPersonal,
|
|
342
|
+
// ⚠ THE MATCHED DENOMINATOR, and it is not optional in practice. `myTurnPersonal` paired with
|
|
343
|
+
// `myTurnTotal` would put two populations in one row, AND the cap disclosure only fires when
|
|
344
|
+
// the displayed figure equals the count it qualifies — so a narrow line borrowing the broad
|
|
345
|
+
// total would silently lose its "of N" on every capped workspace.
|
|
346
|
+
myTurnPersonalTotal: insights.myTurnPersonalTotal,
|
|
347
|
+
// The split, each count paired with the total folded off the SAME pre-cap array it came from
|
|
348
|
+
// (passed straight through — same fold, same scope, same window as `myTurnTotal`).
|
|
349
|
+
...split,
|
|
350
|
+
myTurnDirectTotal: insights.myTurnDirectTotal,
|
|
351
|
+
myTurnMaintainedTotal: insights.myTurnMaintainedTotal,
|
|
352
|
+
myTurnOther,
|
|
353
|
+
myTurnOtherTotal: insights.myTurnOtherTotal,
|
|
354
|
+
ciFailing,
|
|
355
|
+
// The matched denominator, passed straight through from the same getWorkspaceInsights call —
|
|
356
|
+
// pair narrow with narrow, or the "of N" disclosure silently never fires.
|
|
357
|
+
ciFailingTotal: insights.ciFailingTotal,
|
|
358
|
+
// The Dependencies tab's SECURITY population — the figure its "Security" chip shows, uncapped
|
|
359
|
+
// (a survey line, so no `…Total` twin); the line opens the board isolated to `security`. ⚠ It
|
|
360
|
+
// is not in the Pro narration's inputs: it is templated, like `ciFailing`'s line, so adding it
|
|
361
|
+
// re-bills no stored brief.
|
|
362
|
+
security: insights.kindTotals?.security ?? 0,
|
|
363
|
+
// ⚠ THE THREE SURVEY LINES COUNT THE WHOLE POPULATION, not the cards this capped fold kept.
|
|
364
|
+
// Each line opens its Pending tab with its own kind chip selected, and the chip shows
|
|
365
|
+
// `kindTotals` — the uncapped figure — so the strip must say the same number or the reader
|
|
366
|
+
// clicks "15" and lands on "173". The card count stays the fallback for a fold predating it.
|
|
367
|
+
stalled: insights.kindTotals?.stalled_review ?? stalled,
|
|
368
|
+
untouchedThreads: insights.kindTotals?.untouched_thread ?? untouchedThreads,
|
|
369
|
+
needsReviewer: insights.kindTotals?.reviewer_routing ?? needsReviewer,
|
|
370
|
+
resolveBacklog: backlog.totalThreads,
|
|
371
|
+
botAnomalies,
|
|
372
|
+
trunkRed,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* THE ACTIVE WORKSPACE'S BRIEF — the one whose numbers sit above lists the strip can open, so its
|
|
377
|
+
* counts are computed FRESH on every call and never served from a cache (only the anomaly slice
|
|
378
|
+
* inside them is TTL'd; see the header). `computedAt` therefore stamps THE COUNTS, not the object.
|
|
379
|
+
*
|
|
380
|
+
* ⚠ Do not "optimise" this into `getDailyBriefCounts` below. They differ in exactly one way and it
|
|
381
|
+
* is the whole fix: this one cannot lag GET /api/attention, that one may.
|
|
382
|
+
*/
|
|
383
|
+
export async function getDailyBriefEntry(accountId, workspaceId) {
|
|
384
|
+
// Stamped before the fold, so `generatedAt` can never claim to be newer than the read it describes.
|
|
385
|
+
const computedAt = new Date();
|
|
386
|
+
return { counts: await computeBriefCounts(accountId, workspaceId), computedAt };
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* The CACHED daily-brief fold (ProHostQueries.getDailyBriefCounts's real body — bind.ts swaps its
|
|
390
|
+
* declared-inert throw for this). Counts only, no cost fields, behind the module-level 5-min TTL.
|
|
391
|
+
*
|
|
392
|
+
* ⚠ Callers are the ones for whom a ≤5-min-old count cannot be contradicted by a click: the
|
|
393
|
+
* roll-up's OTHER-workspace lines (switching workspace re-derives them fresh) and the Pro
|
|
394
|
+
* narration's input assembly (whose phrasing is digit-free — the figures the strip paints always
|
|
395
|
+
* come from the counts response). The workspace the user is LOOKING at goes through
|
|
396
|
+
* `getDailyBriefEntry` above instead.
|
|
397
|
+
*
|
|
398
|
+
* ⚠ And the fresh path must NOT seed this cache with what it just computed, obvious as that looks:
|
|
399
|
+
* the narration's payload hash is read from here, so a counts vector that tracked every read would
|
|
400
|
+
* bill a regeneration for a count that ticked down and back up inside one window. The TTL is a
|
|
401
|
+
* cost gate on this path, not a performance one.
|
|
402
|
+
*/
|
|
403
|
+
export async function getDailyBriefCounts(accountId, workspaceId) {
|
|
404
|
+
return cached(countsCache, `${accountId}:${workspaceId}`, COUNTS_TTL_MS, () => computeBriefCounts(accountId, workspaceId));
|
|
405
|
+
}
|
|
406
|
+
//# sourceMappingURL=daily-brief.js.map
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { DEPENDENCY_STATE_BASE, MERGE_QUEUE_CARD_DETAIL, REVIEW_BOT_KINDS } from '../shared/index.js';
|
|
2
|
+
import { reviewBotKind, roleForBotLogin } from '../sync/bot-detection.js';
|
|
3
|
+
import { canonicalAdvisoryId } from '../sync/security-detect.js';
|
|
4
|
+
import { isConflicting, isRedCiStatus } from './pending-classify.js';
|
|
5
|
+
/**
|
|
6
|
+
* WHO MADE THIS CHANGE, when it was automation. ONE function behind the card field, the lens side
|
|
7
|
+
* and the Dependencies-tab membership, so the three cannot disagree.
|
|
8
|
+
*
|
|
9
|
+
* 1. `dependencyVendor` set (a tool's own marker on the PR) → { role:'dependency', kind: vendor,
|
|
10
|
+
* source: author is automated ? 'account' : 'marker' }. The marker wins the ROLE even over an
|
|
11
|
+
* automated author with another role (Socket Fix / Frogbot run as github-actions).
|
|
12
|
+
* 2. author null or not in `automatedIds` → null (a person; a manual "human"
|
|
13
|
+
* already removed the actor from the set).
|
|
14
|
+
* 3. role = manualRoleOf ?? roleForBotLogin(login) ?? (reviewBotKind(login) ? 'review')
|
|
15
|
+
* ?? (stored role ≠ 'review' ? stored) ?? (kind ∈ REVIEW_BOT_KINDS ? 'review')
|
|
16
|
+
* ?? 'code_agent'
|
|
17
|
+
* ⚠ The last resort is `code_agent`, NOT `resolveActorLanes`' `quality_gate`: an unknown
|
|
18
|
+
* automation that OPENS a pull request writes code. Different question, deliberate divergence.
|
|
19
|
+
* ⚠ A DERIVED 'review' is the default an unknown login gets, so it is not evidence — the
|
|
20
|
+
* resolveActorLanes rule; every other stored role was positively concluded and wins.
|
|
21
|
+
* 4. kind = kindOf.get(id) ?? null; source 'account'.
|
|
22
|
+
*/
|
|
23
|
+
export function authorAutomationFor(authorId, dependencyVendor, x) {
|
|
24
|
+
const automated = authorId != null && x.automatedIds.has(authorId);
|
|
25
|
+
if (dependencyVendor != null) {
|
|
26
|
+
return { role: 'dependency', kind: dependencyVendor, source: automated ? 'account' : 'marker' };
|
|
27
|
+
}
|
|
28
|
+
if (!automated)
|
|
29
|
+
return null;
|
|
30
|
+
const login = x.loginOf.get(authorId) ?? null;
|
|
31
|
+
const stored = x.storedRoleOf.get(authorId);
|
|
32
|
+
const kind = x.kindOf.get(authorId) ?? null;
|
|
33
|
+
const role = x.manualRoleOf.get(authorId) ??
|
|
34
|
+
roleForBotLogin(login) ??
|
|
35
|
+
(reviewBotKind(login) != null ? 'review' : null) ??
|
|
36
|
+
(stored != null && stored !== 'review' ? stored : null) ??
|
|
37
|
+
(kind != null && REVIEW_BOT_KINDS.has(kind) ? 'review' : null) ??
|
|
38
|
+
'code_agent';
|
|
39
|
+
return { role, kind, source: 'account' };
|
|
40
|
+
}
|
|
41
|
+
/** A dependency PR's next step, folded from its synced columns. FIRST MATCH WINS, in this order:
|
|
42
|
+
* conflicts (mergeStateStatus 'dirty' OR mergeable 'conflicting') → ci_red (ciStatus failure|error,
|
|
43
|
+
* and GitHub would not merge it) → behind (mss 'behind') → ready (`readyToMerge`, i.e.
|
|
44
|
+
* READY_MERGE_STATES.has(mss)) → needs_review (reviewDecision review_required|changes_requested) →
|
|
45
|
+
* blocked (mss 'blocked') → unknown. ⚠ NULL columns are NOT OBSERVED and fall through to `unknown`,
|
|
46
|
+
* never to `ready`.
|
|
47
|
+
* ⚠ A RED BUILD GITHUB WOULD STILL MERGE IS `ready`: `unstable` means only NON-required checks are
|
|
48
|
+
* red, and it is mergeable (`mergeVerdict()`, READY_MERGE_STATES). So this card and a person's
|
|
49
|
+
* `merge` card say one thing about one state — "Can land now, but non-required checks are red". */
|
|
50
|
+
export function dependencyPrState(p) {
|
|
51
|
+
if (isConflicting(p))
|
|
52
|
+
return 'conflicts';
|
|
53
|
+
if (isRedCiStatus(p.ciStatus) && !p.readyToMerge)
|
|
54
|
+
return 'ci_red';
|
|
55
|
+
if (p.mergeStateStatus === 'behind')
|
|
56
|
+
return 'behind';
|
|
57
|
+
if (p.readyToMerge)
|
|
58
|
+
return 'ready';
|
|
59
|
+
if (p.reviewDecision === 'review_required' || p.reviewDecision === 'changes_requested') {
|
|
60
|
+
return 'needs_review';
|
|
61
|
+
}
|
|
62
|
+
if (p.mergeStateStatus === 'blocked')
|
|
63
|
+
return 'blocked';
|
|
64
|
+
return 'unknown';
|
|
65
|
+
}
|
|
66
|
+
// THE ONE SENTENCE a Pending card says while GitHub's merge queue holds its PR. It lives in
|
|
67
|
+
// `packages/shared` (pending-rules.ts, with its reasons) because the SPA recognises it to leave it
|
|
68
|
+
// off a card whose queue chip or merge row already says so; re-exported here for this module's
|
|
69
|
+
// callers and tests.
|
|
70
|
+
export { MERGE_QUEUE_CARD_DETAIL };
|
|
71
|
+
/** The state as a sentence. CODE-WRITTEN, TIME-FREE (the forward-card rule — it is hashed nowhere
|
|
72
|
+
* today, but it is the ranker's `reason` shape), and it says what the state chip beside it cannot.
|
|
73
|
+
* `readyDetail` is the caller's `mergeCardDetail('merge', mss, 0, inMergeQueue)`, so a ready
|
|
74
|
+
* dependency PR and a `merge` card say the same sentence.
|
|
75
|
+
*
|
|
76
|
+
* ⚠ WHILE GITHUB'S QUEUE HOLDS THE PR (`inMergeQueue === true`, a positive observation — null is
|
|
77
|
+
* "not observed" and claims nothing), every state the queue settles by itself says
|
|
78
|
+
* `MERGE_QUEUE_CARD_DETAIL` instead: "behind", "needs review" and "blocked" are all things the
|
|
79
|
+
* queue has already accepted the PR past. `conflicts` and `ci_red` keep their own sentences — a
|
|
80
|
+
* real fault the queue will eject the entry for is still news. */
|
|
81
|
+
export function dependencyStateDetail(state, p, readyDetail) {
|
|
82
|
+
if (p.inMergeQueue === true && state !== 'conflicts' && state !== 'ci_red') {
|
|
83
|
+
return MERGE_QUEUE_CARD_DETAIL;
|
|
84
|
+
}
|
|
85
|
+
switch (state) {
|
|
86
|
+
case 'conflicts':
|
|
87
|
+
return `Conflicts with ${p.baseRefName ?? 'the base branch'}`;
|
|
88
|
+
case 'ci_red':
|
|
89
|
+
return 'CI is failing';
|
|
90
|
+
case 'behind':
|
|
91
|
+
return 'GitHub blocks the merge until the branch is updated';
|
|
92
|
+
case 'ready':
|
|
93
|
+
return readyDetail;
|
|
94
|
+
case 'needs_review':
|
|
95
|
+
return p.reviewDecision === 'changes_requested'
|
|
96
|
+
? 'Changes were requested'
|
|
97
|
+
: 'Needs an approving review';
|
|
98
|
+
case 'blocked':
|
|
99
|
+
return 'Required checks or reviews aren’t satisfied';
|
|
100
|
+
case 'unknown':
|
|
101
|
+
return 'GitHub has not worked out if it can merge this yet';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** The security sentence for a proven fix PR, or '' — when only alerts put the card here, and for
|
|
105
|
+
* an INFERRED fix, whose header already says "Likely security fix" (a sentence could only repeat it,
|
|
106
|
+
* or explain what we cannot know; the why lives in the card's info popover). `advisoryIds` are the
|
|
107
|
+
* ids the FIX names (the PR's own markers) — never an alert's, which the PR may not fix. */
|
|
108
|
+
export function securityFixDetail(fix, advisoryIds) {
|
|
109
|
+
if (fix == null || fix === 'inferred')
|
|
110
|
+
return '';
|
|
111
|
+
const [first] = advisoryIds;
|
|
112
|
+
if (first == null)
|
|
113
|
+
return 'Fixes a known security advisory';
|
|
114
|
+
const more = advisoryIds.length - 1;
|
|
115
|
+
return more > 0 ? `Fixes ${first} and ${more} more` : `Fixes ${first}`;
|
|
116
|
+
}
|
|
117
|
+
/** Every id a security card names, as the wire carries it: complete up to this safety cap — the
|
|
118
|
+
* detector's own per-PR cap — so a reader knows the list is whole below it. */
|
|
119
|
+
const ADVISORY_IDS_MAX = 50;
|
|
120
|
+
/** Fix ids first, then each alert's ids, canonical, deduplicated, capped at the 50-id safety cap. */
|
|
121
|
+
export function unionAdvisoryIds(fixIds, alerts) {
|
|
122
|
+
const out = [];
|
|
123
|
+
const seen = new Set();
|
|
124
|
+
for (const raw of [...fixIds, ...alerts.flatMap((a) => a.advisoryIds)]) {
|
|
125
|
+
const id = canonicalAdvisoryId(raw);
|
|
126
|
+
if (seen.has(id))
|
|
127
|
+
continue;
|
|
128
|
+
seen.add(id);
|
|
129
|
+
out.push(id);
|
|
130
|
+
if (out.length >= ADVISORY_IDS_MAX)
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
/** Where a Dependencies-tab card starts on the Do next scale. `state` null = a person's PR a
|
|
136
|
+
* security tool flagged (no merge row of its own) → 'security_alert'. 'ready' splits on approval
|
|
137
|
+
* exactly like a `merge` card. Everything else reads DEPENDENCY_STATE_BASE. */
|
|
138
|
+
export function dependencyProximityBase(state, approvals) {
|
|
139
|
+
if (state == null)
|
|
140
|
+
return 'security_alert';
|
|
141
|
+
if (state === 'ready')
|
|
142
|
+
return approvals > 0 ? 'merge_approved' : 'merge_unapproved';
|
|
143
|
+
return DEPENDENCY_STATE_BASE[state];
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=dependency-cards.js.map
|