@danielsimonjr/memory-mcp 0.7.2 → 0.41.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 (61) hide show
  1. package/dist/__tests__/edge-cases/edge-cases.test.js +406 -0
  2. package/dist/__tests__/file-path.test.js +5 -5
  3. package/dist/__tests__/integration/workflows.test.js +449 -0
  4. package/dist/__tests__/knowledge-graph.test.js +8 -3
  5. package/dist/__tests__/performance/benchmarks.test.js +411 -0
  6. package/dist/__tests__/unit/core/EntityManager.test.js +334 -0
  7. package/dist/__tests__/unit/core/GraphStorage.test.js +205 -0
  8. package/dist/__tests__/unit/core/RelationManager.test.js +274 -0
  9. package/dist/__tests__/unit/features/CompressionManager.test.js +350 -0
  10. package/dist/__tests__/unit/search/BasicSearch.test.js +311 -0
  11. package/dist/__tests__/unit/search/BooleanSearch.test.js +432 -0
  12. package/dist/__tests__/unit/search/FuzzySearch.test.js +448 -0
  13. package/dist/__tests__/unit/search/RankedSearch.test.js +379 -0
  14. package/dist/__tests__/unit/utils/levenshtein.test.js +77 -0
  15. package/dist/core/EntityManager.js +554 -0
  16. package/dist/core/GraphStorage.js +172 -0
  17. package/dist/core/KnowledgeGraphManager.js +400 -0
  18. package/dist/core/ObservationManager.js +129 -0
  19. package/dist/core/RelationManager.js +186 -0
  20. package/dist/core/TransactionManager.js +389 -0
  21. package/dist/core/index.js +9 -0
  22. package/dist/features/AnalyticsManager.js +222 -0
  23. package/dist/features/ArchiveManager.js +74 -0
  24. package/dist/features/BackupManager.js +311 -0
  25. package/dist/features/CompressionManager.js +310 -0
  26. package/dist/features/ExportManager.js +305 -0
  27. package/dist/features/HierarchyManager.js +219 -0
  28. package/dist/features/ImportExportManager.js +50 -0
  29. package/dist/features/ImportManager.js +328 -0
  30. package/dist/features/TagManager.js +210 -0
  31. package/dist/features/index.js +12 -0
  32. package/dist/index.js +13 -996
  33. package/dist/memory.jsonl +225 -0
  34. package/dist/search/BasicSearch.js +161 -0
  35. package/dist/search/BooleanSearch.js +304 -0
  36. package/dist/search/FuzzySearch.js +115 -0
  37. package/dist/search/RankedSearch.js +206 -0
  38. package/dist/search/SavedSearchManager.js +145 -0
  39. package/dist/search/SearchManager.js +305 -0
  40. package/dist/search/SearchSuggestions.js +57 -0
  41. package/dist/search/TFIDFIndexManager.js +217 -0
  42. package/dist/search/index.js +10 -0
  43. package/dist/server/MCPServer.js +889 -0
  44. package/dist/types/analytics.types.js +6 -0
  45. package/dist/types/entity.types.js +7 -0
  46. package/dist/types/import-export.types.js +7 -0
  47. package/dist/types/index.js +12 -0
  48. package/dist/types/search.types.js +7 -0
  49. package/dist/types/tag.types.js +6 -0
  50. package/dist/utils/constants.js +127 -0
  51. package/dist/utils/dateUtils.js +89 -0
  52. package/dist/utils/errors.js +121 -0
  53. package/dist/utils/index.js +13 -0
  54. package/dist/utils/levenshtein.js +62 -0
  55. package/dist/utils/logger.js +33 -0
  56. package/dist/utils/pathUtils.js +115 -0
  57. package/dist/utils/schemas.js +184 -0
  58. package/dist/utils/searchCache.js +209 -0
  59. package/dist/utils/tfidf.js +90 -0
  60. package/dist/utils/validationUtils.js +109 -0
  61. package/package.json +50 -48
