@basou/cli 0.71.1 → 0.72.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 +469 -78
- package/dist/index.js.map +1 -1
- package/dist/program.js +469 -78
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -842,11 +842,13 @@ import {
|
|
|
842
842
|
assertBasouRootSafe as assertBasouRootSafe3,
|
|
843
843
|
BOARD_INIT_SAVE,
|
|
844
844
|
basouPaths as basouPaths4,
|
|
845
|
+
boardDeclarationSnapshot,
|
|
845
846
|
boardGuide,
|
|
846
847
|
boardInitText,
|
|
847
848
|
buildRecord,
|
|
848
849
|
byCodePoint,
|
|
849
850
|
diffCells,
|
|
851
|
+
diffDeclarations,
|
|
850
852
|
diffObserved,
|
|
851
853
|
displayPath as displayPath3,
|
|
852
854
|
effortStartOf,
|
|
@@ -1042,6 +1044,8 @@ import {
|
|
|
1042
1044
|
CLAUDE_IMPORT_SOURCE,
|
|
1043
1045
|
CODEX_IMPORT_SOURCE,
|
|
1044
1046
|
classifyFilesBySourceRoot,
|
|
1047
|
+
claudeTranscriptOwnRecords,
|
|
1048
|
+
claudeTranscriptRecordUuid,
|
|
1045
1049
|
claudeTranscriptToImportPayload,
|
|
1046
1050
|
codexRolloutToImportPayload,
|
|
1047
1051
|
displayPath as displayPath2,
|
|
@@ -1142,24 +1146,43 @@ async function doRunImportClaudeCode(options, ctx) {
|
|
|
1142
1146
|
});
|
|
1143
1147
|
const projectsRoot = ctx.claudeProjectsDir ?? join2(homedir(), ".claude", "projects");
|
|
1144
1148
|
const files = await selectTranscriptFiles(projectsRoot, projectPaths, options);
|
|
1149
|
+
const store = await loadImportStore(paths, options, CLAUDE_IMPORT_SOURCE);
|
|
1150
|
+
const conversations = await indexConversations(
|
|
1151
|
+
await listTranscriptFiles(projectsRoot, projectPaths),
|
|
1152
|
+
ctx.transcriptCreatedAtMs ?? birthTimeMs,
|
|
1153
|
+
(externalId) => earliestStart(store.byExternalId.get(externalId) ?? [])
|
|
1154
|
+
);
|
|
1145
1155
|
const projectSet = new Set(projectPaths);
|
|
1146
1156
|
const candidates = files.map((file) => {
|
|
1147
1157
|
const externalId = basename3(file, ".jsonl");
|
|
1158
|
+
const { root, older } = conversations.place(file);
|
|
1148
1159
|
return {
|
|
1149
1160
|
externalId,
|
|
1150
1161
|
sourcePath: file,
|
|
1151
|
-
|
|
1162
|
+
// An earlier import that took the copies too began the session at the
|
|
1163
|
+
// conversation's first record or before it; one without them begins after
|
|
1164
|
+
// the last copy.
|
|
1165
|
+
holdsCopies: (prior) => older.length > 0 && root?.timestamp !== void 0 && Date.parse(prior.startedAt) <= Date.parse(root.timestamp),
|
|
1166
|
+
toPayload: async (notBefore) => {
|
|
1152
1167
|
const { records, sizeBytes } = await readJsonlRecords(file);
|
|
1153
1168
|
const cwd = firstTranscriptCwd(records);
|
|
1154
|
-
if (cwd === void 0 || !projectSet.has(cwd))
|
|
1169
|
+
if (cwd === void 0 || !projectSet.has(cwd))
|
|
1170
|
+
return { payload: null, leftOutCopies: false };
|
|
1155
1171
|
const observation2 = await readSessionObservation(paths.observations, externalId);
|
|
1156
1172
|
const observedFiles = observation2 === null ? [] : observedFilesOf(observation2);
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
sourceSizeBytes: sizeBytes,
|
|
1161
|
-
...observedFiles.length > 0 ? { observedFiles } : {}
|
|
1173
|
+
const own = claudeTranscriptOwnRecords(records, {
|
|
1174
|
+
copiedUuids: await conversations.uuidsOf(older),
|
|
1175
|
+
...notBefore !== void 0 ? { notBefore } : {}
|
|
1162
1176
|
});
|
|
1177
|
+
return {
|
|
1178
|
+
payload: claudeTranscriptToImportPayload(own, {
|
|
1179
|
+
workspaceId: manifest.workspace.id,
|
|
1180
|
+
externalId,
|
|
1181
|
+
sourceSizeBytes: sizeBytes,
|
|
1182
|
+
...observedFiles.length > 0 ? { observedFiles } : {}
|
|
1183
|
+
}),
|
|
1184
|
+
leftOutCopies: own.length < records.length
|
|
1185
|
+
};
|
|
1163
1186
|
}
|
|
1164
1187
|
};
|
|
1165
1188
|
});
|
|
@@ -1167,7 +1190,7 @@ async function doRunImportClaudeCode(options, ctx) {
|
|
|
1167
1190
|
paths,
|
|
1168
1191
|
manifest,
|
|
1169
1192
|
options,
|
|
1170
|
-
|
|
1193
|
+
store,
|
|
1171
1194
|
candidates,
|
|
1172
1195
|
projectPaths,
|
|
1173
1196
|
hasDeclaredBoundary(options, manifest)
|
|
@@ -1189,18 +1212,21 @@ async function doRunImportCodex(options, ctx) {
|
|
|
1189
1212
|
sourcePath: file,
|
|
1190
1213
|
toPayload: async () => {
|
|
1191
1214
|
const { records, sizeBytes } = await readJsonlRecords(file);
|
|
1192
|
-
return
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1215
|
+
return {
|
|
1216
|
+
payload: codexRolloutToImportPayload(records, {
|
|
1217
|
+
workspaceId: manifest.workspace.id,
|
|
1218
|
+
externalId,
|
|
1219
|
+
sourceSizeBytes: sizeBytes
|
|
1220
|
+
}),
|
|
1221
|
+
leftOutCopies: false
|
|
1222
|
+
};
|
|
1197
1223
|
}
|
|
1198
1224
|
}));
|
|
1199
1225
|
await importDerivedSessions(
|
|
1200
1226
|
paths,
|
|
1201
1227
|
manifest,
|
|
1202
1228
|
options,
|
|
1203
|
-
CODEX_IMPORT_SOURCE,
|
|
1229
|
+
await loadImportStore(paths, options, CODEX_IMPORT_SOURCE),
|
|
1204
1230
|
candidates,
|
|
1205
1231
|
projectPaths,
|
|
1206
1232
|
hasDeclaredBoundary(options, manifest)
|
|
@@ -1225,14 +1251,8 @@ async function resolveImportTarget(ctx) {
|
|
|
1225
1251
|
const manifest = await readManifest2(paths);
|
|
1226
1252
|
return { repositoryRoot, paths, manifest };
|
|
1227
1253
|
}
|
|
1228
|
-
async function importDerivedSessions(paths, manifest, options,
|
|
1229
|
-
|
|
1230
|
-
if (options.dryRun !== true) await assertLockStoreSafe2(paths);
|
|
1231
|
-
const {
|
|
1232
|
-
byExternalId: existingByExternalId,
|
|
1233
|
-
notDirectories,
|
|
1234
|
-
yamlNotFiles
|
|
1235
|
-
} = await loadExistingByExternalId(paths, sourceKind);
|
|
1254
|
+
async function importDerivedSessions(paths, manifest, options, store, candidates, projectPaths, boundaryDeclared) {
|
|
1255
|
+
const { byExternalId: existingByExternalId, notDirectories, yamlNotFiles } = store;
|
|
1236
1256
|
let notDirectoriesWarned = false;
|
|
1237
1257
|
const seenThisRun = /* @__PURE__ */ new Set();
|
|
1238
1258
|
const crossProjectCheck = boundaryDeclared;
|
|
@@ -1260,7 +1280,8 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
1260
1280
|
skippedLegacy: 0,
|
|
1261
1281
|
skippedDecreased: 0,
|
|
1262
1282
|
skippedDuplicate: 0,
|
|
1263
|
-
skippedUnverifiable: 0
|
|
1283
|
+
skippedUnverifiable: 0,
|
|
1284
|
+
droppedCopies: 0
|
|
1264
1285
|
};
|
|
1265
1286
|
let sanitizedPaths = 0;
|
|
1266
1287
|
const validate = (payload) => {
|
|
@@ -1276,30 +1297,44 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
1276
1297
|
}
|
|
1277
1298
|
return parsed.data;
|
|
1278
1299
|
};
|
|
1279
|
-
for (const { externalId, sourcePath, toPayload } of candidates) {
|
|
1300
|
+
for (const { externalId, sourcePath, toPayload, holdsCopies } of candidates) {
|
|
1280
1301
|
if (seenThisRun.has(externalId)) {
|
|
1281
1302
|
counts.skippedExisting++;
|
|
1282
1303
|
continue;
|
|
1283
1304
|
}
|
|
1284
1305
|
const priors = existingByExternalId.get(externalId) ?? [];
|
|
1285
1306
|
if (priors.length > 0 && options.force !== true) {
|
|
1286
|
-
const
|
|
1287
|
-
if (
|
|
1288
|
-
const
|
|
1307
|
+
const decision = await classifyReimport(priors, sourcePath, externalId, counts, holdsCopies);
|
|
1308
|
+
if (decision === null) continue;
|
|
1309
|
+
const { prior, reason } = decision;
|
|
1310
|
+
const derived = await toPayload(prior.startedAt);
|
|
1311
|
+
const payload2 = validate(derived.payload);
|
|
1289
1312
|
if (payload2 === null) {
|
|
1313
|
+
if (reason === "holds_copies") {
|
|
1314
|
+
console.error(
|
|
1315
|
+
`Import: ${externalId} added no action of its own after the records it copied from an older transcript; its session, imported with those records, is left as it is`
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1290
1318
|
counts.skippedNoAction++;
|
|
1291
1319
|
continue;
|
|
1292
1320
|
}
|
|
1321
|
+
if (reason === "holds_copies" && !derived.leftOutCopies) {
|
|
1322
|
+
counts.skippedExisting++;
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1293
1325
|
const readSize = payload2.session.source.source_size_bytes;
|
|
1294
|
-
if (prior.sourceSizeBytes !== void 0 && readSize !== void 0 && readSize <= prior.sourceSizeBytes) {
|
|
1326
|
+
if (prior.sourceSizeBytes !== void 0 && readSize !== void 0 && (reason === "grew" ? readSize <= prior.sourceSizeBytes : readSize < prior.sourceSizeBytes)) {
|
|
1295
1327
|
console.error(
|
|
1296
|
-
`Import: ${externalId} source changed during read (now ${readSize}
|
|
1328
|
+
`Import: ${externalId} source changed during read (now ${readSize} bytes, was ${prior.sourceSizeBytes}); re-import skipped`
|
|
1297
1329
|
);
|
|
1298
1330
|
counts.skippedDecreased++;
|
|
1299
1331
|
continue;
|
|
1300
1332
|
}
|
|
1301
1333
|
const outcome = await reimportPreservingId(paths, manifest, prior.sessionId, payload2, {
|
|
1302
|
-
dryRun: options.dryRun === true
|
|
1334
|
+
dryRun: options.dryRun === true,
|
|
1335
|
+
// What an earlier import derived from the copies predates the session's
|
|
1336
|
+
// fresh start; anything later that is not reproduced still refuses.
|
|
1337
|
+
...derived.leftOutCopies ? { dropDerivedBefore: payload2.session.started_at } : {}
|
|
1303
1338
|
});
|
|
1304
1339
|
if (outcome.status === "skipped") {
|
|
1305
1340
|
const detail = outcome.reason === "prior_events_unreadable" ? "prior events.jsonl has unreadable lines" : outcome.reason === "prior_chain_broken" ? "prior events.jsonl failed hash-chain verification (run 'basou verify')" : outcome.reason === "prior_yaml_invalid" ? "prior session.yaml does not load as a whole document" : "source changed in a non-append way (derived events would be dropped)";
|
|
@@ -1308,11 +1343,12 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
1308
1343
|
continue;
|
|
1309
1344
|
}
|
|
1310
1345
|
counts.reimported++;
|
|
1346
|
+
if (holdsCopies?.(prior) === true) counts.droppedCopies++;
|
|
1311
1347
|
seenThisRun.add(externalId);
|
|
1312
1348
|
await noteCrossProject(externalId, payload2);
|
|
1313
1349
|
continue;
|
|
1314
1350
|
}
|
|
1315
|
-
const payload = validate(await toPayload());
|
|
1351
|
+
const payload = validate((await toPayload(earliestStart(priors))).payload);
|
|
1316
1352
|
if (payload === null) {
|
|
1317
1353
|
counts.skippedNoAction++;
|
|
1318
1354
|
continue;
|
|
@@ -1363,7 +1399,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
|
|
|
1363
1399
|
}
|
|
1364
1400
|
printImportResult(options, results, counts);
|
|
1365
1401
|
}
|
|
1366
|
-
async function classifyReimport(priors, sourcePath, externalId, counts) {
|
|
1402
|
+
async function classifyReimport(priors, sourcePath, externalId, counts, holdsCopies) {
|
|
1367
1403
|
if (priors.length > 1) {
|
|
1368
1404
|
console.error(
|
|
1369
1405
|
`Import: ${externalId} has ${priors.length} prior sessions; re-import skipped (use --force)`
|
|
@@ -1386,6 +1422,7 @@ async function classifyReimport(priors, sourcePath, externalId, counts) {
|
|
|
1386
1422
|
return null;
|
|
1387
1423
|
}
|
|
1388
1424
|
if (currentSize === prior.sourceSizeBytes) {
|
|
1425
|
+
if (holdsCopies?.(prior) === true) return { prior, reason: "holds_copies" };
|
|
1389
1426
|
counts.skippedExisting++;
|
|
1390
1427
|
return null;
|
|
1391
1428
|
}
|
|
@@ -1396,7 +1433,15 @@ async function classifyReimport(priors, sourcePath, externalId, counts) {
|
|
|
1396
1433
|
counts.skippedDecreased++;
|
|
1397
1434
|
return null;
|
|
1398
1435
|
}
|
|
1399
|
-
return prior;
|
|
1436
|
+
return { prior, reason: "grew" };
|
|
1437
|
+
}
|
|
1438
|
+
function earliestStart(priors) {
|
|
1439
|
+
let earliest;
|
|
1440
|
+
for (const { startedAt } of priors) {
|
|
1441
|
+
if (earliest === void 0 || Date.parse(startedAt) < Date.parse(earliest))
|
|
1442
|
+
earliest = startedAt;
|
|
1443
|
+
}
|
|
1444
|
+
return earliest;
|
|
1400
1445
|
}
|
|
1401
1446
|
function encodeProjectDir(projectPath) {
|
|
1402
1447
|
return projectPath.replace(/[^a-zA-Z0-9]/g, "-");
|
|
@@ -1408,6 +1453,11 @@ function firstTranscriptCwd(records) {
|
|
|
1408
1453
|
}
|
|
1409
1454
|
return void 0;
|
|
1410
1455
|
}
|
|
1456
|
+
async function loadImportStore(paths, options, sourceKind) {
|
|
1457
|
+
await assertSessionStoreSafe(paths);
|
|
1458
|
+
if (options.dryRun !== true) await assertLockStoreSafe2(paths);
|
|
1459
|
+
return loadExistingByExternalId(paths, sourceKind);
|
|
1460
|
+
}
|
|
1411
1461
|
async function loadExistingByExternalId(paths, sourceKind) {
|
|
1412
1462
|
const byExternalId = /* @__PURE__ */ new Map();
|
|
1413
1463
|
const add = (externalId, prior) => {
|
|
@@ -1433,7 +1483,8 @@ async function loadExistingByExternalId(paths, sourceKind) {
|
|
|
1433
1483
|
}
|
|
1434
1484
|
if (session.session.source.kind !== sourceKind) continue;
|
|
1435
1485
|
const sourceSizeBytes = session.session.source.source_size_bytes;
|
|
1436
|
-
const
|
|
1486
|
+
const startedAt = session.session.started_at;
|
|
1487
|
+
const prior = sourceSizeBytes !== void 0 ? { sessionId, startedAt, sourceSizeBytes } : { sessionId, startedAt };
|
|
1437
1488
|
const ext = session.session.source.external_id;
|
|
1438
1489
|
if (typeof ext === "string" && ext.length > 0) {
|
|
1439
1490
|
add(ext, prior);
|
|
@@ -1478,6 +1529,153 @@ async function selectTranscriptFiles(projectsRoot, projectPaths, options) {
|
|
|
1478
1529
|
}
|
|
1479
1530
|
return [...new Set(files)].sort();
|
|
1480
1531
|
}
|
|
1532
|
+
async function listTranscriptFiles(projectsRoot, projectPaths) {
|
|
1533
|
+
const files = [];
|
|
1534
|
+
for (const projectPath of projectPaths) {
|
|
1535
|
+
const transcriptDir = join2(projectsRoot, encodeProjectDir(projectPath));
|
|
1536
|
+
let entries;
|
|
1537
|
+
try {
|
|
1538
|
+
entries = await readdir(transcriptDir);
|
|
1539
|
+
} catch (error) {
|
|
1540
|
+
if (findErrorCode2(error, "ENOENT")) continue;
|
|
1541
|
+
throw new Error("Failed to read Claude transcript directory", { cause: error });
|
|
1542
|
+
}
|
|
1543
|
+
for (const name of entries) {
|
|
1544
|
+
if (name.endsWith(".jsonl")) files.push(join2(transcriptDir, name));
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
return [...new Set(files)].sort();
|
|
1548
|
+
}
|
|
1549
|
+
async function indexConversations(files, createdAtMs, importedStartedAt) {
|
|
1550
|
+
const uuidsByFile = /* @__PURE__ */ new Map();
|
|
1551
|
+
const uuidsIn = async (file) => {
|
|
1552
|
+
let held = uuidsByFile.get(file);
|
|
1553
|
+
if (held === void 0) {
|
|
1554
|
+
held = await readTranscriptUuids(file);
|
|
1555
|
+
uuidsByFile.set(file, held);
|
|
1556
|
+
}
|
|
1557
|
+
return held;
|
|
1558
|
+
};
|
|
1559
|
+
const rootOf = /* @__PURE__ */ new Map();
|
|
1560
|
+
const byRoot = /* @__PURE__ */ new Map();
|
|
1561
|
+
for (const file of files) {
|
|
1562
|
+
const root = await readConversationRoot(file);
|
|
1563
|
+
if (root === void 0) continue;
|
|
1564
|
+
rootOf.set(file, root);
|
|
1565
|
+
const entry = { file, externalId: basename3(file, ".jsonl"), createdAtMs: Number.NaN };
|
|
1566
|
+
const list = byRoot.get(root.uuid);
|
|
1567
|
+
if (list === void 0) byRoot.set(root.uuid, [entry]);
|
|
1568
|
+
else list.push(entry);
|
|
1569
|
+
}
|
|
1570
|
+
for (const list of byRoot.values()) {
|
|
1571
|
+
if (list.length < 2) continue;
|
|
1572
|
+
for (const entry of list) entry.createdAtMs = await createdAtMs(entry.file);
|
|
1573
|
+
list.sort((a, b) => {
|
|
1574
|
+
const knownA = Number.isFinite(a.createdAtMs) && a.createdAtMs > 0;
|
|
1575
|
+
const knownB = Number.isFinite(b.createdAtMs) && b.createdAtMs > 0;
|
|
1576
|
+
if (knownA && knownB && a.createdAtMs !== b.createdAtMs) return a.createdAtMs - b.createdAtMs;
|
|
1577
|
+
if (knownA !== knownB) return knownA ? -1 : 1;
|
|
1578
|
+
return a.externalId < b.externalId ? -1 : a.externalId > b.externalId ? 1 : 0;
|
|
1579
|
+
});
|
|
1580
|
+
const startedAtMs = /* @__PURE__ */ new Map();
|
|
1581
|
+
for (const entry of list) {
|
|
1582
|
+
const ms = Date.parse(importedStartedAt(entry.externalId) ?? "");
|
|
1583
|
+
if (!Number.isNaN(ms)) startedAtMs.set(entry, ms);
|
|
1584
|
+
}
|
|
1585
|
+
const imported = [...startedAtMs.keys()].sort(
|
|
1586
|
+
(a, b) => (startedAtMs.get(a) ?? 0) - (startedAtMs.get(b) ?? 0)
|
|
1587
|
+
);
|
|
1588
|
+
list.forEach((entry, index) => {
|
|
1589
|
+
if (startedAtMs.has(entry)) list[index] = imported.shift() ?? entry;
|
|
1590
|
+
});
|
|
1591
|
+
const rootMs = Date.parse(rootOf.get(list[0]?.file ?? "")?.timestamp ?? "");
|
|
1592
|
+
const forks = list.filter((entry) => (startedAtMs.get(entry) ?? Number.NaN) > rootMs);
|
|
1593
|
+
if (forks.length === 0) continue;
|
|
1594
|
+
const rank = new Map(list.map((entry, index) => [entry, index]));
|
|
1595
|
+
const countedBy = /* @__PURE__ */ new Map();
|
|
1596
|
+
for (const [index, entry] of list.entries()) {
|
|
1597
|
+
if (startedAtMs.has(entry)) continue;
|
|
1598
|
+
for (const fork of forks) {
|
|
1599
|
+
const forkRank = rank.get(fork) ?? index;
|
|
1600
|
+
if (forkRank < index) continue;
|
|
1601
|
+
let counted = countedBy.get(fork);
|
|
1602
|
+
if (counted === void 0) {
|
|
1603
|
+
counted = await readTranscriptUuids(fork.file, importedStartedAt(fork.externalId));
|
|
1604
|
+
countedBy.set(fork, counted);
|
|
1605
|
+
}
|
|
1606
|
+
if (counted.size === 0) continue;
|
|
1607
|
+
const held = await uuidsIn(entry.file);
|
|
1608
|
+
if ([...counted].some((uuid) => held.has(uuid))) {
|
|
1609
|
+
rank.set(entry, Math.max(rank.get(entry) ?? index, forkRank + 0.5));
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
list.sort((a, b) => (rank.get(a) ?? 0) - (rank.get(b) ?? 0));
|
|
1614
|
+
}
|
|
1615
|
+
return {
|
|
1616
|
+
place(file) {
|
|
1617
|
+
const root = rootOf.get(file);
|
|
1618
|
+
if (root === void 0) return { root: void 0, older: [] };
|
|
1619
|
+
const list = byRoot.get(root.uuid) ?? [];
|
|
1620
|
+
const at = list.findIndex((entry) => entry.file === file);
|
|
1621
|
+
return { root, older: list.slice(0, Math.max(at, 0)).map((entry) => entry.file) };
|
|
1622
|
+
},
|
|
1623
|
+
async uuidsOf(older) {
|
|
1624
|
+
const uuids = /* @__PURE__ */ new Set();
|
|
1625
|
+
for (const file of older) {
|
|
1626
|
+
for (const uuid of await uuidsIn(file)) uuids.add(uuid);
|
|
1627
|
+
}
|
|
1628
|
+
return uuids;
|
|
1629
|
+
}
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
async function readTranscriptUuids(file, notBefore) {
|
|
1633
|
+
const uuids = /* @__PURE__ */ new Set();
|
|
1634
|
+
try {
|
|
1635
|
+
const { records } = await readJsonlRecords(file);
|
|
1636
|
+
const kept = notBefore === void 0 ? records : claudeTranscriptOwnRecords(records, { notBefore });
|
|
1637
|
+
for (const record of kept) {
|
|
1638
|
+
const uuid = claudeTranscriptRecordUuid(record);
|
|
1639
|
+
if (uuid !== void 0) uuids.add(uuid);
|
|
1640
|
+
}
|
|
1641
|
+
} catch {
|
|
1642
|
+
}
|
|
1643
|
+
return uuids;
|
|
1644
|
+
}
|
|
1645
|
+
async function readConversationRoot(file) {
|
|
1646
|
+
const stream = createReadStream(file, { encoding: "utf8" });
|
|
1647
|
+
const rl = createInterface({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
1648
|
+
try {
|
|
1649
|
+
for await (const line of rl) {
|
|
1650
|
+
const trimmed = line.trim();
|
|
1651
|
+
if (trimmed.length === 0) continue;
|
|
1652
|
+
let parsed;
|
|
1653
|
+
try {
|
|
1654
|
+
parsed = JSON.parse(trimmed);
|
|
1655
|
+
} catch {
|
|
1656
|
+
continue;
|
|
1657
|
+
}
|
|
1658
|
+
if (!isObject(parsed)) continue;
|
|
1659
|
+
const uuid = claudeTranscriptRecordUuid(parsed);
|
|
1660
|
+
if (uuid === void 0) continue;
|
|
1661
|
+
const timestamp = parsed.timestamp;
|
|
1662
|
+
return typeof timestamp === "string" && timestamp.length > 0 ? { uuid, timestamp } : { uuid };
|
|
1663
|
+
}
|
|
1664
|
+
return void 0;
|
|
1665
|
+
} catch {
|
|
1666
|
+
return void 0;
|
|
1667
|
+
} finally {
|
|
1668
|
+
rl.close();
|
|
1669
|
+
stream.destroy();
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
async function birthTimeMs(file) {
|
|
1673
|
+
try {
|
|
1674
|
+
return (await stat2(file)).birthtimeMs;
|
|
1675
|
+
} catch {
|
|
1676
|
+
return Number.NaN;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1481
1679
|
async function pathExists(file) {
|
|
1482
1680
|
try {
|
|
1483
1681
|
await stat2(file);
|
|
@@ -1613,7 +1811,8 @@ function printImportResult(options, results, counts) {
|
|
|
1613
1811
|
skippedLegacy,
|
|
1614
1812
|
skippedDecreased,
|
|
1615
1813
|
skippedDuplicate,
|
|
1616
|
-
skippedUnverifiable
|
|
1814
|
+
skippedUnverifiable,
|
|
1815
|
+
droppedCopies
|
|
1617
1816
|
} = counts;
|
|
1618
1817
|
if (options.json === true) {
|
|
1619
1818
|
console.log(
|
|
@@ -1627,6 +1826,7 @@ function printImportResult(options, results, counts) {
|
|
|
1627
1826
|
imported_count: results.length,
|
|
1628
1827
|
replaced_count: replaced,
|
|
1629
1828
|
reimported_count: reimported,
|
|
1829
|
+
dropped_copies_count: droppedCopies,
|
|
1630
1830
|
skipped_no_action: skippedNoAction,
|
|
1631
1831
|
skipped_already_imported: skippedExisting,
|
|
1632
1832
|
skipped_legacy_untracked: skippedLegacy,
|
|
@@ -1649,10 +1849,11 @@ function printImportResult(options, results, counts) {
|
|
|
1649
1849
|
skipParts.push(`${skippedUnverifiable} unverifiable (run 'basou verify')`);
|
|
1650
1850
|
const skipSuffix = skipParts.length > 0 ? `; skipped ${skipParts.join(", ")}` : "";
|
|
1651
1851
|
const eventsPart = replaced > 0 ? `${eventTotal} events, ${replaced} replaced` : `${eventTotal} events`;
|
|
1852
|
+
const copiesPart = droppedCopies > 0 ? ` (${droppedCopies} without the records ${droppedCopies === 1 ? "its transcript" : "their transcripts"} copied from an older one)` : "";
|
|
1652
1853
|
if (isDry) {
|
|
1653
1854
|
const parts = [];
|
|
1654
1855
|
if (results.length > 0) parts.push(`import ${results.length} session(s) (${eventsPart})`);
|
|
1655
|
-
if (reimported > 0) parts.push(`re-import ${reimported} changed session(s)`);
|
|
1856
|
+
if (reimported > 0) parts.push(`re-import ${reimported} changed session(s)${copiesPart}`);
|
|
1656
1857
|
const head = parts.length > 0 ? `Dry run: would ${parts.join(", ")}` : "Dry run: no changes";
|
|
1657
1858
|
console.log(`${head}${skipSuffix}`);
|
|
1658
1859
|
return;
|
|
@@ -1670,7 +1871,7 @@ function printImportResult(options, results, counts) {
|
|
|
1670
1871
|
}
|
|
1671
1872
|
if (reimported > 0) {
|
|
1672
1873
|
segments.push(
|
|
1673
|
-
`${results.length > 0 ? "re-imported" : "Re-imported"} ${reimported} changed session(s)`
|
|
1874
|
+
`${results.length > 0 ? "re-imported" : "Re-imported"} ${reimported} changed session(s)${copiesPart}`
|
|
1674
1875
|
);
|
|
1675
1876
|
}
|
|
1676
1877
|
console.log(`${segments.join(", ")}${skipSuffix}`);
|
|
@@ -1756,6 +1957,7 @@ async function runImport(adapter, fn) {
|
|
|
1756
1957
|
importedCount: readCount(json.imported_count),
|
|
1757
1958
|
replacedCount: readCount(json.replaced_count),
|
|
1758
1959
|
reimportedCount: readCount(json.reimported_count),
|
|
1960
|
+
droppedCopiesCount: readCount(json.dropped_copies_count),
|
|
1759
1961
|
skippedNoAction: readCount(json.skipped_no_action),
|
|
1760
1962
|
skippedAlreadyImported: readCount(json.skipped_already_imported),
|
|
1761
1963
|
skippedLegacyUntracked: readCount(json.skipped_legacy_untracked),
|
|
@@ -2145,9 +2347,10 @@ async function doRunBoardMeasure(options, ctx) {
|
|
|
2145
2347
|
throw new Error("--model must name a model.");
|
|
2146
2348
|
}
|
|
2147
2349
|
const loaded = await loadBoard(options, ctx, "board measure");
|
|
2148
|
-
const
|
|
2350
|
+
const previous = await previousOf(loaded);
|
|
2351
|
+
const measurement = await measureLoaded(loaded, ctx, options.model, previous);
|
|
2149
2352
|
if (options.json === true) console.log(JSON.stringify(measurement, null, 2));
|
|
2150
|
-
else printMeasurementText(measurement, hasRecords(loaded));
|
|
2353
|
+
else printMeasurementText(measurement, hasRecords(loaded), declarationChanges(loaded, previous));
|
|
2151
2354
|
if (!measurement.complete) process.exitCode = 1;
|
|
2152
2355
|
return measurement;
|
|
2153
2356
|
}
|
|
@@ -2162,16 +2365,17 @@ async function runBoardRecord(options, ctx = {}) {
|
|
|
2162
2365
|
var RecordRefusal = class extends Error {
|
|
2163
2366
|
};
|
|
2164
2367
|
async function doRunBoardRecord(options, ctx) {
|
|
2165
|
-
let
|
|
2368
|
+
let checked;
|
|
2166
2369
|
try {
|
|
2167
|
-
|
|
2370
|
+
checked = await checkAndRecord(options, ctx);
|
|
2168
2371
|
} catch (error) {
|
|
2169
2372
|
if (error instanceof RecordRefusal || !(error instanceof Error)) throw error;
|
|
2170
2373
|
throw new RecordRefusal(`${error.message}
|
|
2171
2374
|
Nothing was written.`, { cause: error.cause });
|
|
2172
2375
|
}
|
|
2376
|
+
const { result, declared } = checked;
|
|
2173
2377
|
if (options.json === true) console.log(JSON.stringify(result, null, 2));
|
|
2174
|
-
else printRecordText(result);
|
|
2378
|
+
else printRecordText(result, declared ?? null);
|
|
2175
2379
|
return result;
|
|
2176
2380
|
}
|
|
2177
2381
|
async function checkAndRecord(options, ctx) {
|
|
@@ -2197,9 +2401,9 @@ async function checkAndRecord(options, ctx) {
|
|
|
2197
2401
|
${parsed.errors.map((e) => ` - ${displayPath3(e)}`).join("\n")}`
|
|
2198
2402
|
);
|
|
2199
2403
|
}
|
|
2200
|
-
const recordedAt = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
|
|
2201
2404
|
const previous = await previousOf(loaded);
|
|
2202
2405
|
const measurement = await measureLoaded(loaded, ctx, parsed.input.judged_by.model, previous);
|
|
2406
|
+
const recordedAt = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
|
|
2203
2407
|
if (measurement.digest !== parsed.input.measure_digest) {
|
|
2204
2408
|
throw new RecordRefusal(
|
|
2205
2409
|
`measure_digest is ${parsed.input.measure_digest}, but the board measures ${measurement.digest} now: something it measures or board.yaml changed since the judgement, or basou was rebuilt or upgraded since that measurement. Measure again and judge that; nothing was written.`
|
|
@@ -2212,6 +2416,7 @@ ${parsed.errors.map((e) => ` - ${displayPath3(e)}`).join("\n")}`
|
|
|
2212
2416
|
recordedAt,
|
|
2213
2417
|
recordedWith: { basou: BASOU_CLI_VERSION, build: BASOU_BUILD?.commit ?? null }
|
|
2214
2418
|
});
|
|
2419
|
+
const declared = options.json === true ? void 0 : declarationChanges(loaded, previous);
|
|
2215
2420
|
let written = null;
|
|
2216
2421
|
if (options.dryRun !== true) {
|
|
2217
2422
|
let name;
|
|
@@ -2238,14 +2443,22 @@ ${parsed.errors.map((e) => ` - ${displayPath3(e)}`).join("\n")}`
|
|
|
2238
2443
|
};
|
|
2239
2444
|
}
|
|
2240
2445
|
return {
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2446
|
+
result: {
|
|
2447
|
+
record: written,
|
|
2448
|
+
dry_run: options.dryRun === true,
|
|
2449
|
+
complete: measurement.complete,
|
|
2450
|
+
not_found: measurement.not_found,
|
|
2451
|
+
order_anomalies: record.order_anomalies,
|
|
2452
|
+
diff
|
|
2453
|
+
},
|
|
2454
|
+
...declared === void 0 ? {} : { declared }
|
|
2247
2455
|
};
|
|
2248
2456
|
}
|
|
2457
|
+
function declarationChanges(loaded, previous) {
|
|
2458
|
+
const { last } = previous;
|
|
2459
|
+
if (last.status !== "found") return null;
|
|
2460
|
+
return diffDeclarations(last.record.declaration, boardDeclarationSnapshot(loaded.declaration));
|
|
2461
|
+
}
|
|
2249
2462
|
var INPUT_KEYS = /* @__PURE__ */ new Set([
|
|
2250
2463
|
"measure_digest",
|
|
2251
2464
|
"observed",
|
|
@@ -2395,7 +2608,7 @@ async function checkRecordsPlace(loaded, recordsDir, shown, notPrivate) {
|
|
|
2395
2608
|
}
|
|
2396
2609
|
}
|
|
2397
2610
|
}
|
|
2398
|
-
function printRecordText(result) {
|
|
2611
|
+
function printRecordText(result, declared) {
|
|
2399
2612
|
const lines = [
|
|
2400
2613
|
result.record === null ? "Dry run: the record checked out (nothing was written)." : `Recorded ${displayPath3(result.record)}`
|
|
2401
2614
|
];
|
|
@@ -2416,7 +2629,11 @@ function printRecordText(result) {
|
|
|
2416
2629
|
` observed ${displayPath3(o.name)}: ${observation(o.before)} -> ${observation(o.after)}`
|
|
2417
2630
|
);
|
|
2418
2631
|
}
|
|
2419
|
-
|
|
2632
|
+
const declaration = declarationLines(declared);
|
|
2633
|
+
lines.push(
|
|
2634
|
+
...diffLines(result.diff.measure, cells.length + observed.length + declaration.length > 0),
|
|
2635
|
+
...declaration
|
|
2636
|
+
);
|
|
2420
2637
|
}
|
|
2421
2638
|
if (!result.complete) {
|
|
2422
2639
|
lines.push(`The measurement recorded is not complete (${result.not_found.length}):`);
|
|
@@ -2602,6 +2819,16 @@ function diffLines(diff, more) {
|
|
|
2602
2819
|
if (lines.length === 0 && !more) lines.push(" nothing moved");
|
|
2603
2820
|
return lines;
|
|
2604
2821
|
}
|
|
2822
|
+
function declarationLines(changes) {
|
|
2823
|
+
if (changes === null) {
|
|
2824
|
+
return [
|
|
2825
|
+
" declaration: not compared (the previous record holds no declaration basou can compare with)"
|
|
2826
|
+
];
|
|
2827
|
+
}
|
|
2828
|
+
return changes.map(
|
|
2829
|
+
(c) => ` declaration ${displayPath3(c.at)}: ${c.change === "reordered" ? "the order changed" : c.change}`
|
|
2830
|
+
);
|
|
2831
|
+
}
|
|
2605
2832
|
function shownValue(value) {
|
|
2606
2833
|
if (value === null) return "not measured";
|
|
2607
2834
|
if (typeof value === "number") return String(Math.round(value * 1e4) / 1e4);
|
|
@@ -2733,12 +2960,18 @@ function axisLines(m) {
|
|
|
2733
2960
|
for (const u of a.unjudged) lines.push(` (${u.trigger}) not judged: ${u.why}`);
|
|
2734
2961
|
return lines;
|
|
2735
2962
|
}
|
|
2736
|
-
function printMeasurementText(m, hasRecords2) {
|
|
2963
|
+
function printMeasurementText(m, hasRecords2, declared) {
|
|
2737
2964
|
const lines = [displayPath3(m.title)];
|
|
2738
2965
|
const build = m.measured_with.build === null ? "" : ` (build ${m.measured_with.build})`;
|
|
2739
2966
|
lines.push(`Measured ${m.measured_at} with basou ${m.measured_with.basou}${build}`);
|
|
2740
2967
|
if (m.diff !== null) {
|
|
2741
|
-
|
|
2968
|
+
const declaration = declarationLines(declared);
|
|
2969
|
+
lines.push(
|
|
2970
|
+
"",
|
|
2971
|
+
`Since the previous record ${m.diff.against}:`,
|
|
2972
|
+
...diffLines(m.diff, declaration.length > 0),
|
|
2973
|
+
...declaration
|
|
2974
|
+
);
|
|
2742
2975
|
} else if (m.not_found.some((n) => n.at === "diff")) {
|
|
2743
2976
|
lines.push("", "The previous record could not be read (see Not measured).");
|
|
2744
2977
|
} else {
|
|
@@ -2753,7 +2986,8 @@ function printMeasurementText(m, hasRecords2) {
|
|
|
2753
2986
|
const width = Math.max(...measures.map(([id]) => id.length));
|
|
2754
2987
|
lines.push("", "Measures:");
|
|
2755
2988
|
for (const [id, measure] of measures) {
|
|
2756
|
-
const
|
|
2989
|
+
const counted = typeof measure.value === "number" || Array.isArray(measure.value);
|
|
2990
|
+
const unit = counted ? ` ${displayPath3(measure.unit)}` : "";
|
|
2757
2991
|
const listed = Array.isArray(measure.value) ? " (paths in --json)" : "";
|
|
2758
2992
|
const lane = measure.lane === void 0 ? "" : ` [${measure.lane.join(", ")}]`;
|
|
2759
2993
|
lines.push(` ${id.padEnd(width)} ${shownValue(measure.value)}${unit}${listed}${lane}`);
|
|
@@ -9996,7 +10230,10 @@ function describeImport(outcome) {
|
|
|
9996
10230
|
}
|
|
9997
10231
|
const verb = outcome.dryRun ? "would import" : "imported";
|
|
9998
10232
|
const parts = [`${outcome.importedCount} session(s)`, `${outcome.eventTotal} events`];
|
|
9999
|
-
if (outcome.reimportedCount > 0)
|
|
10233
|
+
if (outcome.reimportedCount > 0) {
|
|
10234
|
+
const copies = outcome.droppedCopiesCount > 0 ? ` (${outcome.droppedCopiesCount} without records copied from an older transcript)` : "";
|
|
10235
|
+
parts.push(`${outcome.reimportedCount} re-imported${copies}`);
|
|
10236
|
+
}
|
|
10000
10237
|
if (outcome.replacedCount > 0) parts.push(`${outcome.replacedCount} replaced`);
|
|
10001
10238
|
if (outcome.skippedAlreadyImported > 0)
|
|
10002
10239
|
parts.push(`${outcome.skippedAlreadyImported} already imported`);
|
|
@@ -13754,6 +13991,11 @@ var BOARD_HTML = `<!doctype html>
|
|
|
13754
13991
|
.chip.blocked { background: var(--chip-blocked); }
|
|
13755
13992
|
.chip.unverified { background: var(--chip-unverified); }
|
|
13756
13993
|
.muted { color: var(--fg-2); }
|
|
13994
|
+
.sub { font-size: 12px; color: var(--fg-2); overflow-wrap: anywhere; }
|
|
13995
|
+
ul.sub { margin: 0; padding-left: 18px; }
|
|
13996
|
+
td.wrap { overflow-wrap: anywhere; min-width: 10em; }
|
|
13997
|
+
tr.notes td { padding-top: 0; }
|
|
13998
|
+
.chip.changed { margin-left: 0; margin-right: 6px; background: var(--chip-unverified); }
|
|
13757
13999
|
.bar { height: 10px; background: var(--fill); border-radius: 5px; min-width: 120px; position: relative; overflow: hidden; }
|
|
13758
14000
|
.bar span { position: absolute; left: 0; top: 0; bottom: 0; background: var(--s1); }
|
|
13759
14001
|
figure { margin: 16px 0; }
|
|
@@ -13883,6 +14125,60 @@ var BOARD_HTML = `<!doctype html>
|
|
|
13883
14125
|
return JSON.stringify(k) + ':' + v;
|
|
13884
14126
|
}).join(',') + '}';
|
|
13885
14127
|
}
|
|
14128
|
+
// The parts of a value drawn on one line, and what an http value says of each word.
|
|
14129
|
+
var DOT = ' ' + String.fromCharCode(183) + ' ';
|
|
14130
|
+
var FOUND = String.fromCharCode(10003);
|
|
14131
|
+
var NOT_FOUND = String.fromCharCode(10007);
|
|
14132
|
+
// An http value read as its parts: the status, the redirects, where it
|
|
14133
|
+
// landed, and what it read off the body (when the status is 2xx) or that it
|
|
14134
|
+
// did not. Numbers and strings are drawn as they are.
|
|
14135
|
+
function httpText(h) {
|
|
14136
|
+
var parts = [String(h.status), fill(S.observed.redirects, { n: h.redirects }), h.final_url];
|
|
14137
|
+
if (h.body === null) parts.push(S.observed.bodyNotRead);
|
|
14138
|
+
else if (h.body) {
|
|
14139
|
+
if (h.body.words) {
|
|
14140
|
+
parts.push(h.body.words.map(function (w) { return w.word + ' ' + (w.found ? FOUND : NOT_FOUND); }).join(' '));
|
|
14141
|
+
}
|
|
14142
|
+
if (h.body.count !== undefined) parts.push(fill(S.observed.count, { n: h.body.count }));
|
|
14143
|
+
if (h.body.capture !== undefined) {
|
|
14144
|
+
parts.push(h.body.capture === null ? S.observed.captureNone : fill(S.observed.capture, { value: JSON.stringify(h.body.capture) }));
|
|
14145
|
+
}
|
|
14146
|
+
}
|
|
14147
|
+
return parts.join(DOT);
|
|
14148
|
+
}
|
|
14149
|
+
// A value of an observation as the page draws it: an http value in its
|
|
14150
|
+
// shape read as its parts, one with words beside what it says of each, and
|
|
14151
|
+
// any other as it is (a string) or as JSON. A test lifts this function.
|
|
14152
|
+
function observedText(value, words, http) {
|
|
14153
|
+
if (http) return httpText(http);
|
|
14154
|
+
if (words) return withWords(value, words);
|
|
14155
|
+
return shown(value);
|
|
14156
|
+
}
|
|
14157
|
+
// The value of an observation that moved, and what the previous record
|
|
14158
|
+
// held: a value whose type alone moved ("12" and 12) is drawn as JSON, so
|
|
14159
|
+
// that the two do not read the same. A test lifts this function.
|
|
14160
|
+
function movedText(o) {
|
|
14161
|
+
var m = o.moved;
|
|
14162
|
+
var now = observedText(o.value, o.words, o.http);
|
|
14163
|
+
if (m.status === 'none') return { now: now, then: S.observed.beforeNone };
|
|
14164
|
+
if (m.value === null) {
|
|
14165
|
+
return { now: now, then: S.observed.beforeNotObserved + (m.error ? ' (' + m.error + ')' : '') };
|
|
14166
|
+
}
|
|
14167
|
+
var then = observedText(m.value, m.words, m.http);
|
|
14168
|
+
if ((typeof m.value === 'string') !== (typeof o.value === 'string')) {
|
|
14169
|
+
then = JSON.stringify(m.value);
|
|
14170
|
+
if (then === now) now = JSON.stringify(o.value);
|
|
14171
|
+
}
|
|
14172
|
+
return { now: now, then: fill(S.observed.before, { value: then }) + (m.error ? ' (' + m.error + ')' : '') };
|
|
14173
|
+
}
|
|
14174
|
+
// A measure's value: the paths of a file_paths by how many, a number with
|
|
14175
|
+
// its unit, and a string (a file's name, a capture), which is no amount,
|
|
14176
|
+
// without it.
|
|
14177
|
+
function measureText(m) {
|
|
14178
|
+
if (Array.isArray(m.value)) return num(m.value.length) + ' ' + m.unit;
|
|
14179
|
+
return num(m.value) + (typeof m.value === 'number' ? ' ' + m.unit : '');
|
|
14180
|
+
}
|
|
14181
|
+
function signed(n) { return (n > 0 ? '+' : '') + num(n); }
|
|
13886
14182
|
function stateName(state) { return S.states[state] || state; }
|
|
13887
14183
|
function setStatus(message, isErr) {
|
|
13888
14184
|
var s = $('status');
|
|
@@ -13967,17 +14263,36 @@ var BOARD_HTML = `<!doctype html>
|
|
|
13967
14263
|
if (s.observed.length === 0) {
|
|
13968
14264
|
observed = el('p', { class: 'muted', text: S.observed.none });
|
|
13969
14265
|
} else {
|
|
13970
|
-
var rows =
|
|
14266
|
+
var rows = [];
|
|
14267
|
+
s.observed.forEach(function (o) {
|
|
14268
|
+
var cell = el('td', { class: 'wrap' });
|
|
13971
14269
|
var value;
|
|
13972
|
-
if (o.value !== null
|
|
13973
|
-
|
|
13974
|
-
|
|
13975
|
-
|
|
13976
|
-
|
|
13977
|
-
|
|
13978
|
-
|
|
13979
|
-
|
|
13980
|
-
|
|
14270
|
+
if (o.value !== null && o.moved) {
|
|
14271
|
+
var pair = movedText(o);
|
|
14272
|
+
cell.appendChild(el('span', { class: 'chip changed', text: S.observed.changed }));
|
|
14273
|
+
value = pair.now;
|
|
14274
|
+
cell.appendChild(el('span', { text: value + (o.error ? ' (' + o.error + ')' : '') }));
|
|
14275
|
+
cell.appendChild(el('div', { class: 'sub', text: pair.then }));
|
|
14276
|
+
} else {
|
|
14277
|
+
if (o.value !== null) value = observedText(o.value, o.words, o.http);
|
|
14278
|
+
else if (o.previous && o.previous.status === 'value') value = fill(S.observed.missing, { value: shown(o.previous.value) });
|
|
14279
|
+
else if (o.previous && o.previous.status === 'unreadable') value = S.observed.missingPreviousUnreadable;
|
|
14280
|
+
else value = S.observed.missingNoPrevious;
|
|
14281
|
+
if (o.error) value += ' (' + o.error + ')';
|
|
14282
|
+
cell.appendChild(el('span', { text: value }));
|
|
14283
|
+
}
|
|
14284
|
+
// What the record's declaration says it is and looks at, under its name.
|
|
14285
|
+
var what = o.kind ? [o.kind].concat(o.looks_at || []).join(DOT) : null;
|
|
14286
|
+
rows.push(el('tr', null, [
|
|
14287
|
+
el('td', null, [el('div', { text: o.name }), what === null ? null : el('div', { class: 'sub', text: what })]),
|
|
14288
|
+
cell,
|
|
14289
|
+
el('td', { text: o.observed_at }), el('td', { class: 'wrap', text: o.source })
|
|
14290
|
+
]));
|
|
14291
|
+
if (o.notes && o.notes.length > 0) {
|
|
14292
|
+
rows.push(el('tr', { class: 'notes' }, [el('td', { colspan: '4' }, [
|
|
14293
|
+
el('ul', { class: 'sub' }, o.notes.map(function (n) { return el('li', null, [prose(n, 'span')]); }))
|
|
14294
|
+
])]));
|
|
14295
|
+
}
|
|
13981
14296
|
});
|
|
13982
14297
|
observed = el('div', { class: 'scroll' }, [el('table', { class: 'grid' }, [
|
|
13983
14298
|
el('tr', null, [
|
|
@@ -13987,12 +14302,16 @@ var BOARD_HTML = `<!doctype html>
|
|
|
13987
14302
|
].concat(rows))]);
|
|
13988
14303
|
}
|
|
13989
14304
|
var observedHeading = el('h3', { text: S.observed.heading }, [reported()]);
|
|
13990
|
-
|
|
14305
|
+
var children = [
|
|
13991
14306
|
el('div', null, [prose(s.text), reported()]),
|
|
13992
14307
|
tiles,
|
|
13993
14308
|
observedHeading,
|
|
13994
14309
|
observed
|
|
13995
|
-
]
|
|
14310
|
+
];
|
|
14311
|
+
if (s.previous_record === 'unreadable' && s.observed.length > 0) {
|
|
14312
|
+
children.push(el('p', { class: 'muted', text: S.observed.previousUnreadable }));
|
|
14313
|
+
}
|
|
14314
|
+
return section(S.sections.summary, false, children);
|
|
13996
14315
|
}
|
|
13997
14316
|
|
|
13998
14317
|
var SVGNS = 'http://www.w3.org/2000/svg';
|
|
@@ -14323,13 +14642,35 @@ var BOARD_HTML = `<!doctype html>
|
|
|
14323
14642
|
el('ul', null, m.value.map(function (p) { return el('li', { text: p }); }))
|
|
14324
14643
|
])]);
|
|
14325
14644
|
}
|
|
14326
|
-
|
|
14645
|
+
// A string (a file's name, a capture) is no amount: it takes no unit.
|
|
14646
|
+
return el('li', { text: m.id + ': ' + num(m.value) + (typeof m.value === 'number' ? ' ' + m.unit : '') });
|
|
14327
14647
|
})));
|
|
14328
14648
|
}
|
|
14329
14649
|
return el('div', { class: 'lane', 'data-lane': lane.id }, body);
|
|
14330
14650
|
}));
|
|
14331
14651
|
}
|
|
14332
14652
|
|
|
14653
|
+
// Every measure of the record, with the lanes it is drawn in and how much a
|
|
14654
|
+
// number moved since the previous record.
|
|
14655
|
+
function measures(b) {
|
|
14656
|
+
if (b.measures.length === 0) {
|
|
14657
|
+
return section(S.sections.measures, false, [el('p', { class: 'muted', text: S.measures.none })]);
|
|
14658
|
+
}
|
|
14659
|
+
var head = el('tr', null, [
|
|
14660
|
+
el('th', { text: S.measures.id }), el('th', { text: S.measures.value }),
|
|
14661
|
+
el('th', { text: S.measures.lanes }), el('th', { class: 'n', text: S.measures.delta })
|
|
14662
|
+
]);
|
|
14663
|
+
var rows = b.measures.map(function (m) {
|
|
14664
|
+
return el('tr', { 'data-measure': m.id }, [
|
|
14665
|
+
el('td', null, [el('code', { text: m.id })]),
|
|
14666
|
+
el('td', { class: 'wrap', text: measureText(m) }),
|
|
14667
|
+
el('td', { text: m.lanes.length === 0 ? '-' : m.lanes.join(', ') }),
|
|
14668
|
+
el('td', { class: 'n', text: m.delta === null || m.delta === undefined ? '' : signed(m.delta) })
|
|
14669
|
+
]);
|
|
14670
|
+
});
|
|
14671
|
+
return section(S.sections.measures, false, [el('div', { class: 'scroll' }, [el('table', { class: 'grid' }, [head].concat(rows))])]);
|
|
14672
|
+
}
|
|
14673
|
+
|
|
14333
14674
|
function composition(b) {
|
|
14334
14675
|
if (b.composition.length === 0) {
|
|
14335
14676
|
return section(S.sections.composition, false, [el('p', { class: 'muted', text: S.composition.none })]);
|
|
@@ -14417,7 +14758,8 @@ var BOARD_HTML = `<!doctype html>
|
|
|
14417
14758
|
var b = page.board;
|
|
14418
14759
|
// The board's own title, so that boards open in other tabs can be told apart.
|
|
14419
14760
|
document.title = b.heading.title;
|
|
14420
|
-
[heading(b), summary(b), effort(b, false), matrix(b), lanes(b),
|
|
14761
|
+
[heading(b), summary(b), effort(b, false), matrix(b), lanes(b), measures(b), composition(b), turns(b),
|
|
14762
|
+
recordRepos(b), recordComponents(b), footnotes(b)]
|
|
14421
14763
|
.forEach(function (node) { root.appendChild(node); });
|
|
14422
14764
|
}
|
|
14423
14765
|
|
|
@@ -14443,12 +14785,14 @@ var BOARD_HTML = `<!doctype html>
|
|
|
14443
14785
|
function missingIn(l) {
|
|
14444
14786
|
return function (at) { return l.footnotes.not_found.some(function (n) { return n.at === at; }); };
|
|
14445
14787
|
}
|
|
14446
|
-
|
|
14447
|
-
|
|
14788
|
+
// The repositories as a table. older: a record of version 1 or 2, which
|
|
14789
|
+
// measured no base, ahead or fetch time (drawn as '-') and held the
|
|
14790
|
+
// commits behind as behind_main.
|
|
14791
|
+
function reposTable(list, missing, older) {
|
|
14448
14792
|
var labels = [S.live.repo, S.live.branch, S.live.head, S.live.lastCommit, S.live.commits, S.live.uncommitted, S.live.base, S.live.behind, S.live.ahead, S.live.fetchedAt];
|
|
14449
14793
|
var numeric = [4, 5, 7, 8];
|
|
14450
14794
|
var head = el('tr', null, labels.map(function (label, i) { return el('th', { class: numeric.indexOf(i) !== -1 ? 'n' : '', text: label }); }));
|
|
14451
|
-
var rows =
|
|
14795
|
+
var rows = list.map(function (r) {
|
|
14452
14796
|
var at = 'repos[' + r.path + ']';
|
|
14453
14797
|
// A null the measurement says it could not measure, or one that means
|
|
14454
14798
|
// so (a detached HEAD, no commit yet, no base, no fetch of it on
|
|
@@ -14457,6 +14801,7 @@ var BOARD_HTML = `<!doctype html>
|
|
|
14457
14801
|
if (v !== null) return show(v);
|
|
14458
14802
|
return missing(at) || missing(at + '.' + field) ? S.tiles.notMeasured : '-';
|
|
14459
14803
|
};
|
|
14804
|
+
var unmeasured = function (field, v, show) { return older ? '-' : value(field, v, show); };
|
|
14460
14805
|
return el('tr', null, [
|
|
14461
14806
|
el('td', { text: r.name === null ? r.path : r.name }),
|
|
14462
14807
|
el('td', { text: value('branch', r.branch, String) }),
|
|
@@ -14464,13 +14809,59 @@ var BOARD_HTML = `<!doctype html>
|
|
|
14464
14809
|
el('td', { text: value('last_commit', r.last_commit, when) }),
|
|
14465
14810
|
el('td', { class: 'n', text: num(r.commits) }),
|
|
14466
14811
|
el('td', { class: 'n', text: num(r.uncommitted) }),
|
|
14467
|
-
el('td', { text:
|
|
14468
|
-
el('td', { class: 'n', text: value('behind', r.behind, num) }),
|
|
14469
|
-
el('td', { class: 'n', text:
|
|
14470
|
-
el('td', { text:
|
|
14812
|
+
el('td', { text: unmeasured('base', r.base, String) }),
|
|
14813
|
+
el('td', { class: 'n', text: value(older ? 'behind_main' : 'behind', r.behind, num) }),
|
|
14814
|
+
el('td', { class: 'n', text: unmeasured('ahead', r.ahead, num) }),
|
|
14815
|
+
el('td', { text: unmeasured('fetched_at', r.fetched_at, when) })
|
|
14816
|
+
]);
|
|
14817
|
+
});
|
|
14818
|
+
return el('div', { class: 'scroll' }, [el('table', { class: 'grid' }, [head].concat(rows))]);
|
|
14819
|
+
}
|
|
14820
|
+
function liveRepos(l) {
|
|
14821
|
+
return section(S.live.repos, false, [reposTable(l.repos, missingIn(l), false)]);
|
|
14822
|
+
}
|
|
14823
|
+
// The repositories as the record measured them, by its version.
|
|
14824
|
+
function recordRepos(b) {
|
|
14825
|
+
var r = b.repos;
|
|
14826
|
+
if (!r) return section(S.live.repos, false, [el('p', { class: 'muted', text: S.record.reposUnread })]);
|
|
14827
|
+
var children = [reposTable(r.list, missingIn(b), !r.base_measured)];
|
|
14828
|
+
if (!r.base_measured) children.push(el('p', { class: 'muted', text: fill(S.record.notMeasuredByVersion, { version: r.record_version }) }));
|
|
14829
|
+
return section(S.live.repos, false, children);
|
|
14830
|
+
}
|
|
14831
|
+
// The components the record found, by the lanes its registry counts them
|
|
14832
|
+
// in, then those in no lane, those not registered, and those gone.
|
|
14833
|
+
function recordComponents(b) {
|
|
14834
|
+
var c = b.components;
|
|
14835
|
+
if (!c) return section(S.live.components, false, [el('p', { class: 'muted', text: S.record.componentsUnread })]);
|
|
14836
|
+
if (!c.measured) {
|
|
14837
|
+
return section(S.live.components, false, [
|
|
14838
|
+
el('p', { class: 'muted', text: S.record.componentsNotMeasured }),
|
|
14839
|
+
el('ul', null, c.reasons.map(function (reason) { return el('li', { text: reason }); }))
|
|
14471
14840
|
]);
|
|
14841
|
+
}
|
|
14842
|
+
var item = function (x) {
|
|
14843
|
+
var kinds = x.kinds === null ? S.record.notFound : x.kinds.join(', ');
|
|
14844
|
+
var parts = [el('code', { text: x.key }), el('span', { class: 'muted', text: ' ' + kinds })];
|
|
14845
|
+
if (x.kind_changed) {
|
|
14846
|
+
parts.push(el('span', { class: 'chip changed', text: fill(S.record.kindChanged, { before: x.kind_changed.before.join(', ') }) }));
|
|
14847
|
+
}
|
|
14848
|
+
if (x.note) parts.push(el('div', { class: 'sub', text: x.note }));
|
|
14849
|
+
return el('li', { 'data-component': x.key }, parts);
|
|
14850
|
+
};
|
|
14851
|
+
var group = function (title, list, empty) {
|
|
14852
|
+
return [el('h3', { text: title }), list.length === 0 ? el('p', { class: 'muted', text: empty }) : el('ul', null, list.map(item))];
|
|
14853
|
+
};
|
|
14854
|
+
var children = [];
|
|
14855
|
+
c.lanes.forEach(function (lane) {
|
|
14856
|
+
children = children.concat(group(lane.name, lane.components, S.record.noComponentInLane));
|
|
14472
14857
|
});
|
|
14473
|
-
|
|
14858
|
+
children = children.concat(group(S.record.uncounted, c.uncounted, S.record.none));
|
|
14859
|
+
children = children.concat(group(S.record.unacknowledged, c.unacknowledged, S.record.none));
|
|
14860
|
+
children.push(el('h3', { text: S.record.gone }));
|
|
14861
|
+
children.push(c.gone === null ? el('p', { class: 'muted', text: S.record.goneUnknown })
|
|
14862
|
+
: c.gone.length === 0 ? el('p', { class: 'muted', text: S.record.none })
|
|
14863
|
+
: el('ul', null, c.gone.map(function (key) { return el('li', null, [el('code', { text: key })]); })));
|
|
14864
|
+
return section(S.live.components, false, children);
|
|
14474
14865
|
}
|
|
14475
14866
|
function liveTrail(l) {
|
|
14476
14867
|
var t = l.trail;
|
|
@@ -16131,7 +16522,7 @@ function installExperimentalNotice(program2, names = EXPERIMENTAL_COMMANDS, writ
|
|
|
16131
16522
|
function readBuildStamp() {
|
|
16132
16523
|
if (false) return void 0;
|
|
16133
16524
|
try {
|
|
16134
|
-
return JSON.parse('{"version":"0.
|
|
16525
|
+
return JSON.parse('{"version":"0.72.0","commit":"f92df50","committedAt":"2026-10-20T12:15:21+09:00"}');
|
|
16135
16526
|
} catch {
|
|
16136
16527
|
return void 0;
|
|
16137
16528
|
}
|