@in-the-loop-labs/pair-review 3.0.4 → 3.0.6
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 +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
- package/public/css/pr.css +1 -5
- package/public/js/pr.js +12 -6
- package/src/database.js +5 -0
- package/src/git/base-branch.js +49 -1
- package/src/routes/local.js +89 -57
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pair-review",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.6",
|
|
4
4
|
"description": "pair-review app integration — Open PRs and local changes in the pair-review web UI, run server-side AI analysis, and address review feedback. Requires the pair-review MCP server.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "in-the-loop-labs",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-critic",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.6",
|
|
4
4
|
"description": "AI-powered code review analysis — Run three-level AI analysis and implement-review-fix loops directly in your coding agent. Works standalone, no server required.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "in-the-loop-labs",
|
package/public/css/pr.css
CHANGED
|
@@ -6000,12 +6000,8 @@ body::before {
|
|
|
6000
6000
|
color: var(--color-accent, #2f81f7);
|
|
6001
6001
|
}
|
|
6002
6002
|
|
|
6003
|
-
/* PR description popover */
|
|
6003
|
+
/* PR description popover - appended to document.body with fixed positioning */
|
|
6004
6004
|
.pr-description-popover {
|
|
6005
|
-
position: absolute;
|
|
6006
|
-
top: calc(100% + 8px);
|
|
6007
|
-
left: 50%;
|
|
6008
|
-
transform: translateX(-50%);
|
|
6009
6005
|
z-index: 1000;
|
|
6010
6006
|
width: min(500px, 90vw);
|
|
6011
6007
|
background: var(--color-bg-primary, #0d1117);
|
package/public/js/pr.js
CHANGED
|
@@ -970,11 +970,8 @@ class PRManager {
|
|
|
970
970
|
const toggle = document.getElementById('pr-description-toggle');
|
|
971
971
|
if (!toggle) return;
|
|
972
972
|
|
|
973
|
-
const wrapper = toggle.closest('.pr-title-wrapper');
|
|
974
|
-
if (!wrapper) return;
|
|
975
|
-
|
|
976
973
|
const closePopover = () => {
|
|
977
|
-
const existing =
|
|
974
|
+
const existing = document.querySelector('.pr-description-popover');
|
|
978
975
|
if (existing) existing.remove();
|
|
979
976
|
toggle.classList.remove('active');
|
|
980
977
|
toggle.setAttribute('aria-expanded', 'false');
|
|
@@ -982,7 +979,7 @@ class PRManager {
|
|
|
982
979
|
|
|
983
980
|
toggle.addEventListener('click', (e) => {
|
|
984
981
|
e.stopPropagation();
|
|
985
|
-
const existing =
|
|
982
|
+
const existing = document.querySelector('.pr-description-popover');
|
|
986
983
|
if (existing) {
|
|
987
984
|
closePopover();
|
|
988
985
|
return;
|
|
@@ -1017,7 +1014,16 @@ class PRManager {
|
|
|
1017
1014
|
|
|
1018
1015
|
popover.append(arrow, header, content);
|
|
1019
1016
|
|
|
1020
|
-
|
|
1017
|
+
// Position relative to the toggle button
|
|
1018
|
+
const rect = toggle.getBoundingClientRect();
|
|
1019
|
+
popover.style.position = 'fixed';
|
|
1020
|
+
popover.style.top = `${rect.bottom + 8}px`;
|
|
1021
|
+
popover.style.left = `${rect.left + rect.width / 2}px`;
|
|
1022
|
+
popover.style.transform = 'translateX(-50%)';
|
|
1023
|
+
|
|
1024
|
+
// Append to document.body to escape overflow:hidden on .header-center
|
|
1025
|
+
document.body.appendChild(popover);
|
|
1026
|
+
|
|
1021
1027
|
toggle.classList.add('active');
|
|
1022
1028
|
toggle.setAttribute('aria-expanded', 'true');
|
|
1023
1029
|
|
package/src/database.js
CHANGED
|
@@ -2755,6 +2755,11 @@ class ReviewRepository {
|
|
|
2755
2755
|
params.push(updates.name);
|
|
2756
2756
|
}
|
|
2757
2757
|
|
|
2758
|
+
if (updates.local_base_branch !== undefined) {
|
|
2759
|
+
setClauses.push('local_base_branch = ?');
|
|
2760
|
+
params.push(updates.local_base_branch);
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2758
2763
|
if (updates.local_head_branch !== undefined) {
|
|
2759
2764
|
setClauses.push('local_head_branch = ?');
|
|
2760
2765
|
params.push(updates.local_head_branch);
|
package/src/git/base-branch.js
CHANGED
|
@@ -170,4 +170,52 @@ function tryDefaultBranch(repoPath, currentBranch, deps) {
|
|
|
170
170
|
return null;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Synchronously detect the default branch for a repository using only
|
|
175
|
+
* local refs (no network I/O).
|
|
176
|
+
*
|
|
177
|
+
* Priority:
|
|
178
|
+
* 1. `git symbolic-ref refs/remotes/origin/HEAD` — reads the local ref
|
|
179
|
+
* that `git clone` sets automatically.
|
|
180
|
+
* 2. Check whether `refs/heads/main` or `refs/heads/master` exist locally.
|
|
181
|
+
*
|
|
182
|
+
* @param {string} localPath - Absolute path to the repository
|
|
183
|
+
* @param {Object} [_deps] - Dependency overrides for testing
|
|
184
|
+
* @returns {string|null} Default branch name, or null if it cannot be determined
|
|
185
|
+
*/
|
|
186
|
+
function getDefaultBranch(localPath, _deps) {
|
|
187
|
+
if (!localPath) return null;
|
|
188
|
+
const deps = { ...defaults, ..._deps };
|
|
189
|
+
|
|
190
|
+
// Try symbolic-ref (set by git clone)
|
|
191
|
+
try {
|
|
192
|
+
const ref = deps.execSync('git symbolic-ref refs/remotes/origin/HEAD', {
|
|
193
|
+
cwd: localPath,
|
|
194
|
+
encoding: 'utf8',
|
|
195
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
196
|
+
}).trim();
|
|
197
|
+
// ref looks like "refs/remotes/origin/main"
|
|
198
|
+
const branch = ref.replace(/^refs\/remotes\/origin\//, '');
|
|
199
|
+
if (branch && branch !== ref) return branch;
|
|
200
|
+
} catch {
|
|
201
|
+
// origin/HEAD not set — fall through to local check
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Fallback: check if main or master exists locally
|
|
205
|
+
for (const candidate of ['main', 'master']) {
|
|
206
|
+
try {
|
|
207
|
+
deps.execSync(`git rev-parse --verify refs/heads/${candidate}`, {
|
|
208
|
+
cwd: localPath,
|
|
209
|
+
encoding: 'utf8',
|
|
210
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
211
|
+
});
|
|
212
|
+
return candidate;
|
|
213
|
+
} catch {
|
|
214
|
+
// Branch doesn't exist
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
module.exports = { detectBaseBranch, getDefaultBranch };
|
package/src/routes/local.js
CHANGED
|
@@ -31,6 +31,9 @@ const { getGeneratedFilePatterns } = require('../git/gitattributes');
|
|
|
31
31
|
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
|
+
const { getProviderClass, createProvider } = require('../ai/provider');
|
|
35
|
+
const { getDefaultBranch } = require('../git/base-branch');
|
|
36
|
+
const { CommentRepository } = require('../database');
|
|
34
37
|
const {
|
|
35
38
|
activeAnalyses,
|
|
36
39
|
localReviewDiffs,
|
|
@@ -68,30 +71,23 @@ function deleteLocalReviewDiff(reviewId) {
|
|
|
68
71
|
|
|
69
72
|
/**
|
|
70
73
|
* Check whether branch scope should be selectable in the scope range selector.
|
|
71
|
-
* Returns true when the branch
|
|
72
|
-
*
|
|
74
|
+
* Returns true when the current branch is a non-default, non-detached branch,
|
|
75
|
+
* or when the scope already includes branch.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} branchName - Current branch name
|
|
78
|
+
* @param {string} scopeStart - Current scope start stop
|
|
79
|
+
* @param {string} localPath - Absolute path to the repository (used to detect the actual default branch)
|
|
73
80
|
*/
|
|
74
|
-
|
|
81
|
+
function isBranchAvailable(branchName, scopeStart, localPath) {
|
|
75
82
|
if (includesBranch(scopeStart)) return true;
|
|
76
|
-
if (!branchName || branchName === 'HEAD' || branchName === 'unknown'
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
enableGraphite: config.enable_graphite === true,
|
|
83
|
-
_deps: depsOverride
|
|
84
|
-
});
|
|
85
|
-
if (detection) {
|
|
86
|
-
// Lazy require to ensure testability via vi.spyOn on the module exports
|
|
87
|
-
const localReview = require('../local-review');
|
|
88
|
-
const commitCount = await localReview.getBranchCommitCount(localPath, detection.baseBranch);
|
|
89
|
-
return commitCount > 0;
|
|
90
|
-
}
|
|
91
|
-
} catch {
|
|
92
|
-
// Non-fatal — branch stop stays disabled
|
|
83
|
+
if (!branchName || branchName === 'HEAD' || branchName === 'unknown') return false;
|
|
84
|
+
|
|
85
|
+
// Detect the default branch using only local refs (no network).
|
|
86
|
+
const defaultBranch = getDefaultBranch(localPath);
|
|
87
|
+
if (defaultBranch) {
|
|
88
|
+
return branchName !== defaultBranch;
|
|
93
89
|
}
|
|
94
|
-
return
|
|
90
|
+
return branchName !== 'main' && branchName !== 'master';
|
|
95
91
|
}
|
|
96
92
|
|
|
97
93
|
/**
|
|
@@ -506,6 +502,7 @@ router.post('/api/local/start', async (req, res) => {
|
|
|
506
502
|
* Get local review metadata
|
|
507
503
|
*/
|
|
508
504
|
router.get('/api/local/:reviewId', async (req, res) => {
|
|
505
|
+
const tEndpoint = Date.now();
|
|
509
506
|
try {
|
|
510
507
|
const reviewId = parseInt(req.params.reviewId);
|
|
511
508
|
|
|
@@ -566,19 +563,14 @@ router.get('/api/local/:reviewId', async (req, res) => {
|
|
|
566
563
|
const baseBranch = review.local_base_branch || null;
|
|
567
564
|
|
|
568
565
|
// When scope does NOT include branch, check for branch detection info
|
|
569
|
-
// Frontend uses this to suggest expanding scope to include branch
|
|
566
|
+
// Frontend uses this to suggest expanding scope to include branch.
|
|
567
|
+
// Only use already-cached results here — never block the response on
|
|
568
|
+
// GitHub API calls. Background detection (after res.json) will populate
|
|
569
|
+
// the cache for subsequent requests.
|
|
570
570
|
let branchInfo = null;
|
|
571
571
|
const cachedDiff = getLocalReviewDiff(reviewId);
|
|
572
572
|
if (!includesBranch(scopeStart) && cachedDiff?.branchInfo) {
|
|
573
573
|
branchInfo = cachedDiff.branchInfo;
|
|
574
|
-
} else if (!includesBranch(scopeStart) && !cachedDiff && review.local_path) {
|
|
575
|
-
// No cache (web UI started session) — run detection on-demand
|
|
576
|
-
const config = req.app.get('config') || {};
|
|
577
|
-
branchInfo = await detectAndBuildBranchInfo(review.local_path, branchName, {
|
|
578
|
-
repository: repositoryName,
|
|
579
|
-
githubToken: getGitHubToken(config),
|
|
580
|
-
enableGraphite: config.enable_graphite === true
|
|
581
|
-
});
|
|
582
574
|
}
|
|
583
575
|
|
|
584
576
|
// Check repo settings for auto_branch_review preference
|
|
@@ -602,13 +594,15 @@ router.get('/api/local/:reviewId', async (req, res) => {
|
|
|
602
594
|
|
|
603
595
|
// Determine if Branch stop should be selectable in the scope range selector.
|
|
604
596
|
// This is independent of branchInfo (which guards on no uncommitted changes).
|
|
605
|
-
// Branch is available when: not detached HEAD, not on default branch
|
|
606
|
-
const branchAvailable = Boolean(branchInfo) ||
|
|
607
|
-
review.local_path, branchName, scopeStart, req.app.get('config') || {}, repositoryName
|
|
608
|
-
);
|
|
597
|
+
// Branch is available when: not detached HEAD, not on default branch.
|
|
598
|
+
const branchAvailable = Boolean(branchInfo) || isBranchAvailable(branchName, scopeStart, review.local_path);
|
|
609
599
|
|
|
610
600
|
// Compute SHA abbreviation length from the repo's git config
|
|
611
601
|
const shaAbbrevLength = getShaAbbrevLength(review.local_path);
|
|
602
|
+
const metadataElapsed = Date.now() - tEndpoint;
|
|
603
|
+
if (metadataElapsed > 200) {
|
|
604
|
+
logger.debug(`[perf] metadata#${reviewId} took ${metadataElapsed}ms (threshold: 200ms)`);
|
|
605
|
+
}
|
|
612
606
|
|
|
613
607
|
res.json({
|
|
614
608
|
id: review.id,
|
|
@@ -630,6 +624,29 @@ router.get('/api/local/:reviewId', async (req, res) => {
|
|
|
630
624
|
updatedAt: review.updated_at
|
|
631
625
|
});
|
|
632
626
|
|
|
627
|
+
// Background: pre-cache base branch detection so set-scope is fast later
|
|
628
|
+
if (!includesBranch(scopeStart) && !review.local_base_branch
|
|
629
|
+
&& branchName && branchName !== 'HEAD' && branchName !== 'unknown'
|
|
630
|
+
&& repositoryName && repositoryName.includes('/')) {
|
|
631
|
+
const bgConfig = req.app.get('config') || {};
|
|
632
|
+
const bgToken = getGitHubToken(bgConfig);
|
|
633
|
+
const bgT0 = Date.now();
|
|
634
|
+
const { detectBaseBranch } = require('../git/base-branch');
|
|
635
|
+
detectBaseBranch(review.local_path, branchName, {
|
|
636
|
+
repository: repositoryName,
|
|
637
|
+
enableGraphite: bgConfig.enable_graphite === true,
|
|
638
|
+
_deps: bgToken ? { getGitHubToken: () => bgToken } : undefined
|
|
639
|
+
}).then(detection => {
|
|
640
|
+
if (detection && detection.baseBranch) {
|
|
641
|
+
return reviewRepo.updateReview(reviewId, { local_base_branch: detection.baseBranch });
|
|
642
|
+
}
|
|
643
|
+
}).then(() => {
|
|
644
|
+
logger.debug(`[perf] metadata#${reviewId} background-detectBaseBranch: ${Date.now() - bgT0}ms`);
|
|
645
|
+
}).catch(err => {
|
|
646
|
+
logger.warn(`Background base branch detection failed: ${err.message}`);
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
633
650
|
// Fire review.loaded hook (session already exists to be fetched by ID)
|
|
634
651
|
const hookConfig = req.app.get('config') || {};
|
|
635
652
|
if (hasHooks('review.loaded', hookConfig)) {
|
|
@@ -704,6 +721,7 @@ router.patch('/api/local/:reviewId/name', async (req, res) => {
|
|
|
704
721
|
* Get local diff
|
|
705
722
|
*/
|
|
706
723
|
router.get('/api/local/:reviewId/diff', async (req, res) => {
|
|
724
|
+
const tEndpoint = Date.now();
|
|
707
725
|
try {
|
|
708
726
|
const reviewId = parseInt(req.params.reviewId);
|
|
709
727
|
|
|
@@ -781,6 +799,10 @@ router.get('/api/local/:reviewId/diff', async (req, res) => {
|
|
|
781
799
|
}
|
|
782
800
|
}
|
|
783
801
|
|
|
802
|
+
const diffElapsed = Date.now() - tEndpoint;
|
|
803
|
+
if (diffElapsed > 200) {
|
|
804
|
+
logger.debug(`[perf] diff#${reviewId} took ${diffElapsed}ms (threshold: 200ms)`);
|
|
805
|
+
}
|
|
784
806
|
res.json({
|
|
785
807
|
diff: diffContent || '',
|
|
786
808
|
generated_files: generatedFiles,
|
|
@@ -805,6 +827,7 @@ router.get('/api/local/:reviewId/diff', async (req, res) => {
|
|
|
805
827
|
* Uses a digest of the diff content for accurate change detection
|
|
806
828
|
*/
|
|
807
829
|
router.get('/api/local/:reviewId/check-stale', async (req, res) => {
|
|
830
|
+
const tEndpoint = Date.now();
|
|
808
831
|
try {
|
|
809
832
|
const reviewId = parseInt(req.params.reviewId);
|
|
810
833
|
|
|
@@ -861,6 +884,10 @@ router.get('/api/local/:reviewId/check-stale', async (req, res) => {
|
|
|
861
884
|
|
|
862
885
|
// When branch is in scope and HEAD changed, early return (existing behavior)
|
|
863
886
|
if (includesBranch(scopeStart) && headShaChanged) {
|
|
887
|
+
const staleEarlyElapsed = Date.now() - tEndpoint;
|
|
888
|
+
if (staleEarlyElapsed > 200) {
|
|
889
|
+
logger.debug(`[perf] check-stale#${reviewId} took ${staleEarlyElapsed}ms (threshold: 200ms)`);
|
|
890
|
+
}
|
|
864
891
|
return res.json({
|
|
865
892
|
isStale: true,
|
|
866
893
|
headShaChanged,
|
|
@@ -918,6 +945,10 @@ router.get('/api/local/:reviewId/check-stale', async (req, res) => {
|
|
|
918
945
|
|
|
919
946
|
const isStale = storedDiffData.digest !== currentDigest;
|
|
920
947
|
|
|
948
|
+
const staleElapsed = Date.now() - tEndpoint;
|
|
949
|
+
if (staleElapsed > 200) {
|
|
950
|
+
logger.debug(`[perf] check-stale#${reviewId} took ${staleElapsed}ms (threshold: 200ms)`);
|
|
951
|
+
}
|
|
921
952
|
res.json({
|
|
922
953
|
isStale,
|
|
923
954
|
storedDigest: storedDiffData.digest,
|
|
@@ -1377,12 +1408,10 @@ router.post('/api/local/:reviewId/refresh', async (req, res) => {
|
|
|
1377
1408
|
|
|
1378
1409
|
// Recompute branchAvailable so the frontend can update the scope selector
|
|
1379
1410
|
// (e.g. after a commit creates the first branch-ahead commit).
|
|
1380
|
-
|
|
1411
|
+
// Lazy require to ensure testability via vi.spyOn on the module exports.
|
|
1381
1412
|
let branchName;
|
|
1382
|
-
try { branchName = await getCurrentBranch(localPath); } catch (_) { branchName = review.local_head_branch || null; }
|
|
1383
|
-
const branchAvailable =
|
|
1384
|
-
localPath, branchName, scopeStart, config, review.repository
|
|
1385
|
-
);
|
|
1413
|
+
try { branchName = await require('../local-review').getCurrentBranch(localPath); } catch (_) { branchName = review.local_head_branch || null; }
|
|
1414
|
+
const branchAvailable = isBranchAvailable(branchName, scopeStart, localPath);
|
|
1386
1415
|
|
|
1387
1416
|
// Non-branch HEAD change: skip diff computation entirely — the old diff is
|
|
1388
1417
|
// preserved until the user decides (via resolve-head-change) what to do.
|
|
@@ -1491,8 +1520,8 @@ router.post('/api/local/:reviewId/resolve-head-change', async (req, res) => {
|
|
|
1491
1520
|
|
|
1492
1521
|
// Persist SHA and branch together in a single write so SQLite only
|
|
1493
1522
|
// ever sees the final identity tuple — no transient intermediate state.
|
|
1494
|
-
await reviewRepo.updateReview(reviewId, { local_head_sha: newHeadSha, local_head_branch: headBranch });
|
|
1495
|
-
logger.log('API', `Updated HEAD SHA and branch on session ${reviewId}`, 'cyan');
|
|
1523
|
+
await reviewRepo.updateReview(reviewId, { local_head_sha: newHeadSha, local_head_branch: headBranch, local_base_branch: null });
|
|
1524
|
+
logger.log('API', `Updated HEAD SHA and branch on session ${reviewId} (cleared cached base branch)`, 'cyan');
|
|
1496
1525
|
|
|
1497
1526
|
// Recompute and persist diff
|
|
1498
1527
|
const scopedResult = await generateScopedDiff(localPath, scopeStart, scopeEnd, review.local_base_branch);
|
|
@@ -1506,10 +1535,7 @@ router.post('/api/local/:reviewId/resolve-head-change', async (req, res) => {
|
|
|
1506
1535
|
|
|
1507
1536
|
// Recompute branchAvailable — the commit may have created the first
|
|
1508
1537
|
// branch-ahead commit, making the Branch scope stop selectable.
|
|
1509
|
-
const
|
|
1510
|
-
const branchAvailable = await checkBranchAvailable(
|
|
1511
|
-
localPath, headBranch, scopeStart, config, review.repository
|
|
1512
|
-
);
|
|
1538
|
+
const branchAvailable = isBranchAvailable(headBranch, scopeStart, localPath);
|
|
1513
1539
|
|
|
1514
1540
|
return res.json({ success: true, action: 'updated', branchAvailable });
|
|
1515
1541
|
}
|
|
@@ -1593,20 +1619,26 @@ router.post('/api/local/:reviewId/set-scope', async (req, res) => {
|
|
|
1593
1619
|
let baseBranch = requestBaseBranch || null;
|
|
1594
1620
|
let currentBranch = null;
|
|
1595
1621
|
if (includesBranch(scopeStart)) {
|
|
1596
|
-
currentBranch = await getCurrentBranch(localPath);
|
|
1622
|
+
currentBranch = await require('../local-review').getCurrentBranch(localPath);
|
|
1597
1623
|
if (!baseBranch) {
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1624
|
+
// Use cached base branch from background detection if available
|
|
1625
|
+
if (review.local_base_branch && review.local_head_branch === currentBranch) {
|
|
1626
|
+
baseBranch = review.local_base_branch;
|
|
1627
|
+
logger.debug(`[perf] set-scope#${reviewId} using cached base branch: ${baseBranch}`);
|
|
1628
|
+
} else {
|
|
1629
|
+
const { detectBaseBranch } = require('../git/base-branch');
|
|
1630
|
+
const config = req.app.get('config') || {};
|
|
1631
|
+
const token = getGitHubToken(config);
|
|
1632
|
+
const detection = await detectBaseBranch(localPath, currentBranch, {
|
|
1633
|
+
repository: review.repository,
|
|
1634
|
+
enableGraphite: config.enable_graphite === true,
|
|
1635
|
+
_deps: token ? { getGitHubToken: () => token } : undefined
|
|
1636
|
+
});
|
|
1637
|
+
if (!detection) {
|
|
1638
|
+
return res.status(400).json({ error: 'Could not detect base branch' });
|
|
1639
|
+
}
|
|
1640
|
+
baseBranch = detection.baseBranch;
|
|
1608
1641
|
}
|
|
1609
|
-
baseBranch = detection.baseBranch;
|
|
1610
1642
|
}
|
|
1611
1643
|
|
|
1612
1644
|
// Validate branch name to prevent shell injection
|