@danielsimonjr/memory-mcp 0.48.0 → 9.8.0

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 (209) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +2000 -194
  3. package/dist/__tests__/file-path.test.js +7 -11
  4. package/dist/__tests__/knowledge-graph.test.js +3 -8
  5. package/dist/core/EntityManager.d.ts +266 -0
  6. package/dist/core/EntityManager.d.ts.map +1 -0
  7. package/dist/core/EntityManager.js +85 -133
  8. package/dist/core/GraphEventEmitter.d.ts +202 -0
  9. package/dist/core/GraphEventEmitter.d.ts.map +1 -0
  10. package/dist/core/GraphEventEmitter.js +346 -0
  11. package/dist/core/GraphStorage.d.ts +395 -0
  12. package/dist/core/GraphStorage.d.ts.map +1 -0
  13. package/dist/core/GraphStorage.js +643 -31
  14. package/dist/core/GraphTraversal.d.ts +141 -0
  15. package/dist/core/GraphTraversal.d.ts.map +1 -0
  16. package/dist/core/GraphTraversal.js +573 -0
  17. package/dist/core/HierarchyManager.d.ts +111 -0
  18. package/dist/core/HierarchyManager.d.ts.map +1 -0
  19. package/dist/{features → core}/HierarchyManager.js +14 -9
  20. package/dist/core/ManagerContext.d.ts +72 -0
  21. package/dist/core/ManagerContext.d.ts.map +1 -0
  22. package/dist/core/ManagerContext.js +118 -0
  23. package/dist/core/ObservationManager.d.ts +85 -0
  24. package/dist/core/ObservationManager.d.ts.map +1 -0
  25. package/dist/core/ObservationManager.js +51 -57
  26. package/dist/core/RelationManager.d.ts +131 -0
  27. package/dist/core/RelationManager.d.ts.map +1 -0
  28. package/dist/core/RelationManager.js +31 -7
  29. package/dist/core/SQLiteStorage.d.ts +354 -0
  30. package/dist/core/SQLiteStorage.d.ts.map +1 -0
  31. package/dist/core/SQLiteStorage.js +917 -0
  32. package/dist/core/StorageFactory.d.ts +45 -0
  33. package/dist/core/StorageFactory.d.ts.map +1 -0
  34. package/dist/core/StorageFactory.js +64 -0
  35. package/dist/core/TransactionManager.d.ts +464 -0
  36. package/dist/core/TransactionManager.d.ts.map +1 -0
  37. package/dist/core/TransactionManager.js +490 -13
  38. package/dist/core/index.d.ts +17 -0
  39. package/dist/core/index.d.ts.map +1 -0
  40. package/dist/core/index.js +12 -2
  41. package/dist/features/AnalyticsManager.d.ts +44 -0
  42. package/dist/features/AnalyticsManager.d.ts.map +1 -0
  43. package/dist/features/AnalyticsManager.js +3 -2
  44. package/dist/features/ArchiveManager.d.ts +133 -0
  45. package/dist/features/ArchiveManager.d.ts.map +1 -0
  46. package/dist/features/ArchiveManager.js +221 -14
  47. package/dist/features/CompressionManager.d.ts +117 -0
  48. package/dist/features/CompressionManager.d.ts.map +1 -0
  49. package/dist/features/CompressionManager.js +189 -20
  50. package/dist/features/IOManager.d.ts +225 -0
  51. package/dist/features/IOManager.d.ts.map +1 -0
  52. package/dist/features/IOManager.js +1041 -0
  53. package/dist/features/StreamingExporter.d.ts +123 -0
  54. package/dist/features/StreamingExporter.d.ts.map +1 -0
  55. package/dist/features/StreamingExporter.js +203 -0
  56. package/dist/features/TagManager.d.ts +147 -0
  57. package/dist/features/TagManager.d.ts.map +1 -0
  58. package/dist/features/index.d.ts +12 -0
  59. package/dist/features/index.d.ts.map +1 -0
  60. package/dist/features/index.js +5 -6
  61. package/dist/index.d.ts +9 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +10 -10
  64. package/dist/memory.jsonl +1 -26
  65. package/dist/search/BasicSearch.d.ts +51 -0
  66. package/dist/search/BasicSearch.d.ts.map +1 -0
  67. package/dist/search/BasicSearch.js +9 -3
  68. package/dist/search/BooleanSearch.d.ts +98 -0
  69. package/dist/search/BooleanSearch.d.ts.map +1 -0
  70. package/dist/search/BooleanSearch.js +156 -9
  71. package/dist/search/EmbeddingService.d.ts +178 -0
  72. package/dist/search/EmbeddingService.d.ts.map +1 -0
  73. package/dist/search/EmbeddingService.js +358 -0
  74. package/dist/search/FuzzySearch.d.ts +118 -0
  75. package/dist/search/FuzzySearch.d.ts.map +1 -0
  76. package/dist/search/FuzzySearch.js +241 -25
  77. package/dist/search/QueryCostEstimator.d.ts +111 -0
  78. package/dist/search/QueryCostEstimator.d.ts.map +1 -0
  79. package/dist/search/QueryCostEstimator.js +355 -0
  80. package/dist/search/RankedSearch.d.ts +71 -0
  81. package/dist/search/RankedSearch.d.ts.map +1 -0
  82. package/dist/search/RankedSearch.js +54 -6
  83. package/dist/search/SavedSearchManager.d.ts +79 -0
  84. package/dist/search/SavedSearchManager.d.ts.map +1 -0
  85. package/dist/search/SearchFilterChain.d.ts +120 -0
  86. package/dist/search/SearchFilterChain.d.ts.map +1 -0
  87. package/dist/search/SearchFilterChain.js +2 -4
  88. package/dist/search/SearchManager.d.ts +326 -0
  89. package/dist/search/SearchManager.d.ts.map +1 -0
  90. package/dist/search/SearchManager.js +148 -0
  91. package/dist/search/SearchSuggestions.d.ts +27 -0
  92. package/dist/search/SearchSuggestions.d.ts.map +1 -0
  93. package/dist/search/SearchSuggestions.js +1 -1
  94. package/dist/search/SemanticSearch.d.ts +149 -0
  95. package/dist/search/SemanticSearch.d.ts.map +1 -0
  96. package/dist/search/SemanticSearch.js +323 -0
  97. package/dist/search/TFIDFEventSync.d.ts +85 -0
  98. package/dist/search/TFIDFEventSync.d.ts.map +1 -0
  99. package/dist/search/TFIDFEventSync.js +133 -0
  100. package/dist/search/TFIDFIndexManager.d.ts +151 -0
  101. package/dist/search/TFIDFIndexManager.d.ts.map +1 -0
  102. package/dist/search/TFIDFIndexManager.js +232 -17
  103. package/dist/search/VectorStore.d.ts +235 -0
  104. package/dist/search/VectorStore.d.ts.map +1 -0
  105. package/dist/search/VectorStore.js +311 -0
  106. package/dist/search/index.d.ts +21 -0
  107. package/dist/search/index.d.ts.map +1 -0
  108. package/dist/search/index.js +12 -0
  109. package/dist/server/MCPServer.d.ts +21 -0
  110. package/dist/server/MCPServer.d.ts.map +1 -0
  111. package/dist/server/MCPServer.js +4 -4
  112. package/dist/server/responseCompressor.d.ts +94 -0
  113. package/dist/server/responseCompressor.d.ts.map +1 -0
  114. package/dist/server/responseCompressor.js +127 -0
  115. package/dist/server/toolDefinitions.d.ts +27 -0
  116. package/dist/server/toolDefinitions.d.ts.map +1 -0
  117. package/dist/server/toolDefinitions.js +188 -17
  118. package/dist/server/toolHandlers.d.ts +41 -0
  119. package/dist/server/toolHandlers.d.ts.map +1 -0
  120. package/dist/server/toolHandlers.js +467 -75
  121. package/dist/types/index.d.ts +13 -0
  122. package/dist/types/index.d.ts.map +1 -0
  123. package/dist/types/index.js +1 -1
  124. package/dist/types/types.d.ts +1654 -0
  125. package/dist/types/types.d.ts.map +1 -0
  126. package/dist/types/types.js +9 -0
  127. package/dist/utils/compressedCache.d.ts +192 -0
  128. package/dist/utils/compressedCache.d.ts.map +1 -0
  129. package/dist/utils/compressedCache.js +309 -0
  130. package/dist/utils/compressionUtil.d.ts +214 -0
  131. package/dist/utils/compressionUtil.d.ts.map +1 -0
  132. package/dist/utils/compressionUtil.js +247 -0
  133. package/dist/utils/constants.d.ts +245 -0
  134. package/dist/utils/constants.d.ts.map +1 -0
  135. package/dist/utils/constants.js +124 -0
  136. package/dist/utils/entityUtils.d.ts +321 -0
  137. package/dist/utils/entityUtils.d.ts.map +1 -0
  138. package/dist/utils/entityUtils.js +434 -4
  139. package/dist/utils/errors.d.ts +95 -0
  140. package/dist/utils/errors.d.ts.map +1 -0
  141. package/dist/utils/errors.js +24 -0
  142. package/dist/utils/formatters.d.ts +145 -0
  143. package/dist/utils/formatters.d.ts.map +1 -0
  144. package/dist/utils/{paginationUtils.js → formatters.js} +54 -3
  145. package/dist/utils/index.d.ts +23 -0
  146. package/dist/utils/index.d.ts.map +1 -0
  147. package/dist/utils/index.js +69 -31
  148. package/dist/utils/indexes.d.ts +270 -0
  149. package/dist/utils/indexes.d.ts.map +1 -0
  150. package/dist/utils/indexes.js +526 -0
  151. package/dist/utils/logger.d.ts +24 -0
  152. package/dist/utils/logger.d.ts.map +1 -0
  153. package/dist/utils/operationUtils.d.ts +124 -0
  154. package/dist/utils/operationUtils.d.ts.map +1 -0
  155. package/dist/utils/operationUtils.js +175 -0
  156. package/dist/utils/parallelUtils.d.ts +72 -0
  157. package/dist/utils/parallelUtils.d.ts.map +1 -0
  158. package/dist/utils/parallelUtils.js +169 -0
  159. package/dist/utils/schemas.d.ts +374 -0
  160. package/dist/utils/schemas.d.ts.map +1 -0
  161. package/dist/utils/schemas.js +302 -2
  162. package/dist/utils/searchAlgorithms.d.ts +99 -0
  163. package/dist/utils/searchAlgorithms.d.ts.map +1 -0
  164. package/dist/utils/searchAlgorithms.js +167 -0
  165. package/dist/utils/searchCache.d.ts +108 -0
  166. package/dist/utils/searchCache.d.ts.map +1 -0
  167. package/dist/utils/taskScheduler.d.ts +290 -0
  168. package/dist/utils/taskScheduler.d.ts.map +1 -0
  169. package/dist/utils/taskScheduler.js +466 -0
  170. package/dist/workers/index.d.ts +12 -0
  171. package/dist/workers/index.d.ts.map +1 -0
  172. package/dist/workers/index.js +9 -0
  173. package/dist/workers/levenshteinWorker.d.ts +60 -0
  174. package/dist/workers/levenshteinWorker.d.ts.map +1 -0
  175. package/dist/workers/levenshteinWorker.js +98 -0
  176. package/package.json +17 -4
  177. package/dist/__tests__/edge-cases/edge-cases.test.js +0 -406
  178. package/dist/__tests__/integration/workflows.test.js +0 -449
  179. package/dist/__tests__/performance/benchmarks.test.js +0 -413
  180. package/dist/__tests__/unit/core/EntityManager.test.js +0 -334
  181. package/dist/__tests__/unit/core/GraphStorage.test.js +0 -205
  182. package/dist/__tests__/unit/core/RelationManager.test.js +0 -274
  183. package/dist/__tests__/unit/features/CompressionManager.test.js +0 -350
  184. package/dist/__tests__/unit/search/BasicSearch.test.js +0 -311
  185. package/dist/__tests__/unit/search/BooleanSearch.test.js +0 -432
  186. package/dist/__tests__/unit/search/FuzzySearch.test.js +0 -448
  187. package/dist/__tests__/unit/search/RankedSearch.test.js +0 -379
  188. package/dist/__tests__/unit/utils/levenshtein.test.js +0 -77
  189. package/dist/core/KnowledgeGraphManager.js +0 -423
  190. package/dist/features/BackupManager.js +0 -311
  191. package/dist/features/ExportManager.js +0 -305
  192. package/dist/features/ImportExportManager.js +0 -50
  193. package/dist/features/ImportManager.js +0 -328
  194. package/dist/memory-saved-searches.jsonl +0 -0
  195. package/dist/memory-tag-aliases.jsonl +0 -0
  196. package/dist/types/analytics.types.js +0 -6
  197. package/dist/types/entity.types.js +0 -7
  198. package/dist/types/import-export.types.js +0 -7
  199. package/dist/types/search.types.js +0 -7
  200. package/dist/types/tag.types.js +0 -6
  201. package/dist/utils/dateUtils.js +0 -89
  202. package/dist/utils/filterUtils.js +0 -155
  203. package/dist/utils/levenshtein.js +0 -62
  204. package/dist/utils/pathUtils.js +0 -115
  205. package/dist/utils/responseFormatter.js +0 -55
  206. package/dist/utils/tagUtils.js +0 -107
  207. package/dist/utils/tfidf.js +0 -90
  208. package/dist/utils/validationHelper.js +0 -99
  209. package/dist/utils/validationUtils.js +0 -109
