mcp-memory-keeper 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/CHANGELOG.md +433 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1051 -0
  4. package/bin/mcp-memory-keeper +52 -0
  5. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  6. package/dist/__tests__/helpers/test-server.js +92 -0
  7. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  8. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  9. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  10. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  11. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  12. package/dist/__tests__/integration/channels.test.js +376 -0
  13. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  14. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  15. package/dist/__tests__/integration/context-operations.test.js +243 -0
  16. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  17. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  18. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  19. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  20. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  21. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  22. package/dist/__tests__/integration/contextSearch.test.js +938 -0
  23. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  24. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  25. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  26. package/dist/__tests__/integration/cross-session-sharing.test.js +302 -0
  27. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  28. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  29. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  30. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  31. package/dist/__tests__/integration/error-cases.test.js +407 -0
  32. package/dist/__tests__/integration/export-import.test.js +367 -0
  33. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  34. package/dist/__tests__/integration/file-operations.test.js +264 -0
  35. package/dist/__tests__/integration/git-integration.test.js +237 -0
  36. package/dist/__tests__/integration/index-tools.test.js +496 -0
  37. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  38. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  39. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  40. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  41. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  42. package/dist/__tests__/integration/migrations.test.js +528 -0
  43. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  44. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  45. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  46. package/dist/__tests__/integration/project-directory.test.js +283 -0
  47. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  48. package/dist/__tests__/integration/retention.test.js +513 -0
  49. package/dist/__tests__/integration/search.test.js +333 -0
  50. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  51. package/dist/__tests__/integration/server-initialization.test.js +307 -0
  52. package/dist/__tests__/integration/session-management.test.js +219 -0
  53. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  54. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  55. package/dist/__tests__/integration/summarization.test.js +308 -0
  56. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  57. package/dist/__tests__/security/input-validation.test.js +115 -0
  58. package/dist/__tests__/utils/agents.test.js +473 -0
  59. package/dist/__tests__/utils/database.test.js +177 -0
  60. package/dist/__tests__/utils/git.test.js +122 -0
  61. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  62. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  63. package/dist/__tests__/utils/project-directory-messages.test.js +188 -0
  64. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  65. package/dist/__tests__/utils/validation.test.js +200 -0
  66. package/dist/__tests__/utils/vector-store.test.js +231 -0
  67. package/dist/handlers/contextWatchHandlers.js +206 -0
  68. package/dist/index.js +4310 -0
  69. package/dist/index.phase1.backup.js +410 -0
  70. package/dist/index.phase2.backup.js +704 -0
  71. package/dist/migrations/003_add_channels.js +174 -0
  72. package/dist/migrations/004_add_context_watch.js +151 -0
  73. package/dist/migrations/005_add_context_watch.js +98 -0
  74. package/dist/migrations/simplify-sharing.js +117 -0
  75. package/dist/repositories/BaseRepository.js +30 -0
  76. package/dist/repositories/CheckpointRepository.js +140 -0
  77. package/dist/repositories/ContextRepository.js +1873 -0
  78. package/dist/repositories/FileRepository.js +104 -0
  79. package/dist/repositories/RepositoryManager.js +62 -0
  80. package/dist/repositories/SessionRepository.js +66 -0
  81. package/dist/repositories/WatcherRepository.js +252 -0
  82. package/dist/repositories/index.js +15 -0
  83. package/dist/server.js +384 -0
  84. package/dist/test-helpers/database-helper.js +128 -0
  85. package/dist/types/entities.js +3 -0
  86. package/dist/utils/agents.js +791 -0
  87. package/dist/utils/channels.js +150 -0
  88. package/dist/utils/database.js +731 -0
  89. package/dist/utils/feature-flags.js +476 -0
  90. package/dist/utils/git.js +145 -0
  91. package/dist/utils/knowledge-graph.js +264 -0
  92. package/dist/utils/migrationHealthCheck.js +373 -0
  93. package/dist/utils/migrations.js +452 -0
  94. package/dist/utils/retention.js +460 -0
  95. package/dist/utils/timestamps.js +112 -0
  96. package/dist/utils/validation.js +296 -0
  97. package/dist/utils/vector-store.js +247 -0
  98. package/package.json +84 -0
@@ -0,0 +1,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
+ */