mcp-memory-keeper 0.10.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 (98) hide show
  1. package/CHANGELOG.md +433 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1051 -0
  4. package/bin/mcp-memory-keeper +52 -0
  5. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  6. package/dist/__tests__/helpers/test-server.js +92 -0
  7. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  8. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  9. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  10. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  11. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  12. package/dist/__tests__/integration/channels.test.js +376 -0
  13. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  14. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  15. package/dist/__tests__/integration/context-operations.test.js +243 -0
  16. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  17. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  18. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  19. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  20. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  21. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  22. package/dist/__tests__/integration/contextSearch.test.js +938 -0
  23. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  24. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  25. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  26. package/dist/__tests__/integration/cross-session-sharing.test.js +302 -0
  27. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  28. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  29. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  30. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  31. package/dist/__tests__/integration/error-cases.test.js +407 -0
  32. package/dist/__tests__/integration/export-import.test.js +367 -0
  33. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  34. package/dist/__tests__/integration/file-operations.test.js +264 -0
  35. package/dist/__tests__/integration/git-integration.test.js +237 -0
  36. package/dist/__tests__/integration/index-tools.test.js +496 -0
  37. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  38. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  39. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  40. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  41. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  42. package/dist/__tests__/integration/migrations.test.js +528 -0
  43. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  44. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  45. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  46. package/dist/__tests__/integration/project-directory.test.js +283 -0
  47. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  48. package/dist/__tests__/integration/retention.test.js +513 -0
  49. package/dist/__tests__/integration/search.test.js +333 -0
  50. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  51. package/dist/__tests__/integration/server-initialization.test.js +307 -0
  52. package/dist/__tests__/integration/session-management.test.js +219 -0
  53. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  54. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  55. package/dist/__tests__/integration/summarization.test.js +308 -0
  56. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  57. package/dist/__tests__/security/input-validation.test.js +115 -0
  58. package/dist/__tests__/utils/agents.test.js +473 -0
  59. package/dist/__tests__/utils/database.test.js +177 -0
  60. package/dist/__tests__/utils/git.test.js +122 -0
  61. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  62. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  63. package/dist/__tests__/utils/project-directory-messages.test.js +188 -0
  64. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  65. package/dist/__tests__/utils/validation.test.js +200 -0
  66. package/dist/__tests__/utils/vector-store.test.js +231 -0
  67. package/dist/handlers/contextWatchHandlers.js +206 -0
  68. package/dist/index.js +4310 -0
  69. package/dist/index.phase1.backup.js +410 -0
  70. package/dist/index.phase2.backup.js +704 -0
  71. package/dist/migrations/003_add_channels.js +174 -0
  72. package/dist/migrations/004_add_context_watch.js +151 -0
  73. package/dist/migrations/005_add_context_watch.js +98 -0
  74. package/dist/migrations/simplify-sharing.js +117 -0
  75. package/dist/repositories/BaseRepository.js +30 -0
  76. package/dist/repositories/CheckpointRepository.js +140 -0
  77. package/dist/repositories/ContextRepository.js +1873 -0
  78. package/dist/repositories/FileRepository.js +104 -0
  79. package/dist/repositories/RepositoryManager.js +62 -0
  80. package/dist/repositories/SessionRepository.js +66 -0
  81. package/dist/repositories/WatcherRepository.js +252 -0
  82. package/dist/repositories/index.js +15 -0
  83. package/dist/server.js +384 -0
  84. package/dist/test-helpers/database-helper.js +128 -0
  85. package/dist/types/entities.js +3 -0
  86. package/dist/utils/agents.js +791 -0
  87. package/dist/utils/channels.js +150 -0
  88. package/dist/utils/database.js +731 -0
  89. package/dist/utils/feature-flags.js +476 -0
  90. package/dist/utils/git.js +145 -0
  91. package/dist/utils/knowledge-graph.js +264 -0
  92. package/dist/utils/migrationHealthCheck.js +373 -0
  93. package/dist/utils/migrations.js +452 -0
  94. package/dist/utils/retention.js +460 -0
  95. package/dist/utils/timestamps.js +112 -0
  96. package/dist/utils/validation.js +296 -0
  97. package/dist/utils/vector-store.js +247 -0
  98. package/package.json +84 -0
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const git_1 = require("../../utils/git");
4
+ const simple_git_1 = require("simple-git");
5
+ // Mock simple-git
6
+ jest.mock('simple-git', () => ({
7
+ simpleGit: jest.fn(),
8
+ }));
9
+ describe('GitOperations', () => {
10
+ let gitOps;
11
+ let mockGit;
12
+ beforeEach(() => {
13
+ jest.clearAllMocks();
14
+ mockGit = {
15
+ checkIsRepo: jest.fn(),
16
+ status: jest.fn(),
17
+ branch: jest.fn(),
18
+ add: jest.fn(),
19
+ commit: jest.fn(),
20
+ };
21
+ simple_git_1.simpleGit.mockReturnValue(mockGit);
22
+ gitOps = new git_1.GitOperations('/test/repo');
23
+ });
24
+ afterEach(() => {
25
+ jest.clearAllMocks();
26
+ });
27
+ describe('getGitInfo', () => {
28
+ it('should return git info when in a repository', async () => {
29
+ mockGit.checkIsRepo.mockResolvedValue(true);
30
+ mockGit.status.mockResolvedValue({
31
+ modified: ['file1.ts'],
32
+ created: ['file2.ts'],
33
+ deleted: [],
34
+ staged: ['file1.ts'],
35
+ ahead: 1,
36
+ behind: 0,
37
+ });
38
+ mockGit.branch.mockResolvedValue({ current: 'main' });
39
+ const result = await gitOps.getGitInfo();
40
+ expect(result.isGitRepo).toBe(true);
41
+ expect(result.branch).toBe('main');
42
+ expect(result.status).toContain('modified');
43
+ expect(result.status).toContain('file1.ts');
44
+ });
45
+ it('should handle non-git directories gracefully', async () => {
46
+ mockGit.checkIsRepo.mockRejectedValue(new Error('Not a git repository'));
47
+ const result = await gitOps.getGitInfo();
48
+ expect(result.isGitRepo).toBe(false);
49
+ expect(result.branch).toBe('none');
50
+ expect(result.status).toBe('Not a git repository');
51
+ });
52
+ it('should handle git errors gracefully', async () => {
53
+ mockGit.checkIsRepo.mockResolvedValue(true);
54
+ mockGit.status.mockRejectedValue(new Error('Git command failed'));
55
+ const result = await gitOps.getGitInfo();
56
+ expect(result.isGitRepo).toBe(false);
57
+ expect(result.branch).toBe('error');
58
+ expect(result.status).toContain('Git error');
59
+ });
60
+ });
61
+ describe('getCurrentBranch', () => {
62
+ it('should return current branch name', async () => {
63
+ mockGit.branch.mockResolvedValue({ current: 'feature/test' });
64
+ const branch = await gitOps.getCurrentBranch();
65
+ expect(branch).toBe('feature/test');
66
+ });
67
+ it('should return null when branch cannot be determined', async () => {
68
+ mockGit.branch.mockRejectedValue(new Error('Not a git repository'));
69
+ const branch = await gitOps.getCurrentBranch();
70
+ expect(branch).toBeNull();
71
+ });
72
+ it('should handle empty branch response', async () => {
73
+ // Mock fs.existsSync to prevent fallback to .git/HEAD
74
+ const originalExistsSync = require('fs').existsSync;
75
+ require('fs').existsSync = jest.fn().mockReturnValue(false);
76
+ mockGit.branch.mockResolvedValue({ current: '' });
77
+ const branch = await gitOps.getCurrentBranch();
78
+ // Restore original
79
+ require('fs').existsSync = originalExistsSync;
80
+ // Empty string should return null
81
+ expect(branch).toBeNull();
82
+ });
83
+ });
84
+ describe('safeCommit', () => {
85
+ it('should successfully commit changes', async () => {
86
+ mockGit.checkIsRepo.mockResolvedValue(true);
87
+ mockGit.status.mockResolvedValue({ files: ['file1.ts'] });
88
+ mockGit.add.mockResolvedValue(undefined);
89
+ mockGit.commit.mockResolvedValue({ commit: 'abc123' });
90
+ const result = await gitOps.safeCommit('Test commit');
91
+ expect(result.success).toBe(true);
92
+ expect(result.commit).toBe('abc123');
93
+ expect(result.error).toBeUndefined();
94
+ expect(mockGit.add).toHaveBeenCalledWith('.');
95
+ expect(mockGit.commit).toHaveBeenCalledWith('Test commit');
96
+ });
97
+ it('should handle non-git directories', async () => {
98
+ mockGit.checkIsRepo.mockRejectedValue(new Error('Not a git repository'));
99
+ const result = await gitOps.safeCommit('Test commit');
100
+ expect(result.success).toBe(false);
101
+ expect(result.error).toBe('Not a git repository');
102
+ expect(mockGit.commit).not.toHaveBeenCalled();
103
+ });
104
+ it('should handle no changes to commit', async () => {
105
+ mockGit.checkIsRepo.mockResolvedValue(true);
106
+ mockGit.status.mockResolvedValue({ files: [] });
107
+ const result = await gitOps.safeCommit('Test commit');
108
+ expect(result.success).toBe(false);
109
+ expect(result.error).toBe('No changes to commit');
110
+ expect(mockGit.commit).not.toHaveBeenCalled();
111
+ });
112
+ it('should handle commit failures', async () => {
113
+ mockGit.checkIsRepo.mockResolvedValue(true);
114
+ mockGit.status.mockResolvedValue({ files: ['file1.ts'] });
115
+ mockGit.add.mockResolvedValue(undefined);
116
+ mockGit.commit.mockRejectedValue(new Error('Commit failed'));
117
+ const result = await gitOps.safeCommit('Test commit');
118
+ expect(result.success).toBe(false);
119
+ expect(result.error).toBe('Commit failed');
120
+ });
121
+ });
122
+ });
@@ -0,0 +1,297 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const database_1 = require("../../utils/database");
37
+ const knowledge_graph_1 = require("../../utils/knowledge-graph");
38
+ const os = __importStar(require("os"));
39
+ const path = __importStar(require("path"));
40
+ const fs = __importStar(require("fs"));
41
+ const uuid_1 = require("uuid");
42
+ describe('KnowledgeGraphManager', () => {
43
+ let dbManager;
44
+ let knowledgeGraph;
45
+ let tempDbPath;
46
+ let db;
47
+ let testSessionId;
48
+ beforeEach(() => {
49
+ tempDbPath = path.join(os.tmpdir(), `test-kg-${Date.now()}.db`);
50
+ dbManager = new database_1.DatabaseManager({
51
+ filename: tempDbPath,
52
+ maxSize: 10 * 1024 * 1024,
53
+ walMode: true,
54
+ });
55
+ db = dbManager.getDatabase();
56
+ knowledgeGraph = new knowledge_graph_1.KnowledgeGraphManager(db);
57
+ // Create test session
58
+ testSessionId = (0, uuid_1.v4)();
59
+ db.prepare('INSERT INTO sessions (id, name, description) VALUES (?, ?, ?)').run(testSessionId, 'Test Session', 'Testing knowledge graph');
60
+ });
61
+ afterEach(() => {
62
+ dbManager.close();
63
+ try {
64
+ fs.unlinkSync(tempDbPath);
65
+ fs.unlinkSync(`${tempDbPath}-wal`);
66
+ fs.unlinkSync(`${tempDbPath}-shm`);
67
+ }
68
+ catch (_e) {
69
+ // Ignore
70
+ }
71
+ });
72
+ describe('Entity operations', () => {
73
+ it('should create an entity', () => {
74
+ const entity = knowledgeGraph.createEntity(testSessionId, 'file', 'user.model.ts', {
75
+ language: 'typescript',
76
+ size: 1024,
77
+ });
78
+ expect(entity.id).toBeDefined();
79
+ expect(entity.session_id).toBe(testSessionId);
80
+ expect(entity.type).toBe('file');
81
+ expect(entity.name).toBe('user.model.ts');
82
+ expect(entity.attributes).toEqual({ language: 'typescript', size: 1024 });
83
+ // Verify in database
84
+ const saved = db.prepare('SELECT * FROM entities WHERE id = ?').get(entity.id);
85
+ expect(saved).toBeDefined();
86
+ expect(saved.name).toBe('user.model.ts');
87
+ });
88
+ it('should find entity by name', () => {
89
+ const created = knowledgeGraph.createEntity(testSessionId, 'function', 'getUserById');
90
+ const found = knowledgeGraph.findEntity(testSessionId, 'getUserById');
91
+ expect(found).toBeDefined();
92
+ expect(found?.id).toBe(created.id);
93
+ expect(found?.type).toBe('function');
94
+ });
95
+ it('should find entity by name and type', () => {
96
+ knowledgeGraph.createEntity(testSessionId, 'function', 'test');
97
+ knowledgeGraph.createEntity(testSessionId, 'class', 'test');
98
+ const func = knowledgeGraph.findEntity(testSessionId, 'test', 'function');
99
+ const cls = knowledgeGraph.findEntity(testSessionId, 'test', 'class');
100
+ expect(func?.type).toBe('function');
101
+ expect(cls?.type).toBe('class');
102
+ expect(func?.id).not.toBe(cls?.id);
103
+ });
104
+ it('should get entities by type', () => {
105
+ knowledgeGraph.createEntity(testSessionId, 'file', 'index.ts');
106
+ knowledgeGraph.createEntity(testSessionId, 'file', 'utils.ts');
107
+ knowledgeGraph.createEntity(testSessionId, 'function', 'main');
108
+ const files = knowledgeGraph.getEntitiesByType(testSessionId, 'file');
109
+ expect(files).toHaveLength(2);
110
+ expect(files.every(f => f.type === 'file')).toBe(true);
111
+ });
112
+ });
113
+ describe('Relation operations', () => {
114
+ it('should create a relation between entities', () => {
115
+ const file = knowledgeGraph.createEntity(testSessionId, 'file', 'user.ts');
116
+ const func = knowledgeGraph.createEntity(testSessionId, 'function', 'createUser');
117
+ const relation = knowledgeGraph.createRelation(testSessionId, file.id, 'contains', func.id, 0.9);
118
+ expect(relation.subject_id).toBe(file.id);
119
+ expect(relation.object_id).toBe(func.id);
120
+ expect(relation.predicate).toBe('contains');
121
+ expect(relation.confidence).toBe(0.9);
122
+ });
123
+ it('should get relations for an entity', () => {
124
+ const class1 = knowledgeGraph.createEntity(testSessionId, 'class', 'User');
125
+ const class2 = knowledgeGraph.createEntity(testSessionId, 'class', 'BaseModel');
126
+ const interface1 = knowledgeGraph.createEntity(testSessionId, 'interface', 'IUser');
127
+ knowledgeGraph.createRelation(testSessionId, class1.id, 'extends', class2.id);
128
+ knowledgeGraph.createRelation(testSessionId, class1.id, 'implements', interface1.id);
129
+ const subjectRelations = knowledgeGraph.getRelations(class1.id, 'subject');
130
+ expect(subjectRelations).toHaveLength(2);
131
+ const objectRelations = knowledgeGraph.getRelations(class1.id, 'object');
132
+ expect(objectRelations).toHaveLength(0);
133
+ const allRelations = knowledgeGraph.getRelations(class1.id, 'both');
134
+ expect(allRelations).toHaveLength(2);
135
+ });
136
+ });
137
+ describe('Observation operations', () => {
138
+ it('should add observations to entities', () => {
139
+ const entity = knowledgeGraph.createEntity(testSessionId, 'function', 'calculateTotal');
140
+ const _obs1 = knowledgeGraph.addObservation(entity.id, 'Has high cyclomatic complexity', 'code-analysis');
141
+ const _obs2 = knowledgeGraph.addObservation(entity.id, 'Called 50 times in tests', 'test-coverage');
142
+ const observations = knowledgeGraph.getObservations(entity.id);
143
+ expect(observations).toHaveLength(2);
144
+ // Check both observations exist, order may vary
145
+ const observationTexts = observations.map(o => o.observation);
146
+ expect(observationTexts).toContain('Called 50 times in tests');
147
+ expect(observationTexts).toContain('Has high cyclomatic complexity');
148
+ });
149
+ });
150
+ describe('Graph traversal', () => {
151
+ it('should find connected entities', () => {
152
+ // Create a simple graph: A -> B -> C
153
+ // -> D
154
+ const a = knowledgeGraph.createEntity(testSessionId, 'module', 'A');
155
+ const b = knowledgeGraph.createEntity(testSessionId, 'module', 'B');
156
+ const c = knowledgeGraph.createEntity(testSessionId, 'module', 'C');
157
+ const d = knowledgeGraph.createEntity(testSessionId, 'module', 'D');
158
+ knowledgeGraph.createRelation(testSessionId, a.id, 'imports', b.id);
159
+ knowledgeGraph.createRelation(testSessionId, b.id, 'imports', c.id);
160
+ knowledgeGraph.createRelation(testSessionId, b.id, 'imports', d.id);
161
+ const connected = knowledgeGraph.getConnectedEntities(a.id, 2);
162
+ expect(connected.size).toBe(4); // A, B, C, D
163
+ const connectedDepth1 = knowledgeGraph.getConnectedEntities(a.id, 1);
164
+ expect(connectedDepth1.size).toBe(2); // A, B
165
+ });
166
+ it('should handle circular relationships', () => {
167
+ // Create circular graph: A -> B -> C -> A
168
+ const a = knowledgeGraph.createEntity(testSessionId, 'class', 'A');
169
+ const b = knowledgeGraph.createEntity(testSessionId, 'class', 'B');
170
+ const c = knowledgeGraph.createEntity(testSessionId, 'class', 'C');
171
+ knowledgeGraph.createRelation(testSessionId, a.id, 'uses', b.id);
172
+ knowledgeGraph.createRelation(testSessionId, b.id, 'uses', c.id);
173
+ knowledgeGraph.createRelation(testSessionId, c.id, 'uses', a.id);
174
+ const connected = knowledgeGraph.getConnectedEntities(a.id, 10);
175
+ expect(connected.size).toBe(3); // Should not infinite loop
176
+ });
177
+ });
178
+ describe('Context analysis', () => {
179
+ it('should extract file entities', () => {
180
+ const text = `
181
+ Working on file user.model.ts which imports from auth.service.ts.
182
+ The component called "UserList.tsx" needs to be updated.
183
+ `;
184
+ const analysis = knowledgeGraph.analyzeContext(testSessionId, text);
185
+ expect(analysis.entities).toContainEqual({
186
+ type: 'file',
187
+ name: 'user.model.ts',
188
+ confidence: 0.9,
189
+ });
190
+ expect(analysis.entities).toContainEqual({
191
+ type: 'file',
192
+ name: 'auth.service.ts',
193
+ confidence: 0.9,
194
+ });
195
+ expect(analysis.entities).toContainEqual({
196
+ type: 'file',
197
+ name: 'UserList.tsx',
198
+ confidence: 0.9,
199
+ });
200
+ });
201
+ it('should extract function entities', () => {
202
+ const text = `
203
+ The function getUserById needs refactoring.
204
+ Also, const calculateTotal = (items) => { ... }
205
+ let processData = function() { ... }
206
+ `;
207
+ const analysis = knowledgeGraph.analyzeContext(testSessionId, text);
208
+ const functions = analysis.entities.filter(e => e.type === 'function');
209
+ expect(functions).toHaveLength(3);
210
+ expect(functions.map(f => f.name)).toContain('getUserById');
211
+ expect(functions.map(f => f.name)).toContain('calculateTotal');
212
+ expect(functions.map(f => f.name)).toContain('processData');
213
+ });
214
+ it('should extract class entities', () => {
215
+ const text = `
216
+ class UserModel extends BaseModel { }
217
+ interface IAuthService { }
218
+ type UserRole = 'admin' | 'user';
219
+ `;
220
+ const analysis = knowledgeGraph.analyzeContext(testSessionId, text);
221
+ const classes = analysis.entities.filter(e => e.type === 'class');
222
+ expect(classes.map(c => c.name)).toContain('UserModel');
223
+ expect(classes.map(c => c.name)).toContain('IAuthService');
224
+ expect(classes.map(c => c.name)).toContain('UserRole');
225
+ });
226
+ it('should extract relationships', () => {
227
+ const text = `
228
+ UserService calls validateUser function.
229
+ The AuthModule uses TokenService.
230
+ UserModel implements IUser interface.
231
+ `;
232
+ const analysis = knowledgeGraph.analyzeContext(testSessionId, text);
233
+ expect(analysis.relations).toContainEqual({
234
+ subject: 'UserService',
235
+ predicate: 'calls',
236
+ object: 'validateUser',
237
+ confidence: 0.7,
238
+ });
239
+ expect(analysis.relations).toContainEqual({
240
+ subject: 'UserModel',
241
+ predicate: 'implements',
242
+ object: 'IUser',
243
+ confidence: 0.8,
244
+ });
245
+ });
246
+ });
247
+ describe('Visualization support', () => {
248
+ it('should generate graph data', () => {
249
+ // Create some entities and relations
250
+ const user = knowledgeGraph.createEntity(testSessionId, 'class', 'User');
251
+ const auth = knowledgeGraph.createEntity(testSessionId, 'service', 'AuthService');
252
+ const db = knowledgeGraph.createEntity(testSessionId, 'database', 'UserDB');
253
+ knowledgeGraph.createRelation(testSessionId, auth.id, 'validates', user.id, 0.9);
254
+ knowledgeGraph.createRelation(testSessionId, auth.id, 'queries', db.id, 0.8);
255
+ const graphData = knowledgeGraph.getGraphData(testSessionId);
256
+ expect(graphData.nodes).toHaveLength(3);
257
+ expect(graphData.edges).toHaveLength(2);
258
+ expect(graphData.nodes.map(n => n.name)).toContain('User');
259
+ expect(graphData.nodes.map(n => n.name)).toContain('AuthService');
260
+ expect(graphData.nodes.map(n => n.name)).toContain('UserDB');
261
+ expect(graphData.edges).toContainEqual({
262
+ source: auth.id,
263
+ target: user.id,
264
+ predicate: 'validates',
265
+ confidence: 0.9,
266
+ });
267
+ });
268
+ it('should filter graph data by entity types', () => {
269
+ knowledgeGraph.createEntity(testSessionId, 'class', 'User');
270
+ knowledgeGraph.createEntity(testSessionId, 'class', 'Product');
271
+ knowledgeGraph.createEntity(testSessionId, 'service', 'AuthService');
272
+ knowledgeGraph.createEntity(testSessionId, 'database', 'UserDB');
273
+ const classesOnly = knowledgeGraph.getGraphData(testSessionId, ['class']);
274
+ expect(classesOnly.nodes).toHaveLength(2);
275
+ expect(classesOnly.nodes.every(n => n.type === 'class')).toBe(true);
276
+ });
277
+ });
278
+ describe('Cleanup operations', () => {
279
+ it('should prune orphaned entities', () => {
280
+ // Create entities with relations
281
+ const a = knowledgeGraph.createEntity(testSessionId, 'module', 'A');
282
+ const b = knowledgeGraph.createEntity(testSessionId, 'module', 'B');
283
+ knowledgeGraph.createRelation(testSessionId, a.id, 'imports', b.id);
284
+ // Create orphaned entities
285
+ knowledgeGraph.createEntity(testSessionId, 'module', 'Orphan1');
286
+ knowledgeGraph.createEntity(testSessionId, 'module', 'Orphan2');
287
+ // Entity with observation is not orphaned
288
+ const withObs = knowledgeGraph.createEntity(testSessionId, 'module', 'WithObservation');
289
+ knowledgeGraph.addObservation(withObs.id, 'Important note');
290
+ const pruned = knowledgeGraph.pruneOrphanedEntities(testSessionId);
291
+ expect(pruned).toBe(2); // Orphan1 and Orphan2
292
+ // Verify they were deleted
293
+ const remaining = knowledgeGraph.getEntitiesByType(testSessionId, 'module');
294
+ expect(remaining).toHaveLength(3); // A, B, WithObservation
295
+ });
296
+ });
297
+ });