limn-review 0.1.128 → 0.1.129

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 (383) hide show
  1. package/THIRD_PARTY_NOTICES.md +21 -0
  2. package/dist/ai/agent-isolation.js +45 -0
  3. package/dist/ai/runtime.js +114 -73
  4. package/dist/api/plugins/rate-limit.js +134 -12
  5. package/dist/api/routes/activity.js +6 -17
  6. package/dist/api/routes/bot-triage.js +45 -77
  7. package/dist/api/routes/conflicts.js +1 -1
  8. package/dist/api/routes/daily-brief.js +3 -3
  9. package/dist/api/routes/dependency-merge.js +96 -0
  10. package/dist/api/routes/flow.js +39 -6
  11. package/dist/api/routes/insights.js +92 -24
  12. package/dist/api/routes/me.js +8 -3
  13. package/dist/api/routes/ml-labels.js +2 -2
  14. package/dist/api/routes/ml-runtime.js +64 -0
  15. package/dist/api/routes/prs.js +115 -54
  16. package/dist/api/routes/search.js +6 -1
  17. package/dist/api/routes/sprint-stories.js +80 -0
  18. package/dist/api/routes/users.js +1 -1
  19. package/dist/api/routes/workspaces.js +8 -0
  20. package/dist/app.js +18 -0
  21. package/dist/bot-triage/resolve.js +3 -1
  22. package/dist/cli.js +61 -0
  23. package/dist/coding/agent.js +137 -26
  24. package/dist/coding/ai-fix/auto-fix.js +32 -9
  25. package/dist/coding/ai-fix/auto-push.js +56 -0
  26. package/dist/coding/ai-fix/manager.js +444 -36
  27. package/dist/coding/ai-fix/persist.js +186 -3
  28. package/dist/coding/ai-fix/plan.js +176 -0
  29. package/dist/coding/ai-fix/prompts.js +88 -1
  30. package/dist/coding/ai-fix/push.js +66 -0
  31. package/dist/coding/ai-fix/review-seed.js +431 -125
  32. package/dist/coding/ai-fix/routes.js +245 -159
  33. package/dist/coding/ai-fix/thread-candidates.js +77 -0
  34. package/dist/coding/ai-resolve/prompt.js +154 -0
  35. package/dist/coding/ai-resolve/routes.js +98 -0
  36. package/dist/coding/ai-resolve/run.js +300 -0
  37. package/dist/coding/ai-resolve/schema.js +26 -0
  38. package/dist/coding/ai-resolve/validate.js +136 -0
  39. package/dist/coding/partial-spend.js +19 -0
  40. package/dist/coding/schema.js +24 -0
  41. package/dist/config.js +59 -6
  42. package/dist/conflict/session.js +10 -6
  43. package/dist/db/blast-radius-query.js +44 -0
  44. package/dist/db/changepoint.js +4 -1
  45. package/dist/db/daily-brief.js +3 -0
  46. package/dist/db/erase-account.js +101 -1
  47. package/dist/db/export-account.js +48 -0
  48. package/dist/db/flow-detail.js +38 -25
  49. package/dist/db/flow-pointers.js +11 -2
  50. package/dist/db/flow-settings.js +36 -1
  51. package/dist/db/flow-trend.js +576 -0
  52. package/dist/db/merge-queue.js +5 -1
  53. package/dist/db/merged-prs.js +50 -0
  54. package/dist/db/merged-reach.js +83 -0
  55. package/dist/db/migrations/0091_auto_post_verdict_resolve_fix.sql +24 -0
  56. package/dist/db/migrations/0092_finding_pushback.sql +9 -0
  57. package/dist/db/migrations/0093_dependency_auto_merge.sql +25 -0
  58. package/dist/db/migrations/0094_repo_uses_github_issues.sql +26 -0
  59. package/dist/db/migrations/0095_review_chat_explain.sql +14 -0
  60. package/dist/db/migrations/0096_workspace_setting_events.sql +27 -0
  61. package/dist/db/migrations/0097_story_reports.sql +40 -0
  62. package/dist/db/migrations/0098_tracker_ticket_dates.sql +11 -0
  63. package/dist/db/migrations/0099_story_labels.sql +29 -0
  64. package/dist/db/migrations/0100_sprint_stories.sql +141 -0
  65. package/dist/db/migrations/0101_sprint_reads_api_root.sql +6 -0
  66. package/dist/db/migrations/0102_workspace_sprint_checks.sql +5 -0
  67. package/dist/db/migrations/0103_sprint_statuses_and_blockers.sql +26 -0
  68. package/dist/db/migrations/0104_sprint_ticket_repos_saved.sql +7 -0
  69. package/dist/db/migrations/0105_sprint_assessment_mode.sql +8 -0
  70. package/dist/db/migrations/0106_sprint_ref_tickets.sql +27 -0
  71. package/dist/db/migrations/0107_sprint_graph.sql +47 -0
  72. package/dist/db/migrations/0108_sprint_themes.sql +24 -0
  73. package/dist/db/migrations/0109_sprint_story.sql +23 -0
  74. package/dist/db/migrations/0110_finding_reminder.sql +7 -0
  75. package/dist/db/migrations/0111_sprint_dates_story_attempt.sql +19 -0
  76. package/dist/db/migrations/0112_sprint_view_snapshots.sql +20 -0
  77. package/dist/db/migrations/0113_ai_fix_plan.sql +27 -0
  78. package/dist/db/migrations/0114_sonarcloud.sql +42 -0
  79. package/dist/db/migrations/0115_sequence_diagrams.sql +40 -0
  80. package/dist/db/migrations/0116_reference_docs.sql +64 -0
  81. package/dist/db/migrations/0117_review_doc_references.sql +11 -0
  82. package/dist/db/migrations/0118_sequence_diagram_runs.sql +10 -0
  83. package/dist/db/migrations/0119_design_diagrams.sql +7 -0
  84. package/dist/db/migrations/0120_sprint_paths.sql +33 -0
  85. package/dist/db/migrations/0121_story_docs_alignments.sql +42 -0
  86. package/dist/db/migrations/0122_claude_thread_checks.sql +42 -0
  87. package/dist/db/migrations/0123_sprint_brief_one_liner.sql +5 -0
  88. package/dist/db/migrations/0124_reference_doc_crawl.sql +8 -0
  89. package/dist/db/migrations/meta/_journal.json +238 -0
  90. package/dist/db/migrations-pg/0078_auto_post_verdict_resolve_fix.sql +11 -0
  91. package/dist/db/migrations-pg/0079_finding_pushback.sql +3 -0
  92. package/dist/db/migrations-pg/0080_dependency_auto_merge.sql +16 -0
  93. package/dist/db/migrations-pg/0081_repo_uses_github_issues.sql +7 -0
  94. package/dist/db/migrations-pg/0082_review_chat_explain.sql +5 -0
  95. package/dist/db/migrations-pg/0083_workspace_setting_events.sql +13 -0
  96. package/dist/db/migrations-pg/0084_story_reports.sql +28 -0
  97. package/dist/db/migrations-pg/0085_tracker_ticket_dates.sql +5 -0
  98. package/dist/db/migrations-pg/0086_story_labels.sql +19 -0
  99. package/dist/db/migrations-pg/0087_sprint_stories.sql +121 -0
  100. package/dist/db/migrations-pg/0088_sprint_reads_api_root.sql +3 -0
  101. package/dist/db/migrations-pg/0089_workspace_sprint_checks.sql +3 -0
  102. package/dist/db/migrations-pg/0090_sprint_statuses_and_blockers.sql +14 -0
  103. package/dist/db/migrations-pg/0091_sprint_ticket_repos_saved.sql +4 -0
  104. package/dist/db/migrations-pg/0092_sprint_assessment_mode.sql +4 -0
  105. package/dist/db/migrations-pg/0093_sprint_ref_tickets.sql +21 -0
  106. package/dist/db/migrations-pg/0094_sprint_graph.sql +38 -0
  107. package/dist/db/migrations-pg/0095_sprint_themes.sql +19 -0
  108. package/dist/db/migrations-pg/0096_sprint_story.sql +18 -0
  109. package/dist/db/migrations-pg/0097_finding_reminder.sql +3 -0
  110. package/dist/db/migrations-pg/0098_sprint_dates_story_attempt.sql +12 -0
  111. package/dist/db/migrations-pg/0099_sprint_view_snapshots.sql +15 -0
  112. package/dist/db/migrations-pg/0100_ai_fix_plan.sql +13 -0
  113. package/dist/db/migrations-pg/0101_sonarcloud.sql +31 -0
  114. package/dist/db/migrations-pg/0102_sequence_diagrams.sql +32 -0
  115. package/dist/db/migrations-pg/0103_reference_docs.sql +51 -0
  116. package/dist/db/migrations-pg/0104_review_doc_references.sql +5 -0
  117. package/dist/db/migrations-pg/0105_sequence_diagram_runs.sql +5 -0
  118. package/dist/db/migrations-pg/0106_design_diagrams.sql +3 -0
  119. package/dist/db/migrations-pg/0107_sprint_paths.sql +26 -0
  120. package/dist/db/migrations-pg/0108_story_docs_alignments.sql +34 -0
  121. package/dist/db/migrations-pg/0109_claude_thread_checks.sql +31 -0
  122. package/dist/db/migrations-pg/0110_sprint_brief_one_liner.sql +3 -0
  123. package/dist/db/migrations-pg/0111_reference_doc_crawl.sql +3 -0
  124. package/dist/db/migrations-pg/meta/_journal.json +238 -0
  125. package/dist/db/my-turn-card-facts.js +48 -8
  126. package/dist/db/pending-tabs.js +18 -10
  127. package/dist/db/pr-intervals.js +198 -123
  128. package/dist/db/pr-liveness.js +7 -0
  129. package/dist/db/queries.js +248 -578
  130. package/dist/db/repo-activity.js +18 -25
  131. package/dist/db/reporting-window.js +56 -0
  132. package/dist/db/retention.js +11 -0
  133. package/dist/db/review-threads-for-review.js +77 -14
  134. package/dist/db/schema.pg.js +650 -2
  135. package/dist/db/schema.sqlite.js +929 -5
  136. package/dist/db/search.js +105 -3
  137. package/dist/db/sequence-diagram-prune.js +54 -0
  138. package/dist/db/setting-event-write.js +29 -0
  139. package/dist/db/sprint-briefs.js +168 -0
  140. package/dist/db/sprint-graph-fold.js +199 -0
  141. package/dist/db/sprint-graph.js +474 -0
  142. package/dist/db/sprint-pass-flights.js +22 -0
  143. package/dist/db/sprint-paths.js +473 -0
  144. package/dist/db/sprint-signals.js +276 -0
  145. package/dist/db/sprint-status-layout.js +443 -0
  146. package/dist/db/sprint-status-settings.js +261 -0
  147. package/dist/db/sprint-store.js +106 -0
  148. package/dist/db/sprint-themes.js +120 -0
  149. package/dist/db/sprint-ticket-checks.js +117 -0
  150. package/dist/db/stories.js +309 -0
  151. package/dist/db/story-criteria.js +175 -0
  152. package/dist/db/story-health-fold.js +21 -0
  153. package/dist/db/story-health.js +219 -0
  154. package/dist/db/story-issue-input.js +155 -0
  155. package/dist/db/story-label-input.js +51 -0
  156. package/dist/db/synthesis-input.js +6 -122
  157. package/dist/db/work-plan.js +3 -0
  158. package/dist/db/working-hours.js +29 -0
  159. package/dist/db/workspace-setting-events.js +52 -0
  160. package/dist/github/mutations.js +75 -0
  161. package/dist/github/queries.js +5 -0
  162. package/dist/merge/arm-intent.js +77 -0
  163. package/dist/merge/auto-merge-runner.js +159 -16
  164. package/dist/merge/dependency-policy.js +381 -0
  165. package/dist/ml/blake2b.js +142 -0
  166. package/dist/ml/decode.js +121 -0
  167. package/dist/ml/local-scorer.js +182 -0
  168. package/dist/ml/markers.js +770 -0
  169. package/dist/ml/model.js +88 -0
  170. package/dist/ml/onnx-head.js +129 -0
  171. package/dist/ml/power.js +59 -0
  172. package/dist/ml/runtime.js +377 -0
  173. package/dist/ml/scorer-process.js +44 -0
  174. package/dist/ml/scorer.js +106 -0
  175. package/dist/ml/throttle.js +34 -0
  176. package/dist/pr/detail-enricher.js +5 -3
  177. package/dist/pro/bind.js +19 -3
  178. package/dist/pro/event-hooks.js +37 -0
  179. package/dist/reference-docs/agent.js +329 -0
  180. package/dist/reference-docs/assemble.js +62 -0
  181. package/dist/reference-docs/build-judge.js +130 -0
  182. package/dist/reference-docs/chat.js +166 -0
  183. package/dist/reference-docs/context.js +61 -0
  184. package/dist/reference-docs/convert.js +177 -0
  185. package/dist/reference-docs/crawl-rank.js +106 -0
  186. package/dist/reference-docs/crawl.js +639 -0
  187. package/dist/reference-docs/library.js +158 -0
  188. package/dist/reference-docs/manager.js +399 -0
  189. package/dist/reference-docs/paths.js +122 -0
  190. package/dist/reference-docs/prompts.js +125 -0
  191. package/dist/reference-docs/render.js +207 -0
  192. package/dist/reference-docs/review-refs.js +288 -0
  193. package/dist/reference-docs/robots.js +127 -0
  194. package/dist/reference-docs/routes.js +491 -0
  195. package/dist/reference-docs/schema.js +31 -0
  196. package/dist/reference-docs/split.js +397 -0
  197. package/dist/reference-docs/startup.js +14 -0
  198. package/dist/reference-docs/store.js +345 -0
  199. package/dist/reference-docs/url-fetch.js +177 -0
  200. package/dist/review/agent-context.js +4 -3
  201. package/dist/review/agent.js +40 -8
  202. package/dist/review/agentic.js +63 -2
  203. package/dist/review/chat-agent.js +45 -6
  204. package/dist/review/ci-review/agent.js +3 -1
  205. package/dist/review/ci-review/auto-post.js +221 -0
  206. package/dist/review/ci-review/manager.js +12 -1
  207. package/dist/review/ci-review/persist.js +27 -0
  208. package/dist/review/ci-review/prepare.js +1 -1
  209. package/dist/review/ci-review/prompts.js +5 -3
  210. package/dist/review/ci-review/schema.js +6 -0
  211. package/dist/review/ci-review/with-review.js +37 -0
  212. package/dist/review/claude-review/auto-post.js +274 -87
  213. package/dist/review/claude-review/auto-pushback.js +131 -0
  214. package/dist/review/claude-review/auto-resolve.js +207 -0
  215. package/dist/review/claude-review/auto-settings.js +212 -17
  216. package/dist/review/claude-review/auto.js +103 -1
  217. package/dist/review/claude-review/chat-explain.js +222 -0
  218. package/dist/review/claude-review/chat.js +201 -23
  219. package/dist/review/claude-review/ci-failures.js +154 -34
  220. package/dist/review/claude-review/finding-replies.js +297 -0
  221. package/dist/review/claude-review/finding-thread.js +58 -0
  222. package/dist/review/claude-review/follow-up.js +143 -12
  223. package/dist/review/claude-review/manager.js +136 -16
  224. package/dist/review/claude-review/manual-reply.js +198 -0
  225. package/dist/review/claude-review/persist.js +517 -117
  226. package/dist/review/claude-review/posted-state.js +236 -0
  227. package/dist/review/claude-review/prompts.js +132 -10
  228. package/dist/review/claude-review/reply-text.js +16 -0
  229. package/dist/review/claude-review/routes.js +127 -8
  230. package/dist/review/claude-review/settled-by-reply.js +76 -84
  231. package/dist/review/claude-review/thread-check-agent.js +148 -0
  232. package/dist/review/claude-review/thread-check-persist.js +183 -0
  233. package/dist/review/claude-review/thread-check-routes.js +105 -0
  234. package/dist/review/claude-review/thread-check.js +416 -0
  235. package/dist/review/claude-review/thread-judgements.js +309 -0
  236. package/dist/review/claude-review/threads.js +28 -4
  237. package/dist/review/diagrams/agent.js +173 -0
  238. package/dist/review/diagrams/fingerprint.js +58 -0
  239. package/dist/review/diagrams/manager.js +344 -0
  240. package/dist/review/diagrams/persist.js +253 -0
  241. package/dist/review/diagrams/prompts.js +134 -0
  242. package/dist/review/diagrams/routes.js +196 -0
  243. package/dist/review/diagrams/schema.js +40 -0
  244. package/dist/review/diagrams/trigger.js +160 -0
  245. package/dist/review/diagrams/validate.js +236 -0
  246. package/dist/review/llm.js +53 -7
  247. package/dist/review/path-guard.js +34 -7
  248. package/dist/review/post-review.js +85 -0
  249. package/dist/review/post-seam.js +68 -11
  250. package/dist/review/schema.js +60 -17
  251. package/dist/review/sprint-ask/prompt.js +99 -0
  252. package/dist/review/sprint-ask/routes.js +91 -0
  253. package/dist/review/sprint-briefs/index.js +39 -0
  254. package/dist/review/sprint-briefs/manager.js +191 -0
  255. package/dist/review/sprint-briefs/prompt.js +55 -0
  256. package/dist/review/sprint-briefs/validate.js +96 -0
  257. package/dist/review/sprint-paths/agent.js +174 -0
  258. package/dist/review/sprint-paths/manager.js +113 -0
  259. package/dist/review/sprint-paths/prompt.js +24 -0
  260. package/dist/review/sprint-paths/validate.js +116 -0
  261. package/dist/review/sprint-themes/manager.js +91 -0
  262. package/dist/review/sprint-themes/prompt.js +31 -0
  263. package/dist/review/sprint-themes/validate.js +82 -0
  264. package/dist/review/story-docs/agent.js +157 -0
  265. package/dist/review/story-docs/fingerprint.js +33 -0
  266. package/dist/review/story-docs/inputs.js +100 -0
  267. package/dist/review/story-docs/manager.js +275 -0
  268. package/dist/review/story-docs/persist.js +162 -0
  269. package/dist/review/story-docs/prompts.js +80 -0
  270. package/dist/review/story-docs/routes.js +101 -0
  271. package/dist/review/story-docs/schema.js +46 -0
  272. package/dist/review/story-docs/shortlist.js +202 -0
  273. package/dist/review/story-docs/validate.js +266 -0
  274. package/dist/review/story-labels/manager.js +271 -0
  275. package/dist/review/story-labels/prompt.js +70 -0
  276. package/dist/review/story-labels/routes.js +85 -0
  277. package/dist/review/story-labels/validate.js +198 -0
  278. package/dist/review/submit-map.js +3 -0
  279. package/dist/review/ticket-review/agent.js +8 -4
  280. package/dist/review/ticket-review/manager.js +29 -2
  281. package/dist/review/ticket-review/prepass.js +3 -1
  282. package/dist/review/ticket-review/prompts.js +9 -0
  283. package/dist/review/ticket-review/routes.js +5 -1
  284. package/dist/review/ticket-review/schema.js +10 -3
  285. package/dist/review/ticket-review/sprint-checks.js +134 -0
  286. package/dist/shared/activity-windows.js +8 -0
  287. package/dist/shared/blast-radius.js +207 -0
  288. package/dist/shared/claude-review.js +17 -2
  289. package/dist/shared/conflict-ai.js +24 -0
  290. package/dist/shared/conflicts.js +3 -0
  291. package/dist/shared/dependency-merge.js +21 -0
  292. package/dist/shared/flow-settings.js +1 -1
  293. package/dist/shared/flow-trend.js +26 -0
  294. package/dist/shared/index.js +20 -0
  295. package/dist/shared/pending-rules.js +40 -5
  296. package/dist/shared/reference-docs.js +104 -0
  297. package/dist/shared/reporting-window.js +30 -0
  298. package/dist/shared/sequence-diagrams.js +54 -0
  299. package/dist/shared/sonar.js +144 -0
  300. package/dist/shared/sprint-graph.js +193 -0
  301. package/dist/shared/sprint-lanes.js +36 -0
  302. package/dist/shared/sprint-members.js +87 -0
  303. package/dist/shared/sprint-stories.js +190 -0
  304. package/dist/shared/stories.js +10 -0
  305. package/dist/shared/story-docs-alignment.js +210 -0
  306. package/dist/shared/story-health.js +114 -0
  307. package/dist/shared/story-issues.js +358 -0
  308. package/dist/shared/story-rules.js +134 -0
  309. package/dist/shared/ticket-prs.js +21 -0
  310. package/dist/shared/ticket-refs.js +169 -0
  311. package/dist/shared/types.js +40 -7
  312. package/dist/sonar/client.js +68 -0
  313. package/dist/sonar/comment.js +167 -0
  314. package/dist/sonar/gather.js +571 -0
  315. package/dist/sonar/index.js +18 -0
  316. package/dist/sonar/post.js +101 -0
  317. package/dist/sonar/routes.js +211 -0
  318. package/dist/sonar/settings.js +161 -0
  319. package/dist/sonar/snapshot.js +127 -0
  320. package/dist/sonar/store.js +111 -0
  321. package/dist/sync/bot-detection.js +1 -1
  322. package/dist/sync/branch-status.js +5 -6
  323. package/dist/sync/hydrate-detail.js +1 -1
  324. package/dist/sync/merge-notice.js +56 -0
  325. package/dist/sync/ml-enrichment.js +21 -7
  326. package/dist/sync/scheduler.js +8 -3
  327. package/dist/sync/upsert.js +10 -0
  328. package/dist/tracker/dates-backfill.js +96 -0
  329. package/dist/tracker/dates.js +31 -0
  330. package/dist/tracker/enricher.js +79 -11
  331. package/dist/tracker/github/issues-usage.js +195 -0
  332. package/dist/tracker/github/reader.js +25 -0
  333. package/dist/tracker/index.js +28 -1
  334. package/dist/tracker/jira/client.js +66 -1
  335. package/dist/tracker/jira/fetch.js +35 -4
  336. package/dist/tracker/jira/text.js +6 -0
  337. package/dist/tracker/legacy-import.js +4 -0
  338. package/dist/tracker/linear/client.js +65 -11
  339. package/dist/tracker/links.js +3 -3
  340. package/dist/tracker/manual-links.js +341 -0
  341. package/dist/tracker/routes.js +17 -0
  342. package/dist/tracker/settings.js +90 -8
  343. package/dist/tracker/sprint/github.js +201 -0
  344. package/dist/tracker/sprint/hooks.js +42 -0
  345. package/dist/tracker/sprint/index.js +33 -0
  346. package/dist/tracker/sprint/jira.js +378 -0
  347. package/dist/tracker/sprint/linear.js +217 -0
  348. package/dist/tracker/sprint/project-keys.js +49 -0
  349. package/dist/tracker/sprint/read.js +437 -0
  350. package/dist/tracker/sprint/ref-status.js +283 -0
  351. package/dist/tracker/sprint/routes.js +36 -0
  352. package/dist/tracker/sprint/types.js +29 -0
  353. package/dist/tracker/store.js +12 -1
  354. package/dist/tracker/ticket-prs.js +120 -0
  355. package/dist/tracker/worker.js +76 -10
  356. package/package.json +48 -5
  357. package/public/assets/AiFixTab-IKfiwwnd.js +3 -0
  358. package/public/assets/ClaudeReviewTab-DlefFnYI.js +20 -0
  359. package/public/assets/PrBotBehaviourTab-BJelm30R.js +1 -0
  360. package/public/assets/index-CB4YyNHK.css +1 -0
  361. package/public/assets/index-Cm0XoYfd.js +79 -0
  362. package/public/index.html +2 -2
  363. package/public-landing/assets/{index-CA3eEGXO.js → index-D3DGTvPS.js} +1 -1
  364. package/public-landing/bots/index.html +6 -6
  365. package/public-landing/contact/index.html +3 -3
  366. package/public-landing/cookies/index.html +2 -2
  367. package/public-landing/features/index.html +3 -3
  368. package/public-landing/for-developers/index.html +3 -3
  369. package/public-landing/for-managers/index.html +6 -6
  370. package/public-landing/how-it-works/index.html +3 -3
  371. package/public-landing/how-we-measure/index.html +3 -3
  372. package/public-landing/index.html +3 -3
  373. package/public-landing/insights/index.html +6 -6
  374. package/public-landing/pricing/index.html +3 -3
  375. package/public-landing/privacy/index.html +2 -2
  376. package/public-landing/pro/index.html +6 -6
  377. package/public-landing/reviews/index.html +6 -6
  378. package/public-landing/terms/index.html +2 -2
  379. package/public/assets/AiFixTab-Cq2Wdm2l.js +0 -2
  380. package/public/assets/ClaudeReviewTab-C6WftwSM.js +0 -16
  381. package/public/assets/PrBotBehaviourTab-CY_iLmFp.js +0 -1
  382. package/public/assets/index-CB6bLAqG.js +0 -77
  383. package/public/assets/index-gwxOLty-.css +0 -1
