@in-the-loop-labs/pair-review 1.2.1 → 1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@in-the-loop-labs/pair-review",
3
- "version": "1.2.1",
3
+ "version": "1.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": {
@@ -30,7 +30,7 @@
30
30
  "test:e2e:debug": "playwright test --debug",
31
31
  "generate:skill-prompts": "node scripts/generate-skill-prompts.js",
32
32
  "changeset": "changeset",
33
- "version": "changeset version && node scripts/sync-plugin-versions.js && git add package.json package-lock.json CHANGELOG.md .changeset .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json plugin-code-critic/.claude-plugin/plugin.json && git commit -m 'RELEASING: Bump versions'",
33
+ "version": "changeset version && npm install --package-lock-only && node scripts/sync-plugin-versions.js && git add package.json package-lock.json CHANGELOG.md .changeset .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json plugin-code-critic/.claude-plugin/plugin.json && git commit -m 'RELEASING: Bump versions'",
34
34
  "release": "npm whoami > /dev/null || { echo 'Error: Not logged in to npm. Run: npm login'; exit 1; } && npm run version && changeset tag && npm publish && git push && git push --tags"
35
35
  },
36
36
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pair-review",
3
- "version": "1.2.1",
3
+ "version": "1.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": "1.2.1",
3
+ "version": "1.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",
@@ -218,6 +218,62 @@ class PRArgumentParser {
218
218
  return process.cwd();
219
219
  }
220
220
 
