@in-the-loop-labs/pair-review 2.5.0 → 2.6.1

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.
@@ -205,10 +205,13 @@ async function runTask(
205
205
  onUpdate: OnUpdate | undefined,
206
206
  makeDetails: (results: TaskResult[]) => TaskDetails,
207
207
  ): Promise<TaskResult> {
208
- // Build args: full tool access, JSON output, no session persistence
208
+ // Build args: full tool access, JSON output, no session persistence.
209
+ // Skills and extensions are left enabled so subtasks have access to the
210
+ // user's configured environment. --no-prompt-templates is kept because
211
+ // prompt templates can't be triggered in -p mode.
209
212
  const args: string[] = [
210
213
  "--mode", "json", "-p", "--no-session",
211
- "--no-extensions", "--no-skills", "--no-prompt-templates",
214
+ "--no-prompt-templates",
212
215
  "-e", EXTENSION_DIR,
213
216
  ];
214
217
  if (model) {
@@ -0,0 +1 @@
1
+ {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@in-the-loop-labs/pair-review",
3
- "version": "2.5.0",
3
+ "version": "2.6.1",
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": {
@@ -14,6 +14,7 @@
14
14
  "public/",
15
15
  "plugin/",
16
16
  "plugin-code-critic/",
17
+ "config.managed.json",
17
18
  "README.md",
18
19
  "LICENSE"
19
20
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pair-review",
3
- "version": "2.5.0",
3
+ "version": "2.6.1",
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.5.0",
3
+ "version": "2.6.1",
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/index.html CHANGED
@@ -177,6 +177,14 @@
177
177
  color: var(--color-text-primary);
178
178
  }
179
179
 
180
+ .app-version {
181
+ color: var(--color-fg-muted, #656d76);
182
+ font-size: 12px;
183
+ font-weight: 400;
184
+ margin-left: 6px;
185
+ opacity: 0.7;
186
+ }
187
+
180
188
  .header-right {
181
189
  display: flex;
182
190
  align-items: center;
@@ -1089,6 +1097,7 @@
1089
1097
  <path transform="rotate(-50 12 12)" d="M18.178 8c5.096 0 5.096 8 0 8-5.095 0-7.133-8-12.356-8-5.096 0-5.096 8 0 8 5.223 0 7.26-8 12.356-8z"/>
1090
1098
  </svg>
1091
1099
  <span class="logo-text">pair<span class="logo-accent">review</span></span>
1100
+ <span id="app-version" class="app-version"></span>
1092
1101
  </a>
1093
1102
  </div>
1094
1103
  <div class="header-right">
@@ -1092,6 +1092,12 @@
1092
1092
  const config = await response.json();
1093
1093
  updateCommandExamples(config.is_running_via_npx);
1094
1094
 
1095
+ // Display version in header
1096
+ if (config.version) {
1097
+ const versionEl = document.getElementById('app-version');
1098
+ if (versionEl) versionEl.textContent = 'v' + config.version;
1099
+ }
1100
+
1095
1101
  // Expose chat provider config to components (ChatPanel reads these)
1096
1102
  window.__pairReview = window.__pairReview || {};
1097
1103
  window.__pairReview.chatProvider = config.chat_provider || 'pi';
package/public/setup.html CHANGED
@@ -137,6 +137,14 @@
137
137
  color: var(--color-text-primary);
138
138
  }
139
139
 
140
+ .app-version {
141
+ color: var(--color-fg-muted, #656d76);
142
+ font-size: 12px;
143
+ font-weight: 400;
144
+ margin-left: 6px;
145
+ opacity: 0.7;
146
+ }
147
+
140
148
  .setup-target {
141
149
  font-family: var(--font-mono);
142
150
  font-size: 15px;
@@ -463,6 +471,7 @@
463
471
  <path transform="rotate(-50 12 12)" d="M18.178 8c5.096 0 5.096 8 0 8-5.095 0-7.133-8-12.356-8-5.096 0-5.096 8 0 8 5.223 0 7.26-8 12.356-8z"/>
464
472
  </svg>
465
473
  <span class="logo-text">pair<span class="logo-accent">review</span></span>
474
+ <span id="app-version" class="app-version"></span>
466
475
  </a>
467
476
  <div class="setup-target" id="setup-target"></div>
468
477
  <div class="setup-subtitle">Setting up review</div>
@@ -839,6 +848,16 @@
839
848
  });
840
849
  }
841
850
 
851
+ /* ── Display Version ── */
852
+ fetch('/api/config').then(function(r) {
853
+ return r.ok ? r.json() : null;
854
+ }).then(function(config) {
855
+ if (config && config.version) {
856
+ var versionEl = document.getElementById('app-version');
857
+ if (versionEl) versionEl.textContent = 'v' + config.version;
858
+ }
859
+ }).catch(function() { /* non-critical */ });
860
+
842
861
  /* ── Kick Off ── */
843
862
  startSetup();
844
863
  })();
