@smallironman/mcp-memory-keeper 0.12.2-fork1

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 (110) hide show
  1. package/CHANGELOG.md +542 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1281 -0
  4. package/bin/mcp-memory-keeper +54 -0
  5. package/dist/__tests__/e2e/issue33-reproduce.test.js +234 -0
  6. package/dist/__tests__/e2e/server-e2e.test.js +341 -0
  7. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  8. package/dist/__tests__/helpers/test-server.js +92 -0
  9. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  10. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  11. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  12. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  13. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  14. package/dist/__tests__/integration/channels.test.js +376 -0
  15. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  16. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  17. package/dist/__tests__/integration/context-operations.test.js +243 -0
  18. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  19. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  20. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  21. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  22. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  23. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  24. package/dist/__tests__/integration/contextSearch.test.js +1054 -0
  25. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  26. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  27. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  28. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  29. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  30. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  31. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  32. package/dist/__tests__/integration/error-cases.test.js +411 -0
  33. package/dist/__tests__/integration/export-import.test.js +367 -0
  34. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  35. package/dist/__tests__/integration/file-operations.test.js +264 -0
  36. package/dist/__tests__/integration/filterBySessionId.test.js +251 -0
  37. package/dist/__tests__/integration/git-integration.test.js +241 -0
  38. package/dist/__tests__/integration/index-tools.test.js +496 -0
  39. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  40. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  41. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  42. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  43. package/dist/__tests__/integration/issue24-final-fix.test.js +241 -0
  44. package/dist/__tests__/integration/issue24-fix-validation.test.js +158 -0
  45. package/dist/__tests__/integration/issue24-reproduce.test.js +225 -0
  46. package/dist/__tests__/integration/issue24-token-limit.test.js +199 -0
  47. package/dist/__tests__/integration/issue33-array-items-schema.test.js +165 -0
  48. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  49. package/dist/__tests__/integration/migrations.test.js +528 -0
  50. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  51. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  52. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  53. package/dist/__tests__/integration/project-directory.test.js +291 -0
  54. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  55. package/dist/__tests__/integration/retention.test.js +513 -0
  56. package/dist/__tests__/integration/search.test.js +333 -0
  57. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  58. package/dist/__tests__/integration/server-initialization.test.js +305 -0
  59. package/dist/__tests__/integration/session-management.test.js +219 -0
  60. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  61. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  62. package/dist/__tests__/integration/summarization.test.js +308 -0
  63. package/dist/__tests__/integration/tokenLimitEnforcement.test.js +134 -0
  64. package/dist/__tests__/integration/tool-profiles-integration.test.js +150 -0
  65. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  66. package/dist/__tests__/security/input-validation.test.js +115 -0
  67. package/dist/__tests__/utils/agents.test.js +473 -0
  68. package/dist/__tests__/utils/database.test.js +177 -0
  69. package/dist/__tests__/utils/git.test.js +122 -0
  70. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  71. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  72. package/dist/__tests__/utils/project-directory-messages.test.js +192 -0
  73. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  74. package/dist/__tests__/utils/token-limits.test.js +225 -0
  75. package/dist/__tests__/utils/tool-profiles.test.js +374 -0
  76. package/dist/__tests__/utils/validation.test.js +200 -0
  77. package/dist/__tests__/utils/vector-store.test.js +231 -0
  78. package/dist/handlers/contextWatchHandlers.js +206 -0
  79. package/dist/index.js +4425 -0
  80. package/dist/migrations/003_add_channels.js +174 -0
  81. package/dist/migrations/004_add_context_watch.js +151 -0
  82. package/dist/migrations/005_add_context_watch.js +98 -0
  83. package/dist/migrations/simplify-sharing.js +117 -0
  84. package/dist/repositories/BaseRepository.js +30 -0
  85. package/dist/repositories/CheckpointRepository.js +140 -0
  86. package/dist/repositories/ContextRepository.js +2017 -0
  87. package/dist/repositories/FileRepository.js +104 -0
  88. package/dist/repositories/RepositoryManager.js +62 -0
  89. package/dist/repositories/SessionRepository.js +66 -0
  90. package/dist/repositories/WatcherRepository.js +252 -0
  91. package/dist/repositories/index.js +15 -0
  92. package/dist/test-helpers/database-helper.js +128 -0
  93. package/dist/types/entities.js +3 -0
  94. package/dist/utils/agents.js +791 -0
  95. package/dist/utils/channels.js +150 -0
  96. package/dist/utils/database.js +780 -0
  97. package/dist/utils/feature-flags.js +476 -0
  98. package/dist/utils/git.js +145 -0
  99. package/dist/utils/knowledge-graph.js +264 -0
  100. package/dist/utils/migrationHealthCheck.js +373 -0
  101. package/dist/utils/migrations.js +452 -0
  102. package/dist/utils/retention.js +460 -0
  103. package/dist/utils/timestamps.js +112 -0
  104. package/dist/utils/token-limits.js +350 -0
  105. package/dist/utils/tool-profiles.js +242 -0
  106. package/dist/utils/validation.js +296 -0
  107. package/dist/utils/vector-store.js +247 -0
  108. package/examples/config.json +31 -0
  109. package/examples/project-directory-setup.md +114 -0
  110. package/package.json +85 -0
