@vexdo/cli 0.1.1 → 0.1.2
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/dist/index.js +63 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -552,7 +552,11 @@ ${JSON.stringify(opts.reviewComments)}`
|
|
|
552
552
|
if (!isRetryableError(error) || attempt === MAX_ATTEMPTS) {
|
|
553
553
|
throw new ClaudeError(attempt, error);
|
|
554
554
|
}
|
|
555
|
-
|
|
555
|
+
const backoffMs = 1e3 * 2 ** (attempt - 1);
|
|
556
|
+
warn(
|
|
557
|
+
`Claude API error on attempt ${String(attempt)}/${String(MAX_ATTEMPTS)}. Retrying in ${String(Math.round(backoffMs / 1e3))}s...`
|
|
558
|
+
);
|
|
559
|
+
await sleep(backoffMs);
|
|
556
560
|
}
|
|
557
561
|
}
|
|
558
562
|
throw new ClaudeError(MAX_ATTEMPTS, lastError);
|
|
@@ -673,6 +677,7 @@ function isRetryableError(error) {
|
|
|
673
677
|
// src/lib/codex.ts
|
|
674
678
|
import { execFile as execFileCb } from "child_process";
|
|
675
679
|
var CODEX_TIMEOUT_MS = 6e5;
|
|
680
|
+
var VERBOSE_HEARTBEAT_MS = 15e3;
|
|
676
681
|
var CodexError = class extends Error {
|
|
677
682
|
stdout;
|
|
678
683
|
stderr;
|
|
@@ -691,6 +696,10 @@ var CodexNotFoundError = class extends Error {
|
|
|
691
696
|
this.name = "CodexNotFoundError";
|
|
692
697
|
}
|
|
693
698
|
};
|
|
699
|
+
function formatElapsed(startedAt) {
|
|
700
|
+
const seconds = Math.max(0, Math.round((Date.now() - startedAt) / 1e3));
|
|
701
|
+
return `${String(seconds)}s`;
|
|
702
|
+
}
|
|
694
703
|
async function checkCodexAvailable() {
|
|
695
704
|
await new Promise((resolve, reject) => {
|
|
696
705
|
execFileCb("codex", ["--version"], { timeout: CODEX_TIMEOUT_MS, encoding: "utf8" }, (error) => {
|
|
@@ -704,15 +713,26 @@ async function checkCodexAvailable() {
|
|
|
704
713
|
}
|
|
705
714
|
async function exec(opts) {
|
|
706
715
|
const args = ["exec", "--model", opts.model, "--full-auto", "--", opts.spec];
|
|
716
|
+
const startedAt = Date.now();
|
|
717
|
+
if (opts.verbose) {
|
|
718
|
+
debug(`[codex] starting (model=${opts.model}, cwd=${opts.cwd})`);
|
|
719
|
+
}
|
|
707
720
|
return await new Promise((resolve, reject) => {
|
|
721
|
+
const heartbeat = opts.verbose ? setInterval(() => {
|
|
722
|
+
debug(`[codex] still running (${formatElapsed(startedAt)})`);
|
|
723
|
+
}, VERBOSE_HEARTBEAT_MS) : null;
|
|
708
724
|
execFileCb(
|
|
709
725
|
"codex",
|
|
710
726
|
args,
|
|
711
727
|
{ cwd: opts.cwd, timeout: CODEX_TIMEOUT_MS, encoding: "utf8", maxBuffer: 10 * 1024 * 1024 },
|
|
712
728
|
(error, stdout, stderr) => {
|
|
729
|
+
if (heartbeat) {
|
|
730
|
+
clearInterval(heartbeat);
|
|
731
|
+
}
|
|
713
732
|
const normalizedStdout = stdout.trimEnd();
|
|
714
733
|
const normalizedStderr = stderr.trimEnd();
|
|
715
734
|
if (opts.verbose) {
|
|
735
|
+
debug(`[codex] finished in ${formatElapsed(startedAt)}`);
|
|
716
736
|
if (normalizedStdout) {
|
|
717
737
|
debug(normalizedStdout);
|
|
718
738
|
}
|
|
@@ -722,6 +742,9 @@ async function exec(opts) {
|
|
|
722
742
|
}
|
|
723
743
|
if (error) {
|
|
724
744
|
const exitCode = typeof error.code === "number" ? error.code : 1;
|
|
745
|
+
if (opts.verbose) {
|
|
746
|
+
debug(`[codex] failed in ${formatElapsed(startedAt)} with exit ${String(exitCode)}`);
|
|
747
|
+
}
|
|
725
748
|
reject(new CodexError(normalizedStdout, normalizedStderr || error.message, exitCode));
|
|
726
749
|
return;
|
|
727
750
|
}
|
|
@@ -846,6 +869,10 @@ function getBranchName(taskId, service) {
|
|
|
846
869
|
}
|
|
847
870
|
|
|
848
871
|
// src/lib/review-loop.ts
|
|
872
|
+
function formatElapsed2(startedAt) {
|
|
873
|
+
const seconds = Math.max(0, Math.round((Date.now() - startedAt) / 1e3));
|
|
874
|
+
return `${String(seconds)}s`;
|
|
875
|
+
}
|
|
849
876
|
async function runReviewLoop(opts) {
|
|
850
877
|
if (opts.dryRun) {
|
|
851
878
|
info(`[dry-run] Would run review loop for service ${opts.step.service}`);
|
|
@@ -868,7 +895,11 @@ async function runReviewLoop(opts) {
|
|
|
868
895
|
let iteration2 = opts.stepState.iteration;
|
|
869
896
|
for (; ; ) {
|
|
870
897
|
iteration(iteration2 + 1, opts.config.review.max_iterations);
|
|
898
|
+
info(`Collecting git diff for service ${opts.step.service}`);
|
|
871
899
|
const diff = await getDiff(serviceRoot);
|
|
900
|
+
if (opts.verbose) {
|
|
901
|
+
info(`Diff collected (${String(diff.length)} chars)`);
|
|
902
|
+
}
|
|
872
903
|
if (!diff.trim()) {
|
|
873
904
|
return {
|
|
874
905
|
decision: "submit",
|
|
@@ -881,18 +912,39 @@ async function runReviewLoop(opts) {
|
|
|
881
912
|
}
|
|
882
913
|
};
|
|
883
914
|
}
|
|
915
|
+
info(`Requesting reviewer analysis (model: ${opts.config.review.model})`);
|
|
916
|
+
const reviewerStartedAt = Date.now();
|
|
917
|
+
const reviewerHeartbeat = opts.verbose ? setInterval(() => {
|
|
918
|
+
info(`Waiting for reviewer response (${formatElapsed2(reviewerStartedAt)})`);
|
|
919
|
+
}, 15e3) : null;
|
|
884
920
|
const review = await opts.claude.runReviewer({
|
|
885
921
|
spec: opts.step.spec,
|
|
886
922
|
diff,
|
|
887
923
|
model: opts.config.review.model
|
|
924
|
+
}).finally(() => {
|
|
925
|
+
if (reviewerHeartbeat) {
|
|
926
|
+
clearInterval(reviewerHeartbeat);
|
|
927
|
+
}
|
|
888
928
|
});
|
|
929
|
+
info(`Reviewer response received in ${formatElapsed2(reviewerStartedAt)}`);
|
|
889
930
|
reviewSummary(review.comments);
|
|
931
|
+
info(`Requesting arbiter decision (model: ${opts.config.review.model})`);
|
|
932
|
+
const arbiterStartedAt = Date.now();
|
|
933
|
+
const arbiterHeartbeat = opts.verbose ? setInterval(() => {
|
|
934
|
+
info(`Waiting for arbiter response (${formatElapsed2(arbiterStartedAt)})`);
|
|
935
|
+
}, 15e3) : null;
|
|
890
936
|
const arbiter = await opts.claude.runArbiter({
|
|
891
937
|
spec: opts.step.spec,
|
|
892
938
|
diff,
|
|
893
939
|
reviewComments: review.comments,
|
|
894
940
|
model: opts.config.review.model
|
|
941
|
+
}).finally(() => {
|
|
942
|
+
if (arbiterHeartbeat) {
|
|
943
|
+
clearInterval(arbiterHeartbeat);
|
|
944
|
+
}
|
|
895
945
|
});
|
|
946
|
+
info(`Arbiter response received in ${formatElapsed2(arbiterStartedAt)}`);
|
|
947
|
+
info(`Arbiter decision: ${arbiter.decision} (${arbiter.summary})`);
|
|
896
948
|
saveIterationLog(opts.projectRoot, opts.taskId, opts.step.service, iteration2, {
|
|
897
949
|
diff,
|
|
898
950
|
review,
|
|
@@ -940,6 +992,7 @@ async function runReviewLoop(opts) {
|
|
|
940
992
|
}
|
|
941
993
|
};
|
|
942
994
|
}
|
|
995
|
+
info("Applying arbiter feedback with codex");
|
|
943
996
|
await exec({
|
|
944
997
|
spec: arbiter.feedback_for_codex,
|
|
945
998
|
model: opts.config.codex.model,
|
|
@@ -992,6 +1045,7 @@ async function runFix(feedback, options) {
|
|
|
992
1045
|
verbose: options.verbose
|
|
993
1046
|
});
|
|
994
1047
|
}
|
|
1048
|
+
info(`Running review loop for service ${step2.service}`);
|
|
995
1049
|
const result = await runReviewLoop({
|
|
996
1050
|
taskId: task.id,
|
|
997
1051
|
task,
|
|
@@ -1023,7 +1077,7 @@ async function runFix(feedback, options) {
|
|
|
1023
1077
|
}
|
|
1024
1078
|
}
|
|
1025
1079
|
function registerFixCommand(program2) {
|
|
1026
|
-
program2.command("fix").description("Provide feedback to codex and rerun review").argument("<feedback>").action(async (feedback, options, command) => {
|
|
1080
|
+
program2.command("fix").description("Provide feedback to codex and rerun review").argument("<feedback>").option("--verbose", "Enable verbose logs").option("--dry-run", "Print plan without making changes").action(async (feedback, options, command) => {
|
|
1027
1081
|
const merged = command.optsWithGlobals();
|
|
1028
1082
|
await runFix(feedback, { ...options, ...merged });
|
|
1029
1083
|
});
|
|
@@ -1223,6 +1277,7 @@ async function runReview(options) {
|
|
|
1223
1277
|
if (!step2) {
|
|
1224
1278
|
fatalAndExit4(`Could not locate task step for service '${currentStep.service}'.`);
|
|
1225
1279
|
}
|
|
1280
|
+
info(`Running review loop for service ${step2.service}`);
|
|
1226
1281
|
const result = await runReviewLoop({
|
|
1227
1282
|
taskId: task.id,
|
|
1228
1283
|
task,
|
|
@@ -1265,7 +1320,7 @@ async function runReview(options) {
|
|
|
1265
1320
|
}
|
|
1266
1321
|
}
|
|
1267
1322
|
function registerReviewCommand(program2) {
|
|
1268
|
-
program2.command("review").description("Run review loop for the current step").action(async (options, command) => {
|
|
1323
|
+
program2.command("review").description("Run review loop for the current step").option("--verbose", "Enable verbose logs").option("--dry-run", "Print plan without making changes").action(async (options, command) => {
|
|
1269
1324
|
const merged = command.optsWithGlobals();
|
|
1270
1325
|
await runReview({ ...options, ...merged });
|
|
1271
1326
|
});
|
|
@@ -1382,6 +1437,7 @@ async function runStart(taskFile, options) {
|
|
|
1382
1437
|
saveState(projectRoot, state);
|
|
1383
1438
|
}
|
|
1384
1439
|
if (!options.resume && !options.dryRun) {
|
|
1440
|
+
info(`Running codex implementation for service ${step2.service}`);
|
|
1385
1441
|
await exec({
|
|
1386
1442
|
spec: step2.spec,
|
|
1387
1443
|
model: config.codex.model,
|
|
@@ -1391,6 +1447,7 @@ async function runStart(taskFile, options) {
|
|
|
1391
1447
|
} else if (options.dryRun) {
|
|
1392
1448
|
info(`[dry-run] Would run codex for service ${step2.service}`);
|
|
1393
1449
|
}
|
|
1450
|
+
info(`Starting review loop for service ${step2.service}`);
|
|
1394
1451
|
const result = await runReviewLoop({
|
|
1395
1452
|
taskId: task.id,
|
|
1396
1453
|
task,
|
|
@@ -1445,7 +1502,7 @@ async function runStart(taskFile, options) {
|
|
|
1445
1502
|
}
|
|
1446
1503
|
}
|
|
1447
1504
|
function registerStartCommand(program2) {
|
|
1448
|
-
program2.command("start").description("Start a task from a YAML file").argument("<task-file>").option("--resume", "Resume an existing active task").action(async (taskFile, options, command) => {
|
|
1505
|
+
program2.command("start").description("Start a task from a YAML file").argument("<task-file>").option("--verbose", "Enable verbose logs").option("--dry-run", "Print plan without making changes").option("--resume", "Resume an existing active task").action(async (taskFile, options, command) => {
|
|
1449
1506
|
const merged = command.optsWithGlobals();
|
|
1450
1507
|
await runStart(taskFile, { ...options, ...merged });
|
|
1451
1508
|
});
|
|
@@ -1456,7 +1513,7 @@ function fatalAndExit6(message) {
|
|
|
1456
1513
|
fatal(message);
|
|
1457
1514
|
process.exit(1);
|
|
1458
1515
|
}
|
|
1459
|
-
function
|
|
1516
|
+
function formatElapsed3(startedAt) {
|
|
1460
1517
|
const elapsedMs = Date.now() - new Date(startedAt).getTime();
|
|
1461
1518
|
const minutes = Math.floor(elapsedMs / 1e3 / 60);
|
|
1462
1519
|
const hours = Math.floor(minutes / 60);
|
|
@@ -1484,7 +1541,7 @@ function runStatus() {
|
|
|
1484
1541
|
if (inProgress?.lastArbiterResult?.summary) {
|
|
1485
1542
|
info(`Last arbiter summary: ${inProgress.lastArbiterResult.summary}`);
|
|
1486
1543
|
}
|
|
1487
|
-
info(`Elapsed: ${
|
|
1544
|
+
info(`Elapsed: ${formatElapsed3(state.startedAt)}`);
|
|
1488
1545
|
}
|
|
1489
1546
|
function registerStatusCommand(program2) {
|
|
1490
1547
|
program2.command("status").description("Print active task status").action(() => {
|