@in-the-loop-labs/pair-review 3.6.0 → 3.7.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 +4 -0
- package/package.json +20 -15
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/skills/analyze/scripts/git-diff-lines +0 -0
- package/public/css/analysis-config.css +1807 -0
- package/public/css/pr.css +17 -1737
- package/public/index.html +11 -0
- package/public/js/components/AIPanel.js +89 -44
- package/public/js/components/AdvancedConfigTab.js +56 -4
- package/public/js/components/AnalysisConfigModal.js +41 -25
- package/public/js/components/ChatPanel.js +11 -1
- package/public/js/components/ReviewModal.js +135 -13
- package/public/js/components/SuggestionNavigator.js +55 -10
- package/public/js/components/VoiceCentricConfigTab.js +36 -0
- package/public/js/index.js +175 -16
- package/public/js/local.js +58 -8
- package/public/js/modules/suggestion-manager.js +25 -1
- package/public/js/modules/tour-renderer.js +45 -5
- package/public/js/pr.js +703 -171
- package/public/js/repo-links.js +328 -0
- package/public/js/utils/provider-model.js +88 -0
- package/public/js/utils/scroll-into-view.js +164 -0
- package/public/js/utils/storage-keys.js +50 -0
- package/public/local.html +10 -0
- package/public/pr.html +10 -0
- package/public/repo-settings.html +1 -0
- package/public/setup.html +2 -0
- package/src/ai/analyzer.js +125 -18
- package/src/ai/claude-provider.js +31 -3
- package/src/config.js +664 -10
- package/src/external/github-adapter.js +114 -25
- package/src/git/base-branch.js +11 -4
- package/src/github/client.js +482 -588
- package/src/github/errors.js +55 -0
- package/src/github/impl/graphql/pending-review-comments.js +230 -0
- package/src/github/impl/graphql/pending-review.js +153 -0
- package/src/github/impl/graphql/review-lifecycle.js +161 -0
- package/src/github/impl/graphql/stack-walker.js +210 -0
- package/src/github/impl/host/pending-review-comments.js +338 -0
- package/src/github/impl/rest/pending-review.js +251 -0
- package/src/github/impl/rest/review-lifecycle.js +226 -0
- package/src/github/impl/rest/stack-walker.js +309 -0
- package/src/github/operations/pending-review-comments.js +79 -0
- package/src/github/operations/pending-review.js +89 -0
- package/src/github/operations/review-lifecycle.js +126 -0
- package/src/github/operations/stack-walker.js +87 -0
- package/src/github/parser.js +230 -4
- package/src/github/stack-walker.js +14 -189
- package/src/links/repo-links.js +230 -0
- package/src/local-review.js +13 -4
- package/src/main.js +136 -32
- package/src/routes/analyses.js +30 -7
- package/src/routes/bulk-analysis-configs.js +295 -0
- package/src/routes/config.js +102 -2
- package/src/routes/external-comments.js +20 -10
- package/src/routes/github-collections.js +3 -1
- package/src/routes/local.js +101 -11
- package/src/routes/mcp.js +47 -4
- package/src/routes/pr.js +298 -68
- package/src/routes/setup.js +8 -3
- package/src/routes/stack-analysis.js +33 -9
- package/src/routes/worktrees.js +3 -2
- package/src/server.js +2 -0
- package/src/setup/pr-setup.js +37 -11
- package/src/setup/stack-setup.js +13 -3
- package/src/single-port.js +6 -3
package/public/pr.html
CHANGED
|
@@ -398,6 +398,10 @@
|
|
|
398
398
|
<!-- Tier icons utility -->
|
|
399
399
|
<script src="/js/utils/tier-icons.js"></script>
|
|
400
400
|
|
|
401
|
+
<!-- Shared storage-key and provider/model helpers -->
|
|
402
|
+
<script src="/js/utils/storage-keys.js"></script>
|
|
403
|
+
<script src="/js/utils/provider-model.js"></script>
|
|
404
|
+
|
|
401
405
|
<!-- Category emoji mapping -->
|
|
402
406
|
<script src="/js/utils/category-emoji.js"></script>
|
|
403
407
|
|
|
@@ -413,6 +417,9 @@
|
|
|
413
417
|
<!-- Modal detection (shared by KeyboardShortcuts and PRManager) -->
|
|
414
418
|
<script src="/js/utils/modal-detection.js"></script>
|
|
415
419
|
|
|
420
|
+
<!-- Stable scroll-into-view for lazily rendered diff bodies -->
|
|
421
|
+
<script src="/js/utils/scroll-into-view.js"></script>
|
|
422
|
+
|
|
416
423
|
<!-- WebSocket client -->
|
|
417
424
|
<script src="/js/ws-client.js"></script>
|
|
418
425
|
|
|
@@ -475,6 +482,9 @@
|
|
|
475
482
|
pr.js / AIPanel initialise. Must load BEFORE pr.js. -->
|
|
476
483
|
<script src="/runtime-config.js"></script>
|
|
477
484
|
|
|
485
|
+
<!-- Repo Links UI (must load before pr.js) -->
|
|
486
|
+
<script src="/js/repo-links.js"></script>
|
|
487
|
+
|
|
478
488
|
<!-- PR JavaScript -->
|
|
479
489
|
<script src="/js/pr.js"></script>
|
|
480
490
|
</body>
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
<title>Repository Settings - Pair Review</title>
|
|
14
14
|
<link rel="icon" type="image/png" href="/favicon.png">
|
|
15
15
|
<link rel="stylesheet" href="/css/pr.css">
|
|
16
|
+
<link rel="stylesheet" href="/css/analysis-config.css">
|
|
16
17
|
<link rel="stylesheet" href="/css/repo-settings.css">
|
|
17
18
|
</head>
|
|
18
19
|
<body>
|
package/public/setup.html
CHANGED
|
@@ -769,6 +769,7 @@
|
|
|
769
769
|
var targetUrl = new URL(data.reviewUrl, window.location.origin);
|
|
770
770
|
var qs = new URLSearchParams(window.location.search);
|
|
771
771
|
if (qs.get('analyze') === 'true') targetUrl.searchParams.set('analyze', qs.get('analyze'));
|
|
772
|
+
if (qs.get('analysisConfigId')) targetUrl.searchParams.set('analysisConfigId', qs.get('analysisConfigId'));
|
|
772
773
|
window.location.href = targetUrl.toString();
|
|
773
774
|
return;
|
|
774
775
|
}
|
|
@@ -829,6 +830,7 @@
|
|
|
829
830
|
var targetUrl = new URL(msg.reviewUrl, window.location.origin);
|
|
830
831
|
var qs = new URLSearchParams(window.location.search);
|
|
831
832
|
if (qs.get('analyze') === 'true') targetUrl.searchParams.set('analyze', qs.get('analyze'));
|
|
833
|
+
if (qs.get('analysisConfigId')) targetUrl.searchParams.set('analysisConfigId', qs.get('analysisConfigId'));
|
|
832
834
|
window.location.href = targetUrl.toString();
|
|
833
835
|
}
|
|
834
836
|
}, 400);
|
package/src/ai/analyzer.js
CHANGED
|
@@ -215,11 +215,79 @@ function buildDedupContext(prMetadata, { reviewId, serverPort, runId, excludeRun
|
|
|
215
215
|
return { owner, repo, pullNumber: prMetadata.pr_number, reviewId, serverPort, runId, excludeRunIds };
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Fetch existing PR review comments via the injected GitHubClient/Octokit.
|
|
220
|
+
*
|
|
221
|
+
* Replaces the previous `gh api repos/.../comments --paginate` shell-out so the
|
|
222
|
+
* analyzer no longer depends on the `gh` CLI. The result is embedded directly
|
|
223
|
+
* in the dedup prompt section, removing the need for the AI to spawn a process
|
|
224
|
+
* to fetch the data.
|
|
225
|
+
*
|
|
226
|
+
* Pagination is delegated to Octokit's `paginate` helper so PRs with more than
|
|
227
|
+
* 100 comments are handled transparently. The Octokit instance is taken from
|
|
228
|
+
* the caller-supplied `GitHubClient` so per-repo host bindings (api host,
|
|
229
|
+
* token) are honoured.
|
|
230
|
+
*
|
|
231
|
+
* @param {Object} githubClient - GitHubClient instance (must expose `.octokit`)
|
|
232
|
+
* @param {Object} target - { owner, repo, pullNumber }
|
|
233
|
+
* @param {string} [logPrefix=''] - Log prefix for traceability
|
|
234
|
+
* @returns {Promise<Array<{path: string, line: number|null, start_line: number|null, original_line: number|null, original_start_line: number|null, body: string}>|null>}
|
|
235
|
+
* Array of simplified comment objects, or `null` if the fetch failed or the
|
|
236
|
+
* client/target was incomplete.
|
|
237
|
+
*/
|
|
238
|
+
async function fetchExistingReviewComments(githubClient, target, logPrefix = '') {
|
|
239
|
+
if (!githubClient || !githubClient.octokit) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
const { owner, repo, pullNumber } = target || {};
|
|
243
|
+
if (!owner || !repo || !pullNumber) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
logger.info(`${logPrefix}[Dedup] Fetching existing PR review comments for ${owner}/${repo}#${pullNumber} via Octokit`);
|
|
249
|
+
const comments = await githubClient.octokit.paginate(
|
|
250
|
+
githubClient.octokit.rest.pulls.listReviewComments,
|
|
251
|
+
{
|
|
252
|
+
owner,
|
|
253
|
+
repo,
|
|
254
|
+
pull_number: pullNumber,
|
|
255
|
+
per_page: 100
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const simplified = (comments || []).map(c => ({
|
|
260
|
+
path: c.path,
|
|
261
|
+
line: c.line ?? null,
|
|
262
|
+
start_line: c.start_line ?? null,
|
|
263
|
+
original_line: c.original_line ?? null,
|
|
264
|
+
original_start_line: c.original_start_line ?? null,
|
|
265
|
+
body: c.body || ''
|
|
266
|
+
}));
|
|
267
|
+
|
|
268
|
+
logger.info(`${logPrefix}[Dedup] Fetched ${simplified.length} existing review comment(s) for dedup`);
|
|
269
|
+
return simplified;
|
|
270
|
+
} catch (err) {
|
|
271
|
+
logger.warn(`${logPrefix}[Dedup] Failed to fetch existing review comments: ${err.message}`);
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
218
276
|
/**
|
|
219
277
|
* Build dedup instructions text for excluding previously identified issues.
|
|
220
278
|
*
|
|
279
|
+
* The GitHub section is rendered when `context.githubComments` is a non-empty
|
|
280
|
+
* array of pre-fetched comments (see `fetchExistingReviewComments`). The
|
|
281
|
+
* comments are embedded as JSON directly in the prompt so the AI does not
|
|
282
|
+
* need to spawn `gh` (which is unavailable on alt-hosts) to fetch them.
|
|
283
|
+
*
|
|
284
|
+
* If `excludePrevious.github` is set but no comments were supplied (no client,
|
|
285
|
+
* empty list, or fetch failed), the GitHub section is silently omitted — a
|
|
286
|
+
* warning will already have been logged at fetch time.
|
|
287
|
+
*
|
|
221
288
|
* @param {Object|null} excludePrevious - { github: bool, feedback: bool } (or falsy if disabled)
|
|
222
|
-
* @param {Object} context - {
|
|
289
|
+
* @param {Object} context - { reviewId, serverPort, runId, excludeRunIds, githubComments }
|
|
290
|
+
* @param {Array} [context.githubComments] - Pre-fetched PR review comments (path, line, original_line, body)
|
|
223
291
|
* @param {string} [context.runId] - Single run ID to exclude (backward compat)
|
|
224
292
|
* @param {string[]} [context.excludeRunIds] - Array of run IDs to exclude (takes precedence over runId)
|
|
225
293
|
* @returns {string} Instruction text for the dedup-instructions prompt section, or empty string
|
|
@@ -236,13 +304,13 @@ function buildDedupInstructions(excludePrevious, context) {
|
|
|
236
304
|
|
|
237
305
|
After consolidating suggestions, check your results against previously identified issues and remove any that are duplicates or substantially similar. If you have zero suggestions after consolidation, skip this step entirely.`);
|
|
238
306
|
|
|
239
|
-
|
|
307
|
+
const githubComments = Array.isArray(context.githubComments) ? context.githubComments : null;
|
|
308
|
+
if (excludePrevious.github && githubComments && githubComments.length > 0) {
|
|
240
309
|
sections.push(`### GitHub PR Review Comments
|
|
241
|
-
|
|
242
|
-
\`\`\`
|
|
243
|
-
|
|
310
|
+
The following inline review comments already exist on this pull request. Each entry has \`path\` (file), \`line\`/\`original_line\` (end line), \`start_line\`/\`original_start_line\` (start line for multi-line comments; null for single-line comments), and \`body\` (content):
|
|
311
|
+
\`\`\`json
|
|
312
|
+
${JSON.stringify(githubComments, null, 2)}
|
|
244
313
|
\`\`\`
|
|
245
|
-
Each comment has \`path\` (file), \`line\`/\`original_line\` (line number), and \`body\` (content).
|
|
246
314
|
A suggestion is a duplicate if it matches on **all three** of: (1) same file, (2) overlapping or adjacent line range (within 5 lines), and (3) substantially similar issue — i.e., the same category of issue (error handling, validation, naming, etc.) applied to the same code. If a previous comment partially overlaps your suggestion — e.g., it flags missing error handling while your suggestion flags missing error handling *and* input validation — keep only the novel portion that the previous comment does not address. If there is no novel portion, exclude it entirely.`);
|
|
247
315
|
}
|
|
248
316
|
|
|
@@ -321,11 +389,12 @@ class Analyzer {
|
|
|
321
389
|
* @param {string} [options.tier='balanced'] - Analysis tier (fast, balanced, thorough)
|
|
322
390
|
* @param {Object} [options.excludePrevious] - { github: bool, feedback: bool } for dedup
|
|
323
391
|
* @param {number} [options.serverPort] - Server port for dedup API calls
|
|
392
|
+
* @param {Object} [options.githubClient] - GitHubClient used to pre-fetch existing PR review comments for dedup (PR mode only)
|
|
324
393
|
* @returns {Promise<Object>} Analysis results
|
|
325
394
|
*/
|
|
326
395
|
async analyzeAllLevels(prId, worktreePath, prMetadata, progressCallback = null, instructions = null, changedFiles = null, options = {}) {
|
|
327
396
|
const runId = options.runId || uuidv4();
|
|
328
|
-
const { analysisId, skipRunCreation, skipLevel3, reviewerNum, excludePrevious, serverPort } = options;
|
|
397
|
+
const { analysisId, skipRunCreation, skipLevel3, reviewerNum, excludePrevious, serverPort, githubClient } = options;
|
|
329
398
|
const logPrefix = options.logPrefix || '';
|
|
330
399
|
// Respect provider-configured timeout (e.g. Pi's 15 min, executable providers)
|
|
331
400
|
const ProviderClass = getProviderClass(this.provider);
|
|
@@ -526,7 +595,7 @@ class Analyzer {
|
|
|
526
595
|
// Build dedup context from prMetadata and options
|
|
527
596
|
const dedupContext = buildDedupContext(prMetadata, { reviewId: prId, serverPort, runId });
|
|
528
597
|
|
|
529
|
-
const orchestrationResult = await this.orchestrateWithAI(allSuggestions, prMetadata, mergedInstructions, worktreePath, { analysisId, tier, progressCallback, timeout: executionTimeout, logPrefix, reviewerNum, excludePrevious, dedupContext });
|
|
598
|
+
const orchestrationResult = await this.orchestrateWithAI(allSuggestions, prMetadata, mergedInstructions, worktreePath, { analysisId, tier, progressCallback, timeout: executionTimeout, logPrefix, reviewerNum, excludePrevious, dedupContext, githubClient });
|
|
530
599
|
|
|
531
600
|
// Report orchestration step as completed
|
|
532
601
|
if (progressCallback) {
|
|
@@ -2661,10 +2730,11 @@ File-level suggestions should NOT have a line number. They apply to the entire f
|
|
|
2661
2730
|
* @param {string} options.analysisId - Analysis ID for process tracking (enables cancellation)
|
|
2662
2731
|
* @param {Object} [options.excludePrevious] - { github: bool, feedback: bool } for dedup
|
|
2663
2732
|
* @param {Object} [options.dedupContext] - { owner, repo, pullNumber, reviewId, serverPort }
|
|
2733
|
+
* @param {Object} [options.githubClient] - GitHubClient used to pre-fetch existing PR review comments for dedup
|
|
2664
2734
|
* @returns {Promise<Array>} Curated suggestions array
|
|
2665
2735
|
*/
|
|
2666
2736
|
async orchestrateWithAI(allSuggestions, prMetadata, customInstructions = null, worktreePath = null, options = {}) {
|
|
2667
|
-
const { analysisId, tier = 'balanced', progressCallback, providerOverride, modelOverride, timeout = 600000, logPrefix: lp = '', reviewerNum, excludePrevious, dedupContext } = options;
|
|
2737
|
+
const { analysisId, tier = 'balanced', progressCallback, providerOverride, modelOverride, timeout = 600000, logPrefix: lp = '', reviewerNum, excludePrevious, dedupContext, githubClient } = options;
|
|
2668
2738
|
// Build adapter-level log prefix: when reviewerNum is set (council mode),
|
|
2669
2739
|
// use compact format like [R1 Orch] so concurrent reviewers are disambiguated
|
|
2670
2740
|
const adapterLogPrefix = reviewerNum ? `[R${reviewerNum} Orch]` : '';
|
|
@@ -2685,8 +2755,26 @@ File-level suggestions should NOT have a line number. They apply to the entire f
|
|
|
2685
2755
|
// Create provider instance for consolidation (use overrides if provided)
|
|
2686
2756
|
const aiProvider = createProvider(providerOverride || this.provider, modelOverride || this.model, this.providerOverrides);
|
|
2687
2757
|
|
|
2758
|
+
// Pre-fetch existing PR review comments for dedup (replaces the prior
|
|
2759
|
+
// `gh api` shell-out — the analyzer no longer depends on the `gh` CLI).
|
|
2760
|
+
// The fetch runs only when the caller opted in via excludePrevious.github
|
|
2761
|
+
// *and* supplied a GitHubClient. When the client is unavailable (e.g.
|
|
2762
|
+
// local mode or a caller that has not yet been updated) the GitHub dedup
|
|
2763
|
+
// section is silently omitted; buildDedupInstructions handles that case.
|
|
2764
|
+
let resolvedDedupContext = dedupContext;
|
|
2765
|
+
if (excludePrevious?.github && githubClient && dedupContext?.owner && dedupContext?.repo && dedupContext?.pullNumber) {
|
|
2766
|
+
const githubComments = await fetchExistingReviewComments(
|
|
2767
|
+
githubClient,
|
|
2768
|
+
{ owner: dedupContext.owner, repo: dedupContext.repo, pullNumber: dedupContext.pullNumber },
|
|
2769
|
+
lp
|
|
2770
|
+
);
|
|
2771
|
+
resolvedDedupContext = { ...dedupContext, githubComments: githubComments || [] };
|
|
2772
|
+
} else if (excludePrevious?.github && !githubClient) {
|
|
2773
|
+
logger.warn(`${lp}[Dedup] excludePrevious.github is enabled but no githubClient was supplied — GitHub dedup section will be omitted`);
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2688
2776
|
// Build the consolidation prompt
|
|
2689
|
-
const prompt = this.buildOrchestrationPrompt(allSuggestions, prMetadata, customInstructions, worktreePath, tier, lp, { excludePrevious, dedupContext });
|
|
2777
|
+
const prompt = this.buildOrchestrationPrompt(allSuggestions, prMetadata, customInstructions, worktreePath, tier, lp, { excludePrevious, dedupContext: resolvedDedupContext });
|
|
2690
2778
|
|
|
2691
2779
|
// Execute AI for cross-level consolidation
|
|
2692
2780
|
logger.info(`${lp}[Consolidation] Running AI consolidation to curate and merge suggestions...`);
|
|
@@ -2849,11 +2937,12 @@ File-level suggestions should NOT have a line number. They apply to the entire f
|
|
|
2849
2937
|
* @param {Function} [options.progressCallback] - Progress callback
|
|
2850
2938
|
* @param {Object} [options.excludePrevious] - { github: bool, feedback: bool } for dedup
|
|
2851
2939
|
* @param {number} [options.serverPort] - Server port for dedup API calls
|
|
2940
|
+
* @param {Object} [options.githubClient] - GitHubClient used to pre-fetch existing PR review comments for dedup
|
|
2852
2941
|
* @returns {Promise<Object>} Analysis results { runId, suggestions, summary }
|
|
2853
2942
|
*/
|
|
2854
2943
|
async runReviewerCentricCouncil(reviewContext, councilConfig, options = {}) {
|
|
2855
2944
|
const { reviewId, worktreePath, prMetadata, changedFiles, instructions } = reviewContext;
|
|
2856
|
-
const { analysisId, progressCallback, excludePrevious, serverPort } = options;
|
|
2945
|
+
const { analysisId, progressCallback, excludePrevious, serverPort, githubClient } = options;
|
|
2857
2946
|
const parentRunId = options.runId || uuidv4();
|
|
2858
2947
|
|
|
2859
2948
|
logger.section('Review Council Analysis Starting (Reviewer-Centric)');
|
|
@@ -3027,7 +3116,8 @@ File-level suggestions should NOT have a line number. They apply to the entire f
|
|
|
3027
3116
|
logPrefix: `[${reviewerLabel}] `,
|
|
3028
3117
|
reviewerNum: 1,
|
|
3029
3118
|
excludePrevious,
|
|
3030
|
-
serverPort
|
|
3119
|
+
serverPort,
|
|
3120
|
+
githubClient
|
|
3031
3121
|
}
|
|
3032
3122
|
);
|
|
3033
3123
|
|
|
@@ -3305,7 +3395,7 @@ File-level suggestions should NOT have a line number. They apply to the entire f
|
|
|
3305
3395
|
|
|
3306
3396
|
const consolidated = await this._crossVoiceConsolidate(
|
|
3307
3397
|
voiceReviews, prMetadata, consolInstructions, worktreePath,
|
|
3308
|
-
{ provider: consolProvider, model: consolModel, tier: consolTier, timeout: consolConfig.timeout, analysisId, progressCallback, excludePrevious, dedupContext, providerOverrides: this.providerOverrides }
|
|
3398
|
+
{ provider: consolProvider, model: consolModel, tier: consolTier, timeout: consolConfig.timeout, analysisId, progressCallback, excludePrevious, dedupContext, githubClient, providerOverrides: this.providerOverrides }
|
|
3309
3399
|
);
|
|
3310
3400
|
|
|
3311
3401
|
const finalSuggestions = this.validateAndFinalizeSuggestions(
|
|
@@ -3391,7 +3481,7 @@ File-level suggestions should NOT have a line number. They apply to the entire f
|
|
|
3391
3481
|
*/
|
|
3392
3482
|
async runCouncilAnalysis(reviewContext, councilConfig, options = {}) {
|
|
3393
3483
|
const { reviewId, worktreePath, prMetadata, changedFiles, instructions } = reviewContext;
|
|
3394
|
-
const { analysisId, progressCallback, excludePrevious, serverPort } = options;
|
|
3484
|
+
const { analysisId, progressCallback, excludePrevious, serverPort, githubClient } = options;
|
|
3395
3485
|
const runId = options.runId || uuidv4();
|
|
3396
3486
|
|
|
3397
3487
|
logger.section('Review Council Analysis Starting');
|
|
@@ -3647,7 +3737,7 @@ File-level suggestions should NOT have a line number. They apply to the entire f
|
|
|
3647
3737
|
|
|
3648
3738
|
const orchestrationResult = await this.orchestrateWithAI(
|
|
3649
3739
|
allSuggestions, prMetadata, orchInstructions, worktreePath,
|
|
3650
|
-
{ analysisId, tier: orchTier, progressCallback, providerOverride: orchProvider, modelOverride: orchModel, timeout: orchConfig.timeout || 600000, excludePrevious, dedupContext }
|
|
3740
|
+
{ analysisId, tier: orchTier, progressCallback, providerOverride: orchProvider, modelOverride: orchModel, timeout: orchConfig.timeout || 600000, excludePrevious, dedupContext, githubClient }
|
|
3651
3741
|
);
|
|
3652
3742
|
|
|
3653
3743
|
// Report cross-level orchestration step as completed
|
|
@@ -3958,10 +4048,26 @@ File-level suggestions should NOT have a line number. They apply to the entire f
|
|
|
3958
4048
|
* @private
|
|
3959
4049
|
*/
|
|
3960
4050
|
async _crossVoiceConsolidate(voiceReviews, prMetadata, customInstructions, worktreePath, config) {
|
|
3961
|
-
const { provider, model, tier, timeout, analysisId, progressCallback, excludePrevious, dedupContext, providerOverrides } = config;
|
|
4051
|
+
const { provider, model, tier, timeout, analysisId, progressCallback, excludePrevious, dedupContext, githubClient, providerOverrides } = config;
|
|
3962
4052
|
|
|
3963
4053
|
const aiProvider = createProvider(provider, model, providerOverrides || {});
|
|
3964
4054
|
|
|
4055
|
+
// Pre-fetch existing PR review comments for dedup (replaces the prior
|
|
4056
|
+
// `gh api` shell-out — the analyzer no longer depends on the `gh` CLI).
|
|
4057
|
+
// See orchestrateWithAI for the matching code path used by the other two
|
|
4058
|
+
// top-level analysis flows (analyzeAllLevels, runCouncilAnalysis).
|
|
4059
|
+
let resolvedDedupContext = dedupContext;
|
|
4060
|
+
if (excludePrevious?.github && githubClient && dedupContext?.owner && dedupContext?.repo && dedupContext?.pullNumber) {
|
|
4061
|
+
const githubComments = await fetchExistingReviewComments(
|
|
4062
|
+
githubClient,
|
|
4063
|
+
{ owner: dedupContext.owner, repo: dedupContext.repo, pullNumber: dedupContext.pullNumber },
|
|
4064
|
+
'[ReviewerCouncil]'
|
|
4065
|
+
);
|
|
4066
|
+
resolvedDedupContext = { ...dedupContext, githubComments: githubComments || [] };
|
|
4067
|
+
} else if (excludePrevious?.github && !githubClient) {
|
|
4068
|
+
logger.warn('[ReviewerCouncil][Dedup] excludePrevious.github is enabled but no githubClient was supplied — GitHub dedup section will be omitted');
|
|
4069
|
+
}
|
|
4070
|
+
|
|
3965
4071
|
const voiceDescriptions = voiceReviews.map(v => {
|
|
3966
4072
|
let desc = `### Reviewer: ${v.voiceKey}`;
|
|
3967
4073
|
if (v.isExecutable) desc += ' [external tool]';
|
|
@@ -3987,7 +4093,7 @@ File-level suggestions should NOT have a line number. They apply to the entire f
|
|
|
3987
4093
|
reviewIntro: `You are consolidating code review results from ${voiceReviews.length} independent AI reviewers for ${reviewDescription}. Each reviewer independently analyzed the same code changes and produced a complete review.`,
|
|
3988
4094
|
customInstructions: customInstructions ? this.buildCustomInstructionsSection(customInstructions) : '',
|
|
3989
4095
|
lineNumberGuidance: this.buildOrchestrationLineNumberGuidance(worktreePath),
|
|
3990
|
-
dedupInstructions: buildDedupInstructions(excludePrevious,
|
|
4096
|
+
dedupInstructions: buildDedupInstructions(excludePrevious, resolvedDedupContext || {}),
|
|
3991
4097
|
reviewerSuggestions: voiceDescriptions,
|
|
3992
4098
|
suggestionCount: voiceReviews.reduce((sum, v) => sum + v.suggestionCount, 0),
|
|
3993
4099
|
reviewerCount: voiceReviews.length
|
|
@@ -4052,4 +4158,5 @@ File-level suggestions should NOT have a line number. They apply to the entire f
|
|
|
4052
4158
|
|
|
4053
4159
|
module.exports = Analyzer;
|
|
4054
4160
|
module.exports.buildDedupContext = buildDedupContext;
|
|
4055
|
-
module.exports.buildDedupInstructions = buildDedupInstructions;
|
|
4161
|
+
module.exports.buildDedupInstructions = buildDedupInstructions;
|
|
4162
|
+
module.exports.fetchExistingReviewComments = fetchExistingReviewComments;
|
|
@@ -26,12 +26,40 @@ const BIN_DIR = path.join(__dirname, '..', '..', 'bin');
|
|
|
26
26
|
* in the constructor's base args; individual models can override this via extra_args
|
|
27
27
|
* (e.g., Haiku uses adaptive thinking for efficiency).
|
|
28
28
|
*
|
|
29
|
-
* Effort support by model (newest CLIs): Opus 4.8 / 4.7 support
|
|
30
|
-
* xhigh|max; Opus 4.6 & Sonnet 4.6 support low|medium|high|max
|
|
31
|
-
* has no effort levels.
|
|
29
|
+
* Effort support by model (newest CLIs): Fable 5 and Opus 4.8 / 4.7 support
|
|
30
|
+
* low|medium|high|xhigh|max; Opus 4.6 & Sonnet 4.6 support low|medium|high|max
|
|
31
|
+
* (no xhigh); Haiku has no effort levels.
|
|
32
32
|
*/
|
|
33
33
|
const CLAUDE_MODELS = [
|
|
34
34
|
// ── Thorough tier ───────────────────────────────────────────────────────
|
|
35
|
+
{
|
|
36
|
+
id: 'fable',
|
|
37
|
+
aliases: ['fable-5-xhigh'],
|
|
38
|
+
cli_model: 'claude-fable-5',
|
|
39
|
+
env: { CLAUDE_CODE_EFFORT_LEVEL: 'xhigh' },
|
|
40
|
+
name: 'Fable 5 XHigh',
|
|
41
|
+
tier: 'thorough',
|
|
42
|
+
tagline: 'New Model Tier',
|
|
43
|
+
description: 'Fable 5 (new tier above Opus) with extra-high effort',
|
|
44
|
+
badge: 'Extra-High Effort',
|
|
45
|
+
badgeClass: 'badge-power',
|
|
46
|
+
// Fable 5 is adaptive-thinking-only: an explicit "enabled"/"disabled"
|
|
47
|
+
// thinking mode is rejected by the API, so override the global
|
|
48
|
+
// `--thinking enabled` base arg (last occurrence wins in commander).
|
|
49
|
+
extra_args: ['--thinking', 'adaptive']
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'fable-5-high',
|
|
53
|
+
cli_model: 'claude-fable-5',
|
|
54
|
+
env: { CLAUDE_CODE_EFFORT_LEVEL: 'high' },
|
|
55
|
+
name: 'Fable 5 High',
|
|
56
|
+
tier: 'thorough',
|
|
57
|
+
tagline: 'New Model Tier',
|
|
58
|
+
description: 'Fable 5 with high effort — quicker than XHigh',
|
|
59
|
+
badge: 'High Effort',
|
|
60
|
+
badgeClass: 'badge-power',
|
|
61
|
+
extra_args: ['--thinking', 'adaptive']
|
|
62
|
+
},
|
|
35
63
|
{
|
|
36
64
|
id: 'opus',
|
|
37
65
|
aliases: ['opus-4.7-xhigh'],
|