@fermindi/pwn-cli 0.9.3 → 0.9.4
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/services/batch-runner.js +71 -3
package/package.json
CHANGED
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
* Completed files are cleaned up at the end; failed are kept for review.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { spawn } from 'child_process';
|
|
12
|
+
import { spawn, exec } from 'child_process';
|
|
13
13
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, createWriteStream, appendFileSync, unlinkSync, readdirSync } from 'fs';
|
|
14
14
|
import { join } from 'path';
|
|
15
|
+
import { promisify } from 'util';
|
|
15
16
|
import ora from 'ora';
|
|
17
|
+
|
|
18
|
+
const execAsync = promisify(exec);
|
|
16
19
|
import chalk from 'chalk';
|
|
17
20
|
import {
|
|
18
21
|
parsePrdTasks,
|
|
@@ -31,7 +34,7 @@ import {
|
|
|
31
34
|
} from './batch-service.js';
|
|
32
35
|
|
|
33
36
|
// --- Constants ---
|
|
34
|
-
const RUNNER_VERSION = '2.
|
|
37
|
+
const RUNNER_VERSION = '2.3';
|
|
35
38
|
const DEFAULT_TIMEOUT_MS = 900_000; // 15 minutes fallback
|
|
36
39
|
const MIN_TIMEOUT_MS = 300_000; // 5 minutes minimum (claude init ~30-40s + real work)
|
|
37
40
|
|
|
@@ -405,13 +408,78 @@ export async function runBatch(options = {}, cwd = process.cwd()) {
|
|
|
405
408
|
console.log(chalk.yellow(' Continuing on current branch...'));
|
|
406
409
|
}
|
|
407
410
|
|
|
411
|
+
// --- Pre-validation: check if existing branch already passes gates ---
|
|
412
|
+
let storyDone = false;
|
|
413
|
+
if (taskBranch) {
|
|
414
|
+
// Check if this branch has commits beyond the batch branch
|
|
415
|
+
try {
|
|
416
|
+
const { stdout: diffStat } = await execAsync('git diff --stat HEAD~1 2>/dev/null || echo ""', { cwd });
|
|
417
|
+
const { stdout: logCount } = await execAsync(`git rev-list --count ${batchBranch}..HEAD`, { cwd });
|
|
418
|
+
const commits = parseInt(logCount.trim(), 10);
|
|
419
|
+
|
|
420
|
+
if (commits > 0) {
|
|
421
|
+
console.log(chalk.blue(` Pre-validation: found ${commits} commit(s) on ${taskBranch}, running quality gates...`));
|
|
422
|
+
const preGates = await runGatesWithStatus(cwd);
|
|
423
|
+
|
|
424
|
+
if (preGates.success) {
|
|
425
|
+
console.log(chalk.green(` Pre-validation PASSED — skipping execution`));
|
|
426
|
+
markStoryDone(task.id, cwd);
|
|
427
|
+
appendProgress(progressPath, task.id, 'Pre-validation: existing code passed quality gates');
|
|
428
|
+
|
|
429
|
+
storyDone = true;
|
|
430
|
+
storiesCompleted++;
|
|
431
|
+
noProgressCount = 0;
|
|
432
|
+
|
|
433
|
+
if (taskFile) {
|
|
434
|
+
taskFile.status = 'completed';
|
|
435
|
+
taskFile.completed_at = new Date().toISOString();
|
|
436
|
+
saveTaskFile(taskFile, cwd);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
updateBatchState({
|
|
440
|
+
completed: [task.id],
|
|
441
|
+
current_task: null,
|
|
442
|
+
last_completed_at: new Date().toISOString()
|
|
443
|
+
}, cwd);
|
|
444
|
+
|
|
445
|
+
// Merge into batch branch
|
|
446
|
+
try {
|
|
447
|
+
await checkoutBranch(batchBranch, cwd, { stash: true });
|
|
448
|
+
await mergeBranch(taskBranch, cwd);
|
|
449
|
+
mergedCount++;
|
|
450
|
+
mergedBranches.push(taskBranch);
|
|
451
|
+
console.log(chalk.green(` Merged ${taskBranch} → ${batchBranch}`));
|
|
452
|
+
} catch (err) {
|
|
453
|
+
console.log(chalk.red(` Merge failed: ${err.message}`));
|
|
454
|
+
console.log(chalk.yellow(` Branch ${taskBranch} available for manual merge`));
|
|
455
|
+
unmergedBranches.push(taskBranch);
|
|
456
|
+
}
|
|
457
|
+
} else {
|
|
458
|
+
console.log(chalk.yellow(` Pre-validation FAILED — will re-execute`));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
} catch {
|
|
462
|
+
// No commits or comparison failed — proceed normally
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (storyDone) {
|
|
467
|
+
// Return to batch branch
|
|
468
|
+
if (taskBranch) {
|
|
469
|
+
try {
|
|
470
|
+
await checkoutBranch(batchBranch, cwd, { force: true, stash: true });
|
|
471
|
+
console.log(chalk.dim(` Returned to branch: ${batchBranch}`));
|
|
472
|
+
} catch {}
|
|
473
|
+
}
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
|
|
408
477
|
// --- Phase 2: Execution ---
|
|
409
478
|
const phaseLabel = noPlan ? '' : 'Phase 2';
|
|
410
479
|
console.log(chalk.blue(` ${noPlan ? 'Executing' : 'Phase 2: Executing'} ${task.id}...`));
|
|
411
480
|
|
|
412
481
|
let retry = 0;
|
|
413
482
|
let rateLimitAttempts = 0;
|
|
414
|
-
let storyDone = false;
|
|
415
483
|
let errorContext = '';
|
|
416
484
|
|
|
417
485
|
while (retry <= MAX_RETRIES && !storyDone) {
|