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,565 @@
1
+ import { getPersonPeriod, PERSON_METRIC_META, PERSON_METRICS_SCHEMA_VERSION, } from './person-period.js';
2
+ import { foldBotFlaggingPopulation, hydrateFlaggingPage, } from './ml-labels.js';
3
+ import { foldPrBotVolumePopulation, loadBotIdentities } from './bot-volume.js';
4
+ import { getDailyBriefCounts } from './daily-brief.js';
5
+ import { getBotReviewComments, getResolvableBotThreadPrs, getResolvableBotThreadsForScope, listWorkspaces, } from './queries.js';
6
+ /**
7
+ * The input cap (the themes CLUSTER_CAP precedent — bound the prompt, disclose the bound). 250
8
+ * items × ≤ SYNTHESIS_BODY_CAP chars ≈ 80k chars of item text, a comfortable single Haiku call.
9
+ * The plugin folds analyzed/total into the stored row so the card's coverage line cannot lie.
10
+ */
11
+ export const SYNTHESIS_INPUT_CAP = 250;
12
+ /** Per-item body budget. Whitespace-collapsed first, so the cap buys real words. */
13
+ export const SYNTHESIS_BODY_CAP = 320;
14
+ function capBody(raw) {
15
+ if (!raw)
16
+ return '';
17
+ const s = raw.replace(/\s+/g, ' ').trim();
18
+ return s.length > SYNTHESIS_BODY_CAP ? `${s.slice(0, SYNTHESIS_BODY_CAP).trimEnd()}…` : s;
19
+ }
20
+ // The flagging selector, rebuilt from the descriptor exactly as the route builds it from its
21
+ // query string. An unsatisfiable combination THROWS (the severities-400 rule: silently falling
22
+ // back would name a DIFFERENT population than the drill-down the card sits beside); the plugin
23
+ // route validates first, so a throw here is a programmer error surfacing loudly, not a user path.
24
+ function flaggingSelectorOf(scope) {
25
+ const select = scope.select ?? 'findings';
26
+ if (select === 'severity') {
27
+ if (!scope.severities || scope.severities.length === 0) {
28
+ throw new Error('synthesis scope: select=severity requires a non-empty `severities`');
29
+ }
30
+ return { kind: 'severity', severities: scope.severities };
31
+ }
32
+ if (select === 'category') {
33
+ if (!scope.category) {
34
+ throw new Error('synthesis scope: select=category requires `category`');
35
+ }
36
+ return { kind: 'category', category: scope.category };
37
+ }
38
+ return { kind: select };
39
+ }
40
+ async function flaggingInput(accountId, scope, botScope) {
41
+ const selector = flaggingSelectorOf(scope);
42
+ // The drill-down's refine, verbatim: `botUserId` is the store-seed's one-bot narrowing (a
43
+ // one-element list — `[]` would mean "no bots"), `direction` the inflation charts' over/under.
44
+ // The matrix-CELL refine is deliberately not part of the synthesis descriptor (P2.2 mounts the
45
+ // verdict at tile/bot/direction grain; a per-cell synthesis has no surface).
46
+ const refine = {
47
+ cell: null,
48
+ disagree: scope.direction ?? null,
49
+ authorUserIds: scope.botUserId != null ? [scope.botUserId] : null,
50
+ };
51
+ const pop = await foldBotFlaggingPopulation(accountId, selector, refine, scope.window, botScope);
52
+ const capped = pop.narrowed.slice(0, SYNTHESIS_INPUT_CAP);
53
+ // Hydration BY ID of the capped rows — the same helper the route's page hydration uses. A label
54
+ // whose parent row is gone is dropped here exactly as it is dropped from the page (and stays in
55
+ // `totalCount`, exactly as it stays in `filteredTotal`).
56
+ const hydrated = await hydrateFlaggingPage(accountId, botScope, capped);
57
+ const items = hydrated.map((c) => ({
58
+ // `targetId` is namespaced by targetKind — the ref prefixes keep the three id spaces apart.
59
+ id: c.targetKind === 'review_comment'
60
+ ? `rc:${c.targetId}`
61
+ : c.targetKind === 'pr_comment'
62
+ ? `pc:${c.targetId}`
63
+ : `rv:${c.targetId}`,
64
+ kind: c.targetKind,
65
+ authorLabel: c.authorLabel,
66
+ createdAt: c.createdAt,
67
+ body: capBody(c.body),
68
+ path: c.path,
69
+ // OUR severity, from the same fold that admitted the row — never the vendor badge. (The wire
70
+ // type allows a null label; hydrateFlaggingPage always sets it, but degrade rather than trust.)
71
+ severity: c.mlLabel?.severity ?? null,
72
+ }));
73
+ return {
74
+ kind: 'bot-flagging',
75
+ workspaceId: scope.workspaceId,
76
+ items,
77
+ totalCount: pop.narrowed.length,
78
+ analyzedCount: items.length,
79
+ truncated: pop.truncated || items.length < pop.narrowed.length,
80
+ };
81
+ }
82
+ async function threadsInput(accountId, scope, botScope) {
83
+ // The drill-down's OWN fold: the uncapped PR-grouped backlog. `totalThreads` is the tab's
84
+ // number; the flattened thread ids (group order, then scan order within a group) are the set.
85
+ // NOTE the backlog is windowless — `scope.window` is ignored for this kind, and the plugin's
86
+ // scope key canonicalises it out so two windows cannot mint two cache rows for one set.
87
+ const { prs, totalThreads } = await getResolvableBotThreadPrs(accountId, botScope);
88
+ const orderedIds = [];
89
+ for (const pr of prs)
90
+ orderedIds.push(...pr.threadIds);
91
+ const cappedIds = orderedIds.slice(0, SYNTHESIS_INPUT_CAP);
92
+ if (cappedIds.length === 0) {
93
+ return {
94
+ kind: 'bot-threads',
95
+ workspaceId: scope.workspaceId,
96
+ items: [],
97
+ totalCount: totalThreads,
98
+ analyzedCount: 0,
99
+ truncated: totalThreads > 0,
100
+ };
101
+ }
102
+ // Text hydration through the resolve route's OWN re-derive (same predicate by contract — the
103
+ // listing offers ids that getResolvableBotThreadsForScope re-verifies, exactly as the resolve
104
+ // flow does). Passing `threadIds` bypasses the page cap, so no capped id is silently dropped;
105
+ // a thread that stopped being eligible between the two reads simply falls out, which shrinks
106
+ // `analyzedCount` honestly rather than summarising a row the list no longer shows.
107
+ const { threads } = await getResolvableBotThreadsForScope(accountId, botScope, cappedIds);
108
+ const items = threads.map((t) => ({
109
+ id: `th:${t.threadId}`,
110
+ kind: 'thread',
111
+ authorLabel: t.botLabel,
112
+ createdAt: t.threadCreatedAt,
113
+ body: capBody(t.excerpt),
114
+ path: t.path,
115
+ }));
116
+ return {
117
+ kind: 'bot-threads',
118
+ workspaceId: scope.workspaceId,
119
+ items,
120
+ totalCount: totalThreads,
121
+ analyzedCount: items.length,
122
+ truncated: items.length < totalThreads,
123
+ };
124
+ }
125
+ async function volumeInput(accountId, scope, botScope) {
126
+ // Sort is FIXED at 'comments' — the route's default. The descriptor carries no sort on purpose:
127
+ // the cap slices the fold, so a per-sort synthesis would be two different populations wearing
128
+ // one card, and 'comments' is the ordering the drill-down opens on.
129
+ const pop = await foldPrBotVolumePopulation(accountId, scope.window, botScope, { authorUserIds: scope.botUserId != null ? [scope.botUserId] : null }, 'comments');
130
+ const capped = pop.scored.slice(0, SYNTHESIS_INPUT_CAP);
131
+ // Identities only for the bots on the CAPPED rows (the page-assembly rule).
132
+ const botIds = new Set();
133
+ for (const s of capped) {
134
+ for (const [userId] of s.pr.byBot) {
135
+ if (!pop.only || pop.only.has(userId))
136
+ botIds.add(userId);
137
+ }
138
+ }
139
+ const identities = await loadBotIdentities(accountId, botScope, [...botIds]);
140
+ const items = capped.map((s) => {
141
+ // The row's "author" is its bot mix — top three by comment count, deterministic order.
142
+ const shares = [...s.pr.byBot.entries()]
143
+ .filter(([userId]) => !pop.only || pop.only.has(userId))
144
+ .map(([userId, comments]) => ({
145
+ label: identities.get(userId)?.label ?? `#${userId}`,
146
+ comments,
147
+ }))
148
+ .sort((a, b) => b.comments - a.comments || a.label.localeCompare(b.label))
149
+ .slice(0, 3);
150
+ return {
151
+ id: `pr:${s.pr.id}`,
152
+ kind: 'pr',
153
+ authorLabel: shares.map((x) => x.label).join(' · ') || 'Bots',
154
+ // `mergedAt` — the population is merged PRs and merge time is the row's stable anchor.
155
+ createdAt: s.pr.mergedAt.toISOString(),
156
+ body: capBody(s.pr.title),
157
+ };
158
+ });
159
+ return {
160
+ kind: 'bot-volume',
161
+ workspaceId: scope.workspaceId,
162
+ items,
163
+ totalCount: pop.scored.length,
164
+ analyzedCount: items.length,
165
+ truncated: pop.truncated || items.length < pop.scored.length,
166
+ };
167
+ }
168
+ async function workspaceBotsInput(accountId, scope, botScope) {
169
+ // The C6 grain: the union of the workspace's review-bot comments (inline + issue-level), from
170
+ // the ONE collection query the Themes fold reads. Each source arrives newest-first; the merge
171
+ // re-sorts so the cap keeps the newest overall.
172
+ const { comments, truncated } = await getBotReviewComments(accountId, scope.window, botScope);
173
+ const ordered = [...comments].sort((a, b) => {
174
+ const d = b.createdAt.localeCompare(a.createdAt);
175
+ if (d !== 0)
176
+ return d;
177
+ // Total tiebreak so the capped set is stable under re-reads (the hash depends on it).
178
+ if (a.source !== b.source)
179
+ return a.source < b.source ? -1 : 1;
180
+ return a.id - b.id;
181
+ });
182
+ const capped = ordered.slice(0, SYNTHESIS_INPUT_CAP);
183
+ const items = capped.map((c) => ({
184
+ // `id` is namespaced by `source` — the two id spaces collide numerically (the
185
+ // BotReviewCommentRow contract says so in as many words).
186
+ id: c.source === 'review' ? `rc:${c.id}` : `pc:${c.id}`,
187
+ kind: c.source === 'review' ? 'review_comment' : 'pr_comment',
188
+ authorLabel: c.label,
189
+ createdAt: c.createdAt,
190
+ body: capBody(c.body),
191
+ path: c.path,
192
+ }));
193
+ return {
194
+ kind: 'workspace-bots',
195
+ workspaceId: scope.workspaceId,
196
+ items,
197
+ totalCount: comments.length,
198
+ analyzedCount: items.length,
199
+ truncated: truncated || items.length < comments.length,
200
+ };
201
+ }
202
+ // ── The ORDERING grains ('brief' / 'rollup' — plan P3.1/P3.3) ───────────────────────────────
203
+ //
204
+ // These two are NOT drill-down populations: their "items" are the deterministic brief's LINES,
205
+ // their refs are what the model orders + phrases (digit-free, validated plugin-side), and the
206
+ // figures are appended by the CLIENT from GET /api/daily-brief (D4 — the model authors no
207
+ // number). The unchanged plugin hash formula folds sorted item ids + created-at, so:
208
+ // - each id ENCODES its computed count(s) — a count change changes the id changes the hash;
209
+ // - `createdAt` is EPOCH ZERO, a constant — the hash is a CONTENT hash, never a date hash. An
210
+ // unchanged workspace is a permanent $0 cache hit and the strip says "unchanged since <day>"
211
+ // from the stored row's generatedAt.
212
+ // Bodies are the templated lines (numbers included — that is model INPUT, not output).
213
+ /** Epoch-zero createdAt: the per-item "stable field" slot, deliberately date-free. */
214
+ const BRIEF_EPOCH = new Date(0).toISOString();
215
+ function briefLine(id, body) {
216
+ return { id, kind: 'brief_line', authorLabel: 'brief', createdAt: BRIEF_EPOCH, body };
217
+ }
218
+ /** One item per NONZERO line, ids count-encoded (`myTurn:3` / `anomaly:u42` / `trunk:r7`). */
219
+ export function briefLineItems(counts) {
220
+ const items = [];
221
+ if (counts.myTurn > 0)
222
+ items.push(briefLine(`myTurn:${counts.myTurn}`, `${counts.myTurn} items need your review or reply (My Turn)`));
223
+ if (counts.stalled > 0)
224
+ items.push(briefLine(`stalled:${counts.stalled}`, `${counts.stalled} PRs stalled awaiting review`));
225
+ if (counts.untouchedThreads > 0)
226
+ items.push(briefLine(`untouched:${counts.untouchedThreads}`, `${counts.untouchedThreads} review threads untouched`));
227
+ if (counts.needsReviewer > 0)
228
+ items.push(briefLine(`needsReviewer:${counts.needsReviewer}`, `${counts.needsReviewer} PRs still need a reviewer`));
229
+ if (counts.resolveBacklog > 0)
230
+ items.push(briefLine(`resolveBacklog:${counts.resolveBacklog}`, `${counts.resolveBacklog} likely-addressed bot threads ready to resolve`));
231
+ for (const a of counts.botAnomalies)
232
+ items.push(briefLine(`anomaly:u${a.userId}`, `${a.label}'s comment volume is unusual this week`));
233
+ for (const r of counts.trunkRed)
234
+ items.push(briefLine(`trunk:r${r.repoId}`, `the default branch of ${r.name} is red`));
235
+ return items;
236
+ }
237
+ async function briefInput(accountId, scope) {
238
+ const counts = await getDailyBriefCounts(accountId, scope.workspaceId);
239
+ const items = briefLineItems(counts);
240
+ return {
241
+ kind: 'brief',
242
+ workspaceId: scope.workspaceId,
243
+ items,
244
+ totalCount: items.length,
245
+ analyzedCount: items.length,
246
+ truncated: false,
247
+ };
248
+ }
249
+ /** Matches the roll-up route's own cap (workspaces are few; bounded all the same). */
250
+ const ROLLUP_WORKSPACE_CAP = 12;
251
+ /** A stable one-token signature of a workspace's count vector — the content the ws item's id
252
+ * carries into the hash. Anomalies/trunk fold as their id lists (membership changes count). */
253
+ function rollupSig(c) {
254
+ return [
255
+ c.myTurn,
256
+ c.stalled,
257
+ c.untouchedThreads,
258
+ c.needsReviewer,
259
+ c.resolveBacklog,
260
+ c.botAnomalies.map((a) => `u${a.userId}`).join('+') || '-',
261
+ c.trunkRed.map((r) => `r${r.repoId}`).join('+') || '-',
262
+ ].join('.');
263
+ }
264
+ function rollupHasAnything(c) {
265
+ return (c.myTurn > 0 ||
266
+ c.stalled > 0 ||
267
+ c.untouchedThreads > 0 ||
268
+ c.needsReviewer > 0 ||
269
+ c.resolveBacklog > 0 ||
270
+ c.botAnomalies.length > 0 ||
271
+ c.trunkRed.length > 0);
272
+ }
273
+ async function rollupInput(accountId, scope) {
274
+ // The OTHER workspaces relative to the viewed one — the Elsewhere line's population. Counts
275
+ // ride the brief fold's own TTL cache; NO cost fields exist anywhere in the vector (§8.18).
276
+ const others = (await listWorkspaces(accountId))
277
+ .filter((w) => w.id !== scope.workspaceId)
278
+ .slice(0, ROLLUP_WORKSPACE_CAP);
279
+ const items = [];
280
+ for (const w of others) {
281
+ const counts = await getDailyBriefCounts(accountId, w.id);
282
+ if (!rollupHasAnything(counts))
283
+ continue;
284
+ const bits = [];
285
+ if (counts.myTurn > 0)
286
+ bits.push(`${counts.myTurn} need you`);
287
+ if (counts.stalled > 0)
288
+ bits.push(`${counts.stalled} stalled`);
289
+ if (counts.untouchedThreads > 0)
290
+ bits.push(`${counts.untouchedThreads} untouched threads`);
291
+ if (counts.needsReviewer > 0)
292
+ bits.push(`${counts.needsReviewer} need a reviewer`);
293
+ if (counts.resolveBacklog > 0)
294
+ bits.push(`${counts.resolveBacklog} resolvable bot threads`);
295
+ if (counts.botAnomalies.length > 0)
296
+ bits.push(`${counts.botAnomalies.length} bot anomalies`);
297
+ if (counts.trunkRed.length > 0)
298
+ bits.push(`trunk red on ${counts.trunkRed.map((r) => r.name).join(', ')}`);
299
+ items.push({
300
+ id: `ws:${w.id}:${rollupSig(counts)}`,
301
+ kind: 'brief_line',
302
+ authorLabel: w.name,
303
+ createdAt: BRIEF_EPOCH,
304
+ body: `${w.name}: ${bits.join(' · ')}`,
305
+ });
306
+ }
307
+ return {
308
+ kind: 'rollup',
309
+ workspaceId: scope.workspaceId,
310
+ items,
311
+ totalCount: items.length,
312
+ analyzedCount: items.length,
313
+ truncated: false,
314
+ };
315
+ }
316
+ // ── The 'person' ORDERING grain (plan P4.2 / N4 — the 1:1-prep narration) ───────────────────
317
+ //
318
+ // The third ordering grain, and the brief's content-hash trick at the person grain: one item per
319
+ // NON-NULL vector line, each id encoding `pm<PERSON_METRICS_SCHEMA_VERSION>:<key>:<value>` — so
320
+ // the plugin's unchanged hash formula (sorted ids + created-at) IS "the vector values + the
321
+ // schema version", and the WINDOW rides the scope key (the plugin appends the person grain's
322
+ // `u:`/`pw:` slots). An unchanged fortnight is a $0 cache hit forever; a schema-version bump
323
+ // changes every id and flips the stored row stale in place. `createdAt` stays epoch zero —
324
+ // content hash, never a date hash. Bodies are the templated lines WITH their figures (model
325
+ // INPUT, fenced as JSON by the prompt); phrases come back digit-free and the client re-appends
326
+ // the live figures (D4).
327
+ /** The templated line per key — numbers included (input, not output). Kept tiny and literal;
328
+ * the SPA renders its own labels, so this only has to read well inside the prompt. */
329
+ function personLineBody(v, p) {
330
+ const n = v.value ?? 0;
331
+ switch (v.key) {
332
+ case 'merged_prs_authored':
333
+ return `${p.login} merged ${n} PRs they authored this period`;
334
+ case 'opened_prs_authored':
335
+ return `${p.login} opened ${n} PRs this period`;
336
+ case 'reviews_given':
337
+ return `${p.login} gave ${n} reviews this period`;
338
+ case 'review_comments_written':
339
+ return `${p.login} wrote ${n} inline review comments this period`;
340
+ case 'median_review_response_hours':
341
+ return `median ${n}h from a review request to their review`;
342
+ case 'median_first_human_review_hours_their_prs':
343
+ return `their PRs waited a median ${n}h for a first human review`;
344
+ case 'review_threads_on_their_prs':
345
+ return `${n} review threads were opened on their PRs this period`;
346
+ case 'their_pr_threads_addressed':
347
+ return `${n} of the ${v.sampleSize} threads on their PRs are addressed by now`;
348
+ case 'awaiting_their_review':
349
+ return `${n} open PRs are waiting on their review right now`;
350
+ case 'open_prs_authored':
351
+ return `${n} of their PRs are open right now`;
352
+ }
353
+ }
354
+ /** Epoch-zero createdAt + count-encoded ids (see the header above). Null lines are OMITTED —
355
+ * a null→value transition changes the id set, which changes the hash, which is the point. */
356
+ export function personMetricItems(p) {
357
+ const items = [];
358
+ for (const v of p.metrics) {
359
+ if (v.value == null)
360
+ continue;
361
+ items.push({
362
+ id: `pm${PERSON_METRICS_SCHEMA_VERSION}:${v.key}:${v.value}`,
363
+ kind: 'person_metric',
364
+ authorLabel: p.login,
365
+ createdAt: BRIEF_EPOCH,
366
+ // The meta note travels as context so the model phrases the LIVE keys as "now" facts.
367
+ body: PERSON_METRIC_META[v.key].basis === 'live'
368
+ ? `${personLineBody(v, p)} (a live reading, not a period figure)`
369
+ : personLineBody(v, p),
370
+ });
371
+ }
372
+ return items;
373
+ }
374
+ async function personInput(accountId, scope) {
375
+ // The plugin route validates these three before this runs (they name a POPULATION — its 400
376
+ // rule); a miss here is a programmer error surfacing loudly, exactly like flaggingSelectorOf.
377
+ if (scope.userId == null || scope.fromMs == null || scope.toMs == null) {
378
+ throw new Error("synthesis scope: kind 'person' requires `userId`, `fromMs` and `toMs`");
379
+ }
380
+ const person = await getPersonPeriod(accountId, scope.workspaceId, scope.userId, {
381
+ fromMs: scope.fromMs,
382
+ toMs: scope.toMs,
383
+ });
384
+ // No admissible person (a stranger, a bot, an empty workspace) → an EMPTY input: the POST
385
+ // answers `empty: true`, nothing is stored or billed, the panel renders its own null state.
386
+ const items = person == null ? [] : personMetricItems(person);
387
+ return {
388
+ kind: 'person',
389
+ workspaceId: scope.workspaceId,
390
+ items,
391
+ totalCount: items.length,
392
+ analyzedCount: items.length,
393
+ truncated: false,
394
+ };
395
+ }
396
+ // ── The 'person_report' SECTIONS grain (the People report's per-person narrative) ────────────
397
+ //
398
+ // The seam's third output mode's input: the person vector's `pm…` items VERBATIM (byte-identical
399
+ // to the 'person' grain — two kinds must not describe two vectors) plus the EVIDENCE rows the
400
+ // report section's cards render, read through THE SAME fold with `evidence: true` — same
401
+ // predicates, same caps, so the model summarises precisely the rows shown under it (§8.3 at the
402
+ // person grain). The WINDOW rides the scope key's `u:`/`pw:` slots exactly like 'person'.
403
+ //
404
+ // `pe<PERSON_REPORT_VERSION>:` prefixes every evidence id: ONE kind-scoped literal covering the
405
+ // evidence-item vocabulary AND the plugin's person_report section prompt — a bump changes every
406
+ // `pe…` id, so every stored person_report row flips `stale` honestly on the free GET while NO
407
+ // other kind is re-billed (the `pm<version>` trick; SYNTHESIS_PROMPT_VERSION stays untouched,
408
+ // and future person_report prompt edits bump THIS literal instead of the global one).
409
+ //
410
+ // Hash discipline inherited: every id + createdAt is a plain DB read (pr/comment/thread rows
411
+ // carry their real GitHub timestamps; the count-encoded area lines pin epoch zero), nothing is
412
+ // `Date.now()`-derived, and bodies are NOT folded. A closed period's set is stable; a late
413
+ // backfill that lands new rows flips `stale` honestly.
414
+ // 1 → 2: the author vocabulary made honest — figure lines are labelled 'brief' (the legend's
415
+ // own word for them, not the login: a dashboard line is not something the subject wrote),
416
+ // thread roots the subject authored carry their login instead of 'reviewer' (a self-review
417
+ // note is not feedback they received — and it already travels as their own `pe:rc:` item, so
418
+ // the old label handed the model the same text twice under contradictory attribution), and a
419
+ // zero-evidence input now mints the `:none` sentinel so this literal reaches its hash at all.
420
+ export const PERSON_REPORT_VERSION = 2;
421
+ async function personReportInput(accountId, scope) {
422
+ // Same required-triple rule as 'person' (the plugin route 400s first; a miss here is loud).
423
+ if (scope.userId == null || scope.fromMs == null || scope.toMs == null) {
424
+ throw new Error("synthesis scope: kind 'person_report' requires `userId`, `fromMs` and `toMs`");
425
+ }
426
+ const person = await getPersonPeriod(accountId, scope.workspaceId, scope.userId, { fromMs: scope.fromMs, toMs: scope.toMs }, { evidence: true });
427
+ // No admissible person → EMPTY input → the POST answers `empty: true`, nothing billed.
428
+ if (person == null) {
429
+ return {
430
+ kind: 'person_report',
431
+ workspaceId: scope.workspaceId,
432
+ items: [],
433
+ totalCount: 0,
434
+ analyzedCount: 0,
435
+ truncated: false,
436
+ };
437
+ }
438
+ const ev = person.evidence;
439
+ const pe = `pe${PERSON_REPORT_VERSION}`;
440
+ // The vector lines, verbatim ids/bodies (figures are model INPUT, fenced as JSON by the
441
+ // prompt) — but re-labelled 'brief': the person_report legend defines author as
442
+ // login | 'reviewer' | 'brief', and a dashboard figure line is a brief line, not a quote from
443
+ // the subject. (The 1:1 'person' kind keeps the login — its own prompt defines author that
444
+ // way. authorLabel is not hashed, so the ids stay byte-identical across the two kinds.)
445
+ const items = personMetricItems(person).map((i) => ({
446
+ ...i,
447
+ authorLabel: 'brief',
448
+ }));
449
+ // Authored-PR titles: the merged ∪ opened evidence rows, deduped, cards' own order.
450
+ const seenPr = new Set();
451
+ for (const r of [
452
+ ...(ev?.prs.merged_prs_authored?.rows ?? []),
453
+ ...(ev?.prs.opened_prs_authored?.rows ?? []),
454
+ ]) {
455
+ if (r.prId == null || seenPr.has(r.prId))
456
+ continue;
457
+ seenPr.add(r.prId);
458
+ items.push({
459
+ id: `${pe}:pr:${r.prId}`,
460
+ kind: 'pr',
461
+ authorLabel: person.login,
462
+ createdAt: r.openedAt ?? BRIEF_EPOCH,
463
+ body: capBody(r.title),
464
+ });
465
+ }
466
+ // Their own comment excerpts, split by target kind (two id spaces — the BotReviewCommentRow
467
+ // numeric-collision lesson).
468
+ for (const c of ev?.comments.rows ?? []) {
469
+ const rc = c.targetKind === 'review_comment';
470
+ items.push({
471
+ id: rc ? `${pe}:rc:${c.targetId}` : `${pe}:pc:${c.targetId}`,
472
+ kind: rc ? 'review_comment' : 'pr_comment',
473
+ authorLabel: person.login,
474
+ createdAt: c.createdAt,
475
+ body: capBody(c.body),
476
+ path: c.path,
477
+ });
478
+ }
479
+ // Thread roots on their PRs — usually feedback they RECEIVED ('reviewer', which the prompt
480
+ // legend defines as exactly that). A SELF-authored root (the "flagging this for reviewers"
481
+ // pattern) is the subject's own note and often also travels above as their `pe:rc:` comment
482
+ // item — so it carries their login, or the model reads the same text twice under
483
+ // contradictory attribution and honestly-cites their note as feedback received.
484
+ for (const t of ev?.threads.rows ?? []) {
485
+ items.push({
486
+ id: `${pe}:th:${t.threadId}`,
487
+ kind: 'thread',
488
+ authorLabel: t.selfAuthoredRoot ? person.login : 'reviewer',
489
+ createdAt: t.createdAt,
490
+ body: capBody(t.excerpt),
491
+ path: t.path,
492
+ });
493
+ }
494
+ // Path-area lines — count-encoded ids (the brief's content-hash trick: an area-mix change
495
+ // changes the id changes the hash), epoch-zero createdAt.
496
+ for (const area of ev?.pathAreas ?? []) {
497
+ items.push({
498
+ id: `${pe}:area:${area.bucket}:${area.files}`,
499
+ kind: 'path_area',
500
+ authorLabel: person.login,
501
+ createdAt: BRIEF_EPOCH,
502
+ body: `their commits touched ${area.files} files under ${area.bucket} (${area.commits} commits)`,
503
+ });
504
+ }
505
+ // The kind-version literal rides the hash ONLY via `pe…` ids, and two ordinary shapes carry
506
+ // ZERO evidence items: an awaiting-only admission (every count cell a non-null zero) and a
507
+ // reviewer-only period (the response-median sample PRs are deliberately not minted as items).
508
+ // Without a `pe…` id a PERSON_REPORT_VERSION bump would leave such stored rows `stale: false`
509
+ // forever, serving the old prompt's sections at $0. One CONSTANT sentinel line for exactly
510
+ // that slice (an evidence-carrying input already versions every id): epoch-zero createdAt,
511
+ // digit-free, and honestly citable — it states the absence rather than smuggling a marker.
512
+ if (!items.some((i) => i.id.startsWith(`${pe}:`))) {
513
+ items.push({
514
+ id: `${pe}:none`,
515
+ kind: 'brief_line',
516
+ authorLabel: 'brief',
517
+ createdAt: BRIEF_EPOCH,
518
+ body: 'no PR, comment or thread excerpts are available for this period',
519
+ });
520
+ }
521
+ // `truncated` discloses the evidence caps (each card group's own "and N more"); the item list
522
+ // itself is the analyzed set — ≈ ≤90 items, far under SYNTHESIS_INPUT_CAP by construction.
523
+ const truncated = (ev?.prs.merged_prs_authored?.more ?? 0) > 0 ||
524
+ (ev?.prs.opened_prs_authored?.more ?? 0) > 0 ||
525
+ (ev?.comments.more ?? 0) > 0 ||
526
+ (ev?.threads.more ?? 0) > 0;
527
+ return {
528
+ kind: 'person_report',
529
+ workspaceId: scope.workspaceId,
530
+ items,
531
+ totalCount: items.length,
532
+ analyzedCount: items.length,
533
+ truncated,
534
+ };
535
+ }
536
+ /**
537
+ * The ONE synthesis-input fold (ProHostQueries.getSynthesisInput's real body — bind.ts swaps its
538
+ * declared-inert throw for this). `scope.workspaceId` decides who counts as a bot;
539
+ * `scope.repoIds` narrows what is measured and is resolver-produced (`⊆` the workspace's
540
+ * membership — the plugin route intersects exactly as core's resolveWorkspaceScope does).
541
+ * The ordering grains ('brief'/'rollup') take the WHOLE workspace/account — `repoIds` and
542
+ * `window` are canonicalised out of their scope keys plugin-side and ignored here.
543
+ */
544
+ export async function getSynthesisInput(accountId, scope) {
545
+ const botScope = { workspaceId: scope.workspaceId, repoIds: scope.repoIds };
546
+ switch (scope.kind) {
547
+ case 'bot-flagging':
548
+ return flaggingInput(accountId, scope, botScope);
549
+ case 'bot-threads':
550
+ return threadsInput(accountId, scope, botScope);
551
+ case 'bot-volume':
552
+ return volumeInput(accountId, scope, botScope);
553
+ case 'workspace-bots':
554
+ return workspaceBotsInput(accountId, scope, botScope);
555
+ case 'brief':
556
+ return briefInput(accountId, scope);
557
+ case 'rollup':
558
+ return rollupInput(accountId, scope);
559
+ case 'person':
560
+ return personInput(accountId, scope);
561
+ case 'person_report':
562
+ return personReportInput(accountId, scope);
563
+ }
564
+ }
565
+ //# sourceMappingURL=synthesis-input.js.map