xab 6.0.0 → 7.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 +42 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -43,7 +43,7 @@ var init_config = __esm(() => {
43
43
  promptHints: [],
44
44
  pathRemaps: [],
45
45
  reviewStrictness: "normal",
46
- maxAttempts: 2,
46
+ maxAttempts: undefined,
47
47
  commitPrefix: "backmerge:"
48
48
  };
49
49
  });
@@ -543,7 +543,9 @@ After making all file changes, you MUST run these two commands as your FINAL act
543
543
  If you do not run both commands, your work will be discarded. This is not optional.
544
544
  The validation system checks for exactly one new git commit. Zero commits = failure.
545
545
 
546
- Report what you did.`;
546
+ Report what you did.
547
+
548
+ MAKE SURE TO ACTUALLY GIT COMMIT, NOT JUST MODIFY FILES.`;
547
549
  const firstPrompt = `${MERGE_PREAMBLE}
548
550
  ${opts.repoContext ? `## Repository context
549
551
  ${opts.repoContext}
@@ -622,7 +624,9 @@ After making all fixes, you MUST run these two commands as your FINAL action:
622
624
 
623
625
  If you do not run both commands, your fixes will be discarded. This is not optional.
624
626
 
625
- Report what you fixed.`;
627
+ Report what you fixed.
628
+
629
+ MAKE SURE TO ACTUALLY GIT COMMIT, NOT JUST MODIFY FILES.`;
626
630
  const turn = await thread.run(prompt, { outputSchema: applyResultSchema });