@@ -0,0 +1,355 @@
1
+ /**
2
+ * Query Cost Estimator
3
+ *
4
+ * Phase 10 Sprint 4: Estimates the cost of different search methods
5
+ * and recommends the optimal method based on query characteristics
6
+ * and graph size.
7
+ *
8
+ * @module search/QueryCostEstimator
9
+ */
10
+ /**
11
+ * Default options for the QueryCostEstimator.
12
+ */
13
+ const DEFAULT_OPTIONS = {
14
+ basicTimePerEntity: 0.01,
15
+ rankedTimePerEntity: 0.05,
16
+ booleanTimePerEntity: 0.02,
17
+ fuzzyTimePerEntity: 0.1,
18
+ semanticTimePerEntity: 0.5,
19
+ lowComplexityThreshold: 100,
20
+ highComplexityThreshold: 1000,
21
+ };
22
+ /**
23
+ * Phase 10 Sprint 4: Estimates search query costs and recommends optimal methods.
24
+ *
25
+ * Analyzes query characteristics and graph size to estimate execution time
26
+ * and recommend the most appropriate search method.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const estimator = new QueryCostEstimator();
31
+ *
32
+ * // Get estimate for a specific method
33
+ * const estimate = estimator.estimateMethod('ranked', 'test query', 1000);
34
+ *
35
+ * // Get the recommended method for a query
36
+ * const recommendation = estimator.recommendMethod('test query', 1000);
37
+ *
38
+ * // Get estimates for all methods
39
+ * const allEstimates = estimator.estimateAllMethods('test query', 1000);
40
+ * ```
41
+ */
42
+ export class QueryCostEstimator {
43
+ options;
44
+ /**
45
+ * Create a new QueryCostEstimator instance.
46
+ *
47
+ * @param options - Optional configuration overrides
48
+ */
49
+ constructor(options) {
50
+ this.options = { ...DEFAULT_OPTIONS, ...options };
51
+ }
52
+ /**
53
+ * Estimate the cost of a specific search method.
54
+ *
55
+ * @param method - The search method to estimate
56
+ * @param query - The search query
57
+ * @param entityCount - Number of entities in the graph
58
+ * @returns Cost estimate for the method
59
+ */
60
+ estimateMethod(method, query, entityCount) {
61
+ // Get the recommended method first to determine isRecommended
62
+ const recommendedMethod = this.getRecommendedMethodOnly(query, entityCount);
63
+ return this.estimateMethodInternal(method, query, entityCount, method === recommendedMethod);
64
+ }
65
+ /**
66
+ * Internal method to estimate without triggering recursion.
67
+ * @private
68
+ */
69
+ estimateMethodInternal(method, query, entityCount, isRecommended) {
70
+ const baseTime = this.getBaseTimeForMethod(method);
71
+ const queryComplexityFactor = this.getQueryComplexityFactor(query, method);
72
+ const estimatedTimeMs = baseTime * entityCount * queryComplexityFactor;
73
+ const complexity = this.getComplexity(entityCount);
74
+ const recommendation = this.getRecommendation(method, query, entityCount, complexity);
75
+ return {
76
+ method,
77
+ estimatedTimeMs: Math.round(estimatedTimeMs * 100) / 100,
78
+ complexity,
79
+ entityCount,
80
+ recommendation,
81
+ isRecommended,
82
+ };
83
+ }
84
+ /**
85
+ * Get just the recommended method without full estimate (avoids recursion).
86
+ * @private
87
+ */
88
+ getRecommendedMethodOnly(query, entityCount, preferredMethods) {
89
+ const methods = preferredMethods ?? ['basic', 'ranked', 'boolean', 'fuzzy', 'semantic'];
90
+ // Score each method and find the best
91
+ let bestMethod = methods[0];
92
+ let bestScore = this.scoreMethod(methods[0], query, entityCount);
93
+ for (let i = 1; i < methods.length; i++) {
94
+ const score = this.scoreMethod(methods[i], query, entityCount);
95
+ if (score > bestScore) {
96
+ bestScore = score;
97
+ bestMethod = methods[i];
98
+ }
99
+ }
100
+ return bestMethod;
101
+ }
102
+ /**
103
+ * Get estimates for all available search methods.
104
+ *
105
+ * @param query - The search query
106
+ * @param entityCount - Number of entities in the graph
107
+ * @returns Array of estimates for all methods
108
+ */
109
+ estimateAllMethods(query, entityCount) {
110
+ const methods = ['basic', 'ranked', 'boolean', 'fuzzy', 'semantic'];
111
+ const recommendedMethod = this.getRecommendedMethodOnly(query, entityCount);
112
+ return methods.map(method => this.estimateMethodInternal(method, query, entityCount, method === recommendedMethod));
113
+ }
114
+ /**
115
+ * Recommend the best search method for a query.
116
+ *
117
+ * @param query - The search query
118
+ * @param entityCount - Number of entities in the graph
119
+ * @param preferredMethods - Optional array of methods to consider (default: all)
120
+ * @returns The recommended method and reason
121
+ */
122
+ recommendMethod(query, entityCount, preferredMethods) {
123
+ const methods = preferredMethods ?? ['basic', 'ranked', 'boolean', 'fuzzy', 'semantic'];
124
+ // Score each method based on query characteristics and cost
125
+ const scores = methods.map(method => ({
126
+ method,
127
+ score: this.scoreMethod(method, query, entityCount),
128
+ estimate: this.estimateMethod(method, query, entityCount),
129
+ }));
130
+ // Sort by score (higher is better)
131
+ scores.sort((a, b) => b.score - a.score);
132
+ const best = scores[0];
133
+ const reason = this.getSelectionReason(best.method, query, entityCount);
134
+ return {
135
+ method: best.method,
136
+ reason,
137
+ estimate: best.estimate,
138
+ };
139
+ }
140
+ /**
141
+ * Get the base time per entity for a search method.
142
+ * @private
143
+ */
144
+ getBaseTimeForMethod(method) {
145
+ switch (method) {
146
+ case 'basic':
147
+ return this.options.basicTimePerEntity;
148
+ case 'ranked':
149
+ return this.options.rankedTimePerEntity;
150
+ case 'boolean':
151
+ return this.options.booleanTimePerEntity;
152
+ case 'fuzzy':
153
+ return this.options.fuzzyTimePerEntity;
154
+ case 'semantic':
155
+ return this.options.semanticTimePerEntity;
156
+ }
157
+ }
158
+ /**
159
+ * Calculate a complexity factor based on query characteristics.
160
+ * @private
161
+ */
162
+ getQueryComplexityFactor(query, method) {
163
+ const words = query.trim().split(/\s+/).length;
164
+ const hasOperators = /\b(AND|OR|NOT)\b/.test(query);
165
+ const hasWildcard = query.includes('*');
166
+ const hasQuotes = query.includes('"');
167
+ let factor = 1.0;
168
+ // More words = slightly more complex
169
+ factor += (words - 1) * 0.1;
170
+ // Boolean operators increase boolean search cost, decrease others
171
+ if (hasOperators) {
172
+ if (method === 'boolean') {
173
+ factor *= 0.8; // Boolean search is optimized for operators
174
+ }
175
+ else {
176
+ factor *= 1.5; // Other methods struggle with operators
177
+ }
178
+ }
179
+ // Wildcards increase fuzzy search efficiency
180
+ if (hasWildcard) {
181
+ if (method === 'fuzzy') {
182
+ factor *= 0.9;
183
+ }
184
+ else {
185
+ factor *= 1.3;
186
+ }
187
+ }
188
+ // Quoted phrases benefit ranked search
189
+ if (hasQuotes) {
190
+ if (method === 'ranked') {
191
+ factor *= 0.9;
192
+ }
193
+ else {
194
+ factor *= 1.1;
195
+ }
196
+ }
197
+ return Math.max(0.5, Math.min(factor, 3.0)); // Clamp between 0.5 and 3.0
198
+ }
199
+ /**
200
+ * Get the complexity level based on entity count.
201
+ * @private
202
+ */
203
+ getComplexity(entityCount) {
204
+ if (entityCount <= this.options.lowComplexityThreshold) {
205
+ return 'low';
206
+ }
207
+ if (entityCount >= this.options.highComplexityThreshold) {
208
+ return 'high';
209
+ }
210
+ return 'medium';
211
+ }
212
+ /**
213
+ * Generate a human-readable recommendation.
214
+ * @private
215
+ */
216
+ getRecommendation(method, _query, _entityCount, complexity) {
217
+ const recommendations = {
218
+ basic: 'Fast substring matching, best for simple queries',
219
+ ranked: 'TF-IDF relevance ranking, best for finding most relevant results',
220
+ boolean: 'Boolean operators (AND/OR/NOT), best for precise filtering',
221
+ fuzzy: 'Tolerant of typos and misspellings, best for uncertain queries',
222
+ semantic: 'Meaning-based search using embeddings, best for conceptual queries',
223
+ };
224
+ let recommendation = recommendations[method];
225
+ if (complexity === 'high' && method === 'semantic') {
226
+ recommendation += ' - may be slow for large graphs';
227
+ }
228
+ if (complexity === 'low') {
229
+ recommendation += ' - fast for small graphs';
230
+ }
231
+ return recommendation;
232
+ }
233
+ /**
234
+ * Score a method based on query characteristics and graph size.
235
+ * Higher score = better fit.
236
+ * @private
237
+ */
238
+ scoreMethod(method, query, entityCount) {
239
+ let score = 50; // Base score
240
+ const hasOperators = /\b(AND|OR|NOT)\b/.test(query);
241
+ const hasWildcard = query.includes('*');
242
+ const hasQuotes = query.includes('"');
243
+ const words = query.trim().split(/\s+/).length;
244
+ const isShortQuery = words <= 2;
245
+ const isLongQuery = words >= 5;
246
+ const complexity = this.getComplexity(entityCount);
247
+ switch (method) {
248
+ case 'basic':
249
+ // Basic is good for simple, short queries on any size graph
250
+ if (isShortQuery && !hasOperators && !hasWildcard) {
251
+ score += 30;
252
+ }
253
+ if (complexity === 'low') {
254
+ score += 20;
255
+ }
256
+ // Basic is fastest, give bonus for speed
257
+ score += 10;
258
+ break;
259
+ case 'ranked':
260
+ // Ranked is good for relevance-focused queries
261
+ if (words >= 2) {
262
+ score += 25; // Better for multi-word queries
263
+ }
264
+ if (hasQuotes) {
265
+ score += 15; // Good for phrase matching
266
+ }
267
+ if (!hasOperators) {
268
+ score += 10; // Not optimized for boolean
269
+ }
270
+ // Good balance of speed and quality
271
+ score += 15;
272
+ break;
273
+ case 'boolean':
274
+ // Boolean is best for queries with operators
275
+ if (hasOperators) {
276
+ score += 40;
277
+ }
278
+ if (!hasOperators) {
279
+ score -= 20; // Not useful without operators
280
+ }
281
+ // Fast for filtering
282
+ score += 10;
283
+ break;
284
+ case 'fuzzy':
285
+ // Fuzzy is good for typo-tolerant search
286
+ if (hasWildcard) {
287
+ score += 25;
288
+ }
289
+ if (isShortQuery) {
290
+ score += 15; // Works best on shorter queries
291
+ }
292
+ if (isLongQuery) {
293
+ score -= 15; // Slow on long queries
294
+ }
295
+ if (complexity === 'high') {
296
+ score -= 20; // Slow on large graphs
297
+ }
298
+ break;
299
+ case 'semantic':
300
+ // Semantic is good for conceptual/meaning-based queries
301
+ if (isLongQuery) {
302
+ score += 30; // Better for longer, more descriptive queries
303
+ }
304
+ if (!hasOperators && !hasWildcard) {
305
+ score += 15; // Natural language queries
306
+ }
307
+ // Penalize for large graphs (slow)
308
+ if (complexity === 'high') {
309
+ score -= 30;
310
+ }
311
+ if (complexity === 'medium') {
312
+ score -= 10;
313
+ }
314
+ break;
315
+ }
316
+ return score;
317
+ }
318
+ /**
319
+ * Get a human-readable reason for why a method was selected.
320
+ * @private
321
+ */
322
+ getSelectionReason(method, query, entityCount) {
323
+ const hasOperators = /\b(AND|OR|NOT)\b/.test(query);
324
+ const hasWildcard = query.includes('*');
325
+ const words = query.trim().split(/\s+/).length;
326
+ const complexity = this.getComplexity(entityCount);
327
+ switch (method) {
328
+ case 'basic':
329
+ if (complexity === 'low') {
330
+ return 'Basic search selected for small graph size - fast and efficient';
331
+ }
332
+ return 'Basic search selected for simple query pattern';
333
+ case 'ranked':
334
+ if (words >= 2) {
335
+ return 'Ranked search selected for multi-word query - provides relevance ordering';
336
+ }
337
+ return 'Ranked search selected for best balance of speed and relevance';
338
+ case 'boolean':
339
+ if (hasOperators) {
340
+ return 'Boolean search selected - query contains logical operators (AND/OR/NOT)';
341
+ }
342
+ return 'Boolean search selected for precise filtering';
343
+ case 'fuzzy':
344
+ if (hasWildcard) {
345
+ return 'Fuzzy search selected - query contains wildcard patterns';
346
+ }
347
+ return 'Fuzzy search selected for typo-tolerant matching';
348
+ case 'semantic':
349
+ if (words >= 5) {
350
+ return 'Semantic search selected - longer natural language query benefits from meaning-based matching';
351
+ }
352
+ return 'Semantic search selected for conceptual/meaning-based matching';
353
+ }
354
+ }
355
+ }
@@ -0,0 +1,71 @@
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
@@ -0,0 +1 @@
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"}
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @module search/RankedSearch
7
7
  */
