@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.
@@ -1,5 +1,4 @@
1
1
  import { OkfSource, OkfVerifiedEntry, OkfSourceUsageWindow } from '@equationalapplications/core-okf';
2
- import { SearchResult } from 'minisearch';
3
2
 
4
3
  /**
5
4
  * Platform-agnostic SQLite driver interface.
@@ -1923,6 +1922,108 @@ declare class MetadataRepository extends BaseRepository {
1923
1922
  mergeManifestUpdates(entityId: string, updates: OntologyUpdates, tx: SQLiteAdapter): Promise<OntologyManifest>;
1924
1923
  }
1925
1924
 
1925
+ /**
1926
+ * One document that both keyword-index strategies must understand. Both
1927
+ * implementations expect `tags` to be a single string with the JSON array's
1928
+ * tokens space-joined (see `toIndexDoc` in utils/indexDoc.ts). The exact token
1929
+ * shape is strategy-private — callers do not see it.
1930
+ */
1931
+ interface IndexDocument {
1932
+ id: string;
1933
+ entity_id: string;
1934
+ title: string;
1935
+ body: string;
1936
+ tags: string;
1937
+ }
1938
+ interface IndexSearchOptions {
1939
+ /** Empty array means "no entities match"; strategy returns []. */
1940
+ entityIds: string[];
1941
+ /** Upper bound on results returned. Strategy MUST return ≤ `limit`. */
1942
+ limit: number;
1943
+ /** Optional pre-filter cap used by hybrid retrieval. */
1944
+ preFilterLimit?: number;
1945
+ }
1946
+ interface IndexSearchResult {
1947
+ id: string;
1948
+ /**
1949
+ * Strategy-native relevance: finite, non-negative, higher is better,
1950
+ * comparable only within one result set. Not normalized —
1951
+ * `SearchService.getKeywordScores` divides by `max(1, top)` for the hybrid
1952
+ * blend, and `searchKeyword` passes scores through unscaled. SearchService
1953
+ * drops any result whose score is not finite.
1954
+ */
1955
+ score: number;
1956
+ /**
1957
+ * The entity id this search result belongs to. Populated from
1958
+ * `IndexDocument.entity_id` by the strategy on every result.
1959
+ */
1960
+ entity_id: string;
1961
+ }
1962
+ /**
1963
+ * Keyword-index abstraction. The strategy owns its own backing store
1964
+ * (in-RAM MiniSearch vs on-disk FTS5) and is responsible for keeping it
1965
+ * consistent with the rows it has been told to write. The strategy also owns
1966
+ * the per-entity id bookkeeping — `SearchService` never sees the ids, only
1967
+ * an indexed-or-not probe.
1968
+ *
1969
+ * Concurrency: writes are called from serialized turns on
1970
+ * `SearchService.syncChain`, but searches are not, so each write method MUST
1971
+ * apply as one step — a concurrent `search()` sees the index wholly before or
1972
+ * wholly after it, never between its removal and its insertion. MiniSearch
1973
+ * gets this by never awaiting inside a write; FTS5 by one transaction per
1974
+ * write. Strategies MUST NOT spawn background work.
1975
+ */
1976
+ interface IndexStrategy {
1977
+ /**
1978
+ * Replace documents under `entityId`: remove the given `ids` only if they
1979
+ * are currently tracked under `entityId` (ids under other entities and ids
1980
+ * the strategy never indexed are silently ignored), then upsert
1981
+ * `documents` by id. The strategy owns its id-tracking structure; callers
1982
+ * do not see it. Used for incremental sync.
1983
+ *
1984
+ * Argument order is `(entityId, ids, documents)` so the FTS5 SQL
1985
+ * `DELETE … WHERE entity_id = ? AND id IN (…)` matches the call shape.
1986
+ */
1987
+ replace(entityId: string, ids: readonly string[], documents: readonly IndexDocument[]): Promise<void>;
1988
+ /**
1989
+ * Drop every document currently tracked under `entityId`, then add
1990
+ * `documents` (all of which belong to `entityId`). The entity stays
1991
+ * registered as indexed, even when `documents` is empty — `hasIndexedEntity`
1992
+ * continues to return `true` so the next incremental sync can take the
1993
+ * fast path. Used for per-entity rebuilds.
1994
+ */
1995
+ replaceEntity(entityId: string, documents: readonly IndexDocument[]): Promise<void>;
1996
+ /**
1997
+ * Empty the entire index, then add `documents`. After this call, no entity
1998
+ * is registered as indexed (`hasIndexedEntity` returns `false` for every
1999
+ * entity). Used for global rebuilds and clears.
2000
+ */
2001
+ replaceAll(documents: readonly IndexDocument[]): Promise<void>;
2002
+ /**
2003
+ * Search the index, best match first. If `preFilterLimit` is set, the
2004
+ * strategy MAY return up to `preFilterLimit` candidates before the `limit`
2005
+ * cap is applied (hybrid retrieval uses this to over-fetch candidates for
2006
+ * downstream vector blending). Score semantics are in `IndexSearchResult`.
2007
+ */
2008
+ search(query: string, options: IndexSearchOptions): Promise<IndexSearchResult[]>;
2009
+ /**
2010
+ * Strategy-internal cleanup. For MiniSearch, this is the explicit vacuum
2011
+ * that `syncEntries` invokes after a discard-bearing turn (#64). FTS5 has
2012
+ * no equivalent; the default implementation is a no-op.
2013
+ */
2014
+ vacuum?(): Promise<void>;
2015
+ /**
2016
+ * `true` when `entityId` has been indexed at least once under the current
2017
+ * strategy state; `false` otherwise. `SearchService.syncEntries` uses this
2018
+ * to choose between the empty-ids no-op fast path and the full-rebuild
2019
+ * fallback. Implementations MUST return `false` after `replaceAll([])` and
2020
+ * MUST return `true` after a `replace`/`replaceEntity` that wrote at least
2021
+ * one document (including `replace(entityId, [], [])` for tracking alone).
2022
+ * Returning `undefined` means "unknown — take the rebuild path".
2023
+ */
2024
+ hasIndexedEntity?(entityId: string): boolean | undefined;
2025
+ }
2026
+
1926
2027
  interface ScoredRow {
1927
2028
  id: string;
1928
2029
  entity_id: string;
@@ -1961,8 +2062,7 @@ declare class SearchService {
1961
2062
  * entities while still maintaining a bounded memory footprint.
1962
2063
  */
1963
2064
  private static readonly MAX_VECTOR_CACHE_FACTS_PER_ENTITY;
1964
- private miniSearch;
1965
- private miniSearchEntryIdsByEntity;
2065
+ private indexStrategy;
1966
2066
  private vectorCache;
1967
2067
  /**
1968
2068
  * Serializes rebuilds. `rebuildIndex` awaits a repository read between
@@ -1988,19 +2088,12 @@ declare class SearchService {
1988
2088
  * its flag must outlive the turn (#233 review).
1989
2089
  */
1990
2090
  private staleEpochs;
1991
- constructor(entryRepo: EntryRepository);
1992
- /**
1993
- * A fresh index with the production options. clearAll() swaps one in because
1994
- * MiniSearch.removeAll() empties the index but leaves dirtCount (and its
1995
- * vacuum bookkeeping) at its old value, which would trip syncEntries'
1996
- * conditional vacuum early after a clear.
1997
- */
1998
- private createMiniSearch;
2091
+ constructor(entryRepo: EntryRepository, indexStrategy: IndexStrategy);
1999
2092
  /**
2000
2093
  * Rebuilds the search index and clears the vector cache for a given entity.
2001
2094
  * A direct replacement for manually syncing state after a DB transaction.
2002
2095
  *
2003
- * Rebuilds are serialized per instance and never reject: the MiniSearch index
2096
+ * Rebuilds are serialized per instance and never reject: the keyword index
2004
2097
  * is a rebuildable cache over SQLite, so degraded keyword search is the
2005
2098
  * correct failure mode and killing the host process is not.
2006
2099
  */
@@ -2038,30 +2131,36 @@ declare class SearchService {
2038
2131
  /**
2039
2132
  * Fully resets the search service.
2040
2133
  */
2041
- clearAll(): void;
2134
+ clearAll(): Promise<void>;
2042
2135
  /**
2043
- * Executes a keyword search against the active MiniSearch index.
2136
+ * Executes a keyword search against the active index strategy.
2044
2137
  */
2045
- searchKeyword(query: string, entityIds: string[], limit: number): SearchResult[];
2138
+ searchKeyword(query: string, entityIds: string[], limit: number): Promise<IndexSearchResult[]>;
2046
2139
  /**
2047
- * Pre-fetches MiniSearch scores for candidate hydration, used during hybrid weighting.
2140
+ * Pre-fetches keyword scores for candidate hydration, used during hybrid weighting.
2048
2141
  */
2049
- getMiniSearchScores(query: string, entityIds: string[], preFilterLimit?: number): Map<string, number>;
2142
+ getKeywordScores(query: string, entityIds: string[], preFilterLimit?: number): Promise<Map<string, number>>;
2050
2143
  /**
2051
2144
  * Score candidate rows using in-process JS cosine similarity.
2052
2145
  * Applies hybrid blending (if weight set) and tie-break sorting before returning.
2053
2146
  */
2054
2147
  rankSemantic(args: RankSemanticArgs): Promise<ScoredRow[]>;
2148
+ /**
2149
+ * Rebuild a single entity's index from scratch. Reads every row for
2150
+ * `entityId` from the repository (already filtered by `deleted_at IS NULL`)
2151
+ * and hands the lot to `indexStrategy.replaceEntity`, which owns the
2152
+ * drop-then-add bookkeeping so callers don't need to know the strategy's
2153
+ * id-tracking shape.
2154
+ */
2055
2155
  private rebuildIndex;
2056
- private normalizeMiniSearchRow;
2057
2156
  private _tieBreakSort;
2058
2157
  private _compareScoredRows;
2059
2158
  /**
2060
- * MiniSearch breaks equal-score ties by internal insertion order, which
2061
- * syncEntries changes: a discarded-and-re-added id moves to the end. Every
2062
- * caller truncates these results to a limit, so which ids survive a tie at
2063
- * the boundary must not depend on write history. Re-sort exact score ties
2064
- * by id — the same final tie-break _compareScoredRows applies.
2159
+ * The keyword index breaks equal-score ties by internal insertion order,
2160
+ * which syncEntries changes: a discarded-and-re-added id moves to the end.
2161
+ * Every caller truncates these results to a limit, so which ids survive a
2162
+ * tie at the boundary must not depend on write history. Re-sort exact
2163
+ * score ties by id — the same final tie-break _compareScoredRows applies.
2065
2164
  */
2066
2165
  private _compareSearchResults;
2067
2166
  }
@@ -1,5 +1,4 @@
1
1
  import { OkfSource, OkfVerifiedEntry, OkfSourceUsageWindow } from '@equationalapplications/core-okf';
2
- import { SearchResult } from 'minisearch';
3
2
 
4
3
  /**
5
4
  * Platform-agnostic SQLite driver interface.
@@ -1923,6 +1922,108 @@ declare class MetadataRepository extends BaseRepository {
1923
1922
  mergeManifestUpdates(entityId: string, updates: OntologyUpdates, tx: SQLiteAdapter): Promise<OntologyManifest>;
1924
1923
  }
1925
1924
 
1925
+ /**
1926
+ * One document that both keyword-index strategies must understand. Both
1927
+ * implementations expect `tags` to be a single string with the JSON array's
1928
+ * tokens space-joined (see `toIndexDoc` in utils/indexDoc.ts). The exact token
1929
+ * shape is strategy-private — callers do not see it.
1930
+ */
1931
+ interface IndexDocument {
1932
+ id: string;
1933
+ entity_id: string;
1934
+ title: string;
1935
+ body: string;
1936
+ tags: string;
1937
+ }
1938
+ interface IndexSearchOptions {
1939
+ /** Empty array means "no entities match"; strategy returns []. */
1940
+ entityIds: string[];
1941
+ /** Upper bound on results returned. Strategy MUST return ≤ `limit`. */
1942
+ limit: number;
1943
+ /** Optional pre-filter cap used by hybrid retrieval. */
1944
+ preFilterLimit?: number;
1945
+ }
1946
+ interface IndexSearchResult {
1947
+ id: string;
1948
+ /**
1949
+ * Strategy-native relevance: finite, non-negative, higher is better,
1950
+ * comparable only within one result set. Not normalized —
1951
+ * `SearchService.getKeywordScores` divides by `max(1, top)` for the hybrid
1952
+ * blend, and `searchKeyword` passes scores through unscaled. SearchService
1953
+ * drops any result whose score is not finite.
1954
+ */
1955
+ score: number;
1956
+ /**
1957
+ * The entity id this search result belongs to. Populated from
1958
+ * `IndexDocument.entity_id` by the strategy on every result.
1959
+ */
1960
+ entity_id: string;
1961
+ }
1962
+ /**
1963
+ * Keyword-index abstraction. The strategy owns its own backing store
1964
+ * (in-RAM MiniSearch vs on-disk FTS5) and is responsible for keeping it
1965
+ * consistent with the rows it has been told to write. The strategy also owns
1966
+ * the per-entity id bookkeeping — `SearchService` never sees the ids, only
1967
+ * an indexed-or-not probe.
1968
+ *
1969
+ * Concurrency: writes are called from serialized turns on
1970
+ * `SearchService.syncChain`, but searches are not, so each write method MUST
1971
+ * apply as one step — a concurrent `search()` sees the index wholly before or
1972
+ * wholly after it, never between its removal and its insertion. MiniSearch
1973
+ * gets this by never awaiting inside a write; FTS5 by one transaction per
1974
+ * write. Strategies MUST NOT spawn background work.
1975
+ */
1976
+ interface IndexStrategy {
1977
+ /**
1978
+ * Replace documents under `entityId`: remove the given `ids` only if they
1979
+ * are currently tracked under `entityId` (ids under other entities and ids
1980
+ * the strategy never indexed are silently ignored), then upsert
1981
+ * `documents` by id. The strategy owns its id-tracking structure; callers
1982
+ * do not see it. Used for incremental sync.
1983
+ *
1984
+ * Argument order is `(entityId, ids, documents)` so the FTS5 SQL
1985
+ * `DELETE … WHERE entity_id = ? AND id IN (…)` matches the call shape.
1986
+ */
1987
+ replace(entityId: string, ids: readonly string[], documents: readonly IndexDocument[]): Promise<void>;
1988
+ /**
1989
+ * Drop every document currently tracked under `entityId`, then add
1990
+ * `documents` (all of which belong to `entityId`). The entity stays
1991
+ * registered as indexed, even when `documents` is empty — `hasIndexedEntity`
1992
+ * continues to return `true` so the next incremental sync can take the
1993
+ * fast path. Used for per-entity rebuilds.
1994
+ */
1995
+ replaceEntity(entityId: string, documents: readonly IndexDocument[]): Promise<void>;
1996
+ /**
1997
+ * Empty the entire index, then add `documents`. After this call, no entity
1998
+ * is registered as indexed (`hasIndexedEntity` returns `false` for every
1999
+ * entity). Used for global rebuilds and clears.
2000
+ */
2001
+ replaceAll(documents: readonly IndexDocument[]): Promise<void>;
2002
+ /**
2003
+ * Search the index, best match first. If `preFilterLimit` is set, the
2004
+ * strategy MAY return up to `preFilterLimit` candidates before the `limit`
2005
+ * cap is applied (hybrid retrieval uses this to over-fetch candidates for
2006
+ * downstream vector blending). Score semantics are in `IndexSearchResult`.
2007
+ */
2008
+ search(query: string, options: IndexSearchOptions): Promise<IndexSearchResult[]>;
2009
+ /**
2010
+ * Strategy-internal cleanup. For MiniSearch, this is the explicit vacuum
2011
+ * that `syncEntries` invokes after a discard-bearing turn (#64). FTS5 has
2012
+ * no equivalent; the default implementation is a no-op.
2013
+ */
2014
+ vacuum?(): Promise<void>;
2015
+ /**
2016
+ * `true` when `entityId` has been indexed at least once under the current
2017
+ * strategy state; `false` otherwise. `SearchService.syncEntries` uses this
2018
+ * to choose between the empty-ids no-op fast path and the full-rebuild
2019
+ * fallback. Implementations MUST return `false` after `replaceAll([])` and
2020
+ * MUST return `true` after a `replace`/`replaceEntity` that wrote at least
2021
+ * one document (including `replace(entityId, [], [])` for tracking alone).
2022
+ * Returning `undefined` means "unknown — take the rebuild path".
2023
+ */
2024
+ hasIndexedEntity?(entityId: string): boolean | undefined;
2025
+ }
2026
+
1926
2027
  interface ScoredRow {
1927
2028
  id: string;
1928
2029
  entity_id: string;
@@ -1961,8 +2062,7 @@ declare class SearchService {
1961
2062
  * entities while still maintaining a bounded memory footprint.
1962
2063
  */
1963
2064
  private static readonly MAX_VECTOR_CACHE_FACTS_PER_ENTITY;
1964
- private miniSearch;
1965
- private miniSearchEntryIdsByEntity;
2065
+ private indexStrategy;
1966
2066
  private vectorCache;
1967
2067
  /**
1968
2068
  * Serializes rebuilds. `rebuildIndex` awaits a repository read between
@@ -1988,19 +2088,12 @@ declare class SearchService {
1988
2088
  * its flag must outlive the turn (#233 review).
1989
2089
  */
1990
2090
  private staleEpochs;
1991
- constructor(entryRepo: EntryRepository);
1992
- /**
1993
- * A fresh index with the production options. clearAll() swaps one in because
1994
- * MiniSearch.removeAll() empties the index but leaves dirtCount (and its
1995
- * vacuum bookkeeping) at its old value, which would trip syncEntries'
1996
- * conditional vacuum early after a clear.
1997
- */
1998
- private createMiniSearch;
2091
+ constructor(entryRepo: EntryRepository, indexStrategy: IndexStrategy);
1999
2092
  /**
2000
2093
  * Rebuilds the search index and clears the vector cache for a given entity.
2001
2094
  * A direct replacement for manually syncing state after a DB transaction.
2002
2095
  *
2003
- * Rebuilds are serialized per instance and never reject: the MiniSearch index
2096
+ * Rebuilds are serialized per instance and never reject: the keyword index
2004
2097
  * is a rebuildable cache over SQLite, so degraded keyword search is the
2005
2098
  * correct failure mode and killing the host process is not.
2006
2099
  */
@@ -2038,30 +2131,36 @@ declare class SearchService {
2038
2131
  /**
2039
2132
  * Fully resets the search service.
2040
2133
  */
2041
- clearAll(): void;
2134
+ clearAll(): Promise<void>;
2042
2135
  /**
2043
- * Executes a keyword search against the active MiniSearch index.
2136
+ * Executes a keyword search against the active index strategy.
2044
2137
  */
2045
- searchKeyword(query: string, entityIds: string[], limit: number): SearchResult[];
2138
+ searchKeyword(query: string, entityIds: string[], limit: number): Promise<IndexSearchResult[]>;
2046
2139
  /**
2047
- * Pre-fetches MiniSearch scores for candidate hydration, used during hybrid weighting.
2140
+ * Pre-fetches keyword scores for candidate hydration, used during hybrid weighting.
2048
2141
  */
2049
- getMiniSearchScores(query: string, entityIds: string[], preFilterLimit?: number): Map<string, number>;
2142
+ getKeywordScores(query: string, entityIds: string[], preFilterLimit?: number): Promise<Map<string, number>>;
2050
2143
  /**
2051
2144
  * Score candidate rows using in-process JS cosine similarity.
2052
2145
  * Applies hybrid blending (if weight set) and tie-break sorting before returning.
2053
2146
  */
2054
2147
  rankSemantic(args: RankSemanticArgs): Promise<ScoredRow[]>;
2148
+ /**
2149
+ * Rebuild a single entity's index from scratch. Reads every row for
2150
+ * `entityId` from the repository (already filtered by `deleted_at IS NULL`)
2151
+ * and hands the lot to `indexStrategy.replaceEntity`, which owns the
2152
+ * drop-then-add bookkeeping so callers don't need to know the strategy's
2153
+ * id-tracking shape.
2154
+ */
2055
2155
  private rebuildIndex;
2056
- private normalizeMiniSearchRow;
2057
2156
  private _tieBreakSort;
2058
2157
  private _compareScoredRows;
2059
2158
  /**
2060
- * MiniSearch breaks equal-score ties by internal insertion order, which
2061
- * syncEntries changes: a discarded-and-re-added id moves to the end. Every
2062
- * caller truncates these results to a limit, so which ids survive a tie at
2063
- * the boundary must not depend on write history. Re-sort exact score ties
2064
- * by id — the same final tie-break _compareScoredRows applies.
2159
+ * The keyword index breaks equal-score ties by internal insertion order,
2160
+ * which syncEntries changes: a discarded-and-re-added id moves to the end.
2161
+ * Every caller truncates these results to a limit, so which ids survive a
2162
+ * tie at the boundary must not depend on write history. Re-sort exact
2163
+ * score ties by id — the same final tie-break _compareScoredRows applies.
2065
2164
  */
2066
2165
  private _compareSearchResults;
2067
2166
  }
@@ -1,3 +1,2 @@
1
- export { aB as EmbeddingService, aC as ImportExportService, aD as IngestionService, aE as JobManager, aE as JobManagerType, aF as MaintenanceService, aG as RetrievalService, aH as SearchService, aH as SearchServiceType, ar as WikiMemoryTestAccess, aI as WriteService } from './testing-Kb1uVj1E.mjs';
1
+ export { aB as EmbeddingService, aC as ImportExportService, aD as IngestionService, aE as JobManager, aE as JobManagerType, aF as MaintenanceService, aG as RetrievalService, aH as SearchService, aH as SearchServiceType, ar as WikiMemoryTestAccess, aI as WriteService } from './testing-Bf6etdUy.mjs';
2
2
  import '@equationalapplications/core-okf';
3
- import 'minisearch';
package/dist/testing.d.ts CHANGED
@@ -1,3 +1,2 @@
1
- export { aB as EmbeddingService, aC as ImportExportService, aD as IngestionService, aE as JobManager, aE as JobManagerType, aF as MaintenanceService, aG as RetrievalService, aH as SearchService, aH as SearchServiceType, ar as WikiMemoryTestAccess, aI as WriteService } from './testing-Kb1uVj1E.js';
1
+ export { aB as EmbeddingService, aC as ImportExportService, aD as IngestionService, aE as JobManager, aE as JobManagerType, aF as MaintenanceService, aG as RetrievalService, aH as SearchService, aH as SearchServiceType, ar as WikiMemoryTestAccess, aI as WriteService } from './testing-Bf6etdUy.js';
2
2
  import '@equationalapplications/core-okf';
3
- import 'minisearch';