xab 3.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.
- package/dist/index.js +222 -30
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -784,7 +784,15 @@ You can:
|
|
|
784
784
|
|
|
785
785
|
You MUST NOT modify the worktree in any way. No file writes, no git commits, no destructive commands.
|
|
786
786
|
|
|
787
|
-
|
|
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.`
|
|
788
796
|
}
|
|
789
797
|
});
|
|
790
798
|
let resultText = "";
|
|
@@ -1151,17 +1159,23 @@ async function validateApply(worktreeGit, beforeHash) {
|
|
|
1151
1159
|
errors.push(`Failed to check commits: ${e.message}`);
|
|
1152
1160
|
}
|
|
1153
1161
|
const status = await worktreeGit.status();
|
|
1154
|
-
const
|
|
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;
|
|
1155
1169
|
if (!worktreeClean) {
|
|
1156
1170
|
const parts = [];
|
|
1157
|
-
if (
|
|
1158
|
-
parts.push(`${
|
|
1159
|
-
if (
|
|
1160
|
-
parts.push(`${
|
|
1161
|
-
if (
|
|
1162
|
-
parts.push(`${
|
|
1163
|
-
if (
|
|
1164
|
-
parts.push(`${
|
|
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`);
|
|
1165
1179
|
errors.push(`Working tree not clean: ${parts.join(", ")}`);
|
|
1166
1180
|
}
|
|
1167
1181
|
const conflictMarkers = [];
|
|
@@ -1192,7 +1206,7 @@ async function getAppliedDiffStat(worktreeGit, beforeHash) {
|
|
|
1192
1206
|
}
|
|
1193
1207
|
async function resetHard(git, ref) {
|
|
1194
1208
|
await git.raw(["reset", "--hard", ref]);
|
|
1195
|
-
await git.raw(["clean", "-fd"]);
|
|
1209
|
+
await git.raw(["clean", "-fd", "--exclude=.backmerge"]);
|
|
1196
1210
|
}
|
|
1197
1211
|
async function fetchOrigin(git) {
|
|
1198
1212
|
await git.fetch(["--all", "--prune"]);
|
|
@@ -1335,7 +1349,7 @@ async function runEngine(opts, cb) {
|
|
|
1335
1349
|
cb.onLog(`Eval worktree (detached): ${wtPath}`, "green");
|
|
1336
1350
|
const wtGit = createGit(wtPath);
|
|
1337
1351
|
const runId = `run-${ts}`;
|
|
1338
|
-
const audit = new AuditLog(
|
|
1352
|
+
const audit = new AuditLog(repoPath, runId);
|
|
1339
1353
|
const runMeta = {
|
|
1340
1354
|
runId,
|
|
1341
1355
|
startedAt: new Date().toISOString(),
|
|
@@ -1736,34 +1750,200 @@ var exports_batch = {};
|
|
|
1736
1750
|
__export(exports_batch, {
|
|
1737
1751
|
runBatch: () => runBatch
|
|
1738
1752
|
});
|
|
1739
|
-
|
|
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) {
|
|
1740
1803
|
process.stdout.write(JSON.stringify({ ...obj, ts: new Date().toISOString() }) + `
|
|
1741
1804
|
`);
|
|
1742
1805
|
}
|
|
1743
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("");
|
|
1744
1818
|
const cb = {
|
|
1745
1819
|
onLog(msg, color) {
|
|
1746
|
-
|
|
1820
|
+
if (jsonl)
|
|
1821
|
+
emitJsonl({ event: "log", msg, color });
|
|
1822
|
+
if (!msg) {
|
|
1823
|
+
log("");
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
log(` ${ts()} ${colorize(color, msg)}`);
|
|
1747
1827
|
},
|
|
1748
1828
|
onStatus(msg) {
|
|
1749
|
-
|
|
1829
|
+
if (jsonl)
|
|
1830
|
+
emitJsonl({ event: "status", msg });
|
|
1831
|
+
log(` ${ts()} ${chalk.dim("\u203A")} ${msg}`);
|
|
1750
1832
|
},
|
|
1751
1833
|
onCommitStart(commit, index, total) {
|
|
1752
|
-
|
|
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("");
|
|
1753
1842
|
},
|
|
1754
1843
|
onAnalysis(commit, analysis) {
|
|
1755
|
-
|
|
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
|
+
}
|
|
1756
1854
|
},
|
|
1757
1855
|
onDecision(commit, decision) {
|
|
1758
|
-
|
|
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
|
+
}
|
|
1759
1874
|
},
|
|
1760
1875
|
onReview(commit, review) {
|
|
1761
|
-
|
|
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
|
+
}
|
|
1762
1888
|
}
|
|
1763
1889
|
};
|
|
1890
|
+
let result;
|
|
1764
1891
|
try {
|
|
1765
|
-
|
|
1766
|
-
|
|
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({
|
|
1767
1947
|
event: "done",
|
|
1768
1948
|
summary: result.summary,
|
|
1769
1949
|
worktree: result.worktreePath,
|
|
@@ -1771,17 +1951,24 @@ async function runBatch(opts) {
|
|
|
1771
1951
|
auditDir: result.auditDir,
|
|
1772
1952
|
opsNotes: result.opsNotes
|
|
1773
1953
|
});
|
|
1774
|
-
const { summary } = result;
|
|
1775
|
-
if (summary.failed > 0)
|
|
1776
|
-
return 2;
|
|
1777
|
-
return 0;
|
|
1778
|
-
} catch (e) {
|
|
1779
|
-
emit({ event: "fatal", error: e.message });
|
|
1780
|
-
return 1;
|
|
1781
1954
|
}
|
|
1955
|
+
if (summary.failed > 0)
|
|
1956
|
+
return 2;
|
|
1957
|
+
return 0;
|
|
1782
1958
|
}
|
|
1959
|
+
var colorMap;
|
|
1783
1960
|
var init_batch = __esm(() => {
|
|
1784
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
|
+
};
|
|
1785
1972
|
});
|
|
1786
1973
|
|
|
1787
1974
|
// index.ts
|
|
@@ -2548,6 +2735,7 @@ var startAfter = "";
|
|
|
2548
2735
|
var limit = 0;
|
|
2549
2736
|
var configPath = "";
|
|
2550
2737
|
var batch = false;
|
|
2738
|
+
var jsonl = false;
|
|
2551
2739
|
var resume = true;
|
|
2552
2740
|
var showHelp = false;
|
|
2553
2741
|
for (let i = 0;i < args.length; i++) {
|
|
@@ -2556,7 +2744,10 @@ for (let i = 0;i < args.length; i++) {
|
|
|
2556
2744
|
showHelp = true;
|
|
2557
2745
|
else if (arg === "--batch" || arg === "-b")
|
|
2558
2746
|
batch = true;
|
|
2559
|
-
else if (arg === "--
|
|
2747
|
+
else if (arg === "--jsonl") {
|
|
2748
|
+
batch = true;
|
|
2749
|
+
jsonl = true;
|
|
2750
|
+
} else if (arg === "--dry-run")
|
|
2560
2751
|
dryRun = true;
|
|
2561
2752
|
else if (arg === "--list-only")
|
|
2562
2753
|
listOnly = true;
|
|
@@ -2694,7 +2885,8 @@ if (batch || listOnly) {
|
|
|
2694
2885
|
repoPath: resolvedPath,
|
|
2695
2886
|
sourceRef,
|
|
2696
2887
|
targetRef,
|
|
2697
|
-
...engineOpts
|
|
2888
|
+
...engineOpts,
|
|
2889
|
+
jsonl
|
|
2698
2890
|
});
|
|
2699
2891
|
process.exit(exitCode);
|
|
2700
2892
|
} else {
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xab",
|
|
3
|
-
"version": "
|
|
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",
|