627
631
  return parseJson(turn.finalResponse, {
628
632
  applied: false,
@@ -783,7 +787,7 @@ If you have ANY objections, be specific about what's wrong and how to fix it. Yo
783
787
  options: {
784
788
  cwd: worktreePath,
785
789
  model: "claude-opus-4-6",
786
- maxTurns: 30,
790
+ maxTurns: undefined,
787
791
  permissionMode: "default",
788
792
  outputFormat: reviewSchema,
789
793
  tools: ["Read", "Glob", "Grep", "Bash"],
@@ -1173,12 +1177,12 @@ async function validateApply(worktreeGit, beforeHash) {
1173
1177
  errors.push(`Failed to check commits: ${e.message}`);
1174
1178
  }
1175
1179
  const status = await worktreeGit.status();
1176
- const isOurs = (f) => f.startsWith(".backmerge/") || f.startsWith(".backmerge\\");
1177
- const modified = status.modified.filter((f) => !isOurs(f));
1178
- const created = status.created.filter((f) => !isOurs(f));
1179
- const deleted = status.deleted.filter((f) => !isOurs(f));
1180
- const notAdded = status.not_added.filter((f) => !isOurs(f));
1181
- const conflicted = status.conflicted.filter((f) => !isOurs(f));
1180
+ const isInfra = (f) => f.startsWith(".backmerge/") || f.startsWith(".backmerge\\") || f.startsWith(".git-local/") || f.startsWith(".git-local\\");
1181
+ const modified = status.modified.filter((f) => !isInfra(f));
1182
+ const created = status.created.filter((f) => !isInfra(f));
1183
+ const deleted = status.deleted.filter((f) => !isInfra(f));
1184
+ const notAdded = status.not_added.filter((f) => !isInfra(f));
1185
+ const conflicted = status.conflicted.filter((f) => !isInfra(f));
1182
1186
  const worktreeClean = modified.length === 0 && created.length === 0 && deleted.length === 0 && conflicted.length === 0 && notAdded.length === 0;
1183
1187
  const dirtyFiles = [];
1184
1188
  if (!worktreeClean) {
@@ -1228,7 +1232,7 @@ async function getAppliedDiffStat(worktreeGit, beforeHash) {
1228
1232
  }
1229
1233
  async function resetHard(git, ref) {
1230
1234
  await git.raw(["reset", "--hard", ref]);
1231
- await git.raw(["clean", "-fd", "--exclude=.backmerge"]);
1235
+ await git.raw(["clean", "-fd", "--exclude=.backmerge", "--exclude=.git-local"]);
1232
1236
  }
1233
1237
  async function fetchOrigin(git) {
1234
1238
  await git.fetch(["--all", "--prune"]);
@@ -1268,7 +1272,7 @@ async function runEngine(opts, cb) {
1268
1272
  autoSkip = true
1269
1273
  } = opts;
1270
1274
  const config = loadConfig(repoPath, opts.configPath);
1271
- const effectiveMaxAttempts = opts.maxAttempts ?? config.maxAttempts ?? 2;
1275
+ const effectiveMaxAttempts = opts.maxAttempts ?? config.maxAttempts ?? Infinity;
1272
1276
  const effectiveWorkBranch = opts.workBranch ?? config.workBranch;
1273
1277
  const commitPrefix = config.commitPrefix ?? "backmerge:";
1274
1278
  if (!checkCodexInstalled())
@@ -1548,7 +1552,7 @@ async function processOneCommit(o) {
1548
1552
  }
1549
1553
  for (let attempt = 1;attempt <= o.maxAttempts; attempt++) {
1550
1554
  const headBefore = await getHead(o.wtGit);
1551
- cb.onStatus(`Applying ${commit.hash.slice(0, 8)} (attempt ${attempt}/${o.maxAttempts})...`);
1555
+ cb.onStatus(`Applying ${commit.hash.slice(0, 8)} (attempt ${attempt})...`);
1552
1556
  let applyResult;
1553
1557
  try {
1554
1558
  applyResult = await applyCommit({
@@ -1571,7 +1575,26 @@ async function processOneCommit(o) {
1571
1575
  continue;
1572
1576
  }
1573
1577
  cb.onStatus(`Validating ${commit.hash.slice(0, 8)}...`);
1574
- const validation = await validateApply(o.wtGit, headBefore);
1578
+ let validation = await validateApply(o.wtGit, headBefore);
1579
+ if (!validation.valid && validation.newCommitCount === 0 && !validation.worktreeClean && validation.dirtyFiles.length > 0) {
1580
+ const realChanges = validation.dirtyFiles.filter((f) => !f.includes(".git-local/"));
1581
+ if (realChanges.length > 0) {
1582
+ cb.onLog(`Codex left ${realChanges.length} changed files without committing \u2014 creating rescue commit`, "yellow");
1583
+ for (const f of realChanges)
1584
+ cb.onLog(` ${f}`, "yellow");
1585
+ const commitMsg = `${o.commitPrefix} ${commit.message} (from ${commit.hash.slice(0, 8)})`;
1586
+ try {
1587
+ await o.wtGit.raw(["add", "-A", "--", ".", ":!.git-local"]);
1588
+ await o.wtGit.raw(["commit", "-m", commitMsg]);
1589
+ validation = await validateApply(o.wtGit, headBefore);
1590
+ if (validation.valid) {
1591
+ cb.onLog(`Rescue commit succeeded`, "green");
1592
+ }
1593
+ } catch (e) {
1594
+ cb.onLog(`Rescue commit failed: ${e.message}`, "red");
1595
+ }
1596
+ }
1597
+ }
1575
1598
  if (!validation.valid) {
1576
1599
  cb.onLog(`Validation failed: ${validation.errors.join("; ")}`, "red");
1577
1600
  if (validation.dirtyFiles.length > 0) {
@@ -1625,10 +1648,10 @@ async function processOneCommit(o) {
1625
1648
  }
1626
1649
  if (!reviewResult.approved) {
1627
1650
  cb.onLog(`Review rejected: ${reviewResult.issues.join("; ")}`, "red");
1628
- const maxFixRounds = 2;
1651
+ const maxFixRounds = Infinity;
1629
1652
  let fixed = false;
1630
1653
  for (let fixRound = 1;fixRound <= maxFixRounds; fixRound++) {
1631
- cb.onStatus(`Codex fixing review issues (round ${fixRound}/${maxFixRounds})...`);
1654
+ cb.onStatus(`Codex fixing review issues (round ${fixRound})...`);
1632
1655
  try {
1633
1656
  await fixFromReview({
1634
1657
  worktreePath: o.wtPath,
@@ -1690,7 +1713,7 @@ async function processOneCommit(o) {
1690
1713
  kind: "failed",
1691
1714
  commitHash: commit.hash,
1692
1715
  commitMessage: commit.message,
1693
- reason: `Review rejected after ${maxFixRounds} fix rounds: ${reviewResult.issues.join("; ")}`,
1716
+ reason: `Review rejected after fix attempts: ${reviewResult.issues.join("; ")}`,
1694
1717
  failedPhase: "review",
1695
1718
  reviewApproved: false,
1696
1719
  reviewIssues: reviewResult.issues,
@@ -1704,7 +1727,7 @@ async function processOneCommit(o) {
1704
1727
  kind: "failed",
1705
1728
  commitHash: commit.hash,
1706
1729
  commitMessage: commit.message,
1707
- reason: `Review rejected after ${maxFixRounds} fix rounds: ${reviewResult.issues.join("; ")}`,
1730
+ reason: `Review rejected after fix attempts: ${reviewResult.issues.join("; ")}`,
1708
1731
  failedPhase: "review",
1709
1732
  reviewApproved: false,
1710
1733
  reviewIssues: reviewResult.issues,
@@ -2883,7 +2906,7 @@ Behavior:
2883
2906
  --no-fetch Skip fetch (default)
2884
2907
  --no-review Skip Claude review pass
2885
2908
  --no-auto-skip Don't auto-skip commits AI identifies as present
2886
- --max-attempts <n> Max retries per commit (default: 2)
2909
+ --max-attempts <n> Max retries per commit (default: unlimited)
2887
2910
  --no-resume Don't resume from interrupted runs (default: auto-resume)
2888
2911
  --config <path> Path to config file (default: auto-discover)
2889
2912
  --help, -h Show this help
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xab",
3
- "version": "6.0.0",
3
+ "version": "7.0.0",
4
4
  "description": "AI-powered curated branch reconciliation engine",
5
5
  "type": "module",
6
6
  "bin": {