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.
- package/THIRD_PARTY_NOTICES.md +21 -0
- package/dist/ai/agent-isolation.js +45 -0
- package/dist/ai/runtime.js +114 -73
- package/dist/api/plugins/rate-limit.js +134 -12
- package/dist/api/routes/activity.js +6 -17
- package/dist/api/routes/bot-triage.js +45 -77
- package/dist/api/routes/conflicts.js +1 -1
- package/dist/api/routes/daily-brief.js +3 -3
- package/dist/api/routes/dependency-merge.js +96 -0
- package/dist/api/routes/flow.js +39 -6
- package/dist/api/routes/insights.js +92 -24
- package/dist/api/routes/me.js +8 -3
- package/dist/api/routes/ml-labels.js +2 -2
- package/dist/api/routes/ml-runtime.js +64 -0
- package/dist/api/routes/prs.js +115 -54
- package/dist/api/routes/search.js +6 -1
- package/dist/api/routes/sprint-stories.js +80 -0
- package/dist/api/routes/users.js +1 -1
- package/dist/api/routes/workspaces.js +8 -0
- package/dist/app.js +18 -0
- package/dist/bot-triage/resolve.js +3 -1
- package/dist/cli.js +61 -0
- package/dist/coding/agent.js +137 -26
- package/dist/coding/ai-fix/auto-fix.js +32 -9
- package/dist/coding/ai-fix/auto-push.js +56 -0
- package/dist/coding/ai-fix/manager.js +444 -36
- package/dist/coding/ai-fix/persist.js +186 -3
- package/dist/coding/ai-fix/plan.js +176 -0
- package/dist/coding/ai-fix/prompts.js +88 -1
- package/dist/coding/ai-fix/push.js +66 -0
- package/dist/coding/ai-fix/review-seed.js +431 -125
- package/dist/coding/ai-fix/routes.js +245 -159
- package/dist/coding/ai-fix/thread-candidates.js +77 -0
- package/dist/coding/ai-resolve/prompt.js +154 -0
- package/dist/coding/ai-resolve/routes.js +98 -0
- package/dist/coding/ai-resolve/run.js +300 -0
- package/dist/coding/ai-resolve/schema.js +26 -0
- package/dist/coding/ai-resolve/validate.js +136 -0
- package/dist/coding/partial-spend.js +19 -0
- package/dist/coding/schema.js +24 -0
- package/dist/config.js +59 -6
- package/dist/conflict/session.js +10 -6
- package/dist/db/blast-radius-query.js +44 -0
- package/dist/db/changepoint.js +4 -1
- package/dist/db/daily-brief.js +3 -0
- package/dist/db/erase-account.js +101 -1
- package/dist/db/export-account.js +48 -0
- package/dist/db/flow-detail.js +38 -25
- package/dist/db/flow-pointers.js +11 -2
- package/dist/db/flow-settings.js +36 -1
- package/dist/db/flow-trend.js +576 -0
- package/dist/db/merge-queue.js +5 -1
- package/dist/db/merged-prs.js +50 -0
- package/dist/db/merged-reach.js +83 -0
- package/dist/db/migrations/0091_auto_post_verdict_resolve_fix.sql +24 -0
- package/dist/db/migrations/0092_finding_pushback.sql +9 -0
- package/dist/db/migrations/0093_dependency_auto_merge.sql +25 -0
- package/dist/db/migrations/0094_repo_uses_github_issues.sql +26 -0
- package/dist/db/migrations/0095_review_chat_explain.sql +14 -0
- package/dist/db/migrations/0096_workspace_setting_events.sql +27 -0
- package/dist/db/migrations/0097_story_reports.sql +40 -0
- package/dist/db/migrations/0098_tracker_ticket_dates.sql +11 -0
- package/dist/db/migrations/0099_story_labels.sql +29 -0
- package/dist/db/migrations/0100_sprint_stories.sql +141 -0
- package/dist/db/migrations/0101_sprint_reads_api_root.sql +6 -0
- package/dist/db/migrations/0102_workspace_sprint_checks.sql +5 -0
- package/dist/db/migrations/0103_sprint_statuses_and_blockers.sql +26 -0
- package/dist/db/migrations/0104_sprint_ticket_repos_saved.sql +7 -0
- package/dist/db/migrations/0105_sprint_assessment_mode.sql +8 -0
- package/dist/db/migrations/0106_sprint_ref_tickets.sql +27 -0
- package/dist/db/migrations/0107_sprint_graph.sql +47 -0
- package/dist/db/migrations/0108_sprint_themes.sql +24 -0
- package/dist/db/migrations/0109_sprint_story.sql +23 -0
- package/dist/db/migrations/0110_finding_reminder.sql +7 -0
- package/dist/db/migrations/0111_sprint_dates_story_attempt.sql +19 -0
- package/dist/db/migrations/0112_sprint_view_snapshots.sql +20 -0
- package/dist/db/migrations/0113_ai_fix_plan.sql +27 -0
- package/dist/db/migrations/0114_sonarcloud.sql +42 -0
- package/dist/db/migrations/0115_sequence_diagrams.sql +40 -0
- package/dist/db/migrations/0116_reference_docs.sql +64 -0
- package/dist/db/migrations/0117_review_doc_references.sql +11 -0
- package/dist/db/migrations/0118_sequence_diagram_runs.sql +10 -0
- package/dist/db/migrations/0119_design_diagrams.sql +7 -0
- package/dist/db/migrations/0120_sprint_paths.sql +33 -0
- package/dist/db/migrations/0121_story_docs_alignments.sql +42 -0
- package/dist/db/migrations/0122_claude_thread_checks.sql +42 -0
- package/dist/db/migrations/0123_sprint_brief_one_liner.sql +5 -0
- package/dist/db/migrations/0124_reference_doc_crawl.sql +8 -0
- package/dist/db/migrations/meta/_journal.json +238 -0
- package/dist/db/migrations-pg/0078_auto_post_verdict_resolve_fix.sql +11 -0
- package/dist/db/migrations-pg/0079_finding_pushback.sql +3 -0
- package/dist/db/migrations-pg/0080_dependency_auto_merge.sql +16 -0
- package/dist/db/migrations-pg/0081_repo_uses_github_issues.sql +7 -0
- package/dist/db/migrations-pg/0082_review_chat_explain.sql +5 -0
- package/dist/db/migrations-pg/0083_workspace_setting_events.sql +13 -0
- package/dist/db/migrations-pg/0084_story_reports.sql +28 -0
- package/dist/db/migrations-pg/0085_tracker_ticket_dates.sql +5 -0
- package/dist/db/migrations-pg/0086_story_labels.sql +19 -0
- package/dist/db/migrations-pg/0087_sprint_stories.sql +121 -0
- package/dist/db/migrations-pg/0088_sprint_reads_api_root.sql +3 -0
- package/dist/db/migrations-pg/0089_workspace_sprint_checks.sql +3 -0
- package/dist/db/migrations-pg/0090_sprint_statuses_and_blockers.sql +14 -0
- package/dist/db/migrations-pg/0091_sprint_ticket_repos_saved.sql +4 -0
- package/dist/db/migrations-pg/0092_sprint_assessment_mode.sql +4 -0
- package/dist/db/migrations-pg/0093_sprint_ref_tickets.sql +21 -0
- package/dist/db/migrations-pg/0094_sprint_graph.sql +38 -0
- package/dist/db/migrations-pg/0095_sprint_themes.sql +19 -0
- package/dist/db/migrations-pg/0096_sprint_story.sql +18 -0
- package/dist/db/migrations-pg/0097_finding_reminder.sql +3 -0
- package/dist/db/migrations-pg/0098_sprint_dates_story_attempt.sql +12 -0
- package/dist/db/migrations-pg/0099_sprint_view_snapshots.sql +15 -0
- package/dist/db/migrations-pg/0100_ai_fix_plan.sql +13 -0
- package/dist/db/migrations-pg/0101_sonarcloud.sql +31 -0
- package/dist/db/migrations-pg/0102_sequence_diagrams.sql +32 -0
- package/dist/db/migrations-pg/0103_reference_docs.sql +51 -0
- package/dist/db/migrations-pg/0104_review_doc_references.sql +5 -0
- package/dist/db/migrations-pg/0105_sequence_diagram_runs.sql +5 -0
- package/dist/db/migrations-pg/0106_design_diagrams.sql +3 -0
- package/dist/db/migrations-pg/0107_sprint_paths.sql +26 -0
- package/dist/db/migrations-pg/0108_story_docs_alignments.sql +34 -0
- package/dist/db/migrations-pg/0109_claude_thread_checks.sql +31 -0
- package/dist/db/migrations-pg/0110_sprint_brief_one_liner.sql +3 -0
- package/dist/db/migrations-pg/0111_reference_doc_crawl.sql +3 -0
- package/dist/db/migrations-pg/meta/_journal.json +238 -0
- package/dist/db/my-turn-card-facts.js +48 -8
- package/dist/db/pending-tabs.js +18 -10
- package/dist/db/pr-intervals.js +198 -123
- package/dist/db/pr-liveness.js +7 -0
- package/dist/db/queries.js +248 -578
- package/dist/db/repo-activity.js +18 -25
- package/dist/db/reporting-window.js +56 -0
- package/dist/db/retention.js +11 -0
- package/dist/db/review-threads-for-review.js +77 -14
- package/dist/db/schema.pg.js +650 -2
- package/dist/db/schema.sqlite.js +929 -5
- package/dist/db/search.js +105 -3
- package/dist/db/sequence-diagram-prune.js +54 -0
- package/dist/db/setting-event-write.js +29 -0
- package/dist/db/sprint-briefs.js +168 -0
- package/dist/db/sprint-graph-fold.js +199 -0
- package/dist/db/sprint-graph.js +474 -0
- package/dist/db/sprint-pass-flights.js +22 -0
- package/dist/db/sprint-paths.js +473 -0
- package/dist/db/sprint-signals.js +276 -0
- package/dist/db/sprint-status-layout.js +443 -0
- package/dist/db/sprint-status-settings.js +261 -0
- package/dist/db/sprint-store.js +106 -0
- package/dist/db/sprint-themes.js +120 -0
- package/dist/db/sprint-ticket-checks.js +117 -0
- package/dist/db/stories.js +309 -0
- package/dist/db/story-criteria.js +175 -0
- package/dist/db/story-health-fold.js +21 -0
- package/dist/db/story-health.js +219 -0
- package/dist/db/story-issue-input.js +155 -0
- package/dist/db/story-label-input.js +51 -0
- package/dist/db/synthesis-input.js +6 -122
- package/dist/db/work-plan.js +3 -0
- package/dist/db/working-hours.js +29 -0
- package/dist/db/workspace-setting-events.js +52 -0
- package/dist/github/mutations.js +75 -0
- package/dist/github/queries.js +5 -0
- package/dist/merge/arm-intent.js +77 -0
- package/dist/merge/auto-merge-runner.js +159 -16
- package/dist/merge/dependency-policy.js +381 -0
- package/dist/ml/blake2b.js +142 -0
- package/dist/ml/decode.js +121 -0
- package/dist/ml/local-scorer.js +182 -0
- package/dist/ml/markers.js +770 -0
- package/dist/ml/model.js +88 -0
- package/dist/ml/onnx-head.js +129 -0
- package/dist/ml/power.js +59 -0
- package/dist/ml/runtime.js +377 -0
- package/dist/ml/scorer-process.js +44 -0
- package/dist/ml/scorer.js +106 -0
- package/dist/ml/throttle.js +34 -0
- package/dist/pr/detail-enricher.js +5 -3
- package/dist/pro/bind.js +19 -3
- package/dist/pro/event-hooks.js +37 -0
- package/dist/reference-docs/agent.js +329 -0
- package/dist/reference-docs/assemble.js +62 -0
- package/dist/reference-docs/build-judge.js +130 -0
- package/dist/reference-docs/chat.js +166 -0
- package/dist/reference-docs/context.js +61 -0
- package/dist/reference-docs/convert.js +177 -0
- package/dist/reference-docs/crawl-rank.js +106 -0
- package/dist/reference-docs/crawl.js +639 -0
- package/dist/reference-docs/library.js +158 -0
- package/dist/reference-docs/manager.js +399 -0
- package/dist/reference-docs/paths.js +122 -0
- package/dist/reference-docs/prompts.js +125 -0
- package/dist/reference-docs/render.js +207 -0
- package/dist/reference-docs/review-refs.js +288 -0
- package/dist/reference-docs/robots.js +127 -0
- package/dist/reference-docs/routes.js +491 -0
- package/dist/reference-docs/schema.js +31 -0
- package/dist/reference-docs/split.js +397 -0
- package/dist/reference-docs/startup.js +14 -0
- package/dist/reference-docs/store.js +345 -0
- package/dist/reference-docs/url-fetch.js +177 -0
- package/dist/review/agent-context.js +4 -3
- package/dist/review/agent.js +40 -8
- package/dist/review/agentic.js +63 -2
- package/dist/review/chat-agent.js +45 -6
- package/dist/review/ci-review/agent.js +3 -1
- package/dist/review/ci-review/auto-post.js +221 -0
- package/dist/review/ci-review/manager.js +12 -1
- package/dist/review/ci-review/persist.js +27 -0
- package/dist/review/ci-review/prepare.js +1 -1
- package/dist/review/ci-review/prompts.js +5 -3
- package/dist/review/ci-review/schema.js +6 -0
- package/dist/review/ci-review/with-review.js +37 -0
- package/dist/review/claude-review/auto-post.js +274 -87
- package/dist/review/claude-review/auto-pushback.js +131 -0
- package/dist/review/claude-review/auto-resolve.js +207 -0
- package/dist/review/claude-review/auto-settings.js +212 -17
- package/dist/review/claude-review/auto.js +103 -1
- package/dist/review/claude-review/chat-explain.js +222 -0
- package/dist/review/claude-review/chat.js +201 -23
- package/dist/review/claude-review/ci-failures.js +154 -34
- package/dist/review/claude-review/finding-replies.js +297 -0
- package/dist/review/claude-review/finding-thread.js +58 -0
- package/dist/review/claude-review/follow-up.js +143 -12
- package/dist/review/claude-review/manager.js +136 -16
- package/dist/review/claude-review/manual-reply.js +198 -0
- package/dist/review/claude-review/persist.js +517 -117
- package/dist/review/claude-review/posted-state.js +236 -0
- package/dist/review/claude-review/prompts.js +132 -10
- package/dist/review/claude-review/reply-text.js +16 -0
- package/dist/review/claude-review/routes.js +127 -8
- package/dist/review/claude-review/settled-by-reply.js +76 -84
- package/dist/review/claude-review/thread-check-agent.js +148 -0
- package/dist/review/claude-review/thread-check-persist.js +183 -0
- package/dist/review/claude-review/thread-check-routes.js +105 -0
- package/dist/review/claude-review/thread-check.js +416 -0
- package/dist/review/claude-review/thread-judgements.js +309 -0
- package/dist/review/claude-review/threads.js +28 -4
- package/dist/review/diagrams/agent.js +173 -0
- package/dist/review/diagrams/fingerprint.js +58 -0
- package/dist/review/diagrams/manager.js +344 -0
- package/dist/review/diagrams/persist.js +253 -0
- package/dist/review/diagrams/prompts.js +134 -0
- package/dist/review/diagrams/routes.js +196 -0
- package/dist/review/diagrams/schema.js +40 -0
- package/dist/review/diagrams/trigger.js +160 -0
- package/dist/review/diagrams/validate.js +236 -0
- package/dist/review/llm.js +53 -7
- package/dist/review/path-guard.js +34 -7
- package/dist/review/post-review.js +85 -0
- package/dist/review/post-seam.js +68 -11
- package/dist/review/schema.js +60 -17
- package/dist/review/sprint-ask/prompt.js +99 -0
- package/dist/review/sprint-ask/routes.js +91 -0
- package/dist/review/sprint-briefs/index.js +39 -0
- package/dist/review/sprint-briefs/manager.js +191 -0
- package/dist/review/sprint-briefs/prompt.js +55 -0
- package/dist/review/sprint-briefs/validate.js +96 -0
- package/dist/review/sprint-paths/agent.js +174 -0
- package/dist/review/sprint-paths/manager.js +113 -0
- package/dist/review/sprint-paths/prompt.js +24 -0
- package/dist/review/sprint-paths/validate.js +116 -0
- package/dist/review/sprint-themes/manager.js +91 -0
- package/dist/review/sprint-themes/prompt.js +31 -0
- package/dist/review/sprint-themes/validate.js +82 -0
- package/dist/review/story-docs/agent.js +157 -0
- package/dist/review/story-docs/fingerprint.js +33 -0
- package/dist/review/story-docs/inputs.js +100 -0
- package/dist/review/story-docs/manager.js +275 -0
- package/dist/review/story-docs/persist.js +162 -0
- package/dist/review/story-docs/prompts.js +80 -0
- package/dist/review/story-docs/routes.js +101 -0
- package/dist/review/story-docs/schema.js +46 -0
- package/dist/review/story-docs/shortlist.js +202 -0
- package/dist/review/story-docs/validate.js +266 -0
- package/dist/review/story-labels/manager.js +271 -0
- package/dist/review/story-labels/prompt.js +70 -0
- package/dist/review/story-labels/routes.js +85 -0
- package/dist/review/story-labels/validate.js +198 -0
- package/dist/review/submit-map.js +3 -0
- package/dist/review/ticket-review/agent.js +8 -4
- package/dist/review/ticket-review/manager.js +29 -2
- package/dist/review/ticket-review/prepass.js +3 -1
- package/dist/review/ticket-review/prompts.js +9 -0
- package/dist/review/ticket-review/routes.js +5 -1
- package/dist/review/ticket-review/schema.js +10 -3
- package/dist/review/ticket-review/sprint-checks.js +134 -0
- package/dist/shared/activity-windows.js +8 -0
- package/dist/shared/blast-radius.js +207 -0
- package/dist/shared/claude-review.js +17 -2
- package/dist/shared/conflict-ai.js +24 -0
- package/dist/shared/conflicts.js +3 -0
- package/dist/shared/dependency-merge.js +21 -0
- package/dist/shared/flow-settings.js +1 -1
- package/dist/shared/flow-trend.js +26 -0
- package/dist/shared/index.js +20 -0
- package/dist/shared/pending-rules.js +40 -5
- package/dist/shared/reference-docs.js +104 -0
- package/dist/shared/reporting-window.js +30 -0
- package/dist/shared/sequence-diagrams.js +54 -0
- package/dist/shared/sonar.js +144 -0
- package/dist/shared/sprint-graph.js +193 -0
- package/dist/shared/sprint-lanes.js +36 -0
- package/dist/shared/sprint-members.js +87 -0
- package/dist/shared/sprint-stories.js +190 -0
- package/dist/shared/stories.js +10 -0
- package/dist/shared/story-docs-alignment.js +210 -0
- package/dist/shared/story-health.js +114 -0
- package/dist/shared/story-issues.js +358 -0
- package/dist/shared/story-rules.js +134 -0
- package/dist/shared/ticket-prs.js +21 -0
- package/dist/shared/ticket-refs.js +169 -0
- package/dist/shared/types.js +40 -7
- package/dist/sonar/client.js +68 -0
- package/dist/sonar/comment.js +167 -0
- package/dist/sonar/gather.js +571 -0
- package/dist/sonar/index.js +18 -0
- package/dist/sonar/post.js +101 -0
- package/dist/sonar/routes.js +211 -0
- package/dist/sonar/settings.js +161 -0
- package/dist/sonar/snapshot.js +127 -0
- package/dist/sonar/store.js +111 -0
- package/dist/sync/bot-detection.js +1 -1
- package/dist/sync/branch-status.js +5 -6
- package/dist/sync/hydrate-detail.js +1 -1
- package/dist/sync/merge-notice.js +56 -0
- package/dist/sync/ml-enrichment.js +21 -7
- package/dist/sync/scheduler.js +8 -3
- package/dist/sync/upsert.js +10 -0
- package/dist/tracker/dates-backfill.js +96 -0
- package/dist/tracker/dates.js +31 -0
- package/dist/tracker/enricher.js +79 -11
- package/dist/tracker/github/issues-usage.js +195 -0
- package/dist/tracker/github/reader.js +25 -0
- package/dist/tracker/index.js +28 -1
- package/dist/tracker/jira/client.js +66 -1
- package/dist/tracker/jira/fetch.js +35 -4
- package/dist/tracker/jira/text.js +6 -0
- package/dist/tracker/legacy-import.js +4 -0
- package/dist/tracker/linear/client.js +65 -11
- package/dist/tracker/links.js +3 -3
- package/dist/tracker/manual-links.js +341 -0
- package/dist/tracker/routes.js +17 -0
- package/dist/tracker/settings.js +90 -8
- package/dist/tracker/sprint/github.js +201 -0
- package/dist/tracker/sprint/hooks.js +42 -0
- package/dist/tracker/sprint/index.js +33 -0
- package/dist/tracker/sprint/jira.js +378 -0
- package/dist/tracker/sprint/linear.js +217 -0
- package/dist/tracker/sprint/project-keys.js +49 -0
- package/dist/tracker/sprint/read.js +437 -0
- package/dist/tracker/sprint/ref-status.js +283 -0
- package/dist/tracker/sprint/routes.js +36 -0
- package/dist/tracker/sprint/types.js +29 -0
- package/dist/tracker/store.js +12 -1
- package/dist/tracker/ticket-prs.js +120 -0
- package/dist/tracker/worker.js +76 -10
- package/package.json +48 -5
- package/public/assets/AiFixTab-IKfiwwnd.js +3 -0
- package/public/assets/ClaudeReviewTab-DlefFnYI.js +20 -0
- package/public/assets/PrBotBehaviourTab-BJelm30R.js +1 -0
- package/public/assets/index-CB4YyNHK.css +1 -0
- package/public/assets/index-Cm0XoYfd.js +79 -0
- package/public/index.html +2 -2
- package/public-landing/assets/{index-CA3eEGXO.js → index-D3DGTvPS.js} +1 -1
- package/public-landing/bots/index.html +6 -6
- package/public-landing/contact/index.html +3 -3
- package/public-landing/cookies/index.html +2 -2
- package/public-landing/features/index.html +3 -3
- package/public-landing/for-developers/index.html +3 -3
- package/public-landing/for-managers/index.html +6 -6
- package/public-landing/how-it-works/index.html +3 -3
- package/public-landing/how-we-measure/index.html +3 -3
- package/public-landing/index.html +3 -3
- package/public-landing/insights/index.html +6 -6
- package/public-landing/pricing/index.html +3 -3
- package/public-landing/privacy/index.html +2 -2
- package/public-landing/pro/index.html +6 -6
- package/public-landing/reviews/index.html +6 -6
- package/public-landing/terms/index.html +2 -2
- package/public/assets/AiFixTab-Cq2Wdm2l.js +0 -2
- package/public/assets/ClaudeReviewTab-C6WftwSM.js +0 -16
- package/public/assets/PrBotBehaviourTab-CY_iLmFp.js +0 -1
- package/public/assets/index-CB6bLAqG.js +0 -77
- 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
|
package/dist/ai/runtime.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
|
23
|
-
//
|
|
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)
|
|
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
|
-
//
|
|
453
|
-
//
|
|
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
|
-
|
|
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
|
|
55
|
-
// resolves
|
|
56
|
-
// disagree on the single-PR isolation path (`prId`), which reaches a PR whose repo
|
|
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
|
-
|
|
78
|
-
|
|
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
|