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.
- package/CHANGELOG.md +433 -0
- package/LICENSE +21 -0
- package/README.md +1051 -0
- package/bin/mcp-memory-keeper +52 -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 +938 -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/cross-session-sharing.test.js +302 -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 +407 -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/git-integration.test.js +237 -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/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 +283 -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 +307 -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/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 +188 -0
- package/dist/__tests__/utils/timezone-safe-dates.js +119 -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 +4310 -0
- package/dist/index.phase1.backup.js +410 -0
- package/dist/index.phase2.backup.js +704 -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 +1873 -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/server.js +384 -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 +731 -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/validation.js +296 -0
- package/dist/utils/vector-store.js +247 -0
- package/package.json +84 -0
|
@@ -0,0 +1,302 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const globals_1 = require("@jest/globals");
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
const database_1 = require("../../utils/database");
|
|
44
|
+
const migrationHealthCheck_1 = require("../../utils/migrationHealthCheck");
|
|
45
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
46
|
+
(0, globals_1.describe)('MigrationHealthCheck', () => {
|
|
47
|
+
let tempDir;
|
|
48
|
+
let dbPath;
|
|
49
|
+
let dbManager;
|
|
50
|
+
let healthCheck;
|
|
51
|
+
(0, globals_1.beforeEach)(() => {
|
|
52
|
+
// Create temporary directory for test database
|
|
53
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-test-'));
|
|
54
|
+
dbPath = path.join(tempDir, 'test.db');
|
|
55
|
+
});
|
|
56
|
+
(0, globals_1.afterEach)(() => {
|
|
57
|
+
// Clean up
|
|
58
|
+
if (dbManager) {
|
|
59
|
+
dbManager.close();
|
|
60
|
+
}
|
|
61
|
+
// Remove temporary directory
|
|
62
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
63
|
+
});
|
|
64
|
+
(0, globals_1.describe)('Missing column detection', () => {
|
|
65
|
+
(0, globals_1.it)('should detect missing metadata column in context_items table', () => {
|
|
66
|
+
// Create a database with old schema (missing metadata column)
|
|
67
|
+
const db = new better_sqlite3_1.default(dbPath);
|
|
68
|
+
db.exec(`
|
|
69
|
+
CREATE TABLE sessions (
|
|
70
|
+
id TEXT PRIMARY KEY,
|
|
71
|
+
name TEXT,
|
|
72
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
CREATE TABLE context_items (
|
|
76
|
+
id TEXT PRIMARY KEY,
|
|
77
|
+
session_id TEXT NOT NULL,
|
|
78
|
+
key TEXT NOT NULL,
|
|
79
|
+
value TEXT NOT NULL,
|
|
80
|
+
category TEXT,
|
|
81
|
+
priority TEXT DEFAULT 'normal',
|
|
82
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
83
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
84
|
+
);
|
|
85
|
+
`);
|
|
86
|
+
// Create health check without initializing DatabaseManager to avoid auto-fix
|
|
87
|
+
const dbManager = new better_sqlite3_1.default(dbPath);
|
|
88
|
+
healthCheck = new migrationHealthCheck_1.MigrationHealthCheck({ getDatabase: () => dbManager });
|
|
89
|
+
const result = healthCheck.runHealthCheck();
|
|
90
|
+
db.close();
|
|
91
|
+
dbManager.close();
|
|
92
|
+
// Should detect missing columns
|
|
93
|
+
(0, globals_1.expect)(result.issues.length).toBeGreaterThan(0);
|
|
94
|
+
const metadataIssue = result.issues.find(issue => issue.table === 'context_items' && issue.issue.includes('metadata'));
|
|
95
|
+
(0, globals_1.expect)(metadataIssue).toBeDefined();
|
|
96
|
+
(0, globals_1.expect)(metadataIssue?.severity).toBe('error');
|
|
97
|
+
(0, globals_1.expect)(metadataIssue?.fix).toContain('ALTER TABLE context_items ADD COLUMN metadata TEXT');
|
|
98
|
+
});
|
|
99
|
+
(0, globals_1.it)('should detect multiple missing columns', () => {
|
|
100
|
+
// Create a database with very old schema
|
|
101
|
+
const db = new better_sqlite3_1.default(dbPath);
|
|
102
|
+
db.exec(`
|
|
103
|
+
CREATE TABLE sessions (
|
|
104
|
+
id TEXT PRIMARY KEY,
|
|
105
|
+
name TEXT,
|
|
106
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
CREATE TABLE context_items (
|
|
110
|
+
id TEXT PRIMARY KEY,
|
|
111
|
+
session_id TEXT NOT NULL,
|
|
112
|
+
key TEXT NOT NULL,
|
|
113
|
+
value TEXT NOT NULL,
|
|
114
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
115
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
116
|
+
);
|
|
117
|
+
`);
|
|
118
|
+
// Create health check without initializing DatabaseManager
|
|
119
|
+
const dbManager = new better_sqlite3_1.default(dbPath);
|
|
120
|
+
healthCheck = new migrationHealthCheck_1.MigrationHealthCheck({ getDatabase: () => dbManager });
|
|
121
|
+
const result = healthCheck.runHealthCheck();
|
|
122
|
+
db.close();
|
|
123
|
+
dbManager.close();
|
|
124
|
+
// Should detect multiple missing columns
|
|
125
|
+
const missingColumns = ['category', 'priority', 'metadata', 'size'];
|
|
126
|
+
for (const column of missingColumns) {
|
|
127
|
+
const issue = result.issues.find(i => i.table === 'context_items' && i.issue.includes(column));
|
|
128
|
+
(0, globals_1.expect)(issue).toBeDefined();
|
|
129
|
+
}
|
|
130
|
+
// updated_at might exist depending on schema version, check separately
|
|
131
|
+
const hasUpdatedAt = result.issues.some(i => i.table === 'context_items' && i.issue.includes('updated_at'));
|
|
132
|
+
if (hasUpdatedAt) {
|
|
133
|
+
(0, globals_1.expect)(result.issues.length).toBeGreaterThanOrEqual(missingColumns.length + 1);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
(0, globals_1.expect)(result.issues.length).toBeGreaterThanOrEqual(missingColumns.length);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
(0, globals_1.describe)('Auto-fix functionality', () => {
|
|
141
|
+
(0, globals_1.it)('should successfully add missing columns', () => {
|
|
142
|
+
// Create a database with old schema
|
|
143
|
+
const db = new better_sqlite3_1.default(dbPath);
|
|
144
|
+
db.exec(`
|
|
145
|
+
CREATE TABLE sessions (
|
|
146
|
+
id TEXT PRIMARY KEY,
|
|
147
|
+
name TEXT,
|
|
148
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
CREATE TABLE context_items (
|
|
152
|
+
id TEXT PRIMARY KEY,
|
|
153
|
+
session_id TEXT NOT NULL,
|
|
154
|
+
key TEXT NOT NULL,
|
|
155
|
+
value TEXT NOT NULL,
|
|
156
|
+
category TEXT,
|
|
157
|
+
priority TEXT DEFAULT 'normal',
|
|
158
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
159
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
160
|
+
);
|
|
161
|
+
`);
|
|
162
|
+
db.close();
|
|
163
|
+
// Create proper DatabaseManager instance but prevent auto-fix during init
|
|
164
|
+
const testDb = new better_sqlite3_1.default(dbPath);
|
|
165
|
+
const mockDbManager = {
|
|
166
|
+
getDatabase: () => testDb,
|
|
167
|
+
close: () => testDb.close(),
|
|
168
|
+
};
|
|
169
|
+
healthCheck = new migrationHealthCheck_1.MigrationHealthCheck(mockDbManager);
|
|
170
|
+
// First check what issues exist
|
|
171
|
+
const beforeFix = healthCheck.runHealthCheck();
|
|
172
|
+
(0, globals_1.expect)(beforeFix.issues.length).toBeGreaterThan(0);
|
|
173
|
+
(0, globals_1.expect)(beforeFix.canAutoFix).toBe(true);
|
|
174
|
+
// Apply auto-fix
|
|
175
|
+
const success = healthCheck.runAutoFix();
|
|
176
|
+
(0, globals_1.expect)(success).toBe(true);
|
|
177
|
+
// Verify fixes were applied
|
|
178
|
+
const afterFix = healthCheck.runHealthCheck();
|
|
179
|
+
(0, globals_1.expect)(afterFix.issues.length).toBe(0);
|
|
180
|
+
(0, globals_1.expect)(afterFix.summary).toContain('Database schema is healthy');
|
|
181
|
+
mockDbManager.close();
|
|
182
|
+
});
|
|
183
|
+
(0, globals_1.it)('should handle errors gracefully when fix fails', () => {
|
|
184
|
+
// Create a database with correct schema first
|
|
185
|
+
dbManager = new database_1.DatabaseManager({ filename: dbPath });
|
|
186
|
+
const db = dbManager.getDatabase();
|
|
187
|
+
// Drop a column to simulate missing column
|
|
188
|
+
db.exec(`
|
|
189
|
+
CREATE TABLE temp_context_items AS SELECT id, session_id, key, value, category, priority, created_at FROM context_items;
|
|
190
|
+
DROP TABLE context_items;
|
|
191
|
+
ALTER TABLE temp_context_items RENAME TO context_items;
|
|
192
|
+
`);
|
|
193
|
+
// Create health check instance
|
|
194
|
+
healthCheck = new migrationHealthCheck_1.MigrationHealthCheck(dbManager);
|
|
195
|
+
const result = healthCheck.runHealthCheck();
|
|
196
|
+
(0, globals_1.expect)(result.issues.length).toBeGreaterThan(0);
|
|
197
|
+
// Mock exec to simulate failure
|
|
198
|
+
const originalExec = db.exec.bind(db);
|
|
199
|
+
db.exec = jest.fn().mockImplementation(() => {
|
|
200
|
+
throw new Error('Simulated database error');
|
|
201
|
+
});
|
|
202
|
+
// Try to auto-fix (should fail gracefully)
|
|
203
|
+
const { fixed, failed } = healthCheck.autoFixIssues(result.issues);
|
|
204
|
+
(0, globals_1.expect)(fixed.length).toBe(0);
|
|
205
|
+
(0, globals_1.expect)(failed.length).toBeGreaterThan(0);
|
|
206
|
+
// Restore original exec
|
|
207
|
+
db.exec = originalExec;
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
(0, globals_1.describe)('Schema discovery', () => {
|
|
211
|
+
(0, globals_1.it)('should correctly parse schema from database.ts file', () => {
|
|
212
|
+
// Create a new database with full schema
|
|
213
|
+
dbManager = new database_1.DatabaseManager({ filename: dbPath });
|
|
214
|
+
healthCheck = new migrationHealthCheck_1.MigrationHealthCheck(dbManager);
|
|
215
|
+
// Run health check on a properly initialized database
|
|
216
|
+
const result = healthCheck.runHealthCheck();
|
|
217
|
+
// Should have no issues
|
|
218
|
+
(0, globals_1.expect)(result.issues.length).toBe(0);
|
|
219
|
+
(0, globals_1.expect)(result.summary).toContain('Database schema is healthy');
|
|
220
|
+
});
|
|
221
|
+
(0, globals_1.it)('should work with empty database (new user)', () => {
|
|
222
|
+
// Create completely empty database
|
|
223
|
+
const db = new better_sqlite3_1.default(dbPath);
|
|
224
|
+
db.close();
|
|
225
|
+
// Run health check
|
|
226
|
+
dbManager = new database_1.DatabaseManager({ filename: dbPath });
|
|
227
|
+
healthCheck = new migrationHealthCheck_1.MigrationHealthCheck(dbManager);
|
|
228
|
+
// Check should pass - tables will be created during initialization
|
|
229
|
+
const result = healthCheck.runHealthCheck();
|
|
230
|
+
(0, globals_1.expect)(result.summary).toContain('Database schema is healthy');
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
(0, globals_1.describe)('Integration with DatabaseManager', () => {
|
|
234
|
+
(0, globals_1.it)('should automatically fix issues on database initialization', () => {
|
|
235
|
+
// Create a database with old schema
|
|
236
|
+
const db = new better_sqlite3_1.default(dbPath);
|
|
237
|
+
db.exec(`
|
|
238
|
+
CREATE TABLE sessions (
|
|
239
|
+
id TEXT PRIMARY KEY,
|
|
240
|
+
name TEXT,
|
|
241
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
CREATE TABLE context_items (
|
|
245
|
+
id TEXT PRIMARY KEY,
|
|
246
|
+
session_id TEXT NOT NULL,
|
|
247
|
+
key TEXT NOT NULL,
|
|
248
|
+
value TEXT NOT NULL,
|
|
249
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
250
|
+
);
|
|
251
|
+
`);
|
|
252
|
+
db.close();
|
|
253
|
+
// Initialize DatabaseManager (should auto-fix)
|
|
254
|
+
dbManager = new database_1.DatabaseManager({ filename: dbPath });
|
|
255
|
+
// Verify columns were added
|
|
256
|
+
const db2 = dbManager.getDatabase();
|
|
257
|
+
const columns = db2.prepare('PRAGMA table_info(context_items)').all();
|
|
258
|
+
const columnNames = columns.map((col) => col.name);
|
|
259
|
+
(0, globals_1.expect)(columnNames).toContain('metadata');
|
|
260
|
+
(0, globals_1.expect)(columnNames).toContain('size');
|
|
261
|
+
(0, globals_1.expect)(columnNames).toContain('category');
|
|
262
|
+
(0, globals_1.expect)(columnNames).toContain('priority');
|
|
263
|
+
// Note: context_items table only has created_at, not updated_at
|
|
264
|
+
});
|
|
265
|
+
(0, globals_1.it)('should handle concurrent database access during migration', () => {
|
|
266
|
+
// Create a database with old schema
|
|
267
|
+
const db = new better_sqlite3_1.default(dbPath);
|
|
268
|
+
db.exec(`
|
|
269
|
+
CREATE TABLE sessions (
|
|
270
|
+
id TEXT PRIMARY KEY,
|
|
271
|
+
name TEXT,
|
|
272
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
273
|
+
);
|
|
274
|
+
`);
|
|
275
|
+
db.close();
|
|
276
|
+
// Initialize multiple DatabaseManager instances concurrently
|
|
277
|
+
const managers = [];
|
|
278
|
+
const promises = [];
|
|
279
|
+
for (let i = 0; i < 3; i++) {
|
|
280
|
+
promises.push(new Promise(resolve => {
|
|
281
|
+
setTimeout(() => {
|
|
282
|
+
const manager = new database_1.DatabaseManager({ filename: dbPath });
|
|
283
|
+
managers.push(manager);
|
|
284
|
+
resolve(manager);
|
|
285
|
+
}, i * 10); // Stagger slightly
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
288
|
+
return Promise.all(promises).then(() => {
|
|
289
|
+
// All should complete successfully
|
|
290
|
+
(0, globals_1.expect)(managers.length).toBe(3);
|
|
291
|
+
// Check that migrations were applied
|
|
292
|
+
const db = managers[0].getDatabase();
|
|
293
|
+
const tables = db
|
|
294
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table'")
|
|
295
|
+
.all();
|
|
296
|
+
(0, globals_1.expect)(tables.length).toBeGreaterThan(10); // Should have all tables
|
|
297
|
+
// Clean up
|
|
298
|
+
managers.forEach(m => m.close());
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
});
|
|
@@ -0,0 +1,188 @@
|
|
|
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 simple_git_1 = require("simple-git");
|
|
37
|
+
const os = __importStar(require("os"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
describe('Project Directory Messages and User Guidance', () => {
|
|
41
|
+
describe('Git commit without project directory', () => {
|
|
42
|
+
it('should generate helpful message for missing project directory', () => {
|
|
43
|
+
// This is the expected message when no project directory is set
|
|
44
|
+
const expectedMessage = `⚠️ No project directory set for git tracking!
|
|
45
|
+
|
|
46
|
+
To track git changes in your project, please set your project directory using one of these methods:
|
|
47
|
+
|
|
48
|
+
1. When starting a new session:
|
|
49
|
+
context_session_start with projectDir: "/path/to/your/project"
|
|
50
|
+
|
|
51
|
+
2. For the current session:
|
|
52
|
+
context_set_project_dir with projectDir: "/path/to/your/project"
|
|
53
|
+
|
|
54
|
+
This allows the MCP server to track git changes in your actual project directory.`;
|
|
55
|
+
// Verify the message format is correct
|
|
56
|
+
expect(expectedMessage).toContain('⚠️ No project directory set');
|
|
57
|
+
expect(expectedMessage).toContain('context_session_start with projectDir:');
|
|
58
|
+
expect(expectedMessage).toContain('context_set_project_dir with projectDir:');
|
|
59
|
+
expect(expectedMessage).toContain('track git changes in your actual project directory');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('Session start messages', () => {
|
|
63
|
+
it('should generate tip message when no project directory provided', () => {
|
|
64
|
+
const tipMessage = 'Tip: To enable git tracking, start with projectDir parameter or use context_set_project_dir';
|
|
65
|
+
expect(tipMessage).toContain('To enable git tracking');
|
|
66
|
+
expect(tipMessage).toContain('projectDir parameter');
|
|
67
|
+
expect(tipMessage).toContain('context_set_project_dir');
|
|
68
|
+
});
|
|
69
|
+
it('should include project directory info in session start message', () => {
|
|
70
|
+
const projectPath = '/Users/test/my-project';
|
|
71
|
+
const sessionMessage = `Session started: Test Session
|
|
72
|
+
ID: abc-123
|
|
73
|
+
Description: Testing project directory
|
|
74
|
+
Branch: master
|
|
75
|
+
Project directory: ${projectPath}
|
|
76
|
+
Git detected: Yes`;
|
|
77
|
+
expect(sessionMessage).toContain(`Project directory: ${projectPath}`);
|
|
78
|
+
expect(sessionMessage).toContain('Git detected: Yes');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe('Set project directory messages', () => {
|
|
82
|
+
it('should show success message with git detection', () => {
|
|
83
|
+
const projectPath = '/Users/test/my-project';
|
|
84
|
+
const successMessage = `Project directory set successfully!
|
|
85
|
+
|
|
86
|
+
Path: ${projectPath}
|
|
87
|
+
Git repository: ✓ Detected
|
|
88
|
+
Current branch: main
|
|
89
|
+
Status: Clean (no uncommitted changes)
|
|
90
|
+
|
|
91
|
+
You can now use git-related features like context_git_commit.`;
|
|
92
|
+
expect(successMessage).toContain('Project directory set successfully!');
|
|
93
|
+
expect(successMessage).toContain(`Path: ${projectPath}`);
|
|
94
|
+
expect(successMessage).toContain('Git repository: ✓ Detected');
|
|
95
|
+
expect(successMessage).toContain('Current branch:');
|
|
96
|
+
expect(successMessage).toContain('context_git_commit');
|
|
97
|
+
});
|
|
98
|
+
it('should show message for non-git directory', () => {
|
|
99
|
+
const projectPath = '/Users/test/non-git-project';
|
|
100
|
+
const nonGitMessage = `Project directory set successfully!
|
|
101
|
+
|
|
102
|
+
Path: ${projectPath}
|
|
103
|
+
Git repository: ✗ Not found
|
|
104
|
+
|
|
105
|
+
Tip: Initialize git with 'git init' to enable git tracking features.`;
|
|
106
|
+
expect(nonGitMessage).toContain('Project directory set successfully!');
|
|
107
|
+
expect(nonGitMessage).toContain('Git repository: ✗ Not found');
|
|
108
|
+
expect(nonGitMessage).toContain("Initialize git with 'git init'");
|
|
109
|
+
});
|
|
110
|
+
it('should show error for missing session', () => {
|
|
111
|
+
const errorMessage = 'No active session. Please start a session first with context_session_start.';
|
|
112
|
+
expect(errorMessage).toContain('No active session');
|
|
113
|
+
expect(errorMessage).toContain('context_session_start');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
describe('Git status messages', () => {
|
|
117
|
+
let tempRepoPath;
|
|
118
|
+
let git;
|
|
119
|
+
beforeEach(async () => {
|
|
120
|
+
tempRepoPath = path.join(os.tmpdir(), `test-git-messages-${Date.now()}`);
|
|
121
|
+
fs.mkdirSync(tempRepoPath, { recursive: true });
|
|
122
|
+
git = (0, simple_git_1.simpleGit)(tempRepoPath);
|
|
123
|
+
await git.init();
|
|
124
|
+
await git.addConfig('user.name', 'Test User');
|
|
125
|
+
await git.addConfig('user.email', 'test@example.com');
|
|
126
|
+
});
|
|
127
|
+
afterEach(() => {
|
|
128
|
+
fs.rmSync(tempRepoPath, { recursive: true, force: true });
|
|
129
|
+
});
|
|
130
|
+
it('should format clean repository status', async () => {
|
|
131
|
+
fs.writeFileSync(path.join(tempRepoPath, 'README.md'), '# Test');
|
|
132
|
+
await git.add('.');
|
|
133
|
+
await git.commit('Initial commit');
|
|
134
|
+
const status = await git.status();
|
|
135
|
+
const statusMessage = `Status: ${status.isClean() ? 'Clean (no uncommitted changes)' : 'Has uncommitted changes'}`;
|
|
136
|
+
expect(statusMessage).toBe('Status: Clean (no uncommitted changes)');
|
|
137
|
+
});
|
|
138
|
+
it('should format dirty repository status', async () => {
|
|
139
|
+
fs.writeFileSync(path.join(tempRepoPath, 'README.md'), '# Test');
|
|
140
|
+
await git.add('.');
|
|
141
|
+
await git.commit('Initial commit');
|
|
142
|
+
// Make changes
|
|
143
|
+
fs.writeFileSync(path.join(tempRepoPath, 'new.txt'), 'new content');
|
|
144
|
+
const status = await git.status();
|
|
145
|
+
const statusMessage = `Status: ${status.isClean() ? 'Clean (no uncommitted changes)' : 'Has uncommitted changes'}`;
|
|
146
|
+
expect(statusMessage).toBe('Status: Has uncommitted changes');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
describe('Checkpoint messages with git info', () => {
|
|
150
|
+
it('should include git info in checkpoint creation message', () => {
|
|
151
|
+
const checkpointMessage = `Created checkpoint: Feature Save
|
|
152
|
+
ID: checkpoint-123
|
|
153
|
+
Context items: 5
|
|
154
|
+
Cached files: 3
|
|
155
|
+
Git branch: feature/user-auth
|
|
156
|
+
Git status: captured`;
|
|
157
|
+
expect(checkpointMessage).toContain('Git branch: feature/user-auth');
|
|
158
|
+
expect(checkpointMessage).toContain('Git status: captured');
|
|
159
|
+
});
|
|
160
|
+
it('should show none for git branch when no project directory', () => {
|
|
161
|
+
const checkpointMessage = `Created checkpoint: Quick Save
|
|
162
|
+
ID: checkpoint-456
|
|
163
|
+
Context items: 2
|
|
164
|
+
Cached files: 0
|
|
165
|
+
Git branch: none
|
|
166
|
+
Git status: not captured`;
|
|
167
|
+
expect(checkpointMessage).toContain('Git branch: none');
|
|
168
|
+
expect(checkpointMessage).toContain('Git status: not captured');
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
describe('Error handling messages', () => {
|
|
172
|
+
it('should handle git command failures gracefully', () => {
|
|
173
|
+
const errorMessage = 'Git commit failed: No changes to commit';
|
|
174
|
+
expect(errorMessage).toContain('Git commit failed:');
|
|
175
|
+
expect(errorMessage).toContain('No changes to commit');
|
|
176
|
+
});
|
|
177
|
+
it('should handle invalid directory paths', () => {
|
|
178
|
+
const invalidPathMessage = `Project directory set successfully!
|
|
179
|
+
|
|
180
|
+
Path: /invalid/path/that/does/not/exist
|
|
181
|
+
Git repository: ✗ Not found
|
|
182
|
+
|
|
183
|
+
Directory may not exist or may not be accessible.`;
|
|
184
|
+
expect(invalidPathMessage).toContain('Git repository: ✗ Not found');
|
|
185
|
+
expect(invalidPathMessage).toContain('Directory may not exist');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Timezone-Safe Date Testing Utility
|
|
4
|
+
*
|
|
5
|
+
* This utility provides functions for creating timezone-agnostic test dates
|
|
6
|
+
* that ensure consistent behavior across all environments regardless of system timezone.
|
|
7
|
+
*
|
|
8
|
+
* PROBLEM: Tests that use `new Date()` or local timezone methods fail when run in
|
|
9
|
+
* different timezones (e.g., CI/CD running in UTC vs local development in PST/PDT).
|
|
10
|
+
*
|
|
11
|
+
* SOLUTION: Use fixed UTC reference dates and UTC-based date construction to ensure
|
|
12
|
+
* timeline grouping and date calculations work identically everywhere.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.TEST_BASE_DATE = void 0;
|
|
16
|
+
exports.createUTCDate = createUTCDate;
|
|
17
|
+
exports.createUTCDateByHours = createUTCDateByHours;
|
|
18
|
+
exports.createUTCDateByMs = createUTCDateByMs;
|
|
19
|
+
exports.createTimelineTestDates = createTimelineTestDates;
|
|
20
|
+
exports.createDateRange = createDateRange;
|
|
21
|
+
exports.validateTimezoneSafety = validateTimezoneSafety;
|
|
22
|
+
/**
|
|
23
|
+
* Fixed reference date in UTC for consistent test behavior
|
|
24
|
+
* This date should be:
|
|
25
|
+
* - Fixed and not based on current time
|
|
26
|
+
* - In UTC timezone (ends with 'Z')
|
|
27
|
+
* - At noon to avoid midnight boundary issues
|
|
28
|
+
*/
|
|
29
|
+
exports.TEST_BASE_DATE = new Date('2025-06-20T12:00:00.000Z');
|
|
30
|
+
/**
|
|
31
|
+
* Creates a UTC date offset by the specified number of days from the base date
|
|
32
|
+
* @param daysOffset Number of days to offset (negative for past dates)
|
|
33
|
+
* @param hour Hour in UTC (default: 12 for noon)
|
|
34
|
+
* @param minute Minute (default: 0)
|
|
35
|
+
* @param second Second (default: 0)
|
|
36
|
+
* @returns Date object in UTC
|
|
37
|
+
*/
|
|
38
|
+
function createUTCDate(daysOffset = 0, hour = 12, minute = 0, second = 0) {
|
|
39
|
+
return new Date(Date.UTC(exports.TEST_BASE_DATE.getUTCFullYear(), exports.TEST_BASE_DATE.getUTCMonth(), exports.TEST_BASE_DATE.getUTCDate() + daysOffset, hour, minute, second));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Creates a UTC date offset by the specified number of hours from the base date
|
|
43
|
+
* @param hoursOffset Number of hours to offset
|
|
44
|
+
* @returns Date object in UTC
|
|
45
|
+
*/
|
|
46
|
+
function createUTCDateByHours(hoursOffset) {
|
|
47
|
+
return new Date(exports.TEST_BASE_DATE.getTime() + hoursOffset * 60 * 60 * 1000);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Creates a UTC date offset by the specified number of milliseconds from the base date
|
|
51
|
+
* @param msOffset Number of milliseconds to offset
|
|
52
|
+
* @returns Date object in UTC
|
|
53
|
+
*/
|
|
54
|
+
function createUTCDateByMs(msOffset) {
|
|
55
|
+
return new Date(exports.TEST_BASE_DATE.getTime() + msOffset);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Creates a set of test dates for timeline testing
|
|
59
|
+
* @returns Object with commonly used test dates
|
|
60
|
+
*/
|
|
61
|
+
function createTimelineTestDates() {
|
|
62
|
+
return {
|
|
63
|
+
baseDate: exports.TEST_BASE_DATE,
|
|
64
|
+
today: createUTCDate(0), // 2025-06-20 12:00:00 UTC
|
|
65
|
+
yesterday: createUTCDate(-1), // 2025-06-19 12:00:00 UTC
|
|
66
|
+
threeDaysAgo: createUTCDate(-3), // 2025-06-17 12:00:00 UTC
|
|
67
|
+
fiveDaysAgo: createUTCDate(-5), // 2025-06-15 12:00:00 UTC
|
|
68
|
+
sevenDaysAgo: createUTCDate(-7), // 2025-06-13 12:00:00 UTC
|
|
69
|
+
oneWeekAgo: createUTCDate(-7),
|
|
70
|
+
oneMonthAgo: createUTCDate(-30),
|
|
71
|
+
oneYearAgo: createUTCDate(-365),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Creates a date range for testing
|
|
76
|
+
* @param startDaysOffset Days offset for start date
|
|
77
|
+
* @param endDaysOffset Days offset for end date (default: 0 = base date)
|
|
78
|
+
* @returns Object with start and end dates
|
|
79
|
+
*/
|
|
80
|
+
function createDateRange(startDaysOffset, endDaysOffset = 0) {
|
|
81
|
+
return {
|
|
82
|
+
startDate: createUTCDate(startDaysOffset),
|
|
83
|
+
endDate: createUTCDate(endDaysOffset),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Validates that the timezone-safe pattern is working correctly
|
|
88
|
+
* This function can be used in tests to verify consistent behavior
|
|
89
|
+
*/
|
|
90
|
+
function validateTimezoneSafety() {
|
|
91
|
+
const testDate = createUTCDate(0);
|
|
92
|
+
// Should always produce the same UTC string regardless of system timezone
|
|
93
|
+
const expectedISOString = '2025-06-20T12:00:00.000Z';
|
|
94
|
+
if (testDate.toISOString() !== expectedISOString) {
|
|
95
|
+
throw new Error(`Timezone safety validation failed. Expected: ${expectedISOString}, Got: ${testDate.toISOString()}`);
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Example usage patterns for timezone-safe testing:
|
|
101
|
+
*
|
|
102
|
+
* // ✅ CORRECT - Timezone-safe pattern
|
|
103
|
+
* const { today, yesterday } = createTimelineTestDates();
|
|
104
|
+
* const items = [
|
|
105
|
+
* { time: new Date(today.getTime() + 60 * 60 * 1000), data: 'test' },
|
|
106
|
+
* { time: new Date(yesterday.getTime() + 60 * 60 * 1000), data: 'test' }
|
|
107
|
+
* ];
|
|
108
|
+
*
|
|
109
|
+
* // ✅ CORRECT - For date ranges
|
|
110
|
+
* const { startDate, endDate } = createDateRange(-10, 0);
|
|
111
|
+
*
|
|
112
|
+
* // ❌ WRONG - Timezone-dependent pattern
|
|
113
|
+
* const now = new Date();
|
|
114
|
+
* const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
115
|
+
*
|
|
116
|
+
* // ❌ WRONG - System timezone dependent
|
|
117
|
+
* const yesterday = new Date();
|
|
118
|
+
* yesterday.setDate(yesterday.getDate() - 1);
|
|
119
|
+
*/
|