wiki-graph-sdk 0.7.1 → 0.8.1

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.
Files changed (58) hide show
  1. package/README.md +22 -1
  2. package/dist/archives.cjs +494 -656
  3. package/dist/archives.cjs.map +1 -1
  4. package/dist/archives.d.ts +48 -80
  5. package/dist/archives.js +431 -585
  6. package/dist/archives.js.map +1 -1
  7. package/dist/conversions.cjs +183 -33
  8. package/dist/conversions.cjs.map +1 -1
  9. package/dist/conversions.d.ts +8 -1
  10. package/dist/conversions.js +188 -29
  11. package/dist/conversions.js.map +1 -1
  12. package/dist/default-worker.cjs +322 -62
  13. package/dist/default-worker.cjs.map +1 -1
  14. package/dist/default-worker.d.ts +1 -0
  15. package/dist/default-worker.js +336 -60
  16. package/dist/default-worker.js.map +1 -1
  17. package/dist/embedding.cjs +2 -1
  18. package/dist/embedding.cjs.map +1 -1
  19. package/dist/embedding.d.ts +4 -4
  20. package/dist/embedding.js +2 -1
  21. package/dist/embedding.js.map +1 -1
  22. package/dist/index.cjs +1295 -949
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.ts +58 -24
  25. package/dist/index.js +1164 -805
  26. package/dist/index.js.map +1 -1
  27. package/dist/{jobs-Dd5PMSId.d.ts → jobs-CRK-DZ40.d.ts} +27 -14
  28. package/dist/local-config.cjs +2 -1
  29. package/dist/local-config.cjs.map +1 -1
  30. package/dist/local-config.js +2 -1
  31. package/dist/local-config.js.map +1 -1
  32. package/dist/maintenance.cjs +140 -30
  33. package/dist/maintenance.cjs.map +1 -1
  34. package/dist/maintenance.d.ts +5 -1
  35. package/dist/maintenance.js +145 -26
  36. package/dist/maintenance.js.map +1 -1
  37. package/dist/runtime-config.cjs +4 -3
  38. package/dist/runtime-config.cjs.map +1 -1
  39. package/dist/runtime-config.d.ts +4 -1
  40. package/dist/runtime-config.js +4 -3
  41. package/dist/runtime-config.js.map +1 -1
  42. package/dist/stage-BnWI3Rqt.d.ts +33 -0
  43. package/dist/stage.cjs +22 -12
  44. package/dist/stage.cjs.map +1 -1
  45. package/dist/stage.d.ts +2 -14
  46. package/dist/stage.js +22 -12
  47. package/dist/stage.js.map +1 -1
  48. package/dist/target-CZ7elwmj.d.ts +14 -0
  49. package/dist/wikispine.cjs +14 -10
  50. package/dist/wikispine.cjs.map +1 -1
  51. package/dist/wikispine.js +14 -10
  52. package/dist/wikispine.js.map +1 -1
  53. package/dist/worker.cjs +295 -25
  54. package/dist/worker.cjs.map +1 -1
  55. package/dist/worker.d.ts +28 -5
  56. package/dist/worker.js +313 -28
  57. package/dist/worker.js.map +1 -1
  58. package/package.json +2 -2
