@equationalapplications/core-llm-wiki 7.9.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
  }
@@ -5582,39 +5569,28 @@ var _SearchService = class _SearchService {
5582
5569
  /**
5583
5570
  * Fully resets the search service.
5584
5571
  */
5585
- clearAll() {
5572
+ async clearAll() {
5586
5573
  this.vectorCache.clear();
5587
- this.miniSearch = this.createMiniSearch();
5588
- this.miniSearchEntryIdsByEntity.clear();
5574
+ await this.indexStrategy.replaceAll([]);
5589
5575
  this.staleEntities.clear();
5590
5576
  this.staleEpochs.clear();
5591
5577
  }
5592
5578
  /**
5593
- * Executes a keyword search against the active MiniSearch index.
5579
+ * Executes a keyword search against the active index strategy.
5594
5580
  */
5595
- searchKeyword(query, entityIds, limit) {
5596
- const entityIdSet = new Set(entityIds);
5597
- const results = this.miniSearch.search(query, {
5598
- filter: (r) => entityIdSet.has(r.entity_id),
5599
- combineWith: "OR"
5600
- });
5581
+ async searchKeyword(query, entityIds, limit) {
5582
+ const results = finiteResults(await this.indexStrategy.search(query, { entityIds, limit }));
5601
5583
  return results.sort((a, b) => this._compareSearchResults(a, b)).slice(0, limit);
5602
5584
  }
5603
5585
  /**
5604
- * Pre-fetches MiniSearch scores for candidate hydration, used during hybrid weighting.
5586
+ * Pre-fetches keyword scores for candidate hydration, used during hybrid weighting.
5605
5587
  */
5606
- getMiniSearchScores(query, entityIds, preFilterLimit) {
5607
- const entityIdSet = new Set(entityIds);
5608
- let results = this.miniSearch.search(query, {
5609
- filter: (r) => entityIdSet.has(r.entity_id),
5610
- combineWith: "OR"
5611
- }).sort((a, b) => this._compareSearchResults(a, b));
5612
- if (preFilterLimit !== void 0) {
5613
- results = results.slice(0, preFilterLimit);
5614
- }
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));
5615
5591
  if (results.length === 0) return /* @__PURE__ */ new Map();
5616
- const maxMsScore = Math.max(1, results[0]?.score ?? 1);
5617
- 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]));
5618
5594
  }
5619
5595
  /**
5620
5596
  * Score candidate rows using in-process JS cosine similarity.
@@ -5676,53 +5652,16 @@ var _SearchService = class _SearchService {
5676
5652
  return scored.slice(0, limit);
5677
5653
  }
5678
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
+ */
5679
5662
  async rebuildIndex(entityId) {
5680
- if (entityId) {
5681
- const rows2 = await this.entryRepo.findMiniSearchRows(entityId);
5682
- const previousIds = this.miniSearchEntryIdsByEntity.get(entityId);
5683
- if (previousIds) {
5684
- for (const id of previousIds) {
5685
- this.miniSearch.discard(id);
5686
- }
5687
- }
5688
- const documents2 = rows2.map((row) => this.normalizeMiniSearchRow(row));
5689
- if (documents2.length > 0) {
5690
- this.miniSearch.addAll(documents2);
5691
- }
5692
- this.miniSearchEntryIdsByEntity.set(
5693
- entityId,
5694
- new Set(documents2.map((document) => document.id))
5695
- );
5696
- return;
5697
- }
5698
- const rows = await this.entryRepo.findMiniSearchRows();
5699
- this.miniSearch.removeAll();
5700
- this.miniSearchEntryIdsByEntity.clear();
5701
- const documents = rows.map((row) => this.normalizeMiniSearchRow(row));
5702
- if (documents.length > 0) {
5703
- this.miniSearch.addAll(documents);
5704
- }
5705
- for (const document of documents) {
5706
- const ids = this.miniSearchEntryIdsByEntity.get(document.entity_id) ?? /* @__PURE__ */ new Set();
5707
- ids.add(document.id);
5708
- this.miniSearchEntryIdsByEntity.set(document.entity_id, ids);
5709
- }
5710
- }
5711
- normalizeMiniSearchRow(row) {
5712
- return {
5713
- id: row.id,
5714
- entity_id: row.entity_id,
5715
- title: row.title,
5716
- body: row.body,
5717
- tags: (() => {
5718
- try {
5719
- const parsed = JSON.parse(row.tags);
5720
- return Array.isArray(parsed) ? parsed.join(" ") : row.tags;
5721
- } catch {
5722
- return row.tags;
5723
- }
5724
- })()
5725
- };
5663
+ const rows = await this.entryRepo.findMiniSearchRows(entityId);
5664
+ await this.indexStrategy.replaceEntity(entityId, rows.map(toIndexDoc));
5726
5665
  }
5727
5666
  _tieBreakSort(items) {
5728
5667
  items.sort((a, b) => this._compareScoredRows(a, b));
@@ -5737,11 +5676,11 @@ var _SearchService = class _SearchService {
5737
5676
  return a.id.localeCompare(b.id);
5738
5677
  }
5739
5678
  /**
5740
- * MiniSearch breaks equal-score ties by internal insertion order, which
5741
- * syncEntries changes: a discarded-and-re-added id moves to the end. Every
5742
- * caller truncates these results to a limit, so which ids survive a tie at
5743
- * the boundary must not depend on write history. Re-sort exact score ties
5744
- * 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.
5745
5684
  */
5746
5685
  _compareSearchResults(a, b) {
5747
5686
  const scoreDiff = b.score - a.score;
@@ -5762,6 +5701,9 @@ _SearchService.MAX_VECTOR_CACHE_ENTITIES = 16;
5762
5701
  */
5763
5702
  _SearchService.MAX_VECTOR_CACHE_FACTS_PER_ENTITY = 500;
5764
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
+ }
5765
5707
 
5766
5708
  // src/services/JobManager.ts
5767
5709
  var JobManager = class {