@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.
@@ -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": "@in-the-loop-labs/pair-review",
3
- "version": "2.6.2",
3
+ "version": "2.6.3",
4
4
  "description": "Your AI-powered code review partner - Close the feedback loop with AI coding agents",
5
5
  "main": "src/server.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pair-review",
3
- "version": "2.6.2",
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.2",
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 {
@@ -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-1, gpt-5.3-codex-fast, gemini-3-flash): Quick analysis
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-1.5',
49
- name: 'Composer 1.5',
50
- tier: 'balanced',
48
+ id: 'composer-2',
49
+ name: 'Composer 2',
50
+ tier: 'thorough',
51
51
  tagline: 'Latest Composer',
52
- description: 'Cursor Composer model—positioned between Sonnet and Opus for multi-file edits',
53
- badge: 'Balanced',
54
- badgeClass: 'badge-balanced'
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-1',
58
- name: 'Composer 1',
57
+ id: 'composer-2-fast',
58
+ name: 'Composer 2 Fast',
59
59
  tier: 'fast',
60
- tagline: 'Original Composer',
61
- description: 'Cursor Composer modelgood for quick multi-file editing workflows',
60
+ tagline: 'Fast Composer',
61
+ description: 'Same intelligence as Composer 2 with lower latencydefault 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
  }
@@ -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);
@@ -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}`