@in-the-loop-labs/pair-review 3.7.1 → 3.7.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@in-the-loop-labs/pair-review",
3
- "version": "3.7.1",
3
+ "version": "3.7.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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pair-review",
3
- "version": "3.7.1",
3
+ "version": "3.7.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.7.1",
3
+ "version": "3.7.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/pr.js CHANGED
@@ -1690,8 +1690,12 @@ class PRManager {
1690
1690
  label = 'Exit guided tour';
1691
1691
  } else if (this._tourStops && this._tourStops.length > 0) {
1692
1692
  label = 'Start guided tour';
1693
+ } else if (this._toursAutoGenerate === false) {
1694
+ // No stops yet and auto-generation is off: a click kicks off manual
1695
+ // generation (see startOrToggleTour), so the verb is "Generate".
1696
+ label = 'Generate guided tour';
1693
1697
  } else {
1694
- label = 'Start guided tour (none available yet)';
1698
+ label = 'Guided tour (none available yet)';
1695
1699
  }
1696
1700
  btn.title = label;
1697
1701
  btn.setAttribute('aria-label', label);
@@ -19,7 +19,7 @@ const { normalizeRepository } = require('../utils/paths');
19
19
  const { mergeInstructions } = require('../utils/instructions');
20
20
  const { GitWorktreeManager } = require('../git/worktree');
21
21
  const { GitHubClient } = require('../github/client');
22
- const { getGitHubToken, resolveHostBinding, resolveBindingRepositoryFromPR, resolveLoadSkills, buildCouncilProviderOverrides } = require('../config');
22
+ const { getGitHubToken, resolveHostBinding, resolveBindingRepositoryFromPR, resolveRepoOptions, resolveLoadSkills, buildCouncilProviderOverrides } = require('../config');
23
23
  const { setupStackPR } = require('../setup/stack-setup');
24
24
  const Analyzer = require('../ai/analyzer');
25
25
  const { getProviderClass, createProvider } = require('../ai/provider');
