@danielsimonjr/memory-mcp 0.47.1 → 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.
- package/LICENSE +22 -0
- package/README.md +2000 -194
- package/dist/__tests__/file-path.test.js +5 -5
- package/dist/__tests__/knowledge-graph.test.js +3 -8
- package/dist/core/EntityManager.d.ts +266 -0
- package/dist/core/EntityManager.d.ts.map +1 -0
- package/dist/core/EntityManager.js +85 -133
- package/dist/core/GraphEventEmitter.d.ts +202 -0
- package/dist/core/GraphEventEmitter.d.ts.map +1 -0
- package/dist/core/GraphEventEmitter.js +346 -0
- package/dist/core/GraphStorage.d.ts +395 -0
- package/dist/core/GraphStorage.d.ts.map +1 -0
- package/dist/core/GraphStorage.js +643 -31
- package/dist/core/GraphTraversal.d.ts +141 -0
- package/dist/core/GraphTraversal.d.ts.map +1 -0
- package/dist/core/GraphTraversal.js +573 -0
- package/dist/core/HierarchyManager.d.ts +111 -0
- package/dist/core/HierarchyManager.d.ts.map +1 -0
- package/dist/{features → core}/HierarchyManager.js +14 -9
- package/dist/core/ManagerContext.d.ts +72 -0
- package/dist/core/ManagerContext.d.ts.map +1 -0
- package/dist/core/ManagerContext.js +118 -0
- package/dist/core/ObservationManager.d.ts +85 -0
- package/dist/core/ObservationManager.d.ts.map +1 -0
- package/dist/core/ObservationManager.js +51 -57
- package/dist/core/RelationManager.d.ts +131 -0
- package/dist/core/RelationManager.d.ts.map +1 -0
- package/dist/core/RelationManager.js +31 -7
- package/dist/core/SQLiteStorage.d.ts +354 -0
- package/dist/core/SQLiteStorage.d.ts.map +1 -0
- package/dist/core/SQLiteStorage.js +917 -0
- package/dist/core/StorageFactory.d.ts +45 -0
- package/dist/core/StorageFactory.d.ts.map +1 -0
- package/dist/core/StorageFactory.js +64 -0
- package/dist/core/TransactionManager.d.ts +464 -0
- package/dist/core/TransactionManager.d.ts.map +1 -0
- package/dist/core/TransactionManager.js +490 -13
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +12 -2
- package/dist/features/AnalyticsManager.d.ts +44 -0
- package/dist/features/AnalyticsManager.d.ts.map +1 -0
- package/dist/features/AnalyticsManager.js +14 -13
- package/dist/features/ArchiveManager.d.ts +133 -0
- package/dist/features/ArchiveManager.d.ts.map +1 -0
- package/dist/features/ArchiveManager.js +221 -14
- package/dist/features/CompressionManager.d.ts +117 -0
- package/dist/features/CompressionManager.d.ts.map +1 -0
- package/dist/features/CompressionManager.js +189 -20
- package/dist/features/IOManager.d.ts +225 -0
- package/dist/features/IOManager.d.ts.map +1 -0
- package/dist/features/IOManager.js +1041 -0
- package/dist/features/StreamingExporter.d.ts +123 -0
- package/dist/features/StreamingExporter.d.ts.map +1 -0
- package/dist/features/StreamingExporter.js +203 -0
- package/dist/features/TagManager.d.ts +147 -0
- package/dist/features/TagManager.d.ts.map +1 -0
- package/dist/features/index.d.ts +12 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +5 -6
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -45
- package/dist/memory.jsonl +1 -18
- package/dist/search/BasicSearch.d.ts +51 -0
- package/dist/search/BasicSearch.d.ts.map +1 -0
- package/dist/search/BasicSearch.js +9 -3
- package/dist/search/BooleanSearch.d.ts +98 -0
- package/dist/search/BooleanSearch.d.ts.map +1 -0
- package/dist/search/BooleanSearch.js +156 -9
- package/dist/search/EmbeddingService.d.ts +178 -0
- package/dist/search/EmbeddingService.d.ts.map +1 -0
- package/dist/search/EmbeddingService.js +358 -0
- package/dist/search/FuzzySearch.d.ts +118 -0
- package/dist/search/FuzzySearch.d.ts.map +1 -0
- package/dist/search/FuzzySearch.js +241 -25
- package/dist/search/QueryCostEstimator.d.ts +111 -0
- package/dist/search/QueryCostEstimator.d.ts.map +1 -0
- package/dist/search/QueryCostEstimator.js +355 -0
- package/dist/search/RankedSearch.d.ts +71 -0
- package/dist/search/RankedSearch.d.ts.map +1 -0
- package/dist/search/RankedSearch.js +54 -6
- package/dist/search/SavedSearchManager.d.ts +79 -0
- package/dist/search/SavedSearchManager.d.ts.map +1 -0
- package/dist/search/SearchFilterChain.d.ts +120 -0
- package/dist/search/SearchFilterChain.d.ts.map +1 -0
- package/dist/search/SearchFilterChain.js +2 -4
- package/dist/search/SearchManager.d.ts +326 -0
- package/dist/search/SearchManager.d.ts.map +1 -0
- package/dist/search/SearchManager.js +148 -0
- package/dist/search/SearchSuggestions.d.ts +27 -0
- package/dist/search/SearchSuggestions.d.ts.map +1 -0
- package/dist/search/SearchSuggestions.js +1 -1
- package/dist/search/SemanticSearch.d.ts +149 -0
- package/dist/search/SemanticSearch.d.ts.map +1 -0
- package/dist/search/SemanticSearch.js +323 -0
- package/dist/search/TFIDFEventSync.d.ts +85 -0
- package/dist/search/TFIDFEventSync.d.ts.map +1 -0
- package/dist/search/TFIDFEventSync.js +133 -0
- package/dist/search/TFIDFIndexManager.d.ts +151 -0
- package/dist/search/TFIDFIndexManager.d.ts.map +1 -0
- package/dist/search/TFIDFIndexManager.js +232 -17
- package/dist/search/VectorStore.d.ts +235 -0
- package/dist/search/VectorStore.d.ts.map +1 -0
- package/dist/search/VectorStore.js +311 -0
- package/dist/search/index.d.ts +21 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +12 -0
- package/dist/server/MCPServer.d.ts +21 -0
- package/dist/server/MCPServer.d.ts.map +1 -0
- package/dist/server/MCPServer.js +4 -4
- package/dist/server/responseCompressor.d.ts +94 -0
- package/dist/server/responseCompressor.d.ts.map +1 -0
- package/dist/server/responseCompressor.js +127 -0
- package/dist/server/toolDefinitions.d.ts +27 -0
- package/dist/server/toolDefinitions.d.ts.map +1 -0
- package/dist/server/toolDefinitions.js +189 -18
- package/dist/server/toolHandlers.d.ts +41 -0
- package/dist/server/toolHandlers.d.ts.map +1 -0
- package/dist/server/toolHandlers.js +467 -75
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -1
- package/dist/types/types.d.ts +1654 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/types.js +9 -0
- package/dist/utils/compressedCache.d.ts +192 -0
- package/dist/utils/compressedCache.d.ts.map +1 -0
- package/dist/utils/compressedCache.js +309 -0
- package/dist/utils/compressionUtil.d.ts +214 -0
- package/dist/utils/compressionUtil.d.ts.map +1 -0
- package/dist/utils/compressionUtil.js +247 -0
- package/dist/utils/constants.d.ts +245 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +124 -0
- package/dist/utils/entityUtils.d.ts +321 -0
- package/dist/utils/entityUtils.d.ts.map +1 -0
- package/dist/utils/entityUtils.js +434 -4
- package/dist/utils/errors.d.ts +95 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +24 -0
- package/dist/utils/formatters.d.ts +145 -0
- package/dist/utils/formatters.d.ts.map +1 -0
- package/dist/utils/{paginationUtils.js → formatters.js} +54 -3
- package/dist/utils/index.d.ts +23 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +69 -31
- package/dist/utils/indexes.d.ts +270 -0
- package/dist/utils/indexes.d.ts.map +1 -0
- package/dist/utils/indexes.js +526 -0
- package/dist/utils/logger.d.ts +24 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/operationUtils.d.ts +124 -0
- package/dist/utils/operationUtils.d.ts.map +1 -0
- package/dist/utils/operationUtils.js +175 -0
- package/dist/utils/parallelUtils.d.ts +72 -0
- package/dist/utils/parallelUtils.d.ts.map +1 -0
- package/dist/utils/parallelUtils.js +169 -0
- package/dist/utils/schemas.d.ts +374 -0
- package/dist/utils/schemas.d.ts.map +1 -0
- package/dist/utils/schemas.js +302 -2
- package/dist/utils/searchAlgorithms.d.ts +99 -0
- package/dist/utils/searchAlgorithms.d.ts.map +1 -0
- package/dist/utils/searchAlgorithms.js +167 -0
- package/dist/utils/searchCache.d.ts +108 -0
- package/dist/utils/searchCache.d.ts.map +1 -0
- package/dist/utils/taskScheduler.d.ts +290 -0
- package/dist/utils/taskScheduler.d.ts.map +1 -0
- package/dist/utils/taskScheduler.js +466 -0
- package/dist/workers/index.d.ts +12 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +9 -0
- package/dist/workers/levenshteinWorker.d.ts +60 -0
- package/dist/workers/levenshteinWorker.d.ts.map +1 -0
- package/dist/workers/levenshteinWorker.js +98 -0
- package/package.json +17 -4
- package/dist/__tests__/edge-cases/edge-cases.test.js +0 -406
- package/dist/__tests__/integration/workflows.test.js +0 -449
- package/dist/__tests__/performance/benchmarks.test.js +0 -413
- package/dist/__tests__/unit/core/EntityManager.test.js +0 -334
- package/dist/__tests__/unit/core/GraphStorage.test.js +0 -205
- package/dist/__tests__/unit/core/RelationManager.test.js +0 -274
- package/dist/__tests__/unit/features/CompressionManager.test.js +0 -350
- package/dist/__tests__/unit/search/BasicSearch.test.js +0 -311
- package/dist/__tests__/unit/search/BooleanSearch.test.js +0 -432
- package/dist/__tests__/unit/search/FuzzySearch.test.js +0 -448
- package/dist/__tests__/unit/search/RankedSearch.test.js +0 -379
- package/dist/__tests__/unit/utils/levenshtein.test.js +0 -77
- package/dist/core/KnowledgeGraphManager.js +0 -423
- package/dist/features/BackupManager.js +0 -311
- package/dist/features/ExportManager.js +0 -305
- package/dist/features/ImportExportManager.js +0 -50
- package/dist/features/ImportManager.js +0 -328
- package/dist/types/analytics.types.js +0 -6
- package/dist/types/entity.types.js +0 -7
- package/dist/types/import-export.types.js +0 -7
- package/dist/types/search.types.js +0 -7
- package/dist/types/tag.types.js +0 -6
- package/dist/utils/dateUtils.js +0 -89
- package/dist/utils/filterUtils.js +0 -155
- package/dist/utils/levenshtein.js +0 -62
- package/dist/utils/pathUtils.js +0 -115
- package/dist/utils/responseFormatter.js +0 -55
- package/dist/utils/tagUtils.js +0 -107
- package/dist/utils/tfidf.js +0 -90
- package/dist/utils/validationHelper.js +0 -99
- 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 {
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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"}
|