@equationalapplications/core-llm-wiki 4.15.3 → 4.17.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 CHANGED
@@ -87,6 +87,13 @@ async function setupDatabase(db, prefix) {
87
87
  memory_checkpoint INTEGER NOT NULL DEFAULT 0
88
88
  );
89
89
 
90
+ CREATE TABLE IF NOT EXISTS ${prefix}entity_manifests (
91
+ entity_id TEXT PRIMARY KEY,
92
+ mode TEXT NOT NULL DEFAULT 'off',
93
+ manifest_json TEXT NOT NULL DEFAULT '{"node_types":[],"edge_types":[]}',
94
+ updated_at INTEGER NOT NULL
95
+ );
96
+
90
97
  CREATE TABLE IF NOT EXISTS ${prefix}meta (
91
98
  key TEXT PRIMARY KEY,
92
99
  value TEXT NOT NULL
@@ -196,6 +203,20 @@ var MIGRATIONS = [
196
203
  CREATE INDEX IF NOT EXISTS ${prefix}edges_entity_idx ON ${prefix}edges (entity_id);
197
204
  `);
198
205
  }
206
+ },
207
+ {
208
+ version: 6,
209
+ description: "Add entity_manifests table for per-entity ontology state",
210
+ run: async (db, prefix) => {
211
+ await db.execAsync(`
212
+ CREATE TABLE IF NOT EXISTS ${prefix}entity_manifests (
213
+ entity_id TEXT PRIMARY KEY,
214
+ mode TEXT NOT NULL DEFAULT 'off',
215
+ manifest_json TEXT NOT NULL DEFAULT '{"node_types":[],"edge_types":[]}',
216
+ updated_at INTEGER NOT NULL
217
+ );
218
+ `);
219
+ }
199
220
  }
200
221
  ];
201
222
  for (let i = 1; i < MIGRATIONS.length; i++) {
@@ -319,8 +340,8 @@ var EntryRepository = class extends BaseRepository {
319
340
  `INSERT INTO ${this.prefix}entries (
320
341
  id, entity_id, title, body, tags, confidence, source_type,
321
342
  source_hash, source_ref, created_at, updated_at, last_accessed_at, access_count,
322
- deleted_at, embedding_blob, embedding
323
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
343
+ deleted_at, embedding_blob, embedding, okf_type
344
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
324
345
  ON CONFLICT(id) DO UPDATE SET
325
346
  entity_id = excluded.entity_id,
326
347
  title = excluded.title,
@@ -335,7 +356,8 @@ var EntryRepository = class extends BaseRepository {
335
356
  access_count = excluded.access_count,
336
357
  deleted_at = excluded.deleted_at,
337
358
  embedding_blob = CASE WHEN excluded.embedding_blob IS NULL THEN embedding_blob ELSE excluded.embedding_blob END,
338
- embedding = NULL`,
359
+ embedding = NULL,
360
+ okf_type = excluded.okf_type`,
339
361
  [
340
362
  fact.id,
341
363
  fact.entity_id,
@@ -352,7 +374,8 @@ var EntryRepository = class extends BaseRepository {
352
374
  fact.access_count,
353
375
  fact.deleted_at ?? null,
354
376
  embeddingBlob ?? null,
355
- null
377
+ null,
378
+ fact.okf_type ?? null
356
379
  ]
357
380
  );
358
381
  await this.outbox.push({
@@ -1384,6 +1407,11 @@ var EventRepository = class extends BaseRepository {
1384
1407
  };
1385
1408
 
1386
1409
  // src/repositories/EdgeRepository.ts
1410
+ var CONFIDENCE_RANK = {
1411
+ tentative: 0,
1412
+ inferred: 1,
1413
+ certain: 2
1414
+ };
1387
1415
  var EdgeRepository = class extends BaseRepository {
1388
1416
  /**
1389
1417
  * Insert an edge, silently skipping on primary-key or uniqueness conflicts.
@@ -1414,21 +1442,196 @@ var EdgeRepository = class extends BaseRepository {
1414
1442
  `SELECT * FROM ${this.prefix}edges WHERE entity_id = ? ORDER BY created_at ASC`,
1415
1443
  [entityId]
1416
1444
  );
1417
- return rows.map((row) => ({
1418
- id: String(row.id),
1419
- entity_id: String(row.entity_id),
1420
- source_id: String(row.source_id),
1421
- target_id: String(row.target_id),
1422
- edge_type: String(row.edge_type),
1423
- created_at: Number(row.created_at)
1424
- }));
1445
+ return rows.map(mapRowToEdge);
1425
1446
  }
1426
1447
  /** Hard delete — edges have no soft-delete concept, only presence/absence. `tx` is REQUIRED. */
1427
1448
  async bulkDeleteByEntityId(entityId, tx) {
1428
1449
  const executor = this.getExecutor(tx);
1429
1450
  await executor.runAsync(`DELETE FROM ${this.prefix}edges WHERE entity_id = ?`, [entityId]);
1430
1451
  }
1452
+ /**
1453
+ * Multi-hop traversal from `sourceId` via SQLite `WITH RECURSIVE`. All filtering,
1454
+ * dead-ending, cycle-guarding, capping, and ordering happens in this one query.
1455
+ * The anchor is validated (exists, right entity, not soft-deleted) but never gated
1456
+ * by confidence/source_type — only nodes discovered beyond it are.
1457
+ */
1458
+ async getNeighborhood(entityId, sourceId, opts, tx) {
1459
+ const executor = this.getExecutor(tx);
1460
+ if (opts.edgeTypes && opts.edgeTypes.length === 0) {
1461
+ const anchor = await executor.getFirstAsync(
1462
+ `SELECT id FROM ${this.prefix}entries WHERE id = ? AND entity_id = ? AND deleted_at IS NULL`,
1463
+ [sourceId, entityId]
1464
+ );
1465
+ return { nodeIds: anchor ? [anchor.id] : [], edges: [] };
1466
+ }
1467
+ const edgeTypesClause = opts.edgeTypes ? `e.edge_type IN (${opts.edgeTypes.map(() => "?").join(",")})` : "1=1";
1468
+ const excludeSourceTypesPlaceholders = opts.excludeSourceTypes.map(() => "?").join(",");
1469
+ const minConfidenceRank = CONFIDENCE_RANK[opts.minConfidence];
1470
+ const sql = `
1471
+ WITH RECURSIVE walk(node_id, depth, visited) AS (
1472
+ SELECT id, 0, ',' || id || ','
1473
+ FROM ${this.prefix}entries
1474
+ WHERE id = ? AND entity_id = ? AND deleted_at IS NULL
1475
+
1476
+ UNION
1477
+
1478
+ SELECT
1479
+ CASE WHEN e.source_id = w.node_id THEN e.target_id ELSE e.source_id END,
1480
+ w.depth + 1,
1481
+ w.visited || (CASE WHEN e.source_id = w.node_id THEN e.target_id ELSE e.source_id END) || ','
1482
+ FROM walk w
1483
+ JOIN ${this.prefix}edges e
1484
+ ON e.entity_id = ?
1485
+ AND (
1486
+ (? != 'inbound' AND e.source_id = w.node_id) OR
1487
+ (? != 'outbound' AND e.target_id = w.node_id)
1488
+ )
1489
+ AND (${edgeTypesClause})
1490
+ JOIN ${this.prefix}entries n
1491
+ ON n.id = (CASE WHEN e.source_id = w.node_id THEN e.target_id ELSE e.source_id END)
1492
+ AND n.entity_id = ?
1493
+ AND n.deleted_at IS NULL
1494
+ AND (
1495
+ CASE n.confidence
1496
+ WHEN 'tentative' THEN 0
1497
+ WHEN 'inferred' THEN 1
1498
+ WHEN 'certain' THEN 2
1499
+ ELSE -1
1500
+ END
1501
+ ) >= ?
1502
+ AND n.source_type NOT IN (${excludeSourceTypesPlaceholders})
1503
+ WHERE w.depth < ?
1504
+ AND instr(w.visited, ',' || (CASE WHEN e.source_id = w.node_id THEN e.target_id ELSE e.source_id END) || ',') = 0
1505
+ )
1506
+ SELECT node_id, MIN(depth) AS depth
1507
+ FROM walk
1508
+ GROUP BY node_id
1509
+ ORDER BY depth ASC, (SELECT updated_at FROM ${this.prefix}entries WHERE id = node_id) DESC
1510
+ LIMIT ?
1511
+ `;
1512
+ const params = [
1513
+ sourceId,
1514
+ entityId,
1515
+ entityId,
1516
+ opts.direction,
1517
+ opts.direction,
1518
+ ...opts.edgeTypes ?? [],
1519
+ entityId,
1520
+ minConfidenceRank,
1521
+ ...opts.excludeSourceTypes,
1522
+ opts.maxDepth,
1523
+ opts.maxNodes
1524
+ ];
1525
+ const rows = await executor.getAllAsync(sql, params);
1526
+ const nodeIds = rows.map((r) => r.node_id);
1527
+ if (nodeIds.length === 0) return { nodeIds: [], edges: [] };
1528
+ const valueRows = nodeIds.map(() => "(?)").join(", ");
1529
+ const edgeRows = await executor.getAllAsync(
1530
+ `WITH neighborhood(node_id) AS (VALUES ${valueRows})
1531
+ SELECT e.* FROM ${this.prefix}edges e
1532
+ JOIN neighborhood ns ON e.source_id = ns.node_id
1533
+ JOIN neighborhood nt ON e.target_id = nt.node_id
1534
+ WHERE e.entity_id = ?`,
1535
+ [...nodeIds, entityId]
1536
+ );
1537
+ return { nodeIds, edges: edgeRows.map(mapRowToEdge) };
1538
+ }
1431
1539
  };
1540
+ function mapRowToEdge(row) {
1541
+ return {
1542
+ id: String(row.id),
1543
+ entity_id: String(row.entity_id),
1544
+ source_id: String(row.source_id),
1545
+ target_id: String(row.target_id),
1546
+ edge_type: String(row.edge_type),
1547
+ created_at: Number(row.created_at)
1548
+ };
1549
+ }
1550
+
1551
+ // src/utils/ontology.ts
1552
+ function emptyManifest() {
1553
+ return { node_types: [], edge_types: [] };
1554
+ }
1555
+ function normalizeTitleKey(title) {
1556
+ return title.trim().toLowerCase().replace(/\s+/g, " ");
1557
+ }
1558
+ function resolveNodeType(raw, manifest) {
1559
+ const slug = raw.trim();
1560
+ if (!slug) return null;
1561
+ const hit = manifest.node_types.find((n) => n.type.toLowerCase() === slug.toLowerCase());
1562
+ return hit?.type ?? null;
1563
+ }
1564
+ function resolveEdgeDefinition(rawEdgeType, manifest) {
1565
+ const slug = rawEdgeType.trim();
1566
+ if (!slug) return null;
1567
+ return manifest.edge_types.find((e) => e.type.toLowerCase() === slug.toLowerCase()) ?? null;
1568
+ }
1569
+ function validateManifest(manifest) {
1570
+ const nodeSlugs = /* @__PURE__ */ new Set();
1571
+ for (const node of manifest.node_types ?? []) {
1572
+ const type = node.type?.trim();
1573
+ if (!type) throw new Error("Ontology node type slug must be non-empty");
1574
+ const key = type.toLowerCase();
1575
+ if (nodeSlugs.has(key)) throw new Error(`Duplicate node type: ${type}`);
1576
+ nodeSlugs.add(key);
1577
+ }
1578
+ const edgeSlugs = /* @__PURE__ */ new Set();
1579
+ for (const edge of manifest.edge_types ?? []) {
1580
+ const edgeType = edge.type?.trim();
1581
+ const sourceType = edge.source_type?.trim();
1582
+ const targetType = edge.target_type?.trim();
1583
+ if (!edgeType) throw new Error("Ontology edge type slug must be non-empty");
1584
+ const edgeKey = edgeType.toLowerCase();
1585
+ if (edgeSlugs.has(edgeKey)) throw new Error(`Duplicate edge type: ${edgeType}`);
1586
+ edgeSlugs.add(edgeKey);
1587
+ if (!sourceType || !targetType || !nodeSlugs.has(sourceType.toLowerCase()) || !nodeSlugs.has(targetType.toLowerCase())) {
1588
+ throw new Error(`Edge type ${edgeType} references unknown node type`);
1589
+ }
1590
+ }
1591
+ }
1592
+ function mergeOntologyUpdates(current, updates) {
1593
+ const node_types = [...current.node_types];
1594
+ const edge_types = [...current.edge_types];
1595
+ const nodeSlugs = new Set(node_types.map((n) => n.type.trim().toLowerCase()));
1596
+ const edgeSlugs = new Set(edge_types.map((e) => e.type.trim().toLowerCase()));
1597
+ for (const node of updates.node_types ?? []) {
1598
+ const type = node?.type?.trim();
1599
+ if (!type) continue;
1600
+ const key = type.toLowerCase();
1601
+ if (nodeSlugs.has(key)) continue;
1602
+ node_types.push({ type, description: String(node.description ?? "") });
1603
+ nodeSlugs.add(key);
1604
+ }
1605
+ for (const edge of updates.edge_types ?? []) {
1606
+ const edgeType = edge?.type?.trim();
1607
+ const sourceType = edge?.source_type?.trim();
1608
+ const targetType = edge?.target_type?.trim();
1609
+ if (!edgeType || !sourceType || !targetType) continue;
1610
+ const edgeKey = edgeType.toLowerCase();
1611
+ if (edgeSlugs.has(edgeKey)) continue;
1612
+ if (!nodeSlugs.has(sourceType.toLowerCase()) || !nodeSlugs.has(targetType.toLowerCase())) continue;
1613
+ edge_types.push({
1614
+ type: edgeType,
1615
+ source_type: sourceType,
1616
+ target_type: targetType,
1617
+ description: String(edge.description ?? "")
1618
+ });
1619
+ edgeSlugs.add(edgeKey);
1620
+ }
1621
+ return { node_types, edge_types };
1622
+ }
1623
+ function validateInlineEdges(sourceType, _targetType, edges, manifest) {
1624
+ if (!Array.isArray(edges)) return [];
1625
+ const valid = [];
1626
+ for (const edge of edges) {
1627
+ if (typeof edge?.edge_type !== "string" || typeof edge?.target_title !== "string") continue;
1628
+ const def = resolveEdgeDefinition(edge.edge_type, manifest);
1629
+ if (!def) continue;
1630
+ if (def.source_type.toLowerCase() !== sourceType.toLowerCase()) continue;
1631
+ valid.push({ edge_type: def.type, target_title: edge.target_title });
1632
+ }
1633
+ return valid;
1634
+ }
1432
1635
 
1433
1636
  // src/repositories/MetadataRepository.ts
1434
1637
  var MetadataRepository = class extends BaseRepository {
@@ -1528,6 +1731,44 @@ var MetadataRepository = class extends BaseRepository {
1528
1731
  );
1529
1732
  return rows.map((r) => r.entity_id);
1530
1733
  }
1734
+ async getManifest(entityId, tx) {
1735
+ const executor = this.getExecutor(tx);
1736
+ const row = await executor.getFirstAsync(`SELECT mode, manifest_json FROM ${this.prefix}entity_manifests WHERE entity_id = ?`, [entityId]);
1737
+ if (!row) return null;
1738
+ if (row.mode !== "off" && row.mode !== "strict" && row.mode !== "emergent") {
1739
+ throw new Error(`Invalid ontology mode for entity ${entityId}: ${JSON.stringify(row.mode)}`);
1740
+ }
1741
+ let manifest;
1742
+ try {
1743
+ manifest = JSON.parse(row.manifest_json);
1744
+ } catch (error) {
1745
+ throw new Error(`Invalid manifest_json for entity ${entityId}: ${error.message}`);
1746
+ }
1747
+ validateManifest(manifest);
1748
+ return {
1749
+ mode: row.mode,
1750
+ manifest
1751
+ };
1752
+ }
1753
+ async setManifest(entityId, data, tx) {
1754
+ validateManifest(data.manifest);
1755
+ const executor = this.getExecutor(tx);
1756
+ await executor.runAsync(
1757
+ `INSERT INTO ${this.prefix}entity_manifests (entity_id, mode, manifest_json, updated_at)
1758
+ VALUES (?, ?, ?, ?)
1759
+ ON CONFLICT(entity_id) DO UPDATE SET mode = excluded.mode, manifest_json = excluded.manifest_json, updated_at = excluded.updated_at`,
1760
+ [entityId, data.mode, JSON.stringify(data.manifest), Date.now()]
1761
+ );
1762
+ }
1763
+ async mergeManifestUpdates(entityId, updates, tx) {
1764
+ const current = await this.getManifest(entityId, tx) ?? {
1765
+ mode: "emergent",
1766
+ manifest: emptyManifest()
1767
+ };
1768
+ const merged = mergeOntologyUpdates(current.manifest, updates);
1769
+ await this.setManifest(entityId, { mode: current.mode, manifest: merged }, tx);
1770
+ return merged;
1771
+ }
1531
1772
  };
1532
1773
 
1533
1774
  // src/utils/cosine.ts
@@ -2343,30 +2584,54 @@ var PromptService = class {
2343
2584
  return typeof value === "string" ? value : JSON.stringify(value, null, 2);
2344
2585
  });
2345
2586
  }
2346
- buildIngestPrompt(documentChunk, runtimeOverride) {
2587
+ hasOntologyPlaceholders(template) {
2588
+ return /\{\{\s*ontology(?:Manifest|ModeInstructions)\s*\}\}/.test(template);
2589
+ }
2590
+ buildSystemPrompt(template, variables, ontologyContext) {
2591
+ const shouldHydrate = Object.keys(variables).some(
2592
+ (key) => new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`).test(template)
2593
+ ) || ontologyContext != null && this.hasOntologyPlaceholders(template);
2594
+ const hydrated = shouldHydrate ? this.hydrate(template, { ...variables, ...ontologyContext ?? {} }) : template;
2595
+ return this.hasOntologyPlaceholders(template) ? ontologyContext != null ? hydrated : hydrated.replace(/\{\{\s*ontology(?:Manifest|ModeInstructions)\s*\}\}/g, "") : this.appendOntology(hydrated, ontologyContext);
2596
+ }
2597
+ appendOntology(systemPrompt, ctx) {
2598
+ if (!ctx) return systemPrompt;
2599
+ return `${systemPrompt}
2600
+
2601
+ ${ctx.ontologyModeInstructions}`;
2602
+ }
2603
+ buildIngestPrompt(documentChunk, runtimeOverride, ontologyContext) {
2347
2604
  const template = runtimeOverride ?? this.globalOverrides?.ingestSystemPrompt ?? INGEST_SYSTEM_PROMPT;
2348
- if (/\{\{\s*documentChunk\s*\}\}/.test(template)) {
2605
+ const hasDocumentChunk = /\{\{\s*documentChunk\s*\}\}/.test(template);
2606
+ if (hasDocumentChunk || this.hasOntologyPlaceholders(template)) {
2349
2607
  return {
2350
- systemPrompt: this.hydrate(template, { documentChunk }),
2351
- userPrompt: "Please extract the facts."
2608
+ systemPrompt: this.buildSystemPrompt(template, { documentChunk }, ontologyContext),
2609
+ userPrompt: hasDocumentChunk ? "Please extract the facts." : `Document Chunk:
2610
+ ${documentChunk}`
2352
2611
  };
2353
2612
  }
2354
2613
  return {
2355
- systemPrompt: template,
2614
+ systemPrompt: this.appendOntology(template, ontologyContext),
2356
2615
  userPrompt: `Document Chunk:
2357
2616
  ${documentChunk}`
2358
2617
  };
2359
2618
  }
2360
- buildLibrarianPrompt(events, currentFacts, runtimeOverride) {
2619
+ buildLibrarianPrompt(events, currentFacts, runtimeOverride, ontologyContext) {
2361
2620
  const template = runtimeOverride ?? this.globalOverrides?.librarianSystemPrompt ?? LIBRARIAN_SYSTEM_PROMPT;
2362
- if (/\{\{\s*events\s*\}\}/.test(template) || /\{\{\s*currentFacts\s*\}\}/.test(template)) {
2621
+ const hasEvents = /\{\{\s*events\s*\}\}/.test(template);
2622
+ const hasCurrentFacts = /\{\{\s*currentFacts\s*\}\}/.test(template);
2623
+ if (hasEvents || hasCurrentFacts || this.hasOntologyPlaceholders(template)) {
2363
2624
  return {
2364
- systemPrompt: this.hydrate(template, { events, currentFacts }),
2365
- userPrompt: "Please synthesize the context."
2625
+ systemPrompt: this.buildSystemPrompt(template, { events, currentFacts }, ontologyContext),
2626
+ userPrompt: hasEvents || hasCurrentFacts ? "Please synthesize the context." : `Events:
2627
+ ${JSON.stringify(events, null, 2)}
2628
+
2629
+ Current Facts:
2630
+ ${JSON.stringify(currentFacts, null, 2)}`
2366
2631
  };
2367
2632
  }
2368
2633
  return {
2369
- systemPrompt: template,
2634
+ systemPrompt: this.appendOntology(template, ontologyContext),
2370
2635
  userPrompt: `Events:
2371
2636
  ${JSON.stringify(events, null, 2)}
2372
2637
 
@@ -2399,7 +2664,7 @@ The following document anchors are provided for contradiction detection only. Do
2399
2664
 
2400
2665
  // src/services/IngestionService.ts
2401
2666
  var IngestionService = class {
2402
- constructor(db, prefix, options, entryRepo, searchService, jobManager, embeddingService, promptService) {
2667
+ constructor(db, prefix, options, entryRepo, searchService, jobManager, embeddingService, promptService, ontologyService) {
2403
2668
  this.db = db;
2404
2669
  this.prefix = prefix;
2405
2670
  this.options = options;
@@ -2407,6 +2672,7 @@ var IngestionService = class {
2407
2672
  this.searchService = searchService;
2408
2673
  this.jobManager = jobManager;
2409
2674
  this.embeddingService = embeddingService;
2675
+ this.ontologyService = ontologyService;
2410
2676
  this.promptService = promptService ?? new PromptService(this.options.config?.prompts);
2411
2677
  }
2412
2678
  async ingestDocument(entityId, params) {
@@ -2431,23 +2697,33 @@ var IngestionService = class {
2431
2697
  if (chunks.length === 0) return { truncated: false, chunks: 0 };
2432
2698
  const chunkResults = await withConcurrency(
2433
2699
  chunks.map((chunk) => async () => {
2434
- const { systemPrompt, userPrompt } = this.promptService.buildIngestPrompt(chunk, params.promptOverride);
2700
+ const ontologyContext = await this.ontologyService?.buildPromptContext(entityId) ?? null;
2701
+ const { systemPrompt, userPrompt } = this.promptService.buildIngestPrompt(
2702
+ chunk,
2703
+ params.promptOverride,
2704
+ ontologyContext
2705
+ );
2435
2706
  const responseText = await this.options.llmProvider.generateText({ systemPrompt, userPrompt });
2436
2707
  const result = parseJsonResponse(responseText);
2437
- return (Array.isArray(result.facts) ? result.facts : []).map(validateFact).filter((f) => f !== null);
2708
+ return {
2709
+ facts: (Array.isArray(result.facts) ? result.facts : []).map(validateFact).filter((f) => f !== null),
2710
+ ontology_updates: result.ontology_updates
2711
+ };
2438
2712
  }),
2439
2713
  chunkConcurrency
2440
2714
  );
2441
2715
  const seen = /* @__PURE__ */ new Set();
2442
- const allValidFacts = [];
2443
- for (const facts of chunkResults) {
2444
- for (const fact of facts) {
2445
- const normalized = fact.title.trim().toLowerCase().replace(/\s+/g, " ");
2446
- if (!seen.has(normalized)) {
2447
- seen.add(normalized);
2448
- allValidFacts.push(fact);
2716
+ const orderedChunkFacts = [];
2717
+ for (const chunkResult of chunkResults) {
2718
+ const dedupedFacts = [];
2719
+ for (const fact of chunkResult.facts) {
2720
+ const normalizedTitle = normalizeTitleKey(fact.title);
2721
+ if (!seen.has(normalizedTitle)) {
2722
+ seen.add(normalizedTitle);
2723
+ dedupedFacts.push(fact);
2449
2724
  }
2450
2725
  }
2726
+ orderedChunkFacts.push({ facts: dedupedFacts, ontology_updates: chunkResult.ontology_updates });
2451
2727
  }
2452
2728
  const now = Date.now();
2453
2729
  const insertedFacts = [];
@@ -2455,26 +2731,63 @@ var IngestionService = class {
2455
2731
  await this.db.withTransactionAsync(async (tx) => {
2456
2732
  deletedSourceFactIds.push(...await this.entryRepo.findIdsBySource(entityId, sourceRef, null, tx, false));
2457
2733
  await this.entryRepo.softDeleteBySource(entityId, tx, sourceRef, null);
2458
- for (const fact of allValidFacts) {
2459
- const id = generateId("fact_");
2460
- const wikiFact = {
2461
- id,
2462
- entity_id: entityId,
2463
- title: fact.title,
2464
- body: fact.body,
2465
- tags: fact.tags,
2466
- confidence: fact.confidence,
2467
- source_type: "immutable_document",
2468
- source_hash: sourceHash,
2469
- source_ref: sourceRef,
2470
- created_at: now,
2471
- updated_at: now,
2472
- last_accessed_at: null,
2473
- access_count: 0,
2474
- deleted_at: null
2475
- };
2476
- await this.entryRepo.upsert(wikiFact, tx);
2477
- insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
2734
+ const titleIndex = /* @__PURE__ */ new Map();
2735
+ const pendingEdges = [];
2736
+ const existingFacts = await this.entryRepo.findRecentByEntityId(entityId, 500, tx);
2737
+ for (const existing of existingFacts) {
2738
+ titleIndex.set(normalizeTitleKey(existing.title), {
2739
+ id: existing.id,
2740
+ okf_type: existing.okf_type ?? null
2741
+ });
2742
+ }
2743
+ let ontologyState = await this.ontologyService?.getEffectiveState(entityId, tx) ?? { mode: "off", manifest: { node_types: [], edge_types: [] } };
2744
+ let { mode, manifest } = ontologyState;
2745
+ for (const { facts, ontology_updates } of orderedChunkFacts) {
2746
+ if (mode === "emergent" && ontology_updates && this.ontologyService) {
2747
+ manifest = await this.ontologyService.mergeEmergentUpdates(entityId, ontology_updates, tx);
2748
+ ontologyState = await this.ontologyService.getEffectiveState(entityId, tx);
2749
+ mode = ontologyState.mode;
2750
+ }
2751
+ for (const fact of facts) {
2752
+ const ontologyFact = fact;
2753
+ const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest) ?? { okf_type: null, edges: [] };
2754
+ const id = generateId("fact_");
2755
+ const wikiFact = {
2756
+ id,
2757
+ entity_id: entityId,
2758
+ title: fact.title,
2759
+ body: fact.body,
2760
+ tags: fact.tags,
2761
+ confidence: fact.confidence,
2762
+ source_type: "immutable_document",
2763
+ source_hash: sourceHash,
2764
+ source_ref: sourceRef,
2765
+ created_at: now,
2766
+ updated_at: now,
2767
+ last_accessed_at: null,
2768
+ access_count: 0,
2769
+ deleted_at: null,
2770
+ okf_type: normalized.okf_type
2771
+ };
2772
+ await this.entryRepo.upsert(wikiFact, tx);
2773
+ insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
2774
+ titleIndex.set(normalizeTitleKey(fact.title), { id, okf_type: normalized.okf_type });
2775
+ if (normalized.edges.length > 0) {
2776
+ pendingEdges.push({ sourceId: id, sourceType: normalized.okf_type, edges: normalized.edges });
2777
+ }
2778
+ }
2779
+ }
2780
+ for (const item of pendingEdges) {
2781
+ await this.ontologyService?.resolveAndPersistEdges(
2782
+ entityId,
2783
+ item.sourceId,
2784
+ item.sourceType,
2785
+ item.edges ?? [],
2786
+ manifest,
2787
+ titleIndex,
2788
+ tx,
2789
+ now
2790
+ );
2478
2791
  }
2479
2792
  });
2480
2793
  await this.searchService.sync(entityId);
@@ -2501,7 +2814,7 @@ var IngestionService = class {
2501
2814
  var FUZZY_THRESHOLD = 0.5;
2502
2815
  var MIN_TOKENS_TO_QUALIFY = 3;
2503
2816
  var MaintenanceService = class {
2504
- constructor(db, prefix, options, entryRepo, taskRepo, eventRepo, metadataRepo, searchService, jobManager, embeddingService, promptService) {
2817
+ constructor(db, prefix, options, entryRepo, taskRepo, eventRepo, metadataRepo, searchService, jobManager, embeddingService, promptService, ontologyService) {
2505
2818
  this.db = db;
2506
2819
  this.prefix = prefix;
2507
2820
  this.options = options;
@@ -2512,6 +2825,7 @@ var MaintenanceService = class {
2512
2825
  this.searchService = searchService;
2513
2826
  this.jobManager = jobManager;
2514
2827
  this.embeddingService = embeddingService;
2828
+ this.ontologyService = ontologyService;
2515
2829
  this.promptService = promptService ?? new PromptService(this.options.config?.prompts);
2516
2830
  }
2517
2831
  async runPrune(entityId, options) {
@@ -2734,21 +3048,36 @@ var MaintenanceService = class {
2734
3048
  tags: typeof rest.tags === "string" ? JSON.parse(rest.tags) : rest.tags
2735
3049
  };
2736
3050
  });
3051
+ const ontologyContext = await this.ontologyService?.buildPromptContext(entityId) ?? null;
2737
3052
  const { systemPrompt, userPrompt } = this.promptService.buildLibrarianPrompt(
2738
3053
  events.reverse(),
2739
3054
  currentFacts,
2740
- promptOverride
3055
+ promptOverride,
3056
+ ontologyContext
2741
3057
  );
2742
3058
  const responseText = await this.options.llmProvider.generateText({ systemPrompt, userPrompt });
2743
3059
  const result = parseJsonResponse(responseText);
2744
3060
  const facts = Array.isArray(result.facts) ? result.facts : [];
2745
3061
  const tasks = Array.isArray(result.tasks) ? result.tasks : [];
3062
+ const ontologyUpdates = result.ontology_updates;
2746
3063
  const validFacts = facts.map(validateFact).filter((f) => f !== null);
2747
3064
  const validTasks = tasks.map(validateTask).filter((t) => t !== null);
2748
3065
  const now = Date.now();
2749
3066
  const insertedFacts = [];
2750
3067
  await this.db.withTransactionAsync(async (tx) => {
3068
+ let { mode, manifest } = await this.ontologyService?.getEffectiveState(entityId, tx) ?? { mode: "off", manifest: { node_types: [], edge_types: [] } };
3069
+ if (mode === "emergent" && ontologyUpdates && this.ontologyService) {
3070
+ manifest = await this.ontologyService.mergeEmergentUpdates(entityId, ontologyUpdates, tx);
3071
+ }
3072
+ const titleIndex = /* @__PURE__ */ new Map();
3073
+ for (const existing of currentFactsRows) {
3074
+ titleIndex.set(normalizeTitleKey(existing.title), {
3075
+ id: existing.id,
3076
+ okf_type: existing.okf_type ?? null
3077
+ });
3078
+ }
2751
3079
  const factsForDedupe = await this.entryRepo.findRecentByEntityId(entityId, 100, tx);
3080
+ const pendingEdges = [];
2752
3081
  for (const fact of validFacts) {
2753
3082
  const newTokens = titleTokens(fact.title);
2754
3083
  let skip = false;
@@ -2765,6 +3094,8 @@ var MaintenanceService = class {
2765
3094
  }
2766
3095
  }
2767
3096
  if (skip) continue;
3097
+ const ontologyFact = fact;
3098
+ const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest) ?? { okf_type: null, edges: [] };
2768
3099
  const id = generateId("fact_");
2769
3100
  const factObj = {
2770
3101
  id,
@@ -2780,11 +3111,28 @@ var MaintenanceService = class {
2780
3111
  updated_at: now,
2781
3112
  last_accessed_at: null,
2782
3113
  access_count: 0,
2783
- deleted_at: null
3114
+ deleted_at: null,
3115
+ okf_type: normalized.okf_type
2784
3116
  };
2785
3117
  await this.entryRepo.upsert(factObj, tx);
2786
3118
  insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
2787
3119
  factsForDedupe.push(factObj);
3120
+ titleIndex.set(normalizeTitleKey(fact.title), { id, okf_type: normalized.okf_type });
3121
+ if (normalized.edges.length > 0) {
3122
+ pendingEdges.push({ sourceId: id, sourceType: normalized.okf_type, edges: normalized.edges });
3123
+ }
3124
+ }
3125
+ for (const item of pendingEdges) {
3126
+ await this.ontologyService?.resolveAndPersistEdges(
3127
+ entityId,
3128
+ item.sourceId,
3129
+ item.sourceType,
3130
+ item.edges ?? [],
3131
+ manifest,
3132
+ titleIndex,
3133
+ tx,
3134
+ now
3135
+ );
2788
3136
  }
2789
3137
  for (const task of validTasks) {
2790
3138
  const id = generateId("task_");
@@ -4121,6 +4469,145 @@ var WriteService = class {
4121
4469
  }
4122
4470
  };
4123
4471
 
4472
+ // src/prompts/ontology.ts
4473
+ var FACT_ONTOLOGY_FIELDS = `
4474
+ Each fact may optionally include:
4475
+ - "okf_type": string \u2014 must be one of the node_types in the manifest
4476
+ - "edges": [{ "edge_type": string, "target_title": string }] \u2014 target_title must match another fact's title in this response or existing memory`;
4477
+ var EMERGENT_EXTRA = `
4478
+ You may also return "ontology_updates" to propose new types:
4479
+ "ontology_updates": {
4480
+ "node_types": [{ "type": "slug", "description": "..." }],
4481
+ "edge_types": [{ "type": "slug", "source_type": "...", "target_type": "...", "description": "..." }]
4482
+ }
4483
+ Only propose types not already in the manifest. Do not redefine existing types.`;
4484
+ function buildOntologyPromptAppendix(mode, manifestJson) {
4485
+ const strictRules = mode === "strict" ? "STRICT MODE: Use ONLY types defined in the manifest. If a fact does not fit any node_type, omit okf_type and edges entirely." : "EMERGENT MODE: Prefer manifest types. You may propose new types via ontology_updates when necessary.";
4486
+ const fallback = "If okf_type or an edge is invalid, omit them rather than inventing unlisted types.";
4487
+ const ontologyModeInstructions = [
4488
+ "## Ontology constraints",
4489
+ strictRules,
4490
+ fallback,
4491
+ FACT_ONTOLOGY_FIELDS,
4492
+ mode === "emergent" ? EMERGENT_EXTRA : "",
4493
+ "Manifest:",
4494
+ manifestJson
4495
+ ].filter(Boolean).join("\n");
4496
+ return { ontologyManifest: manifestJson, ontologyModeInstructions };
4497
+ }
4498
+
4499
+ // src/services/OntologyService.ts
4500
+ var OntologyService = class {
4501
+ constructor(metadataRepo, edgeRepo, ontologyConfig) {
4502
+ this.metadataRepo = metadataRepo;
4503
+ this.edgeRepo = edgeRepo;
4504
+ this.ontologyConfig = ontologyConfig;
4505
+ this.cache = /* @__PURE__ */ new Map();
4506
+ }
4507
+ resolveMode(storedMode) {
4508
+ return storedMode ?? this.ontologyConfig?.mode ?? "off";
4509
+ }
4510
+ invalidateCache(entityId) {
4511
+ this.cache.delete(entityId);
4512
+ }
4513
+ async getEffectiveState(entityId, tx) {
4514
+ if (!tx) {
4515
+ const cached = this.cache.get(entityId);
4516
+ if (cached) return cached;
4517
+ }
4518
+ const row = await this.metadataRepo.getManifest(entityId, tx);
4519
+ if (row) {
4520
+ const state = { mode: this.resolveMode(row.mode), manifest: row.manifest };
4521
+ if (!tx) this.cache.set(entityId, state);
4522
+ return state;
4523
+ }
4524
+ const seed = this.ontologyConfig?.seedManifests?.[entityId];
4525
+ if (seed) {
4526
+ const state = {
4527
+ mode: this.resolveMode(seed.mode),
4528
+ manifest: seed.manifest
4529
+ };
4530
+ if (tx) {
4531
+ await this.metadataRepo.setManifest(entityId, state, tx);
4532
+ } else {
4533
+ this.cache.set(entityId, state);
4534
+ }
4535
+ return state;
4536
+ }
4537
+ return { mode: "off", manifest: emptyManifest() };
4538
+ }
4539
+ async buildPromptContext(entityId) {
4540
+ const { mode, manifest } = await this.getEffectiveState(entityId);
4541
+ if (mode === "off") return null;
4542
+ const manifestJson = JSON.stringify(manifest, null, 2);
4543
+ return buildOntologyPromptAppendix(mode, manifestJson);
4544
+ }
4545
+ async mergeEmergentUpdates(entityId, updates, tx) {
4546
+ const merged = await this.metadataRepo.mergeManifestUpdates(entityId, updates, tx);
4547
+ this.invalidateCache(entityId);
4548
+ return merged;
4549
+ }
4550
+ validateAndNormalizeFact(fact, manifest) {
4551
+ const rawType = typeof fact.okf_type === "string" ? fact.okf_type : "";
4552
+ const canonical = resolveNodeType(rawType, manifest);
4553
+ if (!canonical) return { okf_type: null, edges: [] };
4554
+ const edges = validateInlineEdges(canonical, null, fact.edges ?? [], manifest);
4555
+ return { okf_type: canonical, edges };
4556
+ }
4557
+ async resolveAndPersistEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, tx, now) {
4558
+ if (!sourceType || edges.length === 0) return;
4559
+ for (const edge of edges) {
4560
+ const def = resolveEdgeDefinition(edge.edge_type, manifest);
4561
+ if (!def || def.source_type.toLowerCase() !== sourceType.toLowerCase()) continue;
4562
+ const targetKey = normalizeTitleKey(edge.target_title);
4563
+ const target = titleIndex.get(targetKey);
4564
+ if (!target) continue;
4565
+ if (def.target_type.toLowerCase() !== (target.okf_type ?? "").toLowerCase()) continue;
4566
+ const wikiEdge = {
4567
+ id: generateId(),
4568
+ entity_id: entityId,
4569
+ source_id: sourceId,
4570
+ target_id: target.id,
4571
+ edge_type: def.type,
4572
+ created_at: now
4573
+ };
4574
+ await this.edgeRepo.addIgnoreDuplicate(wikiEdge, tx);
4575
+ }
4576
+ }
4577
+ };
4578
+
4579
+ // src/services/GraphTraversalService.ts
4580
+ var GraphTraversalService = class {
4581
+ constructor(edgeRepo, entryRepo, config) {
4582
+ this.edgeRepo = edgeRepo;
4583
+ this.entryRepo = entryRepo;
4584
+ this.config = config;
4585
+ }
4586
+ async traverseGraph(entityId, options) {
4587
+ const fallbackMaxNodes = 20;
4588
+ const rawConfigDefault = this.config.maxTraversalNodes ?? fallbackMaxNodes;
4589
+ const defaultMaxNodes = Number.isFinite(rawConfigDefault) && rawConfigDefault >= 1 ? Math.floor(rawConfigDefault) : fallbackMaxNodes;
4590
+ const rawMaxNodes = options.maxTraversalNodes ?? defaultMaxNodes;
4591
+ const maxNodes = Number.isFinite(rawMaxNodes) && rawMaxNodes >= 1 ? Math.floor(rawMaxNodes) : defaultMaxNodes;
4592
+ const opts = {
4593
+ maxDepth: Math.max(1, Math.min(options.maxDepth ?? 1, 3)),
4594
+ direction: options.direction ?? this.config.traversalDirection ?? "both",
4595
+ edgeTypes: options.edgeTypes,
4596
+ minConfidence: options.minTraversalConfidence ?? this.config.minTraversalConfidence ?? "tentative",
4597
+ excludeSourceTypes: options.excludeSourceTypes ?? this.config.excludeSourceTypes ?? [],
4598
+ maxNodes
4599
+ };
4600
+ const { nodeIds, edges } = await this.edgeRepo.getNeighborhood(entityId, options.sourceId, opts);
4601
+ if (nodeIds.length === 0) return { nodes: [], edges: [] };
4602
+ const nodes = await this.entryRepo.findByIds(nodeIds, [entityId]);
4603
+ const hydratedIds = new Set(nodes.map((node) => node.id));
4604
+ const filteredEdges = edges.filter(
4605
+ (edge) => hydratedIds.has(edge.source_id) && hydratedIds.has(edge.target_id)
4606
+ );
4607
+ return { nodes, edges: filteredEdges };
4608
+ }
4609
+ };
4610
+
4124
4611
  // src/WikiMemory.ts
4125
4612
  var TABLE_PREFIX_PATTERN = /^[A-Za-z][A-Za-z0-9_]{0,30}_$/;
4126
4613
  var _testAccessNonTestEnvWarned;
@@ -4142,6 +4629,11 @@ var WikiMemory = class {
4142
4629
  this.eventRepo = new EventRepository(db, this.prefix);
4143
4630
  this.edgeRepo = new EdgeRepository(db, this.prefix);
4144
4631
  this.metadataRepo = new MetadataRepository(db, this.prefix);
4632
+ this.ontologyService = new OntologyService(
4633
+ this.metadataRepo,
4634
+ this.edgeRepo,
4635
+ options.config?.ontology
4636
+ );
4145
4637
  this.embeddingService = new EmbeddingService(this.db, this.options, this.entryRepo, this.metadataRepo);
4146
4638
  this.searchService = new SearchService(this.entryRepo);
4147
4639
  this.jobManager = new JobManager(this.prefix);
@@ -4154,7 +4646,8 @@ var WikiMemory = class {
4154
4646
  this.searchService,
4155
4647
  this.jobManager,
4156
4648
  this.embeddingService,
4157
- this.promptService
4649
+ this.promptService,
4650
+ this.ontologyService
4158
4651
  );
4159
4652
  this.maintenanceService = new MaintenanceService(
4160
4653
  this.db,
@@ -4167,7 +4660,8 @@ var WikiMemory = class {
4167
4660
  this.searchService,
4168
4661
  this.jobManager,
4169
4662
  this.embeddingService,
4170
- this.promptService
4663
+ this.promptService,
4664
+ this.ontologyService
4171
4665
  );
4172
4666
  this.importExportService = new ImportExportService(
4173
4667
  this.db,
@@ -4197,6 +4691,11 @@ var WikiMemory = class {
4197
4691
  this.jobManager,
4198
4692
  this.maintenanceService
4199
4693
  );
4694
+ this.graphTraversalService = new GraphTraversalService(
4695
+ this.edgeRepo,
4696
+ this.entryRepo,
4697
+ this.options.config ?? {}
4698
+ );
4200
4699
  }
4201
4700
  /**
4202
4701
  * Explicit escape hatch for test suites: typed access to composed services for mocks/spies.
@@ -4217,6 +4716,7 @@ var WikiMemory = class {
4217
4716
  searchService: this.searchService,
4218
4717
  writeService: this.writeService,
4219
4718
  promptService: this.promptService,
4719
+ graphTraversalService: this.graphTraversalService,
4220
4720
  entryRepo: this.entryRepo,
4221
4721
  metadataRepo: this.metadataRepo,
4222
4722
  jobManager: this.jobManager
@@ -4287,6 +4787,9 @@ var WikiMemory = class {
4287
4787
  async read(entityId, query, options) {
4288
4788
  return this.retrievalService.read(entityId, query, options);
4289
4789
  }
4790
+ async traverseGraph(entityId, options) {
4791
+ return this.graphTraversalService.traverseGraph(entityId, options);
4792
+ }
4290
4793
  async getMemoryBundle(entityId) {
4291
4794
  return this.importExportService.getFullBundle(entityId, { maxEvents: 10 });
4292
4795
  }
@@ -4362,6 +4865,34 @@ var WikiMemory = class {
4362
4865
  async markOutboxEventsProcessed(eventIds) {
4363
4866
  await this.outboxRepo.acknowledge(eventIds);
4364
4867
  }
4868
+ /**
4869
+ * Returns the effective ontology mode and manifest for an entity.
4870
+ * Resolution order: persisted DB row → `WikiConfig.ontology.seedManifests[entityId]` → `null`.
4871
+ */
4872
+ async getOntologyManifest(entityId) {
4873
+ const row = await this.metadataRepo.getManifest(entityId);
4874
+ if (row) return { mode: this.ontologyService.resolveMode(row.mode), manifest: row.manifest };
4875
+ const seed = this.options.config?.ontology?.seedManifests?.[entityId];
4876
+ if (seed) {
4877
+ return {
4878
+ mode: this.ontologyService.resolveMode(seed.mode),
4879
+ manifest: seed.manifest
4880
+ };
4881
+ }
4882
+ return null;
4883
+ }
4884
+ /**
4885
+ * Seeds or replaces an entity's ontology manifest and optional mode override.
4886
+ * Validates manifest invariants (unique type slugs, edge endpoints reference node types).
4887
+ * Invalidates the in-memory ontology cache for this entity.
4888
+ */
4889
+ async setOntologyManifest(entityId, manifest, options) {
4890
+ const mode = options?.mode ?? this.ontologyService.resolveMode();
4891
+ await this.db.withTransactionAsync(
4892
+ (tx) => this.metadataRepo.setManifest(entityId, { mode, manifest }, tx)
4893
+ );
4894
+ this.ontologyService.invalidateCache(entityId);
4895
+ }
4365
4896
  };
4366
4897
  _testAccessNonTestEnvWarned = new WeakMap();
4367
4898
 
@@ -4486,6 +5017,38 @@ function formatContext(bundle, options) {
4486
5017
  return lines.join("\n");
4487
5018
  }
4488
5019
 
5020
+ // src/utils/formatGraphContext.ts
5021
+ function formatGraphContext(neighborhood) {
5022
+ const { nodes, edges } = neighborhood;
5023
+ if (nodes.length === 0) return "";
5024
+ const nodeById = new Map(nodes.map((n) => [n.id, n]));
5025
+ const nodeIndex = new Map(nodes.map((n, i) => [n.id, i]));
5026
+ const lines = [];
5027
+ for (const node of nodes) {
5028
+ lines.push(`[${node.okf_type ?? "fact"}] ${node.title} (ID: ${node.id})`);
5029
+ const outbound = edgesByDirection(edges, node.id, "source_id", nodeById, nodeIndex);
5030
+ const inbound = edgesByDirection(edges, node.id, "target_id", nodeById, nodeIndex);
5031
+ for (const { edge, other } of outbound) {
5032
+ lines.push(` -[${edge.edge_type}]-> [${other.okf_type ?? "fact"}] ${other.title}`);
5033
+ }
5034
+ for (const { edge, other } of inbound) {
5035
+ lines.push(` <-[${edge.edge_type}]- [${other.okf_type ?? "fact"}] ${other.title}`);
5036
+ }
5037
+ }
5038
+ return lines.join("\n");
5039
+ }
5040
+ function edgesByDirection(edges, nodeId, endpoint, nodeById, nodeIndex) {
5041
+ const otherEndpoint = endpoint === "source_id" ? "target_id" : "source_id";
5042
+ return edges.filter((e) => e[endpoint] === nodeId).filter((e) => {
5043
+ const otherId = e[otherEndpoint];
5044
+ const selfIdx = nodeIndex.get(nodeId);
5045
+ const otherIdx = nodeIndex.get(otherId);
5046
+ return otherIdx !== void 0 && selfIdx < otherIdx;
5047
+ }).map((edge) => ({ edge, other: nodeById.get(edge[otherEndpoint]) })).sort(
5048
+ (a, b) => a.edge.edge_type.localeCompare(b.edge.edge_type) || a.other.title.localeCompare(b.other.title) || a.other.id.localeCompare(b.other.id) || a.edge.id.localeCompare(b.edge.id)
5049
+ );
5050
+ }
5051
+
4489
5052
  // src/utils/sanitizeForFilename.ts
4490
5053
  function shortHash(value) {
4491
5054
  let h1 = 5381;
@@ -5002,6 +5565,7 @@ exports.WikiBusyError = WikiBusyError;
5002
5565
  exports.WikiMemory = WikiMemory;
5003
5566
  exports.createWiki = createWiki;
5004
5567
  exports.formatContext = formatContext;
5568
+ exports.formatGraphContext = formatGraphContext;
5005
5569
  exports.formatMemoryDump = formatMemoryDump;
5006
5570
  exports.formatOkfBundle = formatOkfBundle;
5007
5571
  exports.hydrateLibrarianPrompt = hydrateLibrarianPrompt;