@@ -166,6 +166,7 @@ const defaults = {
166
166
  getGitHubToken,
167
167
  resolveHostBinding,
168
168
  resolveBindingRepositoryFromPR,
169
+ resolveRepoOptions,
169
170
  setupStackPR,
170
171
  Analyzer,
171
172
  getProviderClass,
@@ -205,8 +206,23 @@ async function executeStackAnalysis(params) {
205
206
  if (!state) return;
206
207
 
207
208
  try {
209
+ // 0. Resolve the config-binding key once. It drives both the per-repo
210
+ // worktree config (just below) and the host binding (step 3). For
211
+ // monorepo-style `url_pattern` configs this differs from the PR
212
+ // identity `${owner}/${repo}`.
213
+ const bindingRepository = deps.resolveBindingRepositoryFromPR(owner, repo, config);
214
+ // Honor the repo's configured worktree options so stack worktrees match the
215
+ // non-stack path. This carries through:
216
+ // - worktreeConfig (worktree_name_template / worktree_directory) so they
217
+ // don't fall back to pair-review's default naming and location, and
218
+ // - the checkout script + timeout and sparse-checkout inheritance used
219
+ // during per-PR worktree creation (forwarded to createWorktreeForPR).
220
+ // These derive purely from file config, so DB repo_settings (pool config)
221
+ // are not needed here.
222
+ const { worktreeConfig, checkoutScript, checkoutTimeout } = deps.resolveRepoOptions(config, bindingRepository);
223
+
208
224
  // 1. Resolve repositoryPath from trigger worktree
209
- const worktreeManager = new deps.GitWorktreeManager(db);
225
+ const worktreeManager = new deps.GitWorktreeManager(db, worktreeConfig || {});
210
226
  let repositoryPath;
211
227
  try {
212
228
  const owningRepoGit = await worktreeManager.resolveOwningRepo(triggerWorktreePath);
@@ -230,13 +246,13 @@ async function executeStackAnalysis(params) {
230
246
  logger.warn(`Bulk git fetch failed, will fetch per-PR: ${fetchError.message}`);
231
247
  }
232
248
 
233
- // 3. Fetch all PR data from GitHub in parallel
234
- // Use the per-repo binding so alt-host stack analyses target the right host.
235
- // The PR identity (`${owner}/${repo}`) is used for DB rows and worktree
236
- // identity. For host-binding lookups we MUST use the config-binding key,
237
- // which differs from the PR identity for monorepo-style `url_pattern`
238
- // configs (one `repos[...]` entry serves many captured owner/repo pairs).
239
- const bindingRepository = deps.resolveBindingRepositoryFromPR(owner, repo, config);
249
+ // 3. Fetch all PR data from GitHub in parallel.
250
+ // `bindingRepository` (resolved in step 0) is the config-binding key. We use
251
+ // it — not the PR identity `${owner}/${repo}` for host-binding lookups so
252
+ // alt-host stack analyses target the right host. The two differ for
253
+ // monorepo-style `url_pattern` configs (one `repos[...]` entry serves many
254
+ // captured owner/repo pairs). The PR identity is still used for DB rows and
255
+ // worktree identity.
240
256
  const stackBinding = deps.resolveHostBinding(bindingRepository, config);
241
257
  const githubToken = stackBinding.token;
242
258
  const prDataMap = new Map();
@@ -275,7 +291,16 @@ async function executeStackAnalysis(params) {
275
291
 
276
292
  const prInfo = { owner, repo, number: prNum };
277
293
  const { path: perPRWorktreePath } = await worktreeManager.createWorktreeForPR(
278
- prInfo, prData, repositoryPath
294
+ prInfo, prData, repositoryPath,
295
+ {
296
+ checkoutScript,
297
+ checkoutTimeout,
298
+ // No checkout script → inherit the trigger worktree's sparse-checkout
299
+ // layout instead of a full checkout from the repo root. When a script is
300
+ // configured it sets up sparse-checkout itself, so worktreeSourcePath is
301
+ // unused (and omitted) in that case.
302
+ ...(checkoutScript ? {} : { worktreeSourcePath: triggerWorktreePath }),
303
+ }
279
304
  );
280
305
  worktreePathMap.set(prNum, perPRWorktreePath);
281
306
  } catch (wtError) {
@@ -307,6 +332,7 @@ async function executeStackAnalysis(params) {
307
332
  worktreePath: worktreePathMap.get(prNum),
308
333
  analysisConfig, stackAnalysisId, state,
309
334
  githubToken, binding: stackBinding, prData: prDataMap.get(prNum),
335
+ worktreeConfig, checkoutScript,
310
336
  onAnalysisIdReady
311
337
  }).then(result => {
312
338
  state.prStatuses.set(prNum, {
@@ -346,6 +372,7 @@ async function executeStackAnalysis(params) {
346
372
  async function analyzeStackPR(deps, db, config, {
347
373
  owner, repo, repository, bindingRepository, prNum, worktreePath,
348
374
  analysisConfig, stackAnalysisId, state, githubToken, binding, prData,
375
+ worktreeConfig, checkoutScript,
349
376
  onAnalysisIdReady
350
377
  }) {
351
378
  // Build a GitHubClient for analyzer-side dedup pre-fetch. The stack
@@ -357,12 +384,17 @@ async function analyzeStackPR(deps, db, config, {
357
384
  if (stackGithubClient) {
358
385
  logger.debug(`analyzer githubClient wired for ${owner}/${repo}#${prNum} (stack)`);
359
386
  }
360
- // 1. Setup PR (generates diff, stores metadata)
361
- const worktreeManager = new deps.GitWorktreeManager(db);
387
+ // 1. Setup PR (expands sparse-checkout, generates diff, stores metadata)
388
+ // Construct with the repo's resolved worktreeConfig for consistency with the
389
+ // creation manager. setupStackPR operates against the explicit worktreePath
390
+ // (diff generation + sparse-cone expansion), so the worktreeConfig naming /
391
+ // directory options are not exercised today — threading them through guards
392
+ // against silent latent regressions if that changes.
393
+ const worktreeManager = new deps.GitWorktreeManager(db, worktreeConfig || {});
362
394
  await deps.setupStackPR({
363
395
  db, owner, repo, prNumber: prNum,
364
396
  githubToken, binding, bindingRepository,
365
- worktreePath, worktreeManager, prData
397
+ worktreePath, worktreeManager, prData, checkoutScript
366
398
  });
367
399
 
368
400
  // 2. Fetch prMetadata from DB
@@ -32,9 +32,12 @@ const logger = require('../utils/logger');
32
32
  * @param {string} params.worktreePath - Path to the per-PR worktree
33
33
  * @param {import('../git/worktree').GitWorktreeManager} params.worktreeManager - Worktree manager instance
34
34
  * @param {Object} [params.prData] - Pre-fetched PR data from GitHub (skips API call when provided)
35
+ * @param {string|null} [params.checkoutScript] - Repo's configured checkout script, if any. When set,
36
+ * the script owns all sparse-checkout setup, so built-in sparse-cone expansion is skipped (mirrors
37
+ * the non-stack `pr-setup.js` contract).
35
38
  * @returns {Promise<{ reviewId: number, prMetadata: Object, prData: Object, isNew: boolean }>}
36
39
  */
37
- async function setupStackPR({ db, owner, repo, prNumber, githubToken, binding, bindingRepository, worktreePath, worktreeManager, prData: prefetchedPRData }) {
40
+ async function setupStackPR({ db, owner, repo, prNumber, githubToken, binding, bindingRepository, worktreePath, worktreeManager, prData: prefetchedPRData, checkoutScript }) {
38
41
  // `bindingRepository` is accepted so callers (e.g. `executeStackAnalysis`)
39
42
  // can thread the resolved config-binding key through to any downstream
40
43
  // per-repo lookups added in this function. Currently unused inside this
@@ -56,13 +59,38 @@ async function setupStackPR({ db, owner, repo, prNumber, githubToken, binding, b
56
59
  const prFiles = await githubClient.fetchPullRequestFiles(owner, repo, prNumber);
57
60
  logger.info(`PR #${prNumber} has ${prFiles.length} changed files`);
58
61
 
59
- // 3. Generate diff in the worktree (SHA-based, works after checkout)
62
+ // 3. Expand sparse-checkout for PR-changed directories (mirrors pr-setup.js).
63
+ // Stack worktrees inherit the trigger worktree's sparse-checkout layout, which
64
+ // may omit directories a sibling PR touches. The SHA-based diff below reads
65
+ // commit objects (not the working tree) so it is unaffected, but the later
66
+ // file-context and codebase-context analysis steps DO read files from disk —
67
+ // an unexpanded cone would silently under-review those files. Expanding here
68
+ // ensures every PR-changed directory is present on disk.
69
+ //
70
+ // IMPORTANT: when a checkout_script is configured the script owns all
71
+ // sparse-checkout setup, so we must NOT auto-expand — doing so would override
72
+ // the cone the script just configured. This matches the pr-setup.js contract.
73
+ if (!checkoutScript && prFiles.length > 0) {
74
+ const isSparse = await worktreeManager.isSparseCheckoutEnabled(worktreePath);
75
+ if (isSparse) {
76
+ try {
77
+ const addedDirs = await worktreeManager.ensurePRDirectoriesInSparseCheckout(worktreePath, prFiles);
78
+ if (addedDirs.length > 0) {
79
+ logger.info(`Stack PR #${prNumber}: expanded sparse-checkout for: ${addedDirs.join(', ')}`);
80
+ }
81
+ } catch (sparseError) {
82
+ logger.warn(`Stack PR #${prNumber}: sparse-checkout expansion failed (non-fatal): ${sparseError.message}`);
83
+ }
84
+ }
85
+ }
86
+
87
+ // 4. Generate diff in the worktree (SHA-based, works after checkout)
60
88
  const diff = await worktreeManager.generateUnifiedDiff(worktreePath, prData);
61
89
 
62
- // 4. Get changed files with stats
90
+ // 5. Get changed files with stats
63
91
  const changedFiles = await worktreeManager.getChangedFiles(worktreePath, prData);
64
92
 
65
- // 5. Store via storePRData (creates/updates pr_metadata, reviews, worktrees records)
93
+ // 6. Store via storePRData (creates/updates pr_metadata, reviews, worktrees records)
66
94
  const prInfo = { owner, repo, number: prNumber };
67
95
  const { isNewReview, reviewId } = await storePRData(db, prInfo, prData, diff, changedFiles, worktreePath);
68
96