@jojonax/codex-copilot 1.2.1 → 1.3.0
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 +83 -51
package/package.json
CHANGED
package/src/commands/run.js
CHANGED
|
@@ -10,7 +10,7 @@ import { resolve } from 'path';
|
|
|
10
10
|
import { log, progressBar } from '../utils/logger.js';
|
|
11
11
|
import { git } from '../utils/git.js';
|
|
12
12
|
import { github } from '../utils/github.js';
|
|
13
|
-
import {
|
|
13
|
+
import { closePrompt } from '../utils/prompt.js';
|
|
14
14
|
import { createCheckpoint } from '../utils/checkpoint.js';
|
|
15
15
|
import { provider } from '../utils/provider.js';
|
|
16
16
|
|
|
@@ -162,6 +162,18 @@ export async function run(projectDir) {
|
|
|
162
162
|
log.dim('⏩ Skipping merge phase (already merged)');
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
// Check if task was blocked during review/merge
|
|
166
|
+
if (task.status === 'blocked') {
|
|
167
|
+
writeJSON(tasksPath, tasks);
|
|
168
|
+
log.blank();
|
|
169
|
+
log.warn(`⚠ Task #${task.id} is blocked — needs manual intervention`);
|
|
170
|
+
log.blank();
|
|
171
|
+
const done = tasks.tasks.filter(t => t.status === 'completed').length;
|
|
172
|
+
const blocked = tasks.tasks.filter(t => t.status === 'blocked').length;
|
|
173
|
+
progressBar(done, tasks.total, `${done}/${tasks.total} done, ${blocked} blocked`);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
165
177
|
// Mark task complete
|
|
166
178
|
task.status = 'completed';
|
|
167
179
|
writeJSON(tasksPath, tasks);
|
|
@@ -282,7 +294,9 @@ async function prPhase(projectDir, task, baseBranch, checkpoint) {
|
|
|
282
294
|
// Phase 3: Review loop
|
|
283
295
|
// ──────────────────────────────────────────────
|
|
284
296
|
async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pollInterval, waitTimeout }, checkpoint, providerId) {
|
|
285
|
-
|
|
297
|
+
const HARD_MAX_ROUNDS = 5;
|
|
298
|
+
const MAX_POLL_RETRIES = 3;
|
|
299
|
+
let maxRounds = Math.min(_maxRounds, HARD_MAX_ROUNDS);
|
|
286
300
|
log.step('Phase 3/4: Waiting for review');
|
|
287
301
|
|
|
288
302
|
// Resume from saved review round
|
|
@@ -291,6 +305,8 @@ async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pol
|
|
|
291
305
|
? state.review_round
|
|
292
306
|
: 1;
|
|
293
307
|
|
|
308
|
+
let pollRetries = 0;
|
|
309
|
+
|
|
294
310
|
for (let round = startRound; round <= maxRounds; round++) {
|
|
295
311
|
// Step: Waiting for review
|
|
296
312
|
checkpoint.saveStep(task.id, 'review', 'waiting_review', {
|
|
@@ -299,40 +315,38 @@ async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pol
|
|
|
299
315
|
review_round: round,
|
|
300
316
|
});
|
|
301
317
|
|
|
302
|
-
log.info(
|
|
303
|
-
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
);
|
|
317
|
-
if (hasReview || hasBotComment) {
|
|
318
|
-
log.info('Found existing review — processing immediately');
|
|
319
|
-
gotReview = true;
|
|
320
|
-
} else {
|
|
321
|
-
gotReview = await waitForReview(projectDir, prInfo.number, pollInterval, waitTimeout);
|
|
322
|
-
}
|
|
318
|
+
log.info('Checking for review feedback...');
|
|
319
|
+
|
|
320
|
+
// Always proactively check for existing reviews first.
|
|
321
|
+
let gotReview = false;
|
|
322
|
+
const existingReviews = github.getReviews(projectDir, prInfo.number);
|
|
323
|
+
const existingComments = github.getIssueComments(projectDir, prInfo.number);
|
|
324
|
+
const hasReview = existingReviews.some(r => r.state !== 'PENDING');
|
|
325
|
+
const hasBotComment = existingComments.some(c =>
|
|
326
|
+
c.user?.type === 'Bot' || c.user?.login?.includes('bot')
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
if (hasReview || hasBotComment) {
|
|
330
|
+
log.info('Review found — processing immediately');
|
|
331
|
+
gotReview = true;
|
|
323
332
|
} else {
|
|
333
|
+
// No reviews yet — enter polling mode
|
|
334
|
+
log.info(`No review yet, polling... (timeout: ${waitTimeout}s)`);
|
|
324
335
|
gotReview = await waitForReview(projectDir, prInfo.number, pollInterval, waitTimeout);
|
|
325
336
|
}
|
|
326
337
|
|
|
327
338
|
if (!gotReview) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
339
|
+
pollRetries++;
|
|
340
|
+
if (pollRetries >= MAX_POLL_RETRIES) {
|
|
341
|
+
log.error(`Review polling timed out ${MAX_POLL_RETRIES} times — marking task as blocked`);
|
|
342
|
+
task.status = 'blocked';
|
|
343
|
+
return;
|
|
333
344
|
}
|
|
334
|
-
|
|
345
|
+
log.warn(`Review wait timed out — auto-retrying (${pollRetries}/${MAX_POLL_RETRIES})...`);
|
|
346
|
+
round--; // retry same round
|
|
347
|
+
continue;
|
|
335
348
|
}
|
|
349
|
+
pollRetries = 0; // reset on success
|
|
336
350
|
|
|
337
351
|
// Step: Feedback received
|
|
338
352
|
checkpoint.saveStep(task.id, 'review', 'feedback_received', {
|
|
@@ -373,40 +387,37 @@ async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pol
|
|
|
373
387
|
const hasInlineComments = inlineComments && inlineComments.length > 0;
|
|
374
388
|
|
|
375
389
|
if (reviewState === 'CHANGES_REQUESTED') {
|
|
376
|
-
// Explicit change request — always treat as needing fixes
|
|
377
390
|
log.info('Review state: CHANGES_REQUESTED — entering fix phase');
|
|
378
391
|
} else if (reviewState === 'COMMENTED' && !hasInlineComments) {
|
|
379
|
-
// General comment with no code-level feedback — treat as passing
|
|
380
392
|
log.info('COMMENTED with no inline code comments — treating as passed ✅');
|
|
381
393
|
return;
|
|
382
394
|
} else if (!hasInlineComments) {
|
|
383
|
-
// Any other state (PENDING, null, etc.) with no inline comments — treat as passing
|
|
384
395
|
log.info('No inline code comments found — treating as passed ✅');
|
|
385
396
|
return;
|
|
386
397
|
} else {
|
|
387
|
-
// Has inline comments — treat as needing fixes
|
|
388
398
|
log.info(`Found ${inlineComments.length} inline code comment(s) — entering fix phase`);
|
|
389
399
|
}
|
|
390
400
|
}
|
|
391
401
|
|
|
392
402
|
// AI says FIX, or structural fallback indicates issues
|
|
393
|
-
|
|
394
403
|
log.blank();
|
|
395
404
|
log.warn(`Received review feedback (round ${round}/${maxRounds})`);
|
|
396
405
|
|
|
397
406
|
if (round >= maxRounds) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
if (choice === 'fix') {
|
|
407
|
+
if (maxRounds < HARD_MAX_ROUNDS) {
|
|
408
|
+
// Auto-extend: give it one more round
|
|
401
409
|
maxRounds++;
|
|
402
|
-
|
|
403
|
-
return;
|
|
410
|
+
log.warn(`Auto-extending fix rounds to ${maxRounds}`);
|
|
404
411
|
} else {
|
|
405
|
-
|
|
412
|
+
// Hard limit reached — cannot keep fixing forever
|
|
413
|
+
log.error(`Hard limit of ${HARD_MAX_ROUNDS} fix rounds reached — marking task as blocked`);
|
|
414
|
+
log.error('This task needs manual intervention to resolve review issues');
|
|
415
|
+
task.status = 'blocked';
|
|
416
|
+
return;
|
|
406
417
|
}
|
|
407
418
|
}
|
|
408
419
|
|
|
409
|
-
// Let AI fix
|
|
420
|
+
// Let AI fix based on the specific review feedback
|
|
410
421
|
await fixPhase(projectDir, task, feedback, round, providerId);
|
|
411
422
|
|
|
412
423
|
// Step: Fix applied
|
|
@@ -422,10 +433,12 @@ async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pol
|
|
|
422
433
|
git.pushBranch(projectDir, task.branch);
|
|
423
434
|
log.info('Fix pushed, waiting for next review round...');
|
|
424
435
|
// Brief wait for review bot to react
|
|
425
|
-
await sleep(
|
|
436
|
+
await sleep(15000);
|
|
426
437
|
} else {
|
|
427
|
-
//
|
|
428
|
-
log.
|
|
438
|
+
// AI fix produced no code changes — it cannot resolve this issue
|
|
439
|
+
log.error('AI fix produced no changes — marking task as blocked');
|
|
440
|
+
log.error('This task needs manual code changes to resolve review issues');
|
|
441
|
+
task.status = 'blocked';
|
|
429
442
|
return;
|
|
430
443
|
}
|
|
431
444
|
}
|
|
@@ -491,15 +504,34 @@ ${feedback}
|
|
|
491
504
|
async function mergePhase(projectDir, task, prInfo, baseBranch, checkpoint) {
|
|
492
505
|
log.step('Phase 4/4: Merge PR');
|
|
493
506
|
|
|
507
|
+
// Skip merge if task was blocked during review
|
|
508
|
+
if (task.status === 'blocked') {
|
|
509
|
+
log.warn(`Task #${task.id} is blocked — skipping merge`);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
494
513
|
log.info(`Auto-merging PR #${prInfo.number}...`);
|
|
495
514
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
515
|
+
let merged = false;
|
|
516
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
517
|
+
try {
|
|
518
|
+
github.mergePR(projectDir, prInfo.number);
|
|
519
|
+
log.info(`PR #${prInfo.number} merged ✅`);
|
|
520
|
+
merged = true;
|
|
521
|
+
break;
|
|
522
|
+
} catch (err) {
|
|
523
|
+
log.warn(`Merge attempt ${attempt}/3 failed: ${err.message}`);
|
|
524
|
+
if (attempt < 3) {
|
|
525
|
+
log.info('Retrying in 10s...');
|
|
526
|
+
await sleep(10000);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (!merged) {
|
|
532
|
+
log.error('Merge failed after 3 attempts — marking task as blocked');
|
|
533
|
+
task.status = 'blocked';
|
|
534
|
+
return;
|
|
503
535
|
}
|
|
504
536
|
|
|
505
537
|
checkpoint.saveStep(task.id, 'merge', 'merged', {
|