@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.
- package/CHANGELOG.md +542 -0
- package/LICENSE +21 -0
- package/README.md +1281 -0
- package/bin/mcp-memory-keeper +54 -0
- package/dist/__tests__/e2e/issue33-reproduce.test.js +234 -0
- package/dist/__tests__/e2e/server-e2e.test.js +341 -0
- package/dist/__tests__/helpers/database-test-helper.js +160 -0
- package/dist/__tests__/helpers/test-server.js +92 -0
- package/dist/__tests__/integration/advanced-features.test.js +614 -0
- package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
- package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
- package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
- package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
- package/dist/__tests__/integration/channels.test.js +376 -0
- package/dist/__tests__/integration/checkpoint.test.js +251 -0
- package/dist/__tests__/integration/concurrent-access.test.js +190 -0
- package/dist/__tests__/integration/context-operations.test.js +243 -0
- package/dist/__tests__/integration/contextDiff.test.js +852 -0
- package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
- package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
- package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
- package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
- package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
- package/dist/__tests__/integration/contextSearch.test.js +1054 -0
- package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
- package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
- package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
- package/dist/__tests__/integration/database-initialization.test.js +134 -0
- package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
- package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
- package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
- package/dist/__tests__/integration/error-cases.test.js +411 -0
- package/dist/__tests__/integration/export-import.test.js +367 -0
- package/dist/__tests__/integration/feature-flags.test.js +542 -0
- package/dist/__tests__/integration/file-operations.test.js +264 -0
- package/dist/__tests__/integration/filterBySessionId.test.js +251 -0
- package/dist/__tests__/integration/git-integration.test.js +241 -0
- package/dist/__tests__/integration/index-tools.test.js +496 -0
- package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
- package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
- package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
- package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
- package/dist/__tests__/integration/issue24-final-fix.test.js +241 -0
- package/dist/__tests__/integration/issue24-fix-validation.test.js +158 -0
- package/dist/__tests__/integration/issue24-reproduce.test.js +225 -0
- package/dist/__tests__/integration/issue24-token-limit.test.js +199 -0
- package/dist/__tests__/integration/issue33-array-items-schema.test.js +165 -0
- package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
- package/dist/__tests__/integration/migrations.test.js +528 -0
- package/dist/__tests__/integration/multi-agent.test.js +546 -0
- package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
- package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
- package/dist/__tests__/integration/project-directory.test.js +291 -0
- package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
- package/dist/__tests__/integration/retention.test.js +513 -0
- package/dist/__tests__/integration/search.test.js +333 -0
- package/dist/__tests__/integration/semantic-search.test.js +266 -0
- package/dist/__tests__/integration/server-initialization.test.js +305 -0
- package/dist/__tests__/integration/session-management.test.js +219 -0
- package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
- package/dist/__tests__/integration/smart-compaction.test.js +230 -0
- package/dist/__tests__/integration/summarization.test.js +308 -0
- package/dist/__tests__/integration/tokenLimitEnforcement.test.js +134 -0
- package/dist/__tests__/integration/tool-profiles-integration.test.js +150 -0
- package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
- package/dist/__tests__/security/input-validation.test.js +115 -0
- package/dist/__tests__/utils/agents.test.js +473 -0
- package/dist/__tests__/utils/database.test.js +177 -0
- package/dist/__tests__/utils/git.test.js +122 -0
- package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
- package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
- package/dist/__tests__/utils/project-directory-messages.test.js +192 -0
- package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
- package/dist/__tests__/utils/token-limits.test.js +225 -0
- package/dist/__tests__/utils/tool-profiles.test.js +374 -0
- package/dist/__tests__/utils/validation.test.js +200 -0
- package/dist/__tests__/utils/vector-store.test.js +231 -0
- package/dist/handlers/contextWatchHandlers.js +206 -0
- package/dist/index.js +4425 -0
- package/dist/migrations/003_add_channels.js +174 -0
- package/dist/migrations/004_add_context_watch.js +151 -0
- package/dist/migrations/005_add_context_watch.js +98 -0
- package/dist/migrations/simplify-sharing.js +117 -0
- package/dist/repositories/BaseRepository.js +30 -0
- package/dist/repositories/CheckpointRepository.js +140 -0
- package/dist/repositories/ContextRepository.js +2017 -0
- package/dist/repositories/FileRepository.js +104 -0
- package/dist/repositories/RepositoryManager.js +62 -0
- package/dist/repositories/SessionRepository.js +66 -0
- package/dist/repositories/WatcherRepository.js +252 -0
- package/dist/repositories/index.js +15 -0
- package/dist/test-helpers/database-helper.js +128 -0
- package/dist/types/entities.js +3 -0
- package/dist/utils/agents.js +791 -0
- package/dist/utils/channels.js +150 -0
- package/dist/utils/database.js +780 -0
- package/dist/utils/feature-flags.js +476 -0
- package/dist/utils/git.js +145 -0
- package/dist/utils/knowledge-graph.js +264 -0
- package/dist/utils/migrationHealthCheck.js +373 -0
- package/dist/utils/migrations.js +452 -0
- package/dist/utils/retention.js +460 -0
- package/dist/utils/timestamps.js +112 -0
- package/dist/utils/token-limits.js +350 -0
- package/dist/utils/tool-profiles.js +242 -0
- package/dist/utils/validation.js +296 -0
- package/dist/utils/vector-store.js +247 -0
- package/examples/config.json +31 -0
- package/examples/project-directory-setup.md +114 -0
- package/package.json +85 -0
|
@@ -0,0 +1,308 @@
|
|
|
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 os = __importStar(require("os"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const uuid_1 = require("uuid");
|
|
41
|
+
describe('Summarization Integration Tests', () => {
|
|
42
|
+
let dbManager;
|
|
43
|
+
let tempDbPath;
|
|
44
|
+
let db;
|
|
45
|
+
let testSessionId;
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
tempDbPath = path.join(os.tmpdir(), `test-summary-${Date.now()}.db`);
|
|
48
|
+
dbManager = new database_1.DatabaseManager({
|
|
49
|
+
filename: tempDbPath,
|
|
50
|
+
maxSize: 10 * 1024 * 1024,
|
|
51
|
+
walMode: true,
|
|
52
|
+
});
|
|
53
|
+
db = dbManager.getDatabase();
|
|
54
|
+
// Create test session
|
|
55
|
+
testSessionId = (0, uuid_1.v4)();
|
|
56
|
+
db.prepare('INSERT INTO sessions (id, name, description) VALUES (?, ?, ?)').run(testSessionId, 'Summary Test Session', 'Testing summarization functionality');
|
|
57
|
+
});
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
dbManager.close();
|
|
60
|
+
try {
|
|
61
|
+
fs.unlinkSync(tempDbPath);
|
|
62
|
+
fs.unlinkSync(`${tempDbPath}-wal`);
|
|
63
|
+
fs.unlinkSync(`${tempDbPath}-shm`);
|
|
64
|
+
}
|
|
65
|
+
catch (_e) {
|
|
66
|
+
// Ignore
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
describe('context_summarize', () => {
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
// Add diverse test data
|
|
72
|
+
const items = [
|
|
73
|
+
// Tasks
|
|
74
|
+
{
|
|
75
|
+
key: 'task_1',
|
|
76
|
+
value: 'Implement authentication system',
|
|
77
|
+
category: 'task',
|
|
78
|
+
priority: 'high',
|
|
79
|
+
},
|
|
80
|
+
{ key: 'task_2', value: 'Add rate limiting to API', category: 'task', priority: 'high' },
|
|
81
|
+
{
|
|
82
|
+
key: 'task_3',
|
|
83
|
+
value: 'Write unit tests for auth module',
|
|
84
|
+
category: 'task',
|
|
85
|
+
priority: 'normal',
|
|
86
|
+
},
|
|
87
|
+
{ key: 'task_4', value: 'Update documentation', category: 'task', priority: 'low' },
|
|
88
|
+
// Decisions
|
|
89
|
+
{
|
|
90
|
+
key: 'decision_1',
|
|
91
|
+
value: 'Use JWT for authentication tokens',
|
|
92
|
+
category: 'decision',
|
|
93
|
+
priority: 'high',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
key: 'decision_2',
|
|
97
|
+
value: 'Set token expiry to 24 hours',
|
|
98
|
+
category: 'decision',
|
|
99
|
+
priority: 'normal',
|
|
100
|
+
},
|
|
101
|
+
// Progress
|
|
102
|
+
{
|
|
103
|
+
key: 'progress_1',
|
|
104
|
+
value: 'Completed login endpoint',
|
|
105
|
+
category: 'progress',
|
|
106
|
+
priority: 'normal',
|
|
107
|
+
},
|
|
108
|
+
{ key: 'progress_2', value: 'Fixed CORS issues', category: 'progress', priority: 'normal' },
|
|
109
|
+
// Notes
|
|
110
|
+
{
|
|
111
|
+
key: 'note_1',
|
|
112
|
+
value: 'Redis connection string: redis://localhost:6379',
|
|
113
|
+
category: 'note',
|
|
114
|
+
priority: 'normal',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
key: 'note_2',
|
|
118
|
+
value: 'API rate limit: 100 requests per minute',
|
|
119
|
+
category: 'note',
|
|
120
|
+
priority: 'normal',
|
|
121
|
+
},
|
|
122
|
+
// Warnings
|
|
123
|
+
{
|
|
124
|
+
key: 'warning_1',
|
|
125
|
+
value: 'Deprecation warning in auth library',
|
|
126
|
+
category: 'warning',
|
|
127
|
+
priority: 'high',
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
items.forEach(item => {
|
|
131
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority) VALUES (?, ?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, item.key, item.value, item.category, item.priority);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
it('should summarize all items in session', () => {
|
|
135
|
+
const items = db
|
|
136
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? ORDER BY priority DESC, created_at DESC')
|
|
137
|
+
.all(testSessionId);
|
|
138
|
+
expect(items).toHaveLength(11);
|
|
139
|
+
// Group by category
|
|
140
|
+
const grouped = items.reduce((acc, item) => {
|
|
141
|
+
if (!acc[item.category])
|
|
142
|
+
acc[item.category] = [];
|
|
143
|
+
acc[item.category].push(item);
|
|
144
|
+
return acc;
|
|
145
|
+
}, {});
|
|
146
|
+
expect(Object.keys(grouped)).toHaveLength(5);
|
|
147
|
+
expect(grouped.task).toHaveLength(4);
|
|
148
|
+
expect(grouped.decision).toHaveLength(2);
|
|
149
|
+
expect(grouped.progress).toHaveLength(2);
|
|
150
|
+
expect(grouped.note).toHaveLength(2);
|
|
151
|
+
expect(grouped.warning).toHaveLength(1);
|
|
152
|
+
});
|
|
153
|
+
it('should filter by categories', () => {
|
|
154
|
+
const categories = ['task', 'decision'];
|
|
155
|
+
const items = db
|
|
156
|
+
.prepare(`SELECT * FROM context_items
|
|
157
|
+
WHERE session_id = ?
|
|
158
|
+
AND category IN (${categories.map(() => '?').join(',')})
|
|
159
|
+
ORDER BY priority DESC, created_at DESC`)
|
|
160
|
+
.all(testSessionId, ...categories);
|
|
161
|
+
expect(items).toHaveLength(6); // 4 tasks + 2 decisions
|
|
162
|
+
expect(items.every((i) => categories.includes(i.category))).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
it('should limit summary length', () => {
|
|
165
|
+
// Add many items
|
|
166
|
+
for (let i = 0; i < 50; i++) {
|
|
167
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, category) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, `extra_${i}`, `Extra item ${i}`, 'note');
|
|
168
|
+
}
|
|
169
|
+
// Get limited summary
|
|
170
|
+
const limit = 20;
|
|
171
|
+
const items = db
|
|
172
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? ORDER BY priority DESC, created_at DESC LIMIT ?')
|
|
173
|
+
.all(testSessionId, limit);
|
|
174
|
+
expect(items).toHaveLength(limit);
|
|
175
|
+
});
|
|
176
|
+
it('should prioritize high priority items', () => {
|
|
177
|
+
const items = db
|
|
178
|
+
.prepare(`SELECT * FROM context_items
|
|
179
|
+
WHERE session_id = ?
|
|
180
|
+
ORDER BY
|
|
181
|
+
CASE priority
|
|
182
|
+
WHEN 'critical' THEN 1
|
|
183
|
+
WHEN 'high' THEN 2
|
|
184
|
+
WHEN 'normal' THEN 3
|
|
185
|
+
WHEN 'low' THEN 4
|
|
186
|
+
END,
|
|
187
|
+
created_at DESC`)
|
|
188
|
+
.all(testSessionId);
|
|
189
|
+
// Check that high priority items come first (after critical if any)
|
|
190
|
+
const highPriorityItems = items.filter((i) => i.priority === 'high');
|
|
191
|
+
const firstHighIndex = items.findIndex((i) => i.priority === 'high');
|
|
192
|
+
// Find last high index manually (findLastIndex not available in older Node)
|
|
193
|
+
let lastHighIndex = -1;
|
|
194
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
195
|
+
if (items[i].priority === 'high') {
|
|
196
|
+
lastHighIndex = i;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (highPriorityItems.length > 0) {
|
|
201
|
+
// All high priority items should be consecutive
|
|
202
|
+
const expectedLastIndex = firstHighIndex + highPriorityItems.length - 1;
|
|
203
|
+
expect(lastHighIndex).toBe(expectedLastIndex);
|
|
204
|
+
}
|
|
205
|
+
// Verify we have the expected high priority items
|
|
206
|
+
expect(highPriorityItems).toHaveLength(4); // 2 tasks + 1 decision + 1 warning
|
|
207
|
+
});
|
|
208
|
+
it('should generate summary with statistics', () => {
|
|
209
|
+
// Get statistics
|
|
210
|
+
const stats = db
|
|
211
|
+
.prepare(`SELECT
|
|
212
|
+
category,
|
|
213
|
+
priority,
|
|
214
|
+
COUNT(*) as count
|
|
215
|
+
FROM context_items
|
|
216
|
+
WHERE session_id = ?
|
|
217
|
+
GROUP BY category, priority`)
|
|
218
|
+
.all(testSessionId);
|
|
219
|
+
// Verify statistics
|
|
220
|
+
const taskHighCount = stats.find((s) => s.category === 'task' && s.priority === 'high')?.count;
|
|
221
|
+
expect(taskHighCount).toBe(2);
|
|
222
|
+
const totalCount = stats.reduce((sum, s) => sum + s.count, 0);
|
|
223
|
+
expect(totalCount).toBe(11);
|
|
224
|
+
});
|
|
225
|
+
it('should include session metadata in summary', () => {
|
|
226
|
+
const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(testSessionId);
|
|
227
|
+
expect(session.name).toBe('Summary Test Session');
|
|
228
|
+
expect(session.description).toBe('Testing summarization functionality');
|
|
229
|
+
expect(session.created_at).toBeDefined();
|
|
230
|
+
});
|
|
231
|
+
it('should handle empty sessions', () => {
|
|
232
|
+
// Create empty session
|
|
233
|
+
const emptySessionId = (0, uuid_1.v4)();
|
|
234
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(emptySessionId, 'Empty Session');
|
|
235
|
+
const items = db
|
|
236
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ?')
|
|
237
|
+
.all(emptySessionId);
|
|
238
|
+
expect(items).toHaveLength(0);
|
|
239
|
+
});
|
|
240
|
+
it('should generate category-specific summaries', () => {
|
|
241
|
+
// Tasks summary
|
|
242
|
+
const tasks = db
|
|
243
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND category = ? ORDER BY priority DESC')
|
|
244
|
+
.all(testSessionId, 'task');
|
|
245
|
+
const taskSummary = {
|
|
246
|
+
total: tasks.length,
|
|
247
|
+
high: tasks.filter((t) => t.priority === 'high').length,
|
|
248
|
+
normal: tasks.filter((t) => t.priority === 'normal').length,
|
|
249
|
+
low: tasks.filter((t) => t.priority === 'low').length,
|
|
250
|
+
items: tasks.slice(0, 5), // Top 5 tasks
|
|
251
|
+
};
|
|
252
|
+
expect(taskSummary.total).toBe(4);
|
|
253
|
+
expect(taskSummary.high).toBe(2);
|
|
254
|
+
expect(taskSummary.normal).toBe(1);
|
|
255
|
+
expect(taskSummary.low).toBe(1);
|
|
256
|
+
});
|
|
257
|
+
it('should format summary for AI consumption', () => {
|
|
258
|
+
const items = db
|
|
259
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? ORDER BY category, priority DESC')
|
|
260
|
+
.all(testSessionId);
|
|
261
|
+
// Format for AI
|
|
262
|
+
const summary = [];
|
|
263
|
+
let currentCategory = '';
|
|
264
|
+
items.forEach((item) => {
|
|
265
|
+
if (item.category !== currentCategory) {
|
|
266
|
+
currentCategory = item.category;
|
|
267
|
+
summary.push(`\n## ${currentCategory.toUpperCase()}`);
|
|
268
|
+
}
|
|
269
|
+
const priorityMarker = item.priority === 'high' ? '🔴' : item.priority === 'normal' ? '🟡' : '⚪';
|
|
270
|
+
summary.push(`${priorityMarker} ${item.key}: ${item.value}`);
|
|
271
|
+
});
|
|
272
|
+
const formattedSummary = summary.join('\n');
|
|
273
|
+
expect(formattedSummary).toContain('## TASK');
|
|
274
|
+
expect(formattedSummary).toContain('## DECISION');
|
|
275
|
+
expect(formattedSummary).toContain('🔴'); // High priority markers
|
|
276
|
+
});
|
|
277
|
+
it('should include temporal information in summary', () => {
|
|
278
|
+
// Get items with time info
|
|
279
|
+
const items = db
|
|
280
|
+
.prepare(`SELECT
|
|
281
|
+
*,
|
|
282
|
+
strftime('%Y-%m-%d %H:%M', created_at) as formatted_time
|
|
283
|
+
FROM context_items
|
|
284
|
+
WHERE session_id = ?
|
|
285
|
+
ORDER BY created_at DESC
|
|
286
|
+
LIMIT 5`)
|
|
287
|
+
.all(testSessionId);
|
|
288
|
+
expect(items).toHaveLength(5);
|
|
289
|
+
items.forEach((item) => {
|
|
290
|
+
expect(item.formatted_time).toBeDefined();
|
|
291
|
+
expect(item.formatted_time).toMatch(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}/);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
describe('Summary with file information', () => {
|
|
296
|
+
it('should include file cache statistics', () => {
|
|
297
|
+
// Add some files
|
|
298
|
+
for (let i = 0; i < 5; i++) {
|
|
299
|
+
db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, `/src/file${i}.ts`, `content${i}`, `hash${i}`);
|
|
300
|
+
}
|
|
301
|
+
const fileStats = db
|
|
302
|
+
.prepare('SELECT COUNT(*) as count, SUM(LENGTH(content)) as total_size FROM file_cache WHERE session_id = ?')
|
|
303
|
+
.get(testSessionId);
|
|
304
|
+
expect(fileStats.count).toBe(5);
|
|
305
|
+
expect(fileStats.total_size).toBeGreaterThan(0);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const globals_1 = require("@jest/globals");
|
|
4
|
+
// Helper functions from the main index.ts file
|
|
5
|
+
function estimateTokens(text) {
|
|
6
|
+
return Math.ceil(text.length / 4);
|
|
7
|
+
}
|
|
8
|
+
function calculateSafeItemCount(items, tokenLimit) {
|
|
9
|
+
if (items.length === 0)
|
|
10
|
+
return 0;
|
|
11
|
+
let safeCount = 0;
|
|
12
|
+
let currentTokens = 0;
|
|
13
|
+
// Include base response structure in token calculation
|
|
14
|
+
const baseResponse = {
|
|
15
|
+
items: [],
|
|
16
|
+
pagination: {
|
|
17
|
+
total: 0,
|
|
18
|
+
returned: 0,
|
|
19
|
+
offset: 0,
|
|
20
|
+
hasMore: false,
|
|
21
|
+
nextOffset: null,
|
|
22
|
+
totalCount: 0,
|
|
23
|
+
page: 1,
|
|
24
|
+
pageSize: 0,
|
|
25
|
+
totalPages: 1,
|
|
26
|
+
hasNextPage: false,
|
|
27
|
+
hasPreviousPage: false,
|
|
28
|
+
previousOffset: null,
|
|
29
|
+
totalSize: 0,
|
|
30
|
+
averageSize: 0,
|
|
31
|
+
defaultsApplied: {},
|
|
32
|
+
truncated: false,
|
|
33
|
+
truncatedCount: 0,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
// Estimate tokens for base response structure
|
|
37
|
+
const baseTokens = estimateTokens(JSON.stringify(baseResponse, null, 2));
|
|
38
|
+
currentTokens = baseTokens;
|
|
39
|
+
// Add items one by one until we approach the token limit
|
|
40
|
+
for (let i = 0; i < items.length; i++) {
|
|
41
|
+
const itemTokens = estimateTokens(JSON.stringify(items[i], null, 2));
|
|
42
|
+
// Leave some buffer (10%) to account for formatting and additional metadata
|
|
43
|
+
if (currentTokens + itemTokens > tokenLimit * 0.9) {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
currentTokens += itemTokens;
|
|
47
|
+
safeCount++;
|
|
48
|
+
}
|
|
49
|
+
// Always return at least 1 item if any exist, even if it exceeds limit
|
|
50
|
+
// This prevents infinite loops and ensures progress
|
|
51
|
+
return Math.max(safeCount, items.length > 0 ? 1 : 0);
|
|
52
|
+
}
|
|
53
|
+
(0, globals_1.describe)('Token Limit Enforcement Unit Tests', () => {
|
|
54
|
+
(0, globals_1.describe)('calculateSafeItemCount', () => {
|
|
55
|
+
(0, globals_1.it)('should return 0 for empty items array', () => {
|
|
56
|
+
const result = calculateSafeItemCount([], 20000);
|
|
57
|
+
(0, globals_1.expect)(result).toBe(0);
|
|
58
|
+
});
|
|
59
|
+
(0, globals_1.it)('should return at least 1 item if any exist', () => {
|
|
60
|
+
const largeItem = {
|
|
61
|
+
key: 'large.item',
|
|
62
|
+
value: 'X'.repeat(100000), // Very large item
|
|
63
|
+
category: 'test',
|
|
64
|
+
priority: 'high',
|
|
65
|
+
};
|
|
66
|
+
const result = calculateSafeItemCount([largeItem], 20000);
|
|
67
|
+
(0, globals_1.expect)(result).toBe(1);
|
|
68
|
+
});
|
|
69
|
+
(0, globals_1.it)('should truncate items when approaching token limit', () => {
|
|
70
|
+
// Create multiple medium-sized items
|
|
71
|
+
const items = [];
|
|
72
|
+
for (let i = 0; i < 50; i++) {
|
|
73
|
+
items.push({
|
|
74
|
+
key: `item.${i}`,
|
|
75
|
+
value: 'This is a medium-sized test value that contains enough text to trigger token limit enforcement when many items are returned together. '.repeat(20),
|
|
76
|
+
category: 'test',
|
|
77
|
+
priority: 'high',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const result = calculateSafeItemCount(items, 20000);
|
|
81
|
+
(0, globals_1.expect)(result).toBeLessThan(50);
|
|
82
|
+
(0, globals_1.expect)(result).toBeGreaterThan(0);
|
|
83
|
+
});
|
|
84
|
+
(0, globals_1.it)('should handle small items that all fit within limit', () => {
|
|
85
|
+
const items = [];
|
|
86
|
+
for (let i = 0; i < 10; i++) {
|
|
87
|
+
items.push({
|
|
88
|
+
key: `small.item.${i}`,
|
|
89
|
+
value: 'Small value',
|
|
90
|
+
category: 'test',
|
|
91
|
+
priority: 'high',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const result = calculateSafeItemCount(items, 20000);
|
|
95
|
+
(0, globals_1.expect)(result).toBe(10);
|
|
96
|
+
});
|
|
97
|
+
(0, globals_1.it)('should respect token limit with buffer', () => {
|
|
98
|
+
// Create items that would exceed token limit
|
|
99
|
+
const items = [];
|
|
100
|
+
const itemValue = 'X'.repeat(2000); // 2KB item that will definitely cause truncation
|
|
101
|
+
for (let i = 0; i < 100; i++) {
|
|
102
|
+
items.push({
|
|
103
|
+
key: `large.buffer.item.${i}`,
|
|
104
|
+
value: itemValue,
|
|
105
|
+
category: 'test',
|
|
106
|
+
priority: 'high',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const result = calculateSafeItemCount(items, 20000);
|
|
110
|
+
// Should be significantly less than all items due to token limits
|
|
111
|
+
(0, globals_1.expect)(result).toBeLessThan(100);
|
|
112
|
+
(0, globals_1.expect)(result).toBeGreaterThan(0);
|
|
113
|
+
// Verify that the result respects the buffer by checking actual tokens
|
|
114
|
+
const actualTokens = result * estimateTokens(JSON.stringify(items[0], null, 2));
|
|
115
|
+
(0, globals_1.expect)(actualTokens).toBeLessThan(20000 * 0.9); // Should be under 90% of limit
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
(0, globals_1.describe)('estimateTokens', () => {
|
|
119
|
+
(0, globals_1.it)('should estimate tokens correctly', () => {
|
|
120
|
+
const text = 'This is a test string';
|
|
121
|
+
const tokens = estimateTokens(text);
|
|
122
|
+
(0, globals_1.expect)(tokens).toBe(Math.ceil(text.length / 4));
|
|
123
|
+
});
|
|
124
|
+
(0, globals_1.it)('should handle empty strings', () => {
|
|
125
|
+
const tokens = estimateTokens('');
|
|
126
|
+
(0, globals_1.expect)(tokens).toBe(0);
|
|
127
|
+
});
|
|
128
|
+
(0, globals_1.it)('should handle large strings', () => {
|
|
129
|
+
const largeText = 'X'.repeat(10000);
|
|
130
|
+
const tokens = estimateTokens(largeText);
|
|
131
|
+
(0, globals_1.expect)(tokens).toBe(2500); // 10000 / 4
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
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
|
+
const tool_profiles_1 = require("../../utils/tool-profiles");
|
|
40
|
+
/**
|
|
41
|
+
* Drift-detection: extract tool names from the ListToolsRequestSchema handler
|
|
42
|
+
* in src/index.ts to verify ALL_TOOL_NAMES stays in sync with actual tool definitions.
|
|
43
|
+
*/
|
|
44
|
+
function extractToolNamesFromIndexTs() {
|
|
45
|
+
const indexPath = path.join(__dirname, '..', '..', 'index.ts');
|
|
46
|
+
const src = fs.readFileSync(indexPath, 'utf-8');
|
|
47
|
+
// Find the allTools array: starts after "const allTools" and ends at the matching "];"
|
|
48
|
+
// We look for tool name strings inside the ListToolsRequestSchema handler
|
|
49
|
+
const toolNameRegex = /^\s+name:\s+'(context_[a-z_]+)'/gm;
|
|
50
|
+
const names = [];
|
|
51
|
+
let match;
|
|
52
|
+
// Only capture tool names outside of block comments (skip commented-out tools)
|
|
53
|
+
// Split by block comment boundaries and only scan non-comment sections
|
|
54
|
+
const sections = src.split(/\/\*[\s\S]*?\*\//);
|
|
55
|
+
for (const section of sections) {
|
|
56
|
+
toolNameRegex.lastIndex = 0;
|
|
57
|
+
while ((match = toolNameRegex.exec(section)) !== null) {
|
|
58
|
+
names.push(match[1]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return names;
|
|
62
|
+
}
|
|
63
|
+
(0, globals_1.describe)('Tool Profile Integration Tests', () => {
|
|
64
|
+
const originalEnv = process.env.TOOL_PROFILE;
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
if (originalEnv !== undefined) {
|
|
67
|
+
process.env.TOOL_PROFILE = originalEnv;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
delete process.env.TOOL_PROFILE;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
(0, globals_1.describe)('Drift detection: ALL_TOOL_NAMES vs index.ts', () => {
|
|
74
|
+
(0, globals_1.it)('ALL_TOOL_NAMES should match active tools defined in index.ts', () => {
|
|
75
|
+
const indexToolNames = extractToolNamesFromIndexTs();
|
|
76
|
+
const allToolNamesArray = [...tool_profiles_1.ALL_TOOL_NAMES];
|
|
77
|
+
// Same count
|
|
78
|
+
(0, globals_1.expect)(allToolNamesArray.length).toBe(indexToolNames.length);
|
|
79
|
+
// Same set of names
|
|
80
|
+
(0, globals_1.expect)(new Set(allToolNamesArray)).toEqual(new Set(indexToolNames));
|
|
81
|
+
});
|
|
82
|
+
(0, globals_1.it)('should not include commented-out tools', () => {
|
|
83
|
+
// context_share and context_get_shared are commented out in index.ts
|
|
84
|
+
(0, globals_1.expect)(tool_profiles_1.ALL_TOOL_NAMES_SET.has('context_share')).toBe(false);
|
|
85
|
+
(0, globals_1.expect)(tool_profiles_1.ALL_TOOL_NAMES_SET.has('context_get_shared')).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
(0, globals_1.describe)('Profile filtering behavior', () => {
|
|
89
|
+
(0, globals_1.it)('minimal profile should include core tools and exclude advanced tools', () => {
|
|
90
|
+
process.env.TOOL_PROFILE = 'minimal';
|
|
91
|
+
const profile = (0, tool_profiles_1.resolveActiveProfile)('/nonexistent/config.json');
|
|
92
|
+
// Core tools present
|
|
93
|
+
(0, globals_1.expect)(profile.tools.has('context_save')).toBe(true);
|
|
94
|
+
(0, globals_1.expect)(profile.tools.has('context_get')).toBe(true);
|
|
95
|
+
(0, globals_1.expect)(profile.tools.has('context_search')).toBe(true);
|
|
96
|
+
(0, globals_1.expect)(profile.tools.has('context_checkpoint')).toBe(true);
|
|
97
|
+
// Advanced tools absent
|
|
98
|
+
(0, globals_1.expect)(profile.tools.has('context_analyze')).toBe(false);
|
|
99
|
+
(0, globals_1.expect)(profile.tools.has('context_visualize')).toBe(false);
|
|
100
|
+
(0, globals_1.expect)(profile.tools.has('context_delegate')).toBe(false);
|
|
101
|
+
(0, globals_1.expect)(profile.tools.has('context_semantic_search')).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
(0, globals_1.it)('default (no env var) should expose all tools with backwards-compatible behavior', () => {
|
|
104
|
+
delete process.env.TOOL_PROFILE;
|
|
105
|
+
const profile = (0, tool_profiles_1.resolveActiveProfile)('/nonexistent/config.json');
|
|
106
|
+
(0, globals_1.expect)(profile.tools.size).toBe(tool_profiles_1.ALL_TOOL_NAMES.length);
|
|
107
|
+
(0, globals_1.expect)(profile.profileName).toBe('full');
|
|
108
|
+
(0, globals_1.expect)(profile.source).toBe('default');
|
|
109
|
+
(0, globals_1.expect)(profile.warnings).toHaveLength(0);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
(0, globals_1.describe)('CallTool guard behavior', () => {
|
|
113
|
+
(0, globals_1.it)('disabled tool should be in ALL_TOOL_NAMES_SET but not in profile tools', () => {
|
|
114
|
+
process.env.TOOL_PROFILE = 'minimal';
|
|
115
|
+
const profile = (0, tool_profiles_1.resolveActiveProfile)('/nonexistent/config.json');
|
|
116
|
+
const disabledTool = 'context_analyze';
|
|
117
|
+
// The guard logic: known tool that is not enabled
|
|
118
|
+
const isKnown = tool_profiles_1.ALL_TOOL_NAMES_SET.has(disabledTool);
|
|
119
|
+
const isEnabled = profile.tools.has(disabledTool);
|
|
120
|
+
(0, globals_1.expect)(isKnown).toBe(true);
|
|
121
|
+
(0, globals_1.expect)(isEnabled).toBe(false);
|
|
122
|
+
// In index.ts: isKnown && !isEnabled → return isError: true
|
|
123
|
+
});
|
|
124
|
+
(0, globals_1.it)('unknown tool should not be in ALL_TOOL_NAMES_SET (falls through to default switch)', () => {
|
|
125
|
+
const unknownTool = 'non_existent_tool';
|
|
126
|
+
(0, globals_1.expect)(tool_profiles_1.ALL_TOOL_NAMES_SET.has(unknownTool)).toBe(false);
|
|
127
|
+
// In index.ts: !isKnown → falls through to default: throw new Error()
|
|
128
|
+
});
|
|
129
|
+
(0, globals_1.it)('enabled tool should pass both checks', () => {
|
|
130
|
+
process.env.TOOL_PROFILE = 'minimal';
|
|
131
|
+
const profile = (0, tool_profiles_1.resolveActiveProfile)('/nonexistent/config.json');
|
|
132
|
+
const enabledTool = 'context_save';
|
|
133
|
+
const isKnown = tool_profiles_1.ALL_TOOL_NAMES_SET.has(enabledTool);
|
|
134
|
+
const isEnabled = profile.tools.has(enabledTool);
|
|
135
|
+
(0, globals_1.expect)(isKnown).toBe(true);
|
|
136
|
+
(0, globals_1.expect)(isEnabled).toBe(true);
|
|
137
|
+
// In index.ts: isKnown && isEnabled → guard does not fire, proceeds to switch
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
(0, globals_1.describe)('TOOL_PROFILE_CONFIG support', () => {
|
|
141
|
+
(0, globals_1.it)('resolveActiveProfile accepts custom config path (used by TOOL_PROFILE_CONFIG)', () => {
|
|
142
|
+
// This tests the mechanism that index.ts uses:
|
|
143
|
+
// resolveActiveProfile(process.env.TOOL_PROFILE_CONFIG)
|
|
144
|
+
const result = (0, tool_profiles_1.resolveActiveProfile)('/nonexistent/custom/path/config.json');
|
|
145
|
+
// Missing file → no config → falls back to built-in 'full'
|
|
146
|
+
(0, globals_1.expect)(result.profileName).toBe('full');
|
|
147
|
+
(0, globals_1.expect)(result.tools.size).toBe(38);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|