xab 11.0.0 → 13.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 +228 -66
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -201,7 +201,7 @@ function buildRepoContext(repoPath, config) {
|
|
|
201
201
|
const docPaths = discoverDocPaths(repoPath);
|
|
202
202
|
return { structure, instructions, docPaths };
|
|
203
203
|
}
|
|
204
|
-
function buildCommitContext(repoPath, repoCtx, config, touchedPaths, commitMessage) {
|
|
204
|
+
function buildCommitContext(repoPath, repoCtx, config, touchedPaths, commitMessage, memoryBlock) {
|
|
205
205
|
const includedFiles = [];
|
|
206
206
|
const sections = [];
|
|
207
207
|
const rs = repoCtx.structure;
|
|
@@ -214,6 +214,9 @@ function buildCommitContext(repoPath, repoCtx, config, touchedPaths, commitMessa
|
|
|
214
214
|
structLines.push(`Packages: ${rs.packages.join(", ")}`);
|
|
215
215
|
sections.push(structLines.join(`
|
|
216
216
|
`));
|
|
217
|
+
if (memoryBlock) {
|
|
218
|
+
sections.push(memoryBlock);
|
|
219
|
+
}
|
|
217
220
|
for (const [name, content] of repoCtx.instructions) {
|
|
218
221
|
sections.push(`--- ${name} ---
|
|
219
222
|
${content}`);
|
|
@@ -476,7 +479,7 @@ async function runStreamedWithProgress(thread, prompt, onProgress, turnOpts) {
|
|
|
476
479
|
const lines = output.split(`
|
|
477
480
|
`).filter(Boolean);
|
|
478
481
|
for (const line of lines.slice(-3)) {
|
|
479
|
-
onProgress("
|
|
482
|
+
onProgress("output", ` ${line.slice(0, 120)}`);
|
|
480
483
|
}
|
|
481
484
|
}
|
|
482
485
|
}
|
|
@@ -511,8 +514,10 @@ async function runStreamedWithProgress(thread, prompt, onProgress, turnOpts) {
|
|
|
511
514
|
if (text) {
|
|
512
515
|
const lines = text.split(`
|
|
513
516
|
`).filter(Boolean);
|
|
514
|
-
|
|
515
|
-
onProgress("think",
|
|
517
|
+
if (lines[0])
|
|
518
|
+
onProgress("think", lines[0].slice(0, 150));
|
|
519
|
+
for (const line of lines.slice(1, 3)) {
|
|
520
|
+
onProgress("output", ` ${line.slice(0, 150)}`);
|
|
516
521
|
}
|
|
517
522
|
}
|
|
518
523
|
break;
|
|
@@ -583,7 +588,18 @@ You are looking at a worktree based on the TARGET branch "${opts.targetBranch}".
|
|
|
583
588
|
- New services, containers, or infrastructure to deploy? \u2192 note what
|
|
584
589
|
- Config files that need manual updates on servers? \u2192 note which
|
|
585
590
|
- Dependencies on external services being added or removed? \u2192 note what
|
|
586
|
-
- If the commit is just normal code changes that only need a deploy+restart, leave opsNotes as []
|
|
591
|
+
- If the commit is just normal code changes that only need a deploy+restart, leave opsNotes as []
|
|
592
|
+
8. Record any non-obvious discoveries that would help analyze FUTURE commits:
|
|
593
|
+
- Path mappings between source and target branches (e.g. "frontend/ in source = apps/frontend/ in target")
|
|
594
|
+
- Key functions, helpers, or patterns you found (e.g. "betExitValueLocal() is the shared P&L helper")
|
|
595
|
+
- Architectural facts (e.g. "store exports are at the bottom of fastMarkets.ts")
|
|
596
|
+
- Only include genuinely useful, non-obvious facts. Do NOT repeat things already in the merge memory or repo docs.
|
|
597
|
+
- Leave discoveries as [] if nothing new and non-obvious was found.
|
|
598
|
+
9. If merge memory entries were provided in the context above, evaluate each one:
|
|
599
|
+
- List the KEYS of entries you want to KEEP in keepMemoryKeys
|
|
600
|
+
- Drop entries that are stale, obvious from repo docs, or no longer relevant
|
|
601
|
+
- Keep entries that saved you time or would save time on similar future commits
|
|
602
|
+
- If no memory was provided, return keepMemoryKeys as []`;
|
|
587
603
|
let response;
|
|
588
604
|
if (diffChunks.length > 1) {
|
|
589
605
|
await thread.run(firstPrompt);
|
|
@@ -611,7 +627,9 @@ You now have the complete diff. Analyze and produce your structured response.`,
|
|
|
611
627
|
reasoning: "Could not parse structured output",
|
|
612
628
|
applicationStrategy: "Manual review recommended",
|
|
613
629
|
affectedComponents: [],
|
|
614
|
-
opsNotes: []
|
|
630
|
+
opsNotes: [],
|
|
631
|
+
discoveries: [],
|
|
632
|
+
keepMemoryKeys: []
|
|
615
633
|
});
|
|
616
634
|
}
|
|
617
635
|
async function applyCommit(opts) {
|
|
@@ -776,9 +794,43 @@ var init_codex = __esm(() => {
|
|
|
776
794
|
type: "array",
|
|
777
795
|
items: { type: "string" },
|
|
778
796
|
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."
|
|
797
|
+
},
|
|
798
|
+
discoveries: {
|
|
799
|
+
type: "array",
|
|
800
|
+
items: {
|
|
801
|
+
type: "object",
|
|
802
|
+
properties: {
|
|
803
|
+
type: {
|
|
804
|
+
type: "string",
|
|
805
|
+
enum: ["path_mapping", "pattern", "codebase", "architecture", "convention", "warning"],
|
|
806
|
+
description: "Category of discovery"
|
|
807
|
+
},
|
|
808
|
+
key: {
|
|
809
|
+
type: "string",
|
|
810
|
+
description: "Short unique key for dedup (e.g. 'frontend_path_prefix', 'pnl_shared_helpers')"
|
|
811
|
+
},
|
|
812
|
+
value: { type: "string", description: "The reusable fact (1-2 sentences max)" }
|
|
813
|
+
},
|
|
814
|
+
required: ["type", "key", "value"]
|
|
815
|
+
},
|
|
816
|
+
description: "Reusable learnings that would help analyze FUTURE commits. Only include genuinely useful, non-obvious facts. Examples: path mappings between source and target ('frontend/' in source = 'apps/frontend/' in target), key shared functions ('betExitValueLocal() is the canonical P&L helper'), architectural patterns ('store exports are at the bottom of the file'), conventions ('pt-BR locale used in all user-facing text'). Leave as [] if nothing non-obvious was discovered."
|
|
817
|
+
},
|
|
818
|
+
keepMemoryKeys: {
|
|
819
|
+
type: "array",
|
|
820
|
+
items: { type: "string" },
|
|
821
|
+
description: "From the merge memory provided in context, list the KEYS of entries that are still useful for future commits. Entries whose keys are NOT listed here will be garbage-collected. If no merge memory was provided, return []. Be selective \u2014 only keep entries that are genuinely useful going forward, not stale or obvious facts."
|
|
779
822
|
}
|
|
780
823
|
},
|
|
781
|
-
required: [
|
|
824
|
+
required: [
|
|
825
|
+
"summary",
|
|
826
|
+
"alreadyInTarget",
|
|
827
|
+
"reasoning",
|
|
828
|
+
"applicationStrategy",
|
|
829
|
+
"affectedComponents",
|
|
830
|
+
"opsNotes",
|
|
831
|
+
"discoveries",
|
|
832
|
+
"keepMemoryKeys"
|
|
833
|
+
],
|
|
782
834
|
additionalProperties: false
|
|
783
835
|
};
|
|
784
836
|
applyResultSchema = {
|
|
@@ -1198,9 +1250,114 @@ function findResumableRun(baseDir, workBranch) {
|
|
|
1198
1250
|
}
|
|
1199
1251
|
var init_audit = () => {};
|
|
1200
1252
|
|
|
1253
|
+
// src/memory.ts
|
|
1254
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1255
|
+
import { join as join4 } from "path";
|
|
1256
|
+
|
|
1257
|
+
class MergeMemory {
|
|
1258
|
+
entries = [];
|
|
1259
|
+
filePath;
|
|
1260
|
+
maxEntries = 50;
|
|
1261
|
+
constructor(repoPath) {
|
|
1262
|
+
this.filePath = join4(repoPath, ".backmerge", "memory.jsonl");
|
|
1263
|
+
this.load();
|
|
1264
|
+
}
|
|
1265
|
+
load() {
|
|
1266
|
+
if (!existsSync4(this.filePath))
|
|
1267
|
+
return;
|
|
1268
|
+
try {
|
|
1269
|
+
const raw = readFileSync4(this.filePath, "utf-8");
|
|
1270
|
+
this.entries = raw.split(`
|
|
1271
|
+
`).filter(Boolean).map((line) => {
|
|
1272
|
+
try {
|
|
1273
|
+
return JSON.parse(line);
|
|
1274
|
+
} catch {
|
|
1275
|
+
return null;
|
|
1276
|
+
}
|
|
1277
|
+
}).filter((e) => e !== null);
|
|
1278
|
+
} catch {}
|
|
1279
|
+
}
|
|
1280
|
+
save() {
|
|
1281
|
+
const dir = join4(this.filePath, "..");
|
|
1282
|
+
mkdirSync2(dir, { recursive: true });
|
|
1283
|
+
writeFileSync2(this.filePath, this.entries.map((e) => JSON.stringify(e)).join(`
|
|
1284
|
+
`) + `
|
|
1285
|
+
`);
|
|
1286
|
+
}
|
|
1287
|
+
get all() {
|
|
1288
|
+
return this.entries;
|
|
1289
|
+
}
|
|
1290
|
+
get count() {
|
|
1291
|
+
return this.entries.length;
|
|
1292
|
+
}
|
|
1293
|
+
addDiscoveries(discoveries, commitHash) {
|
|
1294
|
+
let added = 0;
|
|
1295
|
+
for (const d of discoveries) {
|
|
1296
|
+
const existing = this.entries.find((e) => e.key === d.key);
|
|
1297
|
+
if (existing) {
|
|
1298
|
+
if (existing.value !== d.value) {
|
|
1299
|
+
existing.value = d.value;
|
|
1300
|
+
existing.source = commitHash;
|
|
1301
|
+
existing.ts = new Date().toISOString();
|
|
1302
|
+
}
|
|
1303
|
+
existing.useCount++;
|
|
1304
|
+
} else {
|
|
1305
|
+
this.entries.push({
|
|
1306
|
+
type: d.type,
|
|
1307
|
+
key: d.key,
|
|
1308
|
+
value: d.value,
|
|
1309
|
+
source: commitHash,
|
|
1310
|
+
ts: new Date().toISOString(),
|
|
1311
|
+
useCount: 1
|
|
1312
|
+
});
|
|
1313
|
+
added++;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
if (this.entries.length > this.maxEntries) {
|
|
1317
|
+
this.entries = this.entries.slice(-this.maxEntries);
|
|
1318
|
+
}
|
|
1319
|
+
this.save();
|
|
1320
|
+
return added;
|
|
1321
|
+
}
|
|
1322
|
+
applyGC(kept) {
|
|
1323
|
+
const keptKeys = new Set(kept.map((k) => k.key));
|
|
1324
|
+
const before = this.entries.length;
|
|
1325
|
+
const newEntries = [];
|
|
1326
|
+
for (const k of kept) {
|
|
1327
|
+
const existing = this.entries.find((e) => e.key === k.key);
|
|
1328
|
+
if (existing) {
|
|
1329
|
+
existing.value = k.value;
|
|
1330
|
+
existing.useCount++;
|
|
1331
|
+
newEntries.push(existing);
|
|
1332
|
+
} else {
|
|
1333
|
+
newEntries.push({
|
|
1334
|
+
type: "pattern",
|
|
1335
|
+
key: k.key,
|
|
1336
|
+
value: k.value,
|
|
1337
|
+
source: "gc",
|
|
1338
|
+
ts: new Date().toISOString(),
|
|
1339
|
+
useCount: 1
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
this.entries = newEntries;
|
|
1344
|
+
this.save();
|
|
1345
|
+
return before - this.entries.length;
|
|
1346
|
+
}
|
|
1347
|
+
toPromptBlock() {
|
|
1348
|
+
if (this.entries.length === 0)
|
|
1349
|
+
return "";
|
|
1350
|
+
const lines = this.entries.map((e) => `[${e.type}] ${e.key}: ${e.value}`);
|
|
1351
|
+
return `--- Merge memory (${this.entries.length} learnings from previous commits) ---
|
|
1352
|
+
${lines.join(`
|
|
1353
|
+
`)}`;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
var init_memory = () => {};
|
|
1357
|
+
|
|
1201
1358
|
// src/git.ts
|
|
1202
1359
|
import simpleGit from "simple-git";
|
|
1203
|
-
import { join as
|
|
1360
|
+
import { join as join5 } from "path";
|
|
1204
1361
|
import { tmpdir } from "os";
|
|
1205
1362
|
function createGit(cwd) {
|
|
1206
1363
|
return simpleGit(cwd);
|
|
@@ -1260,7 +1417,7 @@ async function getDescendantCommitsSince(git, since, ref) {
|
|
|
1260
1417
|
}
|
|
1261
1418
|
function generateWorktreePath(repoName) {
|
|
1262
1419
|
const id = Math.random().toString(36).slice(2, 8);
|
|
1263
|
-
return
|
|
1420
|
+
return join5(tmpdir(), `backmerge-${repoName}-${id}`);
|
|
1264
1421
|
}
|
|
1265
1422
|
async function createDetachedWorktree(git, path, ref) {
|
|
1266
1423
|
await git.raw(["worktree", "add", "--detach", path, ref]);
|
|
@@ -1454,6 +1611,10 @@ async function runEngine(opts, cb) {
|
|
|
1454
1611
|
for (const l of logs)
|
|
1455
1612
|
cb.onLog(` ${l}`, "gray");
|
|
1456
1613
|
}
|
|
1614
|
+
const memory = new MergeMemory(repoPath);
|
|
1615
|
+
if (memory.count > 0) {
|
|
1616
|
+
cb.onLog(`Merge memory: ${memory.count} entries from previous commits`, "cyan");
|
|
1617
|
+
}
|
|
1457
1618
|
if (config.promptHints && config.promptHints.length > 0) {
|
|
1458
1619
|
cb.onLog(`Active hints (${config.promptHints.length}):`, "cyan");
|
|
1459
1620
|
for (const h of config.promptHints) {
|
|
@@ -1547,28 +1708,8 @@ async function runEngine(opts, cb) {
|
|
|
1547
1708
|
await createDetachedWorktree(git, wtPath, wbHead);
|
|
1548
1709
|
cb.onLog(`Eval worktree (detached): ${wtPath}`, "green");
|
|
1549
1710
|
const wtGit = createGit(wtPath);
|
|
1550
|
-
const runId = `run-${ts}`;
|
|
1551
|
-
const audit = new AuditLog(repoPath, runId);
|
|
1552
|
-
const runMeta = {
|
|
1553
|
-
runId,
|
|
1554
|
-
startedAt: new Date().toISOString(),
|
|
1555
|
-
sourceRef,
|
|
1556
|
-
targetRef,
|
|
1557
|
-
workBranch: wbName,
|
|
1558
|
-
mergeBase,
|
|
1559
|
-
worktreePath: wtPath,
|
|
1560
|
-
totalCandidates: commitsToProcess.length,
|
|
1561
|
-
cherrySkipped: cherrySkipped.size,
|
|
1562
|
-
dryRun,
|
|
1563
|
-
repoPath
|
|
1564
|
-
};
|
|
1565
|
-
audit.runStart(runMeta);
|
|
1566
|
-
for (const [hash, reason] of cherrySkipped) {
|
|
1567
|
-
const c = allSourceCommits.find((x) => x.hash === hash);
|
|
1568
|
-
if (c)
|
|
1569
|
-
audit.cherrySkip(hash, c.message, reason);
|
|
1570
|
-
}
|
|
1571
1711
|
let resumeFromIndex = 0;
|
|
1712
|
+
let resumedRunId;
|
|
1572
1713
|
if (opts.resume && effectiveWorkBranch) {
|
|
1573
1714
|
const resumeInfo = findResumableRun(repoPath, effectiveWorkBranch);
|
|
1574
1715
|
if (resumeInfo && resumeInfo.lastCommitIndex >= 0) {
|
|
@@ -1580,7 +1721,31 @@ async function runEngine(opts, cb) {
|
|
|
1580
1721
|
resumeFromIndex = commitsToProcess.findIndex((c) => !prevHashes.has(c.hash));
|
|
1581
1722
|
if (resumeFromIndex < 0)
|
|
1582
1723
|
resumeFromIndex = commitsToProcess.length;
|
|
1583
|
-
|
|
1724
|
+
resumedRunId = resumeInfo.runId;
|
|
1725
|
+
cb.onLog(`Resuming run ${resumeInfo.runId} from commit ${resumeFromIndex + 1}/${commitsToProcess.length} (${resumeInfo.decisions.length} already decided)`, "green");
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
const runId = resumedRunId ?? `run-${ts}`;
|
|
1729
|
+
const audit = new AuditLog(repoPath, runId);
|
|
1730
|
+
if (!resumedRunId) {
|
|
1731
|
+
const runMeta = {
|
|
1732
|
+
runId,
|
|
1733
|
+
startedAt: new Date().toISOString(),
|
|
1734
|
+
sourceRef,
|
|
1735
|
+
targetRef,
|
|
1736
|
+
workBranch: wbName,
|
|
1737
|
+
mergeBase,
|
|
1738
|
+
worktreePath: wtPath,
|
|
1739
|
+
totalCandidates: commitsToProcess.length,
|
|
1740
|
+
cherrySkipped: cherrySkipped.size,
|
|
1741
|
+
dryRun,
|
|
1742
|
+
repoPath
|
|
1743
|
+
};
|
|
1744
|
+
audit.runStart(runMeta);
|
|
1745
|
+
for (const [hash, reason] of cherrySkipped) {
|
|
1746
|
+
const c = allSourceCommits.find((x) => x.hash === hash);
|
|
1747
|
+
if (c)
|
|
1748
|
+
audit.cherrySkip(hash, c.message, reason);
|
|
1584
1749
|
}
|
|
1585
1750
|
}
|
|
1586
1751
|
const sourceLatestDiff = await getLatestCommitDiff(git, resolvedSource);
|
|
@@ -1610,6 +1775,7 @@ async function runEngine(opts, cb) {
|
|
|
1610
1775
|
maxAttempts: effectiveMaxAttempts,
|
|
1611
1776
|
commitPrefix,
|
|
1612
1777
|
workBranch: wbName,
|
|
1778
|
+
memory,
|
|
1613
1779
|
workBranchHead: currentBranchHead
|
|
1614
1780
|
});
|
|
1615
1781
|
if (decision.kind === "applied" && decision.newCommitHash) {
|
|
@@ -1651,7 +1817,7 @@ async function processOneCommit(o) {
|
|
|
1651
1817
|
try {
|
|
1652
1818
|
touchedPaths = await getCommitFiles(o.git, commit.hash);
|
|
1653
1819
|
} catch {}
|
|
1654
|
-
const commitCtx = buildCommitContext(o.repoPath, o.repoCtx, o.config, touchedPaths, commit.message);
|
|
1820
|
+
const commitCtx = buildCommitContext(o.repoPath, o.repoCtx, o.config, touchedPaths, commit.message, o.memory.toPromptBlock());
|
|
1655
1821
|
if (commitCtx.includedFiles.length > 0) {
|
|
1656
1822
|
audit.writeRelevantDocs(commit.hash, 0, commitCtx.includedFiles.join(`
|
|
1657
1823
|
`));
|
|
@@ -1679,6 +1845,19 @@ async function processOneCommit(o) {
|
|
|
1679
1845
|
});
|
|
1680
1846
|
audit.writeAnalysis(commit.hash, 1, analysis);
|
|
1681
1847
|
cb.onAnalysis(commit, analysis);
|
|
1848
|
+
if (analysis.discoveries && analysis.discoveries.length > 0) {
|
|
1849
|
+
const added = o.memory.addDiscoveries(analysis.discoveries, commit.hash);
|
|
1850
|
+
if (added > 0) {
|
|
1851
|
+
cb.onLog(`Memory: +${added} new discoveries (${o.memory.count} total)`, "cyan");
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
if (o.memory.count > 0 && analysis.keepMemoryKeys) {
|
|
1855
|
+
const kept = o.memory.all.filter((e) => analysis.keepMemoryKeys.includes(e.key));
|
|
1856
|
+
const gcCount = o.memory.applyGC(kept.map((e) => ({ key: e.key, value: e.value })));
|
|
1857
|
+
if (gcCount > 0) {
|
|
1858
|
+
cb.onLog(`Memory: GC'd ${gcCount} stale entries (${o.memory.count} remaining)`, "gray");
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1682
1861
|
} catch (e) {
|
|
1683
1862
|
audit.error("analysis", commit.hash, commit.message, e.message);
|
|
1684
1863
|
return mkFailed(commit, "analysis", e.message, start);
|
|
@@ -1968,6 +2147,7 @@ var init_engine = __esm(() => {
|
|
|
1968
2147
|
init_codex();
|
|
1969
2148
|
init_review();
|
|
1970
2149
|
init_audit();
|
|
2150
|
+
init_memory();
|
|
1971
2151
|
init_git();
|
|
1972
2152
|
});
|
|
1973
2153
|
|
|
@@ -1977,11 +2157,11 @@ __export(exports_batch, {
|
|
|
1977
2157
|
runBatch: () => runBatch
|
|
1978
2158
|
});
|
|
1979
2159
|
import chalk from "chalk";
|
|
1980
|
-
import { readFileSync as
|
|
1981
|
-
import { join as
|
|
2160
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
2161
|
+
import { join as join7 } from "path";
|
|
1982
2162
|
function getVersion() {
|
|
1983
2163
|
try {
|
|
1984
|
-
const pkg = JSON.parse(
|
|
2164
|
+
const pkg = JSON.parse(readFileSync6(join7(import.meta.dir, "..", "package.json"), "utf-8"));
|
|
1985
2165
|
return pkg.version ?? "?";
|
|
1986
2166
|
} catch {
|
|
1987
2167
|
return "?";
|
|
@@ -2059,43 +2239,20 @@ async function runBatch(opts) {
|
|
|
2059
2239
|
const subMatch = msg.match(/^\[(\w+)\]\s*(.*)/);
|
|
2060
2240
|
const sub = subMatch ? subMatch[1] : phase;
|
|
2061
2241
|
const text = subMatch ? subMatch[2] : msg;
|
|
2062
|
-
let icon;
|
|
2063
2242
|
switch (sub) {
|
|
2064
|
-
case "read":
|
|
2065
|
-
icon = chalk.cyan("\uD83D\uDCD6");
|
|
2066
|
-
break;
|
|
2067
|
-
case "grep":
|
|
2068
|
-
icon = chalk.cyan("\uD83D\uDD0D");
|
|
2069
|
-
break;
|
|
2070
|
-
case "glob":
|
|
2071
|
-
icon = chalk.cyan("\uD83D\uDCC2");
|
|
2072
|
-
break;
|
|
2073
|
-
case "exec":
|
|
2074
|
-
icon = chalk.yellow("\u26A1");
|
|
2075
|
-
break;
|
|
2076
2243
|
case "file":
|
|
2077
|
-
|
|
2244
|
+
log(` ${ts()} \u270F\uFE0F ${chalk.green(text)}`);
|
|
2078
2245
|
break;
|
|
2079
2246
|
case "think":
|
|
2080
|
-
|
|
2081
|
-
break;
|
|
2082
|
-
case "tool":
|
|
2083
|
-
icon = chalk.dim("\uD83D\uDD27");
|
|
2247
|
+
log(` ${ts()} \uD83D\uDCAD ${chalk.blue(text)}`);
|
|
2084
2248
|
break;
|
|
2085
|
-
case "
|
|
2086
|
-
|
|
2087
|
-
break;
|
|
2088
|
-
case "apply":
|
|
2089
|
-
icon = chalk.green("\u25B8");
|
|
2090
|
-
break;
|
|
2091
|
-
case "review":
|
|
2092
|
-
icon = chalk.magenta("\u25CF");
|
|
2249
|
+
case "read":
|
|
2250
|
+
log(` ${ts()} \uD83D\uDCD6 ${chalk.dim(text)}`);
|
|
2093
2251
|
break;
|
|
2094
2252
|
default:
|
|
2095
|
-
|
|
2253
|
+
log(` ${ts()} ${chalk.dim(text)}`);
|
|
2096
2254
|
break;
|
|
2097
2255
|
}
|
|
2098
|
-
log(` ${ts()} ${icon} ${chalk.dim(text)}`);
|
|
2099
2256
|
},
|
|
2100
2257
|
onLog(msg, color) {
|
|
2101
2258
|
if (jsonl)
|
|
@@ -2138,6 +2295,11 @@ async function runBatch(opts) {
|
|
|
2138
2295
|
if (analysis.opsNotes.length > 0) {
|
|
2139
2296
|
log(` ${chalk.yellow(" ops:")} ${analysis.opsNotes.join("; ")}`);
|
|
2140
2297
|
}
|
|
2298
|
+
if (analysis.discoveries && analysis.discoveries.length > 0) {
|
|
2299
|
+
for (const d of analysis.discoveries) {
|
|
2300
|
+
log(` ${chalk.cyan(` \uD83D\uDCA1 [${d.type}] ${d.key}:`)} ${d.value}`);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2141
2303
|
},
|
|
2142
2304
|
onDecision(commit, decision) {
|
|
2143
2305
|
if (jsonl)
|
|
@@ -2282,12 +2444,12 @@ import { useState, useEffect, useCallback, useRef } from "react";
|
|
|
2282
2444
|
import { Box, Text, useInput, useApp, Static, Newline } from "ink";
|
|
2283
2445
|
import SelectInput from "ink-select-input";
|
|
2284
2446
|
import Spinner from "ink-spinner";
|
|
2285
|
-
import { readFileSync as
|
|
2286
|
-
import { join as
|
|
2447
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
2448
|
+
import { join as join6 } from "path";
|
|
2287
2449
|
import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
|
|
2288
2450
|
var XAB_VERSION = (() => {
|
|
2289
2451
|
try {
|
|
2290
|
-
return JSON.parse(
|
|
2452
|
+
return JSON.parse(readFileSync5(join6(import.meta.dir, "..", "package.json"), "utf-8")).version ?? "?";
|
|
2291
2453
|
} catch {
|
|
2292
2454
|
return "?";
|
|
2293
2455
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xab",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "13.0.0",
|
|
4
4
|
"description": "AI-powered curated branch reconciliation engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,6 +33,6 @@
|
|
|
33
33
|
"ink-text-input": "^6.0.0",
|
|
34
34
|
"react": "18.3.1",
|
|
35
35
|
"simple-git": "^3.33.0",
|
|
36
|
-
"xab": "
|
|
36
|
+
"xab": "12"
|
|
37
37
|
}
|
|
38
38
|
}
|