@in-the-loop-labs/pair-review 3.6.0 → 3.7.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/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 +0 -1737
- package/public/index.html +11 -0
- package/public/js/components/AIPanel.js +39 -23
- package/public/js/components/AdvancedConfigTab.js +56 -4
- package/public/js/components/AnalysisConfigModal.js +41 -25
- package/public/js/components/ReviewModal.js +135 -13
- 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 +33 -3
- package/public/js/pr.js +653 -157
- package/public/js/repo-links.js +328 -0
- package/public/js/utils/provider-model.js +88 -0
- package/public/js/utils/storage-keys.js +50 -0
- package/public/local.html +7 -0
- package/public/pr.html +7 -0
- package/public/repo-settings.html +1 -0
- package/public/setup.html +2 -0
- package/src/ai/analyzer.js +125 -18
- 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 +133 -30
- 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/src/routes/local.js
CHANGED
|
@@ -497,10 +497,13 @@ router.post('/api/local/start', async (req, res) => {
|
|
|
497
497
|
const digest = await computeScopedDigest(repoPath, scopeStart, scopeEnd);
|
|
498
498
|
|
|
499
499
|
// Branch detection: when no uncommitted changes, check if branch has commits ahead
|
|
500
|
+
const { resolveHostBinding: _resolveHostBindingForBranch } = require('../config');
|
|
501
|
+
const branchBinding = repository ? _resolveHostBindingForBranch(repository, config) : null;
|
|
500
502
|
const branchInfo = await detectAndBuildBranchInfo(repoPath, branch, {
|
|
501
503
|
repository,
|
|
502
504
|
diff,
|
|
503
|
-
githubToken: getGitHubToken(config),
|
|
505
|
+
githubToken: branchBinding?.token || getGitHubToken(config),
|
|
506
|
+
hostBinding: branchBinding,
|
|
504
507
|
enableGraphite: config.enable_graphite === true
|
|
505
508
|
});
|
|
506
509
|
|
|
@@ -716,13 +719,18 @@ router.get('/api/local/:reviewId', async (req, res) => {
|
|
|
716
719
|
&& branchName && branchName !== 'HEAD' && branchName !== 'unknown'
|
|
717
720
|
&& repositoryName && repositoryName.includes('/')) {
|
|
718
721
|
const bgConfig = req.app.get('config') || {};
|
|
719
|
-
const
|
|
722
|
+
const { resolveHostBinding: _resolveHostBinding } = require('../config');
|
|
723
|
+
const bgBinding = _resolveHostBinding(repositoryName, bgConfig);
|
|
724
|
+
const bgToken = bgBinding.token;
|
|
720
725
|
const bgT0 = Date.now();
|
|
721
726
|
const { detectBaseBranch } = require('../git/base-branch');
|
|
722
727
|
detectBaseBranch(review.local_path, branchName, {
|
|
723
728
|
repository: repositoryName,
|
|
724
729
|
enableGraphite: bgConfig.enable_graphite === true,
|
|
725
|
-
_deps: bgToken ? {
|
|
730
|
+
_deps: bgToken ? {
|
|
731
|
+
getGitHubToken: () => bgToken,
|
|
732
|
+
getHostBinding: () => bgBinding
|
|
733
|
+
} : undefined
|
|
726
734
|
}).then(detection => {
|
|
727
735
|
if (detection && detection.baseBranch) {
|
|
728
736
|
return reviewRepo.updateReview(reviewId, { local_base_branch: detection.baseBranch });
|
|
@@ -1450,7 +1458,9 @@ router.post('/api/local/:reviewId/analyses', async (req, res) => {
|
|
|
1450
1458
|
|
|
1451
1459
|
const progressCallback = createProgressCallback(analysisId);
|
|
1452
1460
|
|
|
1453
|
-
// 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)
|
|
1461
|
+
// 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).
|
|
1462
|
+
// Local mode has no associated GitHub PR, so githubClient is intentionally omitted —
|
|
1463
|
+
// the analyzer drops the GitHub dedup section when no client is supplied.
|
|
1454
1464
|
analyzer.analyzeLevel1(reviewId, localPath, localMetadata, progressCallback, { globalInstructions, repoInstructions, requestInstructions }, changedFiles, { analysisId, runId, skipRunCreation: true, tier, skipLevel3: requestSkipLevel3, enabledLevels: levelsConfig, excludePrevious, serverPort: req.socket.localPort })
|
|
1455
1465
|
.then(async result => {
|
|
1456
1466
|
logger.section('Local Analysis Results');
|
|
@@ -1940,11 +1950,16 @@ router.post('/api/local/:reviewId/set-scope', async (req, res) => {
|
|
|
1940
1950
|
} else {
|
|
1941
1951
|
const { detectBaseBranch } = require('../git/base-branch');
|
|
1942
1952
|
const config = req.app.get('config') || {};
|
|
1943
|
-
const
|
|
1953
|
+
const { resolveHostBinding: _resolveHostBinding } = require('../config');
|
|
1954
|
+
const localBinding = _resolveHostBinding(review.repository, config);
|
|
1955
|
+
const token = localBinding.token;
|
|
1944
1956
|
const detection = await detectBaseBranch(localPath, currentBranch, {
|
|
1945
1957
|
repository: review.repository,
|
|
1946
1958
|
enableGraphite: config.enable_graphite === true,
|
|
1947
|
-
_deps: token ? {
|
|
1959
|
+
_deps: token ? {
|
|
1960
|
+
getGitHubToken: () => token,
|
|
1961
|
+
getHostBinding: () => localBinding
|
|
1962
|
+
} : undefined
|
|
1948
1963
|
});
|
|
1949
1964
|
if (!detection) {
|
|
1950
1965
|
return res.status(400).json({ error: 'Could not detect base branch' });
|
|
@@ -2257,11 +2272,19 @@ router.post('/api/local/:reviewId/analyses/council', async (req, res) => {
|
|
|
2257
2272
|
baseBranch: review.local_base_branch || null,
|
|
2258
2273
|
});
|
|
2259
2274
|
|
|
2260
|
-
// Generate and cache diff
|
|
2275
|
+
// Generate and cache diff. Hoist the result out of the try so we can also
|
|
2276
|
+
// persist it to `local_diffs` below (after reviewRepo is constructed) — the
|
|
2277
|
+
// council path previously cached the diff in-memory only, which left the
|
|
2278
|
+
// manual tour/summary buttons reporting a false "no-diff" after a restart.
|
|
2279
|
+
let councilDiff = null;
|
|
2280
|
+
let councilStats = null;
|
|
2281
|
+
let councilDigest = null;
|
|
2261
2282
|
try {
|
|
2262
2283
|
const diffResult = await generateScopedDiff(localPath, councilScopeStart, councilScopeEnd, review.local_base_branch);
|
|
2263
|
-
|
|
2264
|
-
|
|
2284
|
+
councilDigest = await computeScopedDigest(localPath, councilScopeStart, councilScopeEnd);
|
|
2285
|
+
councilDiff = diffResult.diff;
|
|
2286
|
+
councilStats = diffResult.stats;
|
|
2287
|
+
setLocalReviewDiff(reviewId, { diff: councilDiff, stats: councilStats, digest: councilDigest });
|
|
2265
2288
|
} catch (diffError) {
|
|
2266
2289
|
logger.warn(`Could not generate diff for local council review ${reviewId}: ${diffError.message}`);
|
|
2267
2290
|
}
|
|
@@ -2269,6 +2292,16 @@ router.post('/api/local/:reviewId/analyses/council', async (req, res) => {
|
|
|
2269
2292
|
// Resolve instructions
|
|
2270
2293
|
const repoSettingsRepo = new RepoSettingsRepository(db);
|
|
2271
2294
|
const reviewRepo = new ReviewRepository(db);
|
|
2295
|
+
|
|
2296
|
+
// Durably persist the diff so it survives a restart and the manual
|
|
2297
|
+
// tour/summary buttons can find it (parity with the analysis-push path).
|
|
2298
|
+
if (councilDiff) {
|
|
2299
|
+
try {
|
|
2300
|
+
await reviewRepo.saveLocalDiff(reviewId, { diff: councilDiff, stats: councilStats, digest: councilDigest });
|
|
2301
|
+
} catch (saveError) {
|
|
2302
|
+
logger.warn(`Could not persist diff for local council review ${reviewId}: ${saveError.message}`);
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2272
2305
|
const repoSettings = await repoSettingsRepo.getRepoSettings(review.repository);
|
|
2273
2306
|
const repoInstructions = repoSettings?.default_instructions || null;
|
|
2274
2307
|
const requestInstructions = rawInstructions?.trim() || null;
|
|
@@ -2286,6 +2319,8 @@ router.post('/api/local/:reviewId/analyses/council', async (req, res) => {
|
|
|
2286
2319
|
const { providerOverrides: councilProviderOverrides, providerOverridesMap: councilProviderOverridesMap } =
|
|
2287
2320
|
buildCouncilProviderOverrides(localCouncilConfig, review.repository, repoSettings);
|
|
2288
2321
|
|
|
2322
|
+
// Local mode has no associated GitHub PR, so we do not pass a githubClient.
|
|
2323
|
+
// The analyzer drops the GitHub dedup section when no client is supplied.
|
|
2289
2324
|
const { analysisId, runId } = await analysesRouter.launchCouncilAnalysis(
|
|
2290
2325
|
db,
|
|
2291
2326
|
{
|
|
@@ -2380,10 +2415,65 @@ router.post('/api/local/:reviewId/jobs/:jobKey/start', async (req, res) => {
|
|
|
2380
2415
|
return res.status(404).json({ error: `Local review #${reviewId} not found` });
|
|
2381
2416
|
}
|
|
2382
2417
|
|
|
2383
|
-
const localDiff = await reviewRepo.getLocalDiff(reviewId);
|
|
2384
|
-
const diffText = localDiff ? (localDiff.diff || '') : '';
|
|
2385
2418
|
const worktreePath = review.local_path || null;
|
|
2386
2419
|
|
|
2420
|
+
// Resolve the diff through the same chain the rest of this file uses, rather
|
|
2421
|
+
// than a DB-only read. Reviews created via the analysis-push, council, or MCP
|
|
2422
|
+
// paths may have a diff only in the in-memory cache (or nowhere yet), so a
|
|
2423
|
+
// DB-only read would falsely report "no-diff" for a review that clearly has
|
|
2424
|
+
// changes. Order: (1) in-memory cache, (2) persisted `local_diffs` row,
|
|
2425
|
+
// (3) regenerate from the live working tree (scope-aware) and persist.
|
|
2426
|
+
let diffText = getLocalReviewDiff(reviewId)?.diff || '';
|
|
2427
|
+
|
|
2428
|
+
if (!diffText) {
|
|
2429
|
+
const persistedDiff = await reviewRepo.getLocalDiff(reviewId);
|
|
2430
|
+
diffText = persistedDiff?.diff || '';
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
if (!diffText && worktreePath) {
|
|
2434
|
+
// Regenerate from the current working tree and persist (in-memory + DB) so
|
|
2435
|
+
// the next read is fast and durable, and so pre-Fix-B reviews self-heal.
|
|
2436
|
+
// Mirrors the council diff block above: on error, log and leave it empty.
|
|
2437
|
+
try {
|
|
2438
|
+
const { start: scopeStart, end: scopeEnd } = reviewScope(review);
|
|
2439
|
+
const hasBranch = includesBranch(scopeStart);
|
|
2440
|
+
|
|
2441
|
+
// Snapshot guard: mirror the HEAD invariant enforced by the refresh-diff
|
|
2442
|
+
// handler (see ~line 1702). For a non-branch review, the persisted diff is
|
|
2443
|
+
// pinned to `local_head_sha`. If HEAD has since moved, regenerating here
|
|
2444
|
+
// would silently re-snapshot the CURRENT worktree onto a row that still
|
|
2445
|
+
// claims the OLDER SHA — a data-consistency hole. So we only regenerate
|
|
2446
|
+
// when HEAD still matches; otherwise we leave diffText empty and let the
|
|
2447
|
+
// `{ started: false, reason: 'no-diff' }` response funnel the user through
|
|
2448
|
+
// the established refresh-diff / resolve-head-change flow. Branch-scoped
|
|
2449
|
+
// reviews persist across HEAD changes, so they always regenerate.
|
|
2450
|
+
let headPinned = true;
|
|
2451
|
+
if (!hasBranch && review.local_head_sha) {
|
|
2452
|
+
// Lazy require keeps getHeadSha stubbable via vi.spyOn in tests.
|
|
2453
|
+
const { getHeadSha } = require('../local-review');
|
|
2454
|
+
const currentHeadSha = await getHeadSha(worktreePath);
|
|
2455
|
+
if (currentHeadSha !== review.local_head_sha) {
|
|
2456
|
+
headPinned = false;
|
|
2457
|
+
logger.warn(`Skipping self-heal diff regen for local review ${reviewId} (${jobKey}): HEAD moved on non-branch review (recorded ${review.local_head_sha}, current ${currentHeadSha}) — funneling through resolve-head-change`);
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
if (headPinned) {
|
|
2462
|
+
const diffResult = await generateScopedDiff(worktreePath, scopeStart, scopeEnd, review.local_base_branch);
|
|
2463
|
+
diffText = diffResult.diff || '';
|
|
2464
|
+
if (diffText) {
|
|
2465
|
+
const digest = await computeScopedDigest(worktreePath, scopeStart, scopeEnd);
|
|
2466
|
+
setLocalReviewDiff(reviewId, { diff: diffText, stats: diffResult.stats, digest });
|
|
2467
|
+
await reviewRepo.saveLocalDiff(reviewId, { diff: diffText, stats: diffResult.stats, digest });
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
} catch (regenError) {
|
|
2471
|
+
// A getHeadSha throw (e.g. missing worktree) lands here: leave diffText
|
|
2472
|
+
// empty so the no-diff response fires, matching prior behavior.
|
|
2473
|
+
logger.warn(`Could not regenerate diff for local review ${reviewId} manual ${jobKey} start: ${regenError.message}`);
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2387
2477
|
if (!diffText || !worktreePath) {
|
|
2388
2478
|
return res.json({ started: false, reason: 'no-diff' });
|
|
2389
2479
|
}
|
package/src/routes/mcp.js
CHANGED
|
@@ -11,19 +11,22 @@ 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
|
+
const { getCurrentBranch, generateScopedDiff, computeScopedDigest } = require('../local-review');
|
|
15
|
+
const { reviewScope } = require('../local-scope');
|
|
15
16
|
const { normalizeRepository } = require('../utils/paths');
|
|
16
17
|
const logger = require('../utils/logger');
|
|
17
18
|
const { broadcastReviewEvent } = require('../events/review-events');
|
|
18
19
|
const {
|
|
19
20
|
activeAnalyses,
|
|
20
21
|
reviewToAnalysisId,
|
|
22
|
+
localReviewDiffs,
|
|
21
23
|
determineCompletionInfo,
|
|
22
24
|
broadcastProgress,
|
|
23
25
|
createProgressCallback
|
|
24
26
|
} = require('./shared');
|
|
25
27
|
const { safeParseJson } = require('../utils/safe-parse-json');
|
|
26
|
-
const { resolveLoadSkills } = require('../config');
|
|
28
|
+
const { resolveLoadSkills, resolveHostBinding } = require('../config');
|
|
29
|
+
const { GitHubClient } = require('../github/client');
|
|
27
30
|
|
|
28
31
|
// All valid tier values: canonical tiers + aliases (for Zod enum validation)
|
|
29
32
|
const ALL_TIER_VALUES = /** @type {[string, ...string[]]} */ ([...TIERS, ...Object.keys(TIER_ALIASES)]);
|
|
@@ -557,6 +560,32 @@ function createMCPServer(db, options = {}) {
|
|
|
557
560
|
|
|
558
561
|
const review = await reviewRepo.getLocalReviewById(reviewId);
|
|
559
562
|
|
|
563
|
+
// Persist the diff so the web UI can display it and the manual
|
|
564
|
+
// tour/summary buttons work (including after a restart). The MCP path
|
|
565
|
+
// previously stored the diff only in analysis_runs, leaving no
|
|
566
|
+
// `local_diffs` row.
|
|
567
|
+
//
|
|
568
|
+
// Use the review's recorded scope — not the default-scope wrapper — so
|
|
569
|
+
// re-using a review that was moved to `staged` or `branch` scope does
|
|
570
|
+
// not clobber its durable row with a narrower default-scope patch. Set
|
|
571
|
+
// the in-memory cache alongside the DB row so the cache-first readers
|
|
572
|
+
// in routes/local.js don't keep serving a stale entry. Non-fatal:
|
|
573
|
+
// matches the analysis-push path.
|
|
574
|
+
try {
|
|
575
|
+
const { start: scopeStart, end: scopeEnd } = reviewScope(review);
|
|
576
|
+
const diffResult = await generateScopedDiff(
|
|
577
|
+
localPath,
|
|
578
|
+
scopeStart,
|
|
579
|
+
scopeEnd,
|
|
580
|
+
review.local_base_branch || null
|
|
581
|
+
);
|
|
582
|
+
const digest = await computeScopedDigest(localPath, scopeStart, scopeEnd);
|
|
583
|
+
localReviewDiffs.set(reviewId, { diff: diffResult.diff, stats: diffResult.stats, digest });
|
|
584
|
+
await reviewRepo.saveLocalDiff(reviewId, { diff: diffResult.diff, stats: diffResult.stats, digest });
|
|
585
|
+
} catch (diffError) {
|
|
586
|
+
logger.warn(`Could not generate or persist diff for local review ${reviewId}: ${diffError.message}`);
|
|
587
|
+
}
|
|
588
|
+
|
|
560
589
|
// Resolve provider and model
|
|
561
590
|
const repoSettings = repository ? await repoSettingsRepo.getRepoSettings(repository) : null;
|
|
562
591
|
const provider = process.env.PAIR_REVIEW_PROVIDER || repoSettings?.default_provider || config.default_provider || config.provider || 'claude';
|
|
@@ -640,7 +669,9 @@ function createMCPServer(db, options = {}) {
|
|
|
640
669
|
headSha: localHeadSha
|
|
641
670
|
});
|
|
642
671
|
|
|
643
|
-
// Launch analysis asynchronously (skipRunCreation since we created the record above)
|
|
672
|
+
// Launch analysis asynchronously (skipRunCreation since we created the record above).
|
|
673
|
+
// Local mode has no associated GitHub PR, so githubClient is intentionally
|
|
674
|
+
// omitted — the analyzer drops the GitHub dedup section when no client is supplied.
|
|
644
675
|
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) })
|
|
645
676
|
.then(result => handleAnalysisCompletion(analysisId, runId, result, async (r) => {
|
|
646
677
|
if (r.summary) {
|
|
@@ -762,6 +793,18 @@ function createMCPServer(db, options = {}) {
|
|
|
762
793
|
const progressCallback = createProgressCallback(analysisId);
|
|
763
794
|
const tier = resolveTier(args.tier);
|
|
764
795
|
|
|
796
|
+
// Build a GitHubClient so the analyzer can pre-fetch existing PR
|
|
797
|
+
// review comments when args.excludePrevious.github is set. Uses
|
|
798
|
+
// the repo's binding so alt-host repos route to the right host
|
|
799
|
+
// with the right token.
|
|
800
|
+
const prAnalysisBinding = resolveHostBinding(repository, config);
|
|
801
|
+
const prAnalysisGithubClient = prAnalysisBinding.token
|
|
802
|
+
? new GitHubClient(prAnalysisBinding)
|
|
803
|
+
: undefined;
|
|
804
|
+
if (prAnalysisGithubClient) {
|
|
805
|
+
logger.debug(`analyzer githubClient wired for ${owner}/${repo}#${prNumber} (mcp)`);
|
|
806
|
+
}
|
|
807
|
+
|
|
765
808
|
logger.log('MCP', `Starting PR analysis: PR #${prNumber} in ${repository}, runId=${runId}`, 'magenta');
|
|
766
809
|
|
|
767
810
|
// Create DB analysis_runs record just before launching so it's queryable for polling
|
|
@@ -779,7 +822,7 @@ function createMCPServer(db, options = {}) {
|
|
|
779
822
|
});
|
|
780
823
|
|
|
781
824
|
// Launch analysis asynchronously (skipRunCreation since we created the record above)
|
|
782
|
-
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) })
|
|
825
|
+
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), githubClient: prAnalysisGithubClient })
|
|
783
826
|
.then(result => handleAnalysisCompletion(analysisId, runId, result, async (r) => {
|
|
784
827
|
try { await prMetadataRepo.updateLastAiRunId(prMetadata.id, r.runId); } catch (_) { /* ignore */ }
|
|
785
828
|
if (r.summary) {
|