8
- import { calculateTFIDF, tokenize } from '../utils/tfidf.js';
8
+ import { calculateTF, calculateIDFFromTokenSets, tokenize } from '../utils/index.js';
9
9
  import { SEARCH_LIMITS } from '../utils/constants.js';
10
10
  import { TFIDFIndexManager } from './TFIDFIndexManager.js';
11
11
  import { SearchFilterChain } from './SearchFilterChain.js';
@@ -15,6 +15,13 @@ import { SearchFilterChain } from './SearchFilterChain.js';
15
15
  export class RankedSearch {
16
16
  storage;
17
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;
18
25
  constructor(storage, storageDir) {
19
26
  this.storage = storage;
20
27
  // Initialize index manager if storage directory is provided
@@ -22,6 +29,14 @@ export class RankedSearch {
22
29
  this.indexManager = new TFIDFIndexManager(storageDir);
23
30
  }
24
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
+ }
25
40
  /**
26
41
  * Initialize and build the TF-IDF index for fast searches.
27
42
  *
@@ -148,18 +163,51 @@ export class RankedSearch {
148
163
  }
149
164
  /**
150
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.
151
170
  */
152
171
  searchWithoutIndex(entities, queryTerms, limit) {
153
172
  const results = [];
154
- const documents = entities.map(e => [e.name, e.entityType, ...e.observations].join(' '));
155
- for (let i = 0; i < entities.length; i++) {
156
- const entity = entities[i];
157
- const document = documents[i];
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;
158
201
  // Calculate score for each query term
159
202
  let totalScore = 0;
160
203
  const matchedFields = {};
161
204
  for (const term of queryTerms) {
162
- const score = calculateTFIDF(term, document, documents);
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;
163
211
  totalScore += score;
164
212
  // Track which fields matched
165
213
  if (entity.name.toLowerCase().includes(term)) {
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Saved Search Manager
3
+ *
4
+ * Manages persistent saved searches with JSONL storage.
5
+ *
6
+ * @module search/SavedSearchManager
7
+ */
8
+ import type { SavedSearch, KnowledgeGraph } from '../types/index.js';
9
+ import type { BasicSearch } from './BasicSearch.js';
10
+ /**
11
+ * Manages saved search queries with usage tracking.
12
+ */
13
+ export declare class SavedSearchManager {
14
+ private savedSearchesFilePath;
15
+ private basicSearch;
16
+ constructor(savedSearchesFilePath: string, basicSearch: BasicSearch);
17
+ /**
18
+ * Load all saved searches from JSONL file.
19
+ *
20
+ * @returns Array of saved searches
21
+ */
22
+ private loadSavedSearches;
23
+ /**
24
+ * Save searches to JSONL file.
25
+ *
26
+ * @param searches - Array of saved searches
27
+ */
28
+ private saveSavedSearches;
29
+ /**
30
+ * Save a search query for later reuse.
31
+ *
32
+ * @param search - Search parameters (without createdAt, useCount, lastUsed)
33
+ * @returns The newly created saved search
34
+ * @throws Error if search name already exists
35
+ */
36
+ saveSearch(search: Omit<SavedSearch, 'createdAt' | 'useCount' | 'lastUsed'>): Promise<SavedSearch>;
37
+ /**
38
+ * List all saved searches.
39
+ *
40
+ * @returns Array of all saved searches
41
+ */
42
+ listSavedSearches(): Promise<SavedSearch[]>;
43
+ /**
44
+ * Get a specific saved search by name.
45
+ *
46
+ * @param name - Search name
47
+ * @returns Saved search or null if not found
48
+ */
49
+ getSavedSearch(name: string): Promise<SavedSearch | null>;
50
+ /**
51
+ * Execute a saved search by name.
52
+ *
53
+ * Updates usage statistics (lastUsed, useCount) before executing.
54
+ *
55
+ * @param name - Search name
56
+ * @returns Search results as knowledge graph
57
+ * @throws Error if search not found
58
+ */
59
+ executeSavedSearch(name: string): Promise<KnowledgeGraph>;
60
+ /**
61
+ * Delete a saved search.
62
+ *
63
+ * @param name - Search name
64
+ * @returns True if deleted, false if not found
65
+ */
66
+ deleteSavedSearch(name: string): Promise<boolean>;
67
+ /**
68
+ * Update a saved search.
69
+ *
70
+ * Cannot update name, createdAt, useCount, or lastUsed fields.
71
+ *
72
+ * @param name - Search name
73
+ * @param updates - Partial search with fields to update
74
+ * @returns Updated saved search
75
+ * @throws Error if search not found
76
+ */
77
+ updateSavedSearch(name: string, updates: Partial<Omit<SavedSearch, 'name' | 'createdAt' | 'useCount' | 'lastUsed'>>): Promise<SavedSearch>;
78
+ }
79
+ //# sourceMappingURL=SavedSearchManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SavedSearchManager.d.ts","sourceRoot":"","sources":["../../src/search/SavedSearchManager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;GAEG;AACH,qBAAa,kBAAkB;IAE3B,OAAO,CAAC,qBAAqB;IAC7B,OAAO,CAAC,WAAW;gBADX,qBAAqB,EAAE,MAAM,EAC7B,WAAW,EAAE,WAAW;IAGlC;;;;OAIG;YACW,iBAAiB;IAa/B;;;;OAIG;YACW,iBAAiB;IAK/B;;;;;;OAMG;IACG,UAAU,CACd,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC,GAC/D,OAAO,CAAC,WAAW,CAAC;IAoBvB;;;;OAIG;IACG,iBAAiB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAIjD;;;;;OAKG;IACG,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAK/D;;;;;;;;OAQG;IACG,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAsB/D;;;;;OAKG;IACG,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAavD;;;;;;;;;OASG;IACG,iBAAiB,CACrB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC,CAAC,GAClF,OAAO,CAAC,WAAW,CAAC;CAcxB"}