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