@in-the-loop-labs/pair-review 3.0.6 → 3.1.0
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/package.json +2 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/skills/analyze/references/level1-balanced.md +8 -0
- package/plugin-code-critic/skills/analyze/references/level1-fast.md +7 -0
- package/plugin-code-critic/skills/analyze/references/level1-thorough.md +8 -0
- package/plugin-code-critic/skills/analyze/references/level2-balanced.md +9 -0
- package/plugin-code-critic/skills/analyze/references/level2-fast.md +8 -0
- package/plugin-code-critic/skills/analyze/references/level2-thorough.md +9 -0
- package/plugin-code-critic/skills/analyze/references/level3-balanced.md +9 -0
- package/plugin-code-critic/skills/analyze/references/level3-fast.md +8 -0
- package/plugin-code-critic/skills/analyze/references/level3-thorough.md +9 -0
- package/plugin-code-critic/skills/analyze/references/orchestration-balanced.md +9 -0
- package/plugin-code-critic/skills/analyze/references/orchestration-fast.md +5 -0
- package/plugin-code-critic/skills/analyze/references/orchestration-thorough.md +9 -0
- package/public/css/analysis-config.css +83 -0
- package/public/css/pr.css +191 -4
- package/public/index.html +20 -0
- package/public/js/components/AIPanel.js +1 -1
- package/public/js/components/AdvancedConfigTab.js +83 -8
- package/public/js/components/AnalysisConfigModal.js +155 -5
- package/public/js/components/ChatPanel.js +22 -5
- package/public/js/components/CouncilProgressModal.js +239 -22
- package/public/js/components/TimeoutSelect.js +2 -0
- package/public/js/components/VoiceCentricConfigTab.js +179 -12
- package/public/js/index.js +119 -1
- package/public/js/local.js +141 -47
- package/public/js/modules/suggestion-manager.js +2 -1
- package/public/js/pr.js +71 -12
- package/public/js/repo-settings.js +2 -2
- package/public/local.html +32 -11
- package/public/pr.html +2 -0
- package/src/ai/analyzer.js +371 -111
- package/src/ai/claude-provider.js +2 -0
- package/src/ai/codex-provider.js +1 -1
- package/src/ai/copilot-provider.js +2 -0
- package/src/ai/executable-provider.js +534 -0
- package/src/ai/gemini-provider.js +2 -0
- package/src/ai/index.js +9 -1
- package/src/ai/pi-provider.js +10 -8
- package/src/ai/prompts/baseline/consolidation/balanced.js +54 -2
- package/src/ai/prompts/baseline/consolidation/fast.js +31 -1
- package/src/ai/prompts/baseline/consolidation/thorough.js +46 -3
- package/src/ai/prompts/baseline/level1/balanced.js +12 -0
- package/src/ai/prompts/baseline/level1/fast.js +11 -0
- package/src/ai/prompts/baseline/level1/thorough.js +12 -0
- package/src/ai/prompts/baseline/level2/balanced.js +13 -0
- package/src/ai/prompts/baseline/level2/fast.js +12 -0
- package/src/ai/prompts/baseline/level2/thorough.js +13 -0
- package/src/ai/prompts/baseline/level3/balanced.js +13 -0
- package/src/ai/prompts/baseline/level3/fast.js +12 -0
- package/src/ai/prompts/baseline/level3/thorough.js +13 -0
- package/src/ai/prompts/baseline/orchestration/balanced.js +15 -0
- package/src/ai/prompts/baseline/orchestration/fast.js +11 -0
- package/src/ai/prompts/baseline/orchestration/thorough.js +15 -0
- package/src/ai/prompts/render-for-skill.js +3 -0
- package/src/ai/prompts/shared/output-schema.js +8 -0
- package/src/ai/provider.js +89 -4
- package/src/chat/prompt-builder.js +17 -1
- package/src/chat/session-manager.js +32 -28
- package/src/config.js +15 -2
- package/src/database.js +59 -15
- package/src/git/base-branch.js +113 -29
- package/src/local-review.js +15 -9
- package/src/main.js +3 -2
- package/src/routes/analyses.js +34 -8
- package/src/routes/chat.js +15 -8
- package/src/routes/config.js +3 -120
- package/src/routes/councils.js +15 -6
- package/src/routes/executable-analysis.js +494 -0
- package/src/routes/local.js +152 -15
- package/src/routes/mcp.js +9 -4
- package/src/routes/pr.js +166 -29
- package/src/routes/reviews.js +31 -5
- package/src/routes/shared.js +72 -5
- package/src/routes/worktrees.js +4 -2
- package/src/utils/comment-formatter.js +28 -11
- package/src/utils/instructions.js +22 -8
- package/src/utils/logger.js +20 -10
package/src/routes/local.js
CHANGED
|
@@ -32,8 +32,10 @@ const { getShaAbbrevLength } = require('../git/sha-abbrev');
|
|
|
32
32
|
const { validateCouncilConfig, normalizeCouncilConfig } = require('./councils');
|
|
33
33
|
const { TIERS, TIER_ALIASES, VALID_TIERS, resolveTier } = require('../ai/prompts/config');
|
|
34
34
|
const { getProviderClass, createProvider } = require('../ai/provider');
|
|
35
|
-
const {
|
|
35
|
+
const { readFileSync } = require('fs');
|
|
36
|
+
const { getDefaultBranch, tryGraphiteState, readGraphitePRInfo, enrichStackWithPRInfo } = require('../git/base-branch');
|
|
36
37
|
const { CommentRepository } = require('../database');
|
|
38
|
+
const { runExecutableAnalysis, getChangedFiles } = require('./executable-analysis');
|
|
37
39
|
const {
|
|
38
40
|
activeAnalyses,
|
|
39
41
|
localReviewDiffs,
|
|
@@ -43,7 +45,8 @@ const {
|
|
|
43
45
|
broadcastProgress,
|
|
44
46
|
CancellationError,
|
|
45
47
|
createProgressCallback,
|
|
46
|
-
parseEnabledLevels
|
|
48
|
+
parseEnabledLevels,
|
|
49
|
+
registerProcess: registerProcessForCancellation
|
|
47
50
|
} = require('./shared');
|
|
48
51
|
|
|
49
52
|
const router = express.Router();
|
|
@@ -69,6 +72,26 @@ function deleteLocalReviewDiff(reviewId) {
|
|
|
69
72
|
localReviewDiffs.delete(toIntKey(reviewId));
|
|
70
73
|
}
|
|
71
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Guard: reject the request if the review's scope resolves to zero changed files.
|
|
77
|
+
* Returns true if the guard fired (response already sent), false otherwise.
|
|
78
|
+
*/
|
|
79
|
+
async function rejectIfEmptyScope(res, review, localPath) {
|
|
80
|
+
const scopeContext = {
|
|
81
|
+
scopeStart: review.local_scope_start || DEFAULT_SCOPE.start,
|
|
82
|
+
scopeEnd: review.local_scope_end || DEFAULT_SCOPE.end,
|
|
83
|
+
baseBranch: review.local_base_branch || null,
|
|
84
|
+
};
|
|
85
|
+
const changedFiles = await getChangedFiles(localPath, scopeContext);
|
|
86
|
+
if (changedFiles.length === 0) {
|
|
87
|
+
res.status(409).json({
|
|
88
|
+
error: 'No changes found in the selected scope. Check that your scope includes files with modifications, or adjust the scope range.'
|
|
89
|
+
});
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
72
95
|
/**
|
|
73
96
|
* Check whether branch scope should be selectable in the scope range selector.
|
|
74
97
|
* Returns true when the current branch is a non-default, non-detached branch,
|
|
@@ -599,6 +622,24 @@ router.get('/api/local/:reviewId', async (req, res) => {
|
|
|
599
622
|
|
|
600
623
|
// Compute SHA abbreviation length from the repo's git config
|
|
601
624
|
const shaAbbrevLength = getShaAbbrevLength(review.local_path);
|
|
625
|
+
|
|
626
|
+
// Detect Graphite stack if enabled
|
|
627
|
+
let stackData = null;
|
|
628
|
+
const localConfig = req.app.get('config') || {};
|
|
629
|
+
if (localConfig.enable_graphite === true && review.local_path && branchName && branchName !== 'unknown' && branchName !== 'HEAD') {
|
|
630
|
+
try {
|
|
631
|
+
const graphiteResult = tryGraphiteState(review.local_path, branchName, { execSync, readFileSync });
|
|
632
|
+
if (graphiteResult?.stack) {
|
|
633
|
+
const prInfo = readGraphitePRInfo(review.local_path, { execSync, readFileSync });
|
|
634
|
+
stackData = prInfo?.prInfos
|
|
635
|
+
? enrichStackWithPRInfo(graphiteResult.stack, prInfo.prInfos)
|
|
636
|
+
: graphiteResult.stack;
|
|
637
|
+
}
|
|
638
|
+
} catch {
|
|
639
|
+
// Non-fatal — stack detection is an enhancement
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
602
643
|
const metadataElapsed = Date.now() - tEndpoint;
|
|
603
644
|
if (metadataElapsed > 200) {
|
|
604
645
|
logger.debug(`[perf] metadata#${reviewId} took ${metadataElapsed}ms (threshold: 200ms)`);
|
|
@@ -619,6 +660,7 @@ router.get('/api/local/:reviewId', async (req, res) => {
|
|
|
619
660
|
baseBranch,
|
|
620
661
|
branchInfo,
|
|
621
662
|
branchAvailable,
|
|
663
|
+
stackData,
|
|
622
664
|
shaAbbrevLength,
|
|
623
665
|
createdAt: review.created_at,
|
|
624
666
|
updatedAt: review.updated_at
|
|
@@ -742,18 +784,20 @@ router.get('/api/local/:reviewId/diff', async (req, res) => {
|
|
|
742
784
|
});
|
|
743
785
|
}
|
|
744
786
|
|
|
745
|
-
// When ?w=1
|
|
787
|
+
// When ?w=1 or ?base=<branch>, regenerate the diff (transient view, not cached)
|
|
746
788
|
const hideWhitespace = req.query.w === '1';
|
|
789
|
+
const baseBranchOverride = req.query.base;
|
|
747
790
|
const scopeStart = review.local_scope_start || DEFAULT_SCOPE.start;
|
|
748
791
|
const scopeEnd = review.local_scope_end || DEFAULT_SCOPE.end;
|
|
792
|
+
const baseBranch = baseBranchOverride || review.local_base_branch;
|
|
749
793
|
let diffData;
|
|
750
794
|
|
|
751
|
-
if (hideWhitespace && review.local_path) {
|
|
795
|
+
if ((hideWhitespace || baseBranchOverride) && review.local_path) {
|
|
752
796
|
try {
|
|
753
|
-
const wsResult = await generateScopedDiff(review.local_path, scopeStart, scopeEnd,
|
|
797
|
+
const wsResult = await generateScopedDiff(review.local_path, scopeStart, scopeEnd, baseBranch, { hideWhitespace });
|
|
754
798
|
diffData = { diff: wsResult.diff, stats: wsResult.stats };
|
|
755
799
|
} catch (wsError) {
|
|
756
|
-
logger.warn(`Could not generate
|
|
800
|
+
logger.warn(`Could not generate diff for review #${reviewId}: ${wsError.message}`);
|
|
757
801
|
// Fall through to cached diff below
|
|
758
802
|
}
|
|
759
803
|
}
|
|
@@ -970,6 +1014,63 @@ router.get('/api/local/:reviewId/check-stale', async (req, res) => {
|
|
|
970
1014
|
}
|
|
971
1015
|
});
|
|
972
1016
|
|
|
1017
|
+
/**
|
|
1018
|
+
* Handle analysis for executable providers (external CLI tools).
|
|
1019
|
+
* Spawns the external CLI, maps its output to suggestions, and stores results.
|
|
1020
|
+
*/
|
|
1021
|
+
async function handleExecutableAnalysis(req, res, {
|
|
1022
|
+
reviewId, review, localPath, repository, selectedProvider, selectedModel,
|
|
1023
|
+
repoInstructions, requestInstructions, combinedInstructions, runId, analysisId, reviewRepo
|
|
1024
|
+
}) {
|
|
1025
|
+
return runExecutableAnalysis(req, res, {
|
|
1026
|
+
reviewId,
|
|
1027
|
+
review,
|
|
1028
|
+
selectedProvider,
|
|
1029
|
+
selectedModel,
|
|
1030
|
+
repoInstructions,
|
|
1031
|
+
requestInstructions,
|
|
1032
|
+
runId,
|
|
1033
|
+
analysisId,
|
|
1034
|
+
repository,
|
|
1035
|
+
reviewType: review.review_type || 'local',
|
|
1036
|
+
headSha: review.local_head_sha
|
|
1037
|
+
}, {
|
|
1038
|
+
activeAnalyses,
|
|
1039
|
+
reviewToAnalysisId,
|
|
1040
|
+
broadcastProgress,
|
|
1041
|
+
broadcastReviewEvent,
|
|
1042
|
+
registerProcessForCancellation
|
|
1043
|
+
}, {
|
|
1044
|
+
logLabel: `Review #${reviewId}`,
|
|
1045
|
+
buildContext: (r, { selectedModel: model, requestInstructions: customInstructions }) => ({
|
|
1046
|
+
title: null,
|
|
1047
|
+
description: null,
|
|
1048
|
+
cwd: localPath,
|
|
1049
|
+
model,
|
|
1050
|
+
baseSha: null,
|
|
1051
|
+
headSha: r.local_head_sha || null,
|
|
1052
|
+
baseBranch: r.local_base_branch || null,
|
|
1053
|
+
headBranch: r.local_head_branch || null,
|
|
1054
|
+
scopeStart: r.local_scope_start || DEFAULT_SCOPE.start,
|
|
1055
|
+
scopeEnd: r.local_scope_end || DEFAULT_SCOPE.end,
|
|
1056
|
+
customInstructions: customInstructions || null
|
|
1057
|
+
}),
|
|
1058
|
+
buildHookPayload: () => ({
|
|
1059
|
+
mode: review.review_type || 'local',
|
|
1060
|
+
localContext: { path: localPath, branch: review.local_head_branch, headSha: review.local_head_sha }
|
|
1061
|
+
}),
|
|
1062
|
+
onSuccess: async (_db, _runId, { summary }) => {
|
|
1063
|
+
if (summary) {
|
|
1064
|
+
try {
|
|
1065
|
+
await reviewRepo.updateSummary(reviewId, summary);
|
|
1066
|
+
} catch (e) {
|
|
1067
|
+
logger.warn(`Failed to save summary: ${e.message}`);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
|
|
973
1074
|
/**
|
|
974
1075
|
* Start Level 1 AI analysis for local review
|
|
975
1076
|
*/
|
|
@@ -984,7 +1085,7 @@ router.post('/api/local/:reviewId/analyses', async (req, res) => {
|
|
|
984
1085
|
}
|
|
985
1086
|
|
|
986
1087
|
// Extract optional provider, model, tier, customInstructions and skipLevel3 from request body
|
|
987
|
-
const { provider: requestProvider, model: requestModel, tier: requestTier, customInstructions: rawInstructions, skipLevel3: requestSkipLevel3, enabledLevels: requestEnabledLevels } = req.body || {};
|
|
1088
|
+
const { provider: requestProvider, model: requestModel, tier: requestTier, customInstructions: rawInstructions, skipLevel3: requestSkipLevel3, enabledLevels: requestEnabledLevels, excludePrevious } = req.body || {};
|
|
988
1089
|
|
|
989
1090
|
// Trim and validate custom instructions
|
|
990
1091
|
const MAX_INSTRUCTIONS_LENGTH = 5000;
|
|
@@ -1015,10 +1116,15 @@ router.post('/api/local/:reviewId/analyses', async (req, res) => {
|
|
|
1015
1116
|
const localPath = review.local_path;
|
|
1016
1117
|
const repository = review.repository;
|
|
1017
1118
|
|
|
1119
|
+
// Guard: reject if scope resolves to zero changed files
|
|
1120
|
+
if (await rejectIfEmptyScope(res, review, localPath)) return;
|
|
1121
|
+
|
|
1018
1122
|
// Fetch repo settings for default instructions
|
|
1019
1123
|
const repoSettingsRepo = new RepoSettingsRepository(db);
|
|
1020
1124
|
const repoSettings = repository ? await repoSettingsRepo.getRepoSettings(repository) : null;
|
|
1021
1125
|
|
|
1126
|
+
const appConfig = req.app.get('config') || {};
|
|
1127
|
+
|
|
1022
1128
|
// Determine provider: request body > repo settings > config > default ('claude')
|
|
1023
1129
|
let selectedProvider;
|
|
1024
1130
|
if (requestProvider) {
|
|
@@ -1026,8 +1132,7 @@ router.post('/api/local/:reviewId/analyses', async (req, res) => {
|
|
|
1026
1132
|
} else if (repoSettings && repoSettings.default_provider) {
|
|
1027
1133
|
selectedProvider = repoSettings.default_provider;
|
|
1028
1134
|
} else {
|
|
1029
|
-
|
|
1030
|
-
selectedProvider = config.default_provider || config.provider || 'claude';
|
|
1135
|
+
selectedProvider = appConfig.default_provider || appConfig.provider || 'claude';
|
|
1031
1136
|
}
|
|
1032
1137
|
|
|
1033
1138
|
// Determine model: request body > repo settings > config/CLI > default
|
|
@@ -1042,8 +1147,10 @@ router.post('/api/local/:reviewId/analyses', async (req, res) => {
|
|
|
1042
1147
|
|
|
1043
1148
|
// Get repo instructions from settings
|
|
1044
1149
|
const repoInstructions = repoSettings?.default_instructions || null;
|
|
1150
|
+
// Get global instructions from config (loaded at startup from ~/.pair-review/global-instructions.md)
|
|
1151
|
+
const globalInstructions = appConfig.globalInstructions || null;
|
|
1045
1152
|
// Merge for logging purposes (analyzer will also merge internally)
|
|
1046
|
-
const combinedInstructions = mergeInstructions(repoInstructions, requestInstructions);
|
|
1153
|
+
const combinedInstructions = mergeInstructions({ globalInstructions, repoInstructions, requestInstructions });
|
|
1047
1154
|
|
|
1048
1155
|
// Save custom instructions to the review record
|
|
1049
1156
|
// Only update when requestInstructions has a value - updateReview would accept
|
|
@@ -1058,6 +1165,25 @@ router.post('/api/local/:reviewId/analyses', async (req, res) => {
|
|
|
1058
1165
|
const runId = uuidv4();
|
|
1059
1166
|
const analysisId = runId;
|
|
1060
1167
|
|
|
1168
|
+
// Check if selected provider is an executable provider (external tool)
|
|
1169
|
+
const ProviderClass = getProviderClass(selectedProvider);
|
|
1170
|
+
if (ProviderClass?.isExecutable) {
|
|
1171
|
+
return handleExecutableAnalysis(req, res, {
|
|
1172
|
+
reviewId,
|
|
1173
|
+
review,
|
|
1174
|
+
localPath,
|
|
1175
|
+
repository,
|
|
1176
|
+
selectedProvider,
|
|
1177
|
+
selectedModel,
|
|
1178
|
+
repoInstructions,
|
|
1179
|
+
requestInstructions,
|
|
1180
|
+
combinedInstructions,
|
|
1181
|
+
runId,
|
|
1182
|
+
analysisId,
|
|
1183
|
+
reviewRepo
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1061
1187
|
// Extract scope early — needed for both analysis run creation and diff generation
|
|
1062
1188
|
const scopeStart = review.local_scope_start || DEFAULT_SCOPE.start;
|
|
1063
1189
|
const scopeEnd = review.local_scope_end || DEFAULT_SCOPE.end;
|
|
@@ -1073,6 +1199,7 @@ router.post('/api/local/:reviewId/analyses', async (req, res) => {
|
|
|
1073
1199
|
provider: selectedProvider,
|
|
1074
1200
|
model: selectedModel,
|
|
1075
1201
|
tier,
|
|
1202
|
+
globalInstructions,
|
|
1076
1203
|
repoInstructions,
|
|
1077
1204
|
requestInstructions,
|
|
1078
1205
|
headSha: review.local_head_sha || null,
|
|
@@ -1178,7 +1305,7 @@ router.post('/api/local/:reviewId/analyses', async (req, res) => {
|
|
|
1178
1305
|
const progressCallback = createProgressCallback(analysisId);
|
|
1179
1306
|
|
|
1180
1307
|
// Start analysis asynchronously (skipRunCreation since we created the record above; also passes changedFiles for local mode path validation, tier for prompt selection, and skipLevel3 flag)
|
|
1181
|
-
analyzer.analyzeLevel1(reviewId, localPath, localMetadata, progressCallback, { repoInstructions, requestInstructions }, changedFiles, { analysisId, runId, skipRunCreation: true, tier, skipLevel3: requestSkipLevel3, enabledLevels: levelsConfig })
|
|
1308
|
+
analyzer.analyzeLevel1(reviewId, localPath, localMetadata, progressCallback, { globalInstructions, repoInstructions, requestInstructions }, changedFiles, { analysisId, runId, skipRunCreation: true, tier, skipLevel3: requestSkipLevel3, enabledLevels: levelsConfig, excludePrevious, serverPort: req.socket.localPort })
|
|
1182
1309
|
.then(async result => {
|
|
1183
1310
|
logger.section('Local Analysis Results');
|
|
1184
1311
|
logger.success(`Analysis complete for local review #${reviewId}`);
|
|
@@ -1848,7 +1975,7 @@ router.post('/api/local/:reviewId/review-settings', async (req, res) => {
|
|
|
1848
1975
|
router.post('/api/local/:reviewId/analyses/council', async (req, res) => {
|
|
1849
1976
|
try {
|
|
1850
1977
|
const reviewId = parseInt(req.params.reviewId, 10);
|
|
1851
|
-
const { councilId, councilConfig: inlineConfig, customInstructions: rawInstructions, configType: requestConfigType } = req.body || {};
|
|
1978
|
+
const { councilId, councilConfig: inlineConfig, customInstructions: rawInstructions, configType: requestConfigType, excludePrevious } = req.body || {};
|
|
1852
1979
|
|
|
1853
1980
|
if (isNaN(reviewId) || reviewId <= 0) {
|
|
1854
1981
|
return res.status(400).json({ error: 'Invalid review ID' });
|
|
@@ -1891,6 +2018,9 @@ router.post('/api/local/:reviewId/analyses/council', async (req, res) => {
|
|
|
1891
2018
|
|
|
1892
2019
|
const localPath = review.local_path;
|
|
1893
2020
|
|
|
2021
|
+
// Guard: reject if scope resolves to zero changed files
|
|
2022
|
+
if (await rejectIfEmptyScope(res, review, localPath)) return;
|
|
2023
|
+
|
|
1894
2024
|
const councilScopeStart = review.local_scope_start || DEFAULT_SCOPE.start;
|
|
1895
2025
|
const councilScopeEnd = review.local_scope_end || DEFAULT_SCOPE.end;
|
|
1896
2026
|
const councilHasBranch = includesBranch(councilScopeStart);
|
|
@@ -1911,7 +2041,11 @@ router.post('/api/local/:reviewId/analyses/council', async (req, res) => {
|
|
|
1911
2041
|
title: review.name || (councilHasBranch ? `Branch changes: ${review.local_base_branch}..HEAD` : 'Local changes'),
|
|
1912
2042
|
description: '',
|
|
1913
2043
|
base_sha: analysisBaseSha,
|
|
1914
|
-
head_sha: review.local_head_sha
|
|
2044
|
+
head_sha: review.local_head_sha,
|
|
2045
|
+
base_branch: review.local_base_branch || null,
|
|
2046
|
+
head_branch: review.local_head_branch || null,
|
|
2047
|
+
scopeStart: councilScopeStart,
|
|
2048
|
+
scopeEnd: councilScopeEnd,
|
|
1915
2049
|
};
|
|
1916
2050
|
|
|
1917
2051
|
const analyzer = new Analyzer(db, 'council', 'council');
|
|
@@ -1944,6 +2078,7 @@ router.post('/api/local/:reviewId/analyses/council', async (req, res) => {
|
|
|
1944
2078
|
|
|
1945
2079
|
// Import launchCouncilAnalysis from analyses.js
|
|
1946
2080
|
const analysesRouter = require('./analyses');
|
|
2081
|
+
const localCouncilConfig = req.app.get('config') || {};
|
|
1947
2082
|
const { analysisId, runId } = await analysesRouter.launchCouncilAnalysis(
|
|
1948
2083
|
db,
|
|
1949
2084
|
{
|
|
@@ -1955,7 +2090,9 @@ router.post('/api/local/:reviewId/analyses/council', async (req, res) => {
|
|
|
1955
2090
|
headSha: review.local_head_sha,
|
|
1956
2091
|
logLabel: `local review #${reviewId}`,
|
|
1957
2092
|
initialStatusExtra: { reviewId, reviewType: 'local' },
|
|
1958
|
-
config:
|
|
2093
|
+
config: localCouncilConfig,
|
|
2094
|
+
excludePrevious,
|
|
2095
|
+
serverPort: req.socket.localPort,
|
|
1959
2096
|
hookContext: {
|
|
1960
2097
|
mode: 'local',
|
|
1961
2098
|
localContext: { path: localPath, branch: review.local_head_branch, headSha: review.local_head_sha },
|
|
@@ -1964,7 +2101,7 @@ router.post('/api/local/:reviewId/analyses/council', async (req, res) => {
|
|
|
1964
2101
|
},
|
|
1965
2102
|
councilConfig,
|
|
1966
2103
|
councilId,
|
|
1967
|
-
{ repoInstructions, requestInstructions },
|
|
2104
|
+
{ globalInstructions: localCouncilConfig.globalInstructions || null, repoInstructions, requestInstructions },
|
|
1968
2105
|
configType
|
|
1969
2106
|
);
|
|
1970
2107
|
|
package/src/routes/mcp.js
CHANGED
|
@@ -411,7 +411,7 @@ function createMCPServer(db, options = {}) {
|
|
|
411
411
|
SELECT
|
|
412
412
|
id, ai_run_id, ai_level, ai_confidence,
|
|
413
413
|
file, line_start, line_end, type, title, body,
|
|
414
|
-
reasoning, status, is_file_level, created_at
|
|
414
|
+
reasoning, status, is_file_level, severity, created_at
|
|
415
415
|
FROM comments
|
|
416
416
|
WHERE ${conditions.join('\n AND ')}
|
|
417
417
|
ORDER BY file, line_start
|
|
@@ -433,6 +433,7 @@ function createMCPServer(db, options = {}) {
|
|
|
433
433
|
body: s.body,
|
|
434
434
|
type: s.type,
|
|
435
435
|
ai_confidence: s.ai_confidence,
|
|
436
|
+
severity: s.severity,
|
|
436
437
|
status: s.status,
|
|
437
438
|
reasoning: safeParseJson(s.reasoning),
|
|
438
439
|
}))
|
|
@@ -457,6 +458,10 @@ function createMCPServer(db, options = {}) {
|
|
|
457
458
|
.describe('Whether to skip Level 3 (codebase context) analysis'),
|
|
458
459
|
tier: z.enum(ALL_TIER_VALUES).default('balanced')
|
|
459
460
|
.describe('Analysis tier: fast (surface), balanced (standard), or thorough (deep)'),
|
|
461
|
+
excludePrevious: z.object({
|
|
462
|
+
github: z.boolean().optional().describe('Exclude GitHub PR inline review comments'),
|
|
463
|
+
feedback: z.boolean().optional().describe('Exclude existing pair-review suggestions and comments')
|
|
464
|
+
}).optional().describe('Exclude previously identified issues from results'),
|
|
460
465
|
},
|
|
461
466
|
async (args) => {
|
|
462
467
|
// Track analysisId and reviewId for cleanup in catch block (must be outside try scope)
|
|
@@ -630,7 +635,7 @@ function createMCPServer(db, options = {}) {
|
|
|
630
635
|
});
|
|
631
636
|
|
|
632
637
|
// Launch analysis asynchronously (skipRunCreation since we created the record above)
|
|
633
|
-
analyzer.analyzeLevel1(reviewId, localPath, localMetadata, progressCallback, { repoInstructions, requestInstructions }, changedFiles, { analysisId, runId, skipRunCreation: true, tier, skipLevel3: args.skipLevel3 })
|
|
638
|
+
analyzer.analyzeLevel1(reviewId, localPath, localMetadata, progressCallback, { repoInstructions, requestInstructions }, changedFiles, { analysisId, runId, skipRunCreation: true, tier, skipLevel3: args.skipLevel3, excludePrevious: args.excludePrevious, serverPort: (options.port || config.port || 7247) })
|
|
634
639
|
.then(result => handleAnalysisCompletion(analysisId, runId, result, async (r) => {
|
|
635
640
|
if (r.summary) {
|
|
636
641
|
try { await reviewRepo.updateSummary(reviewId, r.summary); } catch (_) { /* ignore */ }
|
|
@@ -763,7 +768,7 @@ function createMCPServer(db, options = {}) {
|
|
|
763
768
|
});
|
|
764
769
|
|
|
765
770
|
// Launch analysis asynchronously (skipRunCreation since we created the record above)
|
|
766
|
-
analyzer.analyzeLevel1(review.id, worktreePath, prMetadata, progressCallback, { repoInstructions, requestInstructions }, null, { analysisId, runId, skipRunCreation: true, tier, skipLevel3: args.skipLevel3 })
|
|
771
|
+
analyzer.analyzeLevel1(review.id, worktreePath, prMetadata, progressCallback, { repoInstructions, requestInstructions }, null, { analysisId, runId, skipRunCreation: true, tier, skipLevel3: args.skipLevel3, excludePrevious: args.excludePrevious, serverPort: (options.port || config.port || 7247) })
|
|
767
772
|
.then(result => handleAnalysisCompletion(analysisId, runId, result, async (r) => {
|
|
768
773
|
try { await prMetadataRepo.updateLastAiRunId(prMetadata.id, r.runId); } catch (_) { /* ignore */ }
|
|
769
774
|
if (r.summary) {
|
|
@@ -837,7 +842,7 @@ router.post('/mcp', async (req, res) => {
|
|
|
837
842
|
try {
|
|
838
843
|
const db = req.app.get('db');
|
|
839
844
|
const config = req.app.get('config') || {};
|
|
840
|
-
const server = createMCPServer(db, { config });
|
|
845
|
+
const server = createMCPServer(db, { config, port: req.socket.localPort });
|
|
841
846
|
|
|
842
847
|
const transport = new StreamableHTTPServerTransport({
|
|
843
848
|
sessionIdGenerator: undefined,
|