@equationalapplications/core-llm-wiki 4.14.1 → 4.15.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.
package/README.md CHANGED
@@ -20,6 +20,7 @@ Platform-agnostic TypeScript engine for hybrid LLM memory. Features episodic fac
20
20
  - **Immutable vs mutable facts** — Use `WikiFact.source_type` to distinguish document-sourced facts (`immutable_document`) from derived or user-provided facts (`librarian_inferred`, `user_stated`, `user_confirmed`). Immutable document facts are not rewritten by `runLibrarian()` or `runHeal()` and can only be removed by `forget()` or re-ingesting.
21
21
  - **Full-featured memory** — Facts, tasks, events, maintenance jobs (librarian, heal, reembed, prune)
22
22
  - **Type-safe** — Built with TypeScript, full type exports
23
+ - **Interoperability:** Supports [Open Knowledge Format (OKF) v0.1](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf) import and export.
23
24
 
24
25
  ## Installation
25
26
 
@@ -444,6 +445,58 @@ Notes:
444
445
  - A throwing callback is caught (logged via `console.error`) and does not block other subscribers or the underlying job.
445
446
  - Subscriptions are scoped to a single `entityId`. There is no wildcard or "all entities" form.
446
447
 
448
+ ## OKF Import/Export
449
+
450
+ The core package integrates with `@equationalapplications/core-okf` to seamlessly adapt wiki data dumps to and from Open Knowledge Format (OKF) v0.1 bundles.
451
+
452
+ ### Exporting an OKF Bundle
453
+
454
+ Convert an existing wiki dump into a flat array of OKF files, ready to be written to disk or zipped:
455
+
456
+ ```typescript
457
+ import { formatOkfBundle } from '@equationalapplications/core-llm-wiki';
458
+
459
+ const dump = await wiki.exportDump(['entity-123']);
460
+ const { files } = formatOkfBundle(dump);
461
+
462
+ // files: Array<{ path: string; content: string }>
463
+ // e.g., [{ path: 'entities/entity-123/facts/fact_abc.md', content: '---\n...' }]
464
+ ```
465
+
466
+ ### Importing an OKF Bundle
467
+
468
+ Parse raw OKF files back into a `MemoryDump` that the wiki can ingest:
469
+
470
+ ```typescript
471
+ import { parseOkfBundle } from '@equationalapplications/core-llm-wiki';
472
+
473
+ // Assuming you read OKF files for this entity (e.g. under `entities/entity-123/`) from disk/zip into OkfFile[] shape
474
+ const dump = parseOkfBundle('entity-123', files, {
475
+ defaultSchema: 'fact',
476
+ typeMapping: {
477
+ 'custom_type': 'fact',
478
+ 'archived': 'ignore', // Skips these concepts
479
+ },
480
+ });
481
+
482
+ await wiki.importDump(dump, { merge: true });
483
+ ```
484
+
485
+ **Routing Precedence:** Concepts are routed into either the `entries` (facts) or `tasks` tables based on a three-step fallback:
486
+
487
+ 1. `OkfImportOptions.typeMapping` explicitly mapping an OKF `type` to `'fact'`, `'task'`, or `'ignore'`.
488
+ 2. Directory convention (e.g., files in `/facts/` become facts, `/tasks/` become tasks).
489
+ 3. The `OkfImportOptions.defaultSchema` (defaults to `'fact'`).
490
+
491
+ ### WikiEdge and Markdown Links
492
+
493
+ A `WikiEdge` represents a markdown cross-link found inside a concept body, resolved to a `source_id` and `target_id`.
494
+ Edges automatically round-trip during OKF import and export. Because the markdown body is the source of truth for edges in the OKF spec, edges are extracted during `parseOkfBundle()` and persisted via the `EdgeRepository` on `importDump()` — there is no separate edge export step required. The `edges` array is included in bundles returned by `getMemoryBundle()` / `exportDump()` (not by `read()`).
495
+
496
+ ### The `okf_type` Field
497
+
498
+ Facts and tasks include a nullable `okf_type` column. This preserves the literal OKF `type` string from an imported bundle frontmatter, independent of whether the item was routed to the `entries` or `tasks` table. When `formatOkfBundle` runs, it restores this specific string, falling back to `'fact'` or `'task'` if the field is null (ensuring non-imported rows export cleanly).
499
+
447
500
  ## Security
448
501
 
449
502
  `@equationalapplications/core-llm-wiki` enforces multiple security layers:
@@ -706,13 +759,14 @@ The flowchart shows:
706
759
 
707
760
  ## Monorepo Ecosystem
708
761
 