@@ -0,0 +1,21 @@
1
+ # Third-party notices
2
+
3
+ Limn includes material from the projects below. Each is used under the licence shown.
4
+
5
+ ## Material Icon Theme
6
+
7
+ The file-type icons in the Changes tab (`apps/frontend/src/components/FileTypeIcons.tsx`) are
8
+ copied from Material Icon Theme by Material Extensions, npm `material-icon-theme@5.39.0`.
9
+
10
+ Source: https://github.com/material-extensions/vscode-material-icon-theme
11
+
12
+ ```
13
+ The MIT License (MIT)
14
+ Copyright (c) 2025 Material Extensions
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+ ```
@@ -0,0 +1,45 @@
1
+ // AGENT ISOLATION — every Agent SDK run in this app sees ONLY the tools it registered itself.
2
+ //
3
+ // ⚠ MEASURED (2026-10-10): a run on the reader's ambient Claude Code login, with `tools: []`,
4
+ // `settingSources: []` and `bypassPermissions`, still lists the ACCOUNT'S claude.ai CONNECTORS
5
+ // (`mcp__claude_ai_Claude_Docs__create/update/delete/export`, …) a few seconds after start — they
6
+ // connect after `system/init`, so the init message shows none. A trial agent wrote a report into the
7
+ // user's Claude Docs unprompted. Claude Review reads PR text strangers write, so a prompt injection
8
+ // could create, edit or delete documents in the reader's claude.ai account. `strictMcpConfig: true`
9
+ // removes them (measured: 8 connector tools → 0); the PreToolUse hook below is the backstop that
10
+ // refuses any `mcp__` tool whose server the run did not register, and its deny holds under
11
+ // bypassPermissions (the path guard relies on the same property).
12
+ //
13
+ // EVERY `query({ options })` spreads `ISOLATED_MCP` and adds `ownToolsOnlyHook(<its server names>)`
14
+ // to `PreToolUse` — pinned by agent-isolation.test.ts, which reads every call site.
15
+ export const ISOLATED_MCP = { strictMcpConfig: true };
16
+ /** The MCP server a tool name belongs to (`mcp__<server>__<tool>`), or null for a built-in tool. */
17
+ export function mcpServerOf(toolName) {
18
+ if (!toolName.startsWith('mcp__'))
19
+ return null;
20
+ const rest = toolName.slice('mcp__'.length);
21
+ const cut = rest.indexOf('__');
22
+ return cut < 0 ? rest : rest.slice(0, cut);
23
+ }
24
+ /** True when `toolName` is an MCP tool from a server this run did not register. */
25
+ export function isForeignMcpTool(toolName, ownServers) {
26
+ const server = mcpServerOf(toolName);
27
+ return server != null && !ownServers.includes(server);
28
+ }
29
+ /** One SDK PreToolUse matcher entry refusing every MCP tool outside `ownServers`. */
30
+ export function ownToolsOnlyHook(ownServers) {
31
+ return {
32
+ hooks: [
33
+ async (input) => input.hook_event_name === 'PreToolUse' && isForeignMcpTool(input.tool_name ?? '', ownServers)
34
+ ? {
35
+ hookSpecificOutput: {
36
+ hookEventName: 'PreToolUse',
37
+ permissionDecision: 'deny',
38
+ permissionDecisionReason: `${input.tool_name} is not available in this run.`,
39
+ },
40
+ }
41
+ : { continue: true },
42
+ ],
43
+ };
44
+ }
45
+ //# sourceMappingURL=agent-isolation.js.map
@@ -9,6 +9,9 @@
9
9
  //
