@vexdo/cli 0.1.1 → 0.1.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/dist/index.js +106 -9
- 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,33 @@ 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
|
+
}
|
|
703
|
+
function buildVerboseStreamHandler(label) {
|
|
704
|
+
let partialLine = "";
|
|
705
|
+
return {
|
|
706
|
+
onData(chunk) {
|
|
707
|
+
partialLine += chunk.toString();
|
|
708
|
+
const lines = partialLine.split(/\r?\n/);
|
|
709
|
+
partialLine = lines.pop() ?? "";
|
|
710
|
+
for (const line of lines) {
|
|
711
|
+
if (!line) {
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
debug(`[codex:${label}] ${line}`);
|
|
715
|
+
}
|
|
716
|
+
},
|
|
717
|
+
flush() {
|
|
718
|
+
if (!partialLine) {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
debug(`[codex:${label}] ${partialLine}`);
|
|
722
|
+
partialLine = "";
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
}
|
|
694
726
|
async function checkCodexAvailable() {
|
|
695
727
|
await new Promise((resolve, reject) => {
|
|
696
728
|
execFileCb("codex", ["--version"], { timeout: CODEX_TIMEOUT_MS, encoding: "utf8" }, (error) => {
|
|
@@ -704,24 +736,43 @@ async function checkCodexAvailable() {
|
|
|
704
736
|
}
|
|
705
737
|
async function exec(opts) {
|
|
706
738
|
const args = ["exec", "--model", opts.model, "--full-auto", "--", opts.spec];
|
|
739
|
+
const startedAt = Date.now();
|
|
740
|
+
if (opts.verbose) {
|
|
741
|
+
debug(`[codex] starting (model=${opts.model}, cwd=${opts.cwd})`);
|
|
742
|
+
}
|
|
707
743
|
return await new Promise((resolve, reject) => {
|
|
708
|
-
|
|
744
|
+
let liveLogsAttached = false;
|
|
745
|
+
const stdoutHandler = buildVerboseStreamHandler("stdout");
|
|
746
|
+
const stderrHandler = buildVerboseStreamHandler("stderr");
|
|
747
|
+
const heartbeat = opts.verbose ? setInterval(() => {
|
|
748
|
+
debug(`[codex] still running (${formatElapsed(startedAt)})`);
|
|
749
|
+
}, VERBOSE_HEARTBEAT_MS) : null;
|
|
750
|
+
const child = execFileCb(
|
|
709
751
|
"codex",
|
|
710
752
|
args,
|
|
711
753
|
{ cwd: opts.cwd, timeout: CODEX_TIMEOUT_MS, encoding: "utf8", maxBuffer: 10 * 1024 * 1024 },
|
|
712
754
|
(error, stdout, stderr) => {
|
|
755
|
+
if (heartbeat) {
|
|
756
|
+
clearInterval(heartbeat);
|
|
757
|
+
}
|
|
758
|
+
stdoutHandler.flush();
|
|
759
|
+
stderrHandler.flush();
|
|
713
760
|
const normalizedStdout = stdout.trimEnd();
|
|
714
761
|
const normalizedStderr = stderr.trimEnd();
|
|
715
762
|
if (opts.verbose) {
|
|
716
|
-
|
|
763
|
+
debug(`[codex] finished in ${formatElapsed(startedAt)}`);
|
|
764
|
+
if (!liveLogsAttached && normalizedStdout) {
|
|
717
765
|
debug(normalizedStdout);
|
|
718
766
|
}
|
|
719
|
-
if (normalizedStderr) {
|
|
767
|
+
if (!liveLogsAttached && normalizedStderr) {
|
|
720
768
|
debug(normalizedStderr);
|
|
721
769
|
}
|
|
722
770
|
}
|
|
723
771
|
if (error) {
|
|
724
772
|
const exitCode = typeof error.code === "number" ? error.code : 1;
|
|
773
|
+
if (opts.verbose) {
|
|
774
|
+
debug(`[codex] failed in ${formatElapsed(startedAt)} with exit ${String(exitCode)}`);
|
|
775
|
+
}
|
|
725
776
|
reject(new CodexError(normalizedStdout, normalizedStderr || error.message, exitCode));
|
|
726
777
|
return;
|
|
727
778
|
}
|
|
@@ -732,6 +783,18 @@ async function exec(opts) {
|
|
|
732
783
|
});
|
|
733
784
|
}
|
|
734
785
|
);
|
|
786
|
+
if (opts.verbose) {
|
|
787
|
+
const stdout = child.stdout;
|
|
788
|
+
const stderr = child.stderr;
|
|
789
|
+
if (stdout) {
|
|
790
|
+
liveLogsAttached = true;
|
|
791
|
+
stdout.on("data", stdoutHandler.onData);
|
|
792
|
+
}
|
|
793
|
+
if (stderr) {
|
|
794
|
+
liveLogsAttached = true;
|
|
795
|
+
stderr.on("data", stderrHandler.onData);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
735
798
|
});
|
|
736
799
|
}
|
|
737
800
|
|
|
@@ -846,6 +909,10 @@ function getBranchName(taskId, service) {
|
|
|
846
909
|
}
|
|
847
910
|
|
|
848
911
|
// src/lib/review-loop.ts
|
|
912
|
+
function formatElapsed2(startedAt) {
|
|
913
|
+
const seconds = Math.max(0, Math.round((Date.now() - startedAt) / 1e3));
|
|
914
|
+
return `${String(seconds)}s`;
|
|
915
|
+
}
|
|
849
916
|
async function runReviewLoop(opts) {
|
|
850
917
|
if (opts.dryRun) {
|
|
851
918
|
info(`[dry-run] Would run review loop for service ${opts.step.service}`);
|
|
@@ -868,7 +935,11 @@ async function runReviewLoop(opts) {
|
|
|
868
935
|
let iteration2 = opts.stepState.iteration;
|
|
869
936
|
for (; ; ) {
|
|
870
937
|
iteration(iteration2 + 1, opts.config.review.max_iterations);
|
|
938
|
+
info(`Collecting git diff for service ${opts.step.service}`);
|
|
871
939
|
const diff = await getDiff(serviceRoot);
|
|
940
|
+
if (opts.verbose) {
|
|
941
|
+
info(`Diff collected (${String(diff.length)} chars)`);
|
|
942
|
+
}
|
|
872
943
|
if (!diff.trim()) {
|
|
873
944
|
return {
|
|
874
945
|
decision: "submit",
|
|
@@ -881,18 +952,39 @@ async function runReviewLoop(opts) {
|
|
|
881
952
|
}
|
|
882
953
|
};
|
|
883
954
|
}
|
|
955
|
+
info(`Requesting reviewer analysis (model: ${opts.config.review.model})`);
|
|
956
|
+
const reviewerStartedAt = Date.now();
|
|
957
|
+
const reviewerHeartbeat = opts.verbose ? setInterval(() => {
|
|
958
|
+
info(`Waiting for reviewer response (${formatElapsed2(reviewerStartedAt)})`);
|
|
959
|
+
}, 15e3) : null;
|
|
884
960
|
const review = await opts.claude.runReviewer({
|
|
885
961
|
spec: opts.step.spec,
|
|
886
962
|
diff,
|
|
887
963
|
model: opts.config.review.model
|
|
964
|
+
}).finally(() => {
|
|
965
|
+
if (reviewerHeartbeat) {
|
|
966
|
+
clearInterval(reviewerHeartbeat);
|
|
967
|
+
}
|
|
888
968
|
});
|
|
969
|
+
info(`Reviewer response received in ${formatElapsed2(reviewerStartedAt)}`);
|
|
889
970
|
reviewSummary(review.comments);
|
|
971
|
+
info(`Requesting arbiter decision (model: ${opts.config.review.model})`);
|
|
972
|
+
const arbiterStartedAt = Date.now();
|
|
973
|
+
const arbiterHeartbeat = opts.verbose ? setInterval(() => {
|
|
974
|
+
info(`Waiting for arbiter response (${formatElapsed2(arbiterStartedAt)})`);
|
|
975
|
+
}, 15e3) : null;
|
|
890
976
|
const arbiter = await opts.claude.runArbiter({
|
|
891
977
|
spec: opts.step.spec,
|
|
892
978
|
diff,
|
|
893
979
|
reviewComments: review.comments,
|
|
894
980
|
model: opts.config.review.model
|
|
981
|
+
}).finally(() => {
|
|
982
|
+
if (arbiterHeartbeat) {
|
|
983
|
+
clearInterval(arbiterHeartbeat);
|
|
984
|
+
}
|
|
895
985
|
});
|
|
986
|
+
info(`Arbiter response received in ${formatElapsed2(arbiterStartedAt)}`);
|
|
987
|
+
info(`Arbiter decision: ${arbiter.decision} (${arbiter.summary})`);
|
|
896
988
|
saveIterationLog(opts.projectRoot, opts.taskId, opts.step.service, iteration2, {
|
|
897
989
|
diff,
|
|
898
990
|
review,
|
|
@@ -940,6 +1032,7 @@ async function runReviewLoop(opts) {
|
|
|
940
1032
|
}
|
|
941
1033
|
};
|
|
942
1034
|
}
|
|
1035
|
+
info("Applying arbiter feedback with codex");
|
|
943
1036
|
await exec({
|
|
944
1037
|
spec: arbiter.feedback_for_codex,
|
|
945
1038
|
model: opts.config.codex.model,
|
|
@@ -992,6 +1085,7 @@ async function runFix(feedback, options) {
|
|
|
992
1085
|
verbose: options.verbose
|
|
993
1086
|
});
|
|
994
1087
|
}
|
|
1088
|
+
info(`Running review loop for service ${step2.service}`);
|
|
995
1089
|
const result = await runReviewLoop({
|
|
996
1090
|
taskId: task.id,
|
|
997
1091
|
task,
|
|
@@ -1023,7 +1117,7 @@ async function runFix(feedback, options) {
|
|
|
1023
1117
|
}
|
|
1024
1118
|
}
|
|
1025
1119
|
function registerFixCommand(program2) {
|
|
1026
|
-
program2.command("fix").description("Provide feedback to codex and rerun review").argument("<feedback>").action(async (feedback, options, command) => {
|
|
1120
|
+
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
1121
|
const merged = command.optsWithGlobals();
|
|
1028
1122
|
await runFix(feedback, { ...options, ...merged });
|
|
1029
1123
|
});
|
|
@@ -1223,6 +1317,7 @@ async function runReview(options) {
|
|
|
1223
1317
|
if (!step2) {
|
|
1224
1318
|
fatalAndExit4(`Could not locate task step for service '${currentStep.service}'.`);
|
|
1225
1319
|
}
|
|
1320
|
+
info(`Running review loop for service ${step2.service}`);
|
|
1226
1321
|
const result = await runReviewLoop({
|
|
1227
1322
|
taskId: task.id,
|
|
1228
1323
|
task,
|
|
@@ -1265,7 +1360,7 @@ async function runReview(options) {
|
|
|
1265
1360
|
}
|
|
1266
1361
|
}
|
|
1267
1362
|
function registerReviewCommand(program2) {
|
|
1268
|
-
program2.command("review").description("Run review loop for the current step").action(async (options, command) => {
|
|
1363
|
+
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
1364
|
const merged = command.optsWithGlobals();
|
|
1270
1365
|
await runReview({ ...options, ...merged });
|
|
1271
1366
|
});
|
|
@@ -1382,6 +1477,7 @@ async function runStart(taskFile, options) {
|
|
|
1382
1477
|
saveState(projectRoot, state);
|
|
1383
1478
|
}
|
|
1384
1479
|
if (!options.resume && !options.dryRun) {
|
|
1480
|
+
info(`Running codex implementation for service ${step2.service}`);
|
|
1385
1481
|
await exec({
|
|
1386
1482
|
spec: step2.spec,
|
|
1387
1483
|
model: config.codex.model,
|
|
@@ -1391,6 +1487,7 @@ async function runStart(taskFile, options) {
|
|
|
1391
1487
|
} else if (options.dryRun) {
|
|
1392
1488
|
info(`[dry-run] Would run codex for service ${step2.service}`);
|
|
1393
1489
|
}
|
|
1490
|
+
info(`Starting review loop for service ${step2.service}`);
|
|
1394
1491
|
const result = await runReviewLoop({
|
|
1395
1492
|
taskId: task.id,
|
|
1396
1493
|
task,
|
|
@@ -1445,7 +1542,7 @@ async function runStart(taskFile, options) {
|
|
|
1445
1542
|
}
|
|
1446
1543
|
}
|
|
1447
1544
|
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) => {
|
|
1545
|
+
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
1546
|
const merged = command.optsWithGlobals();
|
|
1450
1547
|
await runStart(taskFile, { ...options, ...merged });
|
|
1451
1548
|
});
|
|
@@ -1456,7 +1553,7 @@ function fatalAndExit6(message) {
|
|
|
1456
1553
|
fatal(message);
|
|
1457
1554
|
process.exit(1);
|
|
1458
1555
|
}
|
|
1459
|
-
function
|
|
1556
|
+
function formatElapsed3(startedAt) {
|
|
1460
1557
|
const elapsedMs = Date.now() - new Date(startedAt).getTime();
|
|
1461
1558
|
const minutes = Math.floor(elapsedMs / 1e3 / 60);
|
|
1462
1559
|
const hours = Math.floor(minutes / 60);
|
|
@@ -1484,7 +1581,7 @@ function runStatus() {
|
|
|
1484
1581
|
if (inProgress?.lastArbiterResult?.summary) {
|
|
1485
1582
|
info(`Last arbiter summary: ${inProgress.lastArbiterResult.summary}`);
|
|
1486
1583
|
}
|
|
1487
|
-
info(`Elapsed: ${
|
|
1584
|
+
info(`Elapsed: ${formatElapsed3(state.startedAt)}`);
|
|
1488
1585
|
}
|
|
1489
1586
|
function registerStatusCommand(program2) {
|
|
1490
1587
|
program2.command("status").description("Print active task status").action(() => {
|