709
- | Package | Description |
710
- |---------|-------------|
711
- | **`@equationalapplications/core-llm-wiki`** | Pure TypeScript core — DB-agnostic, bring your own SQLite adapter |
712
- | [`@equationalapplications/expo-llm-wiki`](https://www.npmjs.com/package/@equationalapplications/expo-llm-wiki) | Expo / React Native adapter with `expo-sqlite` |
713
- | [`@equationalapplications/react-llm-wiki`](https://www.npmjs.com/package/@equationalapplications/react-llm-wiki) | React hooks + web adapter with `sql.js` |
714
- | [`@equationalapplications/prisma-outbox`](https://www.npmjs.com/package/@equationalapplications/prisma-outbox) | Sync SQLite outbox events to Prisma in a transaction |
715
- | [`@equationalapplications/core-llm-tools`](https://www.npmjs.com/package/@equationalapplications/core-llm-tools) | Platform-agnostic Gemini tool schemas + capability scope injector |
762
+ | Package | Purpose |
763
+ | ----- | ----- |
764
+ | **@equationalapplications/core-llm-wiki** | Persistent episodic memory |
765
+ | [@equationalapplications/expo-llm-wiki](https://github.com/equationalapplications/expo-llm-wiki/blob/main/packages/expo/README.md) | Persistent episodic memory for Expo/React Native |
766
+ | [@equationalapplications/react-llm-wiki](https://github.com/equationalapplications/expo-llm-wiki/blob/main/packages/react/README.md) | Persistent episodic memory for Web |
767
+ | [@equationalapplications/prisma-outbox](https://github.com/equationalapplications/expo-llm-wiki/blob/main/packages/prisma-outbox/README.md) | Sync SQLite outbox events to Prisma |
768
+ | [@equationalapplications/core-llm-tools](https://github.com/equationalapplications/expo-llm-wiki/blob/main/packages/core-llm-tools/README.md) | Gemini tool schemas and capability injector |
769
+ | [@equationalapplications/core-okf](https://github.com/equationalapplications/expo-llm-wiki/blob/main/packages/okf/README.md) | Zero-dependency Open Knowledge Format (OKF) v0.1 primitives — parse and produce interoperable knowledge bundles. |
716
770
 
717
771
  ## License
718
772
 
@@ -1428,11 +1428,12 @@ var MAX_EMBEDDING_BLOB_BYTES = 32 * 1024;
1428
1428
  var IMPORT_TITLE_MAX = 500;
1429
1429
  var IMPORT_BODY_MAX = 8e3;
1430
1430
  var ImportExportService = class {
1431
- constructor(db, entryRepo, taskRepo, eventRepo, metadataRepo, searchService, jobManager, embeddingService) {
1431
+ constructor(db, entryRepo, taskRepo, eventRepo, edgeRepo, metadataRepo, searchService, jobManager, embeddingService) {
1432
1432
  this.db = db;
1433
1433
  this.entryRepo = entryRepo;
1434
1434
  this.taskRepo = taskRepo;
1435
1435
  this.eventRepo = eventRepo;
1436
+ this.edgeRepo = edgeRepo;
1436
1437
  this.metadataRepo = metadataRepo;
1437
1438
  this.searchService = searchService;
1438
1439
  this.jobManager = jobManager;
@@ -1477,10 +1478,11 @@ var ImportExportService = class {
1477
1478
  }
1478
1479
  }
1479
1480
  async getFullBundle(entityId, opts) {
1480
- const [factsRaw, tasks, events] = await Promise.all([
1481
+ const [factsRaw, tasks, events, edges] = await Promise.all([
1481
1482
  opts?.includeBlobs ? this.entryRepo.findAllByEntityIdWithBlobs(entityId) : this.entryRepo.findAllByEntityId(entityId),
1482
1483
  this.taskRepo.findAllByEntityId(entityId),
1483
- this.eventRepo.getByEntityId(entityId, opts?.maxEvents)
1484
+ this.eventRepo.getByEntityId(entityId, opts?.maxEvents),
1485
+ this.edgeRepo.getByEntityId(entityId)
1484
1486
  ]);
1485
1487
  const facts = factsRaw.map((f) => {
1486
1488
  const {
@@ -1499,7 +1501,7 @@ var ImportExportService = class {
1499
1501
  tags: typeof factBase.tags === "string" ? JSON.parse(factBase.tags) : factBase.tags
1500
1502
  };
1501
1503
  });
1502
- return { facts, tasks, events };
1504
+ return { facts, tasks, events, edges };
1503
1505
  }
1504
1506
  /** Single-entity import transaction + post-processing; package-internal hook for tests. */
1505
1507
  async doImportEntity(entityId, bundle, merge) {
@@ -1521,6 +1523,7 @@ var ImportExportService = class {
1521
1523
  softDeletedFactIds.push(...deletedLiveFactIds);
1522
1524
  await this.entryRepo.bulkSoftDeleteByEntityId(entityId, tx);
1523
1525
  await this.taskRepo.bulkSoftDeleteByEntityId(entityId, tx);
1526
+ await this.edgeRepo.bulkDeleteByEntityId(entityId, tx);
1524
1527
  await this.metadataRepo.deleteCheckpoint(entityId, tx);
1525
1528
  }
1526
1529
  const factIds = bundle.facts.map((fact) => fact.id);
@@ -1618,7 +1621,8 @@ var ImportExportService = class {
1618
1621
  last_accessed_at: fact.last_accessed_at,
1619
1622
  access_count: fact.access_count,
1620
1623
  deleted_at: fact.deleted_at,
1621
- embedding_blob: blobData ?? void 0
1624
+ embedding_blob: blobData ?? void 0,
1625
+ okf_type: fact.okf_type ?? null
1622
1626
  };
1623
1627
  await this.entryRepo.upsertForImport(factObj, tx);
1624
1628
  if (blobData != null) {
@@ -1667,7 +1671,8 @@ var ImportExportService = class {
1667
1671
  created_at: task.created_at,
1668
1672
  updated_at: safeUpdatedAt,
1669
1673
  resolved_at: task.resolved_at,
1670
- deleted_at: task.deleted_at
1674
+ deleted_at: task.deleted_at,
1675
+ okf_type: task.okf_type ?? null
1671
1676
  },
1672
1677
  tx,
1673
1678
  safeUpdatedAt
@@ -1691,6 +1696,19 @@ var ImportExportService = class {
1691
1696
  tx
1692
1697
  );
1693
1698
  }
1699
+ for (const edge of bundle.edges ?? []) {
1700
+ await this.edgeRepo.addIgnoreDuplicate(
1701
+ {
1702
+ id: edge.id,
1703
+ entity_id: entityId,
1704
+ source_id: edge.source_id,
1705
+ target_id: edge.target_id,
1706
+ edge_type: edge.edge_type,
1707
+ created_at: edge.created_at
1708
+ },
1709
+ tx
1710
+ );
1711
+ }
1694
1712
  });
1695
1713
  await this.searchService.sync(entityId);
1696
1714
  for (const fact of bundle.facts) {
@@ -2007,7 +2025,7 @@ var RetrievalService = class {
2007
2025
  const sanitizedTierWeights = shouldExposeReadMetadata(entityId) ? sanitizeTierWeights(entityIds, options?.tierWeights) : void 0;
2008
2026
  const exposeMetadata = shouldExposeReadMetadata(entityId);
2009
2027
  if (entityIds.length === 0) {
2010
- const empty = { facts: [], tasks: [], events: [] };
2028
+ const empty = { facts: [], tasks: [], events: [], edges: [] };
2011
2029
  if (exposeMetadata) {
2012
2030
  empty.metadata = { query, entityIds: [] };
2013
2031
  if (sanitizedTierWeights && Object.keys(sanitizedTierWeights).length > 0) empty.metadata.tierWeights = sanitizedTierWeights;
@@ -2397,7 +2415,7 @@ var RetrievalService = class {
2397
2415
  if (exposeMetadata && trimmedQuery && scoreByFactId) {
2398
2416
  factScores = Object.fromEntries(facts.map((fact) => [fact.id, scoreByFactId.get(fact.id) ?? 0]));
2399
2417
  }
2400
- const bundle = { facts, tasks, events: events.reverse() };
2418
+ const bundle = { facts, tasks, events: events.reverse(), edges: [] };
2401
2419
  if (exposeMetadata) {
2402
2420
  bundle.metadata = { query, entityIds };
2403
2421
  if (sanitizedTierWeights && Object.keys(sanitizedTierWeights).length > 0) bundle.metadata.tierWeights = sanitizedTierWeights;
@@ -2597,5 +2615,5 @@ var WriteService = class {
2597
2615
  };
2598
2616
 
2599
2617
  export { EmbeddingService, HOOK_TIMEOUT_MARKER, ImportExportService, IngestionService, JobManager, MaintenanceService, PromptService, PrunePartialFailureError, RetrievalService, SearchService, WikiBusyError, WriteService, __privateAdd, __privateGet, __privateSet, generateId, normalizeSourceHash, normalizeSourceRef, parseEmbedding };
2600
- //# sourceMappingURL=chunk-24ANTHZB.mjs.map
2601
- //# sourceMappingURL=chunk-24ANTHZB.mjs.map
2618
+ //# sourceMappingURL=chunk-J4GBC6CP.mjs.map
2619
+ //# sourceMappingURL=chunk-J4GBC6CP.mjs.map