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.
- package/dist/index.js +297 -39
- 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
|
-
|
|
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
|
|
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 (
|
|
1145
|
-
parts.push(`${
|
|
1146
|
-
if (
|
|
1147
|
-
parts.push(`${
|
|
1148
|
-
if (
|
|
1149
|
-
parts.push(`${
|
|
1150
|
-
if (
|
|
1151
|
-
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`);
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1829
|
+
if (jsonl)
|
|
1830
|
+
emitJsonl({ event: "status", msg });
|
|
1831
|
+
log(` ${ts()} ${chalk.dim("\u203A")} ${msg}`);
|
|
1721
1832
|
},
|
|
1722
1833
|
onCommitStart(commit, index, total) {
|
|
1723
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1737
|
-
|
|
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 === "--
|
|
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": "
|
|
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",
|