@in-the-loop-labs/pair-review 2.6.3 → 3.0.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/.pi/extensions/task/index.ts +1 -1
- package/.pi/skills/review-roulette/SKILL.md +1 -1
- package/LICENSE +201 -674
- package/README.md +2 -2
- package/bin/pair-review.js +1 -1
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin-code-critic/.claude-plugin/plugin.json +2 -2
- package/plugin-code-critic/skills/analyze/scripts/git-diff-lines +1 -1
- package/public/css/ai-summary-modal.css +1 -1
- package/public/css/pr.css +194 -0
- package/public/index.html +168 -3
- package/public/js/components/AIPanel.js +17 -3
- package/public/js/components/AISummaryModal.js +1 -1
- package/public/js/components/AdvancedConfigTab.js +1 -1
- package/public/js/components/AnalysisConfigModal.js +1 -1
- package/public/js/components/ChatPanel.js +42 -7
- package/public/js/components/ConfirmDialog.js +22 -3
- package/public/js/components/CouncilProgressModal.js +14 -1
- package/public/js/components/DiffOptionsDropdown.js +411 -24
- package/public/js/components/EmojiPicker.js +1 -1
- package/public/js/components/KeyboardShortcuts.js +1 -1
- package/public/js/components/PanelGroup.js +1 -1
- package/public/js/components/PreviewModal.js +1 -1
- package/public/js/components/ReviewModal.js +1 -1
- package/public/js/components/SplitButton.js +1 -1
- package/public/js/components/StatusIndicator.js +1 -1
- package/public/js/components/SuggestionNavigator.js +13 -6
- package/public/js/components/TabTitle.js +96 -0
- package/public/js/components/TextInputDialog.js +1 -1
- package/public/js/components/TimeoutSelect.js +1 -1
- package/public/js/components/Toast.js +7 -1
- package/public/js/components/VoiceCentricConfigTab.js +1 -1
- package/public/js/index.js +649 -44
- package/public/js/local.js +570 -77
- package/public/js/modules/analysis-history.js +4 -3
- package/public/js/modules/comment-manager.js +6 -1
- package/public/js/modules/comment-minimizer.js +304 -0
- package/public/js/modules/diff-context.js +1 -1
- package/public/js/modules/diff-renderer.js +1 -1
- package/public/js/modules/file-comment-manager.js +1 -1
- package/public/js/modules/file-list-merger.js +1 -1
- package/public/js/modules/gap-coordinates.js +1 -1
- package/public/js/modules/hunk-parser.js +1 -1
- package/public/js/modules/line-tracker.js +1 -1
- package/public/js/modules/panel-resizer.js +1 -1
- package/public/js/modules/storage-cleanup.js +1 -1
- package/public/js/modules/suggestion-manager.js +1 -1
- package/public/js/pr.js +83 -7
- package/public/js/repo-settings.js +1 -1
- package/public/js/utils/category-emoji.js +1 -1
- package/public/js/utils/file-order.js +1 -1
- package/public/js/utils/markdown.js +1 -1
- package/public/js/utils/suggestion-ui.js +1 -1
- package/public/js/utils/tier-icons.js +1 -1
- package/public/js/utils/time.js +1 -1
- package/public/js/ws-client.js +1 -1
- package/public/local.html +14 -0
- package/public/pr.html +3 -0
- package/public/setup.html +1 -1
- package/src/ai/analyzer.js +18 -12
- package/src/ai/claude-cli.js +1 -1
- package/src/ai/claude-provider.js +1 -1
- package/src/ai/codex-provider.js +1 -1
- package/src/ai/copilot-provider.js +1 -1
- package/src/ai/cursor-agent-provider.js +1 -1
- package/src/ai/gemini-provider.js +1 -1
- package/src/ai/index.js +1 -1
- package/src/ai/opencode-provider.js +1 -1
- package/src/ai/pi-provider.js +1 -1
- package/src/ai/prompts/baseline/consolidation/balanced.js +1 -1
- package/src/ai/prompts/baseline/consolidation/fast.js +1 -1
- package/src/ai/prompts/baseline/consolidation/thorough.js +1 -1
- package/src/ai/prompts/baseline/level1/balanced.js +1 -1
- package/src/ai/prompts/baseline/level1/fast.js +1 -1
- package/src/ai/prompts/baseline/level1/thorough.js +1 -1
- package/src/ai/prompts/baseline/level2/balanced.js +1 -1
- package/src/ai/prompts/baseline/level2/fast.js +1 -1
- package/src/ai/prompts/baseline/level2/thorough.js +1 -1
- package/src/ai/prompts/baseline/level3/balanced.js +1 -1
- package/src/ai/prompts/baseline/level3/fast.js +1 -1
- package/src/ai/prompts/baseline/level3/thorough.js +1 -1
- package/src/ai/prompts/baseline/orchestration/balanced.js +1 -1
- package/src/ai/prompts/baseline/orchestration/fast.js +1 -1
- package/src/ai/prompts/baseline/orchestration/thorough.js +1 -1
- package/src/ai/prompts/config.js +1 -1
- package/src/ai/prompts/index.js +1 -1
- package/src/ai/prompts/line-number-guidance.js +1 -1
- package/src/ai/prompts/render-for-skill.js +1 -1
- package/src/ai/prompts/shared/diff-instructions.js +1 -1
- package/src/ai/prompts/shared/output-schema.js +1 -1
- package/src/ai/prompts/shared/valid-files.js +1 -1
- package/src/ai/prompts/sparse-checkout-guidance.js +1 -1
- package/src/ai/provider-availability.js +1 -1
- package/src/ai/provider.js +1 -1
- package/src/ai/stream-parser.js +1 -1
- package/src/chat/acp-bridge.js +1 -1
- package/src/chat/api-reference.js +1 -1
- package/src/chat/chat-providers.js +1 -1
- package/src/chat/claude-code-bridge.js +1 -1
- package/src/chat/codex-bridge.js +1 -1
- package/src/chat/pi-bridge.js +1 -1
- package/src/chat/prompt-builder.js +1 -1
- package/src/chat/session-manager.js +1 -1
- package/src/config.js +3 -1
- package/src/database.js +591 -40
- package/src/events/review-events.js +1 -1
- package/src/git/base-branch.js +173 -0
- package/src/git/gitattributes.js +1 -1
- package/src/git/sha-abbrev.js +35 -0
- package/src/git/worktree.js +1 -1
- package/src/github/client.js +33 -2
- package/src/github/parser.js +1 -1
- package/src/hooks/hook-runner.js +100 -0
- package/src/hooks/payloads.js +212 -0
- package/src/local-review.js +469 -130
- package/src/local-scope.js +58 -0
- package/src/main.js +56 -5
- package/src/mcp-stdio.js +1 -1
- package/src/protocol-handler.js +1 -1
- package/src/routes/analyses.js +74 -11
- package/src/routes/chat.js +34 -1
- package/src/routes/config.js +2 -1
- package/src/routes/context-files.js +1 -1
- package/src/routes/councils.js +1 -1
- package/src/routes/github-collections.js +1 -1
- package/src/routes/local.js +735 -69
- package/src/routes/mcp.js +21 -11
- package/src/routes/pr.js +91 -13
- package/src/routes/reviews.js +1 -1
- package/src/routes/setup.js +2 -1
- package/src/routes/shared.js +1 -1
- package/src/routes/worktrees.js +213 -149
- package/src/server.js +31 -1
- package/src/setup/local-setup.js +47 -6
- package/src/setup/pr-setup.js +29 -6
- package/src/utils/auto-context.js +1 -1
- package/src/utils/category-emoji.js +1 -1
- package/src/utils/comment-formatter.js +1 -1
- package/src/utils/diff-annotator.js +1 -1
- package/src/utils/diff-file-list.js +1 -1
- package/src/utils/instructions.js +1 -1
- package/src/utils/json-extractor.js +1 -1
- package/src/utils/line-validation.js +1 -1
- package/src/utils/logger.js +1 -1
- package/src/utils/paths.js +1 -1
- package/src/utils/safe-parse-json.js +1 -1
- package/src/utils/stats-calculator.js +1 -1
- package/src/ws/index.js +1 -1
- package/src/ws/server.js +1 -1
package/src/routes/mcp.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// SPDX-License-Identifier:
|
|
1
|
+
// Copyright 2026 Tim Perkins (tjwp) | SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
const express = require('express');
|
|
3
3
|
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
4
4
|
const { StreamableHTTPServerTransport } = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
@@ -11,6 +11,7 @@ const { getTierForModel } = require('../ai/provider');
|
|
|
11
11
|
const { TIERS, TIER_ALIASES, resolveTier } = require('../ai/prompts/config');
|
|
12
12
|
const { GitWorktreeManager } = require('../git/worktree');
|
|
13
13
|
const path = require('path');
|
|
14
|
+
const { getCurrentBranch } = require('../local-review');
|
|
14
15
|
const { normalizeRepository } = require('../utils/paths');
|
|
15
16
|
const logger = require('../utils/logger');
|
|
16
17
|
const { broadcastReviewEvent } = require('../events/review-events');
|
|
@@ -118,7 +119,11 @@ async function resolveReview(args, db) {
|
|
|
118
119
|
const reviewRepo = new ReviewRepository(db);
|
|
119
120
|
|
|
120
121
|
if (args.path && args.headSha) {
|
|
121
|
-
|
|
122
|
+
let headBranch = args.branch;
|
|
123
|
+
if (!headBranch) {
|
|
124
|
+
try { headBranch = await getCurrentBranch(args.path); } catch (_) { /* non-fatal */ }
|
|
125
|
+
}
|
|
126
|
+
const review = await reviewRepo.findLocalReview(args.path, args.headSha, headBranch);
|
|
122
127
|
if (!review) {
|
|
123
128
|
return { review: null, error: `No local review found for path "${args.path}" with HEAD SHA "${args.headSha}"` };
|
|
124
129
|
}
|
|
@@ -507,22 +512,27 @@ function createMCPServer(db, options = {}) {
|
|
|
507
512
|
const localPath = args.path;
|
|
508
513
|
const localHeadSha = args.headSha;
|
|
509
514
|
|
|
515
|
+
// Resolve current branch for session identity
|
|
516
|
+
let localHeadBranch;
|
|
517
|
+
try { localHeadBranch = await getCurrentBranch(localPath); } catch (_) { /* non-fatal */ }
|
|
518
|
+
|
|
510
519
|
// Look up or create local review record
|
|
511
|
-
|
|
520
|
+
let reviewId;
|
|
512
521
|
let repository;
|
|
513
|
-
const existingReview = await reviewRepo.
|
|
522
|
+
const existingReview = await reviewRepo.findLocalReview(localPath, localHeadSha, localHeadBranch);
|
|
514
523
|
if (existingReview) {
|
|
524
|
+
reviewId = existingReview.id;
|
|
515
525
|
repository = existingReview.repository;
|
|
516
526
|
} else {
|
|
517
527
|
repository = path.basename(localPath);
|
|
528
|
+
reviewId = await reviewRepo.upsertLocalReview({
|
|
529
|
+
localPath,
|
|
530
|
+
localHeadSha,
|
|
531
|
+
repository,
|
|
532
|
+
localHeadBranch
|
|
533
|
+
});
|
|
518
534
|
}
|
|
519
535
|
|
|
520
|
-
const reviewId = await reviewRepo.upsertLocalReview({
|
|
521
|
-
localPath,
|
|
522
|
-
localHeadSha,
|
|
523
|
-
repository
|
|
524
|
-
});
|
|
525
|
-
|
|
526
536
|
// Concurrent analysis guard: check if one is already running
|
|
527
537
|
const existingAnalysisId = reviewToAnalysisId.get(reviewId);
|
|
528
538
|
if (existingAnalysisId && activeAnalyses.get(existingAnalysisId)?.status === 'running') {
|
|
@@ -687,7 +697,7 @@ function createMCPServer(db, options = {}) {
|
|
|
687
697
|
const worktreePath = await worktreeManager.getWorktreePath({ owner, repo, number: prNumber });
|
|
688
698
|
|
|
689
699
|
// Get or create review record
|
|
690
|
-
const review = await reviewRepo.getOrCreate({ prNumber, repository });
|
|
700
|
+
const { review } = await reviewRepo.getOrCreate({ prNumber, repository });
|
|
691
701
|
|
|
692
702
|
// Resolve provider and model
|
|
693
703
|
const repoSettings = await repoSettingsRepo.getRepoSettings(repository);
|
package/src/routes/pr.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// SPDX-License-Identifier:
|
|
1
|
+
// Copyright 2026 Tim Perkins (tjwp) | SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
/**
|
|
3
3
|
* Core PR Routes
|
|
4
4
|
*
|
|
@@ -18,6 +18,7 @@ const { query, queryOne, run, withTransaction, WorktreeRepository, ReviewReposit
|
|
|
18
18
|
const { GitWorktreeManager } = require('../git/worktree');
|
|
19
19
|
const { GitHubClient } = require('../github/client');
|
|
20
20
|
const { getGeneratedFilePatterns } = require('../git/gitattributes');
|
|
21
|
+
const { getShaAbbrevLength, DEFAULT_SHA_ABBREV_LENGTH } = require('../git/sha-abbrev');
|
|
21
22
|
const { normalizeRepository, resolveRenamedFile, resolveRenamedFileOld } = require('../utils/paths');
|
|
22
23
|
const { mergeInstructions } = require('../utils/instructions');
|
|
23
24
|
const Analyzer = require('../ai/analyzer');
|
|
@@ -28,6 +29,8 @@ const { getGitHubToken } = require('../config');
|
|
|
28
29
|
const logger = require('../utils/logger');
|
|
29
30
|
const { buildDiffLineSet } = require('../utils/diff-annotator');
|
|
30
31
|
const { broadcastReviewEvent } = require('../events/review-events');
|
|
32
|
+
const { fireHooks, hasHooks } = require('../hooks/hook-runner');
|
|
33
|
+
const { buildReviewStartedPayload, buildReviewLoadedPayload, buildAnalysisStartedPayload, buildAnalysisCompletedPayload, getCachedUser } = require('../hooks/payloads');
|
|
31
34
|
const simpleGit = require('simple-git');
|
|
32
35
|
const {
|
|
33
36
|
activeAnalyses,
|
|
@@ -179,11 +182,12 @@ router.get('/api/pr/:owner/:repo/:number', async (req, res) => {
|
|
|
179
182
|
});
|
|
180
183
|
}
|
|
181
184
|
|
|
182
|
-
//
|
|
183
|
-
//
|
|
185
|
+
// Eagerly create the review record on first visit so all subsequent
|
|
186
|
+
// events (analysis, comments, hooks) always have a real reviewId.
|
|
187
|
+
// To distinguish new vs returning, snapshot the current record first.
|
|
184
188
|
const db = req.app.get('db');
|
|
185
189
|
const reviewRepo = new ReviewRepository(db);
|
|
186
|
-
const review = await reviewRepo.
|
|
190
|
+
const { review, created: isNewReview } = await reviewRepo.getOrCreate({ prNumber, repository });
|
|
187
191
|
|
|
188
192
|
// Parse extended PR data
|
|
189
193
|
let extendedData = {};
|
|
@@ -196,10 +200,9 @@ router.get('/api/pr/:owner/:repo/:number', async (req, res) => {
|
|
|
196
200
|
// Parse owner and repo from repository field
|
|
197
201
|
const [repoOwner, repoName] = repository.split('/');
|
|
198
202
|
|
|
199
|
-
// Check for pending GitHub draft
|
|
200
|
-
// This avoids unnecessary GitHub API calls for PRs the user hasn't started reviewing
|
|
203
|
+
// Check for pending GitHub draft
|
|
201
204
|
let pendingDraft = null;
|
|
202
|
-
|
|
205
|
+
{
|
|
203
206
|
const config = req.app.get('config');
|
|
204
207
|
const githubToken = getGitHubToken(config || {}) || req.app.get('githubToken');
|
|
205
208
|
|
|
@@ -220,13 +223,17 @@ router.get('/api/pr/:owner/:repo/:number', async (req, res) => {
|
|
|
220
223
|
}
|
|
221
224
|
}
|
|
222
225
|
|
|
226
|
+
// Compute SHA abbreviation length from the worktree's git config
|
|
227
|
+
const shaAbbrevLength = extendedData.worktree_path
|
|
228
|
+
? getShaAbbrevLength(extendedData.worktree_path)
|
|
229
|
+
: DEFAULT_SHA_ABBREV_LENGTH;
|
|
230
|
+
|
|
223
231
|
// Prepare response
|
|
224
232
|
// Use review.id instead of prMetadata.id to avoid ID collision with local mode
|
|
225
|
-
// When no review exists yet, id will be null
|
|
226
233
|
const response = {
|
|
227
234
|
success: true,
|
|
228
235
|
data: {
|
|
229
|
-
id: review
|
|
236
|
+
id: review.id,
|
|
230
237
|
owner: repoOwner,
|
|
231
238
|
repo: repoName,
|
|
232
239
|
number: prMetadata.pr_number,
|
|
@@ -238,6 +245,7 @@ router.get('/api/pr/:owner/:repo/:number', async (req, res) => {
|
|
|
238
245
|
head_branch: prMetadata.head_branch,
|
|
239
246
|
head_sha: extendedData.head_sha || null, // Head commit SHA for GitHub API comments
|
|
240
247
|
node_id: extendedData.node_id || null, // GraphQL node ID for review submission
|
|
248
|
+
shaAbbrevLength,
|
|
241
249
|
created_at: prMetadata.created_at,
|
|
242
250
|
updated_at: prMetadata.updated_at,
|
|
243
251
|
file_changes: extendedData.changed_files ? extendedData.changed_files.length : 0,
|
|
@@ -259,6 +267,22 @@ router.get('/api/pr/:owner/:repo/:number', async (req, res) => {
|
|
|
259
267
|
|
|
260
268
|
res.json(response);
|
|
261
269
|
|
|
270
|
+
// Fire review hook (after response, non-blocking)
|
|
271
|
+
const config = req.app.get('config') || {};
|
|
272
|
+
const prContext = {
|
|
273
|
+
number: prNumber, owner: repoOwner, repo: repoName,
|
|
274
|
+
author: prMetadata.author, baseBranch: prMetadata.base_branch, headBranch: prMetadata.head_branch,
|
|
275
|
+
baseSha: extendedData.base_sha || null, headSha: extendedData.head_sha || null,
|
|
276
|
+
};
|
|
277
|
+
const hookEvent = isNewReview ? 'review.started' : 'review.loaded';
|
|
278
|
+
if (hasHooks(hookEvent, config)) {
|
|
279
|
+
getCachedUser(config).then(user => {
|
|
280
|
+
const builder = isNewReview ? buildReviewStartedPayload : buildReviewLoadedPayload;
|
|
281
|
+
const payload = builder({ reviewId: review.id, mode: 'pr', prContext, user });
|
|
282
|
+
fireHooks(hookEvent, payload, config);
|
|
283
|
+
}).catch(err => { logger.warn(`Review hook failed: ${err.message}`); });
|
|
284
|
+
}
|
|
285
|
+
|
|
262
286
|
} catch (error) {
|
|
263
287
|
console.error('Error fetching PR data:', error);
|
|
264
288
|
res.status(500).json({
|
|
@@ -358,7 +382,7 @@ router.post('/api/pr/:owner/:repo/:number/refresh', async (req, res) => {
|
|
|
358
382
|
// Get or create a review record for this PR
|
|
359
383
|
// The review.id is used for comments to avoid ID collision with local mode
|
|
360
384
|
const reviewRepo = new ReviewRepository(db);
|
|
361
|
-
const review = await reviewRepo.getOrCreate({ prNumber, repository });
|
|
385
|
+
const { review } = await reviewRepo.getOrCreate({ prNumber, repository });
|
|
362
386
|
|
|
363
387
|
// Fetch and return updated PR data (reuse the same structure as GET endpoint)
|
|
364
388
|
const prMetadata = await queryOne(db, `
|
|
@@ -1013,7 +1037,7 @@ router.post('/api/pr/:owner/:repo/:number/submit-review', async (req, res) => {
|
|
|
1013
1037
|
// Get or create a review record for this PR
|
|
1014
1038
|
// Comments are associated with review.id, not prMetadata.id
|
|
1015
1039
|
const reviewRepo = new ReviewRepository(db);
|
|
1016
|
-
const review = await reviewRepo.getOrCreate({ prNumber, repository });
|
|
1040
|
+
const { review } = await reviewRepo.getOrCreate({ prNumber, repository });
|
|
1017
1041
|
|
|
1018
1042
|
// Get all active user comments for this PR using review.id
|
|
1019
1043
|
const comments = await query(db, `
|
|
@@ -1518,7 +1542,7 @@ router.post('/api/pr/:owner/:repo/:number/analyses', async (req, res) => {
|
|
|
1518
1542
|
const runId = uuidv4();
|
|
1519
1543
|
const analysisId = runId;
|
|
1520
1544
|
|
|
1521
|
-
const review = await reviewRepo.getOrCreate({ prNumber, repository });
|
|
1545
|
+
const { review } = await reviewRepo.getOrCreate({ prNumber, repository });
|
|
1522
1546
|
|
|
1523
1547
|
const analysisRunRepo = new AnalysisRunRepository(db);
|
|
1524
1548
|
const levelsConfig = parseEnabledLevels(requestEnabledLevels, requestSkipLevel3);
|
|
@@ -1562,6 +1586,19 @@ router.post('/api/pr/:owner/:repo/:number/analyses', async (req, res) => {
|
|
|
1562
1586
|
|
|
1563
1587
|
broadcastProgress(analysisId, initialStatus);
|
|
1564
1588
|
broadcastReviewEvent(review.id, { type: 'review:analysis_started', analysisId });
|
|
1589
|
+
const analysisConfig = req.app.get('config') || {};
|
|
1590
|
+
const analysisPrContext = {
|
|
1591
|
+
number: prNumber, owner, repo,
|
|
1592
|
+
author: prMetadata.author, baseBranch: prMetadata.base_branch, headBranch: prMetadata.head_branch,
|
|
1593
|
+
baseSha: prMetadata.base_sha || null, headSha: prMetadata.head_sha || null,
|
|
1594
|
+
};
|
|
1595
|
+
if (hasHooks('analysis.started', analysisConfig)) {
|
|
1596
|
+
getCachedUser(analysisConfig).then(user => {
|
|
1597
|
+
fireHooks('analysis.started', buildAnalysisStartedPayload({
|
|
1598
|
+
reviewId: review.id, analysisId, provider, model, mode: 'pr', prContext: analysisPrContext, user,
|
|
1599
|
+
}), analysisConfig);
|
|
1600
|
+
}).catch(() => {});
|
|
1601
|
+
}
|
|
1565
1602
|
|
|
1566
1603
|
const analyzer = new Analyzer(req.app.get('db'), model, provider);
|
|
1567
1604
|
|
|
@@ -1657,6 +1694,18 @@ router.post('/api/pr/:owner/:repo/:number/analyses', async (req, res) => {
|
|
|
1657
1694
|
|
|
1658
1695
|
broadcastProgress(analysisId, completedStatus);
|
|
1659
1696
|
broadcastReviewEvent(review.id, { type: 'review:analysis_completed' });
|
|
1697
|
+
|
|
1698
|
+
// Fire analysis.completed hook
|
|
1699
|
+
const hookConfig = req.app.get('config') || {};
|
|
1700
|
+
if (hasHooks('analysis.completed', hookConfig)) {
|
|
1701
|
+
getCachedUser(hookConfig).then(user => {
|
|
1702
|
+
fireHooks('analysis.completed', buildAnalysisCompletedPayload({
|
|
1703
|
+
reviewId: review.id, analysisId, provider, model, status: 'success',
|
|
1704
|
+
totalSuggestions: completionInfo.totalSuggestions,
|
|
1705
|
+
mode: 'pr', prContext: analysisPrContext, user,
|
|
1706
|
+
}), hookConfig);
|
|
1707
|
+
}).catch(() => {});
|
|
1708
|
+
}
|
|
1660
1709
|
})
|
|
1661
1710
|
.catch(error => {
|
|
1662
1711
|
const currentStatus = activeAnalyses.get(analysisId);
|
|
@@ -1667,6 +1716,15 @@ router.post('/api/pr/:owner/:repo/:number/analyses', async (req, res) => {
|
|
|
1667
1716
|
|
|
1668
1717
|
if (error.isCancellation) {
|
|
1669
1718
|
logger.info(`Analysis cancelled for PR #${prNumber}`);
|
|
1719
|
+
if (hasHooks('analysis.completed', analysisConfig)) {
|
|
1720
|
+
getCachedUser(analysisConfig).then(user => {
|
|
1721
|
+
fireHooks('analysis.completed', buildAnalysisCompletedPayload({
|
|
1722
|
+
reviewId: review.id, analysisId, provider, model,
|
|
1723
|
+
status: 'cancelled', totalSuggestions: 0,
|
|
1724
|
+
mode: 'pr', prContext: analysisPrContext, user,
|
|
1725
|
+
}), analysisConfig);
|
|
1726
|
+
}).catch(() => {});
|
|
1727
|
+
}
|
|
1670
1728
|
return;
|
|
1671
1729
|
}
|
|
1672
1730
|
|
|
@@ -1690,6 +1748,16 @@ router.post('/api/pr/:owner/:repo/:number/analyses', async (req, res) => {
|
|
|
1690
1748
|
activeAnalyses.set(analysisId, failedStatus);
|
|
1691
1749
|
|
|
1692
1750
|
broadcastProgress(analysisId, failedStatus);
|
|
1751
|
+
|
|
1752
|
+
if (hasHooks('analysis.completed', analysisConfig)) {
|
|
1753
|
+
getCachedUser(analysisConfig).then(user => {
|
|
1754
|
+
fireHooks('analysis.completed', buildAnalysisCompletedPayload({
|
|
1755
|
+
reviewId: review.id, analysisId, provider, model,
|
|
1756
|
+
status: 'failed', totalSuggestions: 0,
|
|
1757
|
+
mode: 'pr', prContext: analysisPrContext, user,
|
|
1758
|
+
}), analysisConfig);
|
|
1759
|
+
}).catch(() => {});
|
|
1760
|
+
}
|
|
1693
1761
|
})
|
|
1694
1762
|
.finally(() => {
|
|
1695
1763
|
// Clean up review to analysis ID mapping (unified map)
|
|
@@ -1771,7 +1839,7 @@ router.post('/api/pr/:owner/:repo/:number/analyses/council', async (req, res) =>
|
|
|
1771
1839
|
const repoInstructions = repoSettings?.default_instructions || null;
|
|
1772
1840
|
const requestInstructions = rawInstructions?.trim() || null;
|
|
1773
1841
|
|
|
1774
|
-
const review = await reviewRepo.getOrCreate({ prNumber, repository });
|
|
1842
|
+
const { review } = await reviewRepo.getOrCreate({ prNumber, repository });
|
|
1775
1843
|
|
|
1776
1844
|
if (requestInstructions) {
|
|
1777
1845
|
await reviewRepo.upsertCustomInstructions(prNumber, repository, requestInstructions);
|
|
@@ -1788,6 +1856,16 @@ router.post('/api/pr/:owner/:repo/:number/analyses/council', async (req, res) =>
|
|
|
1788
1856
|
headSha: prMetadata.head_sha,
|
|
1789
1857
|
logLabel: `PR #${prNumber}`,
|
|
1790
1858
|
initialStatusExtra: { prNumber, reviewType: 'pr' },
|
|
1859
|
+
config: req.app.get('config') || {},
|
|
1860
|
+
hookContext: {
|
|
1861
|
+
mode: 'pr',
|
|
1862
|
+
prContext: {
|
|
1863
|
+
number: prNumber, owner, repo,
|
|
1864
|
+
author: prMetadata.author, baseBranch: prMetadata.base_branch,
|
|
1865
|
+
headBranch: prMetadata.head_branch,
|
|
1866
|
+
baseSha: prMetadata.base_sha || null, headSha: prMetadata.head_sha || null,
|
|
1867
|
+
},
|
|
1868
|
+
},
|
|
1791
1869
|
onSuccess: async (result) => {
|
|
1792
1870
|
if (result.summary) {
|
|
1793
1871
|
await reviewRepo.upsertSummary(prNumber, repository, result.summary);
|
package/src/routes/reviews.js
CHANGED
package/src/routes/setup.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// SPDX-License-Identifier:
|
|
1
|
+
// Copyright 2026 Tim Perkins (tjwp) | SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
/**
|
|
3
3
|
* Setup Routes
|
|
4
4
|
*
|
|
@@ -165,6 +165,7 @@ router.post('/api/setup/local', async (req, res) => {
|
|
|
165
165
|
const result = await setupLocalReview({
|
|
166
166
|
db,
|
|
167
167
|
targetPath,
|
|
168
|
+
config: req.app.get('config') || {},
|
|
168
169
|
onProgress: (progress) => {
|
|
169
170
|
sendSetupEvent(setupId, 'step', progress);
|
|
170
171
|
}
|
package/src/routes/shared.js
CHANGED