@jojonax/codex-copilot 1.2.2 → 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 +67 -32
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', {
|
|
@@ -302,8 +318,6 @@ async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pol
|
|
|
302
318
|
log.info('Checking for review feedback...');
|
|
303
319
|
|
|
304
320
|
// Always proactively check for existing reviews first.
|
|
305
|
-
// This handles: resume after restart, review arrived before polling starts,
|
|
306
|
-
// or any case where the review is already available.
|
|
307
321
|
let gotReview = false;
|
|
308
322
|
const existingReviews = github.getReviews(projectDir, prInfo.number);
|
|
309
323
|
const existingComments = github.getIssueComments(projectDir, prInfo.number);
|
|
@@ -322,14 +336,17 @@ async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pol
|
|
|
322
336
|
}
|
|
323
337
|
|
|
324
338
|
if (!gotReview) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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;
|
|
330
344
|
}
|
|
331
|
-
|
|
345
|
+
log.warn(`Review wait timed out — auto-retrying (${pollRetries}/${MAX_POLL_RETRIES})...`);
|
|
346
|
+
round--; // retry same round
|
|
347
|
+
continue;
|
|
332
348
|
}
|
|
349
|
+
pollRetries = 0; // reset on success
|
|
333
350
|
|
|
334
351
|
// Step: Feedback received
|
|
335
352
|
checkpoint.saveStep(task.id, 'review', 'feedback_received', {
|
|
@@ -370,40 +387,37 @@ async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pol
|
|
|
370
387
|
const hasInlineComments = inlineComments && inlineComments.length > 0;
|
|
371
388
|
|
|
372
389
|
if (reviewState === 'CHANGES_REQUESTED') {
|
|
373
|
-
// Explicit change request — always treat as needing fixes
|
|
374
390
|
log.info('Review state: CHANGES_REQUESTED — entering fix phase');
|
|
375
391
|
} else if (reviewState === 'COMMENTED' && !hasInlineComments) {
|
|
376
|
-
// General comment with no code-level feedback — treat as passing
|
|
377
392
|
log.info('COMMENTED with no inline code comments — treating as passed ✅');
|
|
378
393
|
return;
|
|
379
394
|
} else if (!hasInlineComments) {
|
|
380
|
-
// Any other state (PENDING, null, etc.) with no inline comments — treat as passing
|
|
381
395
|
log.info('No inline code comments found — treating as passed ✅');
|
|
382
396
|
return;
|
|
383
397
|
} else {
|
|
384
|
-
// Has inline comments — treat as needing fixes
|
|
385
398
|
log.info(`Found ${inlineComments.length} inline code comment(s) — entering fix phase`);
|
|
386
399
|
}
|
|
387
400
|
}
|
|
388
401
|
|
|
389
402
|
// AI says FIX, or structural fallback indicates issues
|
|
390
|
-
|
|
391
403
|
log.blank();
|
|
392
404
|
log.warn(`Received review feedback (round ${round}/${maxRounds})`);
|
|
393
405
|
|
|
394
406
|
if (round >= maxRounds) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (choice === 'fix') {
|
|
407
|
+
if (maxRounds < HARD_MAX_ROUNDS) {
|
|
408
|
+
// Auto-extend: give it one more round
|
|
398
409
|
maxRounds++;
|
|
399
|
-
|
|
400
|
-
return;
|
|
410
|
+
log.warn(`Auto-extending fix rounds to ${maxRounds}`);
|
|
401
411
|
} else {
|
|
402
|
-
|
|
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;
|
|
403
417
|
}
|
|
404
418
|
}
|
|
405
419
|
|
|
406
|
-
// Let AI fix
|
|
420
|
+
// Let AI fix based on the specific review feedback
|
|
407
421
|
await fixPhase(projectDir, task, feedback, round, providerId);
|
|
408
422
|
|
|
409
423
|
// Step: Fix applied
|
|
@@ -419,10 +433,12 @@ async function reviewLoop(projectDir, task, prInfo, { maxRounds: _maxRounds, pol
|
|
|
419
433
|
git.pushBranch(projectDir, task.branch);
|
|
420
434
|
log.info('Fix pushed, waiting for next review round...');
|
|
421
435
|
// Brief wait for review bot to react
|
|
422
|
-
await sleep(
|
|
436
|
+
await sleep(15000);
|
|
423
437
|
} else {
|
|
424
|
-
//
|
|
425
|
-
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';
|
|
426
442
|
return;
|
|
427
443
|
}
|
|
428
444
|
}
|
|
@@ -488,15 +504,34 @@ ${feedback}
|
|
|
488
504
|
async function mergePhase(projectDir, task, prInfo, baseBranch, checkpoint) {
|
|
489
505
|
log.step('Phase 4/4: Merge PR');
|
|
490
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
|
+
|
|
491
513
|
log.info(`Auto-merging PR #${prInfo.number}...`);
|
|
492
514
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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;
|
|
500
535
|
}
|
|
501
536
|
|
|
502
537
|
checkpoint.saveStep(task.id, 'merge', 'merged', {
|