@@ -174,7 +174,14 @@ class ClaudeProvider extends AIProvider {
174
174
  ].join(',');
175
175
  permissionArgs = ['--allowedTools', allowedTools];
176
176
  }
177
- const baseArgs = ['-p', '--verbose', ...cliModelArgs, '--output-format', 'stream-json', ...permissionArgs];
177
+ // Suppress user hooks in analysis subprocesses to avoid side-effects
178
+ // (notifications, confirmations, etc.) firing on every tool call during review.
179
+ // Uses --settings '{"disableAllHooks":true}' since Claude has no --no-hooks flag.
180
+ // Skills and extensions are left enabled so the subprocess has access to the
181
+ // user's configured environment. To disable skills, add --disable-slash-commands
182
+ // to extra_args in provider/model config.
183
+ const hooksArgs = ['--settings', '{"disableAllHooks":true}'];
184
+ const baseArgs = ['-p', '--verbose', ...cliModelArgs, '--output-format', 'stream-json', ...hooksArgs, ...permissionArgs];
178
185
  if (maxBudget) {
179
186
  const budgetNum = parseFloat(maxBudget);
180
187
  if (isNaN(budgetNum) || budgetNum <= 0) {
@@ -23,7 +23,8 @@ const BIN_DIR = path.join(__dirname, '..', '..', 'bin');
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
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
26
+ * - gpt-5.3-codex: Capable agentic coding model with frontier performance and reasoning
27
+ * - gpt-5.4: Latest generation with enhanced reasoning depth
27
28
  */
28
29
  const CODEX_MODELS = [
29
30
  {
@@ -50,7 +51,16 @@ const CODEX_MODELS = [
50
51
  name: 'GPT-5.3 Codex',
51
52
  tier: 'thorough',
52
53
  tagline: 'Deep Review',
53
- description: 'Most capable agentic coding model—combines frontier coding performance with stronger reasoning for deep cross-file analysis.',
54
+ description: 'Capable agentic coding model—combines frontier coding performance with strong reasoning for cross-file analysis.',
55
+ badge: 'Thorough',
56
+ badgeClass: 'badge-power'
57
+ },
58
+ {
59
+ id: 'gpt-5.4',
60
+ name: 'GPT-5.4',
61
+ tier: 'thorough',
62
+ tagline: 'Latest Gen',
63
+ description: 'Latest generation model with enhanced reasoning depth for complex architectural reviews.',
54
64
  badge: 'Most Thorough',
55
65
  badgeClass: 'badge-power'
56
66
  }
@@ -143,6 +143,24 @@ const CURSOR_AGENT_MODELS = [
143
143
  description: 'Deep analysis with extended thinking—Cursor default for maximum review quality',
144
144
  badge: 'Most Thorough',
145
145
  badgeClass: 'badge-power'
146
+ },
147
+ {
148
+ id: 'gpt-5.4-high',
149
+ name: 'GPT-5.4 High',
150
+ tier: 'thorough',
151
+ tagline: 'Latest OpenAI',
152
+ description: 'Latest generation with high reasoning effort—strong for complex architectural reviews',
153
+ badge: 'Latest Gen',
154
+ badgeClass: 'badge-power'
155
+ },
156
+ {
157
+ id: 'gpt-5.4-medium',
158
+ name: 'GPT-5.4',
159
+ tier: 'thorough',
160
+ tagline: 'Latest Gen',
161
+ description: 'Latest generation at medium reasoning depth',
162
+ badge: 'Latest',
163
+ badgeClass: 'badge-power'
146
164
  }
147
165
  ];
148
166
 
@@ -189,24 +189,23 @@ class PiProvider extends AIProvider {
189
189
  // --no-session: Each pi invocation is an ephemeral analysis — there's no need to
190
190
  // persist session state between runs. Set PAIR_REVIEW_PI_SESSION=1
191
191
  // to enable session saving for debugging (sessions saved to ~/.pi/sessions/).
192
- // --no-skills: Skills are disabled by default to keep runs deterministic. A skill can
193
- // still be loaded via `--skill` in model-specific `extra_args` if needed.
194
-
195
192
  // Build args: base args + built-in extra_args + provider extra_args + model extra_args
196
193
  // In yolo mode, omit --tools entirely to allow all tools (including edit, write)
197
194
  // The task extension is loaded to give the model a subagent tool for delegating
198
195
  // work to isolated subprocesses, preserving the main context window.
199
- // --no-extensions prevents auto-discovery of other extensions.
200
- // --no-skills and --no-prompt-templates keep the subprocess focused.
196
+ // --no-prompt-templates: prompt templates can't be triggered in -p mode, so suppress
197
+ // them to avoid wasting context. Skills and extensions are left enabled so the
198
+ // subprocess has access to the user's configured environment. To disable them,
199
+ // add --no-skills or --no-extensions to extra_args in provider/model config.
201
200
  const sessionArgs = process.env.PAIR_REVIEW_PI_SESSION ? [] : ['--no-session'];
202
201
  let baseArgs;
203
202
  if (configOverrides.yolo) {
204
203
  baseArgs = ['-p', '--mode', 'json', ...cliModelArgs, ...sessionArgs,
205
- '--no-extensions', '--no-skills', '--no-prompt-templates',
204
+ '--no-prompt-templates',
206
205
  '-e', TASK_EXTENSION_DIR];
207
206
  } else {
208
207
  baseArgs = ['-p', '--mode', 'json', ...cliModelArgs, '--tools', 'read,bash,grep,find,ls', ...sessionArgs,
209
- '--no-extensions', '--no-skills', '--no-prompt-templates',
208
+ '--no-prompt-templates',
210
209
  '-e', TASK_EXTENSION_DIR];
211
210
  }
212
211
  const builtInArgs = builtIn?.extra_args || [];
@@ -273,12 +273,9 @@ class PiBridge extends EventEmitter {
273
273
  }
274
274
 
275
275
  // Load extensions via -e (e.g., task extension for subagent delegation).
276
- // --no-extensions prevents auto-discovery; only explicitly listed ones load.
277
- if (this.extensions.length > 0) {
278
- args.push('--no-extensions');
279
- for (const ext of this.extensions) {
280
- args.push('-e', ext);
281
- }
276
+ // These are additive — the user's auto-discovered extensions remain available.
277
+ for (const ext of this.extensions) {
278
+ args.push('-e', ext);
282
279
  }
283
280
 
284
281
  return args;
package/src/config.js CHANGED
@@ -13,6 +13,7 @@ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
13
13
  const CONFIG_LOCAL_FILE = path.join(CONFIG_DIR, 'config.local.json');
14
14
  const CONFIG_EXAMPLE_FILE = path.join(CONFIG_DIR, 'config.example.json');
15
15
  const PACKAGE_ROOT = path.join(__dirname, '..');
16
+ const MANAGED_CONFIG_FILE = path.join(PACKAGE_ROOT, 'config.managed.json');
16
17
 
17
18
  const DEFAULT_CONFIG = {
18
19
  github_token: "",
@@ -175,6 +176,7 @@ async function loadConfig() {
175
176
 
176
177
  const localDir = path.join(process.cwd(), '.pair-review');
177
178
  const sources = [
179
+ { path: MANAGED_CONFIG_FILE, label: 'managed config', required: false },
178
180
  { path: CONFIG_FILE, label: 'global config', required: true },
179
181
  { path: CONFIG_LOCAL_FILE, label: 'global local config', required: false },
180
182
  { path: path.join(localDir, 'config.json'), label: 'project config', required: false },
@@ -183,22 +185,26 @@ async function loadConfig() {
183
185
 
184
186
  let mergedConfig = { ...DEFAULT_CONFIG };
185
187
  let isFirstRun = false;
188
+ let hasManagedConfig = false;
186
189
 
187
190
  for (const source of sources) {
188
191
  try {
189
192
  const data = await fs.readFile(source.path, 'utf8');
190
193
  const parsed = JSON.parse(data);
194
+ if (source.label === 'managed config' && Object.keys(parsed).length > 0) {
195
+ hasManagedConfig = true;
196
+ }
191
197
  mergedConfig = deepMerge(mergedConfig, parsed);
192
198
  } catch (error) {
193
199
  if (error.code === 'ENOENT') {
194
- if (source.required) {
200
+ if (source.required && !hasManagedConfig) {
195
201
  // Global config doesn't exist — create it with defaults
196
202
  const config = { ...DEFAULT_CONFIG };
197
203
  await saveConfig(config);
198
204
  logger.debug(`Created default config file: ${CONFIG_FILE}`);
199
205
  isFirstRun = true;
200
206
  }
201
- // Optional files: skip silently
207
+ // Optional files or managed-config-present: skip silently
202
208
  } else if (error instanceof SyntaxError) {
203
209
  if (source.required) {
204
210
  console.error(`Invalid configuration file at ~/.pair-review/config.json`);
@@ -21,6 +21,7 @@ const {
21
21
  } = require('../ai');
22
22
  const { normalizeRepository } = require('../utils/paths');
23
23
  const { isRunningViaNpx, saveConfig } = require('../config');
24
+ const { version } = require('../../package.json');
24
25
  const { getAllChatProviders, getAllCachedChatAvailability } = require('../chat/chat-providers');
25
26
  const { PRESETS } = require('../utils/comment-formatter');
26
27
  const logger = require('../utils/logger');
@@ -42,6 +43,7 @@ router.get('/api/config', (req, res) => {
42
43
 
43
44
  // Only return safe configuration values (not secrets like github_token)
44
45
  res.json({
46
+ version,
45
47
  theme: config.theme || 'light',
46
48
  comment_button_action: config.comment_button_action || 'submit',
47
49
  comment_format: config.comment_format || 'legacy',