10
10
  // @anthropic-ai/claude-agent-sdk · @anthropic-ai/sdk · @modelcontextprotocol/sdk · zod
11
11
  //
12
+ // (plus the reference-docs converters — mammoth · turndown · turndown-plugin-gfm — which ride the
13
+ // same download because only an agentic feature uses them; see AI_RUNTIME_LATER_PACKAGES)
14
+ //
12
15
  // at EXACT pinned versions into `<dataDir>/ai-runtime` (`~/.limn/ai-runtime`), by
13
16
  // `npm install --prefix`, after the reader presses "Set up AI" or runs `limn ai install`. The pins
14
17
  // ride the release manifest as a NON-dependency field (`limnAiRuntime`), generated by
@@ -43,7 +46,19 @@ export const AI_RUNTIME_PACKAGES = [
43
46
  '@anthropic-ai/sdk',
44
47
  '@modelcontextprotocol/sdk',
45
48
  'zod',
49
+ // THE REFERENCE-DOCS CONVERTERS (docs/REFERENCE-DOCS.md): Word → HTML, and HTML → markdown with
50
+ // GitHub tables. Small, pure JS, used only by the agentic docs library — so they ride the same
51
+ // on-demand download rather than every `npx limn-review`. PDF needs none: Claude reads it natively.
52
+ 'mammoth',
53
+ 'turndown',
54
+ 'turndown-plugin-gfm',
46
55
  ];
