@danielsimonjr/memory-mcp 12.2.3 → 12.5.2

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.
Files changed (158) hide show
  1. package/README.md +88 -102
  2. package/dist/index.js +6 -0
  3. package/dist/server/toolDefinitions.d.ts.map +1 -1
  4. package/dist/server/toolDefinitions.js +745 -3
  5. package/dist/server/toolHandlers.d.ts +1 -9
  6. package/dist/server/toolHandlers.d.ts.map +1 -1
  7. package/dist/server/toolHandlers.js +859 -11
  8. package/package.json +9 -6
  9. package/dist/core/EntityManager.d.ts +0 -263
  10. package/dist/core/EntityManager.d.ts.map +0 -1
  11. package/dist/core/EntityManager.js +0 -478
  12. package/dist/core/GraphStorage.d.ts +0 -344
  13. package/dist/core/GraphStorage.d.ts.map +0 -1
  14. package/dist/core/GraphStorage.js +0 -708
  15. package/dist/core/GraphTraversal.d.ts +0 -136
  16. package/dist/core/GraphTraversal.d.ts.map +0 -1
  17. package/dist/core/GraphTraversal.js +0 -556
  18. package/dist/core/HierarchyManager.d.ts +0 -111
  19. package/dist/core/HierarchyManager.d.ts.map +0 -1
  20. package/dist/core/HierarchyManager.js +0 -224
  21. package/dist/core/ManagerContext.d.ts +0 -72
  22. package/dist/core/ManagerContext.d.ts.map +0 -1
  23. package/dist/core/ManagerContext.js +0 -118
  24. package/dist/core/ObservationManager.d.ts +0 -85
  25. package/dist/core/ObservationManager.d.ts.map +0 -1
  26. package/dist/core/ObservationManager.js +0 -123
  27. package/dist/core/RelationManager.d.ts +0 -131
  28. package/dist/core/RelationManager.d.ts.map +0 -1
  29. package/dist/core/RelationManager.js +0 -210
  30. package/dist/core/SQLiteStorage.d.ts +0 -354
  31. package/dist/core/SQLiteStorage.d.ts.map +0 -1
  32. package/dist/core/SQLiteStorage.js +0 -917
  33. package/dist/core/StorageFactory.d.ts +0 -45
  34. package/dist/core/StorageFactory.d.ts.map +0 -1
  35. package/dist/core/StorageFactory.js +0 -64
  36. package/dist/core/TransactionManager.d.ts +0 -259
  37. package/dist/core/TransactionManager.d.ts.map +0 -1
  38. package/dist/core/TransactionManager.js +0 -392
  39. package/dist/core/index.d.ts +0 -16
  40. package/dist/core/index.d.ts.map +0 -1
  41. package/dist/core/index.js +0 -17
  42. package/dist/features/AnalyticsManager.d.ts +0 -44
  43. package/dist/features/AnalyticsManager.d.ts.map +0 -1
  44. package/dist/features/AnalyticsManager.js +0 -223
  45. package/dist/features/ArchiveManager.d.ts +0 -121
  46. package/dist/features/ArchiveManager.d.ts.map +0 -1
  47. package/dist/features/ArchiveManager.js +0 -240
  48. package/dist/features/CompressionManager.d.ts +0 -109
  49. package/dist/features/CompressionManager.d.ts.map +0 -1
  50. package/dist/features/CompressionManager.js +0 -404
  51. package/dist/features/IOManager.d.ts +0 -221
  52. package/dist/features/IOManager.d.ts.map +0 -1
  53. package/dist/features/IOManager.js +0 -997
  54. package/dist/features/StreamingExporter.d.ts +0 -105
  55. package/dist/features/StreamingExporter.d.ts.map +0 -1
  56. package/dist/features/StreamingExporter.js +0 -150
  57. package/dist/features/TagManager.d.ts +0 -147
  58. package/dist/features/TagManager.d.ts.map +0 -1
  59. package/dist/features/TagManager.js +0 -210
  60. package/dist/features/index.d.ts +0 -12
  61. package/dist/features/index.d.ts.map +0 -1
  62. package/dist/features/index.js +0 -11
  63. package/dist/search/BasicSearch.d.ts +0 -51
  64. package/dist/search/BasicSearch.d.ts.map +0 -1
  65. package/dist/search/BasicSearch.js +0 -137
  66. package/dist/search/BooleanSearch.d.ts +0 -98
  67. package/dist/search/BooleanSearch.d.ts.map +0 -1
  68. package/dist/search/BooleanSearch.js +0 -430
  69. package/dist/search/EmbeddingService.d.ts +0 -178
  70. package/dist/search/EmbeddingService.d.ts.map +0 -1
  71. package/dist/search/EmbeddingService.js +0 -358
  72. package/dist/search/FuzzySearch.d.ts +0 -118
  73. package/dist/search/FuzzySearch.d.ts.map +0 -1
  74. package/dist/search/FuzzySearch.js +0 -312
  75. package/dist/search/RankedSearch.d.ts +0 -71
  76. package/dist/search/RankedSearch.d.ts.map +0 -1
  77. package/dist/search/RankedSearch.js +0 -238
  78. package/dist/search/SavedSearchManager.d.ts +0 -79
  79. package/dist/search/SavedSearchManager.d.ts.map +0 -1
  80. package/dist/search/SavedSearchManager.js +0 -145
  81. package/dist/search/SearchFilterChain.d.ts +0 -120
  82. package/dist/search/SearchFilterChain.d.ts.map +0 -1
  83. package/dist/search/SearchFilterChain.js +0 -185
  84. package/dist/search/SearchManager.d.ts +0 -285
  85. package/dist/search/SearchManager.d.ts.map +0 -1
  86. package/dist/search/SearchManager.js +0 -338
  87. package/dist/search/SearchSuggestions.d.ts +0 -27
  88. package/dist/search/SearchSuggestions.d.ts.map +0 -1
  89. package/dist/search/SearchSuggestions.js +0 -57
  90. package/dist/search/SemanticSearch.d.ts +0 -146
  91. package/dist/search/SemanticSearch.d.ts.map +0 -1
  92. package/dist/search/SemanticSearch.js +0 -313
  93. package/dist/search/TFIDFIndexManager.d.ts +0 -63
  94. package/dist/search/TFIDFIndexManager.d.ts.map +0 -1
  95. package/dist/search/TFIDFIndexManager.js +0 -215
  96. package/dist/search/VectorStore.d.ts +0 -235
  97. package/dist/search/VectorStore.d.ts.map +0 -1
  98. package/dist/search/VectorStore.js +0 -311
  99. package/dist/search/index.d.ts +0 -18
  100. package/dist/search/index.d.ts.map +0 -1
  101. package/dist/search/index.js +0 -21
  102. package/dist/types/index.d.ts +0 -13
  103. package/dist/types/index.d.ts.map +0 -1
  104. package/dist/types/index.js +0 -12
  105. package/dist/types/types.d.ts +0 -1234
  106. package/dist/types/types.d.ts.map +0 -1
  107. package/dist/types/types.js +0 -9
  108. package/dist/utils/compressedCache.d.ts +0 -192
  109. package/dist/utils/compressedCache.d.ts.map +0 -1
  110. package/dist/utils/compressedCache.js +0 -309
  111. package/dist/utils/compressionUtil.d.ts +0 -214
  112. package/dist/utils/compressionUtil.d.ts.map +0 -1
  113. package/dist/utils/compressionUtil.js +0 -247
  114. package/dist/utils/constants.d.ts +0 -245
  115. package/dist/utils/constants.d.ts.map +0 -1
  116. package/dist/utils/constants.js +0 -252
  117. package/dist/utils/entityUtils.d.ts +0 -321
  118. package/dist/utils/entityUtils.d.ts.map +0 -1
  119. package/dist/utils/entityUtils.js +0 -538
  120. package/dist/utils/errors.d.ts +0 -77
  121. package/dist/utils/errors.d.ts.map +0 -1
  122. package/dist/utils/errors.js +0 -121
  123. package/dist/utils/formatters.d.ts +0 -145
  124. package/dist/utils/formatters.d.ts.map +0 -1
  125. package/dist/utils/formatters.js +0 -132
  126. package/dist/utils/index.d.ts +0 -22
  127. package/dist/utils/index.d.ts.map +0 -1
  128. package/dist/utils/index.js +0 -75
  129. package/dist/utils/indexes.d.ts +0 -270
  130. package/dist/utils/indexes.d.ts.map +0 -1
  131. package/dist/utils/indexes.js +0 -526
  132. package/dist/utils/logger.d.ts +0 -24
  133. package/dist/utils/logger.d.ts.map +0 -1
  134. package/dist/utils/logger.js +0 -33
  135. package/dist/utils/parallelUtils.d.ts +0 -72
  136. package/dist/utils/parallelUtils.d.ts.map +0 -1
  137. package/dist/utils/parallelUtils.js +0 -169
  138. package/dist/utils/schemas.d.ts +0 -374
  139. package/dist/utils/schemas.d.ts.map +0 -1
  140. package/dist/utils/schemas.js +0 -484
  141. package/dist/utils/searchAlgorithms.d.ts +0 -99
  142. package/dist/utils/searchAlgorithms.d.ts.map +0 -1
  143. package/dist/utils/searchAlgorithms.js +0 -167
  144. package/dist/utils/searchCache.d.ts +0 -108
  145. package/dist/utils/searchCache.d.ts.map +0 -1
  146. package/dist/utils/searchCache.js +0 -209
  147. package/dist/utils/taskScheduler.d.ts +0 -290
  148. package/dist/utils/taskScheduler.d.ts.map +0 -1
  149. package/dist/utils/taskScheduler.js +0 -466
  150. package/dist/workers/WorkerPool.d.ts +0 -81
  151. package/dist/workers/WorkerPool.d.ts.map +0 -1
  152. package/dist/workers/WorkerPool.js +0 -121
  153. package/dist/workers/index.d.ts +0 -12
  154. package/dist/workers/index.d.ts.map +0 -1
  155. package/dist/workers/index.js +0 -9
  156. package/dist/workers/levenshteinWorker.d.ts +0 -60
  157. package/dist/workers/levenshteinWorker.d.ts.map +0 -1
  158. package/dist/workers/levenshteinWorker.js +0 -98
