@in-the-loop-labs/pair-review 1.4.1 → 1.4.2
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/js/index.js +26 -63
- package/src/ai/claude-provider.js +3 -25
- package/src/ai/codex-provider.js +13 -13
- package/src/ai/copilot-provider.js +42 -21
- package/src/ai/cursor-agent-provider.js +51 -15
- package/src/ai/gemini-provider.js +7 -14
- package/src/ai/opencode-provider.js +3 -3
- package/src/ai/pi-provider.js +3 -3
- package/src/ai/provider.js +19 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pair-review",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
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": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
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/js/index.js
CHANGED
|
@@ -460,7 +460,9 @@
|
|
|
460
460
|
// ─── Local Review Start ─────────────────────────────────────────────────────
|
|
461
461
|
|
|
462
462
|
/**
|
|
463
|
-
* Handle start local review form submission
|
|
463
|
+
* Handle start local review form submission.
|
|
464
|
+
* Navigates to the setup page which shows step-by-step progress,
|
|
465
|
+
* matching the flow used when reviews are started from the MCP/CLI.
|
|
464
466
|
* @param {Event} event - Form submit event
|
|
465
467
|
*/
|
|
466
468
|
async function handleStartLocal(event) {
|
|
@@ -479,29 +481,9 @@
|
|
|
479
481
|
return;
|
|
480
482
|
}
|
|
481
483
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const response = await fetch('/api/local/start', {
|
|
486
|
-
method: 'POST',
|
|
487
|
-
headers: { 'Content-Type': 'application/json' },
|
|
488
|
-
body: JSON.stringify({ path: pathValue })
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
const data = await response.json();
|
|
492
|
-
|
|
493
|
-
if (!response.ok) {
|
|
494
|
-
throw new Error(data.error || 'Failed to start local review');
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
setFormLoading('local', true, 'Redirecting to review...');
|
|
498
|
-
window.location.href = data.reviewUrl;
|
|
499
|
-
|
|
500
|
-
} catch (error) {
|
|
501
|
-
console.error('Error starting local review:', error);
|
|
502
|
-
setFormLoading('local', false);
|
|
503
|
-
showError('local', error.message || 'An unexpected error occurred. Please try again.');
|
|
504
|
-
}
|
|
484
|
+
// Navigate to the setup page which shows step-by-step progress
|
|
485
|
+
// The /local?path= route serves setup.html which handles the full setup flow
|
|
486
|
+
window.location.href = '/local?path=' + encodeURIComponent(pathValue);
|
|
505
487
|
}
|
|
506
488
|
|
|
507
489
|
// ─── Browse Directory ──────────────────────────────────────────────────────
|
|
@@ -849,7 +831,10 @@
|
|
|
849
831
|
}
|
|
850
832
|
|
|
851
833
|
/**
|
|
852
|
-
* Handle start review form submission
|
|
834
|
+
* Handle start review form submission.
|
|
835
|
+
* Parses the PR URL, then navigates to the PR route which serves the
|
|
836
|
+
* setup page with step-by-step progress for new PRs, or the review page
|
|
837
|
+
* directly for PRs that already exist in the database.
|
|
853
838
|
* @param {Event} event - Form submit event
|
|
854
839
|
*/
|
|
855
840
|
async function handleStartReview(event) {
|
|
@@ -881,44 +866,9 @@
|
|
|
881
866
|
return;
|
|
882
867
|
}
|
|
883
868
|
|
|
884
|
-
//
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
try {
|
|
888
|
-
// Call the API to create the worktree
|
|
889
|
-
const response = await fetch('/api/worktrees/create', {
|
|
890
|
-
method: 'POST',
|
|
891
|
-
headers: {
|
|
892
|
-
'Content-Type': 'application/json'
|
|
893
|
-
},
|
|
894
|
-
body: JSON.stringify({
|
|
895
|
-
owner: parsed.owner,
|
|
896
|
-
repo: parsed.repo,
|
|
897
|
-
prNumber: parsed.prNumber
|
|
898
|
-
})
|
|
899
|
-
});
|
|
900
|
-
|
|
901
|
-
const data = await response.json();
|
|
902
|
-
|
|
903
|
-
if (!response.ok) {
|
|
904
|
-
throw new Error(data.error || 'Failed to create worktree');
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
if (!data.success) {
|
|
908
|
-
throw new Error(data.error || 'Failed to create worktree');
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
// Update loading text before redirect
|
|
912
|
-
setFormLoading('pr', true, 'Redirecting to review...');
|
|
913
|
-
|
|
914
|
-
// Redirect to the review page
|
|
915
|
-
window.location.href = data.reviewUrl;
|
|
916
|
-
|
|
917
|
-
} catch (error) {
|
|
918
|
-
console.error('Error starting review:', error);
|
|
919
|
-
setFormLoading('pr', false);
|
|
920
|
-
showError('pr', error.message || 'An unexpected error occurred. Please try again.');
|
|
921
|
-
}
|
|
869
|
+
// Navigate to the PR route which serves setup.html (with step-by-step progress)
|
|
870
|
+
// for new PRs, or pr.html directly for PRs already in the database
|
|
871
|
+
window.location.href = '/pr/' + encodeURIComponent(parsed.owner) + '/' + encodeURIComponent(parsed.repo) + '/' + encodeURIComponent(parsed.prNumber);
|
|
922
872
|
}
|
|
923
873
|
|
|
924
874
|
// ─── Config & Command Examples ──────────────────────────────────────────────
|
|
@@ -1068,4 +1018,17 @@
|
|
|
1068
1018
|
// natively triggers form submission.
|
|
1069
1019
|
});
|
|
1070
1020
|
|
|
1021
|
+
// ─── bfcache Restoration ───────────────────────────────────────────────────
|
|
1022
|
+
|
|
1023
|
+
// When the browser restores this page from bfcache (e.g. user hits the back
|
|
1024
|
+
// button after navigating away), any in-progress loading state on the forms
|
|
1025
|
+
// will still be visible because the DOM snapshot is preserved as-is. Reset
|
|
1026
|
+
// both forms so the user is not stuck with a disabled input and spinner.
|
|
1027
|
+
window.addEventListener('pageshow', function (event) {
|
|
1028
|
+
if (event.persisted) {
|
|
1029
|
+
setFormLoading('pr', false);
|
|
1030
|
+
setFormLoading('local', false);
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1071
1034
|
})();
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const { spawn } = require('child_process');
|
|
10
|
-
const { AIProvider, registerProvider } = require('./provider');
|
|
10
|
+
const { AIProvider, registerProvider, quoteShellArgs } = require('./provider');
|
|
11
11
|
const logger = require('../utils/logger');
|
|
12
12
|
const { extractJSON } = require('../utils/json-extractor');
|
|
13
13
|
const { CancellationError, isAnalysisCancelled } = require('../routes/shared');
|
|
@@ -178,7 +178,7 @@ class ClaudeProvider extends AIProvider {
|
|
|
178
178
|
|
|
179
179
|
if (this.useShell) {
|
|
180
180
|
const allArgs = [...baseArgs, ...extraArgs];
|
|
181
|
-
this.command = `${claudeCmd} ${
|
|
181
|
+
this.command = `${claudeCmd} ${quoteShellArgs(allArgs).join(' ')}`;
|
|
182
182
|
this.args = [];
|
|
183
183
|
} else {
|
|
184
184
|
this.command = claudeCmd;
|
|
@@ -186,28 +186,6 @@ class ClaudeProvider extends AIProvider {
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
/**
|
|
190
|
-
* Quote shell-sensitive arguments for safe shell execution.
|
|
191
|
-
* Any arg containing characters that could be interpreted by the shell
|
|
192
|
-
* (brackets, parentheses, commas, etc.) is wrapped in single quotes
|
|
193
|
-
* with internal single quotes escaped using the POSIX pattern.
|
|
194
|
-
*
|
|
195
|
-
* @param {string[]} args - Array of CLI arguments
|
|
196
|
-
* @returns {string[]} Args with shell-sensitive values quoted
|
|
197
|
-
* @private
|
|
198
|
-
*/
|
|
199
|
-
_quoteShellArgs(args) {
|
|
200
|
-
return args.map((arg, i) => {
|
|
201
|
-
const prevArg = args[i - 1];
|
|
202
|
-
if (prevArg === '--allowedTools' || prevArg === '--model') {
|
|
203
|
-
if (/[][*?(){}$!&|;<>,\s']/.test(arg)) {
|
|
204
|
-
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return arg;
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
189
|
/**
|
|
212
190
|
* Resolve model configuration by looking up built-in and config override definitions.
|
|
213
191
|
* Consolidates the CLAUDE_MODELS.find() and configOverrides.models.find() lookups
|
|
@@ -486,7 +464,7 @@ class ClaudeProvider extends AIProvider {
|
|
|
486
464
|
const args = ['-p', ...cliModelArgs, ...extraArgs];
|
|
487
465
|
|
|
488
466
|
if (useShell) {
|
|
489
|
-
const quotedArgs =
|
|
467
|
+
const quotedArgs = quoteShellArgs(args);
|
|
490
468
|
return {
|
|
491
469
|
command: `${claudeCmd} ${quotedArgs.join(' ')}`,
|
|
492
470
|
args: [],
|
package/src/ai/codex-provider.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const { spawn } = require('child_process');
|
|
11
|
-
const { AIProvider, registerProvider } = require('./provider');
|
|
11
|
+
const { AIProvider, registerProvider, quoteShellArgs } = require('./provider');
|
|
12
12
|
const logger = require('../utils/logger');
|
|
13
13
|
const { extractJSON } = require('../utils/json-extractor');
|
|
14
14
|
const { CancellationError, isAnalysisCancelled } = require('../routes/shared');
|
|
@@ -22,8 +22,8 @@ const BIN_DIR = path.join(__dirname, '..', '..', 'bin');
|
|
|
22
22
|
*
|
|
23
23
|
* Based on OpenAI Codex Models guide (developers.openai.com/codex/models)
|
|
24
24
|
* - gpt-5.1-codex-mini: Smaller, cost-effective variant for quick scans
|
|
25
|
-
* - gpt-5.
|
|
26
|
-
* - gpt-5.
|
|
25
|
+
* - gpt-5.2-codex: Advanced coding model for everyday reviews, good reasoning/cost balance
|
|
26
|
+
* - gpt-5.3-codex: Most capable agentic coding model with frontier performance and reasoning
|
|
27
27
|
*/
|
|
28
28
|
const CODEX_MODELS = [
|
|
29
29
|
{
|
|
@@ -36,21 +36,21 @@ const CODEX_MODELS = [
|
|
|
36
36
|
badgeClass: 'badge-speed'
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
|
-
id: 'gpt-5.
|
|
40
|
-
name: 'GPT-5.
|
|
39
|
+
id: 'gpt-5.2-codex',
|
|
40
|
+
name: 'GPT-5.2 Codex',
|
|
41
41
|
tier: 'balanced',
|
|
42
42
|
tagline: 'Best Balance',
|
|
43
|
-
description: 'Strong everyday reviewer—
|
|
43
|
+
description: 'Strong everyday reviewer—good reasoning and code understanding for PR-sized changes without top-tier cost.',
|
|
44
44
|
badge: 'Recommended',
|
|
45
45
|
badgeClass: 'badge-recommended',
|
|
46
46
|
default: true
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
|
-
id: 'gpt-5.
|
|
50
|
-
name: 'GPT-5.
|
|
49
|
+
id: 'gpt-5.3-codex',
|
|
50
|
+
name: 'GPT-5.3 Codex',
|
|
51
51
|
tier: 'thorough',
|
|
52
52
|
tagline: 'Deep Review',
|
|
53
|
-
description: 'Most capable
|
|
53
|
+
description: 'Most capable agentic coding model—combines frontier coding performance with stronger reasoning for deep cross-file analysis.',
|
|
54
54
|
badge: 'Most Thorough',
|
|
55
55
|
badgeClass: 'badge-power'
|
|
56
56
|
}
|
|
@@ -65,7 +65,7 @@ class CodexProvider extends AIProvider {
|
|
|
65
65
|
* @param {Object} configOverrides.env - Additional environment variables
|
|
66
66
|
* @param {Object[]} configOverrides.models - Custom model definitions
|
|
67
67
|
*/
|
|
68
|
-
constructor(model = 'gpt-5.
|
|
68
|
+
constructor(model = 'gpt-5.2-codex', configOverrides = {}) {
|
|
69
69
|
super(model);
|
|
70
70
|
|
|
71
71
|
// Command precedence: ENV > config > default
|
|
@@ -116,7 +116,7 @@ class CodexProvider extends AIProvider {
|
|
|
116
116
|
|
|
117
117
|
if (this.useShell) {
|
|
118
118
|
// In shell mode, build full command string with args
|
|
119
|
-
this.command = `${codexCmd} ${[...baseArgs, ...providerArgs, ...modelArgs].join(' ')}`;
|
|
119
|
+
this.command = `${codexCmd} ${quoteShellArgs([...baseArgs, ...providerArgs, ...modelArgs]).join(' ')}`;
|
|
120
120
|
this.args = [];
|
|
121
121
|
} else {
|
|
122
122
|
this.command = codexCmd;
|
|
@@ -577,7 +577,7 @@ class CodexProvider extends AIProvider {
|
|
|
577
577
|
|
|
578
578
|
if (useShell) {
|
|
579
579
|
return {
|
|
580
|
-
command: `${codexCmd} ${args.join(' ')}`,
|
|
580
|
+
command: `${codexCmd} ${quoteShellArgs(args).join(' ')}`,
|
|
581
581
|
args: [],
|
|
582
582
|
useShell: true,
|
|
583
583
|
promptViaStdin: true
|
|
@@ -676,7 +676,7 @@ class CodexProvider extends AIProvider {
|
|
|
676
676
|
}
|
|
677
677
|
|
|
678
678
|
static getDefaultModel() {
|
|
679
|
-
return 'gpt-5.
|
|
679
|
+
return 'gpt-5.2-codex';
|
|
680
680
|
}
|
|
681
681
|
|
|
682
682
|
static getInstallInstructions() {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const { spawn } = require('child_process');
|
|
11
|
-
const { AIProvider, registerProvider } = require('./provider');
|
|
11
|
+
const { AIProvider, registerProvider, quoteShellArgs } = require('./provider');
|
|
12
12
|
const logger = require('../utils/logger');
|
|
13
13
|
const { extractJSON } = require('../utils/json-extractor');
|
|
14
14
|
const { CancellationError, isAnalysisCancelled } = require('../routes/shared');
|
|
@@ -21,42 +21,63 @@ const BIN_DIR = path.join(__dirname, '..', '..', 'bin');
|
|
|
21
21
|
*
|
|
22
22
|
* GitHub Copilot CLI supports multiple AI models including OpenAI,
|
|
23
23
|
* Anthropic, and Google models via the --model flag.
|
|
24
|
+
* Available models (as of Feb 2026): claude-haiku-4.5, claude-sonnet-4.5,
|
|
25
|
+
* gemini-3-pro-preview, gpt-5.2-codex, claude-opus-4.5,
|
|
26
|
+
* claude-opus-4.6. Default is claude-sonnet-4.5.
|
|
24
27
|
*/
|
|
25
28
|
const COPILOT_MODELS = [
|
|
26
29
|
{
|
|
27
|
-
id: '
|
|
28
|
-
name: '
|
|
30
|
+
id: 'claude-haiku-4.5',
|
|
31
|
+
name: 'Claude Haiku 4.5',
|
|
29
32
|
tier: 'fast',
|
|
30
33
|
tagline: 'Quick Scan',
|
|
31
|
-
description: 'Rapid feedback for obvious issues and
|
|
34
|
+
description: 'Rapid feedback for obvious issues, style checks, and simple logic errors',
|
|
32
35
|
badge: 'Speedy',
|
|
33
36
|
badgeClass: 'badge-speed'
|
|
34
37
|
},
|
|
35
38
|
{
|
|
36
|
-
id: '
|
|
37
|
-
name: '
|
|
39
|
+
id: 'claude-sonnet-4.5',
|
|
40
|
+
name: 'Claude Sonnet 4.5',
|
|
38
41
|
tier: 'balanced',
|
|
39
42
|
tagline: 'Reliable Review',
|
|
40
|
-
description: '
|
|
43
|
+
description: 'Copilot default—strong code understanding with excellent quality-to-cost ratio',
|
|
41
44
|
badge: 'Recommended',
|
|
42
45
|
badgeClass: 'badge-recommended',
|
|
43
46
|
default: true
|
|
44
47
|
},
|
|
45
48
|
{
|
|
46
|
-
id: '
|
|
47
|
-
name: '
|
|
48
|
-
tier: '
|
|
49
|
-
tagline: '
|
|
50
|
-
description: '
|
|
51
|
-
badge: '
|
|
52
|
-
badgeClass: 'badge-
|
|
49
|
+
id: 'gemini-3-pro-preview',
|
|
50
|
+
name: 'Gemini 3 Pro',
|
|
51
|
+
tier: 'balanced',
|
|
52
|
+
tagline: 'Strong Alternative',
|
|
53
|
+
description: "Google's most capable model—strong reasoning for cross-file analysis",
|
|
54
|
+
badge: 'Balanced',
|
|
55
|
+
badgeClass: 'badge-balanced'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'gpt-5.2-codex',
|
|
59
|
+
name: 'GPT-5.2 Codex',
|
|
60
|
+
tier: 'balanced',
|
|
61
|
+
tagline: 'Alternative View',
|
|
62
|
+
description: 'OpenAI code-specialized model—different perspective for cross-file analysis',
|
|
63
|
+
badge: 'Balanced',
|
|
64
|
+
badgeClass: 'badge-balanced'
|
|
53
65
|
},
|
|
54
66
|
{
|
|
55
67
|
id: 'claude-opus-4.5',
|
|
56
68
|
name: 'Claude Opus 4.5',
|
|
57
|
-
tier: '
|
|
58
|
-
tagline: '
|
|
59
|
-
description: '
|
|
69
|
+
tier: 'thorough',
|
|
70
|
+
tagline: 'Deep Analysis',
|
|
71
|
+
description: 'Highly capable model for critical code reviews—strong reasoning for security and architecture',
|
|
72
|
+
badge: 'Premium',
|
|
73
|
+
badgeClass: 'badge-premium'
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'claude-opus-4.6',
|
|
77
|
+
name: 'Claude Opus 4.6',
|
|
78
|
+
tier: 'thorough',
|
|
79
|
+
tagline: 'Most Capable',
|
|
80
|
+
description: 'Most capable model for critical code reviews—deep reasoning for security and architecture',
|
|
60
81
|
badge: 'Premium',
|
|
61
82
|
badgeClass: 'badge-premium'
|
|
62
83
|
}
|
|
@@ -71,7 +92,7 @@ class CopilotProvider extends AIProvider {
|
|
|
71
92
|
* @param {Object} configOverrides.env - Additional environment variables
|
|
72
93
|
* @param {Object[]} configOverrides.models - Custom model definitions
|
|
73
94
|
*/
|
|
74
|
-
constructor(model = '
|
|
95
|
+
constructor(model = 'claude-sonnet-4.5', configOverrides = {}) {
|
|
75
96
|
super(model);
|
|
76
97
|
|
|
77
98
|
// Command precedence: ENV > config > default
|
|
@@ -191,7 +212,7 @@ class CopilotProvider extends AIProvider {
|
|
|
191
212
|
// Escape the prompt for shell
|
|
192
213
|
const escapedPrompt = prompt.replace(/'/g, "'\\''");
|
|
193
214
|
// Build: copilot --model X --deny-tool ... -s -p 'prompt'
|
|
194
|
-
fullCommand = `${this.command} ${this.baseArgs.join(' ')} -p '${escapedPrompt}'`;
|
|
215
|
+
fullCommand = `${this.command} ${quoteShellArgs(this.baseArgs).join(' ')} -p '${escapedPrompt}'`;
|
|
195
216
|
fullArgs = [];
|
|
196
217
|
} else {
|
|
197
218
|
// Build args array: --model X --deny-tool ... -s -p <prompt>
|
|
@@ -359,7 +380,7 @@ class CopilotProvider extends AIProvider {
|
|
|
359
380
|
// Use stdin for prompt - safer than command args for arbitrary content
|
|
360
381
|
if (useShell) {
|
|
361
382
|
return {
|
|
362
|
-
command: `${copilotCmd} ${args.join(' ')}`,
|
|
383
|
+
command: `${copilotCmd} ${quoteShellArgs(args).join(' ')}`,
|
|
363
384
|
args: [],
|
|
364
385
|
useShell: true,
|
|
365
386
|
promptViaStdin: true
|
|
@@ -441,7 +462,7 @@ class CopilotProvider extends AIProvider {
|
|
|
441
462
|
}
|
|
442
463
|
|
|
443
464
|
static getDefaultModel() {
|
|
444
|
-
return '
|
|
465
|
+
return 'claude-sonnet-4.5';
|
|
445
466
|
}
|
|
446
467
|
|
|
447
468
|
static getInstallInstructions() {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const { spawn } = require('child_process');
|
|
19
|
-
const { AIProvider, registerProvider } = require('./provider');
|
|
19
|
+
const { AIProvider, registerProvider, quoteShellArgs } = require('./provider');
|
|
20
20
|
const logger = require('../utils/logger');
|
|
21
21
|
const { extractJSON } = require('../utils/json-extractor');
|
|
22
22
|
const { CancellationError, isAnalysisCancelled } = require('../routes/shared');
|
|
@@ -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 (gpt-5.
|
|
34
|
-
* - balanced (sonnet-4.5-thinking, gemini-3-pro): Recommended for most reviews
|
|
35
|
-
* - thorough (gpt-5.
|
|
33
|
+
* - fast (composer-1, gpt-5.3-codex-fast, gemini-3-flash): Quick analysis
|
|
34
|
+
* - balanced (composer-1.5, sonnet-4.5-thinking, gemini-3-pro): Recommended for most reviews
|
|
35
|
+
* - thorough (gpt-5.3-codex-high, opus-4.5-thinking, opus-4.6-thinking): Deep analysis for complex code
|
|
36
36
|
*/
|
|
37
37
|
const CURSOR_AGENT_MODELS = [
|
|
38
38
|
{
|
|
@@ -45,20 +45,47 @@ const CURSOR_AGENT_MODELS = [
|
|
|
45
45
|
badgeClass: 'badge-speed'
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
|
-
id: '
|
|
49
|
-
name: '
|
|
48
|
+
id: 'composer-1.5',
|
|
49
|
+
name: 'Composer 1.5',
|
|
50
|
+
tier: 'balanced',
|
|
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'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'composer-1',
|
|
58
|
+
name: 'Composer 1',
|
|
59
|
+
tier: 'fast',
|
|
60
|
+
tagline: 'Original Composer',
|
|
61
|
+
description: 'Cursor Composer model—good for quick multi-file editing workflows',
|
|
62
|
+
badge: 'Fast',
|
|
63
|
+
badgeClass: 'badge-speed'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'gpt-5.3-codex-fast',
|
|
67
|
+
name: 'GPT-5.3 Codex Fast',
|
|
50
68
|
tier: 'fast',
|
|
51
69
|
tagline: 'Lightning Fast',
|
|
52
|
-
description: '
|
|
70
|
+
description: 'Latest code-specialized model optimized for speed—quick scans for obvious issues',
|
|
53
71
|
badge: 'Fastest',
|
|
54
72
|
badgeClass: 'badge-speed'
|
|
55
73
|
},
|
|
74
|
+
{
|
|
75
|
+
id: 'gemini-3-flash',
|
|
76
|
+
name: 'Gemini 3 Flash',
|
|
77
|
+
tier: 'fast',
|
|
78
|
+
tagline: 'Fast & Capable',
|
|
79
|
+
description: 'High SWE-bench scores at a fraction of the cost—great for quick reviews',
|
|
80
|
+
badge: 'Fast',
|
|
81
|
+
badgeClass: 'badge-speed'
|
|
82
|
+
},
|
|
56
83
|
{
|
|
57
84
|
id: 'sonnet-4.5-thinking',
|
|
58
85
|
name: 'Claude 4.5 Sonnet (Thinking)',
|
|
59
86
|
tier: 'balanced',
|
|
60
87
|
tagline: 'Best Balance',
|
|
61
|
-
description: 'Extended thinking for thorough analysis',
|
|
88
|
+
description: 'Extended thinking for thorough analysis with excellent quality-to-cost ratio',
|
|
62
89
|
badge: 'Recommended',
|
|
63
90
|
badgeClass: 'badge-recommended',
|
|
64
91
|
default: true
|
|
@@ -68,16 +95,16 @@ const CURSOR_AGENT_MODELS = [
|
|
|
68
95
|
name: 'Gemini 3 Pro',
|
|
69
96
|
tier: 'balanced',
|
|
70
97
|
tagline: 'Strong Alternative',
|
|
71
|
-
description: "Google's flagship model for code review",
|
|
98
|
+
description: "Google's flagship model for code review—strong agentic and vibe coding capabilities",
|
|
72
99
|
badge: 'Balanced',
|
|
73
100
|
badgeClass: 'badge-balanced'
|
|
74
101
|
},
|
|
75
102
|
{
|
|
76
|
-
id: 'gpt-5.
|
|
77
|
-
name: 'GPT-5.
|
|
103
|
+
id: 'gpt-5.3-codex-high',
|
|
104
|
+
name: 'GPT-5.3 Codex High',
|
|
78
105
|
tier: 'thorough',
|
|
79
106
|
tagline: 'Deep Code Analysis',
|
|
80
|
-
description: "OpenAI's
|
|
107
|
+
description: "OpenAI's latest and most capable for complex code review with deep reasoning",
|
|
81
108
|
badge: 'Thorough',
|
|
82
109
|
badgeClass: 'badge-power'
|
|
83
110
|
},
|
|
@@ -85,8 +112,17 @@ const CURSOR_AGENT_MODELS = [
|
|
|
85
112
|
id: 'opus-4.5-thinking',
|
|
86
113
|
name: 'Claude 4.5 Opus (Thinking)',
|
|
87
114
|
tier: 'thorough',
|
|
115
|
+
tagline: 'Deep Analysis',
|
|
116
|
+
description: 'Deep analysis with extended thinking for complex code reviews',
|
|
117
|
+
badge: 'Thorough',
|
|
118
|
+
badgeClass: 'badge-power'
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'opus-4.6-thinking',
|
|
122
|
+
name: 'Claude 4.6 Opus (Thinking)',
|
|
123
|
+
tier: 'thorough',
|
|
88
124
|
tagline: 'Most Capable',
|
|
89
|
-
description: 'Deep analysis with extended thinking for
|
|
125
|
+
description: 'Deep analysis with extended thinking—Cursor default for maximum review quality',
|
|
90
126
|
badge: 'Most Thorough',
|
|
91
127
|
badgeClass: 'badge-power'
|
|
92
128
|
}
|
|
@@ -159,7 +195,7 @@ class CursorAgentProvider extends AIProvider {
|
|
|
159
195
|
|
|
160
196
|
if (this.useShell) {
|
|
161
197
|
// In shell mode, build full command string with args
|
|
162
|
-
this.command = `${agentCmd} ${[...baseArgs, ...providerArgs, ...modelArgs].join(' ')}`;
|
|
198
|
+
this.command = `${agentCmd} ${quoteShellArgs([...baseArgs, ...providerArgs, ...modelArgs]).join(' ')}`;
|
|
163
199
|
this.args = [];
|
|
164
200
|
} else {
|
|
165
201
|
this.command = agentCmd;
|
|
@@ -662,7 +698,7 @@ class CursorAgentProvider extends AIProvider {
|
|
|
662
698
|
// For extraction, we pass the prompt via stdin
|
|
663
699
|
if (useShell) {
|
|
664
700
|
return {
|
|
665
|
-
command: `${agentCmd} ${args.join(' ')}`,
|
|
701
|
+
command: `${agentCmd} ${quoteShellArgs(args).join(' ')}`,
|
|
666
702
|
args: [],
|
|
667
703
|
useShell: true,
|
|
668
704
|
promptViaStdin: true
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const { spawn } = require('child_process');
|
|
10
|
-
const { AIProvider, registerProvider } = require('./provider');
|
|
10
|
+
const { AIProvider, registerProvider, quoteShellArgs } = require('./provider');
|
|
11
11
|
const logger = require('../utils/logger');
|
|
12
12
|
const { extractJSON } = require('../utils/json-extractor');
|
|
13
13
|
const { CancellationError, isAnalysisCancelled } = require('../routes/shared');
|
|
@@ -25,7 +25,7 @@ const GEMINI_MODELS = [
|
|
|
25
25
|
name: '3.0 Flash',
|
|
26
26
|
tier: 'fast',
|
|
27
27
|
tagline: 'Rapid Sanity Check',
|
|
28
|
-
description: '
|
|
28
|
+
description: 'Fast and capable at a fraction of the cost of larger models',
|
|
29
29
|
badge: 'Quick Look',
|
|
30
30
|
badgeClass: 'badge-speed'
|
|
31
31
|
},
|
|
@@ -34,7 +34,7 @@ const GEMINI_MODELS = [
|
|
|
34
34
|
name: '2.5 Pro',
|
|
35
35
|
tier: 'balanced',
|
|
36
36
|
tagline: 'Standard PR Review',
|
|
37
|
-
description: '
|
|
37
|
+
description: 'Strong reasoning with large context window—reliable for everyday code reviews',
|
|
38
38
|
badge: 'Daily Driver',
|
|
39
39
|
badgeClass: 'badge-recommended',
|
|
40
40
|
default: true
|
|
@@ -44,7 +44,7 @@ const GEMINI_MODELS = [
|
|
|
44
44
|
name: '3.0 Pro',
|
|
45
45
|
tier: 'thorough',
|
|
46
46
|
tagline: 'Architectural Audit',
|
|
47
|
-
description: '
|
|
47
|
+
description: 'Most intelligent Gemini model—advanced reasoning for deep architectural analysis',
|
|
48
48
|
badge: 'Deep Dive',
|
|
49
49
|
badgeClass: 'badge-power'
|
|
50
50
|
}
|
|
@@ -156,16 +156,9 @@ class GeminiProvider extends AIProvider {
|
|
|
156
156
|
|
|
157
157
|
if (this.useShell) {
|
|
158
158
|
// In shell mode, build full command string with args
|
|
159
|
-
// Quote
|
|
159
|
+
// Quote all args to prevent shell interpretation of special characters
|
|
160
160
|
// (commas, parentheses in patterns like "run_shell_command(git diff)")
|
|
161
|
-
|
|
162
|
-
// The allowed-tools value follows the --allowed-tools flag
|
|
163
|
-
if (baseArgs[i - 1] === '--allowed-tools') {
|
|
164
|
-
return `'${arg}'`;
|
|
165
|
-
}
|
|
166
|
-
return arg;
|
|
167
|
-
});
|
|
168
|
-
this.command = `${geminiCmd} ${[...quotedBaseArgs, ...providerArgs, ...modelArgs].join(' ')}`;
|
|
161
|
+
this.command = `${geminiCmd} ${quoteShellArgs([...baseArgs, ...providerArgs, ...modelArgs]).join(' ')}`;
|
|
169
162
|
this.args = [];
|
|
170
163
|
} else {
|
|
171
164
|
this.command = geminiCmd;
|
|
@@ -616,7 +609,7 @@ class GeminiProvider extends AIProvider {
|
|
|
616
609
|
// For extraction, we pass the prompt via stdin
|
|
617
610
|
if (useShell) {
|
|
618
611
|
return {
|
|
619
|
-
command: `${geminiCmd} ${args.join(' ')}`,
|
|
612
|
+
command: `${geminiCmd} ${quoteShellArgs(args).join(' ')}`,
|
|
620
613
|
args: [],
|
|
621
614
|
useShell: true,
|
|
622
615
|
promptViaStdin: true
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
const path = require('path');
|
|
15
15
|
const { spawn } = require('child_process');
|
|
16
|
-
const { AIProvider, registerProvider } = require('./provider');
|
|
16
|
+
const { AIProvider, registerProvider, quoteShellArgs } = require('./provider');
|
|
17
17
|
const logger = require('../utils/logger');
|
|
18
18
|
const { extractJSON } = require('../utils/json-extractor');
|
|
19
19
|
const { CancellationError, isAnalysisCancelled } = require('../routes/shared');
|
|
@@ -114,7 +114,7 @@ class OpenCodeProvider extends AIProvider {
|
|
|
114
114
|
let fullArgs;
|
|
115
115
|
|
|
116
116
|
if (this.useShell) {
|
|
117
|
-
fullCommand = `${this.opencodeCmd} ${this.baseArgs.join(' ')}`;
|
|
117
|
+
fullCommand = `${this.opencodeCmd} ${quoteShellArgs(this.baseArgs).join(' ')}`;
|
|
118
118
|
fullArgs = [];
|
|
119
119
|
} else {
|
|
120
120
|
fullCommand = this.opencodeCmd;
|
|
@@ -554,7 +554,7 @@ class OpenCodeProvider extends AIProvider {
|
|
|
554
554
|
// OpenCode reads from stdin when no positional message arguments are provided
|
|
555
555
|
if (useShell) {
|
|
556
556
|
return {
|
|
557
|
-
command: `${opencodeCmd} ${args.join(' ')}`,
|
|
557
|
+
command: `${opencodeCmd} ${quoteShellArgs(args).join(' ')}`,
|
|
558
558
|
args: [],
|
|
559
559
|
useShell: true,
|
|
560
560
|
promptViaStdin: true
|
package/src/ai/pi-provider.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
const path = require('path');
|
|
20
20
|
const { spawn } = require('child_process');
|
|
21
|
-
const { AIProvider, registerProvider } = require('./provider');
|
|
21
|
+
const { AIProvider, registerProvider, quoteShellArgs } = require('./provider');
|
|
22
22
|
const logger = require('../utils/logger');
|
|
23
23
|
const { extractJSON } = require('../utils/json-extractor');
|
|
24
24
|
const { CancellationError, isAnalysisCancelled } = require('../routes/shared');
|
|
@@ -253,7 +253,7 @@ class PiProvider extends AIProvider {
|
|
|
253
253
|
let fullArgs;
|
|
254
254
|
|
|
255
255
|
if (this.useShell) {
|
|
256
|
-
fullCommand = `${this.piCmd} ${this.baseArgs.join(' ')}`;
|
|
256
|
+
fullCommand = `${this.piCmd} ${quoteShellArgs(this.baseArgs).join(' ')}`;
|
|
257
257
|
fullArgs = [];
|
|
258
258
|
} else {
|
|
259
259
|
fullCommand = this.piCmd;
|
|
@@ -745,7 +745,7 @@ class PiProvider extends AIProvider {
|
|
|
745
745
|
// Pi reads from stdin when using -p with no positional message arguments
|
|
746
746
|
if (useShell) {
|
|
747
747
|
return {
|
|
748
|
-
command: `${piCmd} ${args.join(' ')}`,
|
|
748
|
+
command: `${piCmd} ${quoteShellArgs(args).join(' ')}`,
|
|
749
749
|
args: [],
|
|
750
750
|
useShell: true,
|
|
751
751
|
promptViaStdin: true,
|
package/src/ai/provider.js
CHANGED
|
@@ -14,6 +14,24 @@ const { extractJSON } = require('../utils/json-extractor');
|
|
|
14
14
|
// Directory containing bin scripts (git-diff-lines, etc.)
|
|
15
15
|
const BIN_DIR = path.join(__dirname, '..', '..', 'bin');
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Quote shell-sensitive arguments for safe shell execution.
|
|
19
|
+
* Any arg containing characters that could be interpreted by the shell
|
|
20
|
+
* (brackets, parentheses, commas, etc.) is wrapped in single quotes
|
|
21
|
+
* with internal single quotes escaped using the POSIX pattern.
|
|
22
|
+
*
|
|
23
|
+
* @param {string[]} args - Array of CLI arguments
|
|
24
|
+
* @returns {string[]} Args with shell-sensitive values quoted
|
|
25
|
+
*/
|
|
26
|
+
function quoteShellArgs(args) {
|
|
27
|
+
return args.map(arg => {
|
|
28
|
+
if (/[[\]*?(){}$!&|;<>,\s'"\\`#~]/.test(arg)) {
|
|
29
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
30
|
+
}
|
|
31
|
+
return arg;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
17
35
|
/**
|
|
18
36
|
* Model tier definitions - provider-agnostic tiers that map to specific models
|
|
19
37
|
*/
|
|
@@ -639,6 +657,7 @@ async function testProviderAvailability(providerId, timeout = 10000) {
|
|
|
639
657
|
module.exports = {
|
|
640
658
|
AIProvider,
|
|
641
659
|
MODEL_TIERS,
|
|
660
|
+
quoteShellArgs,
|
|
642
661
|
registerProvider,
|
|
643
662
|
getProviderClass,
|
|
644
663
|
getRegisteredProviderIds,
|