@equationalapplications/core-llm-wiki 7.8.0 → 7.9.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/dist/testing.js CHANGED
@@ -1,11 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  var coreOkf = require('@equationalapplications/core-okf');
4
- var MiniSearch = require('minisearch');
5
-
6
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
-
8
- var MiniSearch__default = /*#__PURE__*/_interopDefault(MiniSearch);
9
4
 
10
5
  // src/db/sqliteCodes.ts
11
6
  var SQLITE_RESULT_CODE_NAMES = {
@@ -3183,7 +3178,7 @@ async function gateCandidates(deps, ctx, candidates, cfg) {
3183
3178
  }
3184
3179
  }
3185
3180
  if (!ranked) {
3186
- ranked = deps.searchService.searchKeyword(`${c.fact.title} ${c.fact.body}`, [entityId], cfg.k + nonLive.size).filter((h) => !nonLive.has(h.id)).slice(0, cfg.k).map((h) => ({ id: h.id, score: null }));
3181
+ ranked = (await deps.searchService.searchKeyword(`${c.fact.title} ${c.fact.body}`, [entityId], cfg.k + nonLive.size)).filter((h) => !nonLive.has(h.id)).slice(0, cfg.k).map((h) => ({ id: h.id, score: null }));
3187
3182
  }
3188
3183
  const facts = ranked.length > 0 ? await deps.entryRepo.findByIds(ranked.map((r) => r.id), [entityId]) : [];
3189
3184
  const byId = new Map(facts.map((f) => [f.id, f]));
