@in-the-loop-labs/pair-review 1.6.2 → 2.0.1
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/README.md +77 -4
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/skills/review-requests/SKILL.md +4 -1
- package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/skills/analyze/SKILL.md +4 -3
- package/public/css/pr.css +1962 -114
- package/public/js/CONVENTIONS.md +16 -0
- package/public/js/components/AIPanel.js +66 -0
- package/public/js/components/AnalysisConfigModal.js +2 -2
- package/public/js/components/ChatPanel.js +2955 -0
- package/public/js/components/CouncilProgressModal.js +12 -16
- package/public/js/components/KeyboardShortcuts.js +3 -0
- package/public/js/components/PanelGroup.js +723 -0
- package/public/js/components/PreviewModal.js +3 -8
- package/public/js/index.js +8 -0
- package/public/js/local.js +17 -615
- package/public/js/modules/analysis-history.js +19 -68
- package/public/js/modules/comment-manager.js +103 -20
- package/public/js/modules/diff-context.js +176 -0
- package/public/js/modules/diff-renderer.js +30 -0
- package/public/js/modules/file-comment-manager.js +126 -105
- package/public/js/modules/file-list-merger.js +64 -0
- package/public/js/modules/panel-resizer.js +25 -6
- package/public/js/modules/suggestion-manager.js +40 -125
- package/public/js/pr.js +1009 -159
- package/public/js/repo-settings.js +36 -6
- package/public/js/utils/category-emoji.js +44 -0
- package/public/js/utils/time.js +32 -0
- package/public/local.html +107 -70
- package/public/pr.html +107 -70
- package/public/repo-settings.html +32 -0
- package/src/ai/analyzer.js +5 -1
- package/src/ai/copilot-provider.js +39 -9
- package/src/ai/cursor-agent-provider.js +45 -11
- package/src/ai/gemini-provider.js +17 -4
- package/src/ai/prompts/config.js +7 -1
- package/src/ai/provider-availability.js +1 -1
- package/src/ai/provider.js +25 -37
- package/src/chat/CONVENTIONS.md +18 -0
- package/src/chat/pi-bridge.js +491 -0
- package/src/chat/prompt-builder.js +272 -0
- package/src/chat/session-manager.js +619 -0
- package/src/config.js +14 -0
- package/src/database.js +322 -15
- package/src/main.js +4 -17
- package/src/routes/analyses.js +721 -0
- package/src/routes/chat.js +655 -0
- package/src/routes/config.js +29 -8
- package/src/routes/context-files.js +274 -0
- package/src/routes/local.js +225 -1133
- package/src/routes/mcp.js +39 -30
- package/src/routes/pr.js +424 -58
- package/src/routes/reviews.js +1035 -0
- package/src/routes/shared.js +4 -29
- package/src/server.js +34 -12
- package/src/sse/review-events.js +46 -0
- package/src/utils/auto-context.js +88 -0
- package/src/utils/category-emoji.js +33 -0
- package/src/utils/diff-annotator.js +75 -1
- package/src/utils/diff-file-list.js +57 -0
- package/src/routes/analysis.js +0 -1600
- package/src/routes/comments.js +0 -534
package/src/routes/mcp.js
CHANGED
|
@@ -7,20 +7,24 @@ const { v4: uuidv4 } = require('uuid');
|
|
|
7
7
|
const { ReviewRepository, CommentRepository, AnalysisRunRepository, RepoSettingsRepository, PRMetadataRepository, query } = require('../database');
|
|
8
8
|
const { renderPromptForSkill } = require('../ai/prompts/render-for-skill');
|
|
9
9
|
const Analyzer = require('../ai/analyzer');
|
|
10
|
+
const { getTierForModel } = require('../ai/provider');
|
|
11
|
+
const { TIERS, TIER_ALIASES, resolveTier } = require('../ai/prompts/config');
|
|
10
12
|
const { GitWorktreeManager } = require('../git/worktree');
|
|
11
13
|
const path = require('path');
|
|
12
14
|
const { normalizeRepository } = require('../utils/paths');
|
|
13
15
|
const logger = require('../utils/logger');
|
|
16
|
+
const { broadcastReviewEvent } = require('../sse/review-events');
|
|
14
17
|
const {
|
|
15
18
|
activeAnalyses,
|
|
16
|
-
|
|
17
|
-
localReviewToAnalysisId,
|
|
18
|
-
getLocalReviewKey,
|
|
19
|
+
reviewToAnalysisId,
|
|
19
20
|
determineCompletionInfo,
|
|
20
21
|
broadcastProgress,
|
|
21
22
|
createProgressCallback
|
|
22
23
|
} = require('./shared');
|
|
23
24
|
|
|
25
|
+
// All valid tier values: canonical tiers + aliases (for Zod enum validation)
|
|
26
|
+
const ALL_TIER_VALUES = /** @type {[string, ...string[]]} */ ([...TIERS, ...Object.keys(TIER_ALIASES)]);
|
|
27
|
+
|
|
24
28
|
const router = express.Router();
|
|
25
29
|
|
|
26
30
|
/**
|
|
@@ -61,6 +65,9 @@ async function handleAnalysisCompletion(analysisId, runId, result, savePersisten
|
|
|
61
65
|
};
|
|
62
66
|
activeAnalyses.set(analysisId, completedStatus);
|
|
63
67
|
broadcastProgress(analysisId, completedStatus);
|
|
68
|
+
if (currentStatus.reviewId) {
|
|
69
|
+
broadcastReviewEvent(currentStatus.reviewId, { type: 'review:analysis_completed' });
|
|
70
|
+
}
|
|
64
71
|
|
|
65
72
|
// Auto-cleanup after 30 minutes
|
|
66
73
|
setTimeout(() => activeAnalyses.delete(analysisId), 30 * 60 * 1000);
|
|
@@ -195,14 +202,15 @@ function createMCPServer(db, options = {}) {
|
|
|
195
202
|
{
|
|
196
203
|
promptType: z.enum(['level1', 'level2', 'level3', 'orchestration'])
|
|
197
204
|
.describe('Analysis level'),
|
|
198
|
-
tier: z.enum(
|
|
205
|
+
tier: z.enum(ALL_TIER_VALUES).default('balanced')
|
|
199
206
|
.describe('Prompt tier — fast (surface), balanced (standard), or thorough (deep)'),
|
|
200
207
|
customInstructions: z.string().max(5000).optional()
|
|
201
208
|
.describe('Optional repo or user-specific review instructions to include'),
|
|
202
209
|
},
|
|
203
210
|
async (args) => {
|
|
204
211
|
try {
|
|
205
|
-
const
|
|
212
|
+
const tier = resolveTier(args.tier);
|
|
213
|
+
const rendered = renderPromptForSkill(args.promptType, tier, {
|
|
206
214
|
customInstructions: args.customInstructions,
|
|
207
215
|
});
|
|
208
216
|
return { content: [{ type: 'text', text: rendered }] };
|
|
@@ -300,6 +308,8 @@ function createMCPServer(db, options = {}) {
|
|
|
300
308
|
id: r.id,
|
|
301
309
|
provider: r.provider,
|
|
302
310
|
model: r.model,
|
|
311
|
+
// Enrich historical runs that predate the tier column (migration 22)
|
|
312
|
+
tier: r.tier ?? (r.provider && r.model ? getTierForModel(r.provider, r.model) : null),
|
|
303
313
|
status: r.status,
|
|
304
314
|
summary: r.summary,
|
|
305
315
|
head_sha: r.head_sha,
|
|
@@ -439,14 +449,13 @@ function createMCPServer(db, options = {}) {
|
|
|
439
449
|
.describe('Optional repo or user-specific review instructions'),
|
|
440
450
|
skipLevel3: z.boolean().default(false)
|
|
441
451
|
.describe('Whether to skip Level 3 (codebase context) analysis'),
|
|
442
|
-
tier: z.enum(
|
|
452
|
+
tier: z.enum(ALL_TIER_VALUES).default('balanced')
|
|
443
453
|
.describe('Analysis tier: fast (surface), balanced (standard), or thorough (deep)'),
|
|
444
454
|
},
|
|
445
455
|
async (args) => {
|
|
446
|
-
// Track analysisId and
|
|
456
|
+
// Track analysisId and reviewId for cleanup in catch block (must be outside try scope)
|
|
447
457
|
let analysisId = null;
|
|
448
|
-
let
|
|
449
|
-
let trackingMap = null;
|
|
458
|
+
let trackingReviewId = null;
|
|
450
459
|
|
|
451
460
|
try {
|
|
452
461
|
const reviewRepo = new ReviewRepository(db);
|
|
@@ -514,8 +523,7 @@ function createMCPServer(db, options = {}) {
|
|
|
514
523
|
});
|
|
515
524
|
|
|
516
525
|
// Concurrent analysis guard: check if one is already running
|
|
517
|
-
const
|
|
518
|
-
const existingAnalysisId = localReviewToAnalysisId.get(reviewKey);
|
|
526
|
+
const existingAnalysisId = reviewToAnalysisId.get(reviewId);
|
|
519
527
|
if (existingAnalysisId && activeAnalyses.get(existingAnalysisId)?.status === 'running') {
|
|
520
528
|
return {
|
|
521
529
|
content: [{
|
|
@@ -540,8 +548,7 @@ function createMCPServer(db, options = {}) {
|
|
|
540
548
|
// Create unified run/analysis ID and DB record immediately
|
|
541
549
|
const runId = uuidv4();
|
|
542
550
|
analysisId = runId;
|
|
543
|
-
|
|
544
|
-
trackingMap = localReviewToAnalysisId;
|
|
551
|
+
trackingReviewId = reviewId;
|
|
545
552
|
|
|
546
553
|
const requestInstructions = args.customInstructions?.trim() || null;
|
|
547
554
|
const repoInstructions = repoSettings?.default_instructions || null;
|
|
@@ -566,10 +573,11 @@ function createMCPServer(db, options = {}) {
|
|
|
566
573
|
};
|
|
567
574
|
activeAnalyses.set(analysisId, initialStatus);
|
|
568
575
|
|
|
569
|
-
// Store
|
|
570
|
-
|
|
576
|
+
// Store review to analysis ID mapping (unified map)
|
|
577
|
+
reviewToAnalysisId.set(reviewId, analysisId);
|
|
571
578
|
|
|
572
579
|
broadcastProgress(analysisId, initialStatus);
|
|
580
|
+
broadcastReviewEvent(reviewId, { type: 'review:analysis_started', analysisId });
|
|
573
581
|
|
|
574
582
|
// Create analyzer and launch asynchronously
|
|
575
583
|
const analyzer = new Analyzer(db, model, provider);
|
|
@@ -592,7 +600,7 @@ function createMCPServer(db, options = {}) {
|
|
|
592
600
|
}
|
|
593
601
|
|
|
594
602
|
const progressCallback = createProgressCallback(analysisId);
|
|
595
|
-
const tier = args.tier;
|
|
603
|
+
const tier = resolveTier(args.tier);
|
|
596
604
|
|
|
597
605
|
logger.log('MCP', `Starting local analysis: review #${reviewId}, runId=${runId}`, 'magenta');
|
|
598
606
|
|
|
@@ -604,6 +612,7 @@ function createMCPServer(db, options = {}) {
|
|
|
604
612
|
reviewId,
|
|
605
613
|
provider,
|
|
606
614
|
model,
|
|
615
|
+
tier,
|
|
607
616
|
repoInstructions,
|
|
608
617
|
requestInstructions,
|
|
609
618
|
headSha: localHeadSha
|
|
@@ -618,7 +627,7 @@ function createMCPServer(db, options = {}) {
|
|
|
618
627
|
}))
|
|
619
628
|
.catch(error => handleAnalysisFailure(analysisId, error, `local review #${reviewId}`))
|
|
620
629
|
.finally(() => {
|
|
621
|
-
|
|
630
|
+
reviewToAnalysisId.delete(reviewId);
|
|
622
631
|
});
|
|
623
632
|
|
|
624
633
|
return {
|
|
@@ -645,18 +654,16 @@ function createMCPServer(db, options = {}) {
|
|
|
645
654
|
const repository = normalizeRepository(owner, repo);
|
|
646
655
|
|
|
647
656
|
// Concurrent analysis guard: check if one is already running
|
|
648
|
-
//
|
|
649
|
-
const
|
|
650
|
-
const existingAnalysisId =
|
|
657
|
+
// First need the review to get the integer reviewId for the unified map
|
|
658
|
+
const existingReviewForGuard = await reviewRepo.getReviewByPR(prNumber, repository);
|
|
659
|
+
const existingAnalysisId = existingReviewForGuard ? reviewToAnalysisId.get(existingReviewForGuard.id) : null;
|
|
651
660
|
if (existingAnalysisId && activeAnalyses.get(existingAnalysisId)?.status === 'running') {
|
|
652
|
-
// Look up the review to return its ID
|
|
653
|
-
const existingReview = await reviewRepo.getReviewByPR(prNumber, repository);
|
|
654
661
|
return {
|
|
655
662
|
content: [{
|
|
656
663
|
type: 'text',
|
|
657
664
|
text: JSON.stringify({
|
|
658
665
|
analysisId: existingAnalysisId,
|
|
659
|
-
reviewId:
|
|
666
|
+
reviewId: existingReviewForGuard?.id || null,
|
|
660
667
|
status: 'already_running',
|
|
661
668
|
message: 'An analysis is already running for this PR'
|
|
662
669
|
}, null, 2)
|
|
@@ -689,8 +696,7 @@ function createMCPServer(db, options = {}) {
|
|
|
689
696
|
// Create unified run/analysis ID and DB record immediately
|
|
690
697
|
const runId = uuidv4();
|
|
691
698
|
analysisId = runId;
|
|
692
|
-
|
|
693
|
-
trackingMap = prToAnalysisId;
|
|
699
|
+
trackingReviewId = review.id;
|
|
694
700
|
|
|
695
701
|
// Save custom instructions if provided
|
|
696
702
|
const requestInstructions = args.customInstructions?.trim() || null;
|
|
@@ -702,6 +708,7 @@ function createMCPServer(db, options = {}) {
|
|
|
702
708
|
|
|
703
709
|
const initialStatus = {
|
|
704
710
|
id: analysisId,
|
|
711
|
+
reviewId: review.id,
|
|
705
712
|
prNumber,
|
|
706
713
|
repository,
|
|
707
714
|
reviewType: 'pr',
|
|
@@ -719,13 +726,14 @@ function createMCPServer(db, options = {}) {
|
|
|
719
726
|
};
|
|
720
727
|
activeAnalyses.set(analysisId, initialStatus);
|
|
721
728
|
|
|
722
|
-
|
|
729
|
+
reviewToAnalysisId.set(review.id, analysisId);
|
|
723
730
|
|
|
724
731
|
broadcastProgress(analysisId, initialStatus);
|
|
732
|
+
broadcastReviewEvent(review.id, { type: 'review:analysis_started', analysisId });
|
|
725
733
|
|
|
726
734
|
const analyzer = new Analyzer(db, model, provider);
|
|
727
735
|
const progressCallback = createProgressCallback(analysisId);
|
|
728
|
-
const tier = args.tier;
|
|
736
|
+
const tier = resolveTier(args.tier);
|
|
729
737
|
|
|
730
738
|
logger.log('MCP', `Starting PR analysis: PR #${prNumber} in ${repository}, runId=${runId}`, 'magenta');
|
|
731
739
|
|
|
@@ -737,6 +745,7 @@ function createMCPServer(db, options = {}) {
|
|
|
737
745
|
reviewId: review.id,
|
|
738
746
|
provider,
|
|
739
747
|
model,
|
|
748
|
+
tier,
|
|
740
749
|
repoInstructions,
|
|
741
750
|
requestInstructions,
|
|
742
751
|
headSha: prMetadata.head_sha || null
|
|
@@ -752,7 +761,7 @@ function createMCPServer(db, options = {}) {
|
|
|
752
761
|
}))
|
|
753
762
|
.catch(error => handleAnalysisFailure(analysisId, error, `PR #${prNumber}`))
|
|
754
763
|
.finally(() => {
|
|
755
|
-
|
|
764
|
+
reviewToAnalysisId.delete(review.id);
|
|
756
765
|
});
|
|
757
766
|
|
|
758
767
|
return {
|
|
@@ -790,8 +799,8 @@ function createMCPServer(db, options = {}) {
|
|
|
790
799
|
await analysisRunRepo.update(analysisId, { status: 'failed' });
|
|
791
800
|
} catch (_) { /* record may not exist yet */ }
|
|
792
801
|
}
|
|
793
|
-
if (
|
|
794
|
-
|
|
802
|
+
if (trackingReviewId != null) {
|
|
803
|
+
reviewToAnalysisId.delete(trackingReviewId);
|
|
795
804
|
}
|
|
796
805
|
|
|
797
806
|
logger.error(`MCP start_analysis error: ${error.message}`);
|