xab 2.0.0 → 4.0.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.
Files changed (2) hide show
  1. package/dist/index.js +297 -39
  2. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -473,7 +473,14 @@ You are looking at a worktree based on the TARGET branch "${opts.targetBranch}".
473
473
  - "no" = missing
474
474
  4. If not fully present, describe a step-by-step strategy for applying cleanly
475
475
  5. List which top-level components/directories are affected
476
- 6. Consider: does this change make sense for the target? Is it useful?`;
476
+ 6. Consider: does this change make sense for the target? Is it useful?
477
+ 7. Check if this commit requires any operator action beyond a normal code deploy:
478
+ - New environment variables added to .env / .env.example? \u2192 note which ones
479
+ - Database schema changes or migrations? \u2192 note what to run
480
+ - New services, containers, or infrastructure to deploy? \u2192 note what
481
+ - Config files that need manual updates on servers? \u2192 note which
482
+ - Dependencies on external services being added or removed? \u2192 note what
483
+ - If the commit is just normal code changes that only need a deploy+restart, leave opsNotes as []`;
477
484
  let turn;
478
485
  if (diffChunks.length > 1) {
479
486
  await thread.run(firstPrompt);
@@ -500,7 +507,8 @@ You now have the complete diff. Analyze and produce your structured response.`,
500
507
  alreadyInTarget: "no",
501
508
  reasoning: "Could not parse structured output",
502
509
  applicationStrategy: "Manual review recommended",
503
- affectedComponents: []
510
+ affectedComponents: [],
511
+ opsNotes: []
504
512
  });
505
513
  }
506
514
  async function applyCommit(opts) {
@@ -642,9 +650,14 @@ var init_codex = __esm(() => {
642
650
  type: "array",
643
651
  items: { type: "string" },
644
652
  description: "Top-level directories/components affected (e.g. 'api', 'frontend', 'contracts')"
653
+ },
654
+ opsNotes: {
655
+ type: "array",
656
+ items: { type: "string" },
657
+ description: "Operator action items ONLY if this commit requires something beyond a standard code deploy+restart. Examples: new env vars to add, database migrations to run, new services to deploy, infrastructure changes, config file updates on servers. Leave as empty array [] if no operator action is needed \u2014 a normal code deploy does NOT count."
645
658
  }
646
659
  },
647
- required: ["summary", "alreadyInTarget", "reasoning", "applicationStrategy", "affectedComponents"],
660
+ required: ["summary", "alreadyInTarget", "reasoning", "applicationStrategy", "affectedComponents", "opsNotes"],
648
661
  additionalProperties: false
649
662
  };
650
663
  applyResultSchema = {
@@ -771,7 +784,15 @@ You can:
771
784
 
772
785
  You MUST NOT modify the worktree in any way. No file writes, no git commits, no destructive commands.
773
786
 
774
- Run relevant tests if you can determine the test command from the repo. Your objections will be sent back to the apply agent for fixing, so be specific and actionable.`
787
+ Testing guidelines:
788
+ - Only run tests that work without API keys, secrets, or external service connections
789
+ - Before running a test, check if it needs env vars by reading the test file or relevant .env.example
790
+ - If a test needs keys, only run it if you can see a .env file with those vars already populated
791
+ - Prefer: type-checks (tsc --noEmit), linters (eslint), unit tests, build checks (forge build, go build)
792
+ - Avoid: integration tests hitting external APIs, tests requiring running databases/services
793
+ - If you can't determine whether a test needs keys, skip it \u2014 don't run and fail
794
+
795
+ Your objections will be sent back to the apply agent for fixing, so be specific and actionable.`
775
796
  }
776
797
  });
777
798
  let resultText = "";
@@ -1138,17 +1159,23 @@ async function validateApply(worktreeGit, beforeHash) {
1138
1159
  errors.push(`Failed to check commits: ${e.message}`);
1139
1160
  }
1140
1161
  const status = await worktreeGit.status();
1141
- const worktreeClean = status.modified.length === 0 && status.created.length === 0 && status.deleted.length === 0 && status.conflicted.length === 0 && status.not_added.length === 0;
1162
+ const isOurs = (f) => f.startsWith(".backmerge/") || f.startsWith(".backmerge\\");
1163
+ const modified = status.modified.filter((f) => !isOurs(f));
1164
+ const created = status.created.filter((f) => !isOurs(f));
1165
+ const deleted = status.deleted.filter((f) => !isOurs(f));
1166
+ const notAdded = status.not_added.filter((f) => !isOurs(f));
1167
+ const conflicted = status.conflicted.filter((f) => !isOurs(f));
1168
+ const worktreeClean = modified.length === 0 && created.length === 0 && deleted.length === 0 && conflicted.length === 0 && notAdded.length === 0;
1142
1169
  if (!worktreeClean) {
1143
1170
  const parts = [];
1144
- if (status.modified.length)
1145
- parts.push(`${status.modified.length} modified`);
1146
- if (status.not_added.length)
1147
- parts.push(`${status.not_added.length} untracked`);
1148
- if (status.deleted.length)
1149
- parts.push(`${status.deleted.length} deleted`);
1150
- if (status.conflicted.length)
1151
- parts.push(`${status.conflicted.length} conflicted`);
1171
+ if (modified.length)
1172
+ parts.push(`${modified.length} modified`);
1173
+ if (notAdded.length)
1174
+ parts.push(`${notAdded.length} untracked`);
1175
+ if (deleted.length)
1176
+ parts.push(`${deleted.length} deleted`);
1177
+ if (conflicted.length)
1178
+ parts.push(`${conflicted.length} conflicted`);
1152
1179
  errors.push(`Working tree not clean: ${parts.join(", ")}`);
1153
1180
  }
1154
1181
  const conflictMarkers = [];
@@ -1179,7 +1206,7 @@ async function getAppliedDiffStat(worktreeGit, beforeHash) {
1179
1206
  }
1180
1207
  async function resetHard(git, ref) {
1181
1208
  await git.raw(["reset", "--hard", ref]);
1182
- await git.raw(["clean", "-fd"]);
1209
+ await git.raw(["clean", "-fd", "--exclude=.backmerge"]);
1183
1210
  }
1184
1211
  async function fetchOrigin(git) {
1185
1212
  await git.fetch(["--all", "--prune"]);
@@ -1260,7 +1287,8 @@ async function runEngine(opts, cb) {
1260
1287
  worktreePath: "",
1261
1288
  workBranch: "",
1262
1289
  auditDir: "",
1263
- commits: []
1290
+ commits: [],
1291
+ opsNotes: []
1264
1292
  };
1265
1293
  }
1266
1294
  cb.onStatus("Detecting already cherry-picked commits...");
@@ -1293,7 +1321,8 @@ async function runEngine(opts, cb) {
1293
1321
  worktreePath: "",
1294
1322
  workBranch: "",
1295
1323
  auditDir: "",
1296
- commits: commitsToProcess
1324
+ commits: commitsToProcess,
1325
+ opsNotes: []
1297
1326
  };
1298
1327
  }
1299
1328
  const repoName = repoPath.split("/").pop() ?? "repo";
@@ -1320,7 +1349,7 @@ async function runEngine(opts, cb) {
1320
1349
  cb.onLog(`Eval worktree (detached): ${wtPath}`, "green");
1321
1350
  const wtGit = createGit(wtPath);
1322
1351
  const runId = `run-${ts}`;
1323
- const audit = new AuditLog(wtPath, runId);
1352
+ const audit = new AuditLog(repoPath, runId);
1324
1353
  const runMeta = {
1325
1354
  runId,
1326
1355
  startedAt: new Date().toISOString(),
@@ -1394,13 +1423,26 @@ async function runEngine(opts, cb) {
1394
1423
  cb.onDecision(commit, decision);
1395
1424
  }
1396
1425
  audit.runEnd(summary, decisions);
1426
+ const opsNotes = decisions.filter((d) => d.opsNotes && d.opsNotes.length > 0).map((d) => ({ commitHash: d.commitHash, commitMessage: d.commitMessage, notes: d.opsNotes }));
1427
+ if (opsNotes.length > 0) {
1428
+ cb.onLog("", "gray");
1429
+ cb.onLog("\u2550\u2550\u2550 OPERATOR NOTES \u2550\u2550\u2550", "yellow");
1430
+ for (const entry of opsNotes) {
1431
+ cb.onLog(` ${entry.commitHash.slice(0, 8)} ${entry.commitMessage}:`, "yellow");
1432
+ for (const note of entry.notes) {
1433
+ cb.onLog(` \u2192 ${note}`, "yellow");
1434
+ }
1435
+ }
1436
+ cb.onLog("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550", "yellow");
1437
+ }
1397
1438
  return {
1398
1439
  summary,
1399
1440
  decisions,
1400
1441
  worktreePath: wtPath,
1401
1442
  workBranch: wbName,
1402
1443
  auditDir: audit.runDir,
1403
- commits: commitsToProcess
1444
+ commits: commitsToProcess,
1445
+ opsNotes
1404
1446
  };
1405
1447
  }
1406
1448
  async function processOneCommit(o) {
@@ -1452,12 +1494,12 @@ async function processOneCommit(o) {
1452
1494
  };
1453
1495
  }
1454
1496
  if (o.dryRun) {
1455
- const kind = analysis.alreadyInTarget === "partial" ? "would_apply" : "would_apply";
1456
1497
  return {
1457
- kind,
1498
+ kind: "would_apply",
1458
1499
  commitHash: commit.hash,
1459
1500
  commitMessage: commit.message,
1460
1501
  reason: analysis.applicationStrategy.slice(0, 300),
1502
+ opsNotes: analysis.opsNotes.length > 0 ? analysis.opsNotes : undefined,
1461
1503
  durationMs: Date.now() - start
1462
1504
  };
1463
1505
  }
@@ -1656,6 +1698,7 @@ async function processOneCommit(o) {
1656
1698
  newCommitHash: validation.newCommitHash ?? undefined,
1657
1699
  filesChanged: applyResult.filesChanged,
1658
1700
  reviewApproved: o.review ? true : undefined,
1701
+ opsNotes: analysis.opsNotes.length > 0 ? analysis.opsNotes : undefined,
1659
1702
  durationMs: Date.now() - start
1660
1703
  };
1661
1704
  const invariantErrors = validateDecision(d, headBefore, headAfter, o.dryRun);
@@ -1707,51 +1750,225 @@ var exports_batch = {};
1707
1750
  __export(exports_batch, {
1708
1751
  runBatch: () => runBatch
1709
1752
  });
1710
- function emit(obj) {
1753
+ import chalk from "chalk";
1754
+ function shortHash2(h) {
1755
+ return h.slice(0, 8);
1756
+ }
1757
+ function ts() {
1758
+ return chalk.dim(new Date().toISOString().slice(11, 19));
1759
+ }
1760
+ function progressBar2(current, total, width = 25) {
1761
+ const ratio = Math.min(current / total, 1);
1762
+ const filled = Math.round(ratio * width);
1763
+ const empty = width - filled;
1764
+ return `${chalk.green("\u2588".repeat(filled))}${chalk.dim("\u2591".repeat(empty))} ${current}/${total}`;
1765
+ }
1766
+ function divider(char = "\u2500", width = 55) {
1767
+ return chalk.dim(char.repeat(width));
1768
+ }
1769
+ function colorize(color, text) {
1770
+ if (!color)
1771
+ return text;
1772
+ return (colorMap[color] ?? chalk.white)(text);
1773
+ }
1774
+ function decisionBadge(kind) {
1775
+ switch (kind) {
1776
+ case "applied":
1777
+ return chalk.bgGreen.black.bold(" APPLIED ");
1778
+ case "would_apply":
1779
+ return chalk.bgCyan.black.bold(" WOULD APPLY ");
1780
+ case "already_applied":
1781
+ return chalk.bgBlue.white.bold(" ALREADY APPLIED ");
1782
+ case "skip":
1783
+ return chalk.bgYellow.black.bold(" SKIP ");
1784
+ case "failed":
1785
+ return chalk.bgRed.white.bold(" FAILED ");
1786
+ }
1787
+ }
1788
+ function analysisBadge(status) {
1789
+ switch (status) {
1790
+ case "yes":
1791
+ return chalk.green.bold("PRESENT");
1792
+ case "no":
1793
+ return chalk.red.bold("MISSING");
1794
+ case "partial":
1795
+ return chalk.yellow.bold("PARTIAL");
1796
+ }
1797
+ }
1798
+ function log(msg) {
1799
+ process.stderr.write(msg + `
1800
+ `);
1801
+ }
1802
+ function emitJsonl(obj) {
1711
1803
  process.stdout.write(JSON.stringify({ ...obj, ts: new Date().toISOString() }) + `
1712
1804
  `);
1713
1805
  }
1714
1806
  async function runBatch(opts) {
1807
+ const jsonl = opts.jsonl ?? false;
1808
+ const startTime = Date.now();
1809
+ log("");
1810
+ log(` ${chalk.cyan.bold("xab")} ${chalk.dim("\u2014 curated branch reconciliation")}`);
1811
+ log(` ${chalk.magenta(opts.sourceRef)} ${chalk.dim("\u2192")} ${chalk.green(opts.targetRef)}`);
1812
+ if (opts.workBranch)
1813
+ log(` ${chalk.dim("work branch:")} ${chalk.cyan(opts.workBranch)}`);
1814
+ if (opts.dryRun)
1815
+ log(` ${chalk.yellow.bold("DRY RUN")}`);
1816
+ log(` ${divider()}`);
1817
+ log("");
1715
1818
  const cb = {
1716
1819
  onLog(msg, color) {
1717
- emit({ event: "log", msg, color });
1820
+ if (jsonl)
1821
+ emitJsonl({ event: "log", msg, color });
1822
+ if (!msg) {
1823
+ log("");
1824
+ return;
1825
+ }
1826
+ log(` ${ts()} ${colorize(color, msg)}`);
1718
1827
  },
1719
1828
  onStatus(msg) {
1720
- emit({ event: "status", msg });
1829
+ if (jsonl)
1830
+ emitJsonl({ event: "status", msg });
1831
+ log(` ${ts()} ${chalk.dim("\u203A")} ${msg}`);
1721
1832
  },
1722
1833
  onCommitStart(commit, index, total) {
1723
- emit({ event: "commit_start", hash: commit.hash, message: commit.message, index, total });
1834
+ if (jsonl)
1835
+ emitJsonl({ event: "commit_start", hash: commit.hash, message: commit.message, index, total });
1836
+ log("");
1837
+ log(` ${divider()}`);
1838
+ log(` ${progressBar2(index + 1, total)} ${chalk.yellow(shortHash2(commit.hash))}`);
1839
+ log(` ${chalk.bold(commit.message)}`);
1840
+ log(` ${chalk.dim(`by ${commit.author} \xB7 ${commit.date}`)}`);
1841
+ log("");
1724
1842
  },
1725
1843
  onAnalysis(commit, analysis) {
1726
- emit({ event: "analysis", hash: commit.hash, result: analysis });
1844
+ if (jsonl)
1845
+ emitJsonl({ event: "analysis", hash: commit.hash, result: analysis });
1846
+ log(` ${ts()} ${chalk.dim("analysis:")} ${analysisBadge(analysis.alreadyInTarget)}`);
1847
+ log(` ${chalk.dim(" summary:")} ${analysis.summary.slice(0, 120)}`);
1848
+ if (analysis.affectedComponents.length > 0) {
1849
+ log(` ${chalk.dim(" components:")} ${analysis.affectedComponents.join(", ")}`);
1850
+ }
1851
+ if (analysis.opsNotes.length > 0) {
1852
+ log(` ${chalk.yellow(" ops:")} ${analysis.opsNotes.join("; ")}`);
1853
+ }
1727
1854
  },
1728
1855
  onDecision(commit, decision) {
1729
- emit({ event: "decision", hash: commit.hash, kind: decision.kind, reason: decision.reason });
1856
+ if (jsonl)
1857
+ emitJsonl({
1858
+ event: "decision",
1859
+ hash: commit.hash,
1860
+ kind: decision.kind,
1861
+ reason: decision.reason,
1862
+ opsNotes: decision.opsNotes
1863
+ });
1864
+ const duration = chalk.dim(`${(decision.durationMs / 1000).toFixed(1)}s`);
1865
+ log(` ${ts()} ${decisionBadge(decision.kind)} ${duration}`);
1866
+ if (decision.kind === "failed" && decision.error) {
1867
+ log(` ${chalk.red(` error: ${decision.error.slice(0, 150)}`)}`);
1868
+ }
1869
+ if (decision.filesChanged && decision.filesChanged.length > 0) {
1870
+ const shown = decision.filesChanged.slice(0, 5).join(", ");
1871
+ const extra = decision.filesChanged.length > 5 ? chalk.dim(` +${decision.filesChanged.length - 5} more`) : "";
1872
+ log(` ${chalk.dim(` files: ${shown}`)}${extra}`);
1873
+ }
1730
1874
  },
1731
1875
  onReview(commit, review) {
1732
- emit({ event: "review", hash: commit.hash, approved: review.approved, issues: review.issues });
1876
+ if (jsonl)
1877
+ emitJsonl({ event: "review", hash: commit.hash, approved: review.approved, issues: review.issues });
1878
+ const badge = review.approved ? chalk.bgGreen.black.bold(" REVIEW OK ") : chalk.bgRed.white.bold(" REVIEW REJECTED ");
1879
+ log(` ${ts()} ${badge} ${chalk.dim(`confidence: ${review.confidence}`)}`);
1880
+ if (!review.approved && review.issues.length > 0) {
1881
+ for (const issue of review.issues.slice(0, 3)) {
1882
+ log(` ${chalk.red(` \xB7 ${issue.slice(0, 120)}`)}`);
1883
+ }
1884
+ if (review.issues.length > 3) {
1885
+ log(` ${chalk.dim(` ...and ${review.issues.length - 3} more issues`)}`);
1886
+ }
1887
+ }
1733
1888
  }
1734
1889
  };
1890
+ let result;
1735
1891
  try {
1736
- const result = await runEngine(opts, cb);
1737
- emit({
1892
+ result = await runEngine(opts, cb);
1893
+ } catch (e) {
1894
+ if (jsonl)
1895
+ emitJsonl({ event: "fatal", error: e.message });
1896
+ log("");
1897
+ log(` ${chalk.bgRed.white.bold(" FATAL ")} ${e.message}`);
1898
+ log("");
1899
+ return 1;
1900
+ }
1901
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);
1902
+ const { summary } = result;
1903
+ log("");
1904
+ log(` ${divider("\u2550")}`);
1905
+ log(` ${chalk.bold("Run complete")} ${chalk.dim(`in ${elapsed}s`)}`);
1906
+ log("");
1907
+ const counters = [
1908
+ summary.applied > 0 && `${chalk.green.bold(`${summary.applied}`)} applied`,
1909
+ summary.wouldApply > 0 && `${chalk.cyan.bold(`${summary.wouldApply}`)} would apply`,
1910
+ summary.alreadyApplied > 0 && `${chalk.blue.bold(`${summary.alreadyApplied}`)} already applied`,
1911
+ summary.skipped > 0 && `${chalk.yellow.bold(`${summary.skipped}`)} skipped`,
1912
+ summary.cherrySkipped > 0 && `${chalk.cyan(`${summary.cherrySkipped}`)} cherry-skipped`,
1913
+ summary.failed > 0 && `${chalk.red.bold(`${summary.failed}`)} failed`
1914
+ ].filter(Boolean);
1915
+ log(` ${counters.join(chalk.dim(" \xB7 "))}`);
1916
+ if (result.worktreePath)
1917
+ log(` ${chalk.dim("worktree:")} ${result.worktreePath}`);
1918
+ if (result.workBranch)
1919
+ log(` ${chalk.dim("branch:")} ${result.workBranch}`);
1920
+ if (result.auditDir)
1921
+ log(` ${chalk.dim("audit:")} ${result.auditDir}`);
1922
+ if (result.opsNotes.length > 0) {
1923
+ log("");
1924
+ log(` ${chalk.yellow.bold("OPERATOR NOTES")}`);
1925
+ log(` ${chalk.yellow(divider("\u2500", 40))}`);
1926
+ for (const entry of result.opsNotes) {
1927
+ log(` ${chalk.yellow(shortHash2(entry.commitHash))} ${entry.commitMessage}`);
1928
+ for (const note of entry.notes) {
1929
+ log(` ${chalk.yellow("\u2192")} ${note}`);
1930
+ }
1931
+ }
1932
+ }
1933
+ const failed = result.decisions.filter((d) => d.kind === "failed");
1934
+ if (failed.length > 0) {
1935
+ log("");
1936
+ log(` ${chalk.red.bold("FAILED COMMITS")}`);
1937
+ log(` ${chalk.red(divider("\u2500", 40))}`);
1938
+ for (const d of failed) {
1939
+ log(` ${chalk.red(shortHash2(d.commitHash))} ${d.commitMessage}`);
1940
+ log(` ${chalk.dim("phase:")} ${d.failedPhase ?? "?"} ${chalk.dim("error:")} ${d.error?.slice(0, 100) ?? "?"}`);
1941
+ }
1942
+ }
1943
+ log(` ${divider("\u2550")}`);
1944
+ log("");
1945
+ if (jsonl) {
1946
+ emitJsonl({
1738
1947
  event: "done",
1739
1948
  summary: result.summary,
1740
1949
  worktree: result.worktreePath,
1741
1950
  branch: result.workBranch,
1742
- auditDir: result.auditDir
1951
+ auditDir: result.auditDir,
1952
+ opsNotes: result.opsNotes
1743
1953
  });
1744
- const { summary } = result;
1745
- if (summary.failed > 0)
1746
- return 2;
1747
- return 0;
1748
- } catch (e) {
1749
- emit({ event: "fatal", error: e.message });
1750
- return 1;
1751
1954
  }
1955
+ if (summary.failed > 0)
1956
+ return 2;
1957
+ return 0;
1752
1958
  }
1959
+ var colorMap;
1753
1960
  var init_batch = __esm(() => {
1754
1961
  init_engine();
1962
+ colorMap = {
1963
+ red: chalk.red,
1964
+ green: chalk.green,
1965
+ yellow: chalk.yellow,
1966
+ blue: chalk.blue,
1967
+ magenta: chalk.magenta,
1968
+ cyan: chalk.cyan,
1969
+ gray: chalk.gray,
1970
+ white: chalk.white
1971
+ };
1755
1972
  });
1756
1973
 
1757
1974
  // index.ts
@@ -2433,6 +2650,42 @@ function App({ repoPath, engineOpts }) {
2433
2650
  }, undefined, true, undefined, this)
2434
2651
  ]
2435
2652
  }, undefined, true, undefined, this),
2653
+ result.opsNotes.length > 0 && /* @__PURE__ */ jsxDEV(Box, {
2654
+ flexDirection: "column",
2655
+ borderStyle: "round",
2656
+ borderColor: "yellow",
2657
+ paddingX: 1,
2658
+ marginTop: 1,
2659
+ children: [
2660
+ /* @__PURE__ */ jsxDEV(Text, {
2661
+ bold: true,
2662
+ color: "yellow",
2663
+ children: "Operator Notes"
2664
+ }, undefined, false, undefined, this),
2665
+ /* @__PURE__ */ jsxDEV(Newline, {}, undefined, false, undefined, this),
2666
+ result.opsNotes.map((entry) => /* @__PURE__ */ jsxDEV(Box, {
2667
+ flexDirection: "column",
2668
+ children: [
2669
+ /* @__PURE__ */ jsxDEV(Text, {
2670
+ color: "yellow",
2671
+ children: [
2672
+ entry.commitHash.slice(0, 8),
2673
+ " ",
2674
+ entry.commitMessage
2675
+ ]
2676
+ }, undefined, true, undefined, this),
2677
+ entry.notes.map((note, i) => /* @__PURE__ */ jsxDEV(Text, {
2678
+ color: "yellow",
2679
+ children: [
2680
+ " ",
2681
+ "\u2192 ",
2682
+ note
2683
+ ]
2684
+ }, i, true, undefined, this))
2685
+ ]
2686
+ }, entry.commitHash, true, undefined, this))
2687
+ ]
2688
+ }, undefined, true, undefined, this),
2436
2689
  /* @__PURE__ */ jsxDEV(ActionBar, {
2437
2690
  actions: [{ key: "q", label: "Exit", color: "gray" }]
2438
2691
  }, undefined, false, undefined, this)
@@ -2482,6 +2735,7 @@ var startAfter = "";
2482
2735
  var limit = 0;
2483
2736
  var configPath = "";
2484
2737
  var batch = false;
2738
+ var jsonl = false;
2485
2739
  var resume = true;
2486
2740
  var showHelp = false;
2487
2741
  for (let i = 0;i < args.length; i++) {
@@ -2490,7 +2744,10 @@ for (let i = 0;i < args.length; i++) {
2490
2744
  showHelp = true;
2491
2745
  else if (arg === "--batch" || arg === "-b")
2492
2746
  batch = true;
2493
- else if (arg === "--dry-run")
2747
+ else if (arg === "--jsonl") {
2748
+ batch = true;
2749
+ jsonl = true;
2750
+ } else if (arg === "--dry-run")
2494
2751
  dryRun = true;
2495
2752
  else if (arg === "--list-only")
2496
2753
  listOnly = true;
@@ -2628,7 +2885,8 @@ if (batch || listOnly) {
2628
2885
  repoPath: resolvedPath,
2629
2886
  sourceRef,
2630
2887
  targetRef,
2631
- ...engineOpts
2888
+ ...engineOpts,
2889
+ jsonl
2632
2890
  });
2633
2891
  process.exit(exitCode);
2634
2892
  } else {
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "xab",
3
- "version": "2.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "AI-powered curated branch reconciliation engine",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "xab": "dist/index.js"
8
8
  },
9
- "private":false,
9
+ "private": false,
10
10
  "files": [
11
11
  "dist"
12
12
  ],
13
13
  "scripts": {
14
- "build": "bun build index.ts --outdir dist --target bun --format esm --external @anthropic-ai/claude-agent-sdk --external @openai/codex-sdk --external simple-git --external ink --external ink-select-input --external ink-spinner --external ink-text-input --external react",
14
+ "build": "bun build index.ts --outdir dist --target bun --format esm --external @anthropic-ai/claude-agent-sdk --external @openai/codex-sdk --external simple-git --external ink --external ink-select-input --external ink-spinner --external ink-text-input --external react --external chalk",
15
15
  "prepublishOnly": "bun run build",
16
16
  "start": "bun run index.ts",
17
17
  "dev": "bun run --watch index.ts"
@@ -26,6 +26,7 @@
26
26
  "dependencies": {
27
27
  "@anthropic-ai/claude-agent-sdk": "^0.2.85",
28
28
  "@openai/codex-sdk": "^0.117.0",
29
+ "chalk": "^5.6.2",
29
30
  "ink": "^5.2.1",
30
31
  "ink-select-input": "^6.2.0",
31
32
  "ink-spinner": "^5.0.0",