@@ -1,312 +0,0 @@
1
- /**
2
- * Fuzzy Search
3
- *
4
- * Search with typo tolerance using Levenshtein distance similarity.
5
- * Uses workerpool for parallel processing on large datasets.
6
- *
7
- * @module search/FuzzySearch
8
- */
9
- import { levenshteinDistance } from '../utils/index.js';
10
- import { SEARCH_LIMITS } from '../utils/constants.js';
11
- import { SearchFilterChain } from './SearchFilterChain.js';
12
- import workerpool from '@danielsimonjr/workerpool/modern';
13
- import { fileURLToPath } from 'url';
14
- import { dirname, join, sep } from 'path';
15
- /**
16
- * Default fuzzy search similarity threshold (70% match required).
17
- * Lower values are more permissive (more typos tolerated).
18
- * Higher values are stricter (fewer typos tolerated).
19
- */
20
- export const DEFAULT_FUZZY_THRESHOLD = 0.7;
21
- /**
22
- * Phase 4 Sprint 3: Maximum cache size to prevent memory bloat.
23
- */
24
- const FUZZY_CACHE_MAX_SIZE = 100;
25
- /**
26
- * Phase 4 Sprint 3: Cache TTL in milliseconds (5 minutes).
27
- */
28
- const FUZZY_CACHE_TTL_MS = 5 * 60 * 1000;
29
- /**
30
- * Phase 7 Sprint 3: Minimum number of entities to activate worker pool.
31
- */
32
- const WORKER_MIN_ENTITIES = 500;
33
- /**
34
- * Phase 7 Sprint 3: Maximum threshold for worker pool activation.
35
- * Higher thresholds have fewer matches, so single-threaded is faster.
36
- */
37
- const WORKER_MAX_THRESHOLD = 0.8;
38
- /**
39
- * Performs fuzzy search with configurable similarity threshold.
40
- */
41
- export class FuzzySearch {
42
- storage;
43
- /**
44
- * Phase 4 Sprint 3: Result cache for fuzzy search.
45
- * Maps cache key -> cached entity names.
46
- */
47
- fuzzyResultCache = new Map();
48
- /**
49
- * Phase 8: Worker pool using workerpool library.
50
- * Initialized lazily when needed.
51
- */
52
- workerPool = null;
53
- /**
54
- * Phase 7 Sprint 3: Path to the worker script.
55
- */
56
- workerPath;
57
- /**
58
- * Phase 8: Whether to use worker pool for parallel processing.
59
- * Can be disabled for testing or when workers are not available.
60
- */
61
- useWorkerPool;
62
- constructor(storage, options = {}) {
63
- this.storage = storage;
64
- this.useWorkerPool = options.useWorkerPool ?? true;
65
- // Calculate worker path using ESM module resolution
66
- const currentFileUrl = import.meta.url;
67
- const currentDir = dirname(fileURLToPath(currentFileUrl));
68
- // Check if we're running from src/ (during tests) or dist/ (production)
69
- const isRunningFromSrc = currentDir.includes(`${sep}src${sep}`);
70
- if (isRunningFromSrc) {
71
- // During tests, worker is in dist/workers/ relative to project root
72
- const projectRoot = join(currentDir, '..', '..');
73
- this.workerPath = join(projectRoot, 'dist', 'workers', 'levenshteinWorker.js');
74
- }
75
- else {
76
- // In production, worker is in dist/workers/ relative to current dist/search/
77
- this.workerPath = join(currentDir, '..', 'workers', 'levenshteinWorker.js');
78
- }
79
- }
80
- /**
81
- * Phase 4 Sprint 3: Generate cache key for fuzzy search parameters.
82
- */
83
- generateCacheKey(query, threshold, tags, minImportance, maxImportance, offset, limit) {
84
- return JSON.stringify({
85
- q: query.toLowerCase(),
86
- t: threshold,
87
- tags: tags?.sort().join(',') ?? '',
88
- min: minImportance,
89
- max: maxImportance,
90
- off: offset,
91
- lim: limit,
92
- });
93
- }
94
- /**
95
- * Phase 4 Sprint 3: Clear the fuzzy search cache.
96
- */
97
- clearCache() {
98
- this.fuzzyResultCache.clear();
99
- }
100
- /**
101
- * Phase 4 Sprint 3: Invalidate stale cache entries.
102
- */
103
- cleanupCache() {
104
- const now = Date.now();
105
- const entries = Array.from(this.fuzzyResultCache.entries());
106
- // Remove expired entries
107
- for (const [key, entry] of entries) {
108
- if (now - entry.timestamp > FUZZY_CACHE_TTL_MS) {
109
- this.fuzzyResultCache.delete(key);
110
- }
111
- }
112
- // If still over limit, remove oldest entries
113
- if (this.fuzzyResultCache.size > FUZZY_CACHE_MAX_SIZE) {
114
- const sortedEntries = entries
115
- .filter(([k]) => this.fuzzyResultCache.has(k))
116
- .sort((a, b) => a[1].timestamp - b[1].timestamp);
117
- const toRemove = sortedEntries.slice(0, this.fuzzyResultCache.size - FUZZY_CACHE_MAX_SIZE);
118
- for (const [key] of toRemove) {
119
- this.fuzzyResultCache.delete(key);
120
- }
121
- }
122
- }
123
- /**
124
- * Fuzzy search for entities with typo tolerance and pagination.
125
- *
126
- * Uses Levenshtein distance to calculate similarity between strings.
127
- * Matches if similarity >= threshold (0.0 to 1.0).
128
- *
129
- * Phase 4 Sprint 3: Implements result caching for repeated queries.
130
- *
131
- * @param query - Search query
132
- * @param threshold - Similarity threshold (0.0 to 1.0), default DEFAULT_FUZZY_THRESHOLD
133
- * @param tags - Optional tags filter
134
- * @param minImportance - Optional minimum importance
135
- * @param maxImportance - Optional maximum importance
136
- * @param offset - Number of results to skip (default: 0)
137
- * @param limit - Maximum number of results (default: 50, max: 200)
138
- * @returns Filtered knowledge graph with fuzzy matches and pagination applied
139
- */
140
- async fuzzySearch(query, threshold = DEFAULT_FUZZY_THRESHOLD, tags, minImportance, maxImportance, offset = 0, limit = SEARCH_LIMITS.DEFAULT) {
141
- const graph = await this.storage.loadGraph();
142
- const queryLower = query.toLowerCase();
143
- // Phase 4 Sprint 3: Generate cache key and check cache
144
- const cacheKey = this.generateCacheKey(query, threshold, tags, minImportance, maxImportance, offset, limit);
145
- const cached = this.fuzzyResultCache.get(cacheKey);
146
- // Check if cache is valid (entity count hasn't changed)
147
- if (cached && cached.entityCount === graph.entities.length) {
148
- const now = Date.now();
149
- if (now - cached.timestamp < FUZZY_CACHE_TTL_MS) {
150
- // Return cached results
151
- const cachedNameSet = new Set(cached.entityNames);
152
- const cachedEntities = graph.entities.filter(e => cachedNameSet.has(e.name));
153
- const cachedEntityNames = new Set(cached.entityNames);
154
- const cachedRelations = graph.relations.filter(r => cachedEntityNames.has(r.from) && cachedEntityNames.has(r.to));
155
- return { entities: cachedEntities, relations: cachedRelations };
156
- }
157
- }
158
- // Phase 7 Sprint 3: Use worker pool for large graphs with low thresholds
159
- // Phase 8: Respect useWorkerPool flag for testing
160
- const shouldUseWorkers = this.useWorkerPool &&
161
- graph.entities.length >= WORKER_MIN_ENTITIES &&
162
- threshold < WORKER_MAX_THRESHOLD;
163
- let fuzzyMatched;
164
- if (shouldUseWorkers) {
165
- fuzzyMatched = await this.searchWithWorkers(query, threshold, graph.entities);
166
- }
167
- else {
168
- // Perform single-threaded fuzzy search
169
- fuzzyMatched = this.performFuzzyMatch(graph.entities, queryLower, threshold);
170
- }
171
- // Apply tag and importance filters using SearchFilterChain
172
- const filters = { tags, minImportance, maxImportance };
173
- const filteredEntities = SearchFilterChain.applyFilters(fuzzyMatched, filters);
174
- // Apply pagination using SearchFilterChain
175
- const pagination = SearchFilterChain.validatePagination(offset, limit);
176
- const paginatedEntities = SearchFilterChain.paginate(filteredEntities, pagination);
177
- // Phase 4 Sprint 3: Cache the results
178
- this.fuzzyResultCache.set(cacheKey, {
179
- entityNames: paginatedEntities.map(e => e.name),
180
- entityCount: graph.entities.length,
181
- timestamp: Date.now(),
182
- });
183
- // Cleanup old cache entries periodically
184
- if (this.fuzzyResultCache.size > FUZZY_CACHE_MAX_SIZE / 2) {
185
- this.cleanupCache();
186
- }
187
- const filteredEntityNames = new Set(paginatedEntities.map(e => e.name));
188
- const filteredRelations = graph.relations.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to));
189
- return {
190
- entities: paginatedEntities,
191
- relations: filteredRelations,
192
- };
193
- }
194
- /**
195
- * Phase 4 Sprint 3: Perform the actual fuzzy matching logic.
196
- * Extracted from fuzzySearch for cleaner code structure.
197
- */
198
- performFuzzyMatch(entities, queryLower, threshold) {
199
- return entities.filter(e => {
200
- const lowercased = this.storage.getLowercased(e.name);
201
- // Check name match (use pre-computed lowercase)
202
- const nameLower = lowercased?.name ?? e.name.toLowerCase();
203
- if (this.isFuzzyMatchLower(nameLower, queryLower, threshold))
204
- return true;
205
- // Check type match (use pre-computed lowercase)
206
- const typeLower = lowercased?.entityType ?? e.entityType.toLowerCase();
207
- if (this.isFuzzyMatchLower(typeLower, queryLower, threshold))
208
- return true;
209
- // Check observations (use pre-computed lowercase array)
210
- const obsLower = lowercased?.observations ?? e.observations.map(o => o.toLowerCase());
211
- return obsLower.some(o =>
212
- // For observations, split into words and check each word
213
- o
214
- .split(/\s+/)
215
- .some(word => this.isFuzzyMatchLower(word, queryLower, threshold)) ||
216
- // Also check if the observation contains the query
217
- this.isFuzzyMatchLower(o, queryLower, threshold));
218
- });
219
- }
220
- /**
221
- * Check if two already-lowercase strings match with fuzzy logic.
222
- *
223
- * OPTIMIZED: Skips toLowerCase() calls when strings are already lowercase.
224
- *
225
- * @param s1 - First string (already lowercase)
226
- * @param s2 - Second string (already lowercase)
227
- * @param threshold - Similarity threshold (0.0 to 1.0)
228
- * @returns True if strings match fuzzily
229
- */
230
- isFuzzyMatchLower(s1, s2, threshold = 0.7) {
231
- // Exact match
232
- if (s1 === s2)
233
- return true;
234
- // One contains the other
235
- if (s1.includes(s2) || s2.includes(s1))
236
- return true;
237
- // Calculate similarity using Levenshtein distance
238
- const distance = levenshteinDistance(s1, s2);
239
- const maxLength = Math.max(s1.length, s2.length);
240
- const similarity = 1 - distance / maxLength;
241
- return similarity >= threshold;
242
- }
243
- /**
244
- * Phase 8: Perform fuzzy search using workerpool for parallel processing.
245
- *
246
- * Splits entities into chunks and processes them in parallel using worker threads.
247
- * Falls back to single-threaded search if worker execution fails.
248
- *
249
- * @param query - Search query
250
- * @param threshold - Similarity threshold
251
- * @param entities - Entities to search
252
- * @returns Array of matched entities
253
- */
254
- async searchWithWorkers(query, threshold, entities) {
255
- try {
256
- // Initialize worker pool lazily using workerpool
257
- if (!this.workerPool) {
258
- // Enable ESM module support for Node.js 20+
259
- // The 'type: module' option is needed for ESM workers but may not be in @types/node
260
- const workerThreadOpts = { type: 'module' };
261
- this.workerPool = workerpool.pool(this.workerPath, {
262
- maxWorkers: Math.max(1, workerpool.cpus - 1),
263
- workerType: 'thread',
264
- workerThreadOpts,
265
- });
266
- }
267
- // Split entities into chunks based on CPU count
268
- const numWorkers = Math.max(1, workerpool.cpus - 1);
269
- const chunkSize = Math.ceil(entities.length / numWorkers);
270
- const chunks = [];
271
- for (let i = 0; i < entities.length; i += chunkSize) {
272
- chunks.push(entities.slice(i, i + chunkSize));
273
- }
274
- // Prepare worker inputs with lowercased data
275
- const workerInputs = chunks.map(chunk => ({
276
- query,
277
- threshold,
278
- entities: chunk.map(e => ({
279
- name: e.name,
280
- nameLower: e.name.toLowerCase(),
281
- observations: e.observations.map(o => o.toLowerCase()),
282
- })),
283
- }));
284
- // Execute all chunks in parallel using workerpool with timeout
285
- const WORKER_TIMEOUT_MS = 30000; // 30 seconds
286
- const results = await Promise.all(workerInputs.map(input => this.workerPool.exec('searchEntities', [input])
287
- .timeout(WORKER_TIMEOUT_MS)));
288
- // Flatten results and extract matched entity names
289
- const matchedNames = new Set(results.flat().map(r => r.name));
290
- // Return entities that matched
291
- return entities.filter(e => matchedNames.has(e.name));
292
- }
293
- catch (error) {
294
- // Worker execution failed - fall back to single-threaded mode
295
- console.warn(`Worker pool execution failed, falling back to single-threaded fuzzy search: ${error instanceof Error ? error.message : String(error)}`);
296
- // Use the existing single-threaded implementation
297
- const queryLower = query.toLowerCase();
298
- return this.performFuzzyMatch(entities, queryLower, threshold);
299
- }
300
- }
301
- /**
302
- * Phase 8: Shutdown the worker pool and clean up resources.
303
- *
304
- * Should be called when FuzzySearch is no longer needed.
305
- */
306
- async shutdown() {
307
- if (this.workerPool) {
308
- await this.workerPool.terminate();
309
- this.workerPool = null;
310
- }
311
- }
312
- }
@@ -1,71 +0,0 @@
1
- /**
2
- * Ranked Search
3
- *
4
- * TF-IDF relevance-based search with scoring and pre-calculated indexes.
5
- *
6
- * @module search/RankedSearch
7
- */
8
- import type { SearchResult } from '../types/index.js';
9
- import type { GraphStorage } from '../core/GraphStorage.js';
10
- /**
11
- * Performs TF-IDF ranked search with optional pre-calculated indexes.
12
- */
13
- export declare class RankedSearch {
14
- private storage;
15
- private indexManager;
16
- /**
17
- * Phase 4 Sprint 2: Fallback token cache for entities.
18
- * Maps entity name -> pre-tokenized entity data.
19
- * Invalidated when graph changes (detected by entity count mismatch).
20
- */
21
- private fallbackTokenCache;
22
- private cachedEntityCount;
23
- constructor(storage: GraphStorage, storageDir?: string);
24
- /**
25
- * Phase 4 Sprint 2: Clear the fallback token cache.
26
- * Called when graph changes are detected or explicitly by external code.
27
- */
28
- clearTokenCache(): void;
29
- /**
30
- * Initialize and build the TF-IDF index for fast searches.
31
- *
32
- * Should be called after graph changes to keep index up-to-date.
33
- */
34
- buildIndex(): Promise<void>;
35
- /**
36
- * Update the index incrementally after entity changes.
37
- *
38
- * @param changedEntityNames - Names of entities that were created, updated, or deleted
39
- */
40
- updateIndex(changedEntityNames: Set<string>): Promise<void>;
41
- /**
42
- * Load the TF-IDF index from disk if available.
43
- */
44
- private ensureIndexLoaded;
45
- /**
46
- * Search with TF-IDF relevance ranking.
47
- *
48
- * Uses pre-calculated index if available, falls back to on-the-fly calculation.
49
- *
50
- * @param query - Search query
51
- * @param tags - Optional tags filter
52
- * @param minImportance - Optional minimum importance
53
- * @param maxImportance - Optional maximum importance
54
- * @param limit - Maximum results to return (default 50, max 200)
55
- * @returns Array of search results sorted by relevance
56
- */
57
- searchNodesRanked(query: string, tags?: string[], minImportance?: number, maxImportance?: number, limit?: number): Promise<SearchResult[]>;
58
- /**
59
- * Search using pre-calculated TF-IDF index (fast path).
60
- */
61
- private searchWithIndex;
62
- /**
63
- * Search without index (on-the-fly calculation, slow path).
64
- *
65
- * OPTIMIZED: Phase 4 Sprint 2 - Uses fallback token cache to avoid
66
- * repeated tokenization of entities. Pre-tokenizes all documents once
67
- * and caches for subsequent searches.
68
- */
69
- private searchWithoutIndex;
70
- }
71
- //# sourceMappingURL=RankedSearch.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"RankedSearch.d.ts","sourceRoot":"","sources":["../../src/search/RankedSearch.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAU,YAAY,EAA+B,MAAM,mBAAmB,CAAC;AAC3F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAM5D;;GAEG;AACH,qBAAa,YAAY;IAYrB,OAAO,CAAC,OAAO;IAXjB,OAAO,CAAC,YAAY,CAAkC;IAEtD;;;;OAIG;IACH,OAAO,CAAC,kBAAkB,CAA2C;IACrE,OAAO,CAAC,iBAAiB,CAAa;gBAG5B,OAAO,EAAE,YAAY,EAC7B,UAAU,CAAC,EAAE,MAAM;IAQrB;;;OAGG;IACH,eAAe,IAAI,IAAI;IAKvB;;;;OAIG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAUjC;;;;OAIG;IACG,WAAW,CAAC,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAUjE;;OAEG;YACW,iBAAiB;IAe/B;;;;;;;;;;;OAWG;IACG,iBAAiB,CACrB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,MAAM,EAAE,EACf,aAAa,CAAC,EAAE,MAAM,EACtB,aAAa,CAAC,EAAE,MAAM,EACtB,KAAK,GAAE,MAA8B,GACpC,OAAO,CAAC,YAAY,EAAE,CAAC;IAsB1B;;OAEG;IACH,OAAO,CAAC,eAAe;IAgEvB;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB;CAqF3B"}
@@ -1,238 +0,0 @@
1
- /**
2
- * Ranked Search
3
- *
4
- * TF-IDF relevance-based search with scoring and pre-calculated indexes.
5
- *
6
- * @module search/RankedSearch
7
- */
8
- import { calculateTF, calculateIDFFromTokenSets, tokenize } from '../utils/index.js';
9
- import { SEARCH_LIMITS } from '../utils/constants.js';
10
- import { TFIDFIndexManager } from './TFIDFIndexManager.js';
11
- import { SearchFilterChain } from './SearchFilterChain.js';
12
- /**
13
- * Performs TF-IDF ranked search with optional pre-calculated indexes.
14
- */
15
- export class RankedSearch {
16
- storage;
17
- indexManager = null;
18
- /**
19
- * Phase 4 Sprint 2: Fallback token cache for entities.
20
- * Maps entity name -> pre-tokenized entity data.
21
- * Invalidated when graph changes (detected by entity count mismatch).
22
- */
23
- fallbackTokenCache = new Map();
24
- cachedEntityCount = 0;
25
- constructor(storage, storageDir) {
26
- this.storage = storage;
27
- // Initialize index manager if storage directory is provided
28
- if (storageDir) {
29
- this.indexManager = new TFIDFIndexManager(storageDir);
30
- }
31
- }
32
- /**
33
- * Phase 4 Sprint 2: Clear the fallback token cache.
34
- * Called when graph changes are detected or explicitly by external code.
35
- */
36
- clearTokenCache() {
37
- this.fallbackTokenCache.clear();
38
- this.cachedEntityCount = 0;
39
- }
40
- /**
41
- * Initialize and build the TF-IDF index for fast searches.
42
- *
43
- * Should be called after graph changes to keep index up-to-date.
44
- */
45
- async buildIndex() {
46
- if (!this.indexManager) {
47
- throw new Error('Index manager not initialized. Provide storageDir to constructor.');
48
- }
49
- const graph = await this.storage.loadGraph();
50
- await this.indexManager.buildIndex(graph);
51
- await this.indexManager.saveIndex();
52
- }
53
- /**
54
- * Update the index incrementally after entity changes.
55
- *
56
- * @param changedEntityNames - Names of entities that were created, updated, or deleted
57
- */
58
- async updateIndex(changedEntityNames) {
59
- if (!this.indexManager) {
60
- return; // No index manager, skip
61
- }
62
- const graph = await this.storage.loadGraph();
63
- await this.indexManager.updateIndex(graph, changedEntityNames);
64
- await this.indexManager.saveIndex();
65
- }
66
- /**
67
- * Load the TF-IDF index from disk if available.
68
- */
69
- async ensureIndexLoaded() {
70
- if (!this.indexManager) {
71
- return null;
72
- }
73
- // Return cached index if already loaded
74
- const cached = this.indexManager.getIndex();
75
- if (cached) {
76
- return cached;
77
- }
78
- // Try to load from disk
79
- return await this.indexManager.loadIndex();
80
- }
81
- /**
82
- * Search with TF-IDF relevance ranking.
83
- *
84
- * Uses pre-calculated index if available, falls back to on-the-fly calculation.
85
- *
86
- * @param query - Search query
87
- * @param tags - Optional tags filter
88
- * @param minImportance - Optional minimum importance
89
- * @param maxImportance - Optional maximum importance
90
- * @param limit - Maximum results to return (default 50, max 200)
91
- * @returns Array of search results sorted by relevance
92
- */
93
- async searchNodesRanked(query, tags, minImportance, maxImportance, limit = SEARCH_LIMITS.DEFAULT) {
94
- // Enforce maximum search limit
95
- const effectiveLimit = Math.min(limit, SEARCH_LIMITS.MAX);
96
- const graph = await this.storage.loadGraph();
97
- // Apply tag and importance filters using SearchFilterChain
98
- const filters = { tags, minImportance, maxImportance };
99
- const filteredEntities = SearchFilterChain.applyFilters(graph.entities, filters);
100
- // Try to use pre-calculated index
101
- const index = await this.ensureIndexLoaded();
102
- const queryTerms = tokenize(query);
103
- if (index) {
104
- // Use pre-calculated index for fast search
105
- return this.searchWithIndex(filteredEntities, queryTerms, index, effectiveLimit);
106
- }
107
- else {
108
- // Fall back to on-the-fly calculation
109
- return this.searchWithoutIndex(filteredEntities, queryTerms, effectiveLimit);
110
- }
111
- }
112
- /**
113
- * Search using pre-calculated TF-IDF index (fast path).
114
- */
115
- searchWithIndex(entities, queryTerms, index, limit) {
116
- const results = [];
117
- for (const entity of entities) {
118
- const docVector = index.documents.get(entity.name);
119
- if (!docVector) {
120
- continue; // Entity not in index
121
- }
122
- // Calculate total terms in document (sum of all term frequencies)
123
- const totalTerms = Object.values(docVector.terms).reduce((sum, count) => sum + count, 0);
124
- if (totalTerms === 0)
125
- continue;
126
- // Calculate score using pre-calculated term frequencies and IDF
127
- let totalScore = 0;
128
- const matchedFields = {};
129
- for (const term of queryTerms) {
130
- const termCount = docVector.terms[term] || 0;
131
- const idf = index.idf.get(term) || 0;
132
- // Calculate TF-IDF: (termCount / totalTerms) * IDF
133
- const tf = termCount / totalTerms;
134
- const tfidf = tf * idf;
135
- totalScore += tfidf;
136
- // Track which fields matched
137
- if (termCount > 0) {
138
- if (entity.name.toLowerCase().includes(term)) {
139
- matchedFields.name = true;
140
- }
141
- if (entity.entityType.toLowerCase().includes(term)) {
142
- matchedFields.entityType = true;
143
- }
144
- const matchedObs = entity.observations.filter(o => o.toLowerCase().includes(term));
145
- if (matchedObs.length > 0) {
146
- matchedFields.observations = matchedObs;
147
- }
148
- }
149
- }
150
- // Only include entities with non-zero scores
151
- if (totalScore > 0) {
152
- results.push({
153
- entity,
154
- score: totalScore,
155
- matchedFields,
156
- });
157
- }
158
- }
159
- // Sort by score descending and apply limit
160
- return results
161
- .sort((a, b) => b.score - a.score)
162
- .slice(0, limit);
163
- }
164
- /**
165
- * Search without index (on-the-fly calculation, slow path).
166
- *
167
- * OPTIMIZED: Phase 4 Sprint 2 - Uses fallback token cache to avoid
168
- * repeated tokenization of entities. Pre-tokenizes all documents once
169
- * and caches for subsequent searches.
170
- */
171
- searchWithoutIndex(entities, queryTerms, limit) {
172
- const results = [];
173
- // Phase 4 Sprint 2: Check if cache needs invalidation
174
- if (entities.length !== this.cachedEntityCount) {
175
- this.clearTokenCache();
176
- this.cachedEntityCount = entities.length;
177
- }
178
- // Phase 4 Sprint 2: Get or compute tokenized data for each entity
179
- const documentData = entities.map(e => {
180
- // Check cache first
181
- const cached = this.fallbackTokenCache.get(e.name);
182
- if (cached) {
183
- return cached;
184
- }
185
- // Compute and cache tokenized data
186
- const text = [e.name, e.entityType, ...e.observations].join(' ');
187
- const tokens = tokenize(text);
188
- const tokenized = {
189
- entity: e,
190
- text,
191
- tokens,
192
- tokenSet: new Set(tokens),
193
- };
194
- this.fallbackTokenCache.set(e.name, tokenized);
195
- return tokenized;
196
- });
197
- // Pre-compute token sets for IDF calculation (O(1) lookup per document)
198
- const tokenSets = documentData.map(d => d.tokenSet);
199
- for (const docData of documentData) {
200
- const { entity, text } = docData;
201
- // Calculate score for each query term
202
- let totalScore = 0;
203
- const matchedFields = {};
204
- for (const term of queryTerms) {
205
- // Calculate TF using pre-tokenized tokens
206
- const tf = calculateTF(term, text);
207
- // Calculate IDF using pre-computed token sets (O(1) per document)
208
- const idf = calculateIDFFromTokenSets(term, tokenSets);
209
- // TF-IDF score
210
- const score = tf * idf;
211
- totalScore += score;
212
- // Track which fields matched
213
- if (entity.name.toLowerCase().includes(term)) {
214
- matchedFields.name = true;
215
- }
216
- if (entity.entityType.toLowerCase().includes(term)) {
217
- matchedFields.entityType = true;
218
- }
219
- const matchedObs = entity.observations.filter(o => o.toLowerCase().includes(term));
220
- if (matchedObs.length > 0) {
221
- matchedFields.observations = matchedObs;
222
- }
223
- }
224
- // Only include entities with non-zero scores
225
- if (totalScore > 0) {
226
- results.push({
227
- entity,
228
- score: totalScore,
229
- matchedFields,
230
- });
231
- }
232
- }
233
- // Sort by score descending and apply limit
234
- return results
235
- .sort((a, b) => b.score - a.score)
236
- .slice(0, limit);
237
- }
238
- }