@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.
Files changed (2) hide show
  1. package/dist/index.js +63 -6
  2. 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
- await sleep(1e3 * 2 ** (attempt - 1));
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 formatElapsed(startedAt) {
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: ${formatElapsed(state.startedAt)}`);
1544
+ info(`Elapsed: ${formatElapsed3(state.startedAt)}`);
1488
1545
  }
1489
1546
  function registerStatusCommand(program2) {
1490
1547
  program2.command("status").description("Print active task status").action(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vexdo/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {