@in-the-loop-labs/pair-review 2.6.2 → 2.6.3
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/bin/git-diff-lines +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/skills/analyze/scripts/git-diff-lines +1 -1
- package/public/css/pr.css +7 -0
- package/src/ai/analyzer.js +5 -5
- package/src/ai/cursor-agent-provider.js +21 -12
- package/src/chat/prompt-builder.js +3 -3
- package/src/git/worktree.js +3 -2
- package/src/local-review.js +1 -1
- package/src/main.js +2 -2
- package/src/routes/github-collections.js +2 -2
- package/src/routes/pr.js +2 -2
- package/src/utils/diff-file-list.js +1 -1
package/bin/git-diff-lines
CHANGED
|
@@ -68,7 +68,7 @@ function executeGitDiff(args, cwd = null) {
|
|
|
68
68
|
if (cwd) {
|
|
69
69
|
spawnOptions.cwd = cwd;
|
|
70
70
|
}
|
|
71
|
-
const gitProcess = spawn('git', ['diff', ...args], spawnOptions);
|
|
71
|
+
const gitProcess = spawn('git', ['diff', '--no-ext-diff', ...args], spawnOptions);
|
|
72
72
|
|
|
73
73
|
let stdout = '';
|
|
74
74
|
let stderr = '';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pair-review",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.3",
|
|
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": "2.6.
|
|
3
|
+
"version": "2.6.3",
|
|
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",
|
|
@@ -84,7 +84,7 @@ GIT_CMD=(git)
|
|
|
84
84
|
if [[ -n "$GIT_CWD" ]]; then
|
|
85
85
|
GIT_CMD+=(-C "$GIT_CWD")
|
|
86
86
|
fi
|
|
87
|
-
GIT_CMD+=(diff)
|
|
87
|
+
GIT_CMD+=(diff --no-ext-diff)
|
|
88
88
|
# Guard: expanding an empty array with set -u fails in Bash < 4.4
|
|
89
89
|
if [[ ${#GIT_ARGS[@]} -gt 0 ]]; then
|
|
90
90
|
GIT_CMD+=("${GIT_ARGS[@]}")
|
package/public/css/pr.css
CHANGED
|
@@ -1587,6 +1587,13 @@
|
|
|
1587
1587
|
color: #f85149;
|
|
1588
1588
|
}
|
|
1589
1589
|
|
|
1590
|
+
/* When collapsed, the wrapper is only as tall as the header, so the header's
|
|
1591
|
+
sticky positioning can't push it below the toolbar. Adding scroll-margin
|
|
1592
|
+
ensures scrollIntoView() lands the header below the sticky toolbar. */
|
|
1593
|
+
.d2h-file-wrapper.collapsed {
|
|
1594
|
+
scroll-margin-top: var(--toolbar-height, 0px);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1590
1597
|
/* Hide diff content when collapsed */
|
|
1591
1598
|
.d2h-file-wrapper.collapsed .d2h-file-body,
|
|
1592
1599
|
.d2h-file-wrapper.collapsed .d2h-diff-table {
|
package/src/ai/analyzer.js
CHANGED
|
@@ -659,7 +659,7 @@ Do NOT create suggestions for any files not in this list. If you cannot find iss
|
|
|
659
659
|
async getChangedFilesList(worktreePath, prMetadata) {
|
|
660
660
|
try {
|
|
661
661
|
const { stdout } = await execPromise(
|
|
662
|
-
`git diff ${prMetadata.base_sha}...${prMetadata.head_sha} --name-only`,
|
|
662
|
+
`git diff --no-ext-diff ${prMetadata.base_sha}...${prMetadata.head_sha} --name-only`,
|
|
663
663
|
{ cwd: worktreePath }
|
|
664
664
|
);
|
|
665
665
|
return stdout.trim().split('\n').filter(f => f.length > 0);
|
|
@@ -685,7 +685,7 @@ Do NOT create suggestions for any files not in this list. If you cannot find iss
|
|
|
685
685
|
try {
|
|
686
686
|
// Get modified tracked files (unstaged only - staged files are excluded by design)
|
|
687
687
|
const { stdout: unstaged } = await execPromise(
|
|
688
|
-
'git diff --name-only',
|
|
688
|
+
'git diff --no-ext-diff --name-only',
|
|
689
689
|
{ cwd: localPath }
|
|
690
690
|
);
|
|
691
691
|
|
|
@@ -777,7 +777,7 @@ The following files are marked as generated in .gitattributes and should be SKIP
|
|
|
777
777
|
${generatedPatterns.map(p => `- ${p}`).join('\n')}
|
|
778
778
|
|
|
779
779
|
These are auto-generated files (like package-lock.json, build outputs, etc.) that should not be reviewed.
|
|
780
|
-
When running git diff, you can exclude these with: git diff ${'{base}'}...${'{head}'} -- ':!pattern' for each pattern.
|
|
780
|
+
When running git diff, you can exclude these with: git diff --no-ext-diff ${'{base}'}...${'{head}'} -- ':!pattern' for each pattern.
|
|
781
781
|
Or simply ignore any changes to files matching these patterns in your analysis.
|
|
782
782
|
`;
|
|
783
783
|
}
|
|
@@ -983,10 +983,10 @@ ${prMetadata.description || '(No description provided)'}
|
|
|
983
983
|
const isLocal = prMetadata.reviewType === 'local';
|
|
984
984
|
if (isLocal) {
|
|
985
985
|
// For local mode, diff against HEAD to see working directory changes
|
|
986
|
-
return suffix ? `git diff HEAD ${suffix}` : 'git diff HEAD';
|
|
986
|
+
return suffix ? `git diff --no-ext-diff HEAD ${suffix}` : 'git diff --no-ext-diff HEAD';
|
|
987
987
|
}
|
|
988
988
|
// For PR mode, diff between base and head commits
|
|
989
|
-
const baseCmd = `git diff ${prMetadata.base_sha}...${prMetadata.head_sha}`;
|
|
989
|
+
const baseCmd = `git diff --no-ext-diff ${prMetadata.base_sha}...${prMetadata.head_sha}`;
|
|
990
990
|
return suffix ? `${baseCmd} ${suffix}` : baseCmd;
|
|
991
991
|
}
|
|
992
992
|
|
|
@@ -30,9 +30,9 @@ const BIN_DIR = path.join(__dirname, '..', '..', 'bin');
|
|
|
30
30
|
*
|
|
31
31
|
* Tier structure:
|
|
32
32
|
* - free (auto): Cursor's default auto-routing model
|
|
33
|
-
* - fast (composer-
|
|
33
|
+
* - fast (composer-2-fast, gpt-5.3-codex-fast, gemini-3-flash): Quick analysis
|
|
34
34
|
* - balanced (composer-1.5, sonnet-4.6-thinking, sonnet-4.5-thinking, gemini-3.1-pro): Recommended for most reviews
|
|
35
|
-
* - thorough (gpt-5.3-codex-high, gpt-5.3-codex-xhigh, opus-4.5-thinking, opus-4.6-thinking): Deep analysis for complex code
|
|
35
|
+
* - thorough (composer-2, gpt-5.3-codex-high, gpt-5.3-codex-xhigh, opus-4.5-thinking, opus-4.6-thinking): Deep analysis for complex code
|
|
36
36
|
*/
|
|
37
37
|
const CURSOR_AGENT_MODELS = [
|
|
38
38
|
{
|
|
@@ -45,23 +45,32 @@ const CURSOR_AGENT_MODELS = [
|
|
|
45
45
|
badgeClass: 'badge-speed'
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
|
-
id: 'composer-
|
|
49
|
-
name: 'Composer
|
|
50
|
-
tier: '
|
|
48
|
+
id: 'composer-2',
|
|
49
|
+
name: 'Composer 2',
|
|
50
|
+
tier: 'thorough',
|
|
51
51
|
tagline: 'Latest Composer',
|
|
52
|
-
description: '
|
|
53
|
-
badge: '
|
|
54
|
-
badgeClass: 'badge-
|
|
52
|
+
description: 'Frontier-level coding model trained with compaction-in-the-loop RL—strong on long-horizon tasks requiring hundreds of actions',
|
|
53
|
+
badge: 'Latest',
|
|
54
|
+
badgeClass: 'badge-power'
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
|
-
id: 'composer-
|
|
58
|
-
name: 'Composer
|
|
57
|
+
id: 'composer-2-fast',
|
|
58
|
+
name: 'Composer 2 Fast',
|
|
59
59
|
tier: 'fast',
|
|
60
|
-
tagline: '
|
|
61
|
-
description: '
|
|
60
|
+
tagline: 'Fast Composer',
|
|
61
|
+
description: 'Same intelligence as Composer 2 with lower latency—default for interactive Cursor sessions',
|
|
62
62
|
badge: 'Fast',
|
|
63
63
|
badgeClass: 'badge-speed'
|
|
64
64
|
},
|
|
65
|
+
{
|
|
66
|
+
id: 'composer-1.5',
|
|
67
|
+
name: 'Composer 1.5',
|
|
68
|
+
tier: 'balanced',
|
|
69
|
+
tagline: 'Previous Composer',
|
|
70
|
+
description: 'Previous generation Cursor Composer model—positioned between Sonnet and Opus for multi-file edits',
|
|
71
|
+
badge: 'Previous Gen',
|
|
72
|
+
badgeClass: 'badge-balanced'
|
|
73
|
+
},
|
|
65
74
|
{
|
|
66
75
|
id: 'gpt-5.3-codex-fast',
|
|
67
76
|
name: 'GPT-5.3 Codex Fast',
|
|
@@ -124,8 +124,8 @@ function buildReviewContext(review, prData) {
|
|
|
124
124
|
lines.push(`This is a local code review for: ${name}`);
|
|
125
125
|
lines.push('');
|
|
126
126
|
lines.push('## Viewing Code Changes');
|
|
127
|
-
lines.push('The changes under review are **unstaged and untracked local changes**. Staged changes (`git diff --cached`) are treated as already reviewed.');
|
|
128
|
-
lines.push('To see the diff under review: `git diff`');
|
|
127
|
+
lines.push('The changes under review are **unstaged and untracked local changes**. Staged changes (`git diff --no-ext-diff --cached`) are treated as already reviewed.');
|
|
128
|
+
lines.push('To see the diff under review: `git diff --no-ext-diff`');
|
|
129
129
|
lines.push('Do NOT use `git diff HEAD~1` or `git log` — those show committed history, not the changes under review.');
|
|
130
130
|
} else {
|
|
131
131
|
const parts = [];
|
|
@@ -146,7 +146,7 @@ function buildReviewContext(review, prData) {
|
|
|
146
146
|
lines.push('');
|
|
147
147
|
lines.push('## Viewing Code Changes');
|
|
148
148
|
lines.push(`The changes under review are the diff between base commit \`${prData.base_sha.substring(0, 8)}\` and head commit \`${prData.head_sha.substring(0, 8)}\`.`);
|
|
149
|
-
lines.push(`To see the full diff: \`git diff ${prData.base_sha}...${prData.head_sha}\``);
|
|
149
|
+
lines.push(`To see the full diff: \`git diff --no-ext-diff ${prData.base_sha}...${prData.head_sha}\``);
|
|
150
150
|
lines.push('Do NOT use `git diff HEAD~1` or `git diff` without arguments — those do not show the PR changes.');
|
|
151
151
|
}
|
|
152
152
|
}
|
package/src/git/worktree.js
CHANGED
|
@@ -536,7 +536,8 @@ class GitWorktreeManager {
|
|
|
536
536
|
// This ensures we compare the exact commits from the PR, even if the base branch has moved
|
|
537
537
|
const diff = await git.diff([
|
|
538
538
|
`${prData.base_sha}...${prData.head_sha}`,
|
|
539
|
-
'--unified=3'
|
|
539
|
+
'--unified=3',
|
|
540
|
+
'--no-ext-diff'
|
|
540
541
|
]);
|
|
541
542
|
|
|
542
543
|
return diff;
|
|
@@ -559,7 +560,7 @@ class GitWorktreeManager {
|
|
|
559
560
|
|
|
560
561
|
// Get file changes with stats using base SHA and head SHA
|
|
561
562
|
// This ensures we get the exact files changed in the PR, even if the base branch has moved
|
|
562
|
-
const diffSummary = await git.diffSummary([`${prData.base_sha}...${prData.head_sha}
|
|
563
|
+
const diffSummary = await git.diffSummary([`${prData.base_sha}...${prData.head_sha}`, '--no-ext-diff']);
|
|
563
564
|
|
|
564
565
|
// Parse .gitattributes to identify generated files
|
|
565
566
|
const gitattributes = await getGeneratedFilePatterns(worktreePath);
|
package/src/local-review.js
CHANGED
|
@@ -658,7 +658,7 @@ async function computeLocalDiffDigest(localPath) {
|
|
|
658
658
|
// Get unstaged diff (the actual content being reviewed)
|
|
659
659
|
let unstagedDiff = '';
|
|
660
660
|
try {
|
|
661
|
-
const result = await execAsync('git diff', {
|
|
661
|
+
const result = await execAsync('git diff --no-ext-diff', {
|
|
662
662
|
cwd: localPath,
|
|
663
663
|
encoding: 'utf8',
|
|
664
664
|
maxBuffer: 50 * 1024 * 1024
|
package/src/main.js
CHANGED
|
@@ -662,10 +662,10 @@ async function performHeadlessReview(args, config, db, flags, options) {
|
|
|
662
662
|
}
|
|
663
663
|
}
|
|
664
664
|
|
|
665
|
-
diff = await git.diff([`${prData.base_sha}...${prData.head_sha}`, '--unified=3']);
|
|
665
|
+
diff = await git.diff([`${prData.base_sha}...${prData.head_sha}`, '--unified=3', '--no-ext-diff']);
|
|
666
666
|
|
|
667
667
|
// Get changed files
|
|
668
|
-
const diffSummary = await git.diffSummary([`${prData.base_sha}...${prData.head_sha}
|
|
668
|
+
const diffSummary = await git.diffSummary([`${prData.base_sha}...${prData.head_sha}`, '--no-ext-diff']);
|
|
669
669
|
const gitattributes = await getGeneratedFilePatterns(worktreePath);
|
|
670
670
|
|
|
671
671
|
changedFiles = diffSummary.files.map(file => {
|
|
@@ -45,7 +45,7 @@ router.post('/api/github/review-requests/refresh', async (req, res) => {
|
|
|
45
45
|
const db = req.app.get('db');
|
|
46
46
|
const client = new GitHubClient(githubToken);
|
|
47
47
|
const user = await client.getAuthenticatedUser();
|
|
48
|
-
const prs = await client.searchPullRequests(`is:pr is:open user-review-requested:${user.login}`);
|
|
48
|
+
const prs = await client.searchPullRequests(`is:pr is:open archived:false user-review-requested:${user.login}`);
|
|
49
49
|
|
|
50
50
|
await withTransaction(db, async () => {
|
|
51
51
|
await run(db, 'DELETE FROM github_pr_cache WHERE collection = ?', ['review-requests']);
|
|
@@ -99,7 +99,7 @@ router.post('/api/github/my-prs/refresh', async (req, res) => {
|
|
|
99
99
|
const db = req.app.get('db');
|
|
100
100
|
const client = new GitHubClient(githubToken);
|
|
101
101
|
const user = await client.getAuthenticatedUser();
|
|
102
|
-
const prs = await client.searchPullRequests(`is:pr is:open author:${user.login}`);
|
|
102
|
+
const prs = await client.searchPullRequests(`is:pr is:open archived:false author:${user.login}`);
|
|
103
103
|
|
|
104
104
|
await withTransaction(db, async () => {
|
|
105
105
|
await run(db, 'DELETE FROM github_pr_cache WHERE collection = ?', ['my-prs']);
|
package/src/routes/pr.js
CHANGED
|
@@ -682,10 +682,10 @@ router.get('/api/pr/:owner/:repo/:number/diff', async (req, res) => {
|
|
|
682
682
|
|
|
683
683
|
if (baseSha && headSha) {
|
|
684
684
|
// Regenerate diff with -w flag to ignore whitespace changes
|
|
685
|
-
diffContent = await git.diff([`${baseSha}...${headSha}`, '--unified=3', '-w']);
|
|
685
|
+
diffContent = await git.diff([`${baseSha}...${headSha}`, '--unified=3', '-w', '--no-ext-diff']);
|
|
686
686
|
|
|
687
687
|
// Regenerate changed files stats with -w flag
|
|
688
|
-
const diffSummary = await git.diffSummary([`${baseSha}...${headSha}`, '-w']);
|
|
688
|
+
const diffSummary = await git.diffSummary([`${baseSha}...${headSha}`, '-w', '--no-ext-diff']);
|
|
689
689
|
const gitattributes = await getGeneratedFilePatterns(worktreePath);
|
|
690
690
|
changedFiles = diffSummary.files.map(file => {
|
|
691
691
|
const resolvedFile = resolveRenamedFile(file.file);
|
|
@@ -37,7 +37,7 @@ async function getDiffFileList(db, review) {
|
|
|
37
37
|
try {
|
|
38
38
|
const opts = { cwd: review.local_path };
|
|
39
39
|
const [{ stdout: unstaged }, { stdout: untracked }] = await Promise.all([
|
|
40
|
-
execPromise('git diff --name-only', opts),
|
|
40
|
+
execPromise('git diff --no-ext-diff --name-only', opts),
|
|
41
41
|
execPromise('git ls-files --others --exclude-standard', opts),
|
|
42
42
|
]);
|
|
43
43
|
const combined = `${unstaged}\n${untracked}`
|