56
+ /**
57
+ * The packages added to the runtime AFTER it first shipped. A runtime directory installed before
58
+ * them still runs every agent, so a missing one reads as STALE ("Set up AI again"), never ABSENT —
59
+ * only the feature that needs it (the docs library's Word / web-page conversion) refuses until then.
60
+ */
61
+ export const AI_RUNTIME_LATER_PACKAGES = ['mammoth', 'turndown', 'turndown-plugin-gfm'];
47
62
  /** The release manifest's field carrying the pins. Spelled once; build-release.mjs mirrors it. */
48
63
  export const AI_RUNTIME_MANIFEST_FIELD = 'limnAiRuntime';
49
64
  /** An exact semver version — no range, no tag. Anything else is refused before npm sees it. */
@@ -59,6 +74,9 @@ const ENTRY_SUBPATH = {
59
74
  '@anthropic-ai/sdk': '.',
60
75
  '@modelcontextprotocol/sdk': './server',
61
76
  zod: '.',
77
+ mammoth: '.',
78
+ turndown: '.',
79
+ 'turndown-plugin-gfm': '.',
62
80
  };
63
81
  const specifierFor = (name) => ENTRY_SUBPATH[name] === '.' ? name : `${name}${ENTRY_SUBPATH[name].slice(1)}`;
64
82
  export const AI_RUNTIME_MISSING_MESSAGE = 'AI is not set up yet. Press "Set up AI" in Limn, or run: limn ai install';
