@jojonax/codex-copilot 1.4.2 → 1.4.3
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 +1 -1
- package/src/commands/run.js +37 -4
- package/src/utils/github.js +16 -1
package/package.json
CHANGED
package/src/commands/run.js
CHANGED
|
@@ -156,6 +156,8 @@ export async function run(projectDir) {
|
|
|
156
156
|
|
|
157
157
|
// Mark task as in_progress
|
|
158
158
|
task.status = 'in_progress';
|
|
159
|
+
const isRetry = (task.retry_count || 0) > 0;
|
|
160
|
+
const retryStartedAt = isRetry ? new Date().toISOString() : null;
|
|
159
161
|
writeJSON(tasksPath, tasks);
|
|
160
162
|
|
|
161
163
|
// ===== Phase 1: Develop =====
|
|
@@ -185,10 +187,19 @@ export async function run(projectDir) {
|
|
|
185
187
|
|
|
186
188
|
// ===== Phase 3: Review loop =====
|
|
187
189
|
if (!checkpoint.isStepDone(task.id, 'merge', 'merged')) {
|
|
190
|
+
// On retry: request fresh re-review since old reviews may be stale
|
|
191
|
+
if (isRetry && prInfo?.number) {
|
|
192
|
+
log.info('🔄 Retry: requesting fresh re-review on existing PR...');
|
|
193
|
+
try {
|
|
194
|
+
github.requestReReview(projectDir, prInfo.number);
|
|
195
|
+
} catch { /* ignore — review bots may not be configured */ }
|
|
196
|
+
}
|
|
197
|
+
|
|
188
198
|
await reviewLoop(projectDir, task, prInfo, {
|
|
189
199
|
maxRounds: maxReviewRounds,
|
|
190
200
|
pollInterval,
|
|
191
201
|
waitTimeout,
|
|
202
|
+
retryStartedAt, // Only count reviews after this timestamp
|
|
192
203
|
}, checkpoint, providerId, isPrivate);
|
|
193
204
|
} else {
|
|
194
205
|
log.dim('⏩ Skipping review phase (already completed)');
|
|
@@ -198,7 +209,18 @@ export async function run(projectDir) {
|
|
|
198
209
|
if (!checkpoint.isStepDone(task.id, 'merge', 'merged')) {
|
|
199
210
|
await mergePhase(projectDir, task, prInfo, baseBranch, checkpoint);
|
|
200
211
|
} else {
|
|
201
|
-
|
|
212
|
+
// Checkpoint says merged — verify against GitHub
|
|
213
|
+
if (prInfo?.number) {
|
|
214
|
+
const prState = github.getPRState(projectDir, prInfo.number);
|
|
215
|
+
if (prState !== 'merged') {
|
|
216
|
+
log.warn(`⚠ Checkpoint says merged but PR #${prInfo.number} is ${prState} — re-entering merge`);
|
|
217
|
+
await mergePhase(projectDir, task, prInfo, baseBranch, checkpoint);
|
|
218
|
+
} else {
|
|
219
|
+
log.dim('⏩ Skipping merge phase (PR confirmed merged on GitHub)');
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
log.dim('⏩ Skipping merge phase (already merged)');
|
|
223
|
+
}
|
|
202
224
|
}
|
|
203
225
|
|
|
204
226
|
// Check if task was blocked during review/merge
|
|
@@ -357,7 +379,7 @@ async function prPhase(projectDir, task, baseBranch, checkpoint, isPrivate) {
|
|
|
357
379
|
// ──────────────────────────────────────────────
|
|
358
380
|
// Phase 3: Review loop
|
|
359
381
|
// ──────────────────────────────────────────────
|
|
360
|
-
async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pollInterval, waitTimeout }, checkpoint, providerId, isPrivate) {
|
|
382
|
+
async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pollInterval, waitTimeout, retryStartedAt }, checkpoint, providerId, isPrivate) {
|
|
361
383
|
const HARD_MAX_ROUNDS = 5;
|
|
362
384
|
const MAX_POLL_RETRIES = 3;
|
|
363
385
|
let maxRounds = Math.min(_maxRounds, HARD_MAX_ROUNDS);
|
|
@@ -388,8 +410,19 @@ async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pol
|
|
|
388
410
|
// and fast bot responses after fix pushes.
|
|
389
411
|
const existingReviews = github.getReviews(projectDir, prInfo.number);
|
|
390
412
|
const existingComments = github.getIssueComments(projectDir, prInfo.number);
|
|
391
|
-
|
|
392
|
-
|
|
413
|
+
|
|
414
|
+
// On retry: only count reviews posted AFTER the retry started
|
|
415
|
+
const isReviewFresh = (item) => {
|
|
416
|
+
if (!retryStartedAt) return true; // Not a retry — all reviews are valid
|
|
417
|
+
const itemDate = item.submitted_at || item.created_at || item.updated_at;
|
|
418
|
+
return itemDate && new Date(itemDate) > new Date(retryStartedAt);
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const freshReviews = existingReviews.filter(isReviewFresh);
|
|
422
|
+
const freshComments = existingComments.filter(isReviewFresh);
|
|
423
|
+
|
|
424
|
+
const hasReview = freshReviews.some(r => r.state !== 'PENDING');
|
|
425
|
+
const hasBotComment = freshComments.some(c =>
|
|
393
426
|
c.user?.type === 'Bot' || c.user?.login?.includes('bot')
|
|
394
427
|
);
|
|
395
428
|
|
package/src/utils/github.js
CHANGED
|
@@ -383,7 +383,7 @@ export const github = {
|
|
|
383
383
|
ensureRemoteBranch, hasCommitsBetween,
|
|
384
384
|
getReviews, getReviewComments, getIssueComments,
|
|
385
385
|
getLatestReviewState, mergePR, collectReviewFeedback,
|
|
386
|
-
isPrivateRepo, requestReReview, closePR, deleteBranch,
|
|
386
|
+
isPrivateRepo, requestReReview, closePR, deleteBranch, getPRState,
|
|
387
387
|
};
|
|
388
388
|
|
|
389
389
|
/**
|
|
@@ -412,3 +412,18 @@ export function deleteBranch(cwd, branch) {
|
|
|
412
412
|
return false;
|
|
413
413
|
}
|
|
414
414
|
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Get the state of a PR: 'open', 'closed', or 'merged'
|
|
418
|
+
*/
|
|
419
|
+
export function getPRState(cwd, prNumber) {
|
|
420
|
+
try {
|
|
421
|
+
const num = validatePRNumber(prNumber);
|
|
422
|
+
const output = gh(`pr view ${num} --json state,mergedAt`, cwd);
|
|
423
|
+
const data = JSON.parse(output);
|
|
424
|
+
if (data.mergedAt) return 'merged';
|
|
425
|
+
return (data.state || 'OPEN').toLowerCase();
|
|
426
|
+
} catch {
|
|
427
|
+
return 'unknown';
|
|
428
|
+
}
|
|
429
|
+
}
|