@@ -0,0 +1,165 @@
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 globals_1 = require("@jest/globals");
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ /**
40
+ * Issue #33: full tool profile exposes a schema that breaks some OpenAI-compatible providers
41
+ *
42
+ * Some providers reject tool schemas where an array property lacks an `items` declaration.
43
+ * Per JSON Schema spec, `items` is required for `type: 'array'` to fully describe the schema.
44
+ *
45
+ * This test scans src/index.ts and verifies that every property with `type: 'array'`
46
+ * has an `items` declaration within the same schema block.
47
+ *
48
+ * @see https://github.com/mkreyman/mcp-memory-keeper/issues/33
49
+ */
50
+ /**
51
+ * Scan the source for tool definitions and find array properties missing `items`.
52
+ * Skips block comments to avoid false positives from commented-out schemas.
53
+ * Line numbers refer to the original source file for accurate debugging.
54
+ */
55
+ function findArrayPropertiesMissingItems(src) {
56
+ const violations = [];
57
+ const lines = src.split('\n');
58
+ let currentTool = '';
59
+ let inBlockComment = false;
60
+ for (let i = 0; i < lines.length; i++) {
61
+ const line = lines[i];
62
+ // Track block comment state
63
+ if (inBlockComment) {
64
+ if (line.includes('*/')) {
65
+ inBlockComment = false;
66
+ }
67
+ continue;
68
+ }
69
+ if (line.includes('/*')) {
70
+ inBlockComment = true;
71
+ if (line.includes('*/')) {
72
+ inBlockComment = false;
73
+ }
74
+ continue;
75
+ }
76
+ // Track which tool we're inside
77
+ const toolMatch = line.match(/name:\s*'(context_[a-z_]+)'/);
78
+ if (toolMatch) {
79
+ currentTool = toolMatch[1];
80
+ }
81
+ // Find array type declarations
82
+ if (!line.match(/type:\s*'array'/))
83
+ continue;
84
+ if (!currentTool)
85
+ continue;
86
+ // Find the property name (check current line first for single-line declarations)
87
+ let propertyName = '(unknown)';
88
+ const currentLinePropMatch = line.match(/(\w+)\s*:\s*\{/);
89
+ if (currentLinePropMatch) {
90
+ propertyName = currentLinePropMatch[1];
91
+ }
92
+ else {
93
+ for (let j = i - 1; j >= Math.max(0, i - 5); j--) {
94
+ const propMatch = lines[j].match(/(\w+)\s*:\s*\{/);
95
+ if (propMatch) {
96
+ propertyName = propMatch[1];
97
+ break;
98
+ }
99
+ }
100
+ }
101
+ // Check current line first (handles single-line array declarations)
102
+ let foundItems = line.includes('items');
103
+ let depth = 0;
104
+ for (let j = i + 1; !foundItems && j < Math.min(lines.length, i + 50); j++) {
105
+ const fwdLine = lines[j];
106
+ for (const ch of fwdLine) {
107
+ if (ch === '{')
108
+ depth++;
109
+ if (ch === '}')
110
+ depth--;
111
+ }
112
+ if (fwdLine.match(/^\s*items\s*:/)) {
113
+ foundItems = true;
114
+ break;
115
+ }
116
+ if (depth < 0)
117
+ break;
118
+ }
119
+ if (!foundItems) {
120
+ violations.push({
121
+ tool: currentTool,
122
+ property: propertyName,
123
+ line: i + 1,
124
+ });
125
+ }
126
+ }
127
+ return violations;
128
+ }
129
+ (0, globals_1.describe)('Issue #33: Array properties must declare items', () => {
130
+ const indexPath = path.join(__dirname, '..', '..', 'index.ts');
131
+ const src = fs.readFileSync(indexPath, 'utf-8');
132
+ (0, globals_1.it)('should find tool definitions in source', () => {
133
+ const toolNames = src.match(/name:\s*'context_[a-z_]+'/g);
134
+ (0, globals_1.expect)(toolNames).not.toBeNull();
135
+ (0, globals_1.expect)(toolNames.length).toBeGreaterThan(0);
136
+ });
137
+ (0, globals_1.it)('every array property in every tool schema must have an items declaration', () => {
138
+ const violations = findArrayPropertiesMissingItems(src);
139
+ (0, globals_1.expect)(violations).toHaveLength(0);
140
+ });
141
+ (0, globals_1.it)('context_delegate.input.insights specifically must have items', () => {
142
+ const lines = src.split('\n');
143
+ let inDelegate = false;
144
+ let insightsLine = -1;
145
+ for (let i = 0; i < lines.length; i++) {
146
+ if (lines[i].match(/name:\s*'context_delegate'/)) {
147
+ inDelegate = true;
148
+ }
149
+ if (inDelegate &&
150
+ i > 0 &&
151
+ lines[i].match(/name:\s*'context_/) &&
152
+ !lines[i].match(/context_delegate/)) {
153
+ break;
154
+ }
155
+ if (inDelegate && lines[i].match(/insights\s*:\s*\{/)) {
156
+ insightsLine = i;
157
+ break;
158
+ }
159
+ }
160
+ (0, globals_1.expect)(insightsLine).toBeGreaterThan(-1);
161
+ const insightsBlock = lines.slice(insightsLine, insightsLine + 5).join('\n');
162
+ (0, globals_1.expect)(insightsBlock).toContain("type: 'array'");
163
+ (0, globals_1.expect)(insightsBlock).toMatch(/items\s*:/);
164
+ });
165
+ });
@@ -0,0 +1,338 @@
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('Knowledge Graph Integration Tests', () => {
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-integration-${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, 'KG Integration Test', 'Testing knowledge graph integration');
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('context_analyze', () => {
73
+ beforeEach(() => {
74
+ // Add some context items to analyze
75
+ const items = [
76
+ {
77
+ key: 'task_auth',
78
+ value: 'Working on AuthService class that implements authentication using JWT tokens',
79
+ category: 'task',
80
+ },
81
+ {
82
+ key: 'decision_db',
83
+ value: 'UserModel extends BaseModel and uses PostgreSQL database',
84
+ category: 'decision',
85
+ },
86
+ {
87
+ key: 'progress_api',
88
+ value: 'The function validateToken calls checkExpiry and getUserById',
89
+ category: 'progress',
90
+ },
91
+ {
92
+ key: 'note_files',
93
+ value: 'Modified files: auth.service.ts, user.model.ts, and token.utils.ts',
94
+ category: 'note',
95
+ },
96
+ ];
97
+ items.forEach(item => {
98
+ db.prepare('INSERT INTO context_items (id, session_id, key, value, category) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, item.key, item.value, item.category);
99
+ });
100
+ });
101
+ it('should analyze all context items and extract entities', () => {
102
+ // Simulate context_analyze tool call
103
+ const items = db
104
+ .prepare('SELECT * FROM context_items WHERE session_id = ?')
105
+ .all(testSessionId);
106
+ let entitiesCreated = 0;
107
+ let _relationsCreated = 0;
108
+ for (const item of items) {
109
+ const analysis = knowledgeGraph.analyzeContext(testSessionId, item.value);
110
+ for (const entityData of analysis.entities) {
111
+ const existing = knowledgeGraph.findEntity(testSessionId, entityData.name, entityData.type);
112
+ if (!existing) {
113
+ knowledgeGraph.createEntity(testSessionId, entityData.type, entityData.name, {
114
+ confidence: entityData.confidence,
115
+ source: item.key,
116
+ });
117
+ entitiesCreated++;
118
+ }
119
+ }
120
+ for (const relationData of analysis.relations) {
121
+ const subject = knowledgeGraph.findEntity(testSessionId, relationData.subject);
122
+ const object = knowledgeGraph.findEntity(testSessionId, relationData.object);
123
+ if (subject && object) {
124
+ knowledgeGraph.createRelation(testSessionId, subject.id, relationData.predicate, object.id, relationData.confidence);
125
+ _relationsCreated++;
126
+ }
127
+ }
128
+ }
129
+ expect(entitiesCreated).toBeGreaterThan(0);
130
+ // Verify specific entities were created
131
+ expect(knowledgeGraph.findEntity(testSessionId, 'AuthService')).toBeDefined();
132
+ expect(knowledgeGraph.findEntity(testSessionId, 'auth.service.ts')).toBeDefined();
133
+ expect(knowledgeGraph.findEntity(testSessionId, 'validateToken')).toBeDefined();
134
+ });
135
+ it('should analyze specific categories only', () => {
136
+ const categories = ['task', 'decision'];
137
+ const items = db
138
+ .prepare(`SELECT * FROM context_items WHERE session_id = ? AND category IN (${categories.map(() => '?').join(',')})`)
139
+ .all(testSessionId, ...categories);
140
+ expect(items).toHaveLength(2);
141
+ for (const item of items) {
142
+ const analysis = knowledgeGraph.analyzeContext(testSessionId, item.value);
143
+ expect(analysis.entities.length).toBeGreaterThan(0);
144
+ }
145
+ });
146
+ });
147
+ describe('context_find_related', () => {
148
+ beforeEach(() => {
149
+ // Create a network of entities
150
+ const authService = knowledgeGraph.createEntity(testSessionId, 'class', 'AuthService');
151
+ const userModel = knowledgeGraph.createEntity(testSessionId, 'class', 'UserModel');
152
+ const tokenUtil = knowledgeGraph.createEntity(testSessionId, 'module', 'TokenUtil');
153
+ const database = knowledgeGraph.createEntity(testSessionId, 'database', 'PostgreSQL');
154
+ const validateFunc = knowledgeGraph.createEntity(testSessionId, 'function', 'validateToken');
155
+ // Create relations
156
+ knowledgeGraph.createRelation(testSessionId, authService.id, 'uses', userModel.id);
157
+ knowledgeGraph.createRelation(testSessionId, authService.id, 'imports', tokenUtil.id);
158
+ knowledgeGraph.createRelation(testSessionId, userModel.id, 'stores_in', database.id);
159
+ knowledgeGraph.createRelation(testSessionId, authService.id, 'contains', validateFunc.id);
160
+ // Add a context item that references AuthService
161
+ db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, 'auth_task', 'Working on AuthService authentication');
162
+ });
163
+ it('should find related entities by entity name', () => {
164
+ const entity = knowledgeGraph.findEntity(testSessionId, 'AuthService');
165
+ expect(entity).toBeDefined();
166
+ const connected = knowledgeGraph.getConnectedEntities(entity.id, 2);
167
+ expect(connected.size).toBeGreaterThan(1);
168
+ // Should include AuthService and its connections
169
+ const entities = Array.from(connected).map(id => {
170
+ const e = db.prepare('SELECT * FROM entities WHERE id = ?').get(id);
171
+ return e.name;
172
+ });
173
+ expect(entities).toContain('AuthService');
174
+ expect(entities).toContain('UserModel');
175
+ expect(entities).toContain('TokenUtil');
176
+ expect(entities).toContain('validateToken');
177
+ });
178
+ it('should find related entities by context key', () => {
179
+ const contextItem = db
180
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
181
+ .get(testSessionId, 'auth_task');
182
+ expect(contextItem).toBeDefined();
183
+ // Extract entity from context
184
+ const analysis = knowledgeGraph.analyzeContext(testSessionId, contextItem.value);
185
+ expect(analysis.entities.length).toBeGreaterThan(0);
186
+ const entity = knowledgeGraph.findEntity(testSessionId, 'AuthService');
187
+ expect(entity).toBeDefined();
188
+ });
189
+ it('should filter by relation types', () => {
190
+ const entity = knowledgeGraph.findEntity(testSessionId, 'AuthService');
191
+ const relations = knowledgeGraph.getRelations(entity.id);
192
+ const usesRelations = relations.filter(r => r.predicate === 'uses');
193
+ const importsRelations = relations.filter(r => r.predicate === 'imports');
194
+ expect(usesRelations.length).toBeGreaterThan(0);
195
+ expect(importsRelations.length).toBeGreaterThan(0);
196
+ });
197
+ });
198
+ describe('context_visualize', () => {
199
+ beforeEach(() => {
200
+ // Create entities for visualization
201
+ knowledgeGraph.createEntity(testSessionId, 'class', 'User');
202
+ knowledgeGraph.createEntity(testSessionId, 'class', 'Product');
203
+ knowledgeGraph.createEntity(testSessionId, 'service', 'AuthService');
204
+ // Create context items with different categories and times
205
+ const now = new Date();
206
+ for (let i = 0; i < 10; i++) {
207
+ const category = ['task', 'decision', 'progress'][i % 3];
208
+ const priority = ['high', 'normal', 'low'][i % 3];
209
+ db.prepare(`INSERT INTO context_items
210
+ (id, session_id, key, value, category, priority, created_at)
211
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run((0, uuid_1.v4)(), testSessionId, `item_${i}`, `Test item ${i}`, category, priority, new Date(now.getTime() - i * 3600000).toISOString() // 1 hour apart
212
+ );
213
+ }
214
+ });
215
+ it('should generate graph visualization data', () => {
216
+ const user = knowledgeGraph.findEntity(testSessionId, 'User');
217
+ const auth = knowledgeGraph.findEntity(testSessionId, 'AuthService');
218
+ knowledgeGraph.createRelation(testSessionId, auth.id, 'validates', user.id);
219
+ const graphData = knowledgeGraph.getGraphData(testSessionId);
220
+ expect(graphData.nodes.length).toBeGreaterThan(0);
221
+ expect(graphData.edges.length).toBeGreaterThan(0);
222
+ // Verify structure
223
+ expect(graphData.nodes[0]).toHaveProperty('id');
224
+ expect(graphData.nodes[0]).toHaveProperty('type');
225
+ expect(graphData.nodes[0]).toHaveProperty('name');
226
+ expect(graphData.edges[0]).toHaveProperty('source');
227
+ expect(graphData.edges[0]).toHaveProperty('target');
228
+ expect(graphData.edges[0]).toHaveProperty('predicate');
229
+ });
230
+ it('should generate timeline visualization data', () => {
231
+ const timeline = db
232
+ .prepare(`
233
+ SELECT
234
+ strftime('%Y-%m-%d %H:00', created_at) as hour,
235
+ COUNT(*) as events,
236
+ GROUP_CONCAT(DISTINCT category) as categories
237
+ FROM context_items
238
+ WHERE session_id = ?
239
+ GROUP BY hour
240
+ ORDER BY hour DESC
241
+ LIMIT 24
242
+ `)
243
+ .all(testSessionId);
244
+ expect(timeline.length).toBeGreaterThan(0);
245
+ expect(timeline[0]).toHaveProperty('hour');
246
+ expect(timeline[0]).toHaveProperty('events');
247
+ expect(timeline[0]).toHaveProperty('categories');
248
+ });
249
+ it('should generate heatmap visualization data', () => {
250
+ const heatmap = db
251
+ .prepare(`
252
+ SELECT
253
+ category,
254
+ priority,
255
+ COUNT(*) as count
256
+ FROM context_items
257
+ WHERE session_id = ?
258
+ GROUP BY category, priority
259
+ `)
260
+ .all(testSessionId);
261
+ expect(heatmap.length).toBeGreaterThan(0);
262
+ expect(heatmap[0]).toHaveProperty('category');
263
+ expect(heatmap[0]).toHaveProperty('priority');
264
+ expect(heatmap[0]).toHaveProperty('count');
265
+ // Verify we have data for different category/priority combinations
266
+ const categories = new Set(heatmap.map(h => h.category));
267
+ const priorities = new Set(heatmap.map(h => h.priority));
268
+ expect(categories.size).toBeGreaterThanOrEqual(2);
269
+ expect(priorities.size).toBeGreaterThanOrEqual(2);
270
+ });
271
+ });
272
+ describe('Complex scenarios', () => {
273
+ it('should handle code analysis workflow', () => {
274
+ // Simulate analyzing a codebase
275
+ const codeContext = [
276
+ 'The AuthController class handles authentication endpoints',
277
+ 'It uses AuthService which implements IAuthService interface',
278
+ 'AuthService calls UserRepository to fetch user data',
279
+ 'The validateToken function uses jwt.verify from jsonwebtoken library',
280
+ 'Files involved: auth.controller.ts, auth.service.ts, user.repository.ts',
281
+ ];
282
+ // Analyze each piece of context
283
+ const allEntities = new Set();
284
+ const allRelations = [];
285
+ codeContext.forEach(text => {
286
+ const analysis = knowledgeGraph.analyzeContext(testSessionId, text);
287
+ analysis.entities.forEach(e => {
288
+ if (!knowledgeGraph.findEntity(testSessionId, e.name, e.type)) {
289
+ knowledgeGraph.createEntity(testSessionId, e.type, e.name);
290
+ allEntities.add(e.name);
291
+ }
292
+ });
293
+ analysis.relations.forEach(r => {
294
+ const subject = knowledgeGraph.findEntity(testSessionId, r.subject);
295
+ const object = knowledgeGraph.findEntity(testSessionId, r.object);
296
+ if (subject && object) {
297
+ knowledgeGraph.createRelation(testSessionId, subject.id, r.predicate, object.id, r.confidence);
298
+ allRelations.push(r);
299
+ }
300
+ });
301
+ });
302
+ // Verify entities were extracted
303
+ expect(allEntities.size).toBeGreaterThan(5);
304
+ expect(allEntities).toContain('AuthController');
305
+ expect(allEntities).toContain('AuthService');
306
+ expect(allEntities).toContain('auth.controller.ts');
307
+ // Verify relationships
308
+ expect(allRelations.length).toBeGreaterThan(0);
309
+ // Test finding related entities
310
+ const authService = knowledgeGraph.findEntity(testSessionId, 'AuthService');
311
+ if (authService) {
312
+ const connected = knowledgeGraph.getConnectedEntities(authService.id, 2);
313
+ expect(connected.size).toBeGreaterThan(1);
314
+ }
315
+ });
316
+ it('should maintain graph consistency across operations', () => {
317
+ // Create initial graph
318
+ const module1 = knowledgeGraph.createEntity(testSessionId, 'module', 'CoreModule');
319
+ const module2 = knowledgeGraph.createEntity(testSessionId, 'module', 'UtilsModule');
320
+ const service = knowledgeGraph.createEntity(testSessionId, 'service', 'DataService');
321
+ knowledgeGraph.createRelation(testSessionId, module1.id, 'imports', module2.id);
322
+ knowledgeGraph.createRelation(testSessionId, module1.id, 'provides', service.id);
323
+ // Add observations
324
+ knowledgeGraph.addObservation(service.id, 'Handles data transformation');
325
+ // Verify graph integrity
326
+ const graphData = knowledgeGraph.getGraphData(testSessionId);
327
+ expect(graphData.nodes).toHaveLength(3);
328
+ expect(graphData.edges).toHaveLength(2);
329
+ // Test pruning doesn't remove entities with relations
330
+ const pruned = knowledgeGraph.pruneOrphanedEntities(testSessionId);
331
+ expect(pruned).toBe(0);
332
+ // All entities should still exist
333
+ expect(knowledgeGraph.findEntity(testSessionId, 'CoreModule')).toBeDefined();
334
+ expect(knowledgeGraph.findEntity(testSessionId, 'UtilsModule')).toBeDefined();
335
+ expect(knowledgeGraph.findEntity(testSessionId, 'DataService')).toBeDefined();
336
+ });
337
+ });
338
+ });