@danielsimonjr/memory-mcp 9.8.3 → 9.9.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 (41) hide show
  1. package/README.md +360 -1829
  2. package/dist/core/ManagerContext.d.ts +4 -0
  3. package/dist/core/ManagerContext.d.ts.map +1 -1
  4. package/dist/core/ManagerContext.js +6 -0
  5. package/dist/features/KeywordExtractor.d.ts +61 -0
  6. package/dist/features/KeywordExtractor.d.ts.map +1 -0
  7. package/dist/features/KeywordExtractor.js +126 -0
  8. package/dist/features/ObservationNormalizer.d.ts +90 -0
  9. package/dist/features/ObservationNormalizer.d.ts.map +1 -0
  10. package/dist/features/ObservationNormalizer.js +193 -0
  11. package/dist/features/index.d.ts +2 -0
  12. package/dist/features/index.d.ts.map +1 -1
  13. package/dist/features/index.js +3 -0
  14. package/dist/search/HybridSearchManager.d.ts +80 -0
  15. package/dist/search/HybridSearchManager.d.ts.map +1 -0
  16. package/dist/search/HybridSearchManager.js +187 -0
  17. package/dist/search/QueryAnalyzer.d.ts +76 -0
  18. package/dist/search/QueryAnalyzer.d.ts.map +1 -0
  19. package/dist/search/QueryAnalyzer.js +227 -0
  20. package/dist/search/QueryPlanner.d.ts +58 -0
  21. package/dist/search/QueryPlanner.d.ts.map +1 -0
  22. package/dist/search/QueryPlanner.js +137 -0
  23. package/dist/search/ReflectionManager.d.ts +71 -0
  24. package/dist/search/ReflectionManager.d.ts.map +1 -0
  25. package/dist/search/ReflectionManager.js +124 -0
  26. package/dist/search/SymbolicSearch.d.ts +61 -0
  27. package/dist/search/SymbolicSearch.d.ts.map +1 -0
  28. package/dist/search/SymbolicSearch.js +163 -0
  29. package/dist/search/index.d.ts +5 -0
  30. package/dist/search/index.d.ts.map +1 -1
  31. package/dist/search/index.js +8 -0
  32. package/dist/server/toolDefinitions.d.ts +1 -1
  33. package/dist/server/toolDefinitions.d.ts.map +1 -1
  34. package/dist/server/toolDefinitions.js +141 -1
  35. package/dist/server/toolHandlers.d.ts.map +1 -1
  36. package/dist/server/toolHandlers.js +167 -0
  37. package/dist/types/index.d.ts +1 -1
  38. package/dist/types/index.d.ts.map +1 -1
  39. package/dist/types/types.d.ts +118 -0
  40. package/dist/types/types.d.ts.map +1 -1
  41. package/package.json +2 -2
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Hybrid Search Manager
3
+ *
4
+ * Phase 11: Orchestrates three-layer hybrid search combining
5
+ * semantic, lexical, and symbolic signals.
6
+ *
7
+ * @module search/HybridSearchManager
8
+ */
9
+ import { SymbolicSearch } from './SymbolicSearch.js';
10
+ import { SEMANTIC_SEARCH_LIMITS } from '../utils/constants.js';
11
+ /**
12
+ * Default weights for hybrid search layers.
13
+ */
14
+ export const DEFAULT_HYBRID_WEIGHTS = {
15
+ semantic: 0.5,
16
+ lexical: 0.3,
17
+ symbolic: 0.2,
18
+ };
19
+ /**
20
+ * Hybrid Search Manager
21
+ *
22
+ * Combines three search layers:
23
+ * 1. Semantic: Vector similarity via embeddings
24
+ * 2. Lexical: Keyword matching via TF-IDF/BM25
25
+ * 3. Symbolic: Structured metadata filtering
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const hybrid = new HybridSearchManager(semanticSearch, rankedSearch);
30
+ * const results = await hybrid.search(graph, 'machine learning', {
31
+ * semanticWeight: 0.5,
32
+ * lexicalWeight: 0.3,
33
+ * symbolicWeight: 0.2,
34
+ * symbolic: { tags: ['ai'] }
35
+ * });
36
+ * ```
37
+ */
38
+ export class HybridSearchManager {
39
+ semanticSearch;
40
+ rankedSearch;
41
+ symbolicSearch;
42
+ constructor(semanticSearch, rankedSearch) {
43
+ this.semanticSearch = semanticSearch;
44
+ this.rankedSearch = rankedSearch;
45
+ this.symbolicSearch = new SymbolicSearch();
46
+ }
47
+ /**
48
+ * Perform hybrid search combining all three layers.
49
+ *
50
+ * @param graph - Knowledge graph to search
51
+ * @param query - Search query text
52
+ * @param options - Hybrid search options with weights
53
+ * @returns Combined and ranked results
54
+ */
55
+ async search(graph, query, options = {}) {
56
+ const { semanticWeight = DEFAULT_HYBRID_WEIGHTS.semantic, lexicalWeight = DEFAULT_HYBRID_WEIGHTS.lexical, symbolicWeight = DEFAULT_HYBRID_WEIGHTS.symbolic, semantic = {}, lexical = {}, symbolic = {}, limit = SEMANTIC_SEARCH_LIMITS.DEFAULT_LIMIT, } = options;
57
+ // Normalize weights
58
+ const totalWeight = semanticWeight + lexicalWeight + symbolicWeight;
59
+ const normSemantic = semanticWeight / totalWeight;
60
+ const normLexical = lexicalWeight / totalWeight;
61
+ const normSymbolic = symbolicWeight / totalWeight;
62
+ // Execute searches in parallel
63
+ const [semanticResults, lexicalResults, symbolicResults] = await Promise.all([
64
+ this.executeSemanticSearch(graph, query, semantic, limit * 2),
65
+ this.executeLexicalSearch(query, lexical, limit * 2),
66
+ this.executeSymbolicSearch(graph.entities, symbolic),
67
+ ]);
68
+ // Merge results
69
+ const merged = this.mergeResults(graph.entities, semanticResults, lexicalResults, symbolicResults, { semantic: normSemantic, lexical: normLexical, symbolic: normSymbolic });
70
+ // Sort by combined score and limit
71
+ return merged
72
+ .sort((a, b) => b.scores.combined - a.scores.combined)
73
+ .slice(0, limit);
74
+ }
75
+ /**
76
+ * Execute semantic search layer.
77
+ */
78
+ async executeSemanticSearch(graph, query, options, limit) {
79
+ const results = new Map();
80
+ if (!this.semanticSearch) {
81
+ return results; // Semantic search not available
82
+ }
83
+ try {
84
+ const semanticResults = await this.semanticSearch.search(graph, query, options.topK ?? limit, options.minSimilarity ?? 0);
85
+ for (const result of semanticResults) {
86
+ results.set(result.entity.name, result.similarity);
87
+ }
88
+ }
89
+ catch {
90
+ // Semantic search may fail if not indexed
91
+ }
92
+ return results;
93
+ }
94
+ /**
95
+ * Execute lexical search layer (TF-IDF/BM25).
96
+ */
97
+ async executeLexicalSearch(query, _options, limit) {
98
+ const results = new Map();
99
+ try {
100
+ const lexicalResults = await this.rankedSearch.searchNodesRanked(query, undefined, // tags
101
+ undefined, // minImportance
102
+ undefined, // maxImportance
103
+ limit);
104
+ // Normalize scores to 0-1 range
105
+ const maxScore = Math.max(...lexicalResults.map(r => r.score), 1);
106
+ for (const result of lexicalResults) {
107
+ results.set(result.entity.name, result.score / maxScore);
108
+ }
109
+ }
110
+ catch {
111
+ // Lexical search may fail
112
+ }
113
+ return results;
114
+ }
115
+ /**
116
+ * Execute symbolic search layer.
117
+ */
118
+ executeSymbolicSearch(entities, filters) {
119
+ const results = new Map();
120
+ if (!filters || Object.keys(filters).length === 0) {
121
+ // No symbolic filters, give all entities base score
122
+ for (const entity of entities) {
123
+ results.set(entity.name, 0.5);
124
+ }
125
+ return results;
126
+ }
127
+ const symbolicResults = this.symbolicSearch.search(entities, filters);
128
+ for (const result of symbolicResults) {
129
+ results.set(result.entity.name, result.score);
130
+ }
131
+ return results;
132
+ }
133
+ /**
134
+ * Merge results from all three layers.
135
+ */
136
+ mergeResults(entities, semanticScores, lexicalScores, symbolicScores, weights) {
137
+ // Collect all unique entity names that have at least one non-zero score
138
+ const allNames = new Set([
139
+ ...semanticScores.keys(),
140
+ ...lexicalScores.keys(),
141
+ ...symbolicScores.keys(),
142
+ ]);
143
+ // Create entity lookup map
144
+ const entityMap = new Map(entities.map(e => [e.name, e]));
145
+ const results = [];
146
+ for (const name of allNames) {
147
+ const entity = entityMap.get(name);
148
+ if (!entity)
149
+ continue;
150
+ const semantic = semanticScores.get(name) ?? 0;
151
+ const lexical = lexicalScores.get(name) ?? 0;
152
+ const symbolic = symbolicScores.get(name) ?? 0;
153
+ const combined = semantic * weights.semantic +
154
+ lexical * weights.lexical +
155
+ symbolic * weights.symbolic;
156
+ const matchedLayers = [];
157
+ if (semantic > 0)
158
+ matchedLayers.push('semantic');
159
+ if (lexical > 0)
160
+ matchedLayers.push('lexical');
161
+ if (symbolic > 0)
162
+ matchedLayers.push('symbolic');
163
+ // Skip if no layers matched meaningfully
164
+ if (matchedLayers.length === 0)
165
+ continue;
166
+ results.push({
167
+ entity,
168
+ scores: { semantic, lexical, symbolic, combined },
169
+ matchedLayers,
170
+ });
171
+ }
172
+ return results;
173
+ }
174
+ /**
175
+ * Search with full entity resolution.
176
+ * Alias for search() since we now always resolve entities.
177
+ */
178
+ async searchWithEntities(graph, query, options = {}) {
179
+ return this.search(graph, query, options);
180
+ }
181
+ /**
182
+ * Get the symbolic search instance for direct access.
183
+ */
184
+ getSymbolicSearch() {
185
+ return this.symbolicSearch;
186
+ }
187
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Query Analyzer
3
+ *
4
+ * Phase 11: Extracts structured information from natural language queries
5
+ * to enable intelligent search planning.
6
+ *
7
+ * @module search/QueryAnalyzer
8
+ */
9
+ import type { QueryAnalysis } from '../types/index.js';
10
+ /**
11
+ * Query Analyzer extracts structured information from queries.
12
+ *
13
+ * Uses rule-based heuristics for reliable extraction of:
14
+ * - Person names
15
+ * - Location names
16
+ * - Organization names
17
+ * - Temporal references
18
+ * - Question type
19
+ * - Query complexity
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const analyzer = new QueryAnalyzer();
24
+ * const analysis = analyzer.analyze(
25
+ * 'What projects did Alice work on last month?'
26
+ * );
27
+ * // { query: '...', entities: [...], persons: ['Alice'], temporalRange: { relative: 'last month' }, ... }
28
+ * ```
29
+ */
30
+ export declare class QueryAnalyzer {
31
+ private personIndicators;
32
+ private temporalKeywords;
33
+ private questionKeywords;
34
+ /**
35
+ * Analyze a query using rule-based heuristics.
36
+ * Main entry point - returns full QueryAnalysis.
37
+ */
38
+ analyze(query: string): QueryAnalysis;
39
+ /**
40
+ * Calculate confidence score for the analysis.
41
+ */
42
+ private calculateConfidence;
43
+ /**
44
+ * Extract person names from query.
45
+ */
46
+ private extractPersons;
47
+ /**
48
+ * Extract location names from query.
49
+ */
50
+ private extractLocations;
51
+ /**
52
+ * Extract organization names from query.
53
+ */
54
+ private extractOrganizations;
55
+ /**
56
+ * Extract temporal range from query.
57
+ */
58
+ private extractTemporalRange;
59
+ /**
60
+ * Detect the type of question.
61
+ */
62
+ private detectQuestionType;
63
+ /**
64
+ * Estimate query complexity.
65
+ */
66
+ private estimateComplexity;
67
+ /**
68
+ * Detect what types of information are being requested.
69
+ */
70
+ private detectRequiredInfoTypes;
71
+ /**
72
+ * Decompose complex queries into sub-queries.
73
+ */
74
+ private decomposeQuery;
75
+ }
76
+ //# sourceMappingURL=QueryAnalyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QueryAnalyzer.d.ts","sourceRoot":"","sources":["../../src/search/QueryAnalyzer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAkC,MAAM,mBAAmB,CAAC;AAEvF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,gBAAgB,CAA0C;IAClE,OAAO,CAAC,gBAAgB,CAKtB;IACF,OAAO,CAAC,gBAAgB,CAOtB;IAEF;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa;IAiCrC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAoB3B;;OAEG;IACH,OAAO,CAAC,cAAc;IAqBtB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAgBxB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAe5B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAsB5B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAc1B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAa/B;;OAEG;IACH,OAAO,CAAC,cAAc;CAYvB"}
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Query Analyzer
3
+ *
4
+ * Phase 11: Extracts structured information from natural language queries
5
+ * to enable intelligent search planning.
6
+ *
7
+ * @module search/QueryAnalyzer
8
+ */
9
+ /**
10
+ * Query Analyzer extracts structured information from queries.
11
+ *
12
+ * Uses rule-based heuristics for reliable extraction of:
13
+ * - Person names
14
+ * - Location names
15
+ * - Organization names
16
+ * - Temporal references
17
+ * - Question type
18
+ * - Query complexity
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const analyzer = new QueryAnalyzer();
23
+ * const analysis = analyzer.analyze(
24
+ * 'What projects did Alice work on last month?'
25
+ * );
26
+ * // { query: '...', entities: [...], persons: ['Alice'], temporalRange: { relative: 'last month' }, ... }
27
+ * ```
28
+ */
29
+ export class QueryAnalyzer {
30
+ personIndicators = ['Mr.', 'Mrs.', 'Ms.', 'Dr.', 'Prof.'];
31
+ temporalKeywords = [
32
+ 'yesterday', 'today', 'tomorrow',
33
+ 'last week', 'last month', 'last year',
34
+ 'this week', 'this month', 'this year',
35
+ 'next week', 'next month', 'next year',
36
+ ];
37
+ questionKeywords = {
38
+ factual: ['what', 'who', 'where', 'which'],
39
+ temporal: ['when', 'how long', 'since', 'until'],
40
+ comparative: ['compare', 'difference', 'vs', 'versus', 'better', 'worse'],
41
+ aggregation: ['how many', 'count', 'total', 'sum', 'average'],
42
+ 'multi-hop': ['and then', 'which means', 'therefore', 'related to'],
43
+ conceptual: ['explain', 'why', 'how does', 'what is the meaning', 'understand'],
44
+ };
45
+ /**
46
+ * Analyze a query using rule-based heuristics.
47
+ * Main entry point - returns full QueryAnalysis.
48
+ */
49
+ analyze(query) {
50
+ const lowerQuery = query.toLowerCase();
51
+ const persons = this.extractPersons(query);
52
+ const locations = this.extractLocations(query);
53
+ const organizations = this.extractOrganizations(query);
54
+ const questionType = this.detectQuestionType(lowerQuery);
55
+ const complexity = this.estimateComplexity(query);
56
+ // Build entities array from extracted names
57
+ const entities = [
58
+ ...persons.map(name => ({ name, type: 'person' })),
59
+ ...locations.map(name => ({ name, type: 'location' })),
60
+ ...organizations.map(name => ({ name, type: 'organization' })),
61
+ ];
62
+ // Calculate confidence based on extraction quality
63
+ const confidence = this.calculateConfidence(entities, complexity, questionType);
64
+ return {
65
+ query,
66
+ entities,
67
+ persons,
68
+ locations,
69
+ organizations,
70
+ temporalRange: this.extractTemporalRange(query) ?? null,
71
+ questionType,
72
+ complexity,
73
+ confidence,
74
+ requiredInfoTypes: this.detectRequiredInfoTypes(lowerQuery),
75
+ subQueries: this.decomposeQuery(query),
76
+ };
77
+ }
78
+ /**
79
+ * Calculate confidence score for the analysis.
80
+ */
81
+ calculateConfidence(entities, complexity, questionType) {
82
+ let confidence = 0.5;
83
+ // Higher confidence for simple queries
84
+ if (complexity === 'low')
85
+ confidence += 0.3;
86
+ else if (complexity === 'medium')
87
+ confidence += 0.1;
88
+ // Higher confidence when entities are detected
89
+ if (entities.length > 0)
90
+ confidence += 0.1;
91
+ // Lower confidence for conceptual queries (harder to satisfy)
92
+ if (questionType === 'conceptual')
93
+ confidence -= 0.2;
94
+ return Math.max(0, Math.min(1, confidence));
95
+ }
96
+ /**
97
+ * Extract person names from query.
98
+ */
99
+ extractPersons(query) {
100
+ const persons = [];
101
+ const words = query.split(/\s+/);
102
+ for (let i = 0; i < words.length; i++) {
103
+ const word = words[i];
104
+ // Check for titles followed by names
105
+ if (this.personIndicators.some(ind => word.startsWith(ind))) {
106
+ if (i + 1 < words.length) {
107
+ persons.push(words[i + 1].replace(/[^a-zA-Z]/g, ''));
108
+ }
109
+ }
110
+ // Check for capitalized words that might be names
111
+ if (/^[A-Z][a-z]+$/.test(word) && i > 0 && !/^[A-Z]/.test(words[i - 1])) {
112
+ persons.push(word);
113
+ }
114
+ }
115
+ return [...new Set(persons)];
116
+ }
117
+ /**
118
+ * Extract location names from query.
119
+ */
120
+ extractLocations(query) {
121
+ const locationIndicators = ['in', 'at', 'from', 'to', 'near'];
122
+ const locations = [];
123
+ const words = query.split(/\s+/);
124
+ for (let i = 0; i < words.length; i++) {
125
+ if (locationIndicators.includes(words[i].toLowerCase())) {
126
+ if (i + 1 < words.length && /^[A-Z]/.test(words[i + 1])) {
127
+ locations.push(words[i + 1].replace(/[^a-zA-Z]/g, ''));
128
+ }
129
+ }
130
+ }
131
+ return [...new Set(locations)];
132
+ }
133
+ /**
134
+ * Extract organization names from query.
135
+ */
136
+ extractOrganizations(query) {
137
+ const orgIndicators = ['Inc.', 'Corp.', 'LLC', 'Ltd.', 'Company', 'Co.'];
138
+ const organizations = [];
139
+ for (const indicator of orgIndicators) {
140
+ const regex = new RegExp(`([A-Z][a-zA-Z]*)\\s*${indicator.replace('.', '\\.')}`, 'g');
141
+ const matches = query.match(regex);
142
+ if (matches) {
143
+ organizations.push(...matches);
144
+ }
145
+ }
146
+ return [...new Set(organizations)];
147
+ }
148
+ /**
149
+ * Extract temporal range from query.
150
+ */
151
+ extractTemporalRange(query) {
152
+ const lowerQuery = query.toLowerCase();
153
+ for (const keyword of this.temporalKeywords) {
154
+ if (lowerQuery.includes(keyword)) {
155
+ return { relative: keyword };
156
+ }
157
+ }
158
+ // Check for date patterns
159
+ const datePattern = /\d{4}-\d{2}-\d{2}/g;
160
+ const dates = query.match(datePattern);
161
+ if (dates && dates.length >= 1) {
162
+ return {
163
+ start: dates[0],
164
+ end: dates.length > 1 ? dates[1] : undefined,
165
+ };
166
+ }
167
+ return undefined;
168
+ }
169
+ /**
170
+ * Detect the type of question.
171
+ */
172
+ detectQuestionType(query) {
173
+ for (const [type, keywords] of Object.entries(this.questionKeywords)) {
174
+ if (keywords.some(kw => query.includes(kw))) {
175
+ return type;
176
+ }
177
+ }
178
+ return 'factual';
179
+ }
180
+ /**
181
+ * Estimate query complexity.
182
+ */
183
+ estimateComplexity(query) {
184
+ const wordCount = query.split(/\s+/).length;
185
+ const hasConjunctions = /\b(and|or|but|then|therefore)\b/i.test(query);
186
+ const hasMultipleClauses = /[,;]/.test(query);
187
+ if (wordCount > 20 || (hasConjunctions && hasMultipleClauses)) {
188
+ return 'high';
189
+ }
190
+ if (wordCount > 10 || hasConjunctions || hasMultipleClauses) {
191
+ return 'medium';
192
+ }
193
+ return 'low';
194
+ }
195
+ /**
196
+ * Detect what types of information are being requested.
197
+ */
198
+ detectRequiredInfoTypes(query) {
199
+ const infoTypes = [];
200
+ if (/\b(who|person|people|name)\b/.test(query))
201
+ infoTypes.push('person');
202
+ if (/\b(where|location|place|city)\b/.test(query))
203
+ infoTypes.push('location');
204
+ if (/\b(when|date|time|year|month)\b/.test(query))
205
+ infoTypes.push('temporal');
206
+ if (/\b(how many|count|number|total)\b/.test(query))
207
+ infoTypes.push('quantity');
208
+ if (/\b(why|reason|because)\b/.test(query))
209
+ infoTypes.push('reason');
210
+ if (/\b(what|which|project|task)\b/.test(query))
211
+ infoTypes.push('entity');
212
+ return infoTypes;
213
+ }
214
+ /**
215
+ * Decompose complex queries into sub-queries.
216
+ */
217
+ decomposeQuery(query) {
218
+ // Split on conjunctions
219
+ const parts = query.split(/\b(and then|and|but|or)\b/i)
220
+ .map(p => p.trim())
221
+ .filter(p => p && !/^(and then|and|but|or)$/i.test(p));
222
+ if (parts.length > 1) {
223
+ return parts;
224
+ }
225
+ return undefined;
226
+ }
227
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Query Planner
3
+ *
4
+ * Phase 11: Generates execution plans for queries based on analysis.
5
+ *
6
+ * @module search/QueryPlanner
7
+ */
8
+ import type { QueryAnalysis, QueryPlan } from '../types/index.js';
9
+ /**
10
+ * Query Planner generates execution plans from query analysis.
11
+ *
12
+ * Creates optimized plans that:
13
+ * - Select appropriate search layers
14
+ * - Determine execution strategy (parallel/sequential)
15
+ * - Set up query dependencies
16
+ * - Configure merge strategies
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const analyzer = new QueryAnalyzer();
21
+ * const planner = new QueryPlanner();
22
+ *
23
+ * const analysis = analyzer.analyze('Find projects by Alice');
24
+ * const plan = planner.createPlan('Find projects by Alice', analysis);
25
+ * // { originalQuery: '...', subQueries: [...], executionStrategy: 'iterative', ... }
26
+ * ```
27
+ */
28
+ export declare class QueryPlanner {
29
+ /**
30
+ * Create an execution plan from query analysis.
31
+ */
32
+ createPlan(query: string, analysis: QueryAnalysis): QueryPlan;
33
+ /**
34
+ * Create sub-queries from analysis.
35
+ */
36
+ private createSubQueries;
37
+ /**
38
+ * Select the most appropriate search layer.
39
+ */
40
+ private selectLayer;
41
+ /**
42
+ * Select execution strategy based on sub-queries.
43
+ */
44
+ private selectExecutionStrategy;
45
+ /**
46
+ * Select merge strategy based on question type.
47
+ */
48
+ private selectMergeStrategy;
49
+ /**
50
+ * Build symbolic filters from analysis.
51
+ */
52
+ private buildFilters;
53
+ /**
54
+ * Calculate plan complexity score.
55
+ */
56
+ private calculateComplexity;
57
+ }
58
+ //# sourceMappingURL=QueryPlanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QueryPlanner.d.ts","sourceRoot":"","sources":["../../src/search/QueryPlanner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAA6B,MAAM,mBAAmB,CAAC;AAE7F;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,YAAY;IACvB;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,SAAS;IAc7D;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA6BxB;;OAEG;IACH,OAAO,CAAC,WAAW;IAanB;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAO/B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAQ3B;;OAEG;IACH,OAAO,CAAC,YAAY;IAepB;;OAEG;IACH,OAAO,CAAC,mBAAmB;CAQ5B"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Query Planner
3
+ *
4
+ * Phase 11: Generates execution plans for queries based on analysis.
5
+ *
6
+ * @module search/QueryPlanner
7
+ */
8
+ /**
9
+ * Query Planner generates execution plans from query analysis.
10
+ *
11
+ * Creates optimized plans that:
12
+ * - Select appropriate search layers
13
+ * - Determine execution strategy (parallel/sequential)
14
+ * - Set up query dependencies
15
+ * - Configure merge strategies
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const analyzer = new QueryAnalyzer();
20
+ * const planner = new QueryPlanner();
21
+ *
22
+ * const analysis = analyzer.analyze('Find projects by Alice');
23
+ * const plan = planner.createPlan('Find projects by Alice', analysis);
24
+ * // { originalQuery: '...', subQueries: [...], executionStrategy: 'iterative', ... }
25
+ * ```
26
+ */
27
+ export class QueryPlanner {
28
+ /**
29
+ * Create an execution plan from query analysis.
30
+ */
31
+ createPlan(query, analysis) {
32
+ const subQueries = this.createSubQueries(query, analysis);
33
+ const executionStrategy = this.selectExecutionStrategy(subQueries);
34
+ const mergeStrategy = this.selectMergeStrategy(analysis);
35
+ return {
36
+ originalQuery: query,
37
+ subQueries,
38
+ executionStrategy,
39
+ mergeStrategy,
40
+ estimatedComplexity: this.calculateComplexity(subQueries),
41
+ };
42
+ }
43
+ /**
44
+ * Create sub-queries from analysis.
45
+ */
46
+ createSubQueries(query, analysis) {
47
+ const subQueries = [];
48
+ let id = 0;
49
+ // If query was decomposed, create sub-query for each part
50
+ if (analysis.subQueries && analysis.subQueries.length > 1) {
51
+ for (const sq of analysis.subQueries) {
52
+ subQueries.push({
53
+ id: `sq_${id++}`,
54
+ query: sq,
55
+ targetLayer: this.selectLayer(analysis),
56
+ priority: id === 1 ? 1 : 2,
57
+ dependsOn: id > 1 ? [`sq_${id - 2}`] : undefined,
58
+ });
59
+ }
60
+ }
61
+ else {
62
+ // Single query
63
+ subQueries.push({
64
+ id: `sq_${id}`,
65
+ query,
66
+ targetLayer: this.selectLayer(analysis),
67
+ priority: 1,
68
+ filters: this.buildFilters(analysis),
69
+ });
70
+ }
71
+ return subQueries;
72
+ }
73
+ /**
74
+ * Select the most appropriate search layer.
75
+ */
76
+ selectLayer(analysis) {
77
+ // Use symbolic for tag/type/date filtered queries
78
+ if (analysis.temporalRange || analysis.requiredInfoTypes.includes('temporal')) {
79
+ return 'symbolic';
80
+ }
81
+ // Use semantic for complex concept queries
82
+ if (analysis.complexity === 'high' || analysis.questionType === 'comparative') {
83
+ return 'semantic';
84
+ }
85
+ // Use hybrid for balanced approach
86
+ return 'hybrid';
87
+ }
88
+ /**
89
+ * Select execution strategy based on sub-queries.
90
+ */
91
+ selectExecutionStrategy(subQueries) {
92
+ const hasDependencies = subQueries.some(sq => sq.dependsOn && sq.dependsOn.length > 0);
93
+ if (hasDependencies)
94
+ return 'sequential';
95
+ if (subQueries.length > 1)
96
+ return 'parallel';
97
+ return 'iterative';
98
+ }
99
+ /**
100
+ * Select merge strategy based on question type.
101
+ */
102
+ selectMergeStrategy(analysis) {
103
+ switch (analysis.questionType) {
104
+ case 'aggregation': return 'union';
105
+ case 'comparative': return 'intersection';
106
+ default: return 'weighted';
107
+ }
108
+ }
109
+ /**
110
+ * Build symbolic filters from analysis.
111
+ */
112
+ buildFilters(analysis) {
113
+ const filters = {};
114
+ let hasFilters = false;
115
+ if (analysis.temporalRange) {
116
+ filters.dateRange = {
117
+ start: analysis.temporalRange.start || '',
118
+ end: analysis.temporalRange.end || '',
119
+ };
120
+ hasFilters = true;
121
+ }
122
+ return hasFilters ? filters : undefined;
123
+ }
124
+ /**
125
+ * Calculate plan complexity score.
126
+ */
127
+ calculateComplexity(subQueries) {
128
+ let complexity = subQueries.length;
129
+ for (const sq of subQueries) {
130
+ if (sq.dependsOn)
131
+ complexity += sq.dependsOn.length * 0.5;
132
+ if (sq.filters)
133
+ complexity += 0.5;
134
+ }
135
+ return Math.min(complexity, 10);
136
+ }
137
+ }