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.
Files changed (474) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +133 -0
  3. package/dist/ai/runtime.js +535 -0
  4. package/dist/api/plugins/auth.js +169 -0
  5. package/dist/api/plugins/error-handler.js +114 -0
  6. package/dist/api/plugins/rate-limit.js +855 -0
  7. package/dist/api/plugins/security.js +321 -0
  8. package/dist/api/routes/activity.js +103 -0
  9. package/dist/api/routes/ai-runtime.js +67 -0
  10. package/dist/api/routes/auth.js +194 -0
  11. package/dist/api/routes/billing.js +209 -0
  12. package/dist/api/routes/bot-triage.js +1289 -0
  13. package/dist/api/routes/conflicts.js +730 -0
  14. package/dist/api/routes/contact.js +318 -0
  15. package/dist/api/routes/daily-brief.js +58 -0
  16. package/dist/api/routes/flow.js +71 -0
  17. package/dist/api/routes/health.js +7 -0
  18. package/dist/api/routes/insights.js +200 -0
  19. package/dist/api/routes/me.js +458 -0
  20. package/dist/api/routes/mentions.js +22 -0
  21. package/dist/api/routes/mergers.js +8 -0
  22. package/dist/api/routes/ml-labels.js +109 -0
  23. package/dist/api/routes/open-prs.js +35 -0
  24. package/dist/api/routes/prs.js +1633 -0
  25. package/dist/api/routes/reactions.js +217 -0
  26. package/dist/api/routes/repos.js +563 -0
  27. package/dist/api/routes/search.js +41 -0
  28. package/dist/api/routes/threads.js +118 -0
  29. package/dist/api/routes/timeline.js +150 -0
  30. package/dist/api/routes/users.js +67 -0
  31. package/dist/api/routes/webhooks.js +181 -0
  32. package/dist/api/routes/workspaces.js +322 -0
  33. package/dist/app.js +274 -0
  34. package/dist/ascii.js +19 -0
  35. package/dist/auth/account.js +482 -0
  36. package/dist/auth/crypto.js +46 -0
  37. package/dist/bot-triage/resolve.js +59 -0
  38. package/dist/cli.js +388 -0
  39. package/dist/coding/activity.js +80 -0
  40. package/dist/coding/agent.js +288 -0
  41. package/dist/coding/ai-fix/auto-fix.js +212 -0
  42. package/dist/coding/ai-fix/manager.js +370 -0
  43. package/dist/coding/ai-fix/persist.js +213 -0
  44. package/dist/coding/ai-fix/pr-context.js +58 -0
  45. package/dist/coding/ai-fix/prompts.js +138 -0
  46. package/dist/coding/ai-fix/review-seed.js +357 -0
  47. package/dist/coding/ai-fix/routes.js +425 -0
  48. package/dist/coding/git-ops.js +321 -0
  49. package/dist/coding/git.js +184 -0
  50. package/dist/coding/merge.js +262 -0
  51. package/dist/coding/safe-path.js +75 -0
  52. package/dist/coding/schema.js +43 -0
  53. package/dist/config.js +648 -0
  54. package/dist/conflict/diff.js +304 -0
  55. package/dist/conflict/git.js +118 -0
  56. package/dist/conflict/hash.js +73 -0
  57. package/dist/conflict/janitor.js +218 -0
  58. package/dist/conflict/land.js +675 -0
  59. package/dist/conflict/model-types.js +2 -0
  60. package/dist/conflict/model.js +1081 -0
  61. package/dist/conflict/parse.js +142 -0
  62. package/dist/conflict/seam.js +136 -0
  63. package/dist/conflict/session.js +532 -0
  64. package/dist/conflict/suggestion.js +398 -0
  65. package/dist/conflict/tokens.js +72 -0
  66. package/dist/data-dir.js +112 -0
  67. package/dist/db/actor-lanes.js +213 -0
  68. package/dist/db/automation-ids.js +97 -0
  69. package/dist/db/automation-output.js +349 -0
  70. package/dist/db/blast-radius-query.js +36 -0
  71. package/dist/db/blast-radius.js +226 -0
  72. package/dist/db/bot-dormancy.js +115 -0
  73. package/dist/db/bot-overlap.js +579 -0
  74. package/dist/db/bot-volume.js +722 -0
  75. package/dist/db/bot-window.js +35 -0
  76. package/dist/db/branch-queries.js +310 -0
  77. package/dist/db/change-shape.js +243 -0
  78. package/dist/db/changepoint.js +75 -0
  79. package/dist/db/cleanup.js +36 -0
  80. package/dist/db/client.js +102 -0
  81. package/dist/db/code-loc.js +388 -0
  82. package/dist/db/credits.js +79 -0
  83. package/dist/db/daily-brief.js +406 -0
  84. package/dist/db/dependency-cards.js +145 -0
  85. package/dist/db/erase-account.js +255 -0
  86. package/dist/db/export-account.js +189 -0
  87. package/dist/db/failing-checks.js +124 -0
  88. package/dist/db/file-coupling.js +197 -0
  89. package/dist/db/flow-detail.js +661 -0
  90. package/dist/db/flow-pointers.js +197 -0
  91. package/dist/db/flow-settings.js +67 -0
  92. package/dist/db/forecast.js +114 -0
  93. package/dist/db/line-overlap.js +81 -0
  94. package/dist/db/merge-queue.js +189 -0
  95. package/dist/db/migrate.js +17 -0
  96. package/dist/db/migrations/0000_purple_ben_grimm.sql +155 -0
  97. package/dist/db/migrations/0001_colorful_ozymandias.sql +31 -0
  98. package/dist/db/migrations/0002_famous_sersi.sql +9 -0
  99. package/dist/db/migrations/0003_clever_shinobi_shaw.sql +3 -0
  100. package/dist/db/migrations/0004_pale_scalphunter.sql +1 -0
  101. package/dist/db/migrations/0005_daffy_guardian.sql +2 -0
  102. package/dist/db/migrations/0006_cute_violations.sql +45 -0
  103. package/dist/db/migrations/0007_dark_anthem.sql +2 -0
  104. package/dist/db/migrations/0008_multitenant_accounts.sql +44 -0
  105. package/dist/db/migrations/0009_lean_bodies.sql +13 -0
  106. package/dist/db/migrations/0010_lean_bodies_nullable.sql +56 -0
  107. package/dist/db/migrations/0011_pr_diff_size.sql +10 -0
  108. package/dist/db/migrations/0012_findings_included_default.sql +7 -0
  109. package/dist/db/migrations/0013_review_routing.sql +9 -0
  110. package/dist/db/migrations/0014_account_last_active.sql +6 -0
  111. package/dist/db/migrations/0015_repos_inbox_watch.sql +9 -0
  112. package/dist/db/migrations/0016_repo_viewer_permission.sql +7 -0
  113. package/dist/db/migrations/0017_account_display_name.sql +7 -0
  114. package/dist/db/migrations/0018_finding_comment_kind.sql +7 -0
  115. package/dist/db/migrations/0019_finding_file_in_diff.sql +7 -0
  116. package/dist/db/migrations/0020_claude_review_cost_telemetry.sql +11 -0
  117. package/dist/db/migrations/0021_account_feed_last_seen.sql +7 -0
  118. package/dist/db/migrations/0022_ci_status_events.sql +21 -0
  119. package/dist/db/migrations/0023_ai_usage.sql +21 -0
  120. package/dist/db/migrations/0024_pr_head_ref_name.sql +6 -0
  121. package/dist/db/migrations/0025_account_billing.sql +7 -0
  122. package/dist/db/migrations/0026_ai_credit_allowance.sql +5 -0
  123. package/dist/db/migrations/0027_user_github_type.sql +6 -0
  124. package/dist/db/migrations/0028_bot_review_classification.sql +21 -0
  125. package/dist/db/migrations/0029_bot_mute_rules.sql +19 -0
  126. package/dist/db/migrations/0030_teams.sql +32 -0
  127. package/dist/db/migrations/0031_events_pr_idx.sql +7 -0
  128. package/dist/db/migrations/0032_benchmark_contributions.sql +29 -0
  129. package/dist/db/migrations/0033_thread_addressed_confidence.sql +9 -0
  130. package/dist/db/migrations/0034_thread_resolved_at.sql +6 -0
  131. package/dist/db/migrations/0035_pr_first_review_requested_at.sql +5 -0
  132. package/dist/db/migrations/0036_search_index.sql +25 -0
  133. package/dist/db/migrations/0037_author_indexes.sql +15 -0
  134. package/dist/db/migrations/0038_auto_merge_requests.sql +37 -0
  135. package/dist/db/migrations/0039_branch_status.sql +44 -0
  136. package/dist/db/migrations/0040_pr_review_decision.sql +10 -0
  137. package/dist/db/migrations/0041_branch_commit_detail.sql +38 -0
  138. package/dist/db/migrations/0042_repo_reviewers.sql +176 -0
  139. package/dist/db/migrations/0043_account_reviewers.sql +131 -0
  140. package/dist/db/migrations/0044_workspaces.sql +138 -0
  141. package/dist/db/migrations/0045_workspace_reviewers.sql +180 -0
  142. package/dist/db/migrations/0046_drop_inbox_watch.sql +25 -0
  143. package/dist/db/migrations/0047_ml_comment_labels.sql +40 -0
  144. package/dist/db/migrations/0048_ml_vendor_severity.sql +31 -0
  145. package/dist/db/migrations/0049_seat_pricing_repo_description.sql +21 -0
  146. package/dist/db/migrations/0050_users_app_slug.sql +12 -0
  147. package/dist/db/migrations/0051_auto_merge_queue.sql +17 -0
  148. package/dist/db/migrations/0052_trunk_ci_status_events.sql +43 -0
  149. package/dist/db/migrations/0053_automation_roles.sql +79 -0
  150. package/dist/db/migrations/0054_automation_vendor_kinds.sql +514 -0
  151. package/dist/db/migrations/0055_auto_merge_phase.sql +12 -0
  152. package/dist/db/migrations/0056_pr_mentions.sql +22 -0
  153. package/dist/db/migrations/0057_large_pr_threshold.sql +14 -0
  154. package/dist/db/migrations/0058_pending_mute.sql +53 -0
  155. package/dist/db/migrations/0059_merge_queue_state.sql +33 -0
  156. package/dist/db/migrations/0060_drop_my_turn_dismissals.sql +33 -0
  157. package/dist/db/migrations/0061_auto_merge_durable_anchors.sql +27 -0
  158. package/dist/db/migrations/0062_blast_radius_config.sql +21 -0
  159. package/dist/db/migrations/0063_repo_file_coupling.sql +33 -0
  160. package/dist/db/migrations/0064_pr_content_kind.sql +21 -0
  161. package/dist/db/migrations/0065_workspace_flow_settings.sql +13 -0
  162. package/dist/db/migrations/0066_review_request_events.sql +31 -0
  163. package/dist/db/migrations/0067_pr_security_signals.sql +86 -0
  164. package/dist/db/migrations/0068_my_turn_settings.sql +18 -0
  165. package/dist/db/migrations/0069_my_turn_dismissals.sql +41 -0
  166. package/dist/db/migrations/0070_claude_review_follow_up_ticket.sql +22 -0
  167. package/dist/db/migrations/0071_claude_review_trigger.sql +11 -0
  168. package/dist/db/migrations/0072_commit_message_headline.sql +13 -0
  169. package/dist/db/migrations/0073_claude_review_chat.sql +31 -0
  170. package/dist/db/migrations/0074_adopt_agentic_tables.sql +98 -0
  171. package/dist/db/migrations/0075_review_lens_drop_learnings.sql +15 -0
  172. package/dist/db/migrations/0076_claude_review_threads.sql +17 -0
  173. package/dist/db/migrations/0077_claude_review_ci_failures.sql +10 -0
  174. package/dist/db/migrations/0078_ai_fix_change_report.sql +17 -0
  175. package/dist/db/migrations/0079_claude_finding_story.sql +16 -0
  176. package/dist/db/migrations/meta/0000_snapshot.json +1116 -0
  177. package/dist/db/migrations/meta/0001_snapshot.json +1321 -0
  178. package/dist/db/migrations/meta/0002_snapshot.json +1375 -0
  179. package/dist/db/migrations/meta/0003_snapshot.json +1396 -0
  180. package/dist/db/migrations/meta/0004_snapshot.json +1416 -0
  181. package/dist/db/migrations/meta/0005_snapshot.json +1430 -0
  182. package/dist/db/migrations/meta/0006_snapshot.json +1745 -0
  183. package/dist/db/migrations/meta/0007_snapshot.json +1759 -0
  184. package/dist/db/migrations/meta/_journal.json +566 -0
  185. package/dist/db/migrations-pg/0000_tired_alex_power.sql +268 -0
  186. package/dist/db/migrations-pg/0001_dizzy_mockingbird.sql +3 -0
  187. package/dist/db/migrations-pg/0002_glorious_ikaris.sql +4 -0
  188. package/dist/db/migrations-pg/0003_melted_secret_warriors.sql +2 -0
  189. package/dist/db/migrations-pg/0004_numerous_triton.sql +1 -0
  190. package/dist/db/migrations-pg/0005_thick_blockbuster.sql +2 -0
  191. package/dist/db/migrations-pg/0006_majestic_dracula.sql +1 -0
  192. package/dist/db/migrations-pg/0007_same_human_torch.sql +2 -0
  193. package/dist/db/migrations-pg/0008_slimy_toxin.sql +1 -0
  194. package/dist/db/migrations-pg/0009_fuzzy_jubilee.sql +4 -0
  195. package/dist/db/migrations-pg/0010_late_hammerhead.sql +1 -0
  196. package/dist/db/migrations-pg/0011_happy_leech.sql +16 -0
  197. package/dist/db/migrations-pg/0012_woozy_captain_midlands.sql +16 -0
  198. package/dist/db/migrations-pg/0013_yielding_nicolaos.sql +1 -0
  199. package/dist/db/migrations-pg/0014_wakeful_bill_hollister.sql +2 -0
  200. package/dist/db/migrations-pg/0015_aspiring_black_tom.sql +1 -0
  201. package/dist/db/migrations-pg/0016_cute_ghost_rider.sql +30 -0
  202. package/dist/db/migrations-pg/0017_far_changeling.sql +24 -0
  203. package/dist/db/migrations-pg/0018_fancy_miss_america.sql +1 -0
  204. package/dist/db/migrations-pg/0019_benchmark_contributions.sql +22 -0
  205. package/dist/db/migrations-pg/0020_majestic_edwin_jarvis.sql +3 -0
  206. package/dist/db/migrations-pg/0021_green_salo.sql +1 -0
  207. package/dist/db/migrations-pg/0022_wise_baron_strucker.sql +1 -0
  208. package/dist/db/migrations-pg/0023_search_index.sql +19 -0
  209. package/dist/db/migrations-pg/0024_author_indexes.sql +9 -0
  210. package/dist/db/migrations-pg/0025_auto_merge_requests.sql +26 -0
  211. package/dist/db/migrations-pg/0026_branch_status.sql +32 -0
  212. package/dist/db/migrations-pg/0027_pr_review_decision.sql +5 -0
  213. package/dist/db/migrations-pg/0028_branch_commit_detail.sql +9 -0
  214. package/dist/db/migrations-pg/0029_repo_reviewers.sql +127 -0
  215. package/dist/db/migrations-pg/0030_account_reviewers.sql +77 -0
  216. package/dist/db/migrations-pg/0031_workspaces.sql +131 -0
  217. package/dist/db/migrations-pg/0032_workspace_reviewers.sql +165 -0
  218. package/dist/db/migrations-pg/0033_drop_inbox_watch.sql +14 -0
  219. package/dist/db/migrations-pg/0034_ml_comment_labels.sql +33 -0
  220. package/dist/db/migrations-pg/0035_ml_vendor_severity.sql +16 -0
  221. package/dist/db/migrations-pg/0036_seat_pricing_repo_description.sql +12 -0
  222. package/dist/db/migrations-pg/0037_users_app_slug.sql +9 -0
  223. package/dist/db/migrations-pg/0038_auto_merge_queue.sql +10 -0
  224. package/dist/db/migrations-pg/0039_trunk_ci_status_events.sql +27 -0
  225. package/dist/db/migrations-pg/0040_automation_roles.sql +63 -0
  226. package/dist/db/migrations-pg/0041_automation_vendor_kinds.sql +488 -0
  227. package/dist/db/migrations-pg/0042_auto_merge_phase.sql +8 -0
  228. package/dist/db/migrations-pg/0043_pr_mentions.sql +19 -0
  229. package/dist/db/migrations-pg/0044_large_pr_threshold.sql +6 -0
  230. package/dist/db/migrations-pg/0045_pending_mute.sql +26 -0
  231. package/dist/db/migrations-pg/0046_merge_queue_state.sql +18 -0
  232. package/dist/db/migrations-pg/0047_drop_my_turn_dismissals.sql +21 -0
  233. package/dist/db/migrations-pg/0048_auto_merge_durable_anchors.sql +27 -0
  234. package/dist/db/migrations-pg/0049_blast_radius_config.sql +8 -0
  235. package/dist/db/migrations-pg/0050_repo_file_coupling.sql +13 -0
  236. package/dist/db/migrations-pg/0051_pr_content_kind.sql +6 -0
  237. package/dist/db/migrations-pg/0052_workspace_flow_settings.sql +4 -0
  238. package/dist/db/migrations-pg/0053_review_request_events.sql +18 -0
  239. package/dist/db/migrations-pg/0054_pr_security_signals.sql +59 -0
  240. package/dist/db/migrations-pg/0055_my_turn_settings.sql +6 -0
  241. package/dist/db/migrations-pg/0056_my_turn_dismissals.sql +21 -0
  242. package/dist/db/migrations-pg/0057_claude_review_follow_up_ticket.sql +8 -0
  243. package/dist/db/migrations-pg/0058_claude_review_trigger.sql +4 -0
  244. package/dist/db/migrations-pg/0059_commit_message_headline.sql +3 -0
  245. package/dist/db/migrations-pg/0060_claude_review_chat.sql +24 -0
  246. package/dist/db/migrations-pg/0061_adopt_agentic_tables.sql +79 -0
  247. package/dist/db/migrations-pg/0062_review_lens_drop_learnings.sql +5 -0
  248. package/dist/db/migrations-pg/0063_claude_review_threads.sql +7 -0
  249. package/dist/db/migrations-pg/0064_claude_review_ci_failures.sql +4 -0
  250. package/dist/db/migrations-pg/0065_ai_fix_change_report.sql +9 -0
  251. package/dist/db/migrations-pg/0066_claude_finding_story.sql +7 -0
  252. package/dist/db/migrations-pg/meta/0000_snapshot.json +2137 -0
  253. package/dist/db/migrations-pg/meta/0001_snapshot.json +2143 -0
  254. package/dist/db/migrations-pg/meta/0002_snapshot.json +2170 -0
  255. package/dist/db/migrations-pg/meta/0003_snapshot.json +2182 -0
  256. package/dist/db/migrations-pg/meta/0004_snapshot.json +2188 -0
  257. package/dist/db/migrations-pg/meta/0005_snapshot.json +2201 -0
  258. package/dist/db/migrations-pg/meta/0006_snapshot.json +2207 -0
  259. package/dist/db/migrations-pg/meta/0007_snapshot.json +2219 -0
  260. package/dist/db/migrations-pg/meta/0008_snapshot.json +2226 -0
  261. package/dist/db/migrations-pg/meta/0009_snapshot.json +2250 -0
  262. package/dist/db/migrations-pg/meta/0010_snapshot.json +2256 -0
  263. package/dist/db/migrations-pg/meta/0011_snapshot.json +2412 -0
  264. package/dist/db/migrations-pg/meta/0012_snapshot.json +2528 -0
  265. package/dist/db/migrations-pg/meta/0013_snapshot.json +2534 -0
  266. package/dist/db/migrations-pg/meta/0014_snapshot.json +2547 -0
  267. package/dist/db/migrations-pg/meta/0015_snapshot.json +2553 -0
  268. package/dist/db/migrations-pg/meta/0016_snapshot.json +2774 -0
  269. package/dist/db/migrations-pg/meta/0017_snapshot.json +2999 -0
  270. package/dist/db/migrations-pg/meta/0018_snapshot.json +3020 -0
  271. package/dist/db/migrations-pg/meta/0020_snapshot.json +3228 -0
  272. package/dist/db/migrations-pg/meta/0021_snapshot.json +3234 -0
  273. package/dist/db/migrations-pg/meta/0022_snapshot.json +3240 -0
  274. package/dist/db/migrations-pg/meta/_journal.json +475 -0
  275. package/dist/db/ml-labels.js +1666 -0
  276. package/dist/db/my-turn-dismissals.js +343 -0
  277. package/dist/db/my-turn-settings.js +34 -0
  278. package/dist/db/pending-classify.js +46 -0
  279. package/dist/db/pending-mute.js +171 -0
  280. package/dist/db/pending-tabs.js +238 -0
  281. package/dist/db/period-metrics.js +820 -0
  282. package/dist/db/person-period.js +763 -0
  283. package/dist/db/pr-intervals.js +930 -0
  284. package/dist/db/pr-liveness.js +240 -0
  285. package/dist/db/pr-mentions.js +279 -0
  286. package/dist/db/pr-merge-queue-stamp.js +91 -0
  287. package/dist/db/queries.js +14631 -0
  288. package/dist/db/reaction-targets.js +94 -0
  289. package/dist/db/repo-activity.js +171 -0
  290. package/dist/db/retention.js +138 -0
  291. package/dist/db/review-threads-for-review.js +240 -0
  292. package/dist/db/run-migrations.js +24 -0
  293. package/dist/db/schema.pg.js +1220 -0
  294. package/dist/db/schema.sqlite.js +1801 -0
  295. package/dist/db/search.js +317 -0
  296. package/dist/db/security-alerts.js +218 -0
  297. package/dist/db/synthesis-input.js +565 -0
  298. package/dist/db/triage.js +320 -0
  299. package/dist/db/usage.js +63 -0
  300. package/dist/db/work-plan.js +1086 -0
  301. package/dist/db/working-hours.js +161 -0
  302. package/dist/feed/my-turn.js +122 -0
  303. package/dist/github/actions-logs.js +397 -0
  304. package/dist/github/auth.js +86 -0
  305. package/dist/github/branch-queries.js +229 -0
  306. package/dist/github/client.js +454 -0
  307. package/dist/github/codeowners.js +221 -0
  308. package/dist/github/commit-checks.js +105 -0
  309. package/dist/github/compare.js +110 -0
  310. package/dist/github/diff-anchor.js +227 -0
  311. package/dist/github/mutations.js +706 -0
  312. package/dist/github/pr-file-content.js +254 -0
  313. package/dist/github/pr-liveness.js +161 -0
  314. package/dist/github/queries.js +875 -0
  315. package/dist/github/rate-budget.js +129 -0
  316. package/dist/github/reactions.js +161 -0
  317. package/dist/github/reviewer-suggest.js +104 -0
  318. package/dist/github/team-reviewers.js +96 -0
  319. package/dist/index.js +184 -0
  320. package/dist/merge/auto-merge-runner.js +960 -0
  321. package/dist/ml/severity-client.js +218 -0
  322. package/dist/pr/detail-enricher.js +26 -0
  323. package/dist/pro/bind.js +302 -0
  324. package/dist/pro/contract.js +37 -0
  325. package/dist/pro/migrate.js +64 -0
  326. package/dist/review/agent-context.js +127 -0
  327. package/dist/review/agent.js +385 -0
  328. package/dist/review/agentic.js +59 -0
  329. package/dist/review/auth.js +94 -0
  330. package/dist/review/chat-agent.js +179 -0
  331. package/dist/review/claude-review/ai-ready.js +16 -0
  332. package/dist/review/claude-review/auto-settings.js +139 -0
  333. package/dist/review/claude-review/auto.js +244 -0
  334. package/dist/review/claude-review/chat.js +489 -0
  335. package/dist/review/claude-review/ci-failures.js +350 -0
  336. package/dist/review/claude-review/follow-up.js +310 -0
  337. package/dist/review/claude-review/manager.js +686 -0
  338. package/dist/review/claude-review/persist.js +996 -0
  339. package/dist/review/claude-review/prompts.js +357 -0
  340. package/dist/review/claude-review/routes.js +518 -0
  341. package/dist/review/claude-review/routing.js +61 -0
  342. package/dist/review/claude-review/specialists.js +190 -0
  343. package/dist/review/claude-review/threads.js +265 -0
  344. package/dist/review/claude-review/ticket.js +282 -0
  345. package/dist/review/clone-hygiene.js +380 -0
  346. package/dist/review/clone-manager.js +367 -0
  347. package/dist/review/llm.js +113 -0
  348. package/dist/review/model-options.js +48 -0
  349. package/dist/review/plugin-providers.js +12 -0
  350. package/dist/review/post-review.js +216 -0
  351. package/dist/review/post-seam.js +161 -0
  352. package/dist/review/prepare.js +168 -0
  353. package/dist/review/pricing.js +17 -0
  354. package/dist/review/schema.js +156 -0
  355. package/dist/review/submit-map.js +41 -0
  356. package/dist/review/usage.js +90 -0
  357. package/dist/shared/claude-review.js +335 -0
  358. package/dist/shared/conflict-fold.js +151 -0
  359. package/dist/shared/conflicts.js +30 -0
  360. package/dist/shared/flow-settings.js +191 -0
  361. package/dist/shared/index.js +8 -0
  362. package/dist/shared/my-turn-settings.js +238 -0
  363. package/dist/shared/pending-rules.js +281 -0
  364. package/dist/shared/product.js +54 -0
  365. package/dist/shared/types.js +896 -0
  366. package/dist/status.js +386 -0
  367. package/dist/sync/adaptive.js +185 -0
  368. package/dist/sync/app-attribution.js +66 -0
  369. package/dist/sync/auth-notices.js +36 -0
  370. package/dist/sync/backfill-ci-history.js +315 -0
  371. package/dist/sync/backfill-commit-headlines.js +136 -0
  372. package/dist/sync/backfill-pr-security.js +175 -0
  373. package/dist/sync/backfill-review-requests.js +97 -0
  374. package/dist/sync/benchmark-rollup.js +50 -0
  375. package/dist/sync/bot-detection.js +309 -0
  376. package/dist/sync/bot-resolution-markers.js +30 -0
  377. package/dist/sync/branch-status.js +634 -0
  378. package/dist/sync/classify-change-shape.js +150 -0
  379. package/dist/sync/commit-files.js +74 -0
  380. package/dist/sync/derive-thread-state.js +99 -0
  381. package/dist/sync/drain-review-threads.js +212 -0
  382. package/dist/sync/hydrate-detail.js +482 -0
  383. package/dist/sync/mention-scan.js +89 -0
  384. package/dist/sync/ml-enrichment.js +478 -0
  385. package/dist/sync/pr-change-signal.js +96 -0
  386. package/dist/sync/pr-liveness-sweep.js +139 -0
  387. package/dist/sync/pr-settle.js +381 -0
  388. package/dist/sync/refresh-pr.js +163 -0
  389. package/dist/sync/reparse-vendor-badges.js +495 -0
  390. package/dist/sync/resync-after-write.js +280 -0
  391. package/dist/sync/review-fingerprint.js +117 -0
  392. package/dist/sync/reviewer-behavior.js +103 -0
  393. package/dist/sync/reviewer-classify.js +503 -0
  394. package/dist/sync/routing-files.js +97 -0
  395. package/dist/sync/scheduled-jobs.js +12 -0
  396. package/dist/sync/scheduler.js +159 -0
  397. package/dist/sync/security-detect.js +521 -0
  398. package/dist/sync/sync-manager.js +750 -0
  399. package/dist/sync/sync-one-pr.js +205 -0
  400. package/dist/sync/sync-repo.js +400 -0
  401. package/dist/sync/unsettled-prs.js +506 -0
  402. package/dist/sync/upsert.js +1190 -0
  403. package/package.json +56 -0
  404. package/public/apple-touch-icon.png +0 -0
  405. package/public/assets/AiFixTab-CQP6PT5u.js +2 -0
  406. package/public/assets/ClaudeReviewTab-DQJ-58ZA.js +8 -0
  407. package/public/assets/PrBotBehaviourTab-CCnFrWbC.js +1 -0
  408. package/public/assets/archivo-latin-600-BN-JkNrl.woff2 +0 -0
  409. package/public/assets/highlight-BEHUn5zE.css +10 -0
  410. package/public/assets/highlight-CIIZLa6Z.js +12 -0
  411. package/public/assets/index-BDnYs6zg.js +73 -0
  412. package/public/assets/index-mCuI--RC.css +1 -0
  413. package/public/assets/markdown-I_JLLVAZ.js +32 -0
  414. package/public/assets/perfProbe-Da13EhTE.js +1 -0
  415. package/public/assets/tanstack-5qsxxaLe.js +17 -0
  416. package/public/assets/vis-C3BCkP5U.js +1280 -0
  417. package/public/assets/vis-ziCVehMy.css +1 -0
  418. package/public/favicon-16.png +0 -0
  419. package/public/favicon-32.png +0 -0
  420. package/public/favicon-48.png +0 -0
  421. package/public/favicon.svg +5 -0
  422. package/public/index.html +41 -0
  423. package/public-landing/apple-touch-icon.png +0 -0
  424. package/public-landing/assets/archivo-latin-500-BWmvx2Mg.woff2 +0 -0
  425. package/public-landing/assets/archivo-latin-600-BN-JkNrl.woff2 +0 -0
  426. package/public-landing/assets/index-CA3eEGXO.js +40 -0
  427. package/public-landing/assets/index-naQrQ6Fe.css +1 -0
  428. package/public-landing/assets/jetbrains-mono-latin-400-RruCX7RW.woff2 +0 -0
  429. package/public-landing/assets/newsreader-latin-400-DExrRRrN.woff2 +0 -0
  430. package/public-landing/assets/newsreader-latin-400-italic-b0I-AwbV.woff2 +0 -0
  431. package/public-landing/bots/index.html +156 -0
  432. package/public-landing/contact/index.html +156 -0
  433. package/public-landing/cookies/index.html +156 -0
  434. package/public-landing/demo/limn-walkthrough-poster.jpg +0 -0
  435. package/public-landing/demo/limn-walkthrough.cues.json +44 -0
  436. package/public-landing/demo/limn-walkthrough.mp4 +0 -0
  437. package/public-landing/favicon-16.png +0 -0
  438. package/public-landing/favicon-32.png +0 -0
  439. package/public-landing/favicon-48.png +0 -0
  440. package/public-landing/favicon.svg +5 -0
  441. package/public-landing/features/index.html +156 -0
  442. package/public-landing/for-developers/index.html +156 -0
  443. package/public-landing/for-managers/index.html +156 -0
  444. package/public-landing/how-it-works/index.html +156 -0
  445. package/public-landing/how-we-measure/index.html +156 -0
  446. package/public-landing/icon-192.png +0 -0
  447. package/public-landing/icon-512.png +0 -0
  448. package/public-landing/index.html +156 -0
  449. package/public-landing/insights/index.html +156 -0
  450. package/public-landing/og-image.png +0 -0
  451. package/public-landing/pricing/index.html +156 -0
  452. package/public-landing/privacy/index.html +156 -0
  453. package/public-landing/pro/index.html +156 -0
  454. package/public-landing/reviews/index.html +156 -0
  455. package/public-landing/robots.txt +8 -0
  456. package/public-landing/shots/benchmark.png +0 -0
  457. package/public-landing/shots/bot-roi.png +0 -0
  458. package/public-landing/shots/bot-settings.png +0 -0
  459. package/public-landing/shots/chronology.png +0 -0
  460. package/public-landing/shots/feed.png +0 -0
  461. package/public-landing/shots/flow-metrics.png +0 -0
  462. package/public-landing/shots/free-reports.png +0 -0
  463. package/public-landing/shots/pending-board.png +0 -0
  464. package/public-landing/shots/pending-card.png +0 -0
  465. package/public-landing/shots/period-report.png +0 -0
  466. package/public-landing/shots/pr-detail.png +0 -0
  467. package/public-landing/shots/pr-threads.png +0 -0
  468. package/public-landing/shots/reach.png +0 -0
  469. package/public-landing/shots/repo-rows.png +0 -0
  470. package/public-landing/shots/severity-findings.png +0 -0
  471. package/public-landing/shots/severity-strip.png +0 -0
  472. package/public-landing/site.webmanifest +14 -0
  473. package/public-landing/sitemap.xml +51 -0
  474. 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