@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
@@ -1,432 +0,0 @@
1
- /**
2
- * BooleanSearch Unit Tests
3
- */
4
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
5
- import { BooleanSearch } from '../../../search/BooleanSearch.js';
6
- import { EntityManager } from '../../../core/EntityManager.js';
7
- import { RelationManager } from '../../../core/RelationManager.js';
8
- import { GraphStorage } from '../../../core/GraphStorage.js';
9
- import { promises as fs } from 'fs';
10
- import { join } from 'path';
11
- import { tmpdir } from 'os';
12
- describe('BooleanSearch', () => {
13
- let storage;
14
- let booleanSearch;
15
- let entityManager;
16
- let relationManager;
17
- let testDir;
18
- let testFilePath;
19
- beforeEach(async () => {
20
- // Create unique temp directory for each test
21
- testDir = join(tmpdir(), `boolean-search-test-${Date.now()}-${Math.random()}`);
22
- await fs.mkdir(testDir, { recursive: true });
23
- testFilePath = join(testDir, 'test-graph.jsonl');
24
- storage = new GraphStorage(testFilePath);
25
- booleanSearch = new BooleanSearch(storage);
26
- entityManager = new EntityManager(storage);
27
- relationManager = new RelationManager(storage);
28
- // Create test data
29
- await entityManager.createEntities([
30
- {
31
- name: 'Alice',
32
- entityType: 'person',
33
- observations: ['Software engineer', 'Loves Python programming', 'Works on AI projects'],
34
- tags: ['engineering', 'python', 'ai'],
35
- importance: 9,
36
- },
37
- {
38
- name: 'Bob',
39
- entityType: 'person',
40
- observations: ['Product manager', 'Leads roadmap planning'],
41
- tags: ['product', 'management'],
42
- importance: 8,
43
- },
44
- {
45
- name: 'Charlie',
46
- entityType: 'person',
47
- observations: ['Designer', 'Creates beautiful UIs', 'Expert in Figma'],
48
- tags: ['design', 'ui'],
49
- importance: 7,
50
- },
51
- {
52
- name: 'Project_X',
53
- entityType: 'project',
54
- observations: ['Internal automation tool', 'Built with Python'],
55
- tags: ['engineering', 'automation', 'python'],
56
- importance: 10,
57
- },
58
- {
59
- name: 'Company',
60
- entityType: 'organization',
61
- observations: ['Tech startup', 'AI-focused company'],
62
- tags: ['business', 'ai'],
63
- importance: 10,
64
- },
65
- ]);
66
- await relationManager.createRelations([
67
- { from: 'Alice', to: 'Project_X', relationType: 'works_on' },
68
- { from: 'Bob', to: 'Project_X', relationType: 'manages' },
69
- { from: 'Alice', to: 'Bob', relationType: 'reports_to' },
70
- ]);
71
- });
72
- afterEach(async () => {
73
- // Clean up test files
74
- try {
75
- await fs.rm(testDir, { recursive: true, force: true });
76
- }
77
- catch {
78
- // Ignore cleanup errors
79
- }
80
- });
81
- describe('AND Operator', () => {
82
- it('should find entities matching all AND terms', async () => {
83
- const result = await booleanSearch.booleanSearch('Python AND engineer');
84
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
85
- const names = result.entities.map(e => e.name);
86
- expect(names).toContain('Alice');
87
- });
88
- it('should support explicit AND operator', async () => {
89
- const result = await booleanSearch.booleanSearch('person AND manager');
90
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
91
- const names = result.entities.map(e => e.name);
92
- expect(names).toContain('Bob');
93
- });
94
- it('should support implicit AND (adjacent terms)', async () => {
95
- const result = await booleanSearch.booleanSearch('engineer python');
96
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
97
- const names = result.entities.map(e => e.name);
98
- expect(names).toContain('Alice');
99
- });
100
- it('should return empty results when no entities match all terms', async () => {
101
- const result = await booleanSearch.booleanSearch('engineer AND designer');
102
- // Alice is engineer, Charlie is designer, but none is both
103
- expect(result.entities).toHaveLength(0);
104
- });
105
- it('should handle multiple AND operators', async () => {
106
- const result = await booleanSearch.booleanSearch('person AND engineer AND Python');
107
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
108
- const names = result.entities.map(e => e.name);
109
- expect(names).toContain('Alice');
110
- });
111
- });
112
- describe('OR Operator', () => {
113
- it('should find entities matching any OR term', async () => {
114
- const result = await booleanSearch.booleanSearch('engineer OR designer');
115
- expect(result.entities.length).toBeGreaterThanOrEqual(2);
116
- const names = result.entities.map(e => e.name);
117
- expect(names).toContain('Alice');
118
- expect(names).toContain('Charlie');
119
- });
120
- it('should support multiple OR operators', async () => {
121
- const result = await booleanSearch.booleanSearch('Alice OR Bob OR Charlie');
122
- expect(result.entities).toHaveLength(3);
123
- const names = result.entities.map(e => e.name);
124
- expect(names).toContain('Alice');
125
- expect(names).toContain('Bob');
126
- expect(names).toContain('Charlie');
127
- });
128
- it('should handle OR with partial matches', async () => {
129
- const result = await booleanSearch.booleanSearch('Python OR Figma');
130
- expect(result.entities.length).toBeGreaterThanOrEqual(2);
131
- const names = result.entities.map(e => e.name);
132
- // Alice and Project_X have Python, Charlie has Figma
133
- expect(names).toContain('Alice');
134
- expect(names).toContain('Charlie');
135
- });
136
- });
137
- describe('NOT Operator', () => {
138
- it('should exclude entities matching NOT term', async () => {
139
- const result = await booleanSearch.booleanSearch('person AND NOT manager');
140
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
141
- const names = result.entities.map(e => e.name);
142
- expect(names).toContain('Alice');
143
- expect(names).toContain('Charlie');
144
- expect(names).not.toContain('Bob'); // Bob is manager
145
- });
146
- it('should handle NOT with specific field', async () => {
147
- const result = await booleanSearch.booleanSearch('NOT type:project');
148
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
149
- const names = result.entities.map(e => e.name);
150
- expect(names).not.toContain('Project_X');
151
- });
152
- it('should support double NOT', async () => {
153
- const result = await booleanSearch.booleanSearch('NOT NOT Alice');
154
- expect(result.entities).toHaveLength(1);
155
- expect(result.entities[0].name).toBe('Alice');
156
- });
157
- });
158
- describe('Parentheses Grouping', () => {
159
- it('should support parentheses for precedence', async () => {
160
- const result = await booleanSearch.booleanSearch('person AND (Python OR Figma)');
161
- expect(result.entities.length).toBeGreaterThanOrEqual(2);
162
- const names = result.entities.map(e => e.name);
163
- expect(names).toContain('Alice'); // person with Python
164
- expect(names).toContain('Charlie'); // person with Figma
165
- expect(names).not.toContain('Project_X'); // not a person
166
- });
167
- it('should handle nested parentheses', async () => {
168
- const result = await booleanSearch.booleanSearch('(person OR project) AND (Python OR automation)');
169
- expect(result.entities.length).toBeGreaterThanOrEqual(2);
170
- const names = result.entities.map(e => e.name);
171
- expect(names).toContain('Alice'); // person with Python
172
- expect(names).toContain('Project_X'); // project with Python and automation
173
- });
174
- it('should handle NOT with parentheses', async () => {
175
- const result = await booleanSearch.booleanSearch('person AND NOT (manager OR designer)');
176
- expect(result.entities).toHaveLength(1);
177
- expect(result.entities[0].name).toBe('Alice'); // Only person who isn't manager or designer
178
- });
179
- });
180
- describe('Field-Specific Queries', () => {
181
- it('should support name field query', async () => {
182
- const result = await booleanSearch.booleanSearch('name:Alice');
183
- expect(result.entities).toHaveLength(1);
184
- expect(result.entities[0].name).toBe('Alice');
185
- });
186
- it('should support type field query', async () => {
187
- const result = await booleanSearch.booleanSearch('type:person');
188
- expect(result.entities).toHaveLength(3);
189
- result.entities.forEach(e => {
190
- expect(e.entityType).toBe('person');
191
- });
192
- });
193
- it('should support entitytype field query', async () => {
194
- const result = await booleanSearch.booleanSearch('entitytype:organization');
195
- expect(result.entities).toHaveLength(1);
196
- expect(result.entities[0].entityType).toBe('organization');
197
- });
198
- it('should support observation field query', async () => {
199
- const result = await booleanSearch.booleanSearch('observation:engineer');
200
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
201
- const names = result.entities.map(e => e.name);
202
- expect(names).toContain('Alice');
203
- });
204
- it('should support observations field query', async () => {
205
- const result = await booleanSearch.booleanSearch('observations:manager');
206
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
207
- const names = result.entities.map(e => e.name);
208
- expect(names).toContain('Bob');
209
- });
210
- it('should support tag field query', async () => {
211
- const result = await booleanSearch.booleanSearch('tag:python');
212
- expect(result.entities.length).toBeGreaterThanOrEqual(2);
213
- result.entities.forEach(e => {
214
- expect(e.tags).toContain('python');
215
- });
216
- });
217
- it('should support tags field query', async () => {
218
- const result = await booleanSearch.booleanSearch('tags:ai');
219
- expect(result.entities.length).toBeGreaterThanOrEqual(2);
220
- result.entities.forEach(e => {
221
- expect(e.tags).toContain('ai');
222
- });
223
- });
224
- it('should combine multiple field queries', async () => {
225
- const result = await booleanSearch.booleanSearch('type:person AND tag:python');
226
- expect(result.entities).toHaveLength(1);
227
- expect(result.entities[0].name).toBe('Alice');
228
- });
229
- it('should handle unknown field gracefully', async () => {
230
- const result = await booleanSearch.booleanSearch('unknownfield:test');
231
- // Should fallback to general search across all fields
232
- expect(Array.isArray(result.entities)).toBe(true);
233
- });
234
- it('should handle field query with colon in value', async () => {
235
- await entityManager.createEntities([
236
- { name: 'URL_Entity', entityType: 'test', observations: ['https://example.com'] },
237
- ]);
238
- const result = await booleanSearch.booleanSearch('observation:https');
239
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
240
- const names = result.entities.map(e => e.name);
241
- expect(names).toContain('URL_Entity');
242
- });
243
- });
244
- describe('Tag Filtering', () => {
245
- it('should filter by single tag', async () => {
246
- const result = await booleanSearch.booleanSearch('engineer', ['python']);
247
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
248
- result.entities.forEach(e => {
249
- expect(e.tags).toContain('python');
250
- });
251
- });
252
- it('should filter by multiple tags (OR logic)', async () => {
253
- const result = await booleanSearch.booleanSearch('person', ['python', 'design']);
254
- expect(result.entities.length).toBeGreaterThanOrEqual(2);
255
- result.entities.forEach(e => {
256
- const hasPython = e.tags?.includes('python');
257
- const hasDesign = e.tags?.includes('design');
258
- expect(hasPython || hasDesign).toBe(true);
259
- });
260
- });
261
- it('should combine boolean query with tag filter', async () => {
262
- const result = await booleanSearch.booleanSearch('person OR project', ['python']);
263
- expect(result.entities.length).toBeGreaterThanOrEqual(2);
264
- const names = result.entities.map(e => e.name);
265
- expect(names).toContain('Alice');
266
- expect(names).toContain('Project_X');
267
- });
268
- it('should exclude entities without matching tags', async () => {
269
- const result = await booleanSearch.booleanSearch('person', ['nonexistent']);
270
- expect(result.entities).toHaveLength(0);
271
- });
272
- });
273
- describe('Importance Filtering', () => {
274
- it('should filter by minimum importance', async () => {
275
- const result = await booleanSearch.booleanSearch('person OR project', undefined, 9);
276
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
277
- result.entities.forEach(e => {
278
- expect(e.importance).toBeGreaterThanOrEqual(9);
279
- });
280
- });
281
- it('should filter by maximum importance', async () => {
282
- const result = await booleanSearch.booleanSearch('person', undefined, undefined, 8);
283
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
284
- result.entities.forEach(e => {
285
- expect(e.importance).toBeLessThanOrEqual(8);
286
- });
287
- });
288
- it('should filter by importance range', async () => {
289
- const result = await booleanSearch.booleanSearch('person OR project', undefined, 8, 9);
290
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
291
- result.entities.forEach(e => {
292
- expect(e.importance).toBeGreaterThanOrEqual(8);
293
- expect(e.importance).toBeLessThanOrEqual(9);
294
- });
295
- });
296
- it('should combine boolean query with importance filter', async () => {
297
- const result = await booleanSearch.booleanSearch('Python', undefined, 10);
298
- expect(result.entities).toHaveLength(1);
299
- expect(result.entities[0].name).toBe('Project_X');
300
- // Company doesn't have Python, Alice has importance 9
301
- });
302
- });
303
- describe('Relations', () => {
304
- it('should include relations between matched entities', async () => {
305
- const result = await booleanSearch.booleanSearch('Alice OR Bob');
306
- expect(result.entities).toHaveLength(2);
307
- expect(result.relations.length).toBeGreaterThan(0);
308
- expect(result.relations.some(r => r.from === 'Alice' && r.to === 'Bob')).toBe(true);
309
- });
310
- it('should exclude relations to non-matched entities', async () => {
311
- const result = await booleanSearch.booleanSearch('name:Alice');
312
- expect(result.entities).toHaveLength(1);
313
- // Alice has relations to Bob and Project_X, but they're not in the result
314
- expect(result.relations).toHaveLength(0);
315
- });
316
- it('should include all relations in matched subgraph', async () => {
317
- const result = await booleanSearch.booleanSearch('Alice OR Bob OR Project_X');
318
- expect(result.entities).toHaveLength(3);
319
- expect(result.relations.length).toBeGreaterThan(0);
320
- expect(result.relations.some(r => r.from === 'Alice' && r.to === 'Project_X')).toBe(true);
321
- expect(result.relations.some(r => r.from === 'Bob' && r.to === 'Project_X')).toBe(true);
322
- });
323
- });
324
- describe('Case Sensitivity', () => {
325
- it('should be case-insensitive for terms', async () => {
326
- const result = await booleanSearch.booleanSearch('ALICE');
327
- expect(result.entities).toHaveLength(1);
328
- expect(result.entities[0].name).toBe('Alice');
329
- });
330
- it('should be case-insensitive for field queries', async () => {
331
- const result = await booleanSearch.booleanSearch('TYPE:PERSON');
332
- expect(result.entities).toHaveLength(3);
333
- });
334
- it('should be case-insensitive for operators', async () => {
335
- const result = await booleanSearch.booleanSearch('alice or bob');
336
- expect(result.entities).toHaveLength(2);
337
- });
338
- });
339
- describe('Quoted Strings', () => {
340
- it('should support quoted strings for multi-word searches', async () => {
341
- const result = await booleanSearch.booleanSearch('"Software engineer"');
342
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
343
- const names = result.entities.map(e => e.name);
344
- expect(names).toContain('Alice');
345
- });
346
- it('should combine quoted strings with operators', async () => {
347
- const result = await booleanSearch.booleanSearch('"Product manager" OR "Software engineer"');
348
- expect(result.entities.length).toBeGreaterThanOrEqual(2);
349
- const names = result.entities.map(e => e.name);
350
- expect(names).toContain('Alice');
351
- expect(names).toContain('Bob');
352
- });
353
- it('should support quoted strings in field queries', async () => {
354
- const result = await booleanSearch.booleanSearch('observation:"beautiful UIs"');
355
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
356
- const names = result.entities.map(e => e.name);
357
- expect(names).toContain('Charlie');
358
- });
359
- });
360
- describe('Edge Cases', () => {
361
- it('should handle single term query', async () => {
362
- const result = await booleanSearch.booleanSearch('Alice');
363
- expect(result.entities).toHaveLength(1);
364
- expect(result.entities[0].name).toBe('Alice');
365
- });
366
- it('should handle entities with empty observations', async () => {
367
- await entityManager.createEntities([
368
- { name: 'EmptyObs', entityType: 'test', observations: [] },
369
- ]);
370
- const result = await booleanSearch.booleanSearch('type:test');
371
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
372
- const names = result.entities.map(e => e.name);
373
- expect(names).toContain('EmptyObs');
374
- });
375
- it('should handle entities without tags', async () => {
376
- await entityManager.createEntities([
377
- { name: 'NoTags', entityType: 'test', observations: ['Test'] },
378
- ]);
379
- const result = await booleanSearch.booleanSearch('test');
380
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
381
- });
382
- it('should handle entities without importance', async () => {
383
- await entityManager.createEntities([
384
- { name: 'NoImportance', entityType: 'test', observations: ['Test'] },
385
- ]);
386
- const result = await booleanSearch.booleanSearch('test', undefined, 5);
387
- const names = result.entities.map(e => e.name);
388
- expect(names).not.toContain('NoImportance');
389
- });
390
- it('should throw error on malformed query (unclosed parenthesis)', async () => {
391
- await expect(booleanSearch.booleanSearch('(person AND manager')).rejects.toThrow();
392
- });
393
- it('should throw error on malformed query (unexpected token)', async () => {
394
- await expect(booleanSearch.booleanSearch('person) AND manager')).rejects.toThrow();
395
- });
396
- it('should throw error on empty query', async () => {
397
- await expect(booleanSearch.booleanSearch('')).rejects.toThrow();
398
- });
399
- it('should handle complex nested query', async () => {
400
- const result = await booleanSearch.booleanSearch('(type:person AND (tag:python OR tag:design)) OR (type:project AND tag:automation)');
401
- expect(result.entities.length).toBeGreaterThanOrEqual(2);
402
- const names = result.entities.map(e => e.name);
403
- // Alice (person with python tag), Charlie (person with design tag), Project_X (project with automation tag)
404
- expect(names).toContain('Alice');
405
- expect(names).toContain('Charlie');
406
- expect(names).toContain('Project_X');
407
- });
408
- it('should handle whitespace in query', async () => {
409
- const result = await booleanSearch.booleanSearch(' person AND engineer ');
410
- expect(result.entities.length).toBeGreaterThanOrEqual(1);
411
- const names = result.entities.map(e => e.name);
412
- expect(names).toContain('Alice');
413
- });
414
- });
415
- describe('Return Value Structure', () => {
416
- it('should return KnowledgeGraph with entities and relations', async () => {
417
- const result = await booleanSearch.booleanSearch('person');
418
- expect(result).toHaveProperty('entities');
419
- expect(result).toHaveProperty('relations');
420
- expect(Array.isArray(result.entities)).toBe(true);
421
- expect(Array.isArray(result.relations)).toBe(true);
422
- });
423
- it('should return complete entity objects', async () => {
424
- const result = await booleanSearch.booleanSearch('Alice');
425
- expect(result.entities[0]).toHaveProperty('name');
426
- expect(result.entities[0]).toHaveProperty('entityType');
427
- expect(result.entities[0]).toHaveProperty('observations');
428
- expect(result.entities[0]).toHaveProperty('tags');
429
- expect(result.entities[0]).toHaveProperty('importance');
430
- });
431
- });
432
- });