package/dist/archives.js CHANGED
@@ -4,20 +4,20 @@ globalThis.__WIKIGRAPH_DATA_DIR__ ??= __WIKIGRAPH_RESOLVE__(__WIKIGRAPH_FILE_URL
4
4
 
5
5
  // src/archive/archive.ts
6
6
  import { randomUUID as randomUUID2 } from "crypto";
7
- import { link, mkdtemp, rename as rename2, rm as rm3, stat as stat2 } from "fs/promises";
7
+ import { link, mkdtemp, rename as rename2, rm as rm3, stat as stat3 } from "fs/promises";
8
8
  import { tmpdir } from "os";
9
- import { basename as basename3, dirname as dirname2, join as join3 } from "path";
9
+ import { basename as basename4, dirname as dirname3, join as join4 } from "path";
10
10
  import { Readable as Readable2 } from "stream";
11
11
  import {
12
12
  addChapter,
13
13
  applyChapterTree,
14
+ assertArchiveIndexArtifactsReady,
14
15
  assertNoActiveBuildJobConflicts,
15
16
  assertNoActiveBuildJobs,
16
17
  deleteArchiveSearchSessions,
17
18
  DirectoryDocument,
18
- finalizeWikiGraphLibraryArchiveWrite,
19
19
  findArchiveObjects,
20
- formatLocatedWikiGraphUri,
20
+ formatLocatedWikiGraphUri as formatLocatedWikiGraphUri2,
21
21
  isArchiveSearchIndexCurrent as isArchiveSearchIndexCurrent2,
22
22
  isSourceLocatorScopeUri,
23
23
  listArchiveCollection,
@@ -28,23 +28,19 @@ import {
28
28
  listRelatedArchiveObjects,
29
29
  moveChapter,
30
30
  packArchiveContext,
31
- parseLocatedWikiGraphUri,
32
- parseWikiGraphLibraryUri,
31
+ parseLocatedWikiGraphUri as parseLocatedWikiGraphUri2,
33
32
  readArchivePage,
34
33
  readSearchIndexCapabilityStatus,
35
- readWikiGraphLibraryIndexState,
36
34
  rebuildArchiveSearchIndex,
37
- rebuildWikiGraphLibraryIndex,
38
35
  removeChapter,
39
36
  resetChapter,
40
37
  resolveChapterPathReadonly,
41
- resolveWikiGraphLibraryArchiveFile,
42
38
  setChapterSource,
43
39
  setChapterSummary,
44
40
  setChapterTitle,
45
41
  getChapterTree,
46
42
  WikiGraph as WikiGraph2,
47
- WikiGraphArchiveFile,
43
+ WikiGraphArchiveFile as WikiGraphArchiveFile2,
48
44
  TOC_FILE_VERSION,
49
45
  writeWikgArchive,
50
46
  ObjectMetadataKind
@@ -54,7 +50,7 @@ import {
54
50
  import { createHash } from "crypto";
55
51
  import { createReadStream } from "fs";
56
52
  import { rm as rm2 } from "fs/promises";
57
- import { basename as basename2 } from "path";
53
+ import { basename as basename3 } from "path";
58
54
  import {
59
55
  WikiGraph
60
56
  } from "wiki-graph-core";
@@ -383,42 +379,42 @@ var NodeDatabaseConnection = class {
383
379
  }
384
380
  database;
385
381
  async close() {
386
- await new Promise((resolve3, reject) => {
382
+ await new Promise((resolve4, reject) => {
387
383
  this.database.close((error) => {
388
384
  if (error) reject(error);
389
- else resolve3();
385
+ else resolve4();
390
386
  });
391
387
  });
392
388
  }
393
389
  async execute(sql) {
394
- await new Promise((resolve3, reject) => {
390
+ await new Promise((resolve4, reject) => {
395
391
  this.database.exec(sql, (error) => {
396
392
  if (error) reject(error);
397
- else resolve3();
393
+ else resolve4();
398
394
  });
399
395
  });
400
396
  }
401
397
  async queryAll(sql, params = []) {
402
- return await new Promise((resolve3, reject) => {
398
+ return await new Promise((resolve4, reject) => {
403
399
  this.database.all(sql, [...params], (error, rows) => {
404
400
  if (error) reject(error);
405
- else resolve3(rows);
401
+ else resolve4(rows);
406
402
  });
407
403
  });
408
404
  }
409
405
  async queryOne(sql, params = []) {
410
- return await new Promise((resolve3, reject) => {
406
+ return await new Promise((resolve4, reject) => {
411
407
  this.database.get(sql, [...params], (error, row) => {
412
408
  if (error) reject(error);
413
- else resolve3(row);
409
+ else resolve4(row);
414
410
  });
415
411
  });
416
412
  }
417
413
  async run(sql, params = []) {
418
- await new Promise((resolve3, reject) => {
414
+ await new Promise((resolve4, reject) => {
419
415
  this.database.run(sql, [...params], (error) => {
420
416
  if (error) reject(error);
421
- else resolve3();
417
+ else resolve4();
422
418
  });
423
419
  });
424
420
  }
@@ -432,10 +428,10 @@ async function openNativeNodeDatabase(file, flags) {
432
428
  if (path === void 0) {
433
429
  throw new TypeError("The Node database adapter requires a NodeFile");
434
430
  }
435
- return await new Promise((resolve3, reject) => {
431
+ return await new Promise((resolve4, reject) => {
436
432
  const database = new nodeSqlite3.Database(path, flags, (error) => {
437
433
  if (error) reject(error);
438
- else resolve3(database);
434
+ else resolve4(database);
439
435
  });
440
436
  });
441
437
  }
@@ -444,19 +440,19 @@ async function openNodeZip(file) {
444
440
  if (path === void 0) {
445
441
  throw new TypeError("The Node ZIP adapter requires a NodeFile");
446
442
  }
447
- const zipFile = await new Promise((resolve3, reject) => {
443
+ const zipFile = await new Promise((resolve4, reject) => {
448
444
  yauzl.open(
449
445
  path,
450
446
  { autoClose: false, lazyEntries: true },
451
447
  (error, opened) => {
452
448
  if (error || opened === void 0)
453
449
  reject(error ?? new Error("Cannot open ZIP"));
454
- else resolve3(opened);
450
+ else resolve4(opened);
455
451
  }
456
452
  );
457
453
  });
458
454
  const entries = await new Promise(
459
- (resolve3, reject) => {
455
+ (resolve4, reject) => {
460
456
  const discovered = /* @__PURE__ */ new Map();
461
457
  const rejectAndClose = (error) => {
462
458
  zipFile.close();
@@ -466,7 +462,7 @@ async function openNodeZip(file) {
466
462
  };
467
463
  const resolveEntries = () => {
468
464
  zipFile.off("error", rejectAndClose);
469
- resolve3(discovered);
465
+ resolve4(discovered);
470
466
  };
471
467
  zipFile.on("entry", (entry) => {
472
468
  if (!entry.fileName.endsWith("/")) {
@@ -576,12 +572,12 @@ async function collectDecodedNodeStreamRange(input, offset, length) {
576
572
  return Buffer.concat(chunks, collected);
577
573
  }
578
574
  async function openNodeZipEntryStream(zipFile, entry, options) {
579
- return await new Promise((resolve3, reject) => {
575
+ return await new Promise((resolve4, reject) => {
580
576
  const callback = (error, opened) => {
581
577
  if (error || opened === void 0) {
582
578
  reject(error ?? new Error(`Cannot read ZIP entry: ${entry.fileName}`));
583
579
  } else {
584
- resolve3(opened);
580
+ resolve4(opened);
585
581
  }
586
582
  };
587
583
  if (options === void 0) zipFile.openReadStream(entry, callback);
@@ -712,11 +708,11 @@ async function writeNodeStream(input, writer) {
712
708
  );
713
709
  }
714
710
  async function collectNodeStream(input) {
715
- return await new Promise((resolve3, reject) => {
711
+ return await new Promise((resolve4, reject) => {
716
712
  const chunks = [];
717
713
  input.on("data", (chunk) => chunks.push(chunk));
718
714
  input.once("error", reject);
719
- input.once("end", () => resolve3(Buffer.concat(chunks)));
715
+ input.once("end", () => resolve4(Buffer.concat(chunks)));
720
716
  });
721
717
  }
722
718
  var nodeWikiGraphPlatform = {
@@ -837,12 +833,16 @@ function getWikiGraphSDKRuntimeContext() {
837
833
  return runtimeContext.getStore() ?? {
838
834
  cwd: process.cwd(),
839
835
  env: process.env,
840
- envPolicy: "production"
836
+ envPolicy: "production",
837
+ providers: {}
841
838
  };
842
839
  }
843
840
  function getWikiGraphSDKStateDir() {
844
841
  return getWikiGraphSDKRuntimeContext().stateDir;
845
842
  }
843
+ function getWikiGraphSDKProviders() {
844
+ return getWikiGraphSDKRuntimeContext().providers ?? {};
845
+ }
846
846
 
847
847
  // src/local-config.ts
848
848
  import { join as join2 } from "path";
@@ -1060,8 +1060,8 @@ var inlineLLMConfigSchema = z.object({
1060
1060
  });
1061
1061
  async function loadWikiGraphRuntimeConfig(options) {
1062
1062
  const [embedding, localLLM, concurrent, wikimedia, wikispine] = await Promise.all([
1063
- readLocalConfigSection("embeddings"),
1064
- readLocalConfigSection("llm"),
1063
+ options?.skipEmbeddingConfig === true ? Promise.resolve({}) : readLocalConfigSection("embeddings"),
1064
+ options?.skipLLMConfig === true ? Promise.resolve({}) : readLocalConfigSection("llm"),
1065
1065
  readLocalConfigSection("concurrent"),
1066
1066
  readLocalConfigSection("wikimedia"),
1067
1067
  readLocalConfigSection("wikispine")
@@ -1257,6 +1257,14 @@ function formatError2(error) {
1257
1257
  return error instanceof Error ? error.message : String(error);
1258
1258
  }
1259
1259
 
1260
+ // src/providers.ts
1261
+ function resolveWikiGraphLLMJSON(options) {
1262
+ if (options.llm !== void 0 && options.llmJSON !== void 0) {
1263
+ throw new Error("Pass either llm or llmJSON, not both.");
1264
+ }
1265
+ return options.llm === void 0 ? options.llmJSON : JSON.stringify(options.llm);
1266
+ }
1267
+
1260
1268
  // src/runtime-path.ts
1261
1269
  import { homedir as homedir3 } from "os";
1262
1270
  import { isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
@@ -1269,6 +1277,173 @@ function resolveWikiGraphRuntimePath(path) {
1269
1277
  return isAbsolute2(path) ? path : resolve2(context.cwd, path);
1270
1278
  }
1271
1279
 
1280
+ // src/archive/target.ts
1281
+ import { realpath, stat as stat2 } from "fs/promises";
1282
+ import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute3, join as join3, relative, resolve as resolve3 } from "path";
1283
+ import {
1284
+ formatLocatedWikiGraphUri,
1285
+ formatWikiGraphLibraryUri,
1286
+ listWikiGraphLibraries,
1287
+ listWikiGraphLibraryArchives,
1288
+ parseLocatedWikiGraphUri,
1289
+ parseWikiGraphLibraryUri,
1290
+ resolveWikiGraphLibrary,
1291
+ resolveWikiGraphLibraryArchiveFile
1292
+ } from "wiki-graph-core";
1293
+ var WikiGraphArchiveOwnershipError = class extends Error {
1294
+ code = "WIKI_GRAPH_ARCHIVE_OWNERSHIP_MISMATCH";
1295
+ constructor(message) {
1296
+ super(message);
1297
+ this.name = "WikiGraphArchiveOwnershipError";
1298
+ }
1299
+ };
1300
+ async function resolveWikiGraphArchiveLocation(target) {
1301
+ if (target.kind === "standalone") {
1302
+ return await resolveStandaloneLocation(target);
1303
+ }
1304
+ return await resolveLibraryLocation(target);
1305
+ }
1306
+ async function assertStandaloneWikiGraphArchivePath(inputPath) {
1307
+ const path = resolveWikiGraphRuntimePath(inputPath);
1308
+ const candidate = await canonicalPath(path);
1309
+ const candidateIdentity = await readFileIdentity(path);
1310
+ const libraries = await listWikiGraphLibraries();
1311
+ const matches = await findMatchingMembers(
1312
+ libraries,
1313
+ candidate,
1314
+ candidateIdentity
1315
+ );
1316
+ if (matches.length > 0) {
1317
+ const match = requireUnambiguousManagedMatch(path, matches);
1318
+ throw new WikiGraphArchiveOwnershipError(
1319
+ `Standalone archive path belongs to registered library ${formatWikiGraphLibraryUri(
1320
+ match.library.isDefault ? void 0 : match.library.publicId
1321
+ )}: ${path}. Use the library UUID URI ${match.archive.uri} instead.`
1322
+ );
1323
+ }
1324
+ for (const library of libraries) {
1325
+ const rootPath = getNodeResourcePath(library.folder);
1326
+ const root = await canonicalPath(rootPath);
1327
+ if (!isWithin(root, candidate)) continue;
1328
+ throw new WikiGraphArchiveOwnershipError(
1329
+ `Standalone archive path belongs to registered library ${formatWikiGraphLibraryUri(
1330
+ library.isDefault ? void 0 : library.publicId
1331
+ )}: ${path}. Scan the library, then address the archive by its library UUID URI.`
1332
+ );
1333
+ }
1334
+ return path;
1335
+ }
1336
+ async function resolveStandaloneLocation(target) {
1337
+ if (target.path.startsWith("wikg://lib/")) {
1338
+ throw new WikiGraphArchiveOwnershipError(
1339
+ `A library archive URI cannot be used as a standalone path: ${target.path}`
1340
+ );
1341
+ }
1342
+ const path = await assertStandaloneWikiGraphArchivePath(target.path);
1343
+ const normalized = {
1344
+ kind: "standalone",
1345
+ path,
1346
+ ...target.objectUri === void 0 ? {} : { objectUri: target.objectUri }
1347
+ };
1348
+ return {
1349
+ archiveFile: new NodeFile(path),
1350
+ archiveKey: path,
1351
+ archivePath: path,
1352
+ indexScope: { archiveKey: path, archivePath: path, kind: "archive-index" },
1353
+ locatedUri: formatLocatedWikiGraphUri(path, target.objectUri),
1354
+ target: normalized
1355
+ };
1356
+ }
1357
+ async function resolveLibraryLocation(target) {
1358
+ const parsedLocated = parseLocatedWikiGraphUri(target.uri);
1359
+ const archiveUri = parsedLocated.archivePath ?? target.uri;
1360
+ const parsed = parseWikiGraphLibraryUri(archiveUri);
1361
+ if (parsed?.kind !== "archive") {
1362
+ throw new WikiGraphArchiveOwnershipError(
1363
+ `Library archive target must be a UUID archive URI: ${target.uri}`
1364
+ );
1365
+ }
1366
+ await resolveWikiGraphLibrary(parsed);
1367
+ const archiveFile = await resolveWikiGraphLibraryArchiveFile(archiveUri);
1368
+ const normalized = {
1369
+ kind: "library",
1370
+ uri: target.uri
1371
+ };
1372
+ return {
1373
+ archiveFile,
1374
+ archiveKey: archiveUri,
1375
+ archivePath: archiveUri,
1376
+ indexScope: {
1377
+ archiveKey: archiveUri,
1378
+ archivePath: archiveUri,
1379
+ kind: "archive-index"
1380
+ },
1381
+ libraryArchiveTarget: parsed,
1382
+ locatedUri: target.uri,
1383
+ publicArchiveUri: archiveUri,
1384
+ target: normalized
1385
+ };
1386
+ }
1387
+ async function canonicalPath(path) {
1388
+ try {
1389
+ return await realpath(path);
1390
+ } catch {
1391
+ const absolute = resolve3(path);
1392
+ const parent = dirname2(absolute);
1393
+ return parent === absolute ? absolute : join3(await canonicalPath(parent), basename2(absolute));
1394
+ }
1395
+ }
1396
+ function isWithin(root, candidate) {
1397
+ const child = relative(root, candidate);
1398
+ return child === "" || !child.startsWith("..") && !isAbsolute3(child);
1399
+ }
1400
+ async function findMatchingMembers(libraries, candidate, candidateIdentity) {
1401
+ const members = [];
1402
+ for (const library of libraries) {
1403
+ const archives = await listWikiGraphLibraryArchives({
1404
+ isDefault: library.isDefault,
1405
+ kind: "scope",
1406
+ ...library.isDefault ? {} : { publicId: library.publicId }
1407
+ });
1408
+ for (const archive of archives) {
1409
+ if (archive.file === void 0) continue;
1410
+ members.push({
1411
+ archive,
1412
+ library,
1413
+ memberPath: getNodeResourcePath(archive.file)
1414
+ });
1415
+ }
1416
+ }
1417
+ const exact = [];
1418
+ for (const member of members) {
1419
+ if (await canonicalPath(member.memberPath) === candidate) {
1420
+ exact.push(member);
1421
+ }
1422
+ }
1423
+ if (exact.length > 0 || candidateIdentity === void 0) return exact;
1424
+ const identityMatches = [];
1425
+ for (const member of members) {
1426
+ if (await readFileIdentity(member.memberPath) === candidateIdentity) {
1427
+ identityMatches.push(member);
1428
+ }
1429
+ }
1430
+ return identityMatches;
1431
+ }
1432
+ function requireUnambiguousManagedMatch(path, matches) {
1433
+ if (matches.length === 1) return matches[0];
1434
+ throw new WikiGraphArchiveOwnershipError(
1435
+ `Archive file identity is ambiguous across managed archives: ${path}. Use an explicit library UUID URI (${matches.map((match) => match.archive.uri).join(", ")}).`
1436
+ );
1437
+ }
1438
+ async function readFileIdentity(path) {
1439
+ try {
1440
+ const value = await stat2(path);
1441
+ return `${value.dev}:${value.ino}`;
1442
+ } catch {
1443
+ return void 0;
1444
+ }
1445
+ }
1446
+
1272
1447
  // src/conversions.ts
1273
1448
  var WikiGraphConversionManager = class {
1274
1449
  #runtime;
@@ -1293,23 +1468,33 @@ var WikiGraphConversionManager = class {
1293
1468
  }
1294
1469
  const requiresDigest = options.input.format !== "wikg";
1295
1470
  const requiresLLM = requiresDigest && targetStage !== "planned" && targetStage !== "sourced";
1471
+ const llmJSON = resolveWikiGraphLLMJSON(options);
1472
+ const injectedLLM = llmJSON === void 0 ? getWikiGraphSDKProviders().llm : void 0;
1296
1473
  const config = await loadWikiGraphRuntimeConfig({
1297
- ...options.llmJSON === void 0 ? {} : { llmJSON: options.llmJSON }
1474
+ ...llmJSON === void 0 ? {} : { llmJSON },
1475
+ ...injectedLLM === void 0 ? {} : { skipLLMConfig: true },
1476
+ ...getWikiGraphSDKProviders().embedding === void 0 ? {} : { skipEmbeddingConfig: true }
1298
1477
  });
1299
1478
  const app = new WikiGraph({
1300
1479
  ...options.verbose === true ? { verbose: true } : {},
1301
- ...requiresLLM ? { llm: buildWikiGraphLLMOptions(config) } : {}
1480
+ ...requiresLLM ? {
1481
+ llm: injectedLLM === void 0 ? buildWikiGraphLLMOptions(config) : { streamProvider: injectedLLM }
1482
+ } : {}
1302
1483
  });
1303
1484
  const documentDirectory = await prepareDigestDirectory(
1304
1485
  options.digestDirectory,
1305
1486
  requiresDigest
1306
1487
  );
1307
1488
  let chapterCount;
1489
+ let chapterPaths;
1308
1490
  const write = async (archive) => {
1309
1491
  if (requiresDigest) await options.onSourceImported?.();
1310
1492
  if (typeof archive.readToc === "function") {
1311
1493
  const toc = await archive.readToc();
1312
1494
  chapterCount = toc === void 0 ? void 0 : countTocChapters(toc.items);
1495
+ if (options.output.format === "wikg") {
1496
+ chapterPaths = toc === void 0 ? [] : collectTocChapterPaths(toc.items);
1497
+ }
1313
1498
  }
1314
1499
  await writeArchive(archive, options.output.path, options.output.format);
1315
1500
  };
@@ -1318,7 +1503,9 @@ var WikiGraphConversionManager = class {
1318
1503
  throw new Error("wikg input requires a file path.");
1319
1504
  }
1320
1505
  await app.openSession(
1321
- new NodeFile(resolveWikiGraphRuntimePath(options.input.path)),
1506
+ new NodeFile(
1507
+ await assertStandaloneWikiGraphArchivePath(options.input.path)
1508
+ ),
1322
1509
  write
1323
1510
  );
1324
1511
  } else if ("stream" in options.input) {
@@ -1361,6 +1548,7 @@ var WikiGraphConversionManager = class {
1361
1548
  const sourceArtifact = await resolveSourceArtifact(options.input);
1362
1549
  return {
1363
1550
  ...chapterCount === void 0 ? {} : { chapterCount },
1551
+ ...chapterPaths === void 0 ? {} : { chapterPaths },
1364
1552
  inputFormat: options.input.format,
1365
1553
  outputFormat: options.output.format,
1366
1554
  outputPath: options.output.path,
@@ -1377,7 +1565,8 @@ async function prepareDigestDirectory(path, required) {
1377
1565
  return new NodeDirectory(resolved);
1378
1566
  }
1379
1567
  async function writeArchive(archive, path, format) {
1380
- const file = new NodeFile(resolveWikiGraphRuntimePath(path));
1568
+ const outputPath = format === "wikg" ? await assertStandaloneWikiGraphArchivePath(path) : resolveWikiGraphRuntimePath(path);
1569
+ const file = new NodeFile(outputPath);
1381
1570
  if (format === "epub") await archive.exportEpub(file);
1382
1571
  else if (format === "wikg") await archive.saveAs(file);
1383
1572
  else if (format === "pcex") throw new Error("pcex output is not supported.");
@@ -1406,7 +1595,7 @@ async function resolveSourceArtifact(input) {
1406
1595
  return {
1407
1596
  digest: hash.digest("hex"),
1408
1597
  mediaType: "application/epub+zip",
1409
- name: basename2(path)
1598
+ name: basename3(path)
1410
1599
  };
1411
1600
  }
1412
1601
  function isNodeErrorCode(error, code) {
@@ -1422,6 +1611,21 @@ function countTocChapters(items) {
1422
1611
  }
1423
1612
  return count;
1424
1613
  }
1614
+ function collectTocChapterPaths(items, ancestorKeys = []) {
1615
+ const paths = [];
1616
+ for (const item of items) {
1617
+ const key = item.key ?? `chapter-${item.serialId ?? "group"}`;
1618
+ const chapterPath = [...ancestorKeys, key];
1619
+ if (item.serialId !== void 0) paths.push(chapterPath.join("/"));
1620
+ paths.push(
1621
+ ...collectTocChapterPaths(
1622
+ item.children,
1623
+ chapterPath
1624
+ )
1625
+ );
1626
+ }
1627
+ return paths;
1628
+ }
1425
1629
 
1426
1630
  // src/query-runtime.ts
1427
1631
  import {
@@ -1565,6 +1769,8 @@ function createOpenAICompatibleName2(baseURL) {
1565
1769
 
1566
1770
  // src/query-runtime.ts
1567
1771
  async function createConfiguredEmbeddingProvider() {
1772
+ const injected = getWikiGraphSDKProviders().embedding;
1773
+ if (injected !== void 0) return injected;
1568
1774
  const config = await readWikiGraphEmbeddingConfig();
1569
1775
  if (config.provider === void 0 || config.model === void 0 || config.provider === "openai-compatible" && config.baseURL === void 0 || config.provider === "openai" && config.baseURL !== void 0) {
1570
1776
  return void 0;
@@ -1593,489 +1799,130 @@ import {
1593
1799
  isArchiveSearchIndexCurrent,
1594
1800
  listChapters
1595
1801
  } from "wiki-graph-core";
1596
-
1597
- // src/planning.ts
1598
- var DEFAULT_GENERATION_JOB_CONCURRENCY = 3;
1599
- var DEFAULT_GENERATION_REQUEST_CONCURRENCY = 6;
1600
- function planGenerationTask(task, words, chapters, concurrent, model) {
1601
- const profile = getGenerationPlanningProfile(task);
1602
- const calls = Math.max(chapters, Math.ceil(words / profile.wordsPerCall));
1603
- const effectiveConcurrency = Math.max(
1604
- 1,
1605
- task === "reading-graph" ? 1 : concurrent.request
1606
- );
1607
- const callBatches = Math.ceil(calls / effectiveConcurrency);
1608
- const wordSeconds = words * profile.secondsPerWord;
1609
- return {
1610
- model,
1611
- timeSeconds: {
1612
- max: Math.ceil(callBatches * profile.maxSecondsPerCall + wordSeconds),
1613
- min: Math.ceil(callBatches * profile.minSecondsPerCall + wordSeconds)
1614
- },
1615
- tokens: {
1616
- cacheableInput: Math.ceil(words * profile.cacheableInputTokenPerWord),
1617
- input: Math.ceil(words * profile.inputTokenPerWord),
1618
- output: Math.ceil(words * profile.outputTokenPerWord)
1619
- }
1620
- };
1621
- }
1622
- function createGenerationPerformanceHints(input) {
1623
- if (!input.hasJobWork) {
1624
- return [];
1625
- }
1626
- const hints = [];
1627
- const recommendedRequest = input.concurrent.request < 4 ? DEFAULT_GENERATION_REQUEST_CONCURRENCY : input.concurrent.request < 8 ? 8 : void 0;
1628
- if (input.hasRequestWork && recommendedRequest !== void 0) {
1629
- hints.push({
1630
- configKey: "request",
1631
- configValue: recommendedRequest,
1632
- current: input.concurrent.request,
1633
- kind: "request",
1634
- message: "LLM request concurrency can often be higher. Use at least 4; 6-8 is usually faster when the provider allows it.",
1635
- recommended: recommendedRequest
1636
- });
1637
- }
1638
- if (input.concurrent.job < 4 && input.chapters > 1) {
1639
- hints.push({
1640
- configKey: "job",
1641
- configValue: 4,
1642
- current: input.concurrent.job,
1643
- kind: "job",
1644
- message: "Multiple chapters need work and job concurrency is below 4. Raising it lets chapter jobs run in parallel.",
1645
- recommended: 4
1646
- });
1647
- }
1648
- return hints;
1649
- }
1650
- function formatGenerationPlanningModel(llm) {
1651
- if (llm?.model === void 0) {
1652
- return "not configured";
1653
- }
1654
- return llm.provider === void 0 ? llm.model : `${llm.provider}/${llm.model}`;
1655
- }
1656
- function getGenerationPlanningProfile(task) {
1657
- switch (task) {
1658
- case "index-fts":
1659
- return {
1660
- cacheableInputTokenPerWord: 0,
1661
- inputTokenPerWord: 0,
1662
- maxSecondsPerCall: 1,
1663
- minSecondsPerCall: 0,
1664
- outputTokenPerWord: 0,
1665
- secondsPerWord: 5e-4,
1666
- wordsPerCall: 1e5
1667
- };
1668
- case "index-embedding-source":
1669
- case "index-embedding-summary":
1670
- return {
1671
- cacheableInputTokenPerWord: 0,
1672
- inputTokenPerWord: 0,
1673
- maxSecondsPerCall: 10,
1674
- minSecondsPerCall: 1,
1675
- outputTokenPerWord: 0,
1676
- secondsPerWord: 2e-3,
1677
- wordsPerCall: 1e4
1678
- };
1679
- case "knowledge-graph":
1680
- return {
1681
- cacheableInputTokenPerWord: 60,
1682
- inputTokenPerWord: 80,
1683
- maxSecondsPerCall: 35,
1684
- minSecondsPerCall: 15,
1685
- outputTokenPerWord: 25,
1686
- secondsPerWord: 0.02,
1687
- wordsPerCall: 35
1688
- };
1689
- case "reading-graph":
1690
- return {
1691
- cacheableInputTokenPerWord: 20,
1692
- inputTokenPerWord: 25,
1693
- maxSecondsPerCall: 45,
1694
- minSecondsPerCall: 15,
1695
- outputTokenPerWord: 4,
1696
- secondsPerWord: 0.01,
1697
- wordsPerCall: 160
1698
- };
1699
- case "reading-summary":
1700
- return {
1701
- cacheableInputTokenPerWord: 45,
1702
- inputTokenPerWord: 55,
1703
- maxSecondsPerCall: 25,
1704
- minSecondsPerCall: 10,
1705
- outputTokenPerWord: 4,
1706
- secondsPerWord: 0.01,
1707
- wordsPerCall: 110
1708
- };
1709
- }
1710
- }
1711
-
1712
- // src/archive/inspect.ts
1713
- async function createWikiGraphArchiveInspectReport(document, options) {
1714
- const { archiveUri } = options;
1715
- const [
1716
- chapters,
1717
- summaryWords,
1718
- indexCacheCurrent,
1719
- config,
1720
- ftsArtifactCoverage,
1721
- sourceEmbeddingArtifactCoverage,
1722
- summaryEmbeddingArtifactCoverage
1723
- ] = await Promise.all([
1724
- readInspectChapters(document, options.chapterId),
1725
- readSummaryWords(document, options.chapterId),
1802
+ async function inspectWikiGraphArchive(document, options) {
1803
+ const [allChapters, indexCacheCurrent, summaryWords, ...coverage] = await Promise.all([
1804
+ listChapters(document),
1726
1805
  isArchiveSearchIndexCurrent(document),
1727
- loadWikiGraphRuntimeConfig(),
1728
- readIndexArtifactCoverage(document, "fts", options.chapterId),
1729
- readIndexArtifactCoverage(document, "embedding-source", options.chapterId),
1730
- readIndexArtifactCoverage(document, "embedding-summary", options.chapterId)
1806
+ readSummaryWords(document),
1807
+ document.indexArtifacts.listCoverage("fts"),
1808
+ document.indexArtifacts.listCoverage("embedding-source"),
1809
+ document.indexArtifacts.listCoverage("embedding-summary")
1731
1810
  ]);
1732
- const selectedChapter = options.chapterId === void 0 ? void 0 : chapters[0];
1733
- const scopeUri = selectedChapter === void 0 ? archiveUri : formatInspectLocatedChapterUri(archiveUri, selectedChapter);
1734
- const concurrent = {
1735
- job: config.concurrent?.job ?? DEFAULT_GENERATION_JOB_CONCURRENCY,
1736
- request: config.concurrent?.request ?? DEFAULT_GENERATION_REQUEST_CONCURRENCY
1811
+ const chapters = selectChapters(allChapters, options.chapterId);
1812
+ const coverageByKind = {
1813
+ fts: byChapterId(coverage[0]),
1814
+ sourceEmbedding: byChapterId(coverage[1]),
1815
+ summaryEmbedding: byChapterId(coverage[2])
1737
1816
  };
1738
- const planningModel = formatGenerationPlanningModel(config.llm);
1739
- const contentChapters = chapters.filter(
1740
- (chapter) => chapter.stage !== "planned"
1741
- );
1742
- const sourceWords = sumWords(contentChapters);
1743
- const readingGraphCovered = contentChapters.filter(
1744
- (chapter) => chapter.readingGraphReady
1745
- );
1746
- const knowledgeGraphCovered = contentChapters.filter(
1747
- (chapter) => chapter.knowledgeGraphReady
1748
- );
1749
- const summaryCovered = contentChapters.filter(
1750
- (chapter) => chapter.summaryReady
1751
- );
1752
- const ftsArtifactCovered = filterChaptersByCurrentIndexArtifact(
1753
- contentChapters,
1754
- ftsArtifactCoverage
1755
- );
1756
- const sourceEmbeddingArtifactCovered = filterChaptersByCurrentIndexArtifact(
1757
- contentChapters,
1758
- sourceEmbeddingArtifactCoverage
1759
- );
1760
- const summaryEmbeddingArtifactCovered = filterChaptersByCurrentIndexArtifact(
1761
- contentChapters,
1762
- summaryEmbeddingArtifactCoverage
1763
- );
1764
- const queryBlockedContentChapters = contentChapters.filter(
1765
- (chapter) => !ftsArtifactCovered.includes(chapter) && !sourceEmbeddingArtifactCovered.includes(chapter)
1766
- );
1767
- const queryBlockedChapters = queryBlockedContentChapters.map(
1768
- (chapter) => createInspectChapterReference(archiveUri, chapter)
1817
+ const inspections = await Promise.all(
1818
+ chapters.map(async (chapter) => {
1819
+ const serial = await document.serials.getById(chapter.chapterId);
1820
+ return {
1821
+ capabilities: {
1822
+ knowledgeGraph: {
1823
+ completed: serial?.knowledgeGraphReady === true
1824
+ },
1825
+ readingGraph: { completed: serial?.topologyReady === true },
1826
+ readingSummary: { completed: chapter.stage === "summarized" }
1827
+ },
1828
+ chapter,
1829
+ indexes: {
1830
+ fts: inspectArtifact(
1831
+ "fts",
1832
+ coverageByKind.fts.get(chapter.chapterId)
1833
+ ),
1834
+ sourceEmbedding: inspectArtifact(
1835
+ "embedding-source",
1836
+ coverageByKind.sourceEmbedding.get(chapter.chapterId)
1837
+ ),
1838
+ summaryEmbedding: inspectArtifact(
1839
+ "embedding-summary",
1840
+ coverageByKind.summaryEmbedding.get(chapter.chapterId)
1841
+ )
1842
+ },
1843
+ revision: serial?.revision ?? 0,
1844
+ summaryWords: summaryWords.get(chapter.chapterId) ?? 0
1845
+ };
1846
+ })
1769
1847
  );
1770
- const queryReady = queryBlockedChapters.length === 0;
1771
- const improvements = createInspectImprovements({
1772
- concurrent,
1773
- contentChapters,
1774
- indexCacheCurrent,
1775
- planningModel,
1776
- queryBlockedChapters: queryBlockedContentChapters,
1777
- summaryCovered,
1778
- knowledgeGraphCovered,
1779
- readingGraphCovered
1780
- });
1781
- const performanceHints = createGenerationPerformanceHints({
1782
- chapters: Math.max(
1783
- 0,
1784
- ...improvements.map((improvement) => improvement.missingChapters ?? 0)
1785
- ),
1786
- concurrent,
1787
- hasJobWork: improvements.some(
1788
- (improvement) => improvement.planning !== void 0
1789
- ),
1790
- hasRequestWork: improvements.some(
1791
- (improvement) => improvement.planning !== void 0
1792
- )
1793
- });
1848
+ const selected = inspections[0];
1794
1849
  return {
1795
- archiveUri,
1796
- uri: scopeUri,
1850
+ archiveUri: options.archiveUri,
1851
+ chapters: inspections,
1852
+ localIndexCache: { current: indexCacheCurrent },
1797
1853
  scope: options.chapterId === void 0 ? { type: "archive" } : { chapterId: options.chapterId, type: "chapter" },
1798
- content: {
1799
- chapters: {
1800
- content: contentChapters.length,
1801
- planned: chapters.length - contentChapters.length,
1802
- total: chapters.length
1803
- },
1804
- sourceWords,
1805
- summaryWords
1806
- },
1807
- localIndexCache: {
1808
- current: indexCacheCurrent,
1809
- ...!queryReady ? {
1810
- blockedBy: "missing-search-artifacts",
1811
- impact: "The cache cannot be built until every content chapter has a current FTS or source embedding artifact."
1812
- } : indexCacheCurrent ? {} : {
1813
- impact: "The next archive query may need to sync index cache first.",
1814
- resource: "local CPU/disk time only; no provider calls."
1815
- },
1816
- status: !queryReady ? "blocked" : indexCacheCurrent ? "current" : "missing-or-outdated",
1817
- syncAvailable: queryReady && !indexCacheCurrent
1818
- },
1819
- coverage: {
1820
- ftsIndexArtifact: createInspectCoverage(
1821
- ftsArtifactCovered,
1822
- contentChapters
1823
- ),
1824
- knowledgeGraph: createInspectCoverage(
1825
- knowledgeGraphCovered,
1826
- contentChapters
1827
- ),
1828
- readingGraph: createInspectCoverage(readingGraphCovered, contentChapters),
1829
- summary: createInspectCoverage(summaryCovered, contentChapters),
1830
- sourceEmbeddingIndexArtifact: createInspectCoverage(
1831
- sourceEmbeddingArtifactCovered,
1832
- contentChapters
1833
- ),
1834
- summaryEmbeddingIndexArtifact: createInspectCoverage(
1835
- summaryEmbeddingArtifactCovered,
1836
- contentChapters
1837
- )
1838
- },
1839
- query: {
1840
- ready: queryReady
1841
- },
1842
- searchArtifacts: {
1843
- blockedChapters: queryBlockedChapters,
1844
- ...!queryReady ? {
1845
- resource: "local CPU/disk time only; no provider calls."
1846
- } : {},
1847
- ready: queryReady,
1848
- status: queryReady ? "ready" : "incomplete"
1849
- },
1850
- retrievalGuidance: formatRetrievalGuidance({
1851
- indexCacheCurrent,
1852
- knowledgeGraphCovered,
1853
- readingGraphCovered,
1854
- contentChapters,
1855
- queryReady,
1856
- sourceWords
1857
- }),
1858
- improvements,
1859
- performanceHints,
1860
- summaryCoverageNote: "Summary coverage means a current summary artifact exists for the chapter; it does not guarantee that every source topic is represented in the summary text."
1854
+ uri: selected === void 0 || options.chapterId === void 0 ? options.archiveUri : formatLocatedChapterUri(options.archiveUri, selected.chapter)
1861
1855
  };
1862
1856
  }
1863
- function createInspectChapterReference(archiveUri, chapter) {
1864
- return {
1865
- chapterId: chapter.chapterId,
1866
- locatedUri: formatInspectLocatedChapterUri(archiveUri, chapter),
1867
- title: chapter.title,
1868
- uri: chapter.uri
1869
- };
1870
- }
1871
- function formatInspectLocatedChapterUri(archiveUri, chapter) {
1872
- return `${archiveUri.replace(/\/+$/u, "")}/${chapter.uri.replace(/^wikg:\/\//u, "")}`;
1873
- }
1874
- async function readInspectChapters(document, chapterId) {
1875
- const chapters = chapterId === void 0 ? await listChapters(document) : (await listChapters(document)).filter(
1857
+ function selectChapters(chapters, chapterId) {
1858
+ if (chapterId === void 0) return chapters;
1859
+ const selected = chapters.filter(
1876
1860
  (chapter) => chapter.chapterId === chapterId
1877
1861
  );
1878
- if (chapterId !== void 0 && chapters.length === 0) {
1862
+ if (selected.length === 0) {
1879
1863
  throw new Error(`Chapter ${chapterId} does not exist.`);
1880
1864
  }
1881
- return await Promise.all(
1882
- chapters.map(async (chapter) => {
1883
- const serial = await document.serials.getById(chapter.chapterId);
1884
- return {
1885
- ...chapter,
1886
- knowledgeGraphReady: serial?.knowledgeGraphReady === true,
1887
- readingGraphReady: serial?.topologyReady === true,
1888
- summaryReady: chapter.stage === "summarized"
1889
- };
1890
- })
1891
- );
1892
- }
1893
- async function readSummaryWords(document, chapterId) {
1894
- return await document.readDatabase(
1895
- async (database) => await database.queryOne(
1896
- `
1897
- SELECT COALESCE(SUM(words_count), 0) AS words
1898
- FROM text_sentence_records
1899
- WHERE kind = 2
1900
- ${chapterId === void 0 ? "" : "AND chapter_id = ?"}
1901
- `,
1902
- chapterId === void 0 ? void 0 : [chapterId],
1903
- (row) => Number(row.words)
1904
- ) ?? 0
1905
- );
1906
- }
1907
- async function readIndexArtifactCoverage(document, kind, chapterId) {
1908
- const coverage = await document.indexArtifacts.listCoverage(kind);
1909
- return chapterId === void 0 ? coverage : coverage.filter((item) => item.serialId === chapterId);
1865
+ return selected;
1910
1866
  }
1911
- function filterChaptersByCurrentIndexArtifact(chapters, coverage) {
1912
- const currentChapterIds = new Set(
1913
- coverage.filter((item) => item.current).map((item) => item.serialId)
1914
- );
1915
- return chapters.filter((chapter) => currentChapterIds.has(chapter.chapterId));
1867
+ function byChapterId(coverage) {
1868
+ return new Map(coverage.map((item) => [item.serialId, item]));
1916
1869
  }
1917
- function createInspectCoverage(covered, total) {
1918
- const coveredWords = sumWords(covered);
1919
- const totalWords = sumWords(total);
1870
+ function inspectArtifact(kind, coverage) {
1920
1871
  return {
1921
- coveredChapters: covered.length,
1922
- coveredWords,
1923
- percent: formatPercent(coveredWords, totalWords),
1924
- totalChapters: total.length,
1925
- totalWords
1872
+ current: coverage?.current === true,
1873
+ exists: coverage?.sourceRevision !== void 0,
1874
+ kind,
1875
+ ...coverage?.sourceRevision === void 0 ? {} : { sourceRevision: coverage.sourceRevision }
1926
1876
  };
1927
1877
  }
1928
- function formatRetrievalGuidance(input) {
1929
- if (input.sourceWords === 0 || input.contentChapters.length === 0) {
1930
- return [
1931
- "Source content: empty.",
1932
- "Add source text before using query coverage or graph-based retrieval."
1933
- ];
1934
- }
1935
- const lines = [
1936
- input.queryReady ? `Query: available; local index cache is ${input.indexCacheCurrent ? "current" : "missing or outdated and can be synced from artifacts"}.` : "Query: unavailable until every content chapter has a current FTS or source embedding artifact."
1937
- ];
1938
- lines.push(
1939
- formatObjectSearchGuidance(
1940
- "Reading Graph object retrieval",
1941
- input.readingGraphCovered,
1942
- input.contentChapters
1943
- )
1944
- );
1945
- lines.push(
1946
- formatObjectSearchGuidance(
1947
- "Entity/triple retrieval",
1948
- input.knowledgeGraphCovered,
1949
- input.contentChapters
1878
+ async function readSummaryWords(document) {
1879
+ const rows = await document.readDatabase(
1880
+ async (database) => await database.queryAll(
1881
+ `
1882
+ SELECT chapter_id, COALESCE(SUM(words_count), 0) AS words
1883
+ FROM text_sentence_records
1884
+ WHERE kind = 2
1885
+ GROUP BY chapter_id
1886
+ `,
1887
+ void 0,
1888
+ (row) => ({
1889
+ chapterId: Number(row.chapter_id),
1890
+ words: Number(row.words)
1891
+ })
1950
1892
  )
1951
1893
  );
1952
- return lines;
1953
- }
1954
- function formatObjectSearchGuidance(label, covered, total) {
1955
- const coveredWords = sumWords(covered);
1956
- const totalWords = sumWords(total);
1957
- const ratio = totalWords === 0 ? 0 : coveredWords / totalWords;
1958
- const coverage = formatPercent(coveredWords, totalWords);
1959
- if (ratio >= 1) {
1960
- return `${label}: covers all source content; it can represent the full scope for object retrieval.`;
1961
- }
1962
- if (ratio >= 0.9) {
1963
- return `${label}: covers ${coverage}; use it as the main path, but source --query is needed for uncovered content.`;
1964
- }
1965
- if (ratio >= 0.5) {
1966
- return `${label}: covers ${coverage}; use it as leads, not as a full-scope substitute for source --query.`;
1967
- }
1968
- return `${label}: covers ${coverage}; build missing graph coverage before relying on object retrieval.`;
1969
- }
1970
- function createInspectImprovements(input) {
1971
- const improvements = [];
1972
- if (input.contentChapters.length === 0) {
1973
- improvements.push({
1974
- kind: "add-source",
1975
- recommendation: "No source content is available yet; add or import source text before graph or summary generation.",
1976
- title: "Add source content"
1977
- });
1978
- return improvements;
1979
- }
1980
- if (input.queryBlockedChapters.length > 0) {
1981
- improvements.push({
1982
- kind: "build",
1983
- missingChapters: input.queryBlockedChapters.length,
1984
- missingWords: sumWords(input.queryBlockedChapters),
1985
- recommendation: "Build local FTS artifacts to make this scope queryable. This does not call a provider.",
1986
- task: "index-fts",
1987
- title: "Enable query with local FTS"
1988
- });
1989
- } else if (!input.indexCacheCurrent) {
1990
- improvements.push({
1991
- kind: "sync-index",
1992
- recommendation: "Sync the index cache from chapter index artifacts so query starts without a lazy cache rebuild.",
1993
- title: "Sync index cache"
1994
- });
1995
- }
1996
- improvements.push(
1997
- ...createGraphImprovement({
1998
- concurrent: input.concurrent,
1999
- covered: input.readingGraphCovered,
2000
- planningModel: input.planningModel,
2001
- task: "reading-graph",
2002
- title: "Complete Reading Graph coverage",
2003
- total: input.contentChapters
2004
- })
2005
- );
2006
- improvements.push(
2007
- ...createGraphImprovement({
2008
- concurrent: input.concurrent,
2009
- covered: input.knowledgeGraphCovered,
2010
- planningModel: input.planningModel,
2011
- task: "knowledge-graph",
2012
- title: "Complete Knowledge Graph coverage",
2013
- total: input.contentChapters
2014
- })
2015
- );
2016
- improvements.push(
2017
- ...createGraphImprovement({
2018
- concurrent: input.concurrent,
2019
- covered: input.summaryCovered,
2020
- planningModel: input.planningModel,
2021
- task: "reading-summary",
2022
- title: "Complete Summary coverage",
2023
- total: input.contentChapters
2024
- })
2025
- );
2026
- return improvements;
2027
- }
2028
- function createGraphImprovement(input) {
2029
- const coveredIds = new Set(input.covered.map((chapter) => chapter.chapterId));
2030
- const missing = input.total.filter(
2031
- (chapter) => !coveredIds.has(chapter.chapterId)
2032
- );
2033
- if (missing.length === 0) {
2034
- return [];
2035
- }
2036
- const missingWords = sumWords(missing);
2037
- return [
2038
- {
2039
- kind: "build",
2040
- missingChapters: missing.length,
2041
- missingWords,
2042
- planning: planGenerationTask(
2043
- input.task,
2044
- missingWords,
2045
- missing.length,
2046
- input.concurrent,
2047
- input.planningModel
2048
- ),
2049
- recommendation: formatImprovementRecommendation(
2050
- input.covered,
2051
- input.total,
2052
- missingWords
2053
- ),
2054
- task: input.task,
2055
- title: input.title
2056
- }
2057
- ];
2058
- }
2059
- function formatImprovementRecommendation(covered, total, missingWords) {
2060
- const totalWords = sumWords(total);
2061
- const coveredWords = sumWords(covered);
2062
- if (totalWords === 0) {
2063
- return "No source content is available.";
2064
- }
2065
- if (total.length === 1 || coveredWords / totalWords >= 0.9 || missingWords <= 1e3) {
2066
- return "Queue the full scope to finish the remaining gap.";
2067
- }
2068
- return "Queue selected chapters first if only part of the scope matters.";
1894
+ return new Map(rows.map((row) => [row.chapterId, row.words]));
2069
1895
  }
2070
- function sumWords(chapters) {
2071
- return chapters.reduce((total, chapter) => total + chapter.words, 0);
1896
+ function formatLocatedChapterUri(archiveUri, chapter) {
1897
+ return `${archiveUri.replace(/\/+$/u, "")}/${chapter.uri.replace(/^wikg:\/\//u, "")}`;
2072
1898
  }
2073
- function formatPercent(numerator, denominator) {
2074
- if (denominator === 0) {
2075
- return "n/a";
1899
+
1900
+ // src/archive/write.ts
1901
+ import {
1902
+ WikiGraphArchiveFile,
1903
+ writeWikiGraphLibraryArchive
1904
+ } from "wiki-graph-core";
1905
+ async function writeWikiGraphArchiveLocation(location, operation, options = {}) {
1906
+ const cacheOnly = options.searchIndexWritebackPolicy === "cache";
1907
+ const write = async (archiveFile = location.archiveFile) => await new WikiGraphArchiveFile(archiveFile).write(operation, {
1908
+ ...cacheOnly ? {} : { derivedStateKeys: [location.archiveKey, location.archivePath] },
1909
+ ...options.invalidateContinuationCursors === void 0 ? {} : {
1910
+ invalidateContinuationCursors: options.invalidateContinuationCursors
1911
+ },
1912
+ ...options.searchIndexWritebackPolicy === void 0 ? {} : { searchIndexWritebackPolicy: options.searchIndexWritebackPolicy }
1913
+ });
1914
+ if (location.libraryArchiveTarget === void 0 || cacheOnly || options.refreshLibraryIndex === false) {
1915
+ return await write();
2076
1916
  }
2077
- const percent = numerator / denominator * 100;
2078
- return `${Number.isInteger(percent) ? percent.toFixed(0) : percent.toFixed(1)}%`;
1917
+ return await writeWikiGraphLibraryArchive({
1918
+ additionalDerivedStateKeys: [location.archiveKey, location.archivePath],
1919
+ ...options.invalidateContinuationCursors === void 0 ? {} : {
1920
+ invalidateContinuationCursors: options.invalidateContinuationCursors
1921
+ },
1922
+ ...options.onIndexSyncError === void 0 ? {} : { onIndexSyncError: options.onIndexSyncError },
1923
+ operation: async (archiveFile) => await write(archiveFile),
1924
+ target: location.libraryArchiveTarget
1925
+ });
2079
1926
  }
2080
1927
 
2081
1928
  // src/archive/archive.ts
@@ -2095,13 +1942,13 @@ var WikiGraphArchiveManager = class {
2095
1942
  }
2096
1943
  async create(options) {
2097
1944
  return await this.#runtime.run(async () => {
2098
- const path = resolveWikiGraphRuntimePath(options.path);
1945
+ const path = await assertStandaloneWikiGraphArchivePath(options.path);
2099
1946
  if (options.replace !== true && await nodePathExists(path)) {
2100
1947
  throw new WikiGraphArchiveExistsError(path);
2101
1948
  }
2102
- const outputPath = join3(
2103
- dirname2(path),
2104
- `.${basename3(path)}.${randomUUID2()}.tmp.wikg`
1949
+ const outputPath = join4(
1950
+ dirname3(path),
1951
+ `.${basename4(path)}.${randomUUID2()}.tmp.wikg`
2105
1952
  );
2106
1953
  try {
2107
1954
  if (options.importPath === void 0) {
@@ -2126,15 +1973,15 @@ var WikiGraphArchiveManager = class {
2126
1973
  throw error;
2127
1974
  }
2128
1975
  }
2129
- return { locatedUri: formatLocatedWikiGraphUri(path), path };
1976
+ return { locatedUri: formatLocatedWikiGraphUri2(path), path };
2130
1977
  } finally {
2131
1978
  await rm3(outputPath, { force: true, recursive: true });
2132
1979
  }
2133
1980
  }, options.signal);
2134
1981
  }
2135
- async open(uriOrPath, options = {}) {
1982
+ async open(target, options = {}) {
2136
1983
  const location = await this.#runtime.run(
2137
- async () => await resolveWikiGraphArchiveLocation(uriOrPath),
1984
+ async () => await resolveWikiGraphArchiveLocation(target),
2138
1985
  options.signal
2139
1986
  );
2140
1987
  return new WikiGraphArchiveHandle(this.#runtime, location);
@@ -2157,14 +2004,17 @@ var WikiGraphArchiveHandle = class {
2157
2004
  return this.#location.locatedUri;
2158
2005
  }
2159
2006
  get objectUri() {
2160
- return parseLocatedWikiGraphUri(this.#location.locatedUri).objectUri ?? "wikg://";
2007
+ return parseLocatedWikiGraphUri2(this.#location.locatedUri).objectUri ?? "wikg://";
2161
2008
  }
2162
2009
  get path() {
2163
2010
  return this.#location.archivePath;
2164
2011
  }
2012
+ get target() {
2013
+ return this.#location.target;
2014
+ }
2165
2015
  async inspect(options = {}) {
2166
2016
  return await this.#readDocument(
2167
- async (document) => await createWikiGraphArchiveInspectReport(document, {
2017
+ async (document) => await inspectWikiGraphArchive(document, {
2168
2018
  archiveUri: this.#location.publicArchiveUri ?? this.locatedUri,
2169
2019
  ...options.chapterId === void 0 ? {} : { chapterId: options.chapterId }
2170
2020
  }),
@@ -2172,13 +2022,15 @@ var WikiGraphArchiveHandle = class {
2172
2022
  );
2173
2023
  }
2174
2024
  async search(query, options = {}) {
2175
- const embeddingProvider = await this.#runtime.run(
2176
- createConfiguredEmbeddingProvider,
2025
+ const embeddingProvider = await this.#resolveQueryEmbeddingProvider(
2026
+ options.queryMode,
2177
2027
  options.signal
2178
2028
  );
2179
2029
  return await this.#writeDocument(
2180
2030
  async (document) => {
2181
- const chapters = await this.#resolveQueryChapters(document, options);
2031
+ const chapters = await this.#resolveQueryChapters(document, options, {
2032
+ embeddingProvider
2033
+ });
2182
2034
  await ensureArchiveSearchIndex(document, {
2183
2035
  ...chapters === void 0 ? {} : { chapters },
2184
2036
  ...embeddingProvider === void 0 ? {} : { embeddingProvider }
@@ -2465,7 +2317,7 @@ var WikiGraphArchiveHandle = class {
2465
2317
  objectUri,
2466
2318
  async (wikimediaOptions) => await this.#readDocument(
2467
2319
  async (document) => await readArchivePage(document, objectUri, {
2468
- ...withoutOperation(options),
2320
+ ...options,
2469
2321
  ...wikimediaOptions
2470
2322
  }),
2471
2323
  options
@@ -2476,6 +2328,9 @@ var WikiGraphArchiveHandle = class {
2476
2328
  }
2477
2329
  async related(objectUri = this.objectUri, options = {}) {
2478
2330
  if (options.query === void 0) {
2331
+ if (options.queryMode !== void 0) {
2332
+ throw new Error("`queryMode` requires `query`.");
2333
+ }
2479
2334
  return await this.#readDocument(async (document) => {
2480
2335
  const chapters = await this.#resolveScope(document, {});
2481
2336
  return await listRelatedArchiveObjects(document, objectUri, {
@@ -2484,19 +2339,22 @@ var WikiGraphArchiveHandle = class {
2484
2339
  });
2485
2340
  }, options);
2486
2341
  }
2487
- const embeddingProvider = await this.#runtime.run(
2488
- createConfiguredEmbeddingProvider,
2342
+ const embeddingProvider = await this.#resolveQueryEmbeddingProvider(
2343
+ options.queryMode,
2489
2344
  options.signal
2490
2345
  );
2491
2346
  return await this.#writeDocument(
2492
2347
  async (document) => {
2493
- const chapters = await this.#resolveQueryChapters(document, options);
2348
+ const chapters = await this.#resolveQueryChapters(document, options, {
2349
+ embeddingProvider
2350
+ });
2494
2351
  await ensureArchiveSearchIndex(document, {
2495
2352
  ...chapters === void 0 ? {} : { chapters },
2496
2353
  ...embeddingProvider === void 0 ? {} : { embeddingProvider }
2497
2354
  });
2498
2355
  return await listRelatedArchiveObjects(document, objectUri, {
2499
2356
  ...withoutOperation(options),
2357
+ ...embeddingProvider === void 0 ? {} : { embeddingProvider },
2500
2358
  ...chapters === void 0 ? {} : { chapters }
2501
2359
  });
2502
2360
  },
@@ -2508,6 +2366,9 @@ var WikiGraphArchiveHandle = class {
2508
2366
  }
2509
2367
  async evidence(objectUri = this.objectUri, options = {}) {
2510
2368
  if (options.query === void 0) {
2369
+ if (options.queryMode !== void 0) {
2370
+ throw new Error("`queryMode` requires `query`.");
2371
+ }
2511
2372
  return await this.#readDocument(async (document) => {
2512
2373
  const chapters = await this.#resolveScope(document, {});
2513
2374
  return await listArchiveEvidence(document, objectUri, {
@@ -2516,19 +2377,22 @@ var WikiGraphArchiveHandle = class {
2516
2377
  });
2517
2378
  }, options);
2518
2379
  }
2519
- const embeddingProvider = await this.#runtime.run(
2520
- createConfiguredEmbeddingProvider,
2380
+ const embeddingProvider = await this.#resolveQueryEmbeddingProvider(
2381
+ options.queryMode,
2521
2382
  options.signal
2522
2383
  );
2523
2384
  return await this.#writeDocument(
2524
2385
  async (document) => {
2525
- const chapters = await this.#resolveQueryChapters(document, options);
2386
+ const chapters = await this.#resolveQueryChapters(document, options, {
2387
+ embeddingProvider
2388
+ });
2526
2389
  await ensureArchiveSearchIndex(document, {
2527
2390
  ...chapters === void 0 ? {} : { chapters },
2528
2391
  ...embeddingProvider === void 0 ? {} : { embeddingProvider }
2529
2392
  });
2530
2393
  return await listArchiveEvidence(document, objectUri, {
2531
2394
  ...withoutOperation(options),
2395
+ ...embeddingProvider === void 0 ? {} : { embeddingProvider },
2532
2396
  ...chapters === void 0 ? {} : { chapters }
2533
2397
  });
2534
2398
  },
@@ -2562,6 +2426,24 @@ var WikiGraphArchiveHandle = class {
2562
2426
  return meta;
2563
2427
  }, options);
2564
2428
  }
2429
+ async setArchiveTitle(title, options = {}) {
2430
+ const normalized = title?.trim() ?? null;
2431
+ if (title !== null && normalized === "") {
2432
+ throw new Error("Archive title cannot be empty.");
2433
+ }
2434
+ return await this.#writeDocument(async (document) => {
2435
+ if (normalized === null) {
2436
+ await document.metadata.deleteKey("", "title");
2437
+ } else {
2438
+ await document.metadata.put(
2439
+ { kind: ObjectMetadataKind.Archive, objectPath: "" },
2440
+ "title",
2441
+ normalized
2442
+ );
2443
+ }
2444
+ return normalized;
2445
+ }, options);
2446
+ }
2565
2447
  async readCover(options = {}) {
2566
2448
  const app = new WikiGraph2({});
2567
2449
  return await this.#runtime.run(
@@ -2611,7 +2493,7 @@ var WikiGraphArchiveHandle = class {
2611
2493
  }
2612
2494
  async #readDocument(operation, options = {}) {
2613
2495
  return await this.#runtime.run(
2614
- async () => await new WikiGraphArchiveFile(this.#location.archiveFile).readDocument(
2496
+ async () => await new WikiGraphArchiveFile2(this.#location.archiveFile).readDocument(
2615
2497
  operation
2616
2498
  ),
2617
2499
  options.signal
@@ -2619,28 +2501,33 @@ var WikiGraphArchiveHandle = class {
2619
2501
  }
2620
2502
  async #writeDocument(operation, options = {}) {
2621
2503
  return await this.#runtime.run(async () => {
2622
- const result = await new WikiGraphArchiveFile(
2623
- this.#location.archiveFile
2624
- ).write(operation, {
2504
+ return await writeWikiGraphArchiveLocation(this.#location, operation, {
2505
+ ...options.onIndexSyncError === void 0 ? {} : { onIndexSyncError: options.onIndexSyncError },
2506
+ ...options.refreshLibraryIndex === void 0 ? {} : { refreshLibraryIndex: options.refreshLibraryIndex },
2625
2507
  ...options.searchIndexWritebackPolicy === void 0 ? {} : {
2626
2508
  searchIndexWritebackPolicy: options.searchIndexWritebackPolicy
2627
2509
  }
2628
2510
  });
2629
- if (options.refreshLibraryIndex !== false) {
2630
- await this.#refreshLibraryIndex(options.onIndexSyncError);
2631
- }
2632
- return result;
2633
2511
  }, options.signal);
2634
2512
  }
2635
2513
  async #resolveScope(document, options) {
2636
2514
  if (options.chapters !== void 0) return options.chapters;
2637
2515
  return (await resolveArchiveScope(document, this.objectUri, options.depth))?.chapterIds;
2638
2516
  }
2639
- async #resolveQueryChapters(document, options) {
2517
+ async #resolveQueryChapters(document, options, capabilities) {
2640
2518
  const chapters = await this.#resolveScope(document, options);
2641
- if (options.skipUnindexed !== true) return chapters;
2519
+ const coverageOptions = {
2520
+ ...chapters === void 0 ? {} : { chapters },
2521
+ ...capabilities.embeddingProvider === void 0 ? {} : { embeddingProvider: capabilities.embeddingProvider },
2522
+ requireEmbeddingProvider: true,
2523
+ ...options.queryMode === void 0 ? {} : { queryMode: options.queryMode }
2524
+ };
2525
+ if (options.skipUnindexed !== true) {
2526
+ await assertArchiveIndexArtifactsReady(document, coverageOptions);
2527
+ return chapters;
2528
+ }
2642
2529
  const queryable = await listArchiveQueryableChapterIds(document, {
2643
- ...chapters === void 0 ? {} : { chapters }
2530
+ ...coverageOptions
2644
2531
  });
2645
2532
  if (queryable.length === 0) {
2646
2533
  throw new Error(
@@ -2649,52 +2536,20 @@ var WikiGraphArchiveHandle = class {
2649
2536
  }
2650
2537
  return queryable;
2651
2538
  }
2652
- async #refreshLibraryIndex(onError) {
2653
- if (this.#location.libraryDirtyTarget === void 0) return;
2654
- if (this.#location.libraryArchiveTarget !== void 0) {
2655
- await finalizeWikiGraphLibraryArchiveWrite({
2656
- target: this.#location.libraryArchiveTarget
2657
- });
2658
- }
2659
- try {
2660
- const state = await readWikiGraphLibraryIndexState(
2661
- this.#location.libraryDirtyTarget
2539
+ async #resolveQueryEmbeddingProvider(queryMode, signal) {
2540
+ if (queryMode === "fts") return void 0;
2541
+ const provider = await this.#runtime.run(
2542
+ createConfiguredEmbeddingProvider,
2543
+ signal
2544
+ );
2545
+ if (queryMode === "embedding" && provider === void 0) {
2546
+ throw new Error(
2547
+ "Embedding query mode requires embeddings configuration at `wikg://local/config/embeddings`."
2662
2548
  );
2663
- if (state.status !== "missing") {
2664
- await rebuildWikiGraphLibraryIndex(this.#location.libraryDirtyTarget);
2665
- }
2666
- } catch (error) {
2667
- onError?.(error);
2668
2549
  }
2550
+ return provider;
2669
2551
  }
2670
2552
  };
2671
- async function resolveWikiGraphArchiveLocation(uriOrPath) {
2672
- if (!uriOrPath.startsWith("wikg://")) {
2673
- return createPathLocation(resolveWikiGraphRuntimePath(uriOrPath));
2674
- }
2675
- const parsed = parseLocatedWikiGraphUri(uriOrPath);
2676
- const portableArchiveLocator = parsed.archivePath ?? uriOrPath;
2677
- const archiveLocator = portableArchiveLocator.startsWith("wikg://lib/") ? portableArchiveLocator : resolveWikiGraphRuntimePath(portableArchiveLocator);
2678
- const libraryArchiveTarget = archiveLocator.startsWith("wikg://lib/") ? parseWikiGraphLibraryUri(archiveLocator) : void 0;
2679
- const archiveFile = libraryArchiveTarget?.kind === "archive" ? await resolveWikiGraphLibraryArchiveFile(archiveLocator) : new NodeFile(archiveLocator);
2680
- const archivePath = getNodeResourcePath(archiveFile);
2681
- return {
2682
- archiveFile,
2683
- archiveKey: archivePath,
2684
- archivePath,
2685
- indexScope: { archiveKey: archivePath, archivePath, kind: "archive-index" },
2686
- ...libraryArchiveTarget?.kind === "archive" ? {
2687
- libraryArchiveTarget,
2688
- libraryDirtyTarget: {
2689
- isDefault: libraryArchiveTarget.isDefault,
2690
- kind: "scope",
2691
- ...libraryArchiveTarget.publicId === void 0 ? {} : { publicId: libraryArchiveTarget.publicId }
2692
- },
2693
- publicArchiveUri: portableArchiveLocator
2694
- } : {},
2695
- locatedUri: formatLocatedWikiGraphUri(archivePath, parsed.objectUri)
2696
- };
2697
- }
2698
2553
  async function ensureArchiveSearchIndex(document, options = {}) {
2699
2554
  const scope = options.chapters === void 0 ? {} : { chapters: options.chapters };
2700
2555
  if (await isArchiveSearchIndexCurrent2(document, scope)) return;
@@ -2733,18 +2588,9 @@ function selectChapterSubtree(chapters, rootChapterId, depth) {
2733
2588
  (chapter) => chapter.chapterId === root.chapterId || chapter.path.startsWith(prefix) && (depth === void 0 || chapter.depth - root.depth <= depth)
2734
2589
  );
2735
2590
  }
2736
- function createPathLocation(path) {
2737
- return {
2738
- archiveFile: new NodeFile(path),
2739
- archiveKey: path,
2740
- archivePath: path,
2741
- indexScope: { archiveKey: path, archivePath: path, kind: "archive-index" },
2742
- locatedUri: formatLocatedWikiGraphUri(path)
2743
- };
2744
- }
2745
2591
  async function createEmptyArchiveFile(path) {
2746
2592
  const directoryPath = await mkdtemp(
2747
- join3(tmpdir(), "wikigraph-archive-write-")
2593
+ join4(tmpdir(), "wikigraph-archive-write-")
2748
2594
  );
2749
2595
  try {
2750
2596
  const directory = new NodeDirectory(directoryPath);
@@ -2766,7 +2612,7 @@ function isNodeEEXISTError(error) {
2766
2612
  }
2767
2613
  async function nodePathExists(path) {
2768
2614
  try {
2769
- await stat2(path);
2615
+ await stat3(path);
2770
2616
  return true;
2771
2617
  } catch (error) {
2772
2618
  if (isNodeENOENTError(error)) return false;
@@ -2872,7 +2718,7 @@ export {
2872
2718
  WikiGraphArchiveExistsError,
2873
2719
  WikiGraphArchiveHandle,
2874
2720
  WikiGraphArchiveManager,
2875
- createWikiGraphArchiveInspectReport,
2876
- resolveWikiGraphArchiveLocation
2721
+ WikiGraphArchiveOwnershipError,
2722
+ inspectWikiGraphArchive
2877
2723
  };
2878
2724
  //# sourceMappingURL=archives.js.map