@in-the-loop-labs/pair-review 3.2.0 → 3.2.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 +6 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
- package/public/css/pr.css +20 -1
- package/src/ai/analyzer.js +7 -3
- package/src/ai/executable-provider.js +5 -0
- package/src/ai/pi-provider.js +13 -5
- package/src/ai/provider.js +4 -0
- package/src/chat/chat-providers.js +4 -0
- package/src/chat/pi-bridge.js +8 -0
- package/src/chat/session-manager.js +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@in-the-loop-labs/pair-review",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.2",
|
|
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": {
|
|
@@ -81,5 +81,10 @@
|
|
|
81
81
|
"jsdom": "^29.0.1",
|
|
82
82
|
"supertest": "^7.1.4",
|
|
83
83
|
"vitest": "^4.0.16"
|
|
84
|
+
},
|
|
85
|
+
"pnpm": {
|
|
86
|
+
"onlyBuiltDependencies": [
|
|
87
|
+
"better-sqlite3"
|
|
88
|
+
]
|
|
84
89
|
}
|
|
85
90
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pair-review",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.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": "3.2.
|
|
3
|
+
"version": "3.2.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/css/pr.css
CHANGED
|
@@ -12085,6 +12085,25 @@ body.resizing * {
|
|
|
12085
12085
|
padding: 0;
|
|
12086
12086
|
}
|
|
12087
12087
|
|
|
12088
|
+
.chat-panel__message--assistant .chat-panel__bubble table {
|
|
12089
|
+
border-collapse: collapse;
|
|
12090
|
+
font-size: 12px;
|
|
12091
|
+
margin: 8px 0;
|
|
12092
|
+
overflow-x: auto;
|
|
12093
|
+
display: block;
|
|
12094
|
+
}
|
|
12095
|
+
|
|
12096
|
+
.chat-panel__message--assistant .chat-panel__bubble th,
|
|
12097
|
+
.chat-panel__message--assistant .chat-panel__bubble td {
|
|
12098
|
+
border: 1px solid var(--color-border-primary);
|
|
12099
|
+
padding: 4px 8px;
|
|
12100
|
+
}
|
|
12101
|
+
|
|
12102
|
+
.chat-panel__message--assistant .chat-panel__bubble th {
|
|
12103
|
+
background: var(--color-bg-tertiary);
|
|
12104
|
+
font-weight: 600;
|
|
12105
|
+
}
|
|
12106
|
+
|
|
12088
12107
|
.chat-panel__message--assistant .chat-panel__bubble ul,
|
|
12089
12108
|
.chat-panel__message--assistant .chat-panel__bubble ol {
|
|
12090
12109
|
margin: 4px 0;
|
|
@@ -13287,7 +13306,7 @@ body.resizing * {
|
|
|
13287
13306
|
box-shadow: 0 4px 16px var(--color-shadow, rgba(0, 0, 0, 0.12));
|
|
13288
13307
|
z-index: 100;
|
|
13289
13308
|
padding: 6px 0;
|
|
13290
|
-
max-height:
|
|
13309
|
+
max-height: min(calc(100vh - 120px), 80vh);
|
|
13291
13310
|
overflow-y: auto;
|
|
13292
13311
|
}
|
|
13293
13312
|
|
package/src/ai/analyzer.js
CHANGED
|
@@ -96,7 +96,7 @@ function buildVoiceContext(voice, idx, instructions, progressCallback, db) {
|
|
|
96
96
|
const voiceProvider = isExecutable ? createProvider(voice.provider, voice.model) : null;
|
|
97
97
|
|
|
98
98
|
const voiceTier = voice.tier || 'balanced';
|
|
99
|
-
const voiceTimeout = voice.timeout || 600000;
|
|
99
|
+
const voiceTimeout = voice.timeout || ProviderClass?.defaultTimeout || 600000;
|
|
100
100
|
|
|
101
101
|
// Wrap progress callback with voice-centric metadata
|
|
102
102
|
const voiceProgressCallback = progressCallback ? (update) => {
|
|
@@ -319,7 +319,10 @@ class Analyzer {
|
|
|
319
319
|
const runId = options.runId || uuidv4();
|
|
320
320
|
const { analysisId, skipRunCreation, skipLevel3, reviewerNum, excludePrevious, serverPort } = options;
|
|
321
321
|
const logPrefix = options.logPrefix || '';
|
|
322
|
-
|
|
322
|
+
// Respect provider-configured timeout (e.g. Pi's 15 min, executable providers)
|
|
323
|
+
const ProviderClass = getProviderClass(this.provider);
|
|
324
|
+
const providerTimeout = ProviderClass?.defaultTimeout;
|
|
325
|
+
const executionTimeout = options.timeout || providerTimeout || 600000; // Default 10 minutes
|
|
323
326
|
|
|
324
327
|
// Resolve enabledLevels: prefer explicit option, fall back to skipLevel3 compat
|
|
325
328
|
const enabledLevels = options.enabledLevels
|
|
@@ -3396,6 +3399,7 @@ File-level suggestions should NOT have a line number. They apply to the entire f
|
|
|
3396
3399
|
: voice.customInstructions;
|
|
3397
3400
|
}
|
|
3398
3401
|
|
|
3402
|
+
const VoiceProviderClass = getProviderClass(voice.provider);
|
|
3399
3403
|
voiceTasks.push({
|
|
3400
3404
|
voiceId,
|
|
3401
3405
|
reviewerLabel,
|
|
@@ -3404,7 +3408,7 @@ File-level suggestions should NOT have a line number. They apply to the entire f
|
|
|
3404
3408
|
provider: voice.provider,
|
|
3405
3409
|
model: voice.model,
|
|
3406
3410
|
tier,
|
|
3407
|
-
timeout: voice.timeout || 600000,
|
|
3411
|
+
timeout: voice.timeout || VoiceProviderClass?.defaultTimeout || 600000,
|
|
3408
3412
|
customInstructions: voiceInstructions,
|
|
3409
3413
|
voiceCustomInstructions: voice.customInstructions || null
|
|
3410
3414
|
});
|
|
@@ -522,6 +522,11 @@ function createExecutableProviderClass(id, config) {
|
|
|
522
522
|
ExecProvider.getDefaultModel = () => resolveDefaultModel(models) || models[0]?.id;
|
|
523
523
|
ExecProvider.getInstallInstructions = () => config.installInstructions || `Install ${id}`;
|
|
524
524
|
|
|
525
|
+
// Surface configured timeout so the UI can pre-populate TimeoutSelect
|
|
526
|
+
if (config.timeout != null) {
|
|
527
|
+
ExecProvider.defaultTimeout = config.timeout;
|
|
528
|
+
}
|
|
529
|
+
|
|
525
530
|
// Flags for the system
|
|
526
531
|
ExecProvider.isExecutable = true;
|
|
527
532
|
const caps = config.capabilities || {};
|
package/src/ai/pi-provider.js
CHANGED
|
@@ -130,6 +130,8 @@ class PiProvider extends AIProvider {
|
|
|
130
130
|
* @param {string[]} configOverrides.extra_args - Additional CLI arguments
|
|
131
131
|
* @param {Object} configOverrides.env - Additional environment variables
|
|
132
132
|
* @param {Object[]} configOverrides.models - Custom model definitions
|
|
133
|
+
* @param {boolean} [configOverrides.load_skills=true] - When false, adds --no-skills to suppress skill auto-discovery
|
|
134
|
+
* @param {boolean} [configOverrides.app_extensions=true] - When false, omits the -e task extension flag
|
|
133
135
|
*/
|
|
134
136
|
constructor(model, configOverrides = {}) {
|
|
135
137
|
super(model || 'default');
|
|
@@ -197,19 +199,25 @@ class PiProvider extends AIProvider {
|
|
|
197
199
|
// The task extension is loaded to give the model a subagent tool for delegating
|
|
198
200
|
// work to isolated subprocesses, preserving the main context window.
|
|
199
201
|
// --no-prompt-templates: prompt templates can't be triggered in -p mode, so suppress
|
|
200
|
-
// them to avoid wasting context.
|
|
201
|
-
//
|
|
202
|
-
//
|
|
202
|
+
// them to avoid wasting context.
|
|
203
|
+
// load_skills (default true): when false, adds --no-skills to suppress auto-discovery.
|
|
204
|
+
// Explicit --skill args from built-in models (e.g. multi-model) still load.
|
|
205
|
+
// app_extensions (default true): when false, omits the -e task extension flag.
|
|
206
|
+
// Useful when auto-discovery already loads the extension (e.g. developing pair-review).
|
|
207
|
+
const loadSkills = configOverrides.load_skills !== false;
|
|
208
|
+
const appExtensions = configOverrides.app_extensions !== false;
|
|
203
209
|
const sessionArgs = process.env.PAIR_REVIEW_PI_SESSION ? [] : ['--no-session'];
|
|
210
|
+
const extensionArgs = appExtensions ? ['-e', TASK_EXTENSION_DIR] : [];
|
|
211
|
+
const skillArgs = loadSkills ? [] : ['--no-skills'];
|
|
204
212
|
let baseArgs;
|
|
205
213
|
if (configOverrides.yolo) {
|
|
206
214
|
baseArgs = ['-p', '--mode', 'json', ...cliModelArgs, ...sessionArgs,
|
|
207
215
|
'--no-prompt-templates',
|
|
208
|
-
|
|
216
|
+
...extensionArgs, ...skillArgs];
|
|
209
217
|
} else {
|
|
210
218
|
baseArgs = ['-p', '--mode', 'json', ...cliModelArgs, '--tools', 'read,bash,grep,find,ls', ...sessionArgs,
|
|
211
219
|
'--no-prompt-templates',
|
|
212
|
-
|
|
220
|
+
...extensionArgs, ...skillArgs];
|
|
213
221
|
}
|
|
214
222
|
const builtInArgs = builtIn?.extra_args || [];
|
|
215
223
|
const providerArgs = configOverrides.extra_args || [];
|
package/src/ai/provider.js
CHANGED
|
@@ -532,6 +532,8 @@ function applyConfigOverrides(config) {
|
|
|
532
532
|
installInstructions: providerConfig.installInstructions,
|
|
533
533
|
extra_args: providerConfig.extra_args,
|
|
534
534
|
env: providerConfig.env,
|
|
535
|
+
load_skills: providerConfig.load_skills,
|
|
536
|
+
app_extensions: providerConfig.app_extensions,
|
|
535
537
|
models: AliasClass.getModels() !== BaseClass.getModels() ? AliasClass.getModels() : null
|
|
536
538
|
});
|
|
537
539
|
logger.debug(`Registered aliased provider: ${providerId} (base: ${providerConfig.type})`);
|
|
@@ -557,6 +559,8 @@ function applyConfigOverrides(config) {
|
|
|
557
559
|
installInstructions: providerConfig.installInstructions,
|
|
558
560
|
extra_args: providerConfig.extra_args,
|
|
559
561
|
env: providerConfig.env,
|
|
562
|
+
load_skills: providerConfig.load_skills,
|
|
563
|
+
app_extensions: providerConfig.app_extensions,
|
|
560
564
|
models: processedModels
|
|
561
565
|
});
|
|
562
566
|
}
|
|
@@ -121,6 +121,8 @@ function getChatProvider(id) {
|
|
|
121
121
|
if (overrides.extra_args && Array.isArray(overrides.extra_args)) {
|
|
122
122
|
provider.args = [...provider.args, ...overrides.extra_args];
|
|
123
123
|
}
|
|
124
|
+
if (overrides.load_skills !== undefined) provider.load_skills = overrides.load_skills;
|
|
125
|
+
if (overrides.app_extensions !== undefined) provider.app_extensions = overrides.app_extensions;
|
|
124
126
|
if (provider.command.includes(' ')) {
|
|
125
127
|
provider.useShell = true;
|
|
126
128
|
}
|
|
@@ -142,6 +144,8 @@ function getChatProvider(id) {
|
|
|
142
144
|
if (overrides.extra_args && Array.isArray(overrides.extra_args)) {
|
|
143
145
|
merged.args = [...(merged.args || []), ...overrides.extra_args];
|
|
144
146
|
}
|
|
147
|
+
if (overrides.load_skills !== undefined) merged.load_skills = overrides.load_skills;
|
|
148
|
+
if (overrides.app_extensions !== undefined) merged.app_extensions = overrides.app_extensions;
|
|
145
149
|
// For multi-word commands (e.g. "devx claude"), use shell mode
|
|
146
150
|
if (merged.command && merged.command.includes(' ')) {
|
|
147
151
|
merged.useShell = true;
|
package/src/chat/pi-bridge.js
CHANGED
|
@@ -37,6 +37,7 @@ class PiBridge extends EventEmitter {
|
|
|
37
37
|
* @param {string[]} [options.extensions] - Array of extension directory paths to load via -e
|
|
38
38
|
* @param {string[]} [options.extraArgs] - Extra CLI args to append (e.g., from config extra_args)
|
|
39
39
|
* @param {string} [options.sessionPath] - Path to a session file for resumption
|
|
40
|
+
* @param {boolean} [options.loadSkills] - When false, adds --no-skills to suppress auto-discovery (default: true)
|
|
40
41
|
*/
|
|
41
42
|
constructor(options = {}) {
|
|
42
43
|
super();
|
|
@@ -52,6 +53,7 @@ class PiBridge extends EventEmitter {
|
|
|
52
53
|
this.extensions = options.extensions || [];
|
|
53
54
|
this.extraArgs = options.extraArgs || [];
|
|
54
55
|
this.sessionPath = options.sessionPath || null;
|
|
56
|
+
this.loadSkills = options.loadSkills !== false;
|
|
55
57
|
|
|
56
58
|
this._process = null;
|
|
57
59
|
this._readline = null;
|
|
@@ -290,6 +292,12 @@ class PiBridge extends EventEmitter {
|
|
|
290
292
|
args.push('-e', ext);
|
|
291
293
|
}
|
|
292
294
|
|
|
295
|
+
// Suppress skill auto-discovery when loadSkills is false.
|
|
296
|
+
// Explicit --skill entries above still load.
|
|
297
|
+
if (!this.loadSkills) {
|
|
298
|
+
args.push('--no-skills');
|
|
299
|
+
}
|
|
300
|
+
|
|
293
301
|
// Append extra args from provider config (e.g., extra_args in chat_providers).
|
|
294
302
|
// These go last so they can override earlier flags if needed.
|
|
295
303
|
if (this.extraArgs.length > 0) {
|
|
@@ -575,6 +575,9 @@ class ChatSessionManager {
|
|
|
575
575
|
// which would forward it as `--provider pi` to the Pi CLI. The CLI's --provider flag
|
|
576
576
|
// expects a model provider ("google", "anthropic", etc.) and should only come from
|
|
577
577
|
// explicit user configuration (providerDef.provider).
|
|
578
|
+
// app_extensions (default true): when false, omit pair-review's task extension.
|
|
579
|
+
// load_skills (default true): when false, suppress Pi's skill auto-discovery.
|
|
580
|
+
const appExtensions = def?.app_extensions !== false;
|
|
578
581
|
return new PiBridge({
|
|
579
582
|
...options,
|
|
580
583
|
provider: def?.provider || null,
|
|
@@ -584,7 +587,8 @@ class ChatSessionManager {
|
|
|
584
587
|
env: def?.env,
|
|
585
588
|
useShell: def?.useShell,
|
|
586
589
|
tools: CHAT_TOOLS,
|
|
587
|
-
extensions: [taskExtensionDir],
|
|
590
|
+
extensions: appExtensions ? [taskExtensionDir] : [],
|
|
591
|
+
loadSkills: def?.load_skills,
|
|
588
592
|
});
|
|
589
593
|
}
|
|
590
594
|
|