@@ -4634,7 +4629,7 @@ var MaintenanceService = class {
4634
4629
  if (!query) return [];
4635
4630
  const cached = cache?.get(query);
4636
4631
  if (cached) return cached;
4637
- const hits = this.searchService.searchKeyword(
4632
+ const hits = await this.searchService.searchKeyword(
4638
4633
  query,
4639
4634
  [entityId],
4640
4635
  cap * HEAL_ANCHOR_SEARCH_OVERFETCH
@@ -4897,7 +4892,7 @@ var RetrievalService = class {
4897
4892
  let miniSearchScores;
4898
4893
  if (effectivePreFilterLimit !== void 0) {
4899
4894
  populateCache = false;
4900
- const preResults = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, Number.MAX_SAFE_INTEGER).filter((r) => !excludedIds.has(r.id));
4895
+ const preResults = (await this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, Number.MAX_SAFE_INTEGER)).filter((r) => !excludedIds.has(r.id));
4901
4896
  if (preResults.length === 0) {
4902
4897
  candidateRows = null;
4903
4898
  } else {
@@ -4924,7 +4919,7 @@ var RetrievalService = class {
4924
4919
  candidateRows = this._withoutIds(await this.entryRepo.findWithEmbeddingsByEntityIds(scoredEntityIds), excludedIds);
4925
4920
  }
4926
4921
  if (weight !== void 0 && weight < 1) {
4927
- miniSearchScores = this.searchService.getMiniSearchScores(trimmedQuery, scoredEntityIds);
4922
+ miniSearchScores = await this.searchService.getKeywordScores(trimmedQuery, scoredEntityIds);
4928
4923
  }
4929
4924
  }
4930
4925
  if (candidateRows === null) {
@@ -5167,7 +5162,7 @@ var RetrievalService = class {
5167
5162
  const hasActiveFloorsKeywordFallback = sanitizedTierFloors !== void 0 && Object.values(sanitizedTierFloors).some((f) => f > 0);
5168
5163
  const keywordOversampledLimit = hasActiveFloorsKeywordFallback ? Number.MAX_SAFE_INTEGER : padLimit(Math.max(maxResults * 2, maxResults + 50));
5169
5164
  const preFilteredIds = effectivePreFilterLimit !== void 0 ? new Set(candidateRows.map((r) => r.id)) : void 0;
5170
- const keywordResults = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, keywordOversampledLimit);
5165
+ const keywordResults = await this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, keywordOversampledLimit);
5171
5166
  const topResults = preFilteredIds === void 0 ? keywordResults : keywordResults.filter((r) => preFilteredIds.has(r.id));
5172
5167
  const topResultIds = new Set(topResults.map((r) => r.id));
5173
5168
  const candidateMap = new Map(candidateRows.filter((r) => topResultIds.has(r.id)).map((row) => [row.id, row]));
@@ -5270,7 +5265,7 @@ var RetrievalService = class {
5270
5265
  if (!usedEmbed && scoredEntityIds.length > 0) {
5271
5266
  const hasActiveFloors = sanitizedTierFloors !== void 0 && Object.values(sanitizedTierFloors).some((f) => f > 0);
5272
5267
  const fallbackOversampledLimit = hasActiveFloors ? Number.MAX_SAFE_INTEGER : padLimit(Math.max(maxResults * 2, maxResults + 50));
5273
- const results = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, fallbackOversampledLimit).filter((r) => !excludedIds.has(r.id));
5268
+ const results = (await this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, fallbackOversampledLimit)).filter((r) => !excludedIds.has(r.id));
5274
5269
  const candidates = results.map((r) => ({
5275
5270
  id: r.id,
5276
5271
  entity_id: r.entity_id,
@@ -5407,10 +5402,29 @@ var RetrievalService = class {
5407
5402
  return scored;
5408
5403
  }
5409
5404
  };
5405
+
5406
+ // src/utils/indexDoc.ts
5407
+ function toIndexDoc(row) {
5408
+ return {
5409
+ id: row.id,
5410
+ entity_id: row.entity_id,
5411
+ title: row.title,
5412
+ body: row.body,
5413
+ tags: (() => {
5414
+ try {
5415
+ const parsed = JSON.parse(row.tags);
5416
+ return Array.isArray(parsed) ? parsed.join(" ") : row.tags;
5417
+ } catch {
5418
+ return row.tags;
5419
+ }
5420
+ })()
5421
+ };
5422
+ }
5423
+
5424
+ // src/services/SearchService.ts
5410
5425
  var _SearchService = class _SearchService {
5411
- constructor(entryRepo) {
5426
+ constructor(entryRepo, indexStrategy) {
5412
5427
  this.entryRepo = entryRepo;
5413
- this.miniSearchEntryIdsByEntity = /* @__PURE__ */ new Map();
5414
5428
  this.vectorCache = /* @__PURE__ */ new Map();
5415
5429
  /**
5416
5430
  * Serializes rebuilds. `rebuildIndex` awaits a repository read between
@@ -5436,35 +5450,13 @@ var _SearchService = class _SearchService {
5436
5450
  * its flag must outlive the turn (#233 review).
5437
5451
  */
5438
5452
  this.staleEpochs = /* @__PURE__ */ new Map();
5439
- this.miniSearch = this.createMiniSearch();
5440
- }
5441
- /**
5442
- * A fresh index with the production options. clearAll() swaps one in because
5443
- * MiniSearch.removeAll() empties the index but leaves dirtCount (and its
5444
- * vacuum bookkeeping) at its old value, which would trip syncEntries'
5445
- * conditional vacuum early after a clear.
5446
- */
5447
- createMiniSearch() {
5448
- return new MiniSearch__default.default({
5449
- fields: ["title", "body", "tags"],
5450
- storeFields: ["entity_id"],
5451
- // Vacuuming is driven explicitly at the end of each serialized rebuild
5452
- // (see sync). Auto-vacuum fires on its own schedule, asynchronously with
5453
- // respect to the caller, and traversing the tree mid-rebuild is what
5454
- // threw the uncaught TypeError in MiniSearch.performVacuuming (#64).
5455
- autoVacuum: false,
5456
- searchOptions: {
5457
- boost: { title: 2 },
5458
- fuzzy: 0.2,
5459
- prefix: true
5460
- }
5461
- });
5453
+ this.indexStrategy = indexStrategy;
5462
5454
  }
5463
5455
  /**
5464
5456
  * Rebuilds the search index and clears the vector cache for a given entity.
5465
5457
  * A direct replacement for manually syncing state after a DB transaction.
5466
5458
  *
5467
- * Rebuilds are serialized per instance and never reject: the MiniSearch index
5459
+ * Rebuilds are serialized per instance and never reject: the keyword index
5468
5460
  * is a rebuildable cache over SQLite, so degraded keyword search is the
5469
5461
  * correct failure mode and killing the host process is not.
5470
5462
  */
@@ -5473,19 +5465,21 @@ var _SearchService = class _SearchService {
5473
5465
  try {
5474
5466
  try {
5475
5467
  const epochsBefore = new Map(this.staleEpochs);
5476
- await this.rebuildIndex(entityId);
5477
- if (entityId) {
5478
- if ((this.staleEpochs.get(entityId) ?? 0) === (epochsBefore.get(entityId) ?? 0)) {
5479
- this.staleEntities.delete(entityId);
5480
- }
5481
- } else {
5468
+ if (entityId === void 0) {
5469
+ const rows = await this.entryRepo.findMiniSearchRows();
5470
+ await this.indexStrategy.replaceAll(rows.map(toIndexDoc));
5482
5471
  for (const id of [...this.staleEntities]) {
5483
5472
  if ((this.staleEpochs.get(id) ?? 0) === (epochsBefore.get(id) ?? 0)) {
5484
5473
  this.staleEntities.delete(id);
5485
5474
  }
5486
5475
  }
5476
+ } else {
5477
+ await this.rebuildIndex(entityId);
5478
+ if ((this.staleEpochs.get(entityId) ?? 0) === (epochsBefore.get(entityId) ?? 0)) {
5479
+ this.staleEntities.delete(entityId);
5480
+ }
5487
5481
  }
5488
- await this.miniSearch.vacuum();
5482
+ await this.indexStrategy.vacuum?.();
5489
5483
  } finally {
5490
5484
  this.evictCache(entityId);
5491
5485
  }
@@ -5510,27 +5504,20 @@ var _SearchService = class _SearchService {
5510
5504
  */
5511
5505
  async syncEntries(entityId, ids) {
5512
5506
  const uniqueIds = [...new Set(ids)];
5513
- if (uniqueIds.length === 0 && !this.staleEntities.has(entityId) && this.miniSearchEntryIdsByEntity.has(entityId)) {
5507
+ const indexed = this.indexStrategy.hasIndexedEntity?.(entityId);
5508
+ if (uniqueIds.length === 0 && !this.staleEntities.has(entityId) && indexed === true) {
5514
5509
  return this.syncChain;
5515
5510
  }
5516
5511
  const work = this.syncChain.then(async () => {
5517
5512
  try {
5518
5513
  try {
5519
5514
  const epochsBefore = new Map(this.staleEpochs);
5520
- const needsRebuild = () => this.staleEntities.has(entityId) || !this.miniSearchEntryIdsByEntity.has(entityId);
5515
+ const needsRebuild = () => this.staleEntities.has(entityId) || this.indexStrategy.hasIndexedEntity?.(entityId) !== true;
5521
5516
  if (!needsRebuild()) {
5522
5517
  const rows = await this.entryRepo.findMiniSearchRowsByIds(entityId, uniqueIds);
5523
- const tracked = this.miniSearchEntryIdsByEntity.get(entityId);
5524
- if (tracked && !this.staleEntities.has(entityId)) {
5525
- for (const id of uniqueIds) {
5526
- if (tracked.delete(id) && this.miniSearch.has(id)) this.miniSearch.discard(id);
5527
- }
5528
- const documents = rows.map((row) => this.normalizeMiniSearchRow(row));
5529
- if (documents.length > 0) this.miniSearch.addAll(documents);
5530
- for (const document of documents) tracked.add(document.id);
5531
- if (this.miniSearch.dirtCount > 0) {
5532
- await this.miniSearch.vacuum();
5533
- }
5518
+ if (!this.staleEntities.has(entityId)) {
5519
+ await this.indexStrategy.replace(entityId, uniqueIds, rows.map(toIndexDoc));
5520
+ await this.indexStrategy.vacuum?.();
5534
5521
  return;
5535
5522
  }
5536
5523
  }
@@ -5538,7 +5525,7 @@ var _SearchService = class _SearchService {
5538
5525
  if ((this.staleEpochs.get(entityId) ?? 0) === (epochsBefore.get(entityId) ?? 0)) {
5539
5526
  this.staleEntities.delete(entityId);
5540
5527
  }
5541
- await this.miniSearch.vacuum();
5528
+ await this.indexStrategy.vacuum?.();
5542
5529
  } finally {
5543
5530
  this.evictCache(entityId);
5544
5531
  }
@@ -5558,6 +5545,16 @@ var _SearchService = class _SearchService {
5558
5545
  this.staleEntities.add(entityId);
5559
5546
  this.staleEpochs.set(entityId, (this.staleEpochs.get(entityId) ?? 0) + 1);
5560
5547
  }
5548
+ /**
5549
+ * Runs syncEntries(id, []) for each entity marked stale when this is called,
5550
+ * so only those entities are rebuilt. Each rebuild still takes its own turn
5551
+ * on the sync chain, and like syncEntries this never rejects. With nothing
5552
+ * stale it resolves at once, without waiting on work already on the chain.
5553
+ */
5554
+ async syncStale() {
5555
+ const ids = [...this.staleEntities];
5556
+ await Promise.all(ids.map((id) => this.syncEntries(id, [])));
5557
+ }
5561
5558
  /**
5562
5559
  * Clears the parsed vector cache. Useful for mid-loop flush guarantees
5563
5560
  * or memory pressure evictions.
@@ -5572,39 +5569,28 @@ var _SearchService = class _SearchService {
5572
5569
  /**
5573
5570
  * Fully resets the search service.
5574
5571
  */
5575
- clearAll() {
5572
+ async clearAll() {
5576
5573
  this.vectorCache.clear();
5577
- this.miniSearch = this.createMiniSearch();
5578
- this.miniSearchEntryIdsByEntity.clear();
5574
+ await this.indexStrategy.replaceAll([]);
5579
5575
  this.staleEntities.clear();
5580
5576
  this.staleEpochs.clear();
5581
5577
  }
5582
5578
  /**
5583
- * Executes a keyword search against the active MiniSearch index.
5579
+ * Executes a keyword search against the active index strategy.
5584
5580
  */
5585
- searchKeyword(query, entityIds, limit) {
5586
- const entityIdSet = new Set(entityIds);
5587
- const results = this.miniSearch.search(query, {
5588
- filter: (r) => entityIdSet.has(r.entity_id),
5589
- combineWith: "OR"
5590
- });
5581
+ async searchKeyword(query, entityIds, limit) {
5582
+ const results = finiteResults(await this.indexStrategy.search(query, { entityIds, limit }));
5591
5583
  return results.sort((a, b) => this._compareSearchResults(a, b)).slice(0, limit);
5592
5584
  }
5593
5585
  /**
5594
- * Pre-fetches MiniSearch scores for candidate hydration, used during hybrid weighting.
5586
+ * Pre-fetches keyword scores for candidate hydration, used during hybrid weighting.
5595
5587
  */
5596
- getMiniSearchScores(query, entityIds, preFilterLimit) {
5597
- const entityIdSet = new Set(entityIds);
5598
- let results = this.miniSearch.search(query, {
5599
- filter: (r) => entityIdSet.has(r.entity_id),
5600
- combineWith: "OR"
5601
- }).sort((a, b) => this._compareSearchResults(a, b));
5602
- if (preFilterLimit !== void 0) {
5603
- results = results.slice(0, preFilterLimit);
5604
- }
5588
+ async getKeywordScores(query, entityIds, preFilterLimit) {
5589
+ const opts = preFilterLimit !== void 0 ? { entityIds, limit: preFilterLimit, preFilterLimit } : { entityIds, limit: Number.MAX_SAFE_INTEGER };
5590
+ const results = finiteResults(await this.indexStrategy.search(query, opts));
5605
5591
  if (results.length === 0) return /* @__PURE__ */ new Map();
5606
- const maxMsScore = Math.max(1, results[0]?.score ?? 1);
5607
- return new Map(results.map((r) => [r.id, r.score / maxMsScore]));
5592
+ const maxScore = Math.max(1, results[0]?.score ?? 1);
5593
+ return new Map(results.map((r) => [r.id, r.score / maxScore]));
5608
5594
  }
5609
5595
  /**
5610
5596
  * Score candidate rows using in-process JS cosine similarity.
@@ -5666,53 +5652,16 @@ var _SearchService = class _SearchService {
5666
5652
  return scored.slice(0, limit);
5667
5653
  }
5668
5654
  // --- Internal Index Management ---
5655
+ /**
5656
+ * Rebuild a single entity's index from scratch. Reads every row for
5657
+ * `entityId` from the repository (already filtered by `deleted_at IS NULL`)
5658
+ * and hands the lot to `indexStrategy.replaceEntity`, which owns the
5659
+ * drop-then-add bookkeeping so callers don't need to know the strategy's
5660
+ * id-tracking shape.
5661
+ */
5669
5662
  async rebuildIndex(entityId) {
5670
- if (entityId) {
5671
- const rows2 = await this.entryRepo.findMiniSearchRows(entityId);
5672
- const previousIds = this.miniSearchEntryIdsByEntity.get(entityId);
5673
- if (previousIds) {
5674
- for (const id of previousIds) {
5675
- this.miniSearch.discard(id);
5676
- }
5677
- }
5678
- const documents2 = rows2.map((row) => this.normalizeMiniSearchRow(row));
5679
- if (documents2.length > 0) {
5680
- this.miniSearch.addAll(documents2);
5681
- }
5682
- this.miniSearchEntryIdsByEntity.set(
5683
- entityId,
5684
- new Set(documents2.map((document) => document.id))
5685
- );
5686
- return;
5687
- }
5688
- const rows = await this.entryRepo.findMiniSearchRows();
5689
- this.miniSearch.removeAll();
5690
- this.miniSearchEntryIdsByEntity.clear();
5691
- const documents = rows.map((row) => this.normalizeMiniSearchRow(row));
5692
- if (documents.length > 0) {
5693
- this.miniSearch.addAll(documents);
5694
- }
5695
- for (const document of documents) {
5696
- const ids = this.miniSearchEntryIdsByEntity.get(document.entity_id) ?? /* @__PURE__ */ new Set();
5697
- ids.add(document.id);
5698
- this.miniSearchEntryIdsByEntity.set(document.entity_id, ids);
5699
- }
5700
- }
5701
- normalizeMiniSearchRow(row) {
5702
- return {
5703
- id: row.id,
5704
- entity_id: row.entity_id,
5705
- title: row.title,
5706
- body: row.body,
5707
- tags: (() => {
5708
- try {
5709
- const parsed = JSON.parse(row.tags);
5710
- return Array.isArray(parsed) ? parsed.join(" ") : row.tags;
5711
- } catch {
5712
- return row.tags;
5713
- }
5714
- })()
5715
- };
5663
+ const rows = await this.entryRepo.findMiniSearchRows(entityId);
5664
+ await this.indexStrategy.replaceEntity(entityId, rows.map(toIndexDoc));
5716
5665
  }
5717
5666
  _tieBreakSort(items) {
5718
5667
  items.sort((a, b) => this._compareScoredRows(a, b));
@@ -5727,11 +5676,11 @@ var _SearchService = class _SearchService {
5727
5676
  return a.id.localeCompare(b.id);
5728
5677
  }
5729
5678
  /**
5730
- * MiniSearch breaks equal-score ties by internal insertion order, which
5731
- * syncEntries changes: a discarded-and-re-added id moves to the end. Every
5732
- * caller truncates these results to a limit, so which ids survive a tie at
5733
- * the boundary must not depend on write history. Re-sort exact score ties
5734
- * by id — the same final tie-break _compareScoredRows applies.
5679
+ * The keyword index breaks equal-score ties by internal insertion order,
5680
+ * which syncEntries changes: a discarded-and-re-added id moves to the end.
5681
+ * Every caller truncates these results to a limit, so which ids survive a
5682
+ * tie at the boundary must not depend on write history. Re-sort exact
5683
+ * score ties by id — the same final tie-break _compareScoredRows applies.
5735
5684
  */
5736
5685
  _compareSearchResults(a, b) {
5737
5686
  const scoreDiff = b.score - a.score;
@@ -5752,6 +5701,9 @@ _SearchService.MAX_VECTOR_CACHE_ENTITIES = 16;
5752
5701
  */
5753
5702
  _SearchService.MAX_VECTOR_CACHE_FACTS_PER_ENTITY = 500;
5754
5703
  var SearchService = _SearchService;
5704
+ function finiteResults(results) {
5705
+ return results.every((r) => Number.isFinite(r.score)) ? results : results.filter((r) => Number.isFinite(r.score));
5706
+ }
5755
5707
 
5756
5708
  // src/services/JobManager.ts
5757
5709
  var JobManager = class {