@@ -88,7 +106,7 @@ function readJson(path) {
88
106
  * which ESM ignores, so a package found there would be one the real import cannot see. (Under
89
107
  * pnpm/vitest NODE_PATH points into the workspace store, which is how this was found.)
90
108
  */
91
- function findPackageDir(spec, fromDir) {
109
+ export function findPackageDir(spec, fromDir) {
92
110
  let dir = resolve(fromDir);
93
111
  for (;;) {
94
112
  const candidate = join(dir, 'node_modules', spec);
@@ -164,12 +182,84 @@ export function esmEntryOf(pkgDir, subpath = '.') {
164
182
  const file = resolve(pkgDir, rel);
165
183
  return existsSync(file) ? file : null;
166
184
  }
167
- function installedVersion(pkgDir) {
185
+ export function installedVersion(pkgDir) {
168
186
  if (!pkgDir)
169
187
  return null;
170
188
  const v = readJson(join(pkgDir, 'package.json'))?.version;
171
189
  return typeof v === 'string' ? v : null;
172
190
  }
191
+ /**
192
+ * npm's own JS entry run with THIS node, so there is no shell and no PATH guessing on Windows
193
+ * (where `npm` is a .cmd that cannot be spawned without one).
194
+ */
195
+ export function resolveNpm(env = process.env) {
196
+ const viaEnv = env.npm_execpath;
197
+ if (viaEnv && /npm-cli\.c?js$/.test(viaEnv) && existsSync(viaEnv)) {
198
+ return { command: process.execPath, args: [viaEnv] };
199
+ }
200
+ const nodeDir = dirname(process.execPath);
201
+ for (const candidate of [
202
+ join(nodeDir, '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
203
+ join(nodeDir, 'node_modules', 'npm', 'bin', 'npm-cli.js'),
204
+ ]) {
205
+ if (existsSync(candidate))
206
+ return { command: process.execPath, args: [candidate] };
207
+ }
208
+ if (process.platform !== 'win32')
209
+ return { command: 'npm', args: [] };
210
+ return null;
211
+ }
212
+ /** Run one npm command to completion. Never throws; a spawn failure comes back as `error`. */
213
+ export function spawnNpm(npm, args, opts) {
214
+ const tail = [];
215
+ return new Promise((done) => {
216
+ let child;
217
+ try {
218
+ child = spawn(npm.command, args, {
219
+ cwd: opts.cwd,
220
+ env: { ...opts.env, npm_config_loglevel: 'http' },
221
+ stdio: ['ignore', 'pipe', 'pipe'],
222
+ shell: false,
223
+ windowsHide: true,
224
+ });
225
+ }
226
+ catch (err) {
227
+ done({ code: null, error: err.message, timedOut: false, tail });
228
+ return;
229
+ }
230
+ let timedOut = false;
231
+ const timer = setTimeout(() => {
232
+ timedOut = true;
233
+ child.kill('SIGTERM');
234
+ }, opts.timeoutMs);
235
+ const onData = (buf) => {
236
+ for (const raw of buf.toString('utf8').split(/\r?\n/)) {
237
+ const line = raw.trim();
238
+ if (!line)
239
+ continue;
240
+ tail.push(line);
241
+ if (tail.length > 8)
242
+ tail.shift();
243
+ opts.onLine?.(line);
244
+ }
245
+ };
246
+ child.stdout?.on('data', onData);
247
+ child.stderr?.on('data', onData);
248
+ child.on('error', (err) => {
249
+ clearTimeout(timer);
250
+ done({
251
+ code: null,
252
+ error: err.code === 'ENOENT' ? 'npm was not found. Install Node.js with npm, then try again.' : err.message,
253
+ timedOut: false,
254
+ tail,
255
+ });
256
+ });
257
+ child.on('close', (code) => {
258
+ clearTimeout(timer);
259
+ done({ code, error: null, timedOut, tail });
260
+ });
261
+ });
262
+ }
173
263
  const HERE = dirname(fileURLToPath(import.meta.url));
174
264
  /**
175
265
  * Build a runtime over one data directory. The app uses the module-level default below; tests
@@ -223,8 +313,13 @@ export function createAiRuntime(opts = {}) {
223
313
  for (const name of AI_RUNTIME_PACKAGES) {
224
314
  const dir = findPackageDir(name, runtimeDir);
225
315
  const have = installedVersion(dir);
226
- if (!dir || !have || !esmEntryOf(dir, ENTRY_SUBPATH[name]))
227
- anyMissing = true;
316
+ if (!dir || !have || !esmEntryOf(dir, ENTRY_SUBPATH[name])) {
317
+ // An install from before a later package existed still runs the agents: stale, not absent.
318
+ if (AI_RUNTIME_LATER_PACKAGES.includes(name))
319
+ anyMismatch = true;
320
+ else
321
+ anyMissing = true;
322
+ }
228
323
  else if (want && want[name] !== have)
229
324
  anyMismatch = true;
230
325
  }
@@ -328,25 +423,6 @@ export function createAiRuntime(opts = {}) {
328
423
  }
329
424
  }
330
425
  }
331
- function resolveNpm() {
332
- // Run npm's own JS entry with THIS node, so there is no shell and no PATH guessing on Windows
333
- // (where `npm` is a .cmd that cannot be spawned without one).
334
- const viaEnv = env.npm_execpath;
335
- if (viaEnv && /npm-cli\.c?js$/.test(viaEnv) && existsSync(viaEnv)) {
336
- return { command: process.execPath, args: [viaEnv] };
337
- }
338
- const nodeDir = dirname(process.execPath);
339
- for (const candidate of [
340
- join(nodeDir, '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
341
- join(nodeDir, 'node_modules', 'npm', 'bin', 'npm-cli.js'),
342
- ]) {
343
- if (existsSync(candidate))
344
- return { command: process.execPath, args: [candidate] };
345
- }
346
- if (process.platform !== 'win32')
347
- return { command: 'npm', args: [] };
348
- return null;
349
- }
350
426
  function install(onProgress, installOpts = {}) {
351
427
  if (onProgress)
352
428
  listeners.add(onProgress);
@@ -372,7 +448,7 @@ export function createAiRuntime(opts = {}) {
372
448
  if (!want) {
373
449
  return failWith('This copy of Limn does not list which AI runtime versions to download. Update Limn and try again.');
374
450
  }
375
- const npm = installOpts.npm ?? resolveNpm();
451
+ const npm = installOpts.npm ?? resolveNpm(env);
376
452
  if (!npm)
377
453
  return failWith('npm was not found. Install Node.js with npm, then try again.');
378
454
  lastFailure = null;
@@ -399,7 +475,7 @@ export function createAiRuntime(opts = {}) {
399
475
  '--no-audit',
400
476
  '--no-fund',
401
477
  '--no-update-notifier',
402
- // None of the four needs an install script, so none gets to run one.
478
+ // None of the packages needs an install script, so none gets to run one.
403
479
  '--ignore-scripts',
404
480
  '--no-package-lock',
405
481
  ];
@@ -407,55 +483,13 @@ export function createAiRuntime(opts = {}) {
407
483
  if (env.LIMN_CLAUDE_PATH?.trim())
408
484
  args.push('--omit=optional');
409
485
  const timeoutMs = installOpts.timeoutMs ?? 10 * 60_000;
410
- const tail = [];
411
- const exit = await new Promise((done) => {
412
- let child;
413
- try {
414
- child = spawn(npm.command, args, {
415
- cwd: staging,
416
- env: { ...env, npm_config_loglevel: 'http' },
417
- stdio: ['ignore', 'pipe', 'pipe'],
418
- shell: false,
419
- windowsHide: true,
420
- });
421
- }
422
- catch (err) {
423
- done({ code: null, error: err.message, timedOut: false });
424
- return;
425
- }
426
- let timedOut = false;
427
- const timer = setTimeout(() => {
428
- timedOut = true;
429
- child.kill('SIGTERM');
430
- }, timeoutMs);
431
- const onData = (buf) => {
432
- for (const raw of buf.toString('utf8').split(/\r?\n/)) {
433
- const line = raw.trim();
434
- if (!line)
435
- continue;
436
- tail.push(line);
437
- if (tail.length > 8)
438
- tail.shift();
439
- emit({ phase: 'downloading', message: line.slice(0, 200) });
440
- }
441
- };
442
- child.stdout?.on('data', onData);
443
- child.stderr?.on('data', onData);
444
- child.on('error', (err) => {
445
- clearTimeout(timer);
446
- done({
447
- code: null,
448
- error: err.code === 'ENOENT'
449
- ? 'npm was not found. Install Node.js with npm, then try again.'
450
- : err.message,
451
- timedOut: false,
452
- });
453
- });
454
- child.on('close', (code) => {
455
- clearTimeout(timer);
456
- done({ code, error: null, timedOut });
457
- });
486
+ const exit = await spawnNpm(npm, args, {
487
+ cwd: staging,
488
+ env,
489
+ timeoutMs,
490
+ onLine: (line) => emit({ phase: 'downloading', message: line.slice(0, 200) }),
458
491
  });
492
+ const tail = exit.tail;
459
493
  if (exit.error || exit.timedOut || exit.code !== 0) {
460
494
  rmSync(staging, { recursive: true, force: true });
461
495
  if (exit.error)
@@ -520,6 +554,13 @@ export const loadMcpSdk = () => runtime.load('@modelcontextprotocol/sdk');
520
554
  export const loadZodModule = () => runtime.load('zod');
521
555
  /** zod's `z` namespace, from the SAME install the Agent SDK uses. */
522
556
  export const loadZod = async () => (await loadZodModule()).z;
557
+ const cjsDefault = (mod) => {
558
+ const m = mod;
559
+ return (m && typeof m === 'object' && m.default != null ? m.default : mod);
560
+ };
561
+ export const loadMammoth = async () => cjsDefault(await runtime.load('mammoth'));
562
+ export const loadTurndown = async () => cjsDefault(await runtime.load('turndown'));
563
+ export const loadTurndownGfm = async () => cjsDefault(await runtime.load('turndown-plugin-gfm'));
523
564
  /**
524
565
  * `LIMN_CLAUDE_PATH` → the Agent SDK's `pathToClaudeCodeExecutable`: run the user's own installed
525
566
  * `claude` instead of the SDK's bundled binary (and `limn ai install` then skips downloading it).
@@ -19,8 +19,8 @@ function tier(name, perWindow, windowMs) {
19
19
  };
20
20
  }
21
21
  const TIERS = {
22
- // Anthropic-spending generation. A digest refresh, a sprint report, an Insights
23
- // chat turn, a themes rebuild, a PR summary, an agentic review or fix. The most
22
+ // Anthropic-spending generation. A sprint report, an Insights chat turn, a themes
23
+ // rebuild, an impact note, an agentic review or fix. The most
24
24
  // expensive thing in the app and the tightest bucket.
25
25
  ai: tier('ai', 20, MINUTE),
26
26
  // A second, longer window on the same routes: 20/min would still be 1200/hour.
@@ -96,6 +96,10 @@ function tierFor(method, path) {
96
96
  // family other routes may join, and none of them should inherit this decision.
97
97
  if (path === '/api/ai/runtime/install')
98
98
  return [TIERS.sync];
99
+ // "Set up" for local bot-comment scoring: the same shape (npm install + ~150 MB of model files
100
+ // over the user's own bandwidth, single-flight in ml/runtime.ts), the same bucket, spelled exactly.
101
+ if (path === '/api/ml/install')
102
+ return [TIERS.sync];
99
103
  // ---- Workspace CRUD: `read`, RECORDED rather than inherited ----
100
104
  //
101
105
  // The six surviving routes (list / create / rename+reassign / delete / move-one-repo-in /
@@ -132,6 +136,11 @@ function tierFor(method, path) {
132
136
  if (!mutating && /^\/api\/workspaces\/\d+\/tracker\/(jira-fields|linear-check)$/.test(path)) {
133
137
  return [TIERS.search, TIERS.read];
134
138
  }
139
+ // SONARCLOUD (sonar/routes.ts): "Test connection" calls SonarCloud with the saved token (one call
140
+ // for the organization + one per repo, ≤ 20) — a THIRD-PARTY quota, so `search`. GET/PUT
141
+ // `…/sonar` are DB-only and fall to `read` on the next line, decided rather than inherited.
142
+ if (method === 'POST' && /^\/api\/workspaces\/\d+\/sonar\/check$/.test(path))
143
+ return [TIERS.search, TIERS.read];
135
144
  if (path === '/api/workspaces' || path.startsWith('/api/workspaces/'))
136
145
  return [TIERS.read];
137
146
  // ---- Per-account settings writes: `read`, RECORDED rather than inherited ----
@@ -406,8 +415,7 @@ function tierFor(method, path) {
406
415
  // calling it 404s, which is the catch-all's problem, not a tier's.
407
416
  // POST /api/pro/slack/test "Send test" — and it is NOT a probe. It runs the SAME billed
408
417
  // path the cron does: `refreshSprintReport` (one Haiku call unless
409
- // the payload hash is unchanged) plus the account-wide repo-digest
410
- // refresh, then an outbound POST to a user-supplied host. It stays
418
+ // the payload hash is unchanged), then an outbound POST to a user-supplied host. It stays
411
419
  // on `ai` + `aiHourly` — where the catch-all would have put it
412
420
  // anyway, spelled here so the family's two tiers are DECIDED
413
421
  // together rather than one of them being inherited. ⚠ It takes ONE
@@ -430,6 +438,51 @@ function tierFor(method, path) {
430
438
  // the token: theirs, not ours, so the 60/min `search` bucket. Anchored at BOTH ends, so a sibling
431
439
  // `/api/prs/:id/*` route is not swept in — and the segment is `tracker-ticket`, never a prefix of
432
440
  // `ticket-reviews` (the ticket review's own, `read`-tier family below).
441
+ // ---- SonarCloud's PR routes (sonar/routes.ts), anchored at BOTH ends ----
442
+ // GET /api/prs/:id/sonar the STORED snapshot, no SonarCloud call → read
443
+ // POST /api/prs/:id/sonar/refresh reads SonarCloud now (issues, hotspots, gate, rules,
444
+ // flagged lines — up to ~100 calls on THEIR quota;
445
+ // + ONE GitHub REST read when SonarCloud analysed
446
+ // another sha, to check it is the head's merge commit) → search
447
+ // POST /api/prs/:id/sonar/post ONE GitHub comment → github_write
448
+ if (!mutating && /^\/api\/prs\/\d+\/sonar$/.test(path))
449
+ return [TIERS.read];
450
+ if (method === 'POST' && /^\/api\/prs\/\d+\/sonar\/refresh$/.test(path))
451
+ return [TIERS.search, TIERS.read];
452
+ if (method === 'POST' && /^\/api\/prs\/\d+\/sonar\/post$/.test(path))
453
+ return [TIERS.githubWrite];
454
+ // ---- SEQUENCE DIAGRAMS (review/diagrams/routes.ts, LOCAL ONLY), anchored at BOTH ends ----
455
+ // GET /api/prs/:id/sequence-diagrams stored rows, no model → read
456
+ // POST /api/prs/:id/sequence-diagrams/redraw queues ONE Sonnet 5 agent run → ai + ai_hourly
457
+ // GET /api/sequence-diagrams/story stored rows, no model → read
458
+ // POST /api/sequence-diagrams/story/redraw queues ONE Sonnet 5 agent run → ai + ai_hourly
459
+ if (!mutating && /^\/api\/prs\/\d+\/sequence-diagrams$/.test(path))
460
+ return [TIERS.read];
461
+ if (method === 'POST' && /^\/api\/prs\/\d+\/sequence-diagrams\/redraw$/.test(path))
462
+ return [TIERS.ai, TIERS.aiHourly];
463
+ if (!mutating && path === '/api/sequence-diagrams/story')
464
+ return [TIERS.read];
465
+ if (method === 'POST' && path === '/api/sequence-diagrams/story/redraw')
466
+ return [TIERS.ai, TIERS.aiHourly];
467
+ // ---- REFERENCE DOCS (reference-docs/routes.ts, LOCAL ONLY), anchored at BOTH ends ----
468
+ // POST /api/docs/upload stores a file (≤ 25 MB) + queues ONE Sonnet 5 build → ai + ai_hourly
469
+ // POST /api/docs/url queues ONE build (which fetches the page) → ai + ai_hourly
470
+ // POST /api/docs/:id/rebuild queues ONE build → ai + ai_hourly
471
+ // POST /api/docs/chat ONE Sonnet 5 agent answer → ai + ai_hourly
472
+ // POST /api/docs/story-alignment ONE Haiku shortlist + ONE Sonnet 5 read of a story → ai + ai_hourly
473
+ // GET /api/docs/story-alignment the stored runs (DB + disk) → read
474
+ // everything else (list, tree, section, stream, cancel, delete, chats, settings) → read
475
+ // The upload's bytes ride the same `ai` bucket: twenty 25 MB files a minute is far past a person
476
+ // adding reference material, and a script hits the hourly window long before the disk notices.
477
+ if (method === 'POST' && (path === '/api/docs/upload' || path === '/api/docs/url' || path === '/api/docs/chat' || path === '/api/docs/story-alignment')) {
478
+ return [TIERS.ai, TIERS.aiHourly];
479
+ }
480
+ if (method === 'POST' && /^\/api\/docs\/\d+\/rebuild$/.test(path))
481
+ return [TIERS.ai, TIERS.aiHourly];
482
+ if (path === '/api/docs' || path.startsWith('/api/docs/'))
483
+ return [TIERS.read];
484
+ if (/^\/api\/workspaces\/\d+\/docs-settings$/.test(path))
485
+ return [TIERS.read];
433
486
  if (!mutating && /^\/api\/prs\/\d+\/tracker-ticket$/.test(path))
434
487
  return [TIERS.search, TIERS.read];
435
488
  if (method === 'POST' && /^\/api\/prs\/\d+\/tracker-ticket\/refresh$/.test(path)) {
@@ -438,6 +491,12 @@ function tierFor(method, path) {
438
491
  if (method === 'PUT' && /^\/api\/prs\/\d+\/tracker-ticket\/ac-field$/.test(path)) {
439
492
  return [TIERS.search, TIERS.read];
440
493
  }
494
+ // POST …/tracker-ticket/resolve — the Story check's paste box: up to TICKET_REFS_MAX tickets a
495
+ // person named, each read from the customer's tracker (or GitHub, for GitHub Issues) with the
496
+ // workspace's credential. Their quota, so `search`. Exact segment `resolve`.
497
+ if (method === 'POST' && /^\/api\/prs\/\d+\/tracker-ticket\/resolve$/.test(path)) {
498
+ return [TIERS.search, TIERS.read];
499
+ }
441
500
  // POST /api/ticket-links — the Open PRs cards' ticket row, ONE request per board: DB-only
442
501
  // detection plus the STORED ticket rows; a detected ticket with no row kicks the background
443
502
  // worker, which spends the customer's tracker quota — so `search`, not `read`. A POST only
@@ -448,13 +507,12 @@ function tierFor(method, path) {
448
507
  // pull_requests), no tracker call. `read`, DECIDED rather than inherited.
449
508
  if (!mutating && path === '/api/ticket-merged-prs')
450
509
  return [TIERS.read];
510
+ // GET /api/ticket-prs — the ticket tab: stored ticket rows joined to PRs, DB-only.
511
+ if (!mutating && path === '/api/ticket-prs')
512
+ return [TIERS.read];
451
513
  // ---- AI generation ----
452
- // ⚠ RE-DECIDED, not inherited: `POST /api/pro/prs/:id/annotations/run` now spends GITHUB quota
453
- // as well as model tokens — one PR_DETAIL_QUERY per uncached PR for the anchor hunks
454
- // (sync/hydrate-detail.ts) on top of the two-sha compares. It stays on `ai` (20/min), which is
455
- // TIGHTER than `prDetail`'s 60/min, so the GitHub exposure is already bounded more
456
- // conservatively than a plain `GET /api/prs/:id`. Nothing to change — but the decision is
457
- // recorded, because this file's failure mode is a tier nobody re-examined.
514
+ // `POST /api/pro/prs/:id/annotations/run` (the blast-radius impact note — the one kind left;
515
+ // the comment kinds that hydrated hunks and compared shas are REMOVED) stays on `ai` (20/min).
458
516
  // The PAIRED `GET /api/pro/prs/:id/annotations` must STAY on `read`: it is a pure DB read fired
459
517
  // on every PR open, and it must never grow a hydration leg (see the prDetail note above for
460
518
  // what that cost the first time).
@@ -480,6 +538,35 @@ function tierFor(method, path) {
480
538
  return [TIERS.githubWrite];
481
539
  if (path.startsWith('/api/ticket-reviews'))
482
540
  return [TIERS.read];
541
+ // STORY LABEL RUN (Reports → Stories; path kept from the retired Story health), EXACT paths:
542
+ // POST /api/story-health/label starts ONE label run (≤150 Haiku calls) → ai + ai_hourly
543
+ // POST /api/story-health/label/cancel stops spend, spends nothing → read
544
+ // GET /api/story-health/label the account's run (memory) → read
545
+ if (path === '/api/story-health/label' && mutating)
546
+ return [TIERS.ai, TIERS.aiHourly];
547
+ if (path === '/api/story-health/label/cancel')
548
+ return [TIERS.read];
549
+ if (path === '/api/story-health/label')
550
+ return [TIERS.read];
551
+ // THIS SPRINT (Activity → This Sprint), matched EXPLICITLY by exact path, FOLLOWING THE TOKEN:
552
+ // POST /api/sprint-stories/refresh kicks a third-party tracker read → search + read
553
+ // … and, with AI on (local), may start ≤ 3 ticket
554
+ // checks (agent runs) → search + ai + ai_hourly
555
+ // POST /api/sprint-graph/ask ONE Haiku completion per turn → ai + ai_hourly
556
+ // GET /api/sprint-graph, GET …/token, PUT …/statuses, GET /api/ticket-story → read
557
+ // ("Check against code" — POST /api/sprint-stories/assess and its assessments/repos routes — is
558
+ // REMOVED; nothing answers those paths any more.)
559
+ if (mutating && path === '/api/sprint-stories/refresh') {
560
+ return config.aiEnabled ? [TIERS.search, TIERS.ai, TIERS.aiHourly] : [TIERS.search, TIERS.read];
561
+ }
562
+ if (mutating && path === '/api/sprint-graph/ask')
563
+ return [TIERS.ai, TIERS.aiHourly];
564
+ if (path === '/api/sprint-graph' ||
565
+ path === '/api/sprint-graph/token' ||
566
+ path === '/api/sprint-stories/statuses' ||
567
+ path === '/api/ticket-story') {
568
+ return [TIERS.read];
569
+ }
483
570
  // CI REVIEW (review/ci-review/routes.ts), matched EXPLICITLY by exact path for the same reason:
484
571
  // POST /api/ci-reviews starts an agent run (reads the head's checks + logs) → ai + ai_hourly
485
572
  // POST /api/ci-reviews/states batched DB read of the SYNCED head + failing names → read
@@ -490,6 +577,21 @@ function tierFor(method, path) {
490
577
  return [TIERS.read];
491
578
  if (path.startsWith('/api/ci-reviews'))
492
579
  return [TIERS.read];
580
+ // "Post reply" on a follow-up reply status: ONE GitHub thread reply (+ a resolve), no model.
581
+ if (mutating && /^\/api\/claude-reviews\/\d+\/follow-up\/\d+\/reply$/.test(path))
582
+ return [TIERS.githubWrite];
583
+ // AI Fix PLAN MODE (coding/ai-fix/routes.ts, LOCAL ONLY). Spelled out so the family cannot drift
584
+ // with the /api/pro/ catch-all below, which would also have parked the DB-only discard on `ai`:
585
+ // POST /api/pro/prs/:id/ai-fix/plan starts a planning agent pass → ai + ai_hourly
586
+ // POST /api/pro/ai-fixes/:fixId/plan/message one re-plan agent pass → ai + ai_hourly
587
+ // POST /api/pro/ai-fixes/:fixId/plan/approve starts the build agent run → ai + ai_hourly
588
+ // POST /api/pro/ai-fixes/:fixId/plan/discard one DB update, no model → read
589
+ if (mutating && /^\/api\/pro\/prs\/\d+\/ai-fix\/plan$/.test(path))
590
+ return [TIERS.ai, TIERS.aiHourly];
591
+ if (mutating && /^\/api\/pro\/ai-fixes\/\d+\/plan\/(message|approve)$/.test(path))
592
+ return [TIERS.ai, TIERS.aiHourly];
593
+ if (/^\/api\/pro\/ai-fixes\/\d+\/plan\/discard$/.test(path))
594
+ return [TIERS.read];
493
595
  if (/^\/api\/claude-reviews\/[^/]+\/chat$/.test(path)) {
494
596
  return mutating ? [TIERS.ai, TIERS.aiHourly] : [TIERS.read];
495
597
  }
@@ -514,6 +616,8 @@ function tierFor(method, path) {
514
616
  // The Open PRs table's batched latest-run read: a POST only because a list of ids does not
515
617
  // fit a query string. One DB query, no model, no GitHub — so `read`, exact path only.
516
618
  path !== '/api/claude-review/states' &&
619
+ // The thread cards' batched Claude-judgement read: the same shape, DB-only.
620
+ path !== '/api/claude-review/thread-judgements' &&
517
621
  !isSettingsWrite;
518
622
  if (generates)
519
623
  return [TIERS.ai, TIERS.aiHourly];
@@ -626,6 +730,11 @@ function tierFor(method, path) {
626
730
  // neither a substitute.
627
731
  if (!mutating && path === '/api/flow-findings')
628
732
  return [TIERS.search, TIERS.read];
733
+ // GET /api/flow-trend — Chronology "Over time" (PAID `periodReports`, deterministic): the same
734
+ // ledger walk as /api/flow-findings over 26 weeks, plus four grouped first-seen scans. DB-only,
735
+ // and heavier than the panel, so the same `search` bucket.
736
+ if (!mutating && path === '/api/flow-trend')
737
+ return [TIERS.search, TIERS.read];
629
738
  // ---- Emoji reactions: BOTH routes reach GitHub, and NEITHER lives under /api/prs/<id>/ ----
630
739
  //
631
740
  // Spelled as two EXACT string matches, above the mutating block, for the reason this file has
@@ -738,6 +847,12 @@ function tierFor(method, path) {
738
847
  // GET …/conflicts/stream an SSE subscribe onto an already-started job — a read.
739
848
  // DELETE …/conflicts a Map.delete. `read` — DECIDED, not inherited from the POST
740
849
  // line, which it would otherwise match.
850
+ // "Resolve with Claude" (coding/ai-resolve/, LOCAL ONLY): the POST starts an agent run — model
851
+ // money, and possibly a clone — so `ai` + `ai_hourly`. Its GET is a Map lookup polled while the
852
+ // run works and its DELETE a cancel: `read`. Exact paths, ABOVE the resolver's own lines.
853
+ if (/^\/api\/prs\/\d+\/conflicts\/ai-resolve$/.test(path)) {
854
+ return method === 'POST' ? [TIERS.ai, TIERS.aiHourly] : [TIERS.read];
855
+ }
741
856
  if (method === 'POST' && /^\/api\/prs\/\d+\/conflicts$/.test(path))
742
857
  return [TIERS.sync];
743
858
  if (method === 'DELETE' && /^\/api\/prs\/\d+\/conflicts$/.test(path))
@@ -777,9 +892,16 @@ function tierFor(method, path) {
777
892
  // it belongs in the same bucket as an explicit merge.
778
893
  //
779
894
  // Deliberately NOT here: `/dismiss` and `/mark-viewed` (pure local bookkeeping).
780
- const hitsGithub = path.startsWith('/api/threads/') ||
895
+ //
896
+ // `/api/dependencies/merge-all` is Pending → Dependencies' "Merge or arm all": up to one
897
+ // merge (or a live probe + arm) per listed PR, sequentially, on the account's quota. Spelled
898
+ // EXACTLY; its dry run hits no GitHub but shares the bucket rather than a second rule.
899
+ // `withdraw-approval` (dismiss the viewer's own APPROVED review: a live reviews list + one REST
900
+ // dismissal) is spelled out — `approve` is ANCHORED after the id and does not prefix-match it.
901
+ const hitsGithub = path === '/api/dependencies/merge-all' ||
902
+ path.startsWith('/api/threads/') ||
781
903
  path.startsWith('/api/bot-threads/') ||
782
- /^\/api\/prs\/\d+\/(review-comment|comments?|approve|request-changes|close|reopen|ci\/rerun|request-reviewers|merge-queue|merge|auto-merge|update-branch|resolve-bot-threads|reviews)/.test(path);
904
+ /^\/api\/prs\/\d+\/(review-comment|comments?|approve|withdraw-approval|request-changes|close|reopen|ci\/rerun|request-reviewers|merge-queue|merge|auto-merge|update-branch|resolve-bot-threads|reviews)/.test(path);
783
905
  if (hitsGithub)
784
906
  return [TIERS.githubWrite];
785
907
  }
@@ -51,10 +51,10 @@ export async function activityRoutes(app) {
51
51
  // every repo in it, with no second visibility axis; `repoIds` + `userIds` narrow WITHIN that.
52
52
  // Pure DB read — no GitHub sync, no AI.
53
53
  //
54
- // `workspaceId` is passed SEPARATELY from `repoIds` and is required: the bots-only path
55
- // resolves an automated-reviewer set, and that answer is a workspace fact. The two deliberately
56
- // disagree on the single-PR isolation path (`prId`), which reaches a PR whose repo may be
57
- // filtered out of `repoIds` — the workspace still owns "is this login a bot" there.
54
+ // `workspaceId` is passed SEPARATELY from `repoIds` and is required: the `authors` split
55
+ // (people | bots) resolves the union bot set, and that answer is a workspace fact. The two
56
+ // deliberately disagree on the single-PR isolation path (`prId`), which reaches a PR whose repo
57
+ // may be filtered out of `repoIds` — the workspace still owns "is this login a bot" there.
58
58
  app.get('/api/activity/feed', async (req) => {
59
59
  const q = req.query;
60
60
  const accountId = accountIdOf(req);
@@ -62,11 +62,6 @@ export async function activityRoutes(app) {
62
62
  const limit = q.limit != null ? Number(q.limit) : null;
63
63
  const offset = q.offset != null ? Number(q.offset) : 0;
64
64
  const prId = q.prId != null ? Number(q.prId) : null;
65
- // Bot-only feed window (days) — clamped to 1..90; only honored on the botsOnly path.
66
- const botWindowDaysRaw = q.botWindowDays != null ? Number(q.botWindowDays) : null;
67
- const botWindowDays = botWindowDaysRaw != null && Number.isFinite(botWindowDaysRaw)
68
- ? Math.min(90, Math.max(1, Math.trunc(botWindowDaysRaw)))
69
- : null;
70
65
  return getConsolidatedFeed(accountId, {
71
66
  workspaceId: scope.workspaceId,
72
67
  repoIds: scope.repoIds,
@@ -74,15 +69,9 @@ export async function activityRoutes(app) {
74
69
  prId: prId != null && Number.isFinite(prId) ? prId : null,
75
70
  limit: Number.isFinite(limit) && limit != null && limit > 0 ? limit : null,
76
71
  offset: Number.isFinite(offset) && offset > 0 ? offset : 0,
77
- excludeBots: q.excludeBots === 'true',
78
- allowBotIds: parseIntList(q.allowBotIds),
79
- botsOnly: q.botsOnly === 'true',
80
- botWindowDays,
72
+ // People or bots, never both. Anything but an explicit 'bots' is people — the default.
73
+ authors: q.authors === 'bots' ? 'bots' : 'humans',
81
74
  includeAllCommits: q.includeAllCommits === 'true',
82
- // Opt-in CI-failure rows (PR heads + the default branch). OFF unless explicitly asked
83
- // for, like includeAllCommits — a missing/garbage value means off, never on. Still a
84
- // pure DB read over two indexed transition logs, so no new rate-limit tier is needed.
85
- includeCiFailures: q.includeCiFailures === 'true',
86
75
  });
87
76
  });
88
77
  // Mark the Activity Feed as seen (bumps the account's server-side "seen" marker to