@basou/cli 0.7.0 → 0.8.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 +178 -53
- package/dist/index.js.map +1 -1
- package/dist/program.js +178 -53
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1343,6 +1343,7 @@ import {
|
|
|
1343
1343
|
importSessionFromJson,
|
|
1344
1344
|
readManifest as readManifest3,
|
|
1345
1345
|
readSessionYaml,
|
|
1346
|
+
reimportPreservingId,
|
|
1346
1347
|
resolveRepositoryRoot as resolveRepositoryRoot6,
|
|
1347
1348
|
SessionImportPayloadSchema
|
|
1348
1349
|
} from "@basou/core";
|
|
@@ -1418,10 +1419,15 @@ async function doRunImportClaudeCode(options, ctx) {
|
|
|
1418
1419
|
const externalId = basename(file, ".jsonl");
|
|
1419
1420
|
return {
|
|
1420
1421
|
externalId,
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1422
|
+
sourcePath: file,
|
|
1423
|
+
toPayload: async () => {
|
|
1424
|
+
const { records, sizeBytes } = await readJsonlRecords(file);
|
|
1425
|
+
return claudeTranscriptToImportPayload(records, {
|
|
1426
|
+
workspaceId: manifest.workspace.id,
|
|
1427
|
+
externalId,
|
|
1428
|
+
sourceSizeBytes: sizeBytes
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1425
1431
|
};
|
|
1426
1432
|
});
|
|
1427
1433
|
await importDerivedSessions(paths, manifest, options, CLAUDE_IMPORT_SOURCE, candidates);
|
|
@@ -1439,10 +1445,15 @@ async function doRunImportCodex(options, ctx) {
|
|
|
1439
1445
|
const rollouts = await discoverCodexRollouts(sessionsRoot, projectPaths, options);
|
|
1440
1446
|
const candidates = rollouts.map(({ file, externalId }) => ({
|
|
1441
1447
|
externalId,
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1448
|
+
sourcePath: file,
|
|
1449
|
+
toPayload: async () => {
|
|
1450
|
+
const { records, sizeBytes } = await readJsonlRecords(file);
|
|
1451
|
+
return codexRolloutToImportPayload(records, {
|
|
1452
|
+
workspaceId: manifest.workspace.id,
|
|
1453
|
+
externalId,
|
|
1454
|
+
sourceSizeBytes: sizeBytes
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1446
1457
|
}));
|
|
1447
1458
|
await importDerivedSessions(paths, manifest, options, CODEX_IMPORT_SOURCE, candidates);
|
|
1448
1459
|
}
|
|
@@ -1466,41 +1477,76 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
1466
1477
|
const existingByExternalId = await loadExistingByExternalId(paths, sourceKind);
|
|
1467
1478
|
const seenThisRun = /* @__PURE__ */ new Set();
|
|
1468
1479
|
const results = [];
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1480
|
+
const counts = {
|
|
1481
|
+
skippedNoAction: 0,
|
|
1482
|
+
skippedExisting: 0,
|
|
1483
|
+
replaced: 0,
|
|
1484
|
+
reimported: 0,
|
|
1485
|
+
skippedLegacy: 0,
|
|
1486
|
+
skippedDecreased: 0,
|
|
1487
|
+
skippedDuplicate: 0
|
|
1488
|
+
};
|
|
1472
1489
|
let sanitizedPaths = 0;
|
|
1473
|
-
|
|
1490
|
+
const validate = (payload) => {
|
|
1491
|
+
if (payload === null) return null;
|
|
1492
|
+
const parsed = SessionImportPayloadSchema.safeParse(payload);
|
|
1493
|
+
if (!parsed.success) {
|
|
1494
|
+
throw new Error("Invalid import payload", { cause: parsed.error });
|
|
1495
|
+
}
|
|
1496
|
+
if (parsed.data.schema_version !== "0.1.0") {
|
|
1497
|
+
throw new Error(`Unsupported import schema_version: ${parsed.data.schema_version}`);
|
|
1498
|
+
}
|
|
1499
|
+
return parsed.data;
|
|
1500
|
+
};
|
|
1501
|
+
for (const { externalId, sourcePath, toPayload } of candidates) {
|
|
1474
1502
|
if (seenThisRun.has(externalId)) {
|
|
1475
|
-
skippedExisting++;
|
|
1503
|
+
counts.skippedExisting++;
|
|
1476
1504
|
continue;
|
|
1477
1505
|
}
|
|
1478
|
-
const
|
|
1479
|
-
if (
|
|
1480
|
-
|
|
1506
|
+
const priors = existingByExternalId.get(externalId) ?? [];
|
|
1507
|
+
if (priors.length > 0 && options.force !== true) {
|
|
1508
|
+
const prior = await classifyReimport(priors, sourcePath, externalId, counts);
|
|
1509
|
+
if (prior === null) continue;
|
|
1510
|
+
const payload2 = validate(await toPayload());
|
|
1511
|
+
if (payload2 === null) {
|
|
1512
|
+
counts.skippedNoAction++;
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1515
|
+
const readSize = payload2.session.source.source_size_bytes;
|
|
1516
|
+
if (prior.sourceSizeBytes !== void 0 && readSize !== void 0 && readSize <= prior.sourceSizeBytes) {
|
|
1517
|
+
console.error(
|
|
1518
|
+
`Import: ${externalId} source changed during read (now ${readSize} <= ${prior.sourceSizeBytes} bytes); re-import skipped`
|
|
1519
|
+
);
|
|
1520
|
+
counts.skippedDecreased++;
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
const outcome = await reimportPreservingId(paths, manifest, prior.sessionId, payload2, {
|
|
1524
|
+
dryRun: options.dryRun === true
|
|
1525
|
+
});
|
|
1526
|
+
if (outcome.status === "skipped") {
|
|
1527
|
+
const detail = outcome.reason === "prior_events_unreadable" ? "prior events.jsonl has unreadable lines" : "source changed in a non-append way (derived events would be dropped)";
|
|
1528
|
+
console.error(`Import: ${externalId} ${detail}; re-import skipped`);
|
|
1529
|
+
counts.skippedNoAction++;
|
|
1530
|
+
continue;
|
|
1531
|
+
}
|
|
1532
|
+
counts.reimported++;
|
|
1533
|
+
seenThisRun.add(externalId);
|
|
1481
1534
|
continue;
|
|
1482
1535
|
}
|
|
1483
|
-
const payload = await toPayload();
|
|
1536
|
+
const payload = validate(await toPayload());
|
|
1484
1537
|
if (payload === null) {
|
|
1485
|
-
skippedNoAction++;
|
|
1538
|
+
counts.skippedNoAction++;
|
|
1486
1539
|
continue;
|
|
1487
1540
|
}
|
|
1488
|
-
|
|
1489
|
-
if (!parsed.success) {
|
|
1490
|
-
throw new Error("Invalid import payload", { cause: parsed.error });
|
|
1491
|
-
}
|
|
1492
|
-
if (parsed.data.schema_version !== "0.1.0") {
|
|
1493
|
-
throw new Error(`Unsupported import schema_version: ${parsed.data.schema_version}`);
|
|
1494
|
-
}
|
|
1495
|
-
if (priorSessionIds.length > 0 && options.force === true) {
|
|
1541
|
+
if (priors.length > 0 && options.force === true) {
|
|
1496
1542
|
if (options.dryRun !== true) {
|
|
1497
|
-
for (const
|
|
1498
|
-
await rm(join3(paths.sessions,
|
|
1543
|
+
for (const { sessionId } of priors) {
|
|
1544
|
+
await rm(join3(paths.sessions, sessionId), { recursive: true, force: true });
|
|
1499
1545
|
}
|
|
1500
1546
|
}
|
|
1501
|
-
replaced++;
|
|
1547
|
+
counts.replaced++;
|
|
1502
1548
|
}
|
|
1503
|
-
const result = await importSessionFromJson(paths, manifest,
|
|
1549
|
+
const result = await importSessionFromJson(paths, manifest, payload, {
|
|
1504
1550
|
dryRun: options.dryRun === true
|
|
1505
1551
|
});
|
|
1506
1552
|
results.push(result);
|
|
@@ -1510,17 +1556,52 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
1510
1556
|
if (sanitizedPaths > 0) {
|
|
1511
1557
|
console.error(`Imported sessions: ${sanitizedPaths} path(s) sanitized`);
|
|
1512
1558
|
}
|
|
1513
|
-
printImportResult(options, results,
|
|
1559
|
+
printImportResult(options, results, counts);
|
|
1560
|
+
}
|
|
1561
|
+
async function classifyReimport(priors, sourcePath, externalId, counts) {
|
|
1562
|
+
if (priors.length > 1) {
|
|
1563
|
+
console.error(
|
|
1564
|
+
`Import: ${externalId} has ${priors.length} prior sessions; re-import skipped (use --force)`
|
|
1565
|
+
);
|
|
1566
|
+
counts.skippedDuplicate++;
|
|
1567
|
+
return null;
|
|
1568
|
+
}
|
|
1569
|
+
const prior = priors[0];
|
|
1570
|
+
if (prior === void 0) {
|
|
1571
|
+
counts.skippedExisting++;
|
|
1572
|
+
return null;
|
|
1573
|
+
}
|
|
1574
|
+
const currentSize = await statSize(sourcePath);
|
|
1575
|
+
if (currentSize === void 0) {
|
|
1576
|
+
counts.skippedExisting++;
|
|
1577
|
+
return null;
|
|
1578
|
+
}
|
|
1579
|
+
if (prior.sourceSizeBytes === void 0) {
|
|
1580
|
+
counts.skippedLegacy++;
|
|
1581
|
+
return null;
|
|
1582
|
+
}
|
|
1583
|
+
if (currentSize === prior.sourceSizeBytes) {
|
|
1584
|
+
counts.skippedExisting++;
|
|
1585
|
+
return null;
|
|
1586
|
+
}
|
|
1587
|
+
if (currentSize < prior.sourceSizeBytes) {
|
|
1588
|
+
console.error(
|
|
1589
|
+
`Import: ${externalId} source shrank (${currentSize} < ${prior.sourceSizeBytes} bytes); re-import skipped (use --force to replace)`
|
|
1590
|
+
);
|
|
1591
|
+
counts.skippedDecreased++;
|
|
1592
|
+
return null;
|
|
1593
|
+
}
|
|
1594
|
+
return prior;
|
|
1514
1595
|
}
|
|
1515
1596
|
function encodeProjectDir(projectPath) {
|
|
1516
1597
|
return projectPath.replaceAll("/", "-");
|
|
1517
1598
|
}
|
|
1518
1599
|
async function loadExistingByExternalId(paths, sourceKind) {
|
|
1519
1600
|
const byExternalId = /* @__PURE__ */ new Map();
|
|
1520
|
-
const add = (externalId,
|
|
1601
|
+
const add = (externalId, prior) => {
|
|
1521
1602
|
const list = byExternalId.get(externalId);
|
|
1522
|
-
if (list === void 0) byExternalId.set(externalId, [
|
|
1523
|
-
else list.push(
|
|
1603
|
+
if (list === void 0) byExternalId.set(externalId, [prior]);
|
|
1604
|
+
else list.push(prior);
|
|
1524
1605
|
};
|
|
1525
1606
|
let sessionIds;
|
|
1526
1607
|
try {
|
|
@@ -1536,14 +1617,16 @@ async function loadExistingByExternalId(paths, sourceKind) {
|
|
|
1536
1617
|
continue;
|
|
1537
1618
|
}
|
|
1538
1619
|
if (session.session.source.kind !== sourceKind) continue;
|
|
1620
|
+
const sourceSizeBytes = session.session.source.source_size_bytes;
|
|
1621
|
+
const prior = sourceSizeBytes !== void 0 ? { sessionId, sourceSizeBytes } : { sessionId };
|
|
1539
1622
|
const ext = session.session.source.external_id;
|
|
1540
1623
|
if (typeof ext === "string" && ext.length > 0) {
|
|
1541
|
-
add(ext,
|
|
1624
|
+
add(ext, prior);
|
|
1542
1625
|
continue;
|
|
1543
1626
|
}
|
|
1544
1627
|
const label = session.session.label;
|
|
1545
1628
|
const match = typeof label === "string" ? label.match(/^claude-code import (\S+)$/) : null;
|
|
1546
|
-
if (match?.[1] !== void 0) add(match[1],
|
|
1629
|
+
if (match?.[1] !== void 0) add(match[1], prior);
|
|
1547
1630
|
}
|
|
1548
1631
|
return byExternalId;
|
|
1549
1632
|
}
|
|
@@ -1589,6 +1672,14 @@ async function pathExists(file) {
|
|
|
1589
1672
|
throw error;
|
|
1590
1673
|
}
|
|
1591
1674
|
}
|
|
1675
|
+
async function statSize(file) {
|
|
1676
|
+
try {
|
|
1677
|
+
return (await stat(file)).size;
|
|
1678
|
+
} catch (error) {
|
|
1679
|
+
if (findErrorCode5(error, "ENOENT")) return void 0;
|
|
1680
|
+
throw error;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1592
1683
|
async function discoverCodexRollouts(sessionsRoot, projectPaths, options) {
|
|
1593
1684
|
const projectSet = new Set(projectPaths);
|
|
1594
1685
|
const files = await findRolloutFiles(sessionsRoot);
|
|
@@ -1667,9 +1758,9 @@ async function readFirstLine(file) {
|
|
|
1667
1758
|
}
|
|
1668
1759
|
}
|
|
1669
1760
|
async function readJsonlRecords(file) {
|
|
1670
|
-
let
|
|
1761
|
+
let buffer;
|
|
1671
1762
|
try {
|
|
1672
|
-
|
|
1763
|
+
buffer = await readFile(file);
|
|
1673
1764
|
} catch (error) {
|
|
1674
1765
|
if (findErrorCode5(error, "ENOENT")) {
|
|
1675
1766
|
throw new Error("Source log not found", { cause: error });
|
|
@@ -1680,7 +1771,7 @@ async function readJsonlRecords(file) {
|
|
|
1680
1771
|
throw new Error("Failed to read source log", { cause: error });
|
|
1681
1772
|
}
|
|
1682
1773
|
const records = [];
|
|
1683
|
-
for (const line of
|
|
1774
|
+
for (const line of buffer.toString("utf8").split("\n")) {
|
|
1684
1775
|
const trimmed = line.trim();
|
|
1685
1776
|
if (trimmed.length === 0) continue;
|
|
1686
1777
|
try {
|
|
@@ -1691,7 +1782,7 @@ async function readJsonlRecords(file) {
|
|
|
1691
1782
|
} catch {
|
|
1692
1783
|
}
|
|
1693
1784
|
}
|
|
1694
|
-
return records;
|
|
1785
|
+
return { records, sizeBytes: buffer.length };
|
|
1695
1786
|
}
|
|
1696
1787
|
function isObject(value) {
|
|
1697
1788
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -1699,7 +1790,15 @@ function isObject(value) {
|
|
|
1699
1790
|
function printImportResult(options, results, counts) {
|
|
1700
1791
|
const isDry = options.dryRun === true;
|
|
1701
1792
|
const eventTotal = results.reduce((sum, r) => sum + r.eventCount, 0);
|
|
1702
|
-
const {
|
|
1793
|
+
const {
|
|
1794
|
+
skippedNoAction,
|
|
1795
|
+
skippedExisting,
|
|
1796
|
+
replaced,
|
|
1797
|
+
reimported,
|
|
1798
|
+
skippedLegacy,
|
|
1799
|
+
skippedDecreased,
|
|
1800
|
+
skippedDuplicate
|
|
1801
|
+
} = counts;
|
|
1703
1802
|
if (options.json === true) {
|
|
1704
1803
|
console.log(
|
|
1705
1804
|
JSON.stringify({
|
|
@@ -1711,8 +1810,12 @@ function printImportResult(options, results, counts) {
|
|
|
1711
1810
|
})),
|
|
1712
1811
|
imported_count: results.length,
|
|
1713
1812
|
replaced_count: replaced,
|
|
1813
|
+
reimported_count: reimported,
|
|
1714
1814
|
skipped_no_action: skippedNoAction,
|
|
1715
1815
|
skipped_already_imported: skippedExisting,
|
|
1816
|
+
skipped_legacy_untracked: skippedLegacy,
|
|
1817
|
+
skipped_decreased: skippedDecreased,
|
|
1818
|
+
skipped_duplicate: skippedDuplicate,
|
|
1716
1819
|
event_total: eventTotal,
|
|
1717
1820
|
dry_run: isDry
|
|
1718
1821
|
})
|
|
@@ -1722,20 +1825,36 @@ function printImportResult(options, results, counts) {
|
|
|
1722
1825
|
const skipParts = [];
|
|
1723
1826
|
if (skippedNoAction > 0) skipParts.push(`${skippedNoAction} with no actions`);
|
|
1724
1827
|
if (skippedExisting > 0) skipParts.push(`${skippedExisting} already imported`);
|
|
1828
|
+
if (skippedLegacy > 0) skipParts.push(`${skippedLegacy} legacy (untracked size)`);
|
|
1829
|
+
if (skippedDecreased > 0) skipParts.push(`${skippedDecreased} shrank`);
|
|
1830
|
+
if (skippedDuplicate > 0) skipParts.push(`${skippedDuplicate} duplicated`);
|
|
1725
1831
|
const skipSuffix = skipParts.length > 0 ? `; skipped ${skipParts.join(", ")}` : "";
|
|
1726
1832
|
const eventsPart = replaced > 0 ? `${eventTotal} events, ${replaced} replaced` : `${eventTotal} events`;
|
|
1727
|
-
if (
|
|
1833
|
+
if (isDry) {
|
|
1834
|
+
const parts = [];
|
|
1835
|
+
if (results.length > 0) parts.push(`import ${results.length} session(s) (${eventsPart})`);
|
|
1836
|
+
if (reimported > 0) parts.push(`re-import ${reimported} changed session(s)`);
|
|
1837
|
+
const head = parts.length > 0 ? `Dry run: would ${parts.join(", ")}` : "Dry run: no changes";
|
|
1838
|
+
console.log(`${head}${skipSuffix}`);
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
if (results.length === 0 && reimported === 0) {
|
|
1728
1842
|
console.log(
|
|
1729
1843
|
skipParts.length > 0 ? `No new sessions imported (skipped ${skipParts.join(", ")})` : "No transcripts found to import"
|
|
1730
1844
|
);
|
|
1731
1845
|
return;
|
|
1732
1846
|
}
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1847
|
+
const segments = [];
|
|
1848
|
+
if (results.length > 0) {
|
|
1849
|
+
const single = results.length === 1 && results[0] !== void 0 ? ` (${shortId2(results[0].sessionId)})` : "";
|
|
1850
|
+
segments.push(`Imported ${results.length} session(s)${single} (${eventsPart})`);
|
|
1851
|
+
}
|
|
1852
|
+
if (reimported > 0) {
|
|
1853
|
+
segments.push(
|
|
1854
|
+
`${results.length > 0 ? "re-imported" : "Re-imported"} ${reimported} changed session(s)`
|
|
1855
|
+
);
|
|
1736
1856
|
}
|
|
1737
|
-
|
|
1738
|
-
console.log(`Imported ${results.length} session(s)${single} (${eventsPart})${skipSuffix}`);
|
|
1857
|
+
console.log(`${segments.join(", ")}${skipSuffix}`);
|
|
1739
1858
|
}
|
|
1740
1859
|
function shortId2(id) {
|
|
1741
1860
|
if (id.startsWith(SES_PREFIX2)) {
|
|
@@ -1908,8 +2027,10 @@ async function runImport(adapter, fn) {
|
|
|
1908
2027
|
status: "ran",
|
|
1909
2028
|
importedCount: readCount(json.imported_count),
|
|
1910
2029
|
replacedCount: readCount(json.replaced_count),
|
|
2030
|
+
reimportedCount: readCount(json.reimported_count),
|
|
1911
2031
|
skippedNoAction: readCount(json.skipped_no_action),
|
|
1912
2032
|
skippedAlreadyImported: readCount(json.skipped_already_imported),
|
|
2033
|
+
skippedLegacyUntracked: readCount(json.skipped_legacy_untracked),
|
|
1913
2034
|
eventTotal: readCount(json.event_total),
|
|
1914
2035
|
dryRun: json.dry_run === true
|
|
1915
2036
|
};
|
|
@@ -2030,11 +2151,13 @@ function scansEqual(a, b) {
|
|
|
2030
2151
|
}
|
|
2031
2152
|
return true;
|
|
2032
2153
|
}
|
|
2033
|
-
function
|
|
2034
|
-
return outcome.status === "ran" ? outcome.importedCount : 0;
|
|
2154
|
+
function changedCount(outcome) {
|
|
2155
|
+
return outcome.status === "ran" ? outcome.importedCount + outcome.reimportedCount + outcome.replacedCount : 0;
|
|
2035
2156
|
}
|
|
2036
2157
|
function describeOutcome(outcome) {
|
|
2037
|
-
|
|
2158
|
+
if (outcome.status !== "ran") return `${outcome.adapter} skipped`;
|
|
2159
|
+
const reimported = outcome.reimportedCount > 0 ? ` ~${outcome.reimportedCount}` : "";
|
|
2160
|
+
return `${outcome.adapter} +${outcome.importedCount}${reimported}`;
|
|
2038
2161
|
}
|
|
2039
2162
|
function hms(date) {
|
|
2040
2163
|
return date.toISOString().slice(11, 19);
|
|
@@ -2042,7 +2165,7 @@ function hms(date) {
|
|
|
2042
2165
|
async function runImports(deps) {
|
|
2043
2166
|
const claude = await importClaudeCode(deps.importOptions, deps.ctx);
|
|
2044
2167
|
const codex = await importCodex(deps.importOptions, deps.ctx);
|
|
2045
|
-
return { claude, codex,
|
|
2168
|
+
return { claude, codex, changed: changedCount(claude) + changedCount(codex) };
|
|
2046
2169
|
}
|
|
2047
2170
|
async function regenerate(deps) {
|
|
2048
2171
|
const nowIso = deps.now().toISOString();
|
|
@@ -2074,8 +2197,8 @@ async function runRefreshWatch(deps) {
|
|
|
2074
2197
|
try {
|
|
2075
2198
|
const current = await scanSourceLogs(roots);
|
|
2076
2199
|
if (scansEqual(current, lastScan) && !scansEqual(current, importedScan)) {
|
|
2077
|
-
const { claude, codex,
|
|
2078
|
-
if (
|
|
2200
|
+
const { claude, codex, changed } = await runImports(deps);
|
|
2201
|
+
if (changed > 0) pendingRegen = true;
|
|
2079
2202
|
if (pendingRegen) {
|
|
2080
2203
|
const sessions = await regenerate(deps);
|
|
2081
2204
|
pendingRegen = false;
|
|
@@ -2214,9 +2337,11 @@ function describeImport(outcome) {
|
|
|
2214
2337
|
}
|
|
2215
2338
|
const verb = outcome.dryRun ? "would import" : "imported";
|
|
2216
2339
|
const parts = [`${outcome.importedCount} session(s)`, `${outcome.eventTotal} events`];
|
|
2340
|
+
if (outcome.reimportedCount > 0) parts.push(`${outcome.reimportedCount} re-imported`);
|
|
2217
2341
|
if (outcome.replacedCount > 0) parts.push(`${outcome.replacedCount} replaced`);
|
|
2218
2342
|
if (outcome.skippedAlreadyImported > 0)
|
|
2219
2343
|
parts.push(`${outcome.skippedAlreadyImported} already imported`);
|
|
2344
|
+
if (outcome.skippedLegacyUntracked > 0) parts.push(`${outcome.skippedLegacyUntracked} legacy`);
|
|
2220
2345
|
return `${outcome.adapter}: ${verb} ${parts.join(", ")}`;
|
|
2221
2346
|
}
|
|
2222
2347
|
function printRefreshSummary(result) {
|