@@ -0,0 +1,206 @@
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 { calculateTFIDF, tokenize } from '../utils/tfidf.js';
9
+ import { SEARCH_LIMITS } from '../utils/constants.js';
10
+ import { TFIDFIndexManager } from './TFIDFIndexManager.js';
11
+ /**
12
+ * Performs TF-IDF ranked search with optional pre-calculated indexes.
13
+ */
14
+ export class RankedSearch {
15
+ storage;
16
+ indexManager = null;
17
+ constructor(storage, storageDir) {
18
+ this.storage = storage;
19
+ // Initialize index manager if storage directory is provided
20
+ if (storageDir) {
21
+ this.indexManager = new TFIDFIndexManager(storageDir);
22
+ }
23
+ }
24
+ /**
25
+ * Initialize and build the TF-IDF index for fast searches.
26
+ *
27
+ * Should be called after graph changes to keep index up-to-date.
28
+ */
29
+ async buildIndex() {
30
+ if (!this.indexManager) {
31
+ throw new Error('Index manager not initialized. Provide storageDir to constructor.');
32
+ }
33
+ const graph = await this.storage.loadGraph();
34
+ await this.indexManager.buildIndex(graph);
35
+ await this.indexManager.saveIndex();
36
+ }
37
+ /**
38
+ * Update the index incrementally after entity changes.
39
+ *
40
+ * @param changedEntityNames - Names of entities that were created, updated, or deleted
41
+ */
42
+ async updateIndex(changedEntityNames) {
43
+ if (!this.indexManager) {
44
+ return; // No index manager, skip
45
+ }
46
+ const graph = await this.storage.loadGraph();
47
+ await this.indexManager.updateIndex(graph, changedEntityNames);
48
+ await this.indexManager.saveIndex();
49
+ }
50
+ /**
51
+ * Load the TF-IDF index from disk if available.
52
+ */
53
+ async ensureIndexLoaded() {
54
+ if (!this.indexManager) {
55
+ return null;
56
+ }
57
+ // Return cached index if already loaded
58
+ const cached = this.indexManager.getIndex();
59
+ if (cached) {
60
+ return cached;
61
+ }
62
+ // Try to load from disk
63
+ return await this.indexManager.loadIndex();
64
+ }
65
+ /**
66
+ * Search with TF-IDF relevance ranking.
67
+ *
68
+ * Uses pre-calculated index if available, falls back to on-the-fly calculation.
69
+ *
70
+ * @param query - Search query
71
+ * @param tags - Optional tags filter
72
+ * @param minImportance - Optional minimum importance
73
+ * @param maxImportance - Optional maximum importance
74
+ * @param limit - Maximum results to return (default 50, max 200)
75
+ * @returns Array of search results sorted by relevance
76
+ */
77
+ async searchNodesRanked(query, tags, minImportance, maxImportance, limit = SEARCH_LIMITS.DEFAULT) {
78
+ // Enforce maximum search limit
79
+ const effectiveLimit = Math.min(limit, SEARCH_LIMITS.MAX);
80
+ const graph = await this.storage.loadGraph();
81
+ const normalizedTags = tags?.map(tag => tag.toLowerCase());
82
+ // Filter entities by tags and importance
83
+ const filteredEntities = graph.entities.filter(e => {
84
+ // Tag filter
85
+ if (normalizedTags && normalizedTags.length > 0) {
86
+ if (!e.tags || e.tags.length === 0)
87
+ return false;
88
+ const entityTags = e.tags.map(tag => tag.toLowerCase());
89
+ if (!normalizedTags.some(tag => entityTags.includes(tag)))
90
+ return false;
91
+ }
92
+ // Importance filter
93
+ if (minImportance !== undefined && (e.importance === undefined || e.importance < minImportance)) {
94
+ return false;
95
+ }
96
+ if (maxImportance !== undefined && (e.importance === undefined || e.importance > maxImportance)) {
97
+ return false;
98
+ }
99
+ return true;
100
+ });
101
+ // Try to use pre-calculated index
102
+ const index = await this.ensureIndexLoaded();
103
+ const queryTerms = tokenize(query);
104
+ if (index) {
105
+ // Use pre-calculated index for fast search
106
+ return this.searchWithIndex(filteredEntities, queryTerms, index, effectiveLimit);
107
+ }
108
+ else {
109
+ // Fall back to on-the-fly calculation
110
+ return this.searchWithoutIndex(filteredEntities, queryTerms, effectiveLimit);
111
+ }
112
+ }
113
+ /**
114
+ * Search using pre-calculated TF-IDF index (fast path).
115
+ */
116
+ searchWithIndex(entities, queryTerms, index, limit) {
117
+ const results = [];
118
+ for (const entity of entities) {
119
+ const docVector = index.documents.get(entity.name);
120
+ if (!docVector) {
121
+ continue; // Entity not in index
122
+ }
123
+ // Calculate total terms in document (sum of all term frequencies)
124
+ const totalTerms = Object.values(docVector.terms).reduce((sum, count) => sum + count, 0);
125
+ if (totalTerms === 0)
126
+ continue;
127
+ // Calculate score using pre-calculated term frequencies and IDF
128
+ let totalScore = 0;
129
+ const matchedFields = {};
130
+ for (const term of queryTerms) {
131
+ const termCount = docVector.terms[term] || 0;
132
+ const idf = index.idf.get(term) || 0;
133
+ // Calculate TF-IDF: (termCount / totalTerms) * IDF
134
+ const tf = termCount / totalTerms;
135
+ const tfidf = tf * idf;
136
+ totalScore += tfidf;
137
+ // Track which fields matched
138
+ if (termCount > 0) {
139
+ if (entity.name.toLowerCase().includes(term)) {
140
+ matchedFields.name = true;
141
+ }
142
+ if (entity.entityType.toLowerCase().includes(term)) {
143
+ matchedFields.entityType = true;
144
+ }
145
+ const matchedObs = entity.observations.filter(o => o.toLowerCase().includes(term));
146
+ if (matchedObs.length > 0) {
147
+ matchedFields.observations = matchedObs;
148
+ }
149
+ }
150
+ }
151
+ // Only include entities with non-zero scores
152
+ if (totalScore > 0) {
153
+ results.push({
154
+ entity,
155
+ score: totalScore,
156
+ matchedFields,
157
+ });
158
+ }
159
+ }
160
+ // Sort by score descending and apply limit
161
+ return results
162
+ .sort((a, b) => b.score - a.score)
163
+ .slice(0, limit);
164
+ }
165
+ /**
166
+ * Search without index (on-the-fly calculation, slow path).
167
+ */
168
+ searchWithoutIndex(entities, queryTerms, limit) {
169
+ const results = [];
170
+ const documents = entities.map(e => [e.name, e.entityType, ...e.observations].join(' '));
171
+ for (let i = 0; i < entities.length; i++) {
172
+ const entity = entities[i];
173
+ const document = documents[i];
174
+ // Calculate score for each query term
175
+ let totalScore = 0;
176
+ const matchedFields = {};
177
+ for (const term of queryTerms) {
178
+ const score = calculateTFIDF(term, document, documents);
179
+ totalScore += score;
180
+ // Track which fields matched
181
+ if (entity.name.toLowerCase().includes(term)) {
182
+ matchedFields.name = true;
183
+ }
184
+ if (entity.entityType.toLowerCase().includes(term)) {
185
+ matchedFields.entityType = true;
186
+ }
187
+ const matchedObs = entity.observations.filter(o => o.toLowerCase().includes(term));
188
+ if (matchedObs.length > 0) {
189
+ matchedFields.observations = matchedObs;
190
+ }
191
+ }
192
+ // Only include entities with non-zero scores
193
+ if (totalScore > 0) {
194
+ results.push({
195
+ entity,
196
+ score: totalScore,
197
+ matchedFields,
198
+ });
199
+ }
200
+ }
201
+ // Sort by score descending and apply limit
202
+ return results
203
+ .sort((a, b) => b.score - a.score)
204
+ .slice(0, limit);
205
+ }
206
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Saved Search Manager
3
+ *
4
+ * Manages persistent saved searches with JSONL storage.
5
+ *
6
+ * @module search/SavedSearchManager
7
+ */
8
+ import * as fs from 'fs/promises';
9
+ /**
10
+ * Manages saved search queries with usage tracking.
11
+ */
12
+ export class SavedSearchManager {
13
+ savedSearchesFilePath;
14
+ basicSearch;
15
+ constructor(savedSearchesFilePath, basicSearch) {
16
+ this.savedSearchesFilePath = savedSearchesFilePath;
17
+ this.basicSearch = basicSearch;
18
+ }
19
+ /**
20
+ * Load all saved searches from JSONL file.
21
+ *
22
+ * @returns Array of saved searches
23
+ */
24
+ async loadSavedSearches() {
25
+ try {
26
+ const data = await fs.readFile(this.savedSearchesFilePath, 'utf-8');
27
+ const lines = data.split('\n').filter((line) => line.trim() !== '');
28
+ return lines.map((line) => JSON.parse(line));
29
+ }
30
+ catch (error) {
31
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
32
+ return [];
33
+ }
34
+ throw error;
35
+ }
36
+ }
37
+ /**
38
+ * Save searches to JSONL file.
39
+ *
40
+ * @param searches - Array of saved searches
41
+ */
42
+ async saveSavedSearches(searches) {
43
+ const lines = searches.map(s => JSON.stringify(s));
44
+ await fs.writeFile(this.savedSearchesFilePath, lines.join('\n'));
45
+ }
46
+ /**
47
+ * Save a search query for later reuse.
48
+ *
49
+ * @param search - Search parameters (without createdAt, useCount, lastUsed)
50
+ * @returns The newly created saved search
51
+ * @throws Error if search name already exists
52
+ */
53
+ async saveSearch(search) {
54
+ const searches = await this.loadSavedSearches();
55
+ // Check if name already exists
56
+ if (searches.some(s => s.name === search.name)) {
57
+ throw new Error(`Saved search with name "${search.name}" already exists`);
58
+ }
59
+ const newSearch = {
60
+ ...search,
61
+ createdAt: new Date().toISOString(),
62
+ useCount: 0,
63
+ };
64
+ searches.push(newSearch);
65
+ await this.saveSavedSearches(searches);
66
+ return newSearch;
67
+ }
68
+ /**
69
+ * List all saved searches.
70
+ *
71
+ * @returns Array of all saved searches
72
+ */
73
+ async listSavedSearches() {
74
+ return await this.loadSavedSearches();
75
+ }
76
+ /**
77
+ * Get a specific saved search by name.
78
+ *
79
+ * @param name - Search name
80
+ * @returns Saved search or null if not found
81
+ */
82
+ async getSavedSearch(name) {
83
+ const searches = await this.loadSavedSearches();
84
+ return searches.find(s => s.name === name) || null;
85
+ }
86
+ /**
87
+ * Execute a saved search by name.
88
+ *
89
+ * Updates usage statistics (lastUsed, useCount) before executing.
90
+ *
91
+ * @param name - Search name
92
+ * @returns Search results as knowledge graph
93
+ * @throws Error if search not found
94
+ */
95
+ async executeSavedSearch(name) {
96
+ const searches = await this.loadSavedSearches();
97
+ const search = searches.find(s => s.name === name);
98
+ if (!search) {
99
+ throw new Error(`Saved search "${name}" not found`);
100
+ }
101
+ // Update usage statistics
102
+ search.lastUsed = new Date().toISOString();
103
+ search.useCount++;
104
+ await this.saveSavedSearches(searches);
105
+ // Execute the search using BasicSearch
106
+ return await this.basicSearch.searchNodes(search.query, search.tags, search.minImportance, search.maxImportance);
107
+ }
108
+ /**
109
+ * Delete a saved search.
110
+ *
111
+ * @param name - Search name
112
+ * @returns True if deleted, false if not found
113
+ */
114
+ async deleteSavedSearch(name) {
115
+ const searches = await this.loadSavedSearches();
116
+ const initialLength = searches.length;
117
+ const filtered = searches.filter(s => s.name !== name);
118
+ if (filtered.length === initialLength) {
119
+ return false; // Search not found
120
+ }
121
+ await this.saveSavedSearches(filtered);
122
+ return true;
123
+ }
124
+ /**
125
+ * Update a saved search.
126
+ *
127
+ * Cannot update name, createdAt, useCount, or lastUsed fields.
128
+ *
129
+ * @param name - Search name
130
+ * @param updates - Partial search with fields to update
131
+ * @returns Updated saved search
132
+ * @throws Error if search not found
133
+ */
134
+ async updateSavedSearch(name, updates) {
135
+ const searches = await this.loadSavedSearches();
136
+ const search = searches.find(s => s.name === name);
137
+ if (!search) {
138
+ throw new Error(`Saved search "${name}" not found`);
139
+ }
140
+ // Apply updates
141
+ Object.assign(search, updates);
142
+ await this.saveSavedSearches(searches);
143
+ return search;
144
+ }
145
+ }
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Search Manager
3
+ *
4
+ * Orchestrates all search types (basic, ranked, boolean, fuzzy).
5
+ *
6
+ * @module search/SearchManager
7
+ */
8
+ import { BasicSearch } from './BasicSearch.js';
9
+ import { RankedSearch } from './RankedSearch.js';
10
+ import { BooleanSearch } from './BooleanSearch.js';
11
+ import { FuzzySearch } from './FuzzySearch.js';
12
+ import { SearchSuggestions } from './SearchSuggestions.js';
13
+ import { SavedSearchManager } from './SavedSearchManager.js';
14
+ /**
15
+ * Unified search manager providing access to all search types.
16
+ */
17
+ export class SearchManager {
18
+ basicSearch;
19
+ rankedSearch;
20
+ booleanSearcher;
21
+ fuzzySearcher;
22
+ searchSuggestions;
23
+ savedSearchManager;
24
+ constructor(storage, savedSearchesFilePath) {
25
+ this.basicSearch = new BasicSearch(storage);
26
+ this.rankedSearch = new RankedSearch(storage);
27
+ this.booleanSearcher = new BooleanSearch(storage);
28
+ this.fuzzySearcher = new FuzzySearch(storage);
29
+ this.searchSuggestions = new SearchSuggestions(storage);
30
+ this.savedSearchManager = new SavedSearchManager(savedSearchesFilePath, this.basicSearch);
31
+ }
32
+ // ==================== Basic Search ====================
33
+ /**
34
+ * Perform a simple text-based search across entity names and observations.
35
+ *
36
+ * This is the primary search method that searches through entity names,
37
+ * observations, and types using case-insensitive substring matching.
38
+ * Optionally filter by tags and importance range.
39
+ *
40
+ * @param query - Text to search for (case-insensitive, searches names/observations/types)
41
+ * @param tags - Optional array of tags to filter results (lowercase)
42
+ * @param minImportance - Optional minimum importance value (0-10)
43
+ * @param maxImportance - Optional maximum importance value (0-10)
44
+ * @returns KnowledgeGraph containing matching entities and their relations
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const manager = new SearchManager(storage, savedSearchesPath);
49
+ *
50
+ * // Simple text search
51
+ * const results = await manager.searchNodes('Alice');
52
+ *
53
+ * // Search with tag filter
54
+ * const engineeringResults = await manager.searchNodes('project', ['engineering']);
55
+ *
56
+ * // Search with importance range
57
+ * const importantResults = await manager.searchNodes('critical', undefined, 8, 10);
58
+ *
59
+ * // Combined filters
60
+ * const filtered = await manager.searchNodes('bug', ['backend'], 5, 10);
61
+ * ```
62
+ */
63
+ async searchNodes(query, tags, minImportance, maxImportance) {
64
+ return this.basicSearch.searchNodes(query, tags, minImportance, maxImportance);
65
+ }
66
+ /**
67
+ * Open specific nodes by name.
68
+ *
69
+ * @param names - Array of entity names
70
+ * @returns Knowledge graph with specified entities
71
+ */
72
+ async openNodes(names) {
73
+ return this.basicSearch.openNodes(names);
74
+ }
75
+ /**
76
+ * Search by date range.
77
+ *
78
+ * @param startDate - Optional start date (ISO 8601)
79
+ * @param endDate - Optional end date (ISO 8601)
80
+ * @param entityType - Optional entity type filter
81
+ * @param tags - Optional tags filter
82
+ * @returns Filtered knowledge graph
83
+ */
84
+ async searchByDateRange(startDate, endDate, entityType, tags) {
85
+ return this.basicSearch.searchByDateRange(startDate, endDate, entityType, tags);
86
+ }
87
+ // ==================== Ranked Search ====================
88
+ /**
89
+ * Perform TF-IDF ranked search with relevance scoring.
90
+ *
91
+ * Uses Term Frequency-Inverse Document Frequency algorithm to rank results
92
+ * by relevance to the query. Results are sorted by score (highest first).
93
+ * This is ideal for finding the most relevant entities for a search query.
94
+ *
95
+ * @param query - Search query (analyzed for term frequency)
96
+ * @param tags - Optional array of tags to filter results (lowercase)
97
+ * @param minImportance - Optional minimum importance value (0-10)
98
+ * @param maxImportance - Optional maximum importance value (0-10)
99
+ * @param limit - Maximum number of results to return (default: 50, max: 200)
100
+ * @returns Array of SearchResult objects sorted by relevance score (descending)
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * const manager = new SearchManager(storage, savedSearchesPath);
105
+ *
106
+ * // Basic ranked search
107
+ * const results = await manager.searchNodesRanked('machine learning algorithms');
108
+ * results.forEach(r => {
109
+ * console.log(`${r.entity.name} (score: ${r.score})`);
110
+ * });
111
+ *
112
+ * // Limit to top 10 most relevant results
113
+ * const top10 = await manager.searchNodesRanked('database optimization', undefined, undefined, undefined, 10);
114
+ *
115
+ * // Ranked search with filters
116
+ * const relevantImportant = await manager.searchNodesRanked(
117
+ * 'security vulnerability',
118
+ * ['security', 'critical'],
119
+ * 8,
120
+ * 10,
121
+ * 20
122
+ * );
123
+ * ```
124
+ */
125
+ async searchNodesRanked(query, tags, minImportance, maxImportance, limit) {
126
+ return this.rankedSearch.searchNodesRanked(query, tags, minImportance, maxImportance, limit);
127
+ }
128
+ // ==================== Boolean Search ====================
129
+ /**
130
+ * Perform boolean search with AND, OR, NOT operators.
131
+ *
132
+ * Supports complex boolean logic for precise search queries.
133
+ * Use AND/OR/NOT operators (case-insensitive) to combine search terms.
134
+ * Parentheses are supported for grouping.
135
+ *
136
+ * @param query - Boolean query string (e.g., "alice AND bob", "frontend OR backend NOT legacy")
137
+ * @param tags - Optional array of tags to filter results (lowercase)
138
+ * @param minImportance - Optional minimum importance value (0-10)
139
+ * @param maxImportance - Optional maximum importance value (0-10)
140
+ * @returns KnowledgeGraph containing entities matching the boolean expression
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * const manager = new SearchManager(storage, savedSearchesPath);
145
+ *
146
+ * // AND operator - entities matching all terms
147
+ * const both = await manager.booleanSearch('database AND performance');
148
+ *
149
+ * // OR operator - entities matching any term
150
+ * const either = await manager.booleanSearch('frontend OR backend');
151
+ *
152
+ * // NOT operator - exclude terms
153
+ * const excluding = await manager.booleanSearch('API NOT deprecated');
154
+ *
155
+ * // Complex queries with grouping
156
+ * const complex = await manager.booleanSearch('(react OR vue) AND (component OR hook) NOT legacy');
157
+ * ```
158
+ */
159
+ async booleanSearch(query, tags, minImportance, maxImportance) {
160
+ return this.booleanSearcher.booleanSearch(query, tags, minImportance, maxImportance);
161
+ }
162
+ // ==================== Fuzzy Search ====================
163
+ /**
164
+ * Perform fuzzy search with typo tolerance.
165
+ *
166
+ * Uses Levenshtein distance to find entities that approximately match the query,
167
+ * making it ideal for handling typos and variations in spelling.
168
+ * Higher threshold values require closer matches.
169
+ *
170
+ * @param query - Search query (will match approximate spellings)
171
+ * @param threshold - Similarity threshold from 0.0 (very lenient) to 1.0 (exact match). Default: 0.7
172
+ * @param tags - Optional array of tags to filter results (lowercase)
173
+ * @param minImportance - Optional minimum importance value (0-10)
174
+ * @param maxImportance - Optional maximum importance value (0-10)
175
+ * @returns KnowledgeGraph containing entities with similar names/observations
176
+ *
177
+ * @example
178
+ * ```typescript
179
+ * const manager = new SearchManager(storage, savedSearchesPath);
180
+ *
181
+ * // Find entities even with typos
182
+ * const results = await manager.fuzzySearch('databse'); // Will match "database"
183
+ *
184
+ * // Adjust threshold for strictness
185
+ * const strict = await manager.fuzzySearch('optmization', 0.9); // Requires very close match
186
+ * const lenient = await manager.fuzzySearch('optmization', 0.6); // More tolerant of differences
187
+ *
188
+ * // Fuzzy search with filters
189
+ * const filtered = await manager.fuzzySearch('secrity', 0.7, ['important'], 7, 10);
190
+ * ```
191
+ */
192
+ async fuzzySearch(query, threshold, tags, minImportance, maxImportance) {
193
+ return this.fuzzySearcher.fuzzySearch(query, threshold, tags, minImportance, maxImportance);
194
+ }
195
+ // ==================== Search Suggestions ====================
196
+ /**
197
+ * Get search suggestions for a query.
198
+ *
199
+ * @param query - Search query
200
+ * @param maxSuggestions - Maximum suggestions to return
201
+ * @returns Array of suggested terms
202
+ */
203
+ async getSearchSuggestions(query, maxSuggestions) {
204
+ return this.searchSuggestions.getSearchSuggestions(query, maxSuggestions);
205
+ }
206
+ // ==================== Saved Searches ====================
207
+ /**
208
+ * Save a search query for later reuse.
209
+ *
210
+ * Saved searches store query parameters and can be re-executed later.
211
+ * The system tracks usage count and last used timestamp automatically.
212
+ *
213
+ * @param search - Search parameters (name, query, and optional filters)
214
+ * @returns Newly created SavedSearch object with metadata
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * const manager = new SearchManager(storage, savedSearchesPath);
219
+ *
220
+ * // Save a simple search
221
+ * const saved = await manager.saveSearch({
222
+ * name: 'High Priority Bugs',
223
+ * query: 'bug',
224
+ * tags: ['critical'],
225
+ * minImportance: 8
226
+ * });
227
+ *
228
+ * // Save a complex search
229
+ * await manager.saveSearch({
230
+ * name: 'Recent Frontend Work',
231
+ * query: 'component OR hook',
232
+ * tags: ['frontend', 'react'],
233
+ * searchType: 'boolean'
234
+ * });
235
+ * ```
236
+ */
237
+ async saveSearch(search) {
238
+ return this.savedSearchManager.saveSearch(search);
239
+ }
240
+ /**
241
+ * List all saved searches.
242
+ *
243
+ * @returns Array of saved searches
244
+ */
245
+ async listSavedSearches() {
246
+ return this.savedSearchManager.listSavedSearches();
247
+ }
248
+ /**
249
+ * Get a saved search by name.
250
+ *
251
+ * @param name - Search name
252
+ * @returns Saved search or null
253
+ */
254
+ async getSavedSearch(name) {
255
+ return this.savedSearchManager.getSavedSearch(name);
256
+ }
257
+ /**
258
+ * Execute a saved search by name.
259
+ *
260
+ * Runs a previously saved search with its stored parameters.
261
+ * Automatically updates the search's useCount and lastUsed timestamp.
262
+ *
263
+ * @param name - The unique name of the saved search to execute
264
+ * @returns KnowledgeGraph containing the search results
265
+ * @throws Error if saved search not found
266
+ *
267
+ * @example
268
+ * ```typescript
269
+ * const manager = new SearchManager(storage, savedSearchesPath);
270
+ *
271
+ * // Execute a saved search
272
+ * const results = await manager.executeSavedSearch('High Priority Bugs');
273
+ * console.log(`Found ${results.entities.length} high priority bugs`);
274
+ *
275
+ * // Handle missing saved search
276
+ * try {
277
+ * await manager.executeSavedSearch('NonExistent');
278
+ * } catch (error) {
279
+ * console.error('Search not found');
280
+ * }
281
+ * ```
282
+ */
283
+ async executeSavedSearch(name) {
284
+ return this.savedSearchManager.executeSavedSearch(name);
285
+ }
286
+ /**
287
+ * Delete a saved search.
288
+ *
289
+ * @param name - Search name
290
+ * @returns True if deleted
291
+ */
292
+ async deleteSavedSearch(name) {
293
+ return this.savedSearchManager.deleteSavedSearch(name);
294
+ }
295
+ /**
296
+ * Update a saved search.
297
+ *
298
+ * @param name - Search name
299
+ * @param updates - Fields to update
300
+ * @returns Updated saved search
301
+ */
302
+ async updateSavedSearch(name, updates) {
303
+ return this.savedSearchManager.updateSavedSearch(name, updates);
304
+ }
305
+ }