221
+ /**
222
+ * Check if a directory is a git repository that matches the specified owner/repo.
223
+ * Compares the git remote origin URL against the expected owner/repo.
224
+ *
225
+ * @param {string} directory - Directory path to check
226
+ * @param {string} expectedOwner - Expected repository owner
227
+ * @param {string} expectedRepo - Expected repository name
228
+ * @returns {Promise<boolean>} True if the directory is a matching git repository
229
+ */
230
+ async isMatchingRepository(directory, expectedOwner, expectedRepo) {
231
+ try {
232
+ // Use _createGitForDirectory for testability (can be overridden in tests)
233
+ const git = this._createGitForDirectory(directory);
234
+
235
+ // Check if it's a git repository
236
+ const isRepo = await git.checkIsRepo();
237
+ if (!isRepo) {
238
+ return false;
239
+ }
240
+
241
+ // Get remote origin URL
242
+ const remotes = await git.getRemotes(true);
243
+ const origin = remotes.find(remote => remote.name === 'origin');
244
+
245
+ if (!origin) {
246
+ return false;
247
+ }
248
+
249
+ const remoteUrl = origin.refs.fetch || origin.refs.push;
250
+ if (!remoteUrl) {
251
+ return false;
252
+ }
253
+
254
+ // Parse the owner/repo from the remote URL
255
+ const { owner, repo } = this.parseRepositoryFromURL(remoteUrl);
256
+
257
+ // Compare case-insensitively (GitHub repos are case-insensitive)
258
+ return owner.toLowerCase() === expectedOwner.toLowerCase() &&
259
+ repo.toLowerCase() === expectedRepo.toLowerCase();
260
+ } catch (error) {
261
+ // Any error means the directory doesn't match
262
+ return false;
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Create a git instance for a given directory.
268
+ * This method exists for testability - tests can override it.
269
+ * @param {string} directory - Directory path
270
+ * @returns {Object} simpleGit instance
271
+ * @private
272
+ */
273
+ _createGitForDirectory(directory) {
274
+ return simpleGit(directory);
275
+ }
276
+
221
277
  /**
222
278
  * Check if current directory is a git repository
223
279
  * @returns {Promise<boolean>} Whether current directory is a git repo
package/src/main.js CHANGED
@@ -9,7 +9,7 @@ const { startServer } = require('./server');
9
9
  const Analyzer = require('./ai/analyzer');
10
10
  const { applyConfigOverrides } = require('./ai');
11
11
  const { handleLocalReview, findMainGitRoot } = require('./local-review');
12
- const { storePRData, registerRepositoryLocation } = require('./setup/pr-setup');
12
+ const { storePRData, registerRepositoryLocation, findRepositoryPath } = require('./setup/pr-setup');
13
13
  const { normalizeRepository, resolveRenamedFile, resolveRenamedFileOld } = require('./utils/paths');
14
14
  const logger = require('./utils/logger');
15
15
  const simpleGit = require('simple-git');
@@ -484,16 +484,39 @@ async function handlePullRequest(args, config, db, flags = {}) {
484
484
  console.log('Fetching pull request data from GitHub...');
485
485
  const prData = await githubClient.fetchPullRequest(prInfo.owner, prInfo.repo, prInfo.number);
486
486
 
487
- // Get current repository path
487
+ // Determine repository path: only use cwd if it matches the target repo
488
488
  const currentDir = parser.getCurrentDirectory();
489
+ const isMatchingRepo = await parser.isMatchingRepository(currentDir, prInfo.owner, prInfo.repo);
489
490
 
490
- // Register the known repository location for future web UI usage
491
- await registerRepositoryLocation(db, currentDir, prInfo.owner, prInfo.repo);
491
+ let repositoryPath;
492
+ if (isMatchingRepo) {
493
+ // Current directory is a checkout of the target repository
494
+ repositoryPath = currentDir;
495
+ // Register the known repository location for future web UI usage
496
+ await registerRepositoryLocation(db, currentDir, prInfo.owner, prInfo.repo);
497
+ } else {
498
+ // Current directory is not the target repository - find or clone it
499
+ console.log(`Current directory is not a checkout of ${prInfo.owner}/${prInfo.repo}, locating repository...`);
500
+ const repository = normalizeRepository(prInfo.owner, prInfo.repo);
501
+ const result = await findRepositoryPath({
502
+ db,
503
+ owner: prInfo.owner,
504
+ repo: prInfo.repo,
505
+ repository,
506
+ prNumber: prInfo.number,
507
+ onProgress: (progress) => {
508
+ if (progress.message) {
509
+ console.log(progress.message);
510
+ }
511
+ }
512
+ });
513
+ repositoryPath = result.repositoryPath;
514
+ }
492
515
 
493
516
  // Setup git worktree
494
517
  console.log('Setting up git worktree...');
495
518
  const worktreeManager = new GitWorktreeManager(db);
496
- const worktreePath = await worktreeManager.createWorktreeForPR(prInfo, prData, currentDir);
519
+ const worktreePath = await worktreeManager.createWorktreeForPR(prInfo, prData, repositoryPath);
497
520
 
498
521
  // Generate unified diff
499
522
  console.log('Generating unified diff...');
@@ -701,7 +724,6 @@ async function performHeadlessReview(args, config, db, flags, options) {
701
724
  console.log('Fetching pull request data from GitHub...');
702
725
  const prData = await githubClient.fetchPullRequest(prInfo.owner, prInfo.repo, prInfo.number);
703
726
 
704
- const repository = normalizeRepository(prInfo.owner, prInfo.repo);
705
727
  let worktreePath;
706
728
  let diff;
707
729
  let changedFiles;
@@ -709,6 +731,16 @@ async function performHeadlessReview(args, config, db, flags, options) {
709
731
  // Determine working directory: --use-checkout uses current directory
710
732
  if (flags.useCheckout) {
711
733
  worktreePath = process.cwd();
734
+
735
+ // Verify cwd matches the target repository when using --use-checkout
736
+ const isMatchingRepo = await parser.isMatchingRepository(worktreePath, prInfo.owner, prInfo.repo);
737
+ if (!isMatchingRepo) {
738
+ throw new Error(
739
+ `--use-checkout requires running from a checkout of ${prInfo.owner}/${prInfo.repo}, ` +
740
+ `but current directory does not match. Either cd to the correct repository or remove --use-checkout.`
741
+ );
742
+ }
743
+
712
744
  await registerRepositoryLocation(db, worktreePath, prInfo.owner, prInfo.repo);
713
745
  console.log(`Using current checkout at ${worktreePath}`);
714
746
 
@@ -752,13 +784,37 @@ async function performHeadlessReview(args, config, db, flags, options) {
752
784
  return result;
753
785
  });
754
786
  } else {
755
- // Use worktree approach
787
+ // Use worktree approach - only use cwd if it matches the target repo
756
788
  const currentDir = parser.getCurrentDirectory();
757
- await registerRepositoryLocation(db, currentDir, prInfo.owner, prInfo.repo);
789
+ const isMatchingRepo = await parser.isMatchingRepository(currentDir, prInfo.owner, prInfo.repo);
790
+
791
+ let repositoryPath;
792
+ if (isMatchingRepo) {
793
+ // Current directory is a checkout of the target repository
794
+ repositoryPath = currentDir;
795
+ await registerRepositoryLocation(db, currentDir, prInfo.owner, prInfo.repo);
796
+ } else {
797
+ // Current directory is not the target repository - find or clone it
798
+ console.log(`Current directory is not a checkout of ${prInfo.owner}/${prInfo.repo}, locating repository...`);
799
+ const repository = normalizeRepository(prInfo.owner, prInfo.repo);
800
+ const result = await findRepositoryPath({
801
+ db,
802
+ owner: prInfo.owner,
803
+ repo: prInfo.repo,
804
+ repository,
805
+ prNumber: prInfo.number,
806
+ onProgress: (progress) => {
807
+ if (progress.message) {
808
+ console.log(progress.message);
809
+ }
810
+ }
811
+ });
812
+ repositoryPath = result.repositoryPath;
813
+ }
758
814
 
759
815
  console.log('Setting up git worktree...');
760
816
  const worktreeManager = new GitWorktreeManager(db);
761
- worktreePath = await worktreeManager.createWorktreeForPR(prInfo, prData, currentDir);
817
+ worktreePath = await worktreeManager.createWorktreeForPR(prInfo, prData, repositoryPath);
762
818
 
763
819
  console.log('Generating unified diff...');
764
820
  diff = await worktreeManager.generateUnifiedDiff(worktreePath, prData);