@in-the-loop-labs/pair-review 3.3.0 → 3.3.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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@in-the-loop-labs/pair-review",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.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": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"test:e2e:debug": "playwright test --debug",
|
|
33
33
|
"generate:skill-prompts": "node scripts/generate-skill-prompts.js",
|
|
34
34
|
"changeset": "changeset",
|
|
35
|
-
"version": "changeset version && pnpm install --lockfile-only && node scripts/sync-plugin-versions.js && git add package.json pnpm-lock.yaml CHANGELOG.md .changeset .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json plugin-code-critic/.claude-plugin/plugin.json && git commit -m \"RELEASING: v$(node -p \"require('./package.json').version\")\"",
|
|
35
|
+
"version": "changeset version && pnpm install --lockfile-only && bash scripts/generate-package-lock.sh && node scripts/sync-plugin-versions.js && git add package.json pnpm-lock.yaml 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: v$(node -p \"require('./package.json').version\")\"",
|
|
36
36
|
"release": "npm whoami > /dev/null || { echo 'Error: Not logged in to npm. Run: npm login'; exit 1; } && pnpm run version && changeset tag && npm publish && git push && git push --tags"
|
|
37
37
|
},
|
|
38
38
|
"keywords": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pair-review",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.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": "3.3.
|
|
3
|
+
"version": "3.3.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",
|
|
@@ -231,15 +231,22 @@ class WorktreePoolLifecycle {
|
|
|
231
231
|
// Note: poolEntry was already atomically marked 'switching' by claimAvailable()
|
|
232
232
|
try {
|
|
233
233
|
const git = this._simpleGit(poolEntry.path);
|
|
234
|
+
const worktreeManager = new this._GitWorktreeManager(this.db);
|
|
234
235
|
|
|
235
236
|
// Resolve the remote
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
237
|
+
const remoteName = await worktreeManager.resolveRemoteForPR(git, prData, {
|
|
238
|
+
owner: prInfo.owner,
|
|
239
|
+
repo: prInfo.repo,
|
|
240
|
+
number: prInfo.prNumber,
|
|
241
|
+
});
|
|
239
242
|
|
|
240
|
-
// Fetch new PR refs (incremental -- cheap on a warm worktree)
|
|
243
|
+
// Fetch new PR refs (incremental -- cheap on a warm worktree) with fallback
|
|
241
244
|
logger.info(`Fetching PR #${prInfo.prNumber} refs into pool worktree ${poolEntry.id}`);
|
|
242
|
-
await
|
|
245
|
+
const fetchedHead = await worktreeManager.fetchPRHead(git, {
|
|
246
|
+
owner: prInfo.owner,
|
|
247
|
+
repo: prInfo.repo,
|
|
248
|
+
number: prInfo.prNumber,
|
|
249
|
+
}, prData, { remote: remoteName });
|
|
243
250
|
|
|
244
251
|
// Clean the working tree before switching PRs. Without this, untracked
|
|
245
252
|
// files (build artifacts, generated code) from the previous PR leak into
|
|
@@ -254,7 +261,7 @@ class WorktreePoolLifecycle {
|
|
|
254
261
|
if (targetSha) {
|
|
255
262
|
await git.checkout([targetSha]);
|
|
256
263
|
} else {
|
|
257
|
-
await git.checkout([
|
|
264
|
+
await git.checkout([fetchedHead.checkoutTarget]);
|
|
258
265
|
}
|
|
259
266
|
|
|
260
267
|
// Run reset_script if configured
|
|
@@ -273,7 +280,6 @@ class WorktreePoolLifecycle {
|
|
|
273
280
|
PR_NUMBER: String(prInfo.prNumber),
|
|
274
281
|
WORKTREE_PATH: poolEntry.path,
|
|
275
282
|
};
|
|
276
|
-
const worktreeManager = new this._GitWorktreeManager();
|
|
277
283
|
await worktreeManager.executeCheckoutScript(
|
|
278
284
|
options.resetScript, poolEntry.path, scriptEnv, options.checkoutTimeout
|
|
279
285
|
);
|
|
@@ -285,7 +291,6 @@ class WorktreePoolLifecycle {
|
|
|
285
291
|
|
|
286
292
|
// Best-effort disk cleanup for deleted non-pool worktree directories
|
|
287
293
|
if (deletedPaths && deletedPaths.length > 0) {
|
|
288
|
-
const worktreeManager = new this._GitWorktreeManager();
|
|
289
294
|
for (const deletedPath of deletedPaths) {
|
|
290
295
|
try {
|
|
291
296
|
await worktreeManager.cleanupWorktree(deletedPath);
|
package/src/git/worktree.js
CHANGED
|
@@ -170,6 +170,97 @@ class GitWorktreeManager {
|
|
|
170
170
|
return this.resolveRemoteForRepo(git, cloneUrl, sshUrl);
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Extract a PR number from either { number } or { prNumber } shapes.
|
|
175
|
+
* @param {Object|null} prInfo
|
|
176
|
+
* @returns {number|null}
|
|
177
|
+
*/
|
|
178
|
+
getPRNumber(prInfo) {
|
|
179
|
+
if (!prInfo) return null;
|
|
180
|
+
return prInfo.number || prInfo.prNumber || null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Extract the PR head branch name from either REST or stored PR metadata.
|
|
185
|
+
* @param {Object|null} prData
|
|
186
|
+
* @returns {string}
|
|
187
|
+
*/
|
|
188
|
+
getPRHeadBranch(prData) {
|
|
189
|
+
return prData?.head?.ref || prData?.head_branch || '';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Extract the PR head SHA from either REST or stored PR metadata.
|
|
194
|
+
* @param {Object|null} prData
|
|
195
|
+
* @returns {string}
|
|
196
|
+
*/
|
|
197
|
+
getPRHeadSha(prData) {
|
|
198
|
+
return prData?.head?.sha || prData?.head_sha || '';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Detect whether a fetch failed because the remote does not expose a PR ref.
|
|
203
|
+
* @param {Error} error
|
|
204
|
+
* @returns {boolean}
|
|
205
|
+
*/
|
|
206
|
+
isMissingRemoteRefError(error) {
|
|
207
|
+
const message = String(error?.message || '').toLowerCase();
|
|
208
|
+
return message.includes('couldn\'t find remote ref') ||
|
|
209
|
+
message.includes('could not find remote ref') ||
|
|
210
|
+
message.includes('remote ref does not exist') ||
|
|
211
|
+
message.includes('fatal: invalid refspec');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Fetch a PR head into a stable tracking ref, falling back from GitHub PR
|
|
216
|
+
* refs to a direct SHA fetch when the git transport does not expose
|
|
217
|
+
* refs/pull/* (for example, alternate internal fetch backends).
|
|
218
|
+
*
|
|
219
|
+
* @param {Object} git - simple-git instance
|
|
220
|
+
* @param {Object} prInfo - PR info { owner, repo, number } or { prNumber }
|
|
221
|
+
* @param {Object} prData - PR data from GitHub API or stored metadata
|
|
222
|
+
* @param {Object} [options={}]
|
|
223
|
+
* @param {string|null} [options.remote] - Base repository remote to use for PR-ref fetch
|
|
224
|
+
* @returns {Promise<{remote: string, trackingRef: string|null, checkoutTarget: string}>}
|
|
225
|
+
*/
|
|
226
|
+
async fetchPRHead(git, prInfo, prData, options = {}) {
|
|
227
|
+
const prNumber = this.getPRNumber(prInfo);
|
|
228
|
+
const headSha = this.getPRHeadSha(prData);
|
|
229
|
+
const baseRemote = options.remote || await this.resolveRemoteForPR(git, prData, prInfo);
|
|
230
|
+
|
|
231
|
+
if (!prNumber) {
|
|
232
|
+
throw new Error('Cannot fetch PR head without a PR number');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const prTrackingRef = `refs/remotes/${baseRemote}/pr-${prNumber}`;
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
await git.fetch([baseRemote, `+refs/pull/${prNumber}/head:${prTrackingRef}`]);
|
|
239
|
+
return {
|
|
240
|
+
remote: baseRemote,
|
|
241
|
+
trackingRef: prTrackingRef,
|
|
242
|
+
checkoutTarget: prTrackingRef
|
|
243
|
+
};
|
|
244
|
+
} catch (prRefError) {
|
|
245
|
+
if (!this.isMissingRemoteRefError(prRefError)) {
|
|
246
|
+
throw prRefError;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.warn(`PR ref fetch unavailable for PR #${prNumber} on remote ${baseRemote}, falling back to SHA fetch: ${prRefError.message}`);
|
|
250
|
+
|
|
251
|
+
if (!headSha) {
|
|
252
|
+
throw prRefError;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
await git.raw(['fetch', baseRemote, headSha]);
|
|
256
|
+
return {
|
|
257
|
+
remote: baseRemote,
|
|
258
|
+
trackingRef: null,
|
|
259
|
+
checkoutTarget: headSha
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
173
264
|
/**
|
|
174
265
|
* Execute a user-provided checkout script in the worktree.
|
|
175
266
|
* The script receives PR context as environment variables and is responsible
|
|
@@ -427,20 +518,21 @@ class GitWorktreeManager {
|
|
|
427
518
|
console.log(`Base SHA fetch not needed or already available: ${fetchError.message}`);
|
|
428
519
|
}
|
|
429
520
|
|
|
430
|
-
// Fetch the PR head using
|
|
521
|
+
// Fetch the PR head using PR refs when available, with a branch/SHA fallback
|
|
431
522
|
console.log(`Fetching PR #${prInfo.number} head...`);
|
|
432
|
-
await
|
|
523
|
+
const fetchedHead = await this.fetchPRHead(worktreeGit, prInfo, prData, { remote });
|
|
433
524
|
|
|
434
525
|
// Execute checkout script if configured (before checkout so sparse-checkout is set up)
|
|
435
526
|
if (checkoutScript) {
|
|
436
527
|
// Fetch the actual head branch by name (for checkout scripts that expect branch refs)
|
|
437
528
|
// This may fail for fork PRs where the branch is in a different repo - that's okay
|
|
438
|
-
|
|
529
|
+
const headBranch = this.getPRHeadBranch(prData);
|
|
530
|
+
if (headBranch) {
|
|
439
531
|
try {
|
|
440
|
-
console.log(`Fetching head branch ${
|
|
441
|
-
await worktreeGit.fetch([remote, `+refs/heads/${
|
|
532
|
+
console.log(`Fetching head branch ${headBranch}...`);
|
|
533
|
+
await worktreeGit.fetch([remote, `+refs/heads/${headBranch}:refs/remotes/${remote}/${headBranch}`]);
|
|
442
534
|
// Create/update a local branch pointing to the fetched ref so tooling can reference it by name
|
|
443
|
-
await worktreeGit.branch(['-f',
|
|
535
|
+
await worktreeGit.branch(['-f', headBranch, `${remote}/${headBranch}`]);
|
|
444
536
|
} catch (branchFetchError) {
|
|
445
537
|
// Expected for fork PRs - the branch exists in the fork, not the base repo
|
|
446
538
|
console.log(`Could not fetch head branch (may be from a fork): ${branchFetchError.message}`);
|
|
@@ -450,9 +542,9 @@ class GitWorktreeManager {
|
|
|
450
542
|
console.log(`Executing checkout script: ${checkoutScript}`);
|
|
451
543
|
const scriptEnv = {
|
|
452
544
|
BASE_BRANCH: prData.base_branch,
|
|
453
|
-
HEAD_BRANCH:
|
|
545
|
+
HEAD_BRANCH: headBranch,
|
|
454
546
|
BASE_SHA: prData.base_sha,
|
|
455
|
-
HEAD_SHA: prData
|
|
547
|
+
HEAD_SHA: this.getPRHeadSha(prData),
|
|
456
548
|
PR_NUMBER: String(prInfo.number),
|
|
457
549
|
WORKTREE_PATH: worktreePath
|
|
458
550
|
};
|
|
@@ -461,13 +553,13 @@ class GitWorktreeManager {
|
|
|
461
553
|
}
|
|
462
554
|
|
|
463
555
|
// Checkout to PR head commit
|
|
464
|
-
const targetSha = prData
|
|
556
|
+
const targetSha = this.getPRHeadSha(prData);
|
|
465
557
|
if (targetSha) {
|
|
466
558
|
console.log(`Checking out to PR head commit ${targetSha}...`);
|
|
467
559
|
await worktreeGit.checkout([targetSha]);
|
|
468
560
|
} else {
|
|
469
|
-
console.log(`Checking out to PR head ref ${
|
|
470
|
-
await worktreeGit.checkout([
|
|
561
|
+
console.log(`Checking out to PR head ref ${fetchedHead.checkoutTarget}...`);
|
|
562
|
+
await worktreeGit.checkout([fetchedHead.checkoutTarget]);
|
|
471
563
|
}
|
|
472
564
|
|
|
473
565
|
// Verify we're at the correct commit
|
|
@@ -540,13 +632,13 @@ class GitWorktreeManager {
|
|
|
540
632
|
console.log(`Fetching latest changes from ${remote}...`);
|
|
541
633
|
await worktreeGit.fetch([remote, '--prune']);
|
|
542
634
|
|
|
543
|
-
// Fetch the PR head using
|
|
635
|
+
// Fetch the PR head using PR refs when available, with a branch/SHA fallback
|
|
544
636
|
console.log(`Fetching PR #${number} head...`);
|
|
545
|
-
await
|
|
637
|
+
const fetchedHead = await this.fetchPRHead(worktreeGit, prInfo, prData, { remote });
|
|
546
638
|
|
|
547
639
|
// Checkout to PR head commit
|
|
548
640
|
console.log(`Checking out to PR head commit ${headSha}...`);
|
|
549
|
-
await worktreeGit.checkout([
|
|
641
|
+
await worktreeGit.checkout([fetchedHead.checkoutTarget]);
|
|
550
642
|
|
|
551
643
|
// Verify we're at the correct commit
|
|
552
644
|
const currentCommit = await worktreeGit.revparse(['HEAD']);
|
|
@@ -919,11 +1011,11 @@ class GitWorktreeManager {
|
|
|
919
1011
|
|
|
920
1012
|
// Fetch the latest PR head from remote
|
|
921
1013
|
console.log(`Fetching PR #${prNumber} head from ${remote}...`);
|
|
922
|
-
await
|
|
1014
|
+
const fetchedHead = await this.fetchPRHead(git, prInfo || { number: prNumber }, prData, { remote });
|
|
923
1015
|
|
|
924
1016
|
// Reset to the fetched PR head
|
|
925
1017
|
console.log(`Resetting worktree to PR head...`);
|
|
926
|
-
await git.raw(['reset', '--hard',
|
|
1018
|
+
await git.raw(['reset', '--hard', fetchedHead.checkoutTarget]);
|
|
927
1019
|
|
|
928
1020
|
// Update last_accessed_at in database
|
|
929
1021
|
if (this.worktreeRepo) {
|
|
@@ -977,13 +1069,13 @@ class GitWorktreeManager {
|
|
|
977
1069
|
? await this.resolveRemoteForPR(git, prData, prInfo)
|
|
978
1070
|
: defaultRemote;
|
|
979
1071
|
|
|
980
|
-
// 3. Fetch PR head into a persistent ref
|
|
981
|
-
console.log(`Fetching PR #${prNumber} head from ${remote}
|
|
982
|
-
await
|
|
1072
|
+
// 3. Fetch PR head into a persistent ref (or by SHA when refs are unavailable)
|
|
1073
|
+
console.log(`Fetching PR #${prNumber} head from ${remote}...`);
|
|
1074
|
+
const fetchedHead = await this.fetchPRHead(git, prInfo || { number: prNumber }, prData, { remote });
|
|
983
1075
|
|
|
984
1076
|
// 4. Reset worktree to the fetched ref
|
|
985
|
-
console.log(`Resetting worktree to
|
|
986
|
-
await git.raw(['reset', '--hard',
|
|
1077
|
+
console.log(`Resetting worktree to ${fetchedHead.checkoutTarget}...`);
|
|
1078
|
+
await git.raw(['reset', '--hard', fetchedHead.checkoutTarget]);
|
|
987
1079
|
|
|
988
1080
|
// 5. Return the new HEAD SHA
|
|
989
1081
|
const headSha = (await git.revparse(['HEAD'])).trim();
|
|
@@ -1219,4 +1311,4 @@ class GitWorktreeManager {
|
|
|
1219
1311
|
}
|
|
1220
1312
|
}
|
|
1221
1313
|
|
|
1222
|
-
module.exports = { GitWorktreeManager };
|
|
1314
|
+
module.exports = { GitWorktreeManager };
|