@smallironman/mcp-memory-keeper 0.12.2-fork1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/CHANGELOG.md +542 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1281 -0
  4. package/bin/mcp-memory-keeper +54 -0
  5. package/dist/__tests__/e2e/issue33-reproduce.test.js +234 -0
  6. package/dist/__tests__/e2e/server-e2e.test.js +341 -0
  7. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  8. package/dist/__tests__/helpers/test-server.js +92 -0
  9. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  10. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  11. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  12. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  13. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  14. package/dist/__tests__/integration/channels.test.js +376 -0
  15. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  16. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  17. package/dist/__tests__/integration/context-operations.test.js +243 -0
  18. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  19. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  20. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  21. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  22. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  23. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  24. package/dist/__tests__/integration/contextSearch.test.js +1054 -0
  25. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  26. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  27. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  28. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  29. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  30. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  31. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  32. package/dist/__tests__/integration/error-cases.test.js +411 -0
  33. package/dist/__tests__/integration/export-import.test.js +367 -0
  34. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  35. package/dist/__tests__/integration/file-operations.test.js +264 -0
  36. package/dist/__tests__/integration/filterBySessionId.test.js +251 -0
  37. package/dist/__tests__/integration/git-integration.test.js +241 -0
  38. package/dist/__tests__/integration/index-tools.test.js +496 -0
  39. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  40. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  41. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  42. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  43. package/dist/__tests__/integration/issue24-final-fix.test.js +241 -0
  44. package/dist/__tests__/integration/issue24-fix-validation.test.js +158 -0
  45. package/dist/__tests__/integration/issue24-reproduce.test.js +225 -0
  46. package/dist/__tests__/integration/issue24-token-limit.test.js +199 -0
  47. package/dist/__tests__/integration/issue33-array-items-schema.test.js +165 -0
  48. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  49. package/dist/__tests__/integration/migrations.test.js +528 -0
  50. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  51. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  52. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  53. package/dist/__tests__/integration/project-directory.test.js +291 -0
  54. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  55. package/dist/__tests__/integration/retention.test.js +513 -0
  56. package/dist/__tests__/integration/search.test.js +333 -0
  57. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  58. package/dist/__tests__/integration/server-initialization.test.js +305 -0
  59. package/dist/__tests__/integration/session-management.test.js +219 -0
  60. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  61. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  62. package/dist/__tests__/integration/summarization.test.js +308 -0
  63. package/dist/__tests__/integration/tokenLimitEnforcement.test.js +134 -0
  64. package/dist/__tests__/integration/tool-profiles-integration.test.js +150 -0
  65. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  66. package/dist/__tests__/security/input-validation.test.js +115 -0
  67. package/dist/__tests__/utils/agents.test.js +473 -0
  68. package/dist/__tests__/utils/database.test.js +177 -0
  69. package/dist/__tests__/utils/git.test.js +122 -0
  70. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  71. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  72. package/dist/__tests__/utils/project-directory-messages.test.js +192 -0
  73. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  74. package/dist/__tests__/utils/token-limits.test.js +225 -0
  75. package/dist/__tests__/utils/tool-profiles.test.js +374 -0
  76. package/dist/__tests__/utils/validation.test.js +200 -0
  77. package/dist/__tests__/utils/vector-store.test.js +231 -0
  78. package/dist/handlers/contextWatchHandlers.js +206 -0
  79. package/dist/index.js +4425 -0
  80. package/dist/migrations/003_add_channels.js +174 -0
  81. package/dist/migrations/004_add_context_watch.js +151 -0
  82. package/dist/migrations/005_add_context_watch.js +98 -0
  83. package/dist/migrations/simplify-sharing.js +117 -0
  84. package/dist/repositories/BaseRepository.js +30 -0
  85. package/dist/repositories/CheckpointRepository.js +140 -0
  86. package/dist/repositories/ContextRepository.js +2017 -0
  87. package/dist/repositories/FileRepository.js +104 -0
  88. package/dist/repositories/RepositoryManager.js +62 -0
  89. package/dist/repositories/SessionRepository.js +66 -0
  90. package/dist/repositories/WatcherRepository.js +252 -0
  91. package/dist/repositories/index.js +15 -0
  92. package/dist/test-helpers/database-helper.js +128 -0
  93. package/dist/types/entities.js +3 -0
  94. package/dist/utils/agents.js +791 -0
  95. package/dist/utils/channels.js +150 -0
  96. package/dist/utils/database.js +780 -0
  97. package/dist/utils/feature-flags.js +476 -0
  98. package/dist/utils/git.js +145 -0
  99. package/dist/utils/knowledge-graph.js +264 -0
  100. package/dist/utils/migrationHealthCheck.js +373 -0
  101. package/dist/utils/migrations.js +452 -0
  102. package/dist/utils/retention.js +460 -0
  103. package/dist/utils/timestamps.js +112 -0
  104. package/dist/utils/token-limits.js +350 -0
  105. package/dist/utils/tool-profiles.js +242 -0
  106. package/dist/utils/validation.js +296 -0
  107. package/dist/utils/vector-store.js +247 -0
  108. package/examples/config.json +31 -0
  109. package/examples/project-directory-setup.md +114 -0
  110. package/package.json +85 -0
package/dist/index.js ADDED
@@ -0,0 +1,4425 @@
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
+ exports.dbManager = exports._featureFlagManager = void 0;
37
+ exports.debugLog = debugLog;
38
+ exports.validatePaginationParams = validatePaginationParams;
39
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
40
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
41
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
42
+ const uuid_1 = require("uuid");
43
+ const crypto = __importStar(require("crypto"));
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const os = __importStar(require("os"));
47
+ const database_js_1 = require("./utils/database.js");
48
+ const knowledge_graph_js_1 = require("./utils/knowledge-graph.js");
49
+ const vector_store_js_1 = require("./utils/vector-store.js");
50
+ const timestamps_js_1 = require("./utils/timestamps.js");
51
+ const agents_js_1 = require("./utils/agents.js");
52
+ const retention_js_1 = require("./utils/retention.js");
53
+ const feature_flags_js_1 = require("./utils/feature-flags.js");
54
+ const RepositoryManager_js_1 = require("./repositories/RepositoryManager.js");
55
+ const simple_git_1 = require("simple-git");
56
+ const channels_js_1 = require("./utils/channels.js");
57
+ const token_limits_js_1 = require("./utils/token-limits.js");
58
+ const contextWatchHandlers_js_1 = require("./handlers/contextWatchHandlers.js");
59
+ const tool_profiles_js_1 = require("./utils/tool-profiles.js");
60
+ // Initialize database with migrations
61
+ const dataDir = process.env.DATA_DIR
62
+ ? path.resolve(process.env.DATA_DIR)
63
+ : path.join(os.homedir(), 'mcp-data', 'memory-keeper');
64
+ try {
65
+ fs.mkdirSync(dataDir, { recursive: true });
66
+ }
67
+ catch (err) {
68
+ console.error(`[memory-keeper] FATAL: Cannot create data directory "${dataDir}": ${err.message}\n` +
69
+ `Set DATA_DIR to a writable location or create the directory manually.`);
70
+ process.exit(1);
71
+ }
72
+ // Warn users whose legacy DB is sitting in CWD
73
+ const legacyDb = path.join(process.cwd(), 'context.db');
74
+ if (process.cwd() !== dataDir && fs.existsSync(legacyDb)) {
75
+ console.error(`[memory-keeper] WARNING: context.db found in current directory but the ` +
76
+ `database now lives in ${dataDir}. ` +
77
+ `To preserve your data, run:\n` +
78
+ ` cp "${legacyDb}" "${path.join(dataDir, 'context.db')}"`);
79
+ }
80
+ const dbManager = new database_js_1.DatabaseManager({ filename: path.join(dataDir, 'context.db') });
81
+ exports.dbManager = dbManager;
82
+ const db = dbManager.getDatabase();
83
+ // Initialize repository manager
84
+ const repositories = new RepositoryManager_js_1.RepositoryManager(dbManager);
85
+ // Initialize git - will be created per session as needed
86
+ // REMOVED: Global project directory was causing conflicts between sessions
87
+ // Initialize knowledge graph manager
88
+ const knowledgeGraph = new knowledge_graph_js_1.KnowledgeGraphManager(db);
89
+ // Initialize vector store
90
+ const vectorStore = new vector_store_js_1.VectorStore(db);
91
+ // Initialize multi-agent system
92
+ const agentCoordinator = new agents_js_1.AgentCoordinator();
93
+ const analyzerAgent = new agents_js_1.AnalyzerAgent(db, knowledgeGraph, vectorStore);
94
+ const synthesizerAgent = new agents_js_1.SynthesizerAgent(db, vectorStore);
95
+ agentCoordinator.registerAgent(analyzerAgent);
96
+ agentCoordinator.registerAgent(synthesizerAgent);
97
+ // Initialize retention manager
98
+ const _retentionManager = new retention_js_1.RetentionManager(dbManager);
99
+ // Initialize feature flag manager
100
+ const _featureFlagManager = new feature_flags_js_1.FeatureFlagManager(dbManager);
101
+ exports._featureFlagManager = _featureFlagManager;
102
+ // Initialize debug logging flag if it doesn't exist
103
+ try {
104
+ if (!_featureFlagManager.getFlagByKey('debug_logging')) {
105
+ _featureFlagManager.createFlag({
106
+ name: 'Debug Logging',
107
+ key: 'debug_logging',
108
+ enabled: Boolean(process.env.MCP_DEBUG_LOGGING),
109
+ description: 'Enable debug logging for development and troubleshooting',
110
+ category: 'debug',
111
+ tags: ['debug', 'logging'],
112
+ });
113
+ }
114
+ }
115
+ catch (_error) {
116
+ // Silently continue if flag creation fails (migrations might not be complete)
117
+ }
118
+ // Migration manager is no longer needed - watcher migrations are now applied by DatabaseManager
119
+ // Tables are now created by DatabaseManager in utils/database.ts
120
+ // Resolve active tool profile (TOOL_PROFILE_CONFIG overrides config file path)
121
+ const activeProfile = (0, tool_profiles_js_1.resolveActiveProfile)(process.env.TOOL_PROFILE_CONFIG);
122
+ const enabledTools = activeProfile.tools;
123
+ console.error(`[MCP-Memory-Keeper] Tool profile: "${activeProfile.profileName}" (${enabledTools.size}/${tool_profiles_js_1.ALL_TOOL_NAMES.length} tools, source: ${activeProfile.source})`);
124
+ for (const warning of activeProfile.warnings) {
125
+ console.warn(`[MCP-Memory-Keeper] ${warning}`);
126
+ }
127
+ // Track current session
128
+ let currentSessionId = null;
129
+ // Debug logging utility
130
+ function debugLog(message, ...args) {
131
+ try {
132
+ if (_featureFlagManager.isEnabled('debug_logging') || process.env.MCP_DEBUG_LOGGING) {
133
+ // eslint-disable-next-line no-console
134
+ console.log(`[MCP-Memory-Keeper DEBUG] ${message}`, ...args);
135
+ }
136
+ }
137
+ catch (_error) {
138
+ // Silently fail if feature flags aren't available yet
139
+ }
140
+ }
141
+ function validatePaginationParams(params) {
142
+ const errors = [];
143
+ let limit = 25; // default
144
+ let offset = 0; // default
145
+ // Validate limit
146
+ if (params.limit !== undefined && params.limit !== null) {
147
+ const rawLimit = params.limit;
148
+ if (!Number.isInteger(rawLimit) || rawLimit <= 0) {
149
+ errors.push(`Invalid limit: expected positive integer, got ${typeof rawLimit} '${rawLimit}'`);
150
+ debugLog(`Pagination validation: Invalid limit ${rawLimit}, using default ${limit}`);
151
+ }
152
+ else {
153
+ limit = Math.min(Math.max(1, rawLimit), 100); // clamp between 1-100
154
+ if (limit !== rawLimit) {
155
+ debugLog(`Pagination validation: Clamped limit from ${rawLimit} to ${limit}`);
156
+ }
157
+ }
158
+ }
159
+ // Validate offset
160
+ if (params.offset !== undefined && params.offset !== null) {
161
+ const rawOffset = params.offset;
162
+ if (!Number.isInteger(rawOffset) || rawOffset < 0) {
163
+ errors.push(`Invalid offset: expected non-negative integer, got ${typeof rawOffset} '${rawOffset}'`);
164
+ debugLog(`Pagination validation: Invalid offset ${rawOffset}, using default ${offset}`);
165
+ }
166
+ else {
167
+ offset = rawOffset;
168
+ }
169
+ }
170
+ return { limit, offset, errors };
171
+ }
172
+ // Helper function to get or create default session
173
+ function ensureSession() {
174
+ if (!currentSessionId) {
175
+ const session = repositories.sessions.getLatest();
176
+ if (session) {
177
+ currentSessionId = session.id;
178
+ }
179
+ else {
180
+ // Create default session
181
+ const newSession = repositories.sessions.create({
182
+ name: 'Default Session',
183
+ description: 'Auto-created default session',
184
+ });
185
+ currentSessionId = newSession.id;
186
+ }
187
+ }
188
+ return currentSessionId;
189
+ }
190
+ // Helper to calculate file hash
191
+ function calculateFileHash(content) {
192
+ return crypto.createHash('sha256').update(content).digest('hex');
193
+ }
194
+ // Use token limit utilities instead of hardcoded functions
195
+ const calculateSize = token_limits_js_1.calculateSize;
196
+ const estimateTokens = token_limits_js_1.estimateTokens;
197
+ // Helper to calculate total response size and token estimate
198
+ function calculateResponseMetrics(items) {
199
+ let totalSize = 0;
200
+ for (const item of items) {
201
+ const itemSize = item.size || calculateSize(item.value);
202
+ totalSize += itemSize;
203
+ }
204
+ // Convert to JSON string to get actual response size
205
+ const jsonString = JSON.stringify(items);
206
+ const estimatedTokens = estimateTokens(jsonString);
207
+ const averageSize = items.length > 0 ? Math.round(totalSize / items.length) : 0;
208
+ return { totalSize, estimatedTokens, averageSize };
209
+ }
210
+ // Note: calculateSafeItemCount is now handled by the token-limits utility module
211
+ // which provides dynamic calculation based on actual content
212
+ // Helper to parse relative time strings
213
+ function parseRelativeTime(relativeTime) {
214
+ const now = new Date();
215
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
216
+ if (relativeTime === 'today') {
217
+ return today.toISOString();
218
+ }
219
+ else if (relativeTime === 'yesterday') {
220
+ return new Date(today.getTime() - 24 * 60 * 60 * 1000).toISOString();
221
+ }
222
+ else if (relativeTime.match(/^(\d+) hours? ago$/)) {
223
+ const hours = parseInt(relativeTime.match(/^(\d+)/)[1]);
224
+ return new Date(now.getTime() - hours * 60 * 60 * 1000).toISOString();
225
+ }
226
+ else if (relativeTime.match(/^(\d+) days? ago$/)) {
227
+ const days = parseInt(relativeTime.match(/^(\d+)/)[1]);
228
+ return new Date(now.getTime() - days * 24 * 60 * 60 * 1000).toISOString();
229
+ }
230
+ else if (relativeTime === 'this week') {
231
+ const startOfWeek = new Date(today);
232
+ startOfWeek.setDate(today.getDate() - today.getDay());
233
+ return startOfWeek.toISOString();
234
+ }
235
+ else if (relativeTime === 'last week') {
236
+ const startOfLastWeek = new Date(today);
237
+ startOfLastWeek.setDate(today.getDate() - today.getDay() - 7);
238
+ return startOfLastWeek.toISOString();
239
+ }
240
+ return null;
241
+ }
242
+ // Helper to get project directory setup message
243
+ function getProjectDirectorySetupMessage() {
244
+ return `⚠️ No project directory set for git tracking!
245
+
246
+ To enable git tracking for your project, use one of these methods:
247
+
248
+ 1. For the current session:
249
+ context_set_project_dir({ projectDir: "/path/to/your/project" })
250
+
251
+ 2. When starting a new session:
252
+ context_session_start({ name: "My Session", projectDir: "/path/to/your/project" })
253
+
254
+ This allows the MCP server to track git changes in your actual project directory.`;
255
+ }
256
+ // Helper to get git status for a session
257
+ async function getGitStatus(sessionId) {
258
+ // Get the current session's working directory
259
+ const session = sessionId
260
+ ? repositories.sessions.getById(sessionId)
261
+ : repositories.sessions.getById(currentSessionId || '');
262
+ if (!session || !session.working_directory) {
263
+ return { status: 'No project directory set', branch: 'none' };
264
+ }
265
+ try {
266
+ const git = (0, simple_git_1.simpleGit)(session.working_directory);
267
+ const status = await git.status();
268
+ const branch = await git.branch();
269
+ return {
270
+ status: JSON.stringify({
271
+ modified: status.modified,
272
+ created: status.created,
273
+ deleted: status.deleted,
274
+ staged: status.staged,
275
+ ahead: status.ahead,
276
+ behind: status.behind,
277
+ }),
278
+ branch: branch.current,
279
+ };
280
+ }
281
+ catch (_e) {
282
+ return { status: 'No git repository', branch: 'none' };
283
+ }
284
+ }
285
+ // Helper to create summary
286
+ function createSummary(items, options) {
287
+ const { categories, maxLength = 1000 } = options;
288
+ let filteredItems = items;
289
+ if (categories && categories.length > 0) {
290
+ filteredItems = items.filter(item => categories.includes(item.category));
291
+ }
292
+ // Group by category
293
+ const grouped = filteredItems.reduce((acc, item) => {
294
+ const cat = item.category || 'uncategorized';
295
+ if (!acc[cat])
296
+ acc[cat] = [];
297
+ acc[cat].push(item);
298
+ return acc;
299
+ }, {});
300
+ // Build summary
301
+ let summary = '# Context Summary\n\n';
302
+ // High priority items first
303
+ const highPriorityItems = filteredItems.filter(item => item.priority === 'high');
304
+ if (highPriorityItems.length > 0) {
305
+ summary += '## High Priority Items\n';
306
+ highPriorityItems.forEach(item => {
307
+ summary += `- **${item.key}**: ${item.value.substring(0, 200)}${item.value.length > 200 ? '...' : ''}\n`;
308
+ });
309
+ summary += '\n';
310
+ }
311
+ // Then by category
312
+ Object.entries(grouped).forEach(([category, categoryItems]) => {
313
+ if (category !== 'uncategorized') {
314
+ summary += `## ${category.charAt(0).toUpperCase() + category.slice(1)}\n`;
315
+ categoryItems.forEach((item) => {
316
+ if (item.priority !== 'high') {
317
+ // Already shown above
318
+ summary += `- ${item.key}: ${item.value.substring(0, 100)}${item.value.length > 100 ? '...' : ''}\n`;
319
+ }
320
+ });
321
+ summary += '\n';
322
+ }
323
+ });
324
+ // Truncate if needed
325
+ if (summary.length > maxLength) {
326
+ summary = summary.substring(0, maxLength - 3) + '...';
327
+ }
328
+ return summary;
329
+ }
330
+ // Create MCP server
331
+ const server = new index_js_1.Server({
332
+ name: 'memory-keeper',
333
+ version: '0.10.0',
334
+ }, {
335
+ capabilities: {
336
+ tools: {},
337
+ },
338
+ });
339
+ // Main request handler
340
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
341
+ const toolName = request.params.name;
342
+ const args = request.params.arguments;
343
+ // Reject calls to known tools that are disabled by the active profile.
344
+ // Unknown tools fall through to the default switch case for standard error handling.
345
+ if (tool_profiles_js_1.ALL_TOOL_NAMES_SET.has(toolName) && !enabledTools.has(toolName)) {
346
+ const configPath = path.join(os.homedir(), '.mcp-memory-keeper', 'config.json');
347
+ return {
348
+ content: [
349
+ {
350
+ type: 'text',
351
+ text: `Tool "${toolName}" is not available in the current tool profile "${activeProfile.profileName}". To enable it, use TOOL_PROFILE=full or TOOL_PROFILE=standard, or add it to your profile in ${configPath}.`,
352
+ },
353
+ ],
354
+ isError: true,
355
+ };
356
+ }
357
+ switch (toolName) {
358
+ // Session Management
359
+ case 'context_session_start': {
360
+ const { name, description, continueFrom, projectDir, defaultChannel } = args;
361
+ // Project directory will be saved with the session if provided
362
+ // Get current git branch if available
363
+ let branch = null;
364
+ let gitDetected = false;
365
+ try {
366
+ const checkPath = projectDir || process.cwd();
367
+ // Try to detect if directory has git
368
+ const gitHeadPath = path.join(checkPath, '.git', 'HEAD');
369
+ if (fs.existsSync(gitHeadPath)) {
370
+ // Use simple-git to get proper branch info
371
+ const tempGit = (0, simple_git_1.simpleGit)(checkPath);
372
+ const branchInfo = await tempGit.branch();
373
+ branch = branchInfo.current;
374
+ gitDetected = true;
375
+ }
376
+ }
377
+ catch (_e) {
378
+ // Ignore git errors
379
+ }
380
+ // Derive default channel if not provided
381
+ let channel = defaultChannel;
382
+ if (!channel) {
383
+ channel = (0, channels_js_1.deriveDefaultChannel)(branch || undefined, name || undefined);
384
+ }
385
+ // Create new session using repository
386
+ const session = repositories.sessions.create({
387
+ name: name || `Session ${new Date().toISOString()}`,
388
+ description: description || '',
389
+ branch: branch || undefined,
390
+ working_directory: projectDir || undefined,
391
+ defaultChannel: channel,
392
+ });
393
+ // Copy context from previous session if specified
394
+ if (continueFrom) {
395
+ repositories.contexts.copyBetweenSessions(continueFrom, session.id);
396
+ }
397
+ currentSessionId = session.id;
398
+ let statusMessage = `Started new session: ${session.id}\nName: ${name || 'Unnamed'}\nChannel: ${channel}`;
399
+ if (projectDir) {
400
+ statusMessage += `\nProject directory: ${projectDir}`;
401
+ if (gitDetected) {
402
+ statusMessage += `\nGit branch: ${branch || 'unknown'}`;
403
+ }
404
+ else {
405
+ statusMessage += `\nGit: No repository found in project directory`;
406
+ }
407
+ }
408
+ else {
409
+ statusMessage += `\nGit branch: ${branch || 'unknown'}`;
410
+ // Provide helpful guidance about setting project directory
411
+ const cwdHasGit = fs.existsSync(path.join(process.cwd(), '.git'));
412
+ if (cwdHasGit) {
413
+ statusMessage += `\n\n💡 Tip: Your current directory has a git repository. To enable full git tracking, start a session with:\ncontext_session_start({ name: "${name || 'My Session'}", projectDir: "${process.cwd()}" })`;
414
+ }
415
+ else {
416
+ // Check for git repos in immediate subdirectories
417
+ const subdirs = fs
418
+ .readdirSync(process.cwd(), { withFileTypes: true })
419
+ .filter(dirent => dirent.isDirectory())
420
+ .map(dirent => dirent.name)
421
+ .filter(name => !name.startsWith('.'));
422
+ const gitSubdirs = subdirs.filter(dir => {
423
+ try {
424
+ return fs.existsSync(path.join(process.cwd(), dir, '.git'));
425
+ }
426
+ catch {
427
+ return false;
428
+ }
429
+ });
430
+ if (gitSubdirs.length > 0) {
431
+ statusMessage += `\n\n💡 Found git repositories in: ${gitSubdirs.join(', ')}`;
432
+ statusMessage += `\nTo enable git tracking, start a session with your project directory:`;
433
+ statusMessage += `\ncontext_session_start({ name: "${name || 'My Session'}", projectDir: "${path.join(process.cwd(), gitSubdirs[0])}" })`;
434
+ }
435
+ else {
436
+ statusMessage += `\n\n💡 To enable git tracking, start a session with your project directory:`;
437
+ statusMessage += `\ncontext_session_start({ name: "${name || 'My Session'}", projectDir: "/path/to/your/project" })`;
438
+ }
439
+ }
440
+ }
441
+ return {
442
+ content: [
443
+ {
444
+ type: 'text',
445
+ text: statusMessage,
446
+ },
447
+ ],
448
+ };
449
+ }
450
+ case 'context_set_project_dir': {
451
+ const { projectDir } = args;
452
+ const sessionId = ensureSession();
453
+ if (!projectDir) {
454
+ throw new Error('Project directory path is required');
455
+ }
456
+ // Verify the directory exists
457
+ if (!fs.existsSync(projectDir)) {
458
+ return {
459
+ content: [
460
+ {
461
+ type: 'text',
462
+ text: `Error: Directory not found: ${projectDir}`,
463
+ },
464
+ ],
465
+ };
466
+ }
467
+ // Update the current session's working directory
468
+ repositories.sessions.update(sessionId, { working_directory: projectDir });
469
+ // Try to get git info to verify it's a git repo
470
+ let gitInfo = 'No git repository found';
471
+ try {
472
+ const git = (0, simple_git_1.simpleGit)(projectDir);
473
+ const branchInfo = await git.branch();
474
+ const status = await git.status();
475
+ gitInfo = `Git repository detected\nBranch: ${branchInfo.current}\nStatus: ${status.modified.length} modified, ${status.created.length} new, ${status.deleted.length} deleted`;
476
+ }
477
+ catch (_e) {
478
+ // Not a git repo, that's okay
479
+ }
480
+ return {
481
+ content: [
482
+ {
483
+ type: 'text',
484
+ text: `Project directory set for session ${sessionId.substring(0, 8)}: ${projectDir}\n\n${gitInfo}`,
485
+ },
486
+ ],
487
+ };
488
+ }
489
+ case 'context_session_list': {
490
+ const { limit = 10 } = args;
491
+ const sessions = db
492
+ .prepare(`
493
+ SELECT id, name, description, branch, created_at,
494
+ (SELECT COUNT(*) FROM context_items WHERE session_id = sessions.id) as item_count
495
+ FROM sessions
496
+ ORDER BY created_at DESC
497
+ LIMIT ?
498
+ `)
499
+ .all(limit);
500
+ const sessionList = sessions
501
+ .map((s) => `• ${s.name} (${s.id.substring(0, 8)})\n Created: ${s.created_at}\n Items: ${s.item_count}\n Branch: ${s.branch || 'unknown'}`)
502
+ .join('\n\n');
503
+ return {
504
+ content: [
505
+ {
506
+ type: 'text',
507
+ text: `Recent sessions:\n\n${sessionList}`,
508
+ },
509
+ ],
510
+ };
511
+ }
512
+ // Enhanced Context Storage
513
+ case 'context_save': {
514
+ const { key, value, category, priority = 'normal', private: isPrivate = false, channel, } = args;
515
+ try {
516
+ const sessionId = ensureSession();
517
+ // Verify session exists before saving context
518
+ const session = repositories.sessions.getById(sessionId);
519
+ if (!session) {
520
+ // Session was deleted or corrupted, create a new one
521
+ console.warn(`Session ${sessionId} not found, creating new session`);
522
+ const newSession = repositories.sessions.create({
523
+ name: 'Recovery Session',
524
+ description: 'Auto-created after session corruption',
525
+ });
526
+ currentSessionId = newSession.id;
527
+ const _contextItem = repositories.contexts.save(newSession.id, {
528
+ key,
529
+ value,
530
+ category,
531
+ priority: priority,
532
+ isPrivate,
533
+ channel,
534
+ });
535
+ return {
536
+ content: [
537
+ {
538
+ type: 'text',
539
+ text: `Saved: ${key}\nCategory: ${category || 'none'}\nPriority: ${priority}\nSession: ${newSession.id.substring(0, 8)} (recovered)`,
540
+ },
541
+ ],
542
+ };
543
+ }
544
+ const contextItem = repositories.contexts.save(sessionId, {
545
+ key,
546
+ value,
547
+ category,
548
+ priority: priority,
549
+ isPrivate,
550
+ channel,
551
+ });
552
+ // Create embedding for semantic search
553
+ try {
554
+ const content = `${key}: ${value}`;
555
+ const metadata = { key, category, priority };
556
+ await vectorStore.storeDocument(contextItem.id, content, metadata);
557
+ }
558
+ catch (error) {
559
+ // Log but don't fail the save operation
560
+ console.error('Failed to create embedding:', error);
561
+ }
562
+ return {
563
+ content: [
564
+ {
565
+ type: 'text',
566
+ text: `Saved: ${key}\nCategory: ${category || 'none'}\nPriority: ${priority}\nChannel: ${contextItem.channel || 'general'}\nSession: ${sessionId.substring(0, 8)}`,
567
+ },
568
+ ],
569
+ };
570
+ }
571
+ catch (error) {
572
+ console.error('Context save error:', error);
573
+ // If it's a foreign key constraint error, try recovery
574
+ if (error.message?.includes('FOREIGN KEY constraint failed')) {
575
+ try {
576
+ console.warn('Foreign key constraint failed, attempting recovery...');
577
+ const newSession = repositories.sessions.create({
578
+ name: 'Emergency Recovery Session',
579
+ description: 'Created due to foreign key constraint failure',
580
+ });
581
+ currentSessionId = newSession.id;
582
+ const _contextItem = repositories.contexts.save(newSession.id, {
583
+ key,
584
+ value,
585
+ category,
586
+ priority: priority,
587
+ isPrivate,
588
+ channel,
589
+ });
590
+ return {
591
+ content: [
592
+ {
593
+ type: 'text',
594
+ text: `Saved: ${key}\nCategory: ${category || 'none'}\nPriority: ${priority}\nSession: ${newSession.id.substring(0, 8)} (emergency recovery)`,
595
+ },
596
+ ],
597
+ };
598
+ }
599
+ catch (recoveryError) {
600
+ return {
601
+ content: [
602
+ {
603
+ type: 'text',
604
+ text: `Failed to save context item: ${recoveryError.message}`,
605
+ },
606
+ ],
607
+ };
608
+ }
609
+ }
610
+ return {
611
+ content: [
612
+ {
613
+ type: 'text',
614
+ text: `Failed to save context item: ${error.message}`,
615
+ },
616
+ ],
617
+ };
618
+ }
619
+ }
620
+ case 'context_get': {
621
+ const { key, category, channel, channels, sessionId: specificSessionId, includeMetadata, sort, limit: rawLimit, offset: rawOffset, createdAfter, createdBefore, keyPattern, priorities, } = args;
622
+ // Current session for privacy checking (can see own private items)
623
+ const currentSession = currentSessionId || ensureSession();
624
+ // If user specified a session, use it for filtering; otherwise use current session
625
+ const filterSession = specificSessionId || currentSession;
626
+ // Dynamically calculate safe default limit based on actual data
627
+ const defaultLimit = (0, token_limits_js_1.calculateDynamicDefaultLimit)(filterSession, includeMetadata, db);
628
+ const paginationValidation = validatePaginationParams({
629
+ limit: rawLimit !== undefined ? rawLimit : defaultLimit,
630
+ offset: rawOffset,
631
+ });
632
+ const { limit, offset, errors: paginationErrors } = paginationValidation;
633
+ // Log pagination validation errors for debugging
634
+ if (paginationErrors.length > 0) {
635
+ debugLog('context_get pagination validation errors:', paginationErrors);
636
+ }
637
+ // Always use enhanced query to ensure consistent pagination
638
+ // This prevents token limit issues when querying large datasets
639
+ // Removed the conditional check since we always want to use this path
640
+ {
641
+ const result = repositories.contexts.queryEnhanced({
642
+ sessionId: currentSession, // Used for privacy check (is_private = 0 OR session_id = ?)
643
+ filterBySessionId: specificSessionId, // If specified, filter results to this session only
644
+ key,
645
+ category,
646
+ channel,
647
+ channels,
648
+ sort,
649
+ limit,
650
+ offset,
651
+ createdAfter,
652
+ createdBefore,
653
+ keyPattern,
654
+ priorities,
655
+ includeMetadata,
656
+ });
657
+ if (result.items.length === 0) {
658
+ return {
659
+ content: [
660
+ {
661
+ type: 'text',
662
+ text: 'No matching context found',
663
+ },
664
+ ],
665
+ };
666
+ }
667
+ // Use dynamic token limit checking
668
+ const tokenConfig = (0, token_limits_js_1.getTokenConfig)();
669
+ const { exceedsLimit, estimatedTokens, safeItemCount } = (0, token_limits_js_1.checkTokenLimit)(result.items, includeMetadata, tokenConfig);
670
+ let actualItems = result.items;
671
+ let wasTruncated = false;
672
+ let truncatedCount = 0;
673
+ if (exceedsLimit) {
674
+ // Truncate to safe item count
675
+ if (safeItemCount < result.items.length) {
676
+ actualItems = result.items.slice(0, safeItemCount);
677
+ wasTruncated = true;
678
+ truncatedCount = result.items.length - safeItemCount;
679
+ debugLog(`Token limit enforcement: Truncating from ${result.items.length} to ${safeItemCount} items`);
680
+ }
681
+ }
682
+ // Calculate response metrics for the actual items being returned
683
+ const metrics = calculateResponseMetrics(actualItems);
684
+ // Calculate pagination metadata
685
+ // Use the validated limit and offset from paginationValidation
686
+ const effectiveLimit = limit; // Already validated and defaulted
687
+ const effectiveOffset = offset; // Already validated and defaulted
688
+ const currentPage = effectiveLimit > 0 ? Math.floor(effectiveOffset / effectiveLimit) + 1 : 1;
689
+ const totalPages = effectiveLimit > 0 ? Math.ceil(result.totalCount / effectiveLimit) : 1;
690
+ // Update pagination to account for truncation
691
+ const hasNextPage = wasTruncated || currentPage < totalPages;
692
+ const hasPreviousPage = currentPage > 1;
693
+ // Calculate next offset accounting for truncation
694
+ const nextOffset = hasNextPage
695
+ ? wasTruncated
696
+ ? effectiveOffset + actualItems.length
697
+ : effectiveOffset + effectiveLimit
698
+ : null;
699
+ // Track whether defaults were applied
700
+ const defaultsApplied = {
701
+ limit: rawLimit === undefined,
702
+ sort: sort === undefined,
703
+ };
704
+ // Enhanced response format
705
+ if (includeMetadata) {
706
+ const itemsWithMetadata = actualItems.map(item => ({
707
+ key: item.key,
708
+ value: item.value,
709
+ category: item.category,
710
+ priority: item.priority,
711
+ channel: item.channel,
712
+ metadata: item.metadata ? JSON.parse(item.metadata) : null,
713
+ size: item.size || calculateSize(item.value),
714
+ created_at: item.created_at,
715
+ updated_at: item.updated_at,
716
+ }));
717
+ const response = {
718
+ items: itemsWithMetadata,
719
+ pagination: {
720
+ total: result.totalCount,
721
+ returned: actualItems.length,
722
+ offset: effectiveOffset,
723
+ hasMore: hasNextPage,
724
+ nextOffset: nextOffset,
725
+ // Extended pagination metadata
726
+ totalCount: result.totalCount,
727
+ page: currentPage,
728
+ pageSize: effectiveLimit,
729
+ totalPages: totalPages,
730
+ hasNextPage: hasNextPage,
731
+ hasPreviousPage: hasPreviousPage,
732
+ previousOffset: hasPreviousPage
733
+ ? Math.max(0, effectiveOffset - effectiveLimit)
734
+ : null,
735
+ // Size information
736
+ totalSize: metrics.totalSize,
737
+ averageSize: metrics.averageSize,
738
+ // Defaults applied
739
+ defaultsApplied: defaultsApplied,
740
+ // Truncation information
741
+ truncated: wasTruncated,
742
+ truncatedCount: truncatedCount,
743
+ },
744
+ };
745
+ // Add warning if truncation occurred
746
+ if (wasTruncated) {
747
+ response.pagination.warning = `Response truncated to prevent token overflow (estimated ${estimatedTokens} tokens). ${truncatedCount} items omitted. Use pagination with offset=${nextOffset} to retrieve remaining items.`;
748
+ response.pagination.tokenInfo = {
749
+ estimatedTokens,
750
+ maxAllowed: tokenConfig.mcpMaxTokens,
751
+ safeLimit: Math.floor(tokenConfig.mcpMaxTokens * tokenConfig.safetyBuffer),
752
+ };
753
+ }
754
+ else if (estimatedTokens > tokenConfig.mcpMaxTokens * token_limits_js_1.TOKEN_WARNING_THRESHOLD) {
755
+ response.pagination.warning =
756
+ 'Large result set approaching token limits. Consider using smaller limit or more specific filters.';
757
+ }
758
+ return {
759
+ content: [
760
+ {
761
+ type: 'text',
762
+ text: JSON.stringify(response, null, 2),
763
+ },
764
+ ],
765
+ };
766
+ }
767
+ // Return enhanced format for all queries to support pagination
768
+ const response = {
769
+ items: actualItems,
770
+ pagination: {
771
+ total: result.totalCount,
772
+ returned: actualItems.length,
773
+ offset: effectiveOffset,
774
+ hasMore: hasNextPage,
775
+ nextOffset: nextOffset,
776
+ // Truncation information
777
+ truncated: wasTruncated,
778
+ truncatedCount: truncatedCount,
779
+ },
780
+ };
781
+ // Add warning if truncation occurred
782
+ if (wasTruncated) {
783
+ response.pagination.warning = `Response truncated to prevent token overflow. ${truncatedCount} items omitted. Use pagination with offset=${nextOffset} to retrieve remaining items.`;
784
+ }
785
+ else if (estimatedTokens > tokenConfig.mcpMaxTokens * token_limits_js_1.TOKEN_WARNING_THRESHOLD) {
786
+ response.pagination.warning =
787
+ 'Large result set approaching token limits. Consider using smaller limit or more specific filters.';
788
+ }
789
+ return {
790
+ content: [
791
+ {
792
+ type: 'text',
793
+ text: JSON.stringify(response, null, 2),
794
+ },
795
+ ],
796
+ };
797
+ }
798
+ }
799
+ // File Caching
800
+ case 'context_cache_file': {
801
+ const { filePath, content } = args;
802
+ const sessionId = ensureSession();
803
+ const hash = calculateFileHash(content);
804
+ const stmt = db.prepare(`
805
+ INSERT OR REPLACE INTO file_cache (id, session_id, file_path, content, hash)
806
+ VALUES (?, ?, ?, ?, ?)
807
+ `);
808
+ stmt.run((0, uuid_1.v4)(), sessionId, filePath, content, hash);
809
+ return {
810
+ content: [
811
+ {
812
+ type: 'text',
813
+ text: `Cached file: ${filePath}\nHash: ${hash.substring(0, 16)}...\nSize: ${content.length} bytes`,
814
+ },
815
+ ],
816
+ };
817
+ }
818
+ case 'context_file_changed': {
819
+ const { filePath, currentContent } = args;
820
+ const sessionId = ensureSession();
821
+ const cached = db
822
+ .prepare('SELECT hash, content FROM file_cache WHERE session_id = ? AND file_path = ?')
823
+ .get(sessionId, filePath);
824
+ if (!cached) {
825
+ return {
826
+ content: [
827
+ {
828
+ type: 'text',
829
+ text: `No cached version found for: ${filePath}`,
830
+ },
831
+ ],
832
+ };
833
+ }
834
+ const currentHash = currentContent ? calculateFileHash(currentContent) : null;
835
+ const hasChanged = currentHash !== cached.hash;
836
+ return {
837
+ content: [
838
+ {
839
+ type: 'text',
840
+ text: `File: ${filePath}\nChanged: ${hasChanged}\nCached hash: ${cached.hash.substring(0, 16)}...\nCurrent hash: ${currentHash ? currentHash.substring(0, 16) + '...' : 'N/A'}`,
841
+ },
842
+ ],
843
+ };
844
+ }
845
+ case 'context_status': {
846
+ const sessionId = currentSessionId || ensureSession();
847
+ const stats = db
848
+ .prepare(`
849
+ SELECT
850
+ (SELECT COUNT(*) FROM context_items WHERE session_id = ?) as item_count,
851
+ (SELECT COUNT(*) FROM file_cache WHERE session_id = ?) as file_count,
852
+ (SELECT created_at FROM sessions WHERE id = ?) as session_created,
853
+ (SELECT name FROM sessions WHERE id = ?) as session_name
854
+ `)
855
+ .get(sessionId, sessionId, sessionId, sessionId);
856
+ const recentItems = db
857
+ .prepare(`
858
+ SELECT key, category, priority FROM context_items
859
+ WHERE session_id = ?
860
+ ORDER BY created_at DESC
861
+ LIMIT 5
862
+ `)
863
+ .all(sessionId);
864
+ const recentList = recentItems
865
+ .map((item) => ` • [${item.priority}] ${item.key} (${item.category || 'uncategorized'})`)
866
+ .join('\n');
867
+ return {
868
+ content: [
869
+ {
870
+ type: 'text',
871
+ text: `Current Session: ${stats.session_name}
872
+ Session ID: ${sessionId.substring(0, 8)}
873
+ Created: ${stats.session_created}
874
+ Context Items: ${stats.item_count}
875
+ Cached Files: ${stats.file_count}
876
+
877
+ Recent Items:
878
+ ${recentList || ' None'}`,
879
+ },
880
+ ],
881
+ };
882
+ }
883
+ // Phase 2: Checkpoint System
884
+ case 'context_checkpoint': {
885
+ const { name, description, includeFiles = true, includeGitStatus = true } = args;
886
+ const sessionId = ensureSession();
887
+ const checkpointId = (0, uuid_1.v4)();
888
+ // Get git status if requested
889
+ let gitStatus = null;
890
+ let gitBranch = null;
891
+ if (includeGitStatus) {
892
+ const gitInfo = await getGitStatus();
893
+ gitStatus = gitInfo.status;
894
+ gitBranch = gitInfo.branch;
895
+ }
896
+ // Create checkpoint
897
+ db.prepare(`
898
+ INSERT INTO checkpoints (id, session_id, name, description, git_status, git_branch)
899
+ VALUES (?, ?, ?, ?, ?, ?)
900
+ `).run(checkpointId, sessionId, name, description || '', gitStatus, gitBranch);
901
+ // Save context items
902
+ const contextItems = db
903
+ .prepare('SELECT id FROM context_items WHERE session_id = ?')
904
+ .all(sessionId);
905
+ const itemStmt = db.prepare('INSERT INTO checkpoint_items (id, checkpoint_id, context_item_id) VALUES (?, ?, ?)');
906
+ for (const item of contextItems) {
907
+ itemStmt.run((0, uuid_1.v4)(), checkpointId, item.id);
908
+ }
909
+ // Save file cache if requested
910
+ let fileCount = 0;
911
+ if (includeFiles) {
912
+ const files = db.prepare('SELECT id FROM file_cache WHERE session_id = ?').all(sessionId);
913
+ const fileStmt = db.prepare('INSERT INTO checkpoint_files (id, checkpoint_id, file_cache_id) VALUES (?, ?, ?)');
914
+ for (const file of files) {
915
+ fileStmt.run((0, uuid_1.v4)(), checkpointId, file.id);
916
+ fileCount++;
917
+ }
918
+ }
919
+ let statusText = `Created checkpoint: ${name}
920
+ ID: ${checkpointId.substring(0, 8)}
921
+ Context items: ${contextItems.length}
922
+ Cached files: ${fileCount}
923
+ Git branch: ${gitBranch || 'none'}
924
+ Git status: ${gitStatus ? 'captured' : 'not captured'}`;
925
+ // Add helpful message if git status was requested but no project directory is set
926
+ const currentSession = repositories.sessions.getById(sessionId);
927
+ if (includeGitStatus && (!currentSession || !currentSession.working_directory)) {
928
+ statusText += `\n\n💡 Note: Git status was requested but no project directory is set.
929
+ To enable git tracking, use context_set_project_dir with your project path.`;
930
+ }
931
+ return {
932
+ content: [
933
+ {
934
+ type: 'text',
935
+ text: statusText,
936
+ },
937
+ ],
938
+ };
939
+ }
940
+ case 'context_restore_checkpoint': {
941
+ const { name, checkpointId, restoreFiles = true } = args;
942
+ // Find checkpoint
943
+ let checkpoint;
944
+ if (checkpointId) {
945
+ checkpoint = db.prepare('SELECT * FROM checkpoints WHERE id = ?').get(checkpointId);
946
+ }
947
+ else if (name) {
948
+ checkpoint = db
949
+ .prepare('SELECT * FROM checkpoints ORDER BY created_at DESC')
950
+ .all()
951
+ .find((cp) => cp.name === name);
952
+ }
953
+ else {
954
+ // Get latest checkpoint
955
+ checkpoint = db.prepare('SELECT * FROM checkpoints ORDER BY created_at DESC LIMIT 1').get();
956
+ }
957
+ if (!checkpoint) {
958
+ return {
959
+ content: [
960
+ {
961
+ type: 'text',
962
+ text: 'No checkpoint found',
963
+ },
964
+ ],
965
+ };
966
+ }
967
+ const cp = checkpoint;
968
+ // Start new session from checkpoint
969
+ const newSessionId = (0, uuid_1.v4)();
970
+ db.prepare(`
971
+ INSERT INTO sessions (id, name, description, branch, working_directory)
972
+ VALUES (?, ?, ?, ?, ?)
973
+ `).run(newSessionId, `Restored from: ${cp.name}`, `Checkpoint ${cp.id.substring(0, 8)} created at ${cp.created_at}`, cp.git_branch, null);
974
+ // Restore context items
975
+ const contextItems = db
976
+ .prepare(`
977
+ SELECT ci.* FROM context_items ci
978
+ JOIN checkpoint_items cpi ON ci.id = cpi.context_item_id
979
+ WHERE cpi.checkpoint_id = ?
980
+ `)
981
+ .all(cp.id);
982
+ const itemStmt = db.prepare(`
983
+ INSERT INTO context_items (id, session_id, key, value, category, priority, size, created_at, updated_at)
984
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
985
+ `);
986
+ for (const item of contextItems) {
987
+ const itemData = item;
988
+ itemStmt.run((0, uuid_1.v4)(), newSessionId, itemData.key, itemData.value, itemData.category, itemData.priority, itemData.size || calculateSize(itemData.value), itemData.created_at);
989
+ }
990
+ // Restore file cache if requested
991
+ let fileCount = 0;
992
+ if (restoreFiles) {
993
+ const files = db
994
+ .prepare(`
995
+ SELECT fc.* FROM file_cache fc
996
+ JOIN checkpoint_files cpf ON fc.id = cpf.file_cache_id
997
+ WHERE cpf.checkpoint_id = ?
998
+ `)
999
+ .all(cp.id);
1000
+ const fileStmt = db.prepare(`
1001
+ INSERT INTO file_cache (id, session_id, file_path, content, hash, last_read)
1002
+ VALUES (?, ?, ?, ?, ?, ?)
1003
+ `);
1004
+ for (const file of files) {
1005
+ fileStmt.run((0, uuid_1.v4)(), newSessionId, file.file_path, file.content, file.hash, file.last_read);
1006
+ fileCount++;
1007
+ }
1008
+ }
1009
+ currentSessionId = newSessionId;
1010
+ // Get session information for enhanced messaging
1011
+ const sessionCount = db.prepare('SELECT COUNT(*) as count FROM sessions').get();
1012
+ const originalSession = db
1013
+ .prepare('SELECT name FROM sessions WHERE id = ?')
1014
+ .get(cp.session_id);
1015
+ return {
1016
+ content: [
1017
+ {
1018
+ type: 'text',
1019
+ text: `✅ Successfully restored from checkpoint: ${cp.name}
1020
+
1021
+ 🔄 Data Safety: A new session was created to preserve your current work
1022
+ 📋 New Session: ${newSessionId.substring(0, 8)} ("${`Restored from: ${cp.name}`}")
1023
+ 🔙 Original Session: ${originalSession?.name || 'Unknown'} remains accessible
1024
+
1025
+ 📊 Restored Data:
1026
+ - Context items: ${contextItems.length}
1027
+ - Files: ${fileCount}
1028
+ - Git branch: ${cp.git_branch || 'none'}
1029
+ - Checkpoint created: ${cp.created_at}
1030
+
1031
+ 💡 Next Steps:
1032
+ - You are now working in the restored session
1033
+ - Your previous work is safely preserved in session ${sessionCount.count - 1}
1034
+ - Use context_session_list to see all sessions
1035
+ - Switch sessions anytime without losing data
1036
+
1037
+ 🆘 Need your previous work? Use context_search_all to find items across sessions`,
1038
+ },
1039
+ ],
1040
+ };
1041
+ }
1042
+ // Phase 2: Summarization
1043
+ case 'context_summarize': {
1044
+ const { sessionId: specificSessionId, categories, maxLength } = args;
1045
+ const targetSessionId = specificSessionId || currentSessionId || ensureSession();
1046
+ const items = db
1047
+ .prepare(`
1048
+ SELECT * FROM context_items
1049
+ WHERE session_id = ?
1050
+ ORDER BY priority DESC, created_at DESC
1051
+ `)
1052
+ .all(targetSessionId);
1053
+ const summary = createSummary(items, { categories, maxLength });
1054
+ return {
1055
+ content: [
1056
+ {
1057
+ type: 'text',
1058
+ text: summary,
1059
+ },
1060
+ ],
1061
+ };
1062
+ }
1063
+ // Phase 3: Smart Compaction Helper
1064
+ case 'context_prepare_compaction': {
1065
+ const sessionId = ensureSession();
1066
+ // Get all high priority items
1067
+ const highPriorityItems = db
1068
+ .prepare(`
1069
+ SELECT * FROM context_items
1070
+ WHERE session_id = ? AND priority = 'high'
1071
+ ORDER BY created_at DESC
1072
+ `)
1073
+ .all(sessionId);
1074
+ // Get recent tasks
1075
+ const recentTasks = db
1076
+ .prepare(`
1077
+ SELECT * FROM context_items
1078
+ WHERE session_id = ? AND category = 'task'
1079
+ ORDER BY created_at DESC LIMIT 10
1080
+ `)
1081
+ .all(sessionId);
1082
+ // Get all decisions
1083
+ const decisions = db
1084
+ .prepare(`
1085
+ SELECT * FROM context_items
1086
+ WHERE session_id = ? AND category = 'decision'
1087
+ ORDER BY created_at DESC
1088
+ `)
1089
+ .all(sessionId);
1090
+ // Get files that changed
1091
+ const changedFiles = db
1092
+ .prepare(`
1093
+ SELECT file_path, hash FROM file_cache
1094
+ WHERE session_id = ?
1095
+ `)
1096
+ .all(sessionId);
1097
+ // Auto-create checkpoint
1098
+ const checkpointId = (0, uuid_1.v4)();
1099
+ const checkpointName = `auto-compaction-${new Date().toISOString()}`;
1100
+ const gitInfo = await getGitStatus();
1101
+ db.prepare(`
1102
+ INSERT INTO checkpoints (id, session_id, name, description, git_status, git_branch)
1103
+ VALUES (?, ?, ?, ?, ?, ?)
1104
+ `).run(checkpointId, sessionId, checkpointName, 'Automatic checkpoint before compaction', gitInfo.status, gitInfo.branch);
1105
+ // Save all context items to checkpoint
1106
+ const allItems = db
1107
+ .prepare('SELECT id FROM context_items WHERE session_id = ?')
1108
+ .all(sessionId);
1109
+ const itemStmt = db.prepare('INSERT INTO checkpoint_items (id, checkpoint_id, context_item_id) VALUES (?, ?, ?)');
1110
+ for (const item of allItems) {
1111
+ itemStmt.run((0, uuid_1.v4)(), checkpointId, item.id);
1112
+ }
1113
+ // Generate summary for next session
1114
+ const summary = createSummary([...highPriorityItems, ...recentTasks, ...decisions], {
1115
+ maxLength: 2000,
1116
+ });
1117
+ // Determine next steps
1118
+ const nextSteps = [];
1119
+ const unfinishedTasks = recentTasks.filter((t) => !t.value.toLowerCase().includes('completed') && !t.value.toLowerCase().includes('done'));
1120
+ unfinishedTasks.forEach((task) => {
1121
+ nextSteps.push(`Continue: ${task.key}`);
1122
+ });
1123
+ // Save prepared context
1124
+ const preparedContext = {
1125
+ checkpoint: checkpointName,
1126
+ summary,
1127
+ nextSteps,
1128
+ criticalItems: highPriorityItems.map((i) => ({ key: i.key, value: i.value })),
1129
+ decisions: decisions.map((d) => ({ key: d.key, value: d.value })),
1130
+ filesModified: changedFiles.length,
1131
+ gitBranch: gitInfo.branch,
1132
+ };
1133
+ // Save as special context item
1134
+ const preparedValue = JSON.stringify(preparedContext);
1135
+ db.prepare(`
1136
+ INSERT OR REPLACE INTO context_items (id, session_id, key, value, category, priority, size, updated_at)
1137
+ VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
1138
+ `).run((0, uuid_1.v4)(), sessionId, '_prepared_compaction', preparedValue, 'system', 'high', calculateSize(preparedValue));
1139
+ return {
1140
+ content: [
1141
+ {
1142
+ type: 'text',
1143
+ text: `Prepared for compaction:
1144
+
1145
+ Checkpoint: ${checkpointName}
1146
+ Critical items saved: ${highPriorityItems.length}
1147
+ Decisions preserved: ${decisions.length}
1148
+ Next steps identified: ${nextSteps.length}
1149
+ Files tracked: ${changedFiles.length}
1150
+
1151
+ Summary:
1152
+ ${summary.substring(0, 500)}${summary.length > 500 ? '...' : ''}
1153
+
1154
+ Next Steps:
1155
+ ${nextSteps.join('\n')}
1156
+
1157
+ To restore after compaction:
1158
+ mcp_context_restore_checkpoint({ name: "${checkpointName}" })`,
1159
+ },
1160
+ ],
1161
+ };
1162
+ }
1163
+ // Phase 3: Git Integration
1164
+ case 'context_git_commit': {
1165
+ const { message, autoSave = true } = args;
1166
+ const sessionId = ensureSession();
1167
+ // Check if project directory is set for this session
1168
+ const session = repositories.sessions.getById(sessionId);
1169
+ if (!session || !session.working_directory) {
1170
+ return {
1171
+ content: [
1172
+ {
1173
+ type: 'text',
1174
+ text: getProjectDirectorySetupMessage(),
1175
+ },
1176
+ ],
1177
+ };
1178
+ }
1179
+ if (autoSave) {
1180
+ // Save current context state
1181
+ const timestamp = new Date().toISOString();
1182
+ const commitValue = message || 'No commit message';
1183
+ db.prepare(`
1184
+ INSERT OR REPLACE INTO context_items (id, session_id, key, value, category, priority, size, updated_at)
1185
+ VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
1186
+ `).run((0, uuid_1.v4)(), sessionId, `commit_${timestamp}`, commitValue, 'git', 'normal', calculateSize(commitValue));
1187
+ // Create checkpoint
1188
+ const checkpointId = (0, uuid_1.v4)();
1189
+ const checkpointName = `git-commit-${timestamp}`;
1190
+ const gitInfo = await getGitStatus();
1191
+ db.prepare(`
1192
+ INSERT INTO checkpoints (id, session_id, name, description, git_status, git_branch)
1193
+ VALUES (?, ?, ?, ?, ?, ?)
1194
+ `).run(checkpointId, sessionId, checkpointName, `Git commit: ${message || 'No message'}`, gitInfo.status, gitInfo.branch);
1195
+ // Link current context to checkpoint
1196
+ const items = db
1197
+ .prepare('SELECT id FROM context_items WHERE session_id = ?')
1198
+ .all(sessionId);
1199
+ const itemStmt = db.prepare('INSERT INTO checkpoint_items (id, checkpoint_id, context_item_id) VALUES (?, ?, ?)');
1200
+ for (const item of items) {
1201
+ itemStmt.run((0, uuid_1.v4)(), checkpointId, item.id);
1202
+ }
1203
+ }
1204
+ // Execute git commit
1205
+ try {
1206
+ const git = (0, simple_git_1.simpleGit)(session.working_directory);
1207
+ await git.add('.');
1208
+ const commitResult = await git.commit(message || 'Commit via Memory Keeper');
1209
+ return {
1210
+ content: [
1211
+ {
1212
+ type: 'text',
1213
+ text: `Git commit successful!
1214
+ Commit: ${commitResult.commit}
1215
+ Context saved: ${autoSave ? 'Yes' : 'No'}
1216
+ Checkpoint: ${autoSave ? `git-commit-${new Date().toISOString()}` : 'None'}`,
1217
+ },
1218
+ ],
1219
+ };
1220
+ }
1221
+ catch (error) {
1222
+ return {
1223
+ content: [
1224
+ {
1225
+ type: 'text',
1226
+ text: `Git commit failed: ${error.message}`,
1227
+ },
1228
+ ],
1229
+ };
1230
+ }
1231
+ }
1232
+ // Phase 3: Context Search
1233
+ case 'context_search': {
1234
+ const { query, searchIn = ['key', 'value'], sessionId: specificSessionId, category, channel, channels, sort, limit, offset, createdAfter, createdBefore, keyPattern, priorities, includeMetadata, matchMode, useFts5, } = args;
1235
+ const targetSessionId = specificSessionId || currentSessionId || ensureSession();
1236
+ // Use enhanced search for all cases
1237
+ const result = repositories.contexts.searchEnhanced({
1238
+ query,
1239
+ sessionId: targetSessionId,
1240
+ searchIn,
1241
+ category,
1242
+ channel,
1243
+ channels,
1244
+ sort,
1245
+ limit,
1246
+ offset,
1247
+ createdAfter,
1248
+ createdBefore,
1249
+ keyPattern,
1250
+ priorities,
1251
+ includeMetadata,
1252
+ matchMode,
1253
+ useFts5,
1254
+ });
1255
+ if (result.items.length === 0) {
1256
+ return {
1257
+ content: [
1258
+ {
1259
+ type: 'text',
1260
+ text: `No results found for: "${query}"`,
1261
+ },
1262
+ ],
1263
+ };
1264
+ }
1265
+ // Enhanced response format with metadata
1266
+ if (includeMetadata) {
1267
+ const itemsWithMetadata = result.items.map(item => ({
1268
+ key: item.key,
1269
+ value: item.value,
1270
+ category: item.category,
1271
+ priority: item.priority,
1272
+ channel: item.channel,
1273
+ metadata: item.metadata ? JSON.parse(item.metadata) : null,
1274
+ size: item.size || calculateSize(item.value),
1275
+ created_at: item.created_at,
1276
+ updated_at: item.updated_at,
1277
+ }));
1278
+ return {
1279
+ content: [
1280
+ {
1281
+ type: 'text',
1282
+ text: JSON.stringify({
1283
+ items: itemsWithMetadata,
1284
+ totalCount: result.totalCount,
1285
+ page: offset && limit ? Math.floor(offset / limit) + 1 : 1,
1286
+ pageSize: limit || result.items.length,
1287
+ query,
1288
+ }, null, 2),
1289
+ },
1290
+ ],
1291
+ };
1292
+ }
1293
+ // Backward compatible format
1294
+ const resultText = result.items
1295
+ .map((r) => `• [${r.priority}] ${r.key} (${r.category || 'none'})\n ${r.value.substring(0, 100)}${r.value.length > 100 ? '...' : ''}`)
1296
+ .join('\n\n');
1297
+ return {
1298
+ content: [
1299
+ {
1300
+ type: 'text',
1301
+ text: `Found ${result.items.length} results for "${query}":\n\n${resultText}`,
1302
+ },
1303
+ ],
1304
+ };
1305
+ }
1306
+ // Phase 3: Export/Import
1307
+ case 'context_export': {
1308
+ const { sessionId: specificSessionId, format = 'json', includeStats = false, confirmEmpty = false, } = args;
1309
+ const targetSessionId = specificSessionId || currentSessionId;
1310
+ // Phase 1: Validation
1311
+ if (!targetSessionId) {
1312
+ throw new Error('No session ID provided and no current session active');
1313
+ }
1314
+ // Check if session exists
1315
+ const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(targetSessionId);
1316
+ if (!session) {
1317
+ throw new Error(`Session not found: ${targetSessionId}`);
1318
+ }
1319
+ // Get session data
1320
+ const contextItems = db
1321
+ .prepare('SELECT * FROM context_items WHERE session_id = ?')
1322
+ .all(targetSessionId);
1323
+ const fileCache = db
1324
+ .prepare('SELECT * FROM file_cache WHERE session_id = ?')
1325
+ .all(targetSessionId);
1326
+ const checkpoints = db
1327
+ .prepare('SELECT * FROM checkpoints WHERE session_id = ?')
1328
+ .all(targetSessionId);
1329
+ // Check if session is empty
1330
+ const isEmpty = contextItems.length === 0 && fileCache.length === 0 && checkpoints.length === 0;
1331
+ if (isEmpty && !confirmEmpty) {
1332
+ return {
1333
+ content: [
1334
+ {
1335
+ type: 'text',
1336
+ text: 'Warning: Session appears to be empty. No context items, files, or checkpoints found.\n\nTo export anyway, use confirmEmpty: true',
1337
+ },
1338
+ ],
1339
+ isEmpty: true,
1340
+ requiresConfirmation: true,
1341
+ };
1342
+ }
1343
+ const exportData = {
1344
+ version: '0.4.0',
1345
+ exported: new Date().toISOString(),
1346
+ session,
1347
+ contextItems,
1348
+ fileCache,
1349
+ checkpoints,
1350
+ metadata: {
1351
+ itemCount: contextItems.length,
1352
+ fileCount: fileCache.length,
1353
+ checkpointCount: checkpoints.length,
1354
+ totalSize: JSON.stringify({ contextItems, fileCache, checkpoints }).length,
1355
+ },
1356
+ };
1357
+ if (format === 'json') {
1358
+ const exportPath = path.join(os.tmpdir(), `memory-keeper-export-${targetSessionId.substring(0, 8)}.json`);
1359
+ // Check write permissions
1360
+ try {
1361
+ fs.writeFileSync(exportPath, JSON.stringify(exportData, null, 2));
1362
+ }
1363
+ catch (error) {
1364
+ if (error.code === 'EACCES') {
1365
+ throw new Error(`Permission denied: Cannot write to ${exportPath}`);
1366
+ }
1367
+ throw error;
1368
+ }
1369
+ const stats = {
1370
+ items: contextItems.length,
1371
+ files: fileCache.length,
1372
+ checkpoints: checkpoints.length,
1373
+ size: fs.statSync(exportPath).size,
1374
+ };
1375
+ return {
1376
+ content: [
1377
+ {
1378
+ type: 'text',
1379
+ text: includeStats
1380
+ ? `✅ Successfully exported session "${session.name}" to: ${exportPath}
1381
+
1382
+ 📊 Export Statistics:
1383
+ - Context Items: ${stats.items}
1384
+ - Cached Files: ${stats.files}
1385
+ - Checkpoints: ${stats.checkpoints}
1386
+ - Export Size: ${(stats.size / 1024).toFixed(2)} KB
1387
+
1388
+ Session ID: ${targetSessionId}`
1389
+ : `Exported session to: ${exportPath}
1390
+ Items: ${stats.items}
1391
+ Files: ${stats.files}`,
1392
+ },
1393
+ ],
1394
+ exportPath,
1395
+ statistics: stats,
1396
+ };
1397
+ }
1398
+ // Inline format
1399
+ return {
1400
+ content: [
1401
+ {
1402
+ type: 'text',
1403
+ text: JSON.stringify(exportData, null, 2),
1404
+ },
1405
+ ],
1406
+ statistics: {
1407
+ items: contextItems.length,
1408
+ files: fileCache.length,
1409
+ checkpoints: checkpoints.length,
1410
+ },
1411
+ };
1412
+ }
1413
+ case 'context_import': {
1414
+ const { filePath, merge = false } = args;
1415
+ try {
1416
+ const importData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
1417
+ // Create new session or merge
1418
+ let targetSessionId;
1419
+ if (merge && currentSessionId) {
1420
+ targetSessionId = currentSessionId;
1421
+ }
1422
+ else {
1423
+ targetSessionId = (0, uuid_1.v4)();
1424
+ const importedSession = importData.session;
1425
+ db.prepare(`
1426
+ INSERT INTO sessions (id, name, description, branch, working_directory, created_at)
1427
+ VALUES (?, ?, ?, ?, ?, ?)
1428
+ `).run(targetSessionId, `Imported: ${importedSession.name}`, `Imported from ${filePath} on ${new Date().toISOString()}`, importedSession.branch, null, new Date().toISOString());
1429
+ currentSessionId = targetSessionId;
1430
+ }
1431
+ // Import context items
1432
+ const itemStmt = db.prepare(`
1433
+ INSERT OR REPLACE INTO context_items (id, session_id, key, value, category, priority, size, created_at, updated_at)
1434
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
1435
+ `);
1436
+ let itemCount = 0;
1437
+ for (const item of importData.contextItems) {
1438
+ itemStmt.run((0, uuid_1.v4)(), targetSessionId, item.key, item.value, item.category, item.priority, item.size || calculateSize(item.value), item.created_at);
1439
+ itemCount++;
1440
+ }
1441
+ // Import file cache
1442
+ const fileStmt = db.prepare(`
1443
+ INSERT OR REPLACE INTO file_cache (id, session_id, file_path, content, hash, last_read)
1444
+ VALUES (?, ?, ?, ?, ?, ?)
1445
+ `);
1446
+ let fileCount = 0;
1447
+ for (const file of importData.fileCache || []) {
1448
+ fileStmt.run((0, uuid_1.v4)(), targetSessionId, file.file_path, file.content, file.hash, file.last_read);
1449
+ fileCount++;
1450
+ }
1451
+ return {
1452
+ content: [
1453
+ {
1454
+ type: 'text',
1455
+ text: `Import successful!
1456
+ Session: ${targetSessionId.substring(0, 8)}
1457
+ Context items: ${itemCount}
1458
+ Files: ${fileCount}
1459
+ Mode: ${merge ? 'Merged' : 'New session'}`,
1460
+ },
1461
+ ],
1462
+ };
1463
+ }
1464
+ catch (error) {
1465
+ return {
1466
+ content: [
1467
+ {
1468
+ type: 'text',
1469
+ text: `Import failed: ${error.message}`,
1470
+ },
1471
+ ],
1472
+ };
1473
+ }
1474
+ }
1475
+ // Phase 4.1: Knowledge Graph Tools
1476
+ case 'context_analyze': {
1477
+ const { sessionId, categories } = args;
1478
+ const targetSessionId = sessionId || ensureSession();
1479
+ try {
1480
+ // Get context items to analyze
1481
+ let query = 'SELECT * FROM context_items WHERE session_id = ?';
1482
+ const params = [targetSessionId];
1483
+ if (categories && categories.length > 0) {
1484
+ query += ` AND category IN (${categories.map(() => '?').join(',')})`;
1485
+ params.push(...categories);
1486
+ }
1487
+ const items = db.prepare(query).all(...params);
1488
+ let entitiesCreated = 0;
1489
+ let relationsCreated = 0;
1490
+ // Analyze each context item
1491
+ for (const item of items) {
1492
+ const analysis = knowledgeGraph.analyzeContext(targetSessionId, item.value);
1493
+ // Create entities
1494
+ for (const entityData of analysis.entities) {
1495
+ const existing = knowledgeGraph.findEntity(targetSessionId, entityData.name, entityData.type);
1496
+ if (!existing) {
1497
+ knowledgeGraph.createEntity(targetSessionId, entityData.type, entityData.name, {
1498
+ confidence: entityData.confidence,
1499
+ source: item.key,
1500
+ });
1501
+ entitiesCreated++;
1502
+ }
1503
+ }
1504
+ // Create relations
1505
+ for (const relationData of analysis.relations) {
1506
+ const subject = knowledgeGraph.findEntity(targetSessionId, relationData.subject);
1507
+ const object = knowledgeGraph.findEntity(targetSessionId, relationData.object);
1508
+ if (subject && object) {
1509
+ knowledgeGraph.createRelation(targetSessionId, subject.id, relationData.predicate, object.id, relationData.confidence);
1510
+ relationsCreated++;
1511
+ }
1512
+ }
1513
+ }
1514
+ // Get summary statistics
1515
+ const entityStats = db
1516
+ .prepare(`
1517
+ SELECT type, COUNT(*) as count
1518
+ FROM entities
1519
+ WHERE session_id = ?
1520
+ GROUP BY type
1521
+ `)
1522
+ .all(targetSessionId);
1523
+ return {
1524
+ content: [
1525
+ {
1526
+ type: 'text',
1527
+ text: `Analysis complete!
1528
+ Items analyzed: ${items.length}
1529
+ Entities created: ${entitiesCreated}
1530
+ Relations created: ${relationsCreated}
1531
+
1532
+ Entity breakdown:
1533
+ ${entityStats.map(s => `- ${s.type}: ${s.count}`).join('\n')}`,
1534
+ },
1535
+ ],
1536
+ };
1537
+ }
1538
+ catch (error) {
1539
+ return {
1540
+ content: [
1541
+ {
1542
+ type: 'text',
1543
+ text: `Analysis failed: ${error.message}`,
1544
+ },
1545
+ ],
1546
+ };
1547
+ }
1548
+ }
1549
+ case 'context_find_related': {
1550
+ const { key, relationTypes, maxDepth = 2 } = args;
1551
+ const sessionId = ensureSession();
1552
+ try {
1553
+ // First try to find as entity
1554
+ let entity = knowledgeGraph.findEntity(sessionId, key);
1555
+ // If not found as entity, check if it's a context key
1556
+ if (!entity) {
1557
+ const contextItem = db
1558
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
1559
+ .get(sessionId, key);
1560
+ if (contextItem) {
1561
+ // Try to extract entities from the context value
1562
+ const analysis = knowledgeGraph.analyzeContext(sessionId, contextItem.value);
1563
+ if (analysis.entities.length > 0) {
1564
+ entity = knowledgeGraph.findEntity(sessionId, analysis.entities[0].name);
1565
+ }
1566
+ }
1567
+ }
1568
+ if (!entity) {
1569
+ return {
1570
+ content: [
1571
+ {
1572
+ type: 'text',
1573
+ text: `No entity found for key: ${key}`,
1574
+ },
1575
+ ],
1576
+ };
1577
+ }
1578
+ // Get connected entities
1579
+ const connectedIds = knowledgeGraph.getConnectedEntities(entity.id, maxDepth);
1580
+ // Get details for connected entities
1581
+ const entities = Array.from(connectedIds).map(id => {
1582
+ const entityData = db.prepare('SELECT * FROM entities WHERE id = ?').get(id);
1583
+ const relations = knowledgeGraph.getRelations(id);
1584
+ const observations = knowledgeGraph.getObservations(id);
1585
+ return {
1586
+ ...entityData,
1587
+ attributes: entityData.attributes ? JSON.parse(entityData.attributes) : {},
1588
+ relations: relations.length,
1589
+ observations: observations.length,
1590
+ };
1591
+ });
1592
+ // Filter by relation types if specified
1593
+ let relevantRelations = knowledgeGraph.getRelations(entity.id);
1594
+ if (relationTypes && relationTypes.length > 0) {
1595
+ relevantRelations = relevantRelations.filter(r => relationTypes.includes(r.predicate));
1596
+ }
1597
+ return {
1598
+ content: [
1599
+ {
1600
+ type: 'text',
1601
+ text: `Related entities for "${key}":
1602
+
1603
+ Found ${entities.length} connected entities (max depth: ${maxDepth})
1604
+
1605
+ Main entity:
1606
+ - Type: ${entity.type}
1607
+ - Name: ${entity.name}
1608
+ - Direct relations: ${relevantRelations.length}
1609
+
1610
+ Connected entities:
1611
+ ${entities
1612
+ .slice(0, 20)
1613
+ .map(e => `- ${e.type}: ${e.name} (${e.relations} relations, ${e.observations} observations)`)
1614
+ .join('\n')}
1615
+ ${entities.length > 20 ? `\n... and ${entities.length - 20} more` : ''}`,
1616
+ },
1617
+ ],
1618
+ };
1619
+ }
1620
+ catch (error) {
1621
+ return {
1622
+ content: [
1623
+ {
1624
+ type: 'text',
1625
+ text: `Find related failed: ${error.message}`,
1626
+ },
1627
+ ],
1628
+ };
1629
+ }
1630
+ }
1631
+ case 'context_visualize': {
1632
+ const { type = 'graph', entityTypes, sessionId } = args;
1633
+ const targetSessionId = sessionId || ensureSession();
1634
+ try {
1635
+ if (type === 'graph') {
1636
+ const graphData = knowledgeGraph.getGraphData(targetSessionId, entityTypes);
1637
+ return {
1638
+ content: [
1639
+ {
1640
+ type: 'text',
1641
+ text: JSON.stringify(graphData, null, 2),
1642
+ },
1643
+ ],
1644
+ };
1645
+ }
1646
+ else if (type === 'timeline') {
1647
+ // Get time-based data
1648
+ const timeline = db
1649
+ .prepare(`
1650
+ SELECT
1651
+ strftime('%Y-%m-%d %H:00', created_at) as hour,
1652
+ COUNT(*) as events,
1653
+ GROUP_CONCAT(DISTINCT category) as categories
1654
+ FROM context_items
1655
+ WHERE session_id = ?
1656
+ GROUP BY hour
1657
+ ORDER BY hour DESC
1658
+ LIMIT 24
1659
+ `)
1660
+ .all(targetSessionId);
1661
+ return {
1662
+ content: [
1663
+ {
1664
+ type: 'text',
1665
+ text: JSON.stringify({
1666
+ type: 'timeline',
1667
+ data: timeline,
1668
+ }, null, 2),
1669
+ },
1670
+ ],
1671
+ };
1672
+ }
1673
+ else if (type === 'heatmap') {
1674
+ // Get category/priority heatmap data
1675
+ const heatmap = db
1676
+ .prepare(`
1677
+ SELECT
1678
+ category,
1679
+ priority,
1680
+ COUNT(*) as count
1681
+ FROM context_items
1682
+ WHERE session_id = ?
1683
+ GROUP BY category, priority
1684
+ `)
1685
+ .all(targetSessionId);
1686
+ return {
1687
+ content: [
1688
+ {
1689
+ type: 'text',
1690
+ text: JSON.stringify({
1691
+ type: 'heatmap',
1692
+ data: heatmap,
1693
+ }, null, 2),
1694
+ },
1695
+ ],
1696
+ };
1697
+ }
1698
+ return {
1699
+ content: [
1700
+ {
1701
+ type: 'text',
1702
+ text: `Unknown visualization type: ${type}`,
1703
+ },
1704
+ ],
1705
+ };
1706
+ }
1707
+ catch (error) {
1708
+ return {
1709
+ content: [
1710
+ {
1711
+ type: 'text',
1712
+ text: `Visualization failed: ${error.message}`,
1713
+ },
1714
+ ],
1715
+ };
1716
+ }
1717
+ }
1718
+ // Phase 4.2: Semantic Search
1719
+ case 'context_semantic_search': {
1720
+ const { query, topK = 10, minSimilarity = 0.3, sessionId } = args;
1721
+ const targetSessionId = sessionId || ensureSession();
1722
+ try {
1723
+ // Ensure embeddings are up to date for the session
1724
+ const _embeddingCount = await vectorStore.updateSessionEmbeddings(targetSessionId);
1725
+ // Perform semantic search
1726
+ const results = await vectorStore.searchInSession(targetSessionId, query, topK, minSimilarity);
1727
+ if (results.length === 0) {
1728
+ return {
1729
+ content: [
1730
+ {
1731
+ type: 'text',
1732
+ text: `No results found for query: "${query}"`,
1733
+ },
1734
+ ],
1735
+ };
1736
+ }
1737
+ // Format results
1738
+ let response = `Found ${results.length} results for: "${query}"\n\n`;
1739
+ results.forEach((result, index) => {
1740
+ const similarity = (result.similarity * 100).toFixed(1);
1741
+ response += `${index + 1}. [${similarity}% match]\n`;
1742
+ // Extract key and value from content
1743
+ const colonIndex = result.content.indexOf(':');
1744
+ if (colonIndex > -1) {
1745
+ const key = result.content.substring(0, colonIndex);
1746
+ const value = result.content.substring(colonIndex + 1).trim();
1747
+ response += ` Key: ${key}\n`;
1748
+ response += ` Value: ${value.substring(0, 200)}${value.length > 200 ? '...' : ''}\n`;
1749
+ }
1750
+ else {
1751
+ response += ` ${result.content.substring(0, 200)}${result.content.length > 200 ? '...' : ''}\n`;
1752
+ }
1753
+ if (result.metadata) {
1754
+ if (result.metadata.category) {
1755
+ response += ` Category: ${result.metadata.category}`;
1756
+ }
1757
+ if (result.metadata.priority) {
1758
+ response += `, Priority: ${result.metadata.priority}`;
1759
+ }
1760
+ response += '\n';
1761
+ }
1762
+ response += '\n';
1763
+ });
1764
+ return {
1765
+ content: [
1766
+ {
1767
+ type: 'text',
1768
+ text: response,
1769
+ },
1770
+ ],
1771
+ };
1772
+ }
1773
+ catch (error) {
1774
+ return {
1775
+ content: [
1776
+ {
1777
+ type: 'text',
1778
+ text: `Semantic search failed: ${error.message}`,
1779
+ },
1780
+ ],
1781
+ };
1782
+ }
1783
+ }
1784
+ // Phase 4.3: Multi-Agent System
1785
+ case 'context_delegate': {
1786
+ const { taskType, input, sessionId, chain = false } = args;
1787
+ const targetSessionId = sessionId || ensureSession();
1788
+ try {
1789
+ // Create agent task
1790
+ const task = {
1791
+ id: (0, uuid_1.v4)(),
1792
+ type: taskType,
1793
+ input: {
1794
+ ...input,
1795
+ sessionId: targetSessionId,
1796
+ },
1797
+ };
1798
+ // Process with agents
1799
+ let results;
1800
+ if (chain && Array.isArray(input)) {
1801
+ // Process as a chain of tasks
1802
+ const tasks = input.map((inp, index) => ({
1803
+ id: (0, uuid_1.v4)(),
1804
+ type: Array.isArray(taskType) ? taskType[index] : taskType,
1805
+ input: { ...inp, sessionId: targetSessionId },
1806
+ }));
1807
+ results = await agentCoordinator.processChain(tasks);
1808
+ }
1809
+ else {
1810
+ // Single task delegation
1811
+ results = await agentCoordinator.delegate(task);
1812
+ }
1813
+ // Format response
1814
+ let response = `Agent Processing Results:\n\n`;
1815
+ for (const result of results) {
1816
+ response += `## ${result.agentType.toUpperCase()} Agent\n`;
1817
+ response += `Confidence: ${(result.confidence * 100).toFixed(0)}%\n`;
1818
+ response += `Processing Time: ${result.processingTime}ms\n`;
1819
+ if (result.reasoning) {
1820
+ response += `Reasoning: ${result.reasoning}\n`;
1821
+ }
1822
+ response += `\nOutput:\n`;
1823
+ response += JSON.stringify(result.output, null, 2);
1824
+ response += '\n\n---\n\n';
1825
+ }
1826
+ // Get best result if multiple agents processed
1827
+ if (results.length > 1) {
1828
+ const best = agentCoordinator.getBestResult(task.id);
1829
+ if (best) {
1830
+ response += `\n## Best Result (${best.agentType}, ${(best.confidence * 100).toFixed(0)}% confidence):\n`;
1831
+ response += JSON.stringify(best.output, null, 2);
1832
+ }
1833
+ }
1834
+ return {
1835
+ content: [
1836
+ {
1837
+ type: 'text',
1838
+ text: response,
1839
+ },
1840
+ ],
1841
+ };
1842
+ }
1843
+ catch (error) {
1844
+ return {
1845
+ content: [
1846
+ {
1847
+ type: 'text',
1848
+ text: `Agent delegation failed: ${error.message}`,
1849
+ },
1850
+ ],
1851
+ };
1852
+ }
1853
+ }
1854
+ // Phase 4.4: Session Branching
1855
+ case 'context_branch_session': {
1856
+ const { branchName, copyDepth = 'shallow' } = args;
1857
+ const sourceSessionId = ensureSession();
1858
+ try {
1859
+ // Get source session info
1860
+ const sourceSession = db
1861
+ .prepare('SELECT * FROM sessions WHERE id = ?')
1862
+ .get(sourceSessionId);
1863
+ if (!sourceSession) {
1864
+ throw new Error('Source session not found');
1865
+ }
1866
+ // Create new branch session
1867
+ const branchId = (0, uuid_1.v4)();
1868
+ db.prepare(`
1869
+ INSERT INTO sessions (id, name, description, branch, working_directory, parent_id)
1870
+ VALUES (?, ?, ?, ?, ?, ?)
1871
+ `).run(branchId, branchName, `Branch of ${sourceSession.name} created at ${new Date().toISOString()}`, sourceSession.branch, null, sourceSessionId);
1872
+ if (copyDepth === 'deep') {
1873
+ // Copy all context items
1874
+ const items = db
1875
+ .prepare('SELECT * FROM context_items WHERE session_id = ?')
1876
+ .all(sourceSessionId);
1877
+ const stmt = db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)');
1878
+ for (const item of items) {
1879
+ stmt.run((0, uuid_1.v4)(), branchId, item.key, item.value, item.category, item.priority, item.created_at);
1880
+ }
1881
+ // Copy file cache
1882
+ const files = db
1883
+ .prepare('SELECT * FROM file_cache WHERE session_id = ?')
1884
+ .all(sourceSessionId);
1885
+ const fileStmt = db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash, last_read) VALUES (?, ?, ?, ?, ?, ?)');
1886
+ for (const file of files) {
1887
+ fileStmt.run((0, uuid_1.v4)(), branchId, file.file_path, file.content, file.hash, file.last_read);
1888
+ }
1889
+ }
1890
+ else {
1891
+ // Shallow copy - only copy high priority items
1892
+ const items = db
1893
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND priority = ?')
1894
+ .all(sourceSessionId, 'high');
1895
+ const stmt = db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)');
1896
+ for (const item of items) {
1897
+ stmt.run((0, uuid_1.v4)(), branchId, item.key, item.value, item.category, item.priority, item.created_at);
1898
+ }
1899
+ }
1900
+ // Switch to the new branch
1901
+ currentSessionId = branchId;
1902
+ return {
1903
+ content: [
1904
+ {
1905
+ type: 'text',
1906
+ text: `Created branch session: ${branchName}
1907
+ ID: ${branchId}
1908
+ Parent: ${sourceSession.name} (${sourceSessionId.substring(0, 8)})
1909
+ Copy depth: ${copyDepth}
1910
+ Items copied: ${copyDepth === 'deep' ? 'All' : 'High priority only'}
1911
+
1912
+ Now working in branch: ${branchName}`,
1913
+ },
1914
+ ],
1915
+ };
1916
+ }
1917
+ catch (error) {
1918
+ return {
1919
+ content: [
1920
+ {
1921
+ type: 'text',
1922
+ text: `Branch creation failed: ${error.message}`,
1923
+ },
1924
+ ],
1925
+ };
1926
+ }
1927
+ }
1928
+ // Phase 4.4: Session Merging
1929
+ case 'context_merge_sessions': {
1930
+ const { sourceSessionId, conflictResolution = 'keep_current' } = args;
1931
+ const targetSessionId = ensureSession();
1932
+ try {
1933
+ // Get both sessions
1934
+ const sourceSession = db
1935
+ .prepare('SELECT * FROM sessions WHERE id = ?')
1936
+ .get(sourceSessionId);
1937
+ const targetSession = db
1938
+ .prepare('SELECT * FROM sessions WHERE id = ?')
1939
+ .get(targetSessionId);
1940
+ if (!sourceSession) {
1941
+ throw new Error('Source session not found');
1942
+ }
1943
+ // Get items from source session
1944
+ const sourceItems = db
1945
+ .prepare('SELECT * FROM context_items WHERE session_id = ?')
1946
+ .all(sourceSessionId);
1947
+ let merged = 0;
1948
+ let skipped = 0;
1949
+ for (const item of sourceItems) {
1950
+ // Check if item exists in target
1951
+ const existing = db
1952
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
1953
+ .get(targetSessionId, item.key);
1954
+ if (existing) {
1955
+ // Handle conflict
1956
+ if (conflictResolution === 'keep_source' ||
1957
+ (conflictResolution === 'keep_newest' &&
1958
+ new Date(item.created_at) > new Date(existing.created_at))) {
1959
+ db.prepare('UPDATE context_items SET value = ?, category = ?, priority = ? WHERE session_id = ? AND key = ?').run(item.value, item.category, item.priority, targetSessionId, item.key);
1960
+ merged++;
1961
+ }
1962
+ else {
1963
+ skipped++;
1964
+ }
1965
+ }
1966
+ else {
1967
+ // No conflict, insert item
1968
+ db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), targetSessionId, item.key, item.value, item.category, item.priority, item.created_at);
1969
+ merged++;
1970
+ }
1971
+ }
1972
+ return {
1973
+ content: [
1974
+ {
1975
+ type: 'text',
1976
+ text: `Merge completed!
1977
+ Source: ${sourceSession.name} (${sourceSessionId.substring(0, 8)})
1978
+ Target: ${targetSession.name} (${targetSessionId.substring(0, 8)})
1979
+ Items merged: ${merged}
1980
+ Items skipped: ${skipped}
1981
+ Conflict resolution: ${conflictResolution}`,
1982
+ },
1983
+ ],
1984
+ };
1985
+ }
1986
+ catch (error) {
1987
+ return {
1988
+ content: [
1989
+ {
1990
+ type: 'text',
1991
+ text: `Session merge failed: ${error.message}`,
1992
+ },
1993
+ ],
1994
+ };
1995
+ }
1996
+ }
1997
+ // Phase 4.4: Journal Entry
1998
+ case 'context_journal_entry': {
1999
+ const { entry, tags = [], mood } = args;
2000
+ const sessionId = ensureSession();
2001
+ try {
2002
+ const id = (0, uuid_1.v4)();
2003
+ db.prepare(`
2004
+ INSERT INTO journal_entries (id, session_id, entry, tags, mood)
2005
+ VALUES (?, ?, ?, ?, ?)
2006
+ `).run(id, sessionId, entry, JSON.stringify(tags), mood);
2007
+ return {
2008
+ content: [
2009
+ {
2010
+ type: 'text',
2011
+ text: `Journal entry added!
2012
+ Time: ${new Date().toISOString()}
2013
+ Mood: ${mood || 'not specified'}
2014
+ Tags: ${tags.join(', ') || 'none'}
2015
+ Entry saved with ID: ${id.substring(0, 8)}`,
2016
+ },
2017
+ ],
2018
+ };
2019
+ }
2020
+ catch (error) {
2021
+ return {
2022
+ content: [
2023
+ {
2024
+ type: 'text',
2025
+ text: `Journal entry failed: ${error.message}`,
2026
+ },
2027
+ ],
2028
+ };
2029
+ }
2030
+ }
2031
+ // Phase 4.4: Timeline
2032
+ case 'context_timeline': {
2033
+ const { startDate, endDate, groupBy = 'day', sessionId, categories, relativeTime, itemsPerPeriod, includeItems, minItemsPerPeriod, showEmpty, } = args;
2034
+ const targetSessionId = sessionId || ensureSession();
2035
+ try {
2036
+ // Use the enhanced timeline method
2037
+ const timeline = repositories.contexts.getTimelineData({
2038
+ sessionId: targetSessionId,
2039
+ startDate,
2040
+ endDate,
2041
+ categories,
2042
+ relativeTime,
2043
+ itemsPerPeriod,
2044
+ includeItems,
2045
+ groupBy,
2046
+ minItemsPerPeriod,
2047
+ showEmpty,
2048
+ });
2049
+ // Get journal entries for the same period
2050
+ let journalQuery = 'SELECT * FROM journal_entries WHERE session_id = ?';
2051
+ const journalParams = [targetSessionId];
2052
+ // Calculate effective dates based on relativeTime if needed
2053
+ let effectiveStartDate = startDate;
2054
+ let effectiveEndDate = endDate;
2055
+ if (relativeTime) {
2056
+ const now = new Date();
2057
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
2058
+ if (relativeTime === 'today') {
2059
+ effectiveStartDate = today.toISOString();
2060
+ effectiveEndDate = new Date(today.getTime() + 24 * 60 * 60 * 1000).toISOString();
2061
+ }
2062
+ else if (relativeTime === 'yesterday') {
2063
+ const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
2064
+ effectiveStartDate = yesterday.toISOString();
2065
+ effectiveEndDate = today.toISOString();
2066
+ }
2067
+ else if (relativeTime.match(/^(\d+) hours? ago$/)) {
2068
+ const hours = parseInt(relativeTime.match(/^(\d+)/)[1]);
2069
+ effectiveStartDate = new Date(now.getTime() - hours * 60 * 60 * 1000).toISOString();
2070
+ }
2071
+ else if (relativeTime.match(/^(\d+) days? ago$/)) {
2072
+ const days = parseInt(relativeTime.match(/^(\d+)/)[1]);
2073
+ effectiveStartDate = new Date(now.getTime() - days * 24 * 60 * 60 * 1000).toISOString();
2074
+ }
2075
+ else if (relativeTime === 'this week') {
2076
+ const startOfWeek = new Date(today);
2077
+ startOfWeek.setDate(today.getDate() - today.getDay());
2078
+ effectiveStartDate = startOfWeek.toISOString();
2079
+ }
2080
+ else if (relativeTime === 'last week') {
2081
+ const startOfLastWeek = new Date(today);
2082
+ startOfLastWeek.setDate(today.getDate() - today.getDay() - 7);
2083
+ const endOfLastWeek = new Date(startOfLastWeek);
2084
+ endOfLastWeek.setDate(startOfLastWeek.getDate() + 7);
2085
+ effectiveStartDate = startOfLastWeek.toISOString();
2086
+ effectiveEndDate = endOfLastWeek.toISOString();
2087
+ }
2088
+ }
2089
+ if (effectiveStartDate) {
2090
+ journalQuery += ' AND created_at >= ?';
2091
+ journalParams.push(effectiveStartDate);
2092
+ }
2093
+ if (effectiveEndDate) {
2094
+ journalQuery += ' AND created_at <= ?';
2095
+ journalParams.push(effectiveEndDate);
2096
+ }
2097
+ const journals = db
2098
+ .prepare(journalQuery + ' ORDER BY created_at')
2099
+ .all(...journalParams);
2100
+ // Format enhanced timeline response
2101
+ const timelineData = {
2102
+ session_id: targetSessionId,
2103
+ period: {
2104
+ start: effectiveStartDate || startDate || 'beginning',
2105
+ end: effectiveEndDate || endDate || 'now',
2106
+ relative: relativeTime || null,
2107
+ },
2108
+ groupBy,
2109
+ filters: {
2110
+ categories: categories || null,
2111
+ },
2112
+ timeline: timeline.map(period => {
2113
+ const result = {
2114
+ period: period.period,
2115
+ count: period.count,
2116
+ };
2117
+ if (includeItems && period.items) {
2118
+ result.items = period.items.map((item) => ({
2119
+ key: item.key,
2120
+ value: item.value,
2121
+ category: item.category,
2122
+ priority: item.priority,
2123
+ created_at: item.created_at,
2124
+ }));
2125
+ if (period.hasMore) {
2126
+ result.hasMore = true;
2127
+ result.totalCount = period.totalCount;
2128
+ }
2129
+ }
2130
+ return result;
2131
+ }),
2132
+ journal_entries: journals.map((journal) => ({
2133
+ entry: journal.entry,
2134
+ tags: JSON.parse(journal.tags || '[]'),
2135
+ mood: journal.mood,
2136
+ created_at: journal.created_at,
2137
+ })),
2138
+ };
2139
+ return {
2140
+ content: [
2141
+ {
2142
+ type: 'text',
2143
+ text: JSON.stringify(timelineData, null, 2),
2144
+ },
2145
+ ],
2146
+ };
2147
+ }
2148
+ catch (error) {
2149
+ return {
2150
+ content: [
2151
+ {
2152
+ type: 'text',
2153
+ text: `Timeline generation failed: ${error.message}`,
2154
+ },
2155
+ ],
2156
+ };
2157
+ }
2158
+ }
2159
+ // Phase 4.4: Progressive Compression
2160
+ case 'context_compress': {
2161
+ const { olderThan, preserveCategories = [], targetSize: _targetSize, sessionId } = args;
2162
+ const targetSessionId = sessionId || ensureSession();
2163
+ try {
2164
+ // Build query for items to compress
2165
+ let query = 'SELECT * FROM context_items WHERE session_id = ?';
2166
+ const params = [targetSessionId];
2167
+ if (olderThan) {
2168
+ query += ' AND created_at < ?';
2169
+ params.push(olderThan);
2170
+ }
2171
+ if (preserveCategories.length > 0) {
2172
+ query += ` AND category NOT IN (${preserveCategories.map(() => '?').join(',')})`;
2173
+ params.push(...preserveCategories);
2174
+ }
2175
+ const itemsToCompress = db.prepare(query).all(...params);
2176
+ if (itemsToCompress.length === 0) {
2177
+ return {
2178
+ content: [
2179
+ {
2180
+ type: 'text',
2181
+ text: 'No items found to compress with given criteria.',
2182
+ },
2183
+ ],
2184
+ };
2185
+ }
2186
+ // Group items by category for compression
2187
+ const categoryGroups = {};
2188
+ for (const item of itemsToCompress) {
2189
+ const category = item.category || 'uncategorized';
2190
+ if (!categoryGroups[category]) {
2191
+ categoryGroups[category] = [];
2192
+ }
2193
+ categoryGroups[category].push(item);
2194
+ }
2195
+ // Compress each category group
2196
+ const compressed = [];
2197
+ for (const [category, items] of Object.entries(categoryGroups)) {
2198
+ const summary = {
2199
+ category,
2200
+ count: items.length,
2201
+ priorities: { high: 0, normal: 0, low: 0 },
2202
+ keys: items.map((i) => i.key),
2203
+ samples: items
2204
+ .slice(0, 3)
2205
+ .map((i) => ({ key: i.key, value: i.value.substring(0, 100) })),
2206
+ };
2207
+ for (const item of items) {
2208
+ const priority = (item.priority || 'normal');
2209
+ summary.priorities[priority]++;
2210
+ }
2211
+ compressed.push(summary);
2212
+ }
2213
+ // Calculate compression
2214
+ const originalSize = JSON.stringify(itemsToCompress).length;
2215
+ const compressedData = JSON.stringify(compressed);
2216
+ const compressedSize = compressedData.length;
2217
+ const compressionRatio = 1 - compressedSize / originalSize;
2218
+ // Store compressed data
2219
+ const compressedId = (0, uuid_1.v4)();
2220
+ const dateRange = itemsToCompress.reduce((acc, item) => {
2221
+ const date = new Date(item.created_at);
2222
+ if (!acc.start || date < acc.start)
2223
+ acc.start = date;
2224
+ if (!acc.end || date > acc.end)
2225
+ acc.end = date;
2226
+ return acc;
2227
+ }, { start: null, end: null });
2228
+ db.prepare(`
2229
+ INSERT INTO compressed_context (id, session_id, original_count, compressed_data, compression_ratio, date_range_start, date_range_end)
2230
+ VALUES (?, ?, ?, ?, ?, ?, ?)
2231
+ `).run(compressedId, targetSessionId, itemsToCompress.length, compressedData, compressionRatio, dateRange.start?.toISOString(), dateRange.end?.toISOString());
2232
+ // Delete original items
2233
+ const deleteStmt = db.prepare('DELETE FROM context_items WHERE id = ?');
2234
+ for (const item of itemsToCompress) {
2235
+ deleteStmt.run(item.id);
2236
+ }
2237
+ return {
2238
+ content: [
2239
+ {
2240
+ type: 'text',
2241
+ text: `Compression completed!
2242
+ Items compressed: ${itemsToCompress.length}
2243
+ Original size: ${(originalSize / 1024).toFixed(2)} KB
2244
+ Compressed size: ${(compressedSize / 1024).toFixed(2)} KB
2245
+ Compression ratio: ${(compressionRatio * 100).toFixed(1)}%
2246
+ Date range: ${dateRange.start?.toISOString().substring(0, 10)} to ${dateRange.end?.toISOString().substring(0, 10)}
2247
+
2248
+ Categories compressed:
2249
+ ${Object.entries(categoryGroups)
2250
+ .map(([cat, items]) => `- ${cat}: ${items.length} items`)
2251
+ .join('\n')}
2252
+
2253
+ Compressed data ID: ${compressedId.substring(0, 8)}`,
2254
+ },
2255
+ ],
2256
+ };
2257
+ }
2258
+ catch (error) {
2259
+ return {
2260
+ content: [
2261
+ {
2262
+ type: 'text',
2263
+ text: `Compression failed: ${error.message}`,
2264
+ },
2265
+ ],
2266
+ };
2267
+ }
2268
+ }
2269
+ // Phase 4.4: Cross-Tool Integration
2270
+ case 'context_integrate_tool': {
2271
+ const { toolName, eventType, data } = args;
2272
+ const sessionId = ensureSession();
2273
+ try {
2274
+ const id = (0, uuid_1.v4)();
2275
+ db.prepare(`
2276
+ INSERT INTO tool_events (id, session_id, tool_name, event_type, data)
2277
+ VALUES (?, ?, ?, ?, ?)
2278
+ `).run(id, sessionId, toolName, eventType, JSON.stringify(data));
2279
+ // Optionally create a context item for important events
2280
+ if (data.important || eventType === 'error' || eventType === 'milestone') {
2281
+ db.prepare(`
2282
+ INSERT INTO context_items (id, session_id, key, value, category, priority)
2283
+ VALUES (?, ?, ?, ?, ?, ?)
2284
+ `).run((0, uuid_1.v4)(), sessionId, `${toolName}_${eventType}_${Date.now()}`, `Tool event: ${toolName} - ${eventType}: ${JSON.stringify(data)}`, 'tool_event', data.important ? 'high' : 'normal');
2285
+ }
2286
+ return {
2287
+ content: [
2288
+ {
2289
+ type: 'text',
2290
+ text: `Tool event recorded!
2291
+ Tool: ${toolName}
2292
+ Event: ${eventType}
2293
+ Data recorded: ${JSON.stringify(data).length} bytes
2294
+ Event ID: ${id.substring(0, 8)}`,
2295
+ },
2296
+ ],
2297
+ };
2298
+ }
2299
+ catch (error) {
2300
+ return {
2301
+ content: [
2302
+ {
2303
+ type: 'text',
2304
+ text: `Tool integration failed: ${error.message}`,
2305
+ },
2306
+ ],
2307
+ };
2308
+ }
2309
+ }
2310
+ // Cross-Session Collaboration Tools
2311
+ // REMOVED: Sharing is now automatic (public by default)
2312
+ /*
2313
+ case 'context_share': {
2314
+ const { key, targetSessions, makePublic = false } = args;
2315
+ const sessionId = ensureSession();
2316
+
2317
+ try {
2318
+ // Get the item to share
2319
+ const item = repositories.contexts.getByKey(sessionId, key);
2320
+ if (!item) {
2321
+ return {
2322
+ content: [{
2323
+ type: 'text',
2324
+ text: `Item not found: ${key}`,
2325
+ }],
2326
+ };
2327
+ }
2328
+
2329
+ // Share with specific sessions or make public
2330
+ const targetSessionIds = makePublic ? [] : (targetSessions || []);
2331
+ repositories.contexts.shareByKey(sessionId, key, targetSessionIds);
2332
+
2333
+ return {
2334
+ content: [{
2335
+ type: 'text',
2336
+ text: `Shared "${key}" ${makePublic ? 'publicly' : `with ${targetSessionIds.length} session(s)`}`,
2337
+ }],
2338
+ };
2339
+ } catch (error: any) {
2340
+ return {
2341
+ content: [{
2342
+ type: 'text',
2343
+ text: `Failed to share context: ${error.message}`,
2344
+ }],
2345
+ };
2346
+ }
2347
+ }
2348
+ */
2349
+ // REMOVED: All accessible items are retrieved via context_get
2350
+ /*
2351
+ case 'context_get_shared': {
2352
+ const { includeAll = false } = args;
2353
+ const sessionId = ensureSession();
2354
+
2355
+ try {
2356
+ const items = includeAll
2357
+ ? repositories.contexts.getAllSharedItems()
2358
+ : repositories.contexts.getSharedItems(sessionId);
2359
+
2360
+ if (items.length === 0) {
2361
+ return {
2362
+ content: [{
2363
+ type: 'text',
2364
+ text: 'No shared context items found',
2365
+ }],
2366
+ };
2367
+ }
2368
+
2369
+ const itemsList = items.map((item: any) => {
2370
+ const sharedWith = item.shared_with_sessions
2371
+ ? JSON.parse(item.shared_with_sessions).length
2372
+ : 'all';
2373
+ return `• [${item.priority}] ${item.key} (from session: ${item.session_id.substring(0, 8)}, shared with: ${sharedWith})\n ${item.value.substring(0, 100)}${item.value.length > 100 ? '...' : ''}`;
2374
+ }).join('\n\n');
2375
+
2376
+ return {
2377
+ content: [{
2378
+ type: 'text',
2379
+ text: `Found ${items.length} shared items:\n\n${itemsList}`,
2380
+ }],
2381
+ };
2382
+ } catch (error: any) {
2383
+ return {
2384
+ content: [{
2385
+ type: 'text',
2386
+ text: `Failed to get shared context: ${error.message}`,
2387
+ }],
2388
+ };
2389
+ }
2390
+ }
2391
+ */
2392
+ case 'context_search_all': {
2393
+ const { query, sessions, includeShared = true, limit: rawLimit = 25, offset: rawOffset = 0, sort = 'created_desc', category, channel, channels, priorities, createdAfter, createdBefore, keyPattern, searchIn = ['key', 'value'], includeMetadata = false, matchMode, useFts5, } = args;
2394
+ // Enhanced pagination validation with proper error handling
2395
+ const paginationValidation = validatePaginationParams({ limit: rawLimit, offset: rawOffset });
2396
+ const { limit, offset, errors: paginationErrors } = paginationValidation;
2397
+ const currentSession = currentSessionId || ensureSession();
2398
+ // Log pagination validation errors for debugging
2399
+ if (paginationErrors.length > 0) {
2400
+ debugLog('Pagination validation errors:', paginationErrors);
2401
+ }
2402
+ try {
2403
+ // Use enhanced search across sessions with pagination
2404
+ const result = repositories.contexts.searchAcrossSessionsEnhanced({
2405
+ query,
2406
+ currentSessionId: currentSession,
2407
+ sessions,
2408
+ includeShared,
2409
+ searchIn,
2410
+ limit,
2411
+ offset,
2412
+ sort,
2413
+ category,
2414
+ channel,
2415
+ channels,
2416
+ priorities,
2417
+ createdAfter,
2418
+ createdBefore,
2419
+ keyPattern,
2420
+ includeMetadata,
2421
+ matchMode,
2422
+ useFts5,
2423
+ });
2424
+ // PAGINATION VALIDATION: Ensure pagination is working as expected
2425
+ if (result.items.length > limit && limit < result.totalCount) {
2426
+ debugLog(`Pagination warning: Expected max ${limit} items, got ${result.items.length}. This may indicate a pagination implementation issue.`);
2427
+ }
2428
+ if (result.items.length === 0) {
2429
+ return {
2430
+ content: [
2431
+ {
2432
+ type: 'text',
2433
+ text: `No results found for: "${query}"${result.totalCount > 0 ? ` (showing page ${result.pagination.currentPage} of ${result.pagination.totalPages})` : ''}`,
2434
+ },
2435
+ ],
2436
+ };
2437
+ }
2438
+ const resultsList = result.items
2439
+ .map((item) => `• [${item.session_id.substring(0, 8)}] ${item.key}: ${item.value.substring(0, 100)}${item.value.length > 100 ? '...' : ''}`)
2440
+ .join('\n');
2441
+ // Build pagination info
2442
+ const paginationInfo = result.pagination.totalPages > 1
2443
+ ? `\n\nPagination: Page ${result.pagination.currentPage} of ${result.pagination.totalPages} (${result.pagination.totalItems} total items)${result.pagination.hasNextPage
2444
+ ? `\nNext page: offset=${result.pagination.nextOffset}, limit=${result.pagination.itemsPerPage}`
2445
+ : ''}${result.pagination.hasPreviousPage
2446
+ ? `\nPrevious page: offset=${result.pagination.previousOffset}, limit=${result.pagination.itemsPerPage}`
2447
+ : ''}`
2448
+ : '';
2449
+ return {
2450
+ content: [
2451
+ {
2452
+ type: 'text',
2453
+ text: `Found ${result.items.length} results on this page (${result.totalCount} total across sessions):\n\n${resultsList}${paginationInfo}`,
2454
+ },
2455
+ ],
2456
+ };
2457
+ }
2458
+ catch (error) {
2459
+ // Enhanced error handling to distinguish pagination errors from search errors
2460
+ let errorMessage = 'Search failed';
2461
+ if (paginationErrors.length > 0) {
2462
+ errorMessage = `Search failed due to pagination validation errors: ${paginationErrors.join(', ')}. ${error.message}`;
2463
+ }
2464
+ else if (error.message.includes('pagination') ||
2465
+ error.message.includes('limit') ||
2466
+ error.message.includes('offset')) {
2467
+ errorMessage = `Search failed due to pagination parameter issue: ${error.message}`;
2468
+ }
2469
+ else {
2470
+ errorMessage = `Search failed: ${error.message}`;
2471
+ }
2472
+ debugLog('Search error:', { error: error.message, paginationErrors, limit, offset });
2473
+ return {
2474
+ content: [
2475
+ {
2476
+ type: 'text',
2477
+ text: errorMessage,
2478
+ },
2479
+ ],
2480
+ };
2481
+ }
2482
+ }
2483
+ // Context Diff - Track changes since a specific point in time
2484
+ case 'context_diff': {
2485
+ const { since, sessionId: specificSessionId, category, channel, channels, includeValues = true, limit, offset, } = args;
2486
+ const targetSessionId = specificSessionId || currentSessionId || ensureSession();
2487
+ try {
2488
+ // Parse the 'since' parameter
2489
+ let sinceTimestamp = null;
2490
+ let checkpointId = null;
2491
+ if (since) {
2492
+ // Check if it's a checkpoint name or ID
2493
+ const checkpointByName = db
2494
+ .prepare('SELECT * FROM checkpoints WHERE name = ? ORDER BY created_at DESC LIMIT 1')
2495
+ .get(since);
2496
+ const checkpointById = !checkpointByName
2497
+ ? db.prepare('SELECT * FROM checkpoints WHERE id = ?').get(since)
2498
+ : null;
2499
+ const checkpoint = checkpointByName || checkpointById;
2500
+ if (checkpoint) {
2501
+ checkpointId = checkpoint.id;
2502
+ sinceTimestamp = checkpoint.created_at;
2503
+ }
2504
+ else {
2505
+ // Try to parse as relative time
2506
+ const parsedTime = parseRelativeTime(since);
2507
+ if (parsedTime) {
2508
+ sinceTimestamp = parsedTime;
2509
+ }
2510
+ else {
2511
+ // Assume it's an ISO timestamp
2512
+ sinceTimestamp = since;
2513
+ }
2514
+ }
2515
+ }
2516
+ else {
2517
+ // Default to 1 hour ago if no 'since' provided
2518
+ sinceTimestamp = new Date(Date.now() - 60 * 60 * 1000).toISOString();
2519
+ }
2520
+ // Convert ISO timestamp to SQLite format for repository compatibility
2521
+ const sqliteTimestamp = (0, timestamps_js_1.ensureSQLiteFormat)(sinceTimestamp);
2522
+ // Use repository method to get diff data
2523
+ const diffData = repositories.contexts.getDiff({
2524
+ sessionId: targetSessionId,
2525
+ sinceTimestamp: sqliteTimestamp,
2526
+ category,
2527
+ channel,
2528
+ channels,
2529
+ limit,
2530
+ offset,
2531
+ includeValues,
2532
+ });
2533
+ // Handle deleted items if we have a checkpoint
2534
+ let deletedKeys = [];
2535
+ if (checkpointId) {
2536
+ deletedKeys = repositories.contexts.getDeletedKeysFromCheckpoint(targetSessionId, checkpointId);
2537
+ }
2538
+ // Format response
2539
+ const toDate = new Date().toISOString();
2540
+ const response = {
2541
+ added: includeValues
2542
+ ? diffData.added
2543
+ : diffData.added.map(i => ({ key: i.key, category: i.category })),
2544
+ modified: includeValues
2545
+ ? diffData.modified
2546
+ : diffData.modified.map(i => ({ key: i.key, category: i.category })),
2547
+ deleted: deletedKeys,
2548
+ summary: `${diffData.added.length} added, ${diffData.modified.length} modified, ${deletedKeys.length} deleted`,
2549
+ period: {
2550
+ from: sinceTimestamp,
2551
+ to: toDate,
2552
+ },
2553
+ };
2554
+ return {
2555
+ content: [
2556
+ {
2557
+ type: 'text',
2558
+ text: JSON.stringify(response, null, 2),
2559
+ },
2560
+ ],
2561
+ };
2562
+ }
2563
+ catch (error) {
2564
+ return {
2565
+ content: [
2566
+ {
2567
+ type: 'text',
2568
+ text: `Failed to get context diff: ${error.message}`,
2569
+ },
2570
+ ],
2571
+ };
2572
+ }
2573
+ }
2574
+ // Channel Management
2575
+ case 'context_list_channels': {
2576
+ const { sessionId, sessionIds, sort, includeEmpty } = args;
2577
+ try {
2578
+ const channels = repositories.contexts.listChannels({
2579
+ sessionId: sessionId || currentSessionId,
2580
+ sessionIds,
2581
+ sort,
2582
+ includeEmpty,
2583
+ });
2584
+ if (channels.length === 0) {
2585
+ return {
2586
+ content: [
2587
+ {
2588
+ type: 'text',
2589
+ text: 'No channels found.',
2590
+ },
2591
+ ],
2592
+ };
2593
+ }
2594
+ // Format the response
2595
+ const channelList = channels
2596
+ .map((ch) => `• ${ch.channel}: ${ch.total_count} items (${ch.public_count} public, ${ch.private_count} private)\n Last activity: ${new Date(ch.last_activity).toLocaleString()}\n Categories: ${ch.categories.join(', ') || 'none'}\n Sessions: ${ch.session_count}`)
2597
+ .join('\n\n');
2598
+ return {
2599
+ content: [
2600
+ {
2601
+ type: 'text',
2602
+ text: `Found ${channels.length} channels:\n\n${channelList}`,
2603
+ },
2604
+ ],
2605
+ };
2606
+ }
2607
+ catch (error) {
2608
+ return {
2609
+ content: [
2610
+ {
2611
+ type: 'text',
2612
+ text: `Failed to list channels: ${error.message}`,
2613
+ },
2614
+ ],
2615
+ };
2616
+ }
2617
+ }
2618
+ case 'context_channel_stats': {
2619
+ const { channel, sessionId, includeTimeSeries, includeInsights } = args;
2620
+ try {
2621
+ const stats = repositories.contexts.getChannelStats({
2622
+ channel,
2623
+ sessionId: sessionId || currentSessionId,
2624
+ includeTimeSeries,
2625
+ includeInsights,
2626
+ });
2627
+ return {
2628
+ content: [
2629
+ {
2630
+ type: 'text',
2631
+ text: JSON.stringify(stats, null, 2),
2632
+ },
2633
+ ],
2634
+ };
2635
+ }
2636
+ catch (error) {
2637
+ return {
2638
+ content: [
2639
+ {
2640
+ type: 'text',
2641
+ text: `Failed to get channel stats: ${error.message}`,
2642
+ },
2643
+ ],
2644
+ };
2645
+ }
2646
+ }
2647
+ // Context Watch functionality
2648
+ case 'context_watch': {
2649
+ return await (0, contextWatchHandlers_js_1.handleContextWatch)(args, repositories, ensureSession());
2650
+ }
2651
+ // Context Reassign Channel
2652
+ case 'context_reassign_channel': {
2653
+ const { keys, keyPattern, fromChannel, toChannel, sessionId, category, priorities, dryRun = false, } = args;
2654
+ try {
2655
+ // Validate input
2656
+ if (!toChannel || !toChannel.trim()) {
2657
+ throw new Error('Target channel name cannot be empty');
2658
+ }
2659
+ if (!keys && !keyPattern && !fromChannel) {
2660
+ throw new Error('Must provide either keys array, keyPattern, or fromChannel');
2661
+ }
2662
+ if (fromChannel && fromChannel === toChannel) {
2663
+ throw new Error('Source and destination channels cannot be the same');
2664
+ }
2665
+ const targetSessionId = sessionId || ensureSession();
2666
+ // Call repository method
2667
+ const result = await repositories.contexts.reassignChannel({
2668
+ keys,
2669
+ keyPattern,
2670
+ fromChannel,
2671
+ toChannel,
2672
+ sessionId: targetSessionId,
2673
+ category,
2674
+ priorities,
2675
+ dryRun,
2676
+ });
2677
+ return {
2678
+ content: [
2679
+ {
2680
+ type: 'text',
2681
+ text: JSON.stringify(result, null, 2),
2682
+ },
2683
+ ],
2684
+ };
2685
+ }
2686
+ catch (error) {
2687
+ return {
2688
+ content: [
2689
+ {
2690
+ type: 'text',
2691
+ text: `Failed to reassign channel: ${error.message}`,
2692
+ },
2693
+ ],
2694
+ };
2695
+ }
2696
+ }
2697
+ // Batch Operations
2698
+ case 'context_batch_save': {
2699
+ const { items, updateExisting = true } = args;
2700
+ const sessionId = ensureSession();
2701
+ // Validate items
2702
+ if (!items || !Array.isArray(items) || items.length === 0) {
2703
+ return {
2704
+ content: [
2705
+ {
2706
+ type: 'text',
2707
+ text: 'No items provided for batch save',
2708
+ },
2709
+ ],
2710
+ };
2711
+ }
2712
+ // Enforce batch size limit
2713
+ const maxBatchSize = 100;
2714
+ if (items.length > maxBatchSize) {
2715
+ return {
2716
+ content: [
2717
+ {
2718
+ type: 'text',
2719
+ text: `Batch size ${items.length} exceeds maximum allowed size of ${maxBatchSize}`,
2720
+ },
2721
+ ],
2722
+ };
2723
+ }
2724
+ // Validate items
2725
+ const validationErrors = [];
2726
+ items.forEach((item, index) => {
2727
+ try {
2728
+ // Validate item
2729
+ if (!item.key || !item.key.trim()) {
2730
+ throw new Error('Key is required and cannot be empty');
2731
+ }
2732
+ if (!item.value) {
2733
+ throw new Error('Value is required');
2734
+ }
2735
+ // Validate category
2736
+ if (item.category) {
2737
+ const validCategories = ['task', 'decision', 'progress', 'note', 'error', 'warning'];
2738
+ if (!validCategories.includes(item.category)) {
2739
+ throw new Error(`Invalid category: ${item.category}`);
2740
+ }
2741
+ }
2742
+ // Validate priority
2743
+ if (item.priority) {
2744
+ const validPriorities = ['high', 'normal', 'low'];
2745
+ if (!validPriorities.includes(item.priority)) {
2746
+ throw new Error(`Invalid priority: ${item.priority}`);
2747
+ }
2748
+ }
2749
+ }
2750
+ catch (error) {
2751
+ validationErrors.push({
2752
+ index,
2753
+ key: item.key || 'undefined',
2754
+ error: error.message,
2755
+ });
2756
+ }
2757
+ });
2758
+ // If all items have validation errors, return early
2759
+ if (validationErrors.length === items.length) {
2760
+ return {
2761
+ content: [
2762
+ {
2763
+ type: 'text',
2764
+ text: JSON.stringify({
2765
+ operation: 'batch_save',
2766
+ totalItems: items.length,
2767
+ succeeded: 0,
2768
+ failed: validationErrors.length,
2769
+ totalSize: 0,
2770
+ results: [],
2771
+ errors: validationErrors,
2772
+ timestamp: new Date().toISOString(),
2773
+ }, null, 2),
2774
+ },
2775
+ ],
2776
+ };
2777
+ }
2778
+ let results = [];
2779
+ let errors = [];
2780
+ let totalSize = 0;
2781
+ // Begin transaction
2782
+ db.prepare('BEGIN TRANSACTION').run();
2783
+ try {
2784
+ // Use repository method
2785
+ const batchResult = repositories.contexts.batchSave(sessionId, items, { updateExisting });
2786
+ totalSize = batchResult.totalSize;
2787
+ // Merge validation errors with operation results
2788
+ const allResults = batchResult.results.filter(r => r.success);
2789
+ const allErrors = [
2790
+ ...validationErrors,
2791
+ ...batchResult.results
2792
+ .filter(r => !r.success)
2793
+ .map(r => ({
2794
+ index: r.index,
2795
+ key: r.key,
2796
+ error: r.error,
2797
+ })),
2798
+ ];
2799
+ // Commit transaction
2800
+ db.prepare('COMMIT').run();
2801
+ // Create embeddings for successful saves (async, don't wait)
2802
+ allResults.forEach(async (result) => {
2803
+ if (result.success && result.action === 'created') {
2804
+ try {
2805
+ const item = items[result.index];
2806
+ const content = `${item.key}: ${item.value}`;
2807
+ const metadata = { key: item.key, category: item.category, priority: item.priority };
2808
+ await vectorStore.storeDocument(result.id, content, metadata);
2809
+ }
2810
+ catch (error) {
2811
+ // Log but don't fail
2812
+ console.error('Failed to create embedding:', error);
2813
+ }
2814
+ }
2815
+ });
2816
+ results = allResults;
2817
+ errors = allErrors;
2818
+ }
2819
+ catch (error) {
2820
+ // Rollback transaction
2821
+ db.prepare('ROLLBACK').run();
2822
+ return {
2823
+ content: [
2824
+ {
2825
+ type: 'text',
2826
+ text: `Batch save failed: ${error.message}`,
2827
+ },
2828
+ ],
2829
+ };
2830
+ }
2831
+ // Prepare response
2832
+ const response = {
2833
+ operation: 'batch_save',
2834
+ totalItems: items.length,
2835
+ succeeded: results.length,
2836
+ failed: errors.length,
2837
+ totalSize: totalSize,
2838
+ averageSize: results.length > 0 ? Math.round(totalSize / results.length) : 0,
2839
+ results: results,
2840
+ errors: errors,
2841
+ timestamp: new Date().toISOString(),
2842
+ };
2843
+ return {
2844
+ content: [
2845
+ {
2846
+ type: 'text',
2847
+ text: JSON.stringify(response, null, 2),
2848
+ },
2849
+ ],
2850
+ };
2851
+ }
2852
+ case 'context_batch_delete': {
2853
+ const { keys, keyPattern, sessionId: specificSessionId, dryRun = false } = args;
2854
+ const targetSessionId = specificSessionId || currentSessionId || ensureSession();
2855
+ // Validate input
2856
+ if (!keys && !keyPattern) {
2857
+ return {
2858
+ content: [
2859
+ {
2860
+ type: 'text',
2861
+ text: 'Either keys array or keyPattern must be provided',
2862
+ },
2863
+ ],
2864
+ };
2865
+ }
2866
+ if (keys && (!Array.isArray(keys) || keys.length === 0)) {
2867
+ return {
2868
+ content: [
2869
+ {
2870
+ type: 'text',
2871
+ text: 'Keys must be a non-empty array',
2872
+ },
2873
+ ],
2874
+ };
2875
+ }
2876
+ let results = [];
2877
+ let totalDeleted = 0;
2878
+ try {
2879
+ if (dryRun) {
2880
+ // Dry run - just show what would be deleted
2881
+ const itemsToDelete = repositories.contexts.getDryRunItems(targetSessionId, {
2882
+ keys,
2883
+ keyPattern,
2884
+ });
2885
+ return {
2886
+ content: [
2887
+ {
2888
+ type: 'text',
2889
+ text: JSON.stringify({
2890
+ operation: 'batch_delete',
2891
+ dryRun: true,
2892
+ keys: keys,
2893
+ pattern: keyPattern,
2894
+ itemsToDelete: itemsToDelete,
2895
+ totalItems: itemsToDelete.length,
2896
+ }, null, 2),
2897
+ },
2898
+ ],
2899
+ };
2900
+ }
2901
+ // Actual deletion
2902
+ db.prepare('BEGIN TRANSACTION').run();
2903
+ const deleteResult = repositories.contexts.batchDelete(targetSessionId, {
2904
+ keys,
2905
+ keyPattern,
2906
+ });
2907
+ results = deleteResult.results || [];
2908
+ totalDeleted = deleteResult.totalDeleted;
2909
+ db.prepare('COMMIT').run();
2910
+ }
2911
+ catch (error) {
2912
+ db.prepare('ROLLBACK').run();
2913
+ return {
2914
+ content: [
2915
+ {
2916
+ type: 'text',
2917
+ text: `Batch delete failed: ${error.message}`,
2918
+ },
2919
+ ],
2920
+ };
2921
+ }
2922
+ // Prepare response
2923
+ const response = keys
2924
+ ? {
2925
+ operation: 'batch_delete',
2926
+ keys: keys,
2927
+ totalRequested: keys.length,
2928
+ totalDeleted: totalDeleted,
2929
+ notFound: results.filter(r => !r.deleted).map(r => r.key),
2930
+ results: results,
2931
+ }
2932
+ : {
2933
+ operation: 'batch_delete',
2934
+ pattern: keyPattern,
2935
+ totalDeleted: totalDeleted,
2936
+ };
2937
+ return {
2938
+ content: [
2939
+ {
2940
+ type: 'text',
2941
+ text: JSON.stringify(response, null, 2),
2942
+ },
2943
+ ],
2944
+ };
2945
+ }
2946
+ case 'context_batch_update': {
2947
+ const { updates, sessionId: specificSessionId } = args;
2948
+ const targetSessionId = specificSessionId || currentSessionId || ensureSession();
2949
+ // Validate input
2950
+ if (!updates || !Array.isArray(updates) || updates.length === 0) {
2951
+ return {
2952
+ content: [
2953
+ {
2954
+ type: 'text',
2955
+ text: 'Updates array must be provided and non-empty',
2956
+ },
2957
+ ],
2958
+ };
2959
+ }
2960
+ // Validate updates
2961
+ const validationErrors = [];
2962
+ updates.forEach((update, index) => {
2963
+ try {
2964
+ // Validate update
2965
+ if (!update.key || !update.key.trim()) {
2966
+ throw new Error('Key is required and cannot be empty');
2967
+ }
2968
+ // Check if any updates are provided
2969
+ const hasUpdates = update.value !== undefined ||
2970
+ update.category !== undefined ||
2971
+ update.priority !== undefined ||
2972
+ update.channel !== undefined;
2973
+ if (!hasUpdates) {
2974
+ throw new Error('No updates provided');
2975
+ }
2976
+ // Validate fields if provided
2977
+ if (update.category !== undefined) {
2978
+ const validCategories = ['task', 'decision', 'progress', 'note', 'error', 'warning'];
2979
+ if (!validCategories.includes(update.category)) {
2980
+ throw new Error(`Invalid category: ${update.category}`);
2981
+ }
2982
+ }
2983
+ if (update.priority !== undefined) {
2984
+ const validPriorities = ['high', 'normal', 'low'];
2985
+ if (!validPriorities.includes(update.priority)) {
2986
+ throw new Error(`Invalid priority: ${update.priority}`);
2987
+ }
2988
+ }
2989
+ if (update.value !== undefined && update.value === '') {
2990
+ throw new Error('Value cannot be empty');
2991
+ }
2992
+ }
2993
+ catch (error) {
2994
+ validationErrors.push({
2995
+ index,
2996
+ key: update.key || 'undefined',
2997
+ error: error.message,
2998
+ });
2999
+ }
3000
+ });
3001
+ // If all updates have validation errors, return early
3002
+ if (validationErrors.length === updates.length) {
3003
+ return {
3004
+ content: [
3005
+ {
3006
+ type: 'text',
3007
+ text: JSON.stringify({
3008
+ operation: 'batch_update',
3009
+ totalItems: updates.length,
3010
+ succeeded: 0,
3011
+ failed: validationErrors.length,
3012
+ results: [],
3013
+ errors: validationErrors,
3014
+ }, null, 2),
3015
+ },
3016
+ ],
3017
+ };
3018
+ }
3019
+ let results = [];
3020
+ let errors = [];
3021
+ // Begin transaction
3022
+ db.prepare('BEGIN TRANSACTION').run();
3023
+ try {
3024
+ // Use repository method
3025
+ const updateResult = repositories.contexts.batchUpdate(targetSessionId, updates);
3026
+ // Merge validation errors with operation results
3027
+ results = updateResult.results.filter(r => r.updated);
3028
+ errors = [
3029
+ ...validationErrors,
3030
+ ...updateResult.results
3031
+ .filter(r => !r.updated)
3032
+ .map(r => ({
3033
+ index: r.index,
3034
+ key: r.key,
3035
+ error: r.error,
3036
+ })),
3037
+ ];
3038
+ // Commit transaction
3039
+ db.prepare('COMMIT').run();
3040
+ }
3041
+ catch (error) {
3042
+ // Rollback transaction
3043
+ db.prepare('ROLLBACK').run();
3044
+ return {
3045
+ content: [
3046
+ {
3047
+ type: 'text',
3048
+ text: `Batch update failed: ${error.message}`,
3049
+ },
3050
+ ],
3051
+ };
3052
+ }
3053
+ // Prepare response
3054
+ const response = {
3055
+ operation: 'batch_update',
3056
+ totalItems: updates.length,
3057
+ succeeded: results.length,
3058
+ failed: errors.length,
3059
+ results: results,
3060
+ errors: errors,
3061
+ };
3062
+ return {
3063
+ content: [
3064
+ {
3065
+ type: 'text',
3066
+ text: JSON.stringify(response, null, 2),
3067
+ },
3068
+ ],
3069
+ };
3070
+ }
3071
+ // Context Relationships
3072
+ case 'context_link': {
3073
+ const { sourceKey, targetKey, relationship, metadata } = args;
3074
+ const sessionId = currentSessionId || ensureSession();
3075
+ // Validate inputs
3076
+ if (!sourceKey || !sourceKey.trim()) {
3077
+ return {
3078
+ content: [
3079
+ {
3080
+ type: 'text',
3081
+ text: 'Error: sourceKey cannot be empty',
3082
+ },
3083
+ ],
3084
+ };
3085
+ }
3086
+ if (!targetKey || !targetKey.trim()) {
3087
+ return {
3088
+ content: [
3089
+ {
3090
+ type: 'text',
3091
+ text: 'Error: targetKey cannot be empty',
3092
+ },
3093
+ ],
3094
+ };
3095
+ }
3096
+ if (!relationship || !relationship.trim()) {
3097
+ return {
3098
+ content: [
3099
+ {
3100
+ type: 'text',
3101
+ text: 'Error: relationship cannot be empty',
3102
+ },
3103
+ ],
3104
+ };
3105
+ }
3106
+ // Create relationship
3107
+ const result = repositories.contexts.createRelationship({
3108
+ sessionId,
3109
+ sourceKey,
3110
+ targetKey,
3111
+ relationship,
3112
+ metadata,
3113
+ });
3114
+ if (!result.created) {
3115
+ return {
3116
+ content: [
3117
+ {
3118
+ type: 'text',
3119
+ text: `Error: ${result.error}`,
3120
+ },
3121
+ ],
3122
+ };
3123
+ }
3124
+ return {
3125
+ content: [
3126
+ {
3127
+ type: 'text',
3128
+ text: JSON.stringify({
3129
+ operation: 'context_link',
3130
+ relationshipId: result.id,
3131
+ sourceKey,
3132
+ targetKey,
3133
+ relationship,
3134
+ metadata,
3135
+ created: true,
3136
+ timestamp: new Date().toISOString(),
3137
+ }, null, 2),
3138
+ },
3139
+ ],
3140
+ };
3141
+ }
3142
+ case 'context_get_related': {
3143
+ const { key, relationship, depth = 1, direction = 'both' } = args;
3144
+ const sessionId = currentSessionId || ensureSession();
3145
+ if (!key || !key.trim()) {
3146
+ return {
3147
+ content: [
3148
+ {
3149
+ type: 'text',
3150
+ text: 'Error: key cannot be empty',
3151
+ },
3152
+ ],
3153
+ };
3154
+ }
3155
+ // Get related items
3156
+ const result = repositories.contexts.getRelatedItems({
3157
+ sessionId,
3158
+ key,
3159
+ relationship,
3160
+ depth,
3161
+ direction,
3162
+ });
3163
+ const totalRelated = result.outgoing.length + result.incoming.length;
3164
+ // Prepare response
3165
+ let response = {
3166
+ operation: 'context_get_related',
3167
+ key,
3168
+ related: {
3169
+ outgoing: result.outgoing,
3170
+ incoming: result.incoming,
3171
+ },
3172
+ totalRelated,
3173
+ };
3174
+ // Add graph data if depth > 1
3175
+ if (depth > 1 && result.graph) {
3176
+ response.visualization = {
3177
+ format: 'graph',
3178
+ nodes: result.graph.nodes,
3179
+ edges: result.graph.edges,
3180
+ };
3181
+ response.summary = {
3182
+ totalNodes: result.graph.nodes.length,
3183
+ totalEdges: result.graph.edges.length,
3184
+ relationshipTypes: [...new Set(result.graph.edges.map((e) => e.type))],
3185
+ };
3186
+ }
3187
+ // Add message if no relationships found
3188
+ if (totalRelated === 0) {
3189
+ response.message = 'No relationships found for this item';
3190
+ }
3191
+ return {
3192
+ content: [
3193
+ {
3194
+ type: 'text',
3195
+ text: JSON.stringify(response, null, 2),
3196
+ },
3197
+ ],
3198
+ };
3199
+ }
3200
+ default:
3201
+ throw new Error(`Unknown tool: ${toolName}`);
3202
+ }
3203
+ });
3204
+ // List available tools (filtered by active tool profile)
3205
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
3206
+ const allTools = [
3207
+ // Session Management
3208
+ {
3209
+ name: 'context_session_start',
3210
+ description: 'Start a new context session with optional project directory for git tracking',
3211
+ inputSchema: {
3212
+ type: 'object',
3213
+ properties: {
3214
+ name: { type: 'string', description: 'Session name' },
3215
+ description: { type: 'string', description: 'Session description' },
3216
+ continueFrom: { type: 'string', description: 'Session ID to continue from' },
3217
+ projectDir: {
3218
+ type: 'string',
3219
+ description: 'Project directory path for git tracking (e.g., "/path/to/your/project")',
3220
+ },
3221
+ defaultChannel: {
3222
+ type: 'string',
3223
+ description: 'Default channel for context items (auto-derived from git branch if not provided)',
3224
+ },
3225
+ },
3226
+ },
3227
+ },
3228
+ {
3229
+ name: 'context_session_list',
3230
+ description: 'List recent sessions',
3231
+ inputSchema: {
3232
+ type: 'object',
3233
+ properties: {
3234
+ limit: {
3235
+ type: 'number',
3236
+ description: 'Maximum number of sessions to return',
3237
+ default: 10,
3238
+ },
3239
+ },
3240
+ },
3241
+ },
3242
+ {
3243
+ name: 'context_set_project_dir',
3244
+ description: 'Set the project directory for git tracking in the current session',
3245
+ inputSchema: {
3246
+ type: 'object',
3247
+ properties: {
3248
+ projectDir: {
3249
+ type: 'string',
3250
+ description: 'Project directory path for git tracking (e.g., "/path/to/your/project")',
3251
+ },
3252
+ },
3253
+ required: ['projectDir'],
3254
+ },
3255
+ },
3256
+ // Enhanced Context Storage
3257
+ {
3258
+ name: 'context_save',
3259
+ description: 'Save a context item with optional category, priority, and privacy setting',
3260
+ inputSchema: {
3261
+ type: 'object',
3262
+ properties: {
3263
+ key: { type: 'string', description: 'Unique key for the context item' },
3264
+ value: { type: 'string', description: 'Context value to save' },
3265
+ category: {
3266
+ type: 'string',
3267
+ description: 'Category (e.g., task, decision, progress)',
3268
+ enum: ['task', 'decision', 'progress', 'note', 'error', 'warning'],
3269
+ },
3270
+ priority: {
3271
+ type: 'string',
3272
+ description: 'Priority level',
3273
+ enum: ['high', 'normal', 'low'],
3274
+ default: 'normal',
3275
+ },
3276
+ private: {
3277
+ type: 'boolean',
3278
+ description: 'If true, item is only accessible from the current session. Default: false (accessible from all sessions)',
3279
+ default: false,
3280
+ },
3281
+ channel: {
3282
+ type: 'string',
3283
+ description: 'Channel to organize this item (uses session default if not provided)',
3284
+ },
3285
+ },
3286
+ required: ['key', 'value'],
3287
+ },
3288
+ },
3289
+ {
3290
+ name: 'context_get',
3291
+ description: 'Retrieve saved context by key, category, or session with enhanced filtering. Returns all accessible items (public items + own private items)',
3292
+ inputSchema: {
3293
+ type: 'object',
3294
+ properties: {
3295
+ key: { type: 'string', description: 'Specific key to retrieve' },
3296
+ category: { type: 'string', description: 'Filter by category' },
3297
+ sessionId: { type: 'string', description: 'Specific session ID (defaults to current)' },
3298
+ channel: { type: 'string', description: 'Filter by single channel' },
3299
+ channels: {
3300
+ type: 'array',
3301
+ items: { type: 'string' },
3302
+ description: 'Filter by multiple channels',
3303
+ },
3304
+ includeMetadata: {
3305
+ type: 'boolean',
3306
+ description: 'Include timestamps and size info',
3307
+ },
3308
+ sort: {
3309
+ type: 'string',
3310
+ enum: ['created_desc', 'created_asc', 'updated_desc', 'key_asc', 'key_desc'],
3311
+ description: 'Sort order for results',
3312
+ },
3313
+ limit: {
3314
+ type: 'number',
3315
+ description: 'Maximum items to return. Must be a positive integer. Invalid values will cause validation error. (default: auto-derived)',
3316
+ },
3317
+ offset: {
3318
+ type: 'number',
3319
+ description: 'Pagination offset. Must be a non-negative integer. Invalid values will cause validation error. (default: 0)',
3320
+ },
3321
+ createdAfter: {
3322
+ type: 'string',
3323
+ description: 'ISO date - items created after this time',
3324
+ },
3325
+ createdBefore: {
3326
+ type: 'string',
3327
+ description: 'ISO date - items created before this time',
3328
+ },
3329
+ keyPattern: {
3330
+ type: 'string',
3331
+ description: 'Regex pattern for key matching',
3332
+ },
3333
+ priorities: {
3334
+ type: 'array',
3335
+ items: { type: 'string', enum: ['high', 'normal', 'low'] },
3336
+ description: 'Filter by priority levels',
3337
+ },
3338
+ },
3339
+ },
3340
+ },
3341
+ // File Caching
3342
+ {
3343
+ name: 'context_cache_file',
3344
+ description: 'Cache file content with hash for change detection',
3345
+ inputSchema: {
3346
+ type: 'object',
3347
+ properties: {
3348
+ filePath: { type: 'string', description: 'Path to the file' },
3349
+ content: { type: 'string', description: 'File content to cache' },
3350
+ },
3351
+ required: ['filePath', 'content'],
3352
+ },
3353
+ },
3354
+ {
3355
+ name: 'context_file_changed',
3356
+ description: 'Check if a file has changed since it was cached',
3357
+ inputSchema: {
3358
+ type: 'object',
3359
+ properties: {
3360
+ filePath: { type: 'string', description: 'Path to the file' },
3361
+ currentContent: { type: 'string', description: 'Current file content to compare' },
3362
+ },
3363
+ required: ['filePath'],
3364
+ },
3365
+ },
3366
+ // Status
3367
+ {
3368
+ name: 'context_status',
3369
+ description: 'Get current context status and statistics',
3370
+ inputSchema: {
3371
+ type: 'object',
3372
+ properties: {},
3373
+ },
3374
+ },
3375
+ // Phase 2: Checkpoint System
3376
+ {
3377
+ name: 'context_checkpoint',
3378
+ description: 'Create a named checkpoint of current context',
3379
+ inputSchema: {
3380
+ type: 'object',
3381
+ properties: {
3382
+ name: { type: 'string', description: 'Checkpoint name' },
3383
+ description: { type: 'string', description: 'Checkpoint description' },
3384
+ includeFiles: {
3385
+ type: 'boolean',
3386
+ description: 'Include cached files in checkpoint',
3387
+ default: true,
3388
+ },
3389
+ includeGitStatus: {
3390
+ type: 'boolean',
3391
+ description: 'Capture current git status',
3392
+ default: true,
3393
+ },
3394
+ },
3395
+ required: ['name'],
3396
+ },
3397
+ },
3398
+ {
3399
+ name: 'context_restore_checkpoint',
3400
+ description: 'Restore context from a checkpoint',
3401
+ inputSchema: {
3402
+ type: 'object',
3403
+ properties: {
3404
+ name: { type: 'string', description: 'Checkpoint name to restore' },
3405
+ checkpointId: { type: 'string', description: 'Specific checkpoint ID' },
3406
+ restoreFiles: {
3407
+ type: 'boolean',
3408
+ description: 'Restore cached files',
3409
+ default: true,
3410
+ },
3411
+ },
3412
+ },
3413
+ },
3414
+ // Phase 2: Summarization
3415
+ {
3416
+ name: 'context_summarize',
3417
+ description: 'Get AI-friendly summary of session context',
3418
+ inputSchema: {
3419
+ type: 'object',
3420
+ properties: {
3421
+ sessionId: {
3422
+ type: 'string',
3423
+ description: 'Session to summarize (defaults to current)',
3424
+ },
3425
+ categories: {
3426
+ type: 'array',
3427
+ items: { type: 'string' },
3428
+ description: 'Filter by specific categories',
3429
+ },
3430
+ maxLength: {
3431
+ type: 'number',
3432
+ description: 'Maximum summary length',
3433
+ default: 1000,
3434
+ },
3435
+ },
3436
+ },
3437
+ },
3438
+ // Phase 3: Smart Compaction
3439
+ {
3440
+ name: 'context_prepare_compaction',
3441
+ description: 'Automatically save critical context before compaction',
3442
+ inputSchema: {
3443
+ type: 'object',
3444
+ properties: {},
3445
+ },
3446
+ },
3447
+ // Phase 3: Git Integration
3448
+ {
3449
+ name: 'context_git_commit',
3450
+ description: 'Create git commit with automatic context save',
3451
+ inputSchema: {
3452
+ type: 'object',
3453
+ properties: {
3454
+ message: { type: 'string', description: 'Commit message' },
3455
+ autoSave: {
3456
+ type: 'boolean',
3457
+ description: 'Automatically save context state',
3458
+ default: true,
3459
+ },
3460
+ },
3461
+ required: ['message'],
3462
+ },
3463
+ },
3464
+ // Phase 3: Search
3465
+ {
3466
+ name: 'context_search',
3467
+ description: 'Search through saved context items with advanced filtering',
3468
+ inputSchema: {
3469
+ type: 'object',
3470
+ properties: {
3471
+ query: { type: 'string', description: 'Search query' },
3472
+ searchIn: {
3473
+ type: 'array',
3474
+ items: { type: 'string', enum: ['key', 'value'] },
3475
+ description: 'Fields to search in',
3476
+ default: ['key', 'value'],
3477
+ },
3478
+ sessionId: { type: 'string', description: 'Session to search (defaults to current)' },
3479
+ category: { type: 'string', description: 'Filter by category' },
3480
+ channel: { type: 'string', description: 'Filter by single channel' },
3481
+ channels: {
3482
+ type: 'array',
3483
+ items: { type: 'string' },
3484
+ description: 'Filter by multiple channels',
3485
+ },
3486
+ createdAfter: {
3487
+ type: 'string',
3488
+ description: 'ISO date - items created after this time',
3489
+ },
3490
+ createdBefore: {
3491
+ type: 'string',
3492
+ description: 'ISO date - items created before this time',
3493
+ },
3494
+ relativeTime: {
3495
+ type: 'string',
3496
+ description: 'Natural language time (e.g., "2 hours ago", "yesterday")',
3497
+ },
3498
+ keyPattern: {
3499
+ type: 'string',
3500
+ description: 'Pattern for key matching (uses GLOB syntax)',
3501
+ },
3502
+ priorities: {
3503
+ type: 'array',
3504
+ items: { type: 'string', enum: ['high', 'normal', 'low'] },
3505
+ description: 'Filter by priority levels',
3506
+ },
3507
+ sort: {
3508
+ type: 'string',
3509
+ enum: ['created_desc', 'created_asc', 'updated_desc', 'key_asc', 'key_desc'],
3510
+ description: 'Sort order for results',
3511
+ },
3512
+ limit: {
3513
+ type: 'number',
3514
+ description: 'Maximum items to return. Must be a positive integer. Invalid values will cause validation error. (default: auto-derived)',
3515
+ },
3516
+ offset: {
3517
+ type: 'number',
3518
+ description: 'Pagination offset. Must be a non-negative integer. Invalid values will cause validation error. (default: 0)',
3519
+ },
3520
+ includeMetadata: {
3521
+ type: 'boolean',
3522
+ description: 'Include timestamps and size info',
3523
+ },
3524
+ matchMode: {
3525
+ type: 'string',
3526
+ enum: ['and', 'or'],
3527
+ default: 'and',
3528
+ description: 'Multi-word query mode. AND (default) requires all terms to match; OR returns results matching any term.',
3529
+ },
3530
+ useFts5: {
3531
+ type: 'boolean',
3532
+ default: false,
3533
+ description: 'Use FTS5 full-text search with BM25 ranking. Best for large datasets and ASCII content. Terms < 3 characters automatically fall back to LIKE search.',
3534
+ },
3535
+ },
3536
+ required: ['query'],
3537
+ },
3538
+ },
3539
+ // Cross-Session Collaboration
3540
+ // REMOVED: Sharing is now automatic (public by default)
3541
+ /*
3542
+ {
3543
+ name: 'context_share',
3544
+ description: 'Share a context item with other sessions for cross-session collaboration',
3545
+ inputSchema: {
3546
+ type: 'object',
3547
+ properties: {
3548
+ key: { type: 'string', description: 'Key of the item to share' },
3549
+ targetSessions: {
3550
+ type: 'array',
3551
+ items: { type: 'string' },
3552
+ description: 'Session IDs to share with (empty for public sharing)'
3553
+ },
3554
+ makePublic: {
3555
+ type: 'boolean',
3556
+ description: 'Share with all sessions',
3557
+ default: false
3558
+ },
3559
+ },
3560
+ required: ['key'],
3561
+ },
3562
+ },
3563
+ */
3564
+ // REMOVED: All accessible items are retrieved via context_get
3565
+ /*
3566
+ {
3567
+ name: 'context_get_shared',
3568
+ description: 'Get shared context items from other sessions',
3569
+ inputSchema: {
3570
+ type: 'object',
3571
+ properties: {
3572
+ includeAll: {
3573
+ type: 'boolean',
3574
+ description: 'Include all shared items from all sessions',
3575
+ default: false
3576
+ },
3577
+ },
3578
+ },
3579
+ },
3580
+ */
3581
+ {
3582
+ name: 'context_search_all',
3583
+ description: 'Search across multiple or all sessions with pagination support',
3584
+ inputSchema: {
3585
+ type: 'object',
3586
+ properties: {
3587
+ query: { type: 'string', description: 'Search query' },
3588
+ sessions: {
3589
+ type: 'array',
3590
+ items: { type: 'string' },
3591
+ description: 'Session IDs to search (empty for all sessions)',
3592
+ },
3593
+ includeShared: {
3594
+ type: 'boolean',
3595
+ description: 'Include shared items in search',
3596
+ default: true,
3597
+ },
3598
+ limit: {
3599
+ type: 'number',
3600
+ description: 'Maximum number of items to return. Must be a positive integer between 1-100. Non-integer values will be rejected with validation error. (default: 25)',
3601
+ minimum: 1,
3602
+ maximum: 100,
3603
+ default: 25,
3604
+ },
3605
+ offset: {
3606
+ type: 'number',
3607
+ description: 'Number of items to skip for pagination. Must be a non-negative integer (0 or higher). Non-integer values will be rejected with validation error. (default: 0)',
3608
+ minimum: 0,
3609
+ default: 0,
3610
+ },
3611
+ sort: {
3612
+ type: 'string',
3613
+ description: 'Sort order for results',
3614
+ enum: ['created_desc', 'created_asc', 'updated_desc', 'key_asc', 'key_desc'],
3615
+ default: 'created_desc',
3616
+ },
3617
+ category: {
3618
+ type: 'string',
3619
+ description: 'Filter by category',
3620
+ enum: ['task', 'decision', 'progress', 'note', 'error', 'warning'],
3621
+ },
3622
+ channel: {
3623
+ type: 'string',
3624
+ description: 'Filter by single channel',
3625
+ },
3626
+ channels: {
3627
+ type: 'array',
3628
+ items: { type: 'string' },
3629
+ description: 'Filter by multiple channels',
3630
+ },
3631
+ priorities: {
3632
+ type: 'array',
3633
+ items: { type: 'string', enum: ['high', 'normal', 'low'] },
3634
+ description: 'Filter by priority levels',
3635
+ },
3636
+ createdAfter: {
3637
+ type: 'string',
3638
+ description: 'Filter items created after this date (ISO format or relative time)',
3639
+ },
3640
+ createdBefore: {
3641
+ type: 'string',
3642
+ description: 'Filter items created before this date (ISO format or relative time)',
3643
+ },
3644
+ keyPattern: {
3645
+ type: 'string',
3646
+ description: 'Pattern to match keys (supports wildcards: *, ?)',
3647
+ },
3648
+ searchIn: {
3649
+ type: 'array',
3650
+ items: { type: 'string', enum: ['key', 'value'] },
3651
+ description: 'Fields to search in',
3652
+ default: ['key', 'value'],
3653
+ },
3654
+ includeMetadata: {
3655
+ type: 'boolean',
3656
+ description: 'Include timestamps and size info',
3657
+ default: false,
3658
+ },
3659
+ matchMode: {
3660
+ type: 'string',
3661
+ enum: ['and', 'or'],
3662
+ default: 'and',
3663
+ description: 'Multi-word query mode. AND (default) requires all terms to match; OR returns results matching any term.',
3664
+ },
3665
+ useFts5: {
3666
+ type: 'boolean',
3667
+ default: false,
3668
+ description: 'Use FTS5 full-text search with BM25 ranking. Best for large datasets and ASCII content. Terms < 3 characters automatically fall back to LIKE search.',
3669
+ },
3670
+ },
3671
+ required: ['query'],
3672
+ },
3673
+ },
3674
+ // Phase 3: Export/Import
3675
+ {
3676
+ name: 'context_export',
3677
+ description: 'Export session data for backup or sharing',
3678
+ inputSchema: {
3679
+ type: 'object',
3680
+ properties: {
3681
+ sessionId: { type: 'string', description: 'Session to export (defaults to current)' },
3682
+ format: {
3683
+ type: 'string',
3684
+ enum: ['json', 'inline'],
3685
+ description: 'Export format',
3686
+ default: 'json',
3687
+ },
3688
+ },
3689
+ },
3690
+ },
3691
+ {
3692
+ name: 'context_import',
3693
+ description: 'Import previously exported session data',
3694
+ inputSchema: {
3695
+ type: 'object',
3696
+ properties: {
3697
+ filePath: { type: 'string', description: 'Path to import file' },
3698
+ merge: {
3699
+ type: 'boolean',
3700
+ description: 'Merge with current session instead of creating new',
3701
+ default: false,
3702
+ },
3703
+ },
3704
+ required: ['filePath'],
3705
+ },
3706
+ },
3707
+ // Phase 4.1: Knowledge Graph
3708
+ {
3709
+ name: 'context_analyze',
3710
+ description: 'Analyze context to extract entities and relationships',
3711
+ inputSchema: {
3712
+ type: 'object',
3713
+ properties: {
3714
+ sessionId: {
3715
+ type: 'string',
3716
+ description: 'Session ID to analyze (defaults to current)',
3717
+ },
3718
+ categories: {
3719
+ type: 'array',
3720
+ items: { type: 'string' },
3721
+ description: 'Categories to analyze',
3722
+ },
3723
+ },
3724
+ },
3725
+ },
3726
+ {
3727
+ name: 'context_find_related',
3728
+ description: 'Find entities related to a key or entity',
3729
+ inputSchema: {
3730
+ type: 'object',
3731
+ properties: {
3732
+ key: { type: 'string', description: 'Context key or entity name' },
3733
+ relationTypes: {
3734
+ type: 'array',
3735
+ items: { type: 'string' },
3736
+ description: 'Types of relations to include',
3737
+ },
3738
+ maxDepth: {
3739
+ type: 'number',
3740
+ description: 'Maximum graph traversal depth',
3741
+ default: 2,
3742
+ },
3743
+ },
3744
+ required: ['key'],
3745
+ },
3746
+ },
3747
+ {
3748
+ name: 'context_visualize',
3749
+ description: 'Generate visualization data for the knowledge graph',
3750
+ inputSchema: {
3751
+ type: 'object',
3752
+ properties: {
3753
+ type: {
3754
+ type: 'string',
3755
+ enum: ['graph', 'timeline', 'heatmap'],
3756
+ description: 'Visualization type',
3757
+ default: 'graph',
3758
+ },
3759
+ entityTypes: {
3760
+ type: 'array',
3761
+ items: { type: 'string' },
3762
+ description: 'Entity types to include',
3763
+ },
3764
+ sessionId: {
3765
+ type: 'string',
3766
+ description: 'Session to visualize (defaults to current)',
3767
+ },
3768
+ },
3769
+ },
3770
+ },
3771
+ // Phase 4.2: Semantic Search
3772
+ {
3773
+ name: 'context_semantic_search',
3774
+ description: 'Search context using natural language queries',
3775
+ inputSchema: {
3776
+ type: 'object',
3777
+ properties: {
3778
+ query: { type: 'string', description: 'Natural language search query' },
3779
+ topK: {
3780
+ type: 'number',
3781
+ description: 'Number of results to return',
3782
+ default: 10,
3783
+ },
3784
+ minSimilarity: {
3785
+ type: 'number',
3786
+ description: 'Minimum similarity score (0-1)',
3787
+ default: 0.3,
3788
+ },
3789
+ sessionId: {
3790
+ type: 'string',
3791
+ description: 'Search within specific session (defaults to current)',
3792
+ },
3793
+ },
3794
+ required: ['query'],
3795
+ },
3796
+ },
3797
+ // Phase 4.3: Multi-Agent System
3798
+ {
3799
+ name: 'context_delegate',
3800
+ description: 'Delegate complex analysis tasks to specialized agents',
3801
+ inputSchema: {
3802
+ type: 'object',
3803
+ properties: {
3804
+ taskType: {
3805
+ type: 'string',
3806
+ enum: ['analyze', 'synthesize'],
3807
+ description: 'Type of task to delegate',
3808
+ },
3809
+ input: {
3810
+ type: 'object',
3811
+ properties: {
3812
+ analysisType: {
3813
+ type: 'string',
3814
+ enum: ['patterns', 'relationships', 'trends', 'comprehensive'],
3815
+ description: 'For analyze tasks: type of analysis',
3816
+ },
3817
+ synthesisType: {
3818
+ type: 'string',
3819
+ enum: ['summary', 'merge', 'recommendations'],
3820
+ description: 'For synthesize tasks: type of synthesis',
3821
+ },
3822
+ categories: {
3823
+ type: 'array',
3824
+ items: { type: 'string' },
3825
+ description: 'Categories to include in analysis',
3826
+ },
3827
+ timeframe: {
3828
+ type: 'string',
3829
+ description: 'Time period for analysis (e.g., "-7 days")',
3830
+ },
3831
+ maxLength: {
3832
+ type: 'number',
3833
+ description: 'Maximum length for summaries',
3834
+ },
3835
+ insights: {
3836
+ type: 'array',
3837
+ items: {
3838
+ type: 'object',
3839
+ properties: {
3840
+ patterns: { type: 'object', properties: {} },
3841
+ relationships: { type: 'object', properties: {} },
3842
+ trends: { type: 'object', properties: {} },
3843
+ themes: { type: 'array', items: { type: 'string' } },
3844
+ },
3845
+ },
3846
+ description: 'For merge synthesis: array of insights to merge',
3847
+ },
3848
+ },
3849
+ },
3850
+ chain: {
3851
+ type: 'boolean',
3852
+ description: 'Process multiple tasks in sequence',
3853
+ default: false,
3854
+ },
3855
+ sessionId: { type: 'string', description: 'Session to analyze (defaults to current)' },
3856
+ },
3857
+ required: ['taskType', 'input'],
3858
+ },
3859
+ },
3860
+ // Phase 4.4: Advanced Features
3861
+ {
3862
+ name: 'context_branch_session',
3863
+ description: 'Create a branch from current session for exploring alternatives',
3864
+ inputSchema: {
3865
+ type: 'object',
3866
+ properties: {
3867
+ branchName: {
3868
+ type: 'string',
3869
+ description: 'Name for the new branch',
3870
+ },
3871
+ copyDepth: {
3872
+ type: 'string',
3873
+ enum: ['shallow', 'deep'],
3874
+ description: 'How much to copy: shallow (high priority only) or deep (everything)',
3875
+ default: 'shallow',
3876
+ },
3877
+ },
3878
+ required: ['branchName'],
3879
+ },
3880
+ },
3881
+ {
3882
+ name: 'context_merge_sessions',
3883
+ description: 'Merge another session into the current one',
3884
+ inputSchema: {
3885
+ type: 'object',
3886
+ properties: {
3887
+ sourceSessionId: {
3888
+ type: 'string',
3889
+ description: 'ID of the session to merge from',
3890
+ },
3891
+ conflictResolution: {
3892
+ type: 'string',
3893
+ enum: ['keep_current', 'keep_source', 'keep_newest'],
3894
+ description: 'How to resolve conflicts',
3895
+ default: 'keep_current',
3896
+ },
3897
+ },
3898
+ required: ['sourceSessionId'],
3899
+ },
3900
+ },
3901
+ {
3902
+ name: 'context_journal_entry',
3903
+ description: 'Add a timestamped journal entry with optional tags and mood',
3904
+ inputSchema: {
3905
+ type: 'object',
3906
+ properties: {
3907
+ entry: {
3908
+ type: 'string',
3909
+ description: 'Journal entry text',
3910
+ },
3911
+ tags: {
3912
+ type: 'array',
3913
+ items: { type: 'string' },
3914
+ description: 'Tags for categorization',
3915
+ },
3916
+ mood: {
3917
+ type: 'string',
3918
+ description: 'Current mood/feeling',
3919
+ },
3920
+ },
3921
+ required: ['entry'],
3922
+ },
3923
+ },
3924
+ {
3925
+ name: 'context_timeline',
3926
+ description: 'Get timeline of activities with optional grouping',
3927
+ inputSchema: {
3928
+ type: 'object',
3929
+ properties: {
3930
+ startDate: {
3931
+ type: 'string',
3932
+ description: 'Start date (ISO format)',
3933
+ },
3934
+ endDate: {
3935
+ type: 'string',
3936
+ description: 'End date (ISO format)',
3937
+ },
3938
+ groupBy: {
3939
+ type: 'string',
3940
+ enum: ['hour', 'day', 'week'],
3941
+ description: 'How to group timeline data',
3942
+ default: 'day',
3943
+ },
3944
+ sessionId: {
3945
+ type: 'string',
3946
+ description: 'Session to analyze (defaults to current)',
3947
+ },
3948
+ includeItems: {
3949
+ type: 'boolean',
3950
+ description: 'Include item details in timeline',
3951
+ },
3952
+ categories: {
3953
+ type: 'array',
3954
+ items: { type: 'string' },
3955
+ description: 'Filter by categories',
3956
+ },
3957
+ relativeTime: {
3958
+ type: 'string',
3959
+ description: 'Natural language time (e.g., "2 hours ago", "today")',
3960
+ },
3961
+ itemsPerPeriod: {
3962
+ type: 'number',
3963
+ description: 'Max items per time period',
3964
+ },
3965
+ minItemsPerPeriod: {
3966
+ type: 'number',
3967
+ description: 'Only include periods with at least N items',
3968
+ },
3969
+ showEmpty: {
3970
+ type: 'boolean',
3971
+ description: 'Include periods with 0 items (default: false)',
3972
+ },
3973
+ },
3974
+ },
3975
+ },
3976
+ {
3977
+ name: 'context_compress',
3978
+ description: 'Intelligently compress old context to save space',
3979
+ inputSchema: {
3980
+ type: 'object',
3981
+ properties: {
3982
+ olderThan: {
3983
+ type: 'string',
3984
+ description: 'Compress items older than this date (ISO format)',
3985
+ },
3986
+ preserveCategories: {
3987
+ type: 'array',
3988
+ items: { type: 'string' },
3989
+ description: 'Categories to preserve (not compress)',
3990
+ },
3991
+ targetSize: {
3992
+ type: 'number',
3993
+ description: 'Target size in KB (optional)',
3994
+ },
3995
+ sessionId: {
3996
+ type: 'string',
3997
+ description: 'Session to compress (defaults to current)',
3998
+ },
3999
+ },
4000
+ },
4001
+ },
4002
+ {
4003
+ name: 'context_integrate_tool',
4004
+ description: 'Track events from other MCP tools',
4005
+ inputSchema: {
4006
+ type: 'object',
4007
+ properties: {
4008
+ toolName: {
4009
+ type: 'string',
4010
+ description: 'Name of the tool',
4011
+ },
4012
+ eventType: {
4013
+ type: 'string',
4014
+ description: 'Type of event',
4015
+ },
4016
+ data: {
4017
+ type: 'object',
4018
+ description: 'Event data',
4019
+ properties: {
4020
+ important: {
4021
+ type: 'boolean',
4022
+ description: 'Mark as important to save as context item',
4023
+ },
4024
+ },
4025
+ },
4026
+ },
4027
+ required: ['toolName', 'eventType', 'data'],
4028
+ },
4029
+ },
4030
+ {
4031
+ name: 'context_diff',
4032
+ description: 'Get changes to context items since a specific point in time (timestamp, checkpoint, or relative time)',
4033
+ inputSchema: {
4034
+ type: 'object',
4035
+ properties: {
4036
+ since: {
4037
+ type: 'string',
4038
+ description: 'Point in time to compare against (ISO timestamp, checkpoint name/ID, or relative time like "2 hours ago")',
4039
+ },
4040
+ sessionId: {
4041
+ type: 'string',
4042
+ description: 'Session ID to analyze (defaults to current)',
4043
+ },
4044
+ category: {
4045
+ type: 'string',
4046
+ description: 'Filter by category',
4047
+ },
4048
+ channel: {
4049
+ type: 'string',
4050
+ description: 'Filter by single channel',
4051
+ },
4052
+ channels: {
4053
+ type: 'array',
4054
+ items: { type: 'string' },
4055
+ description: 'Filter by multiple channels',
4056
+ },
4057
+ includeValues: {
4058
+ type: 'boolean',
4059
+ description: 'Include full item values in response',
4060
+ default: true,
4061
+ },
4062
+ limit: {
4063
+ type: 'number',
4064
+ description: 'Maximum items per category (added/modified)',
4065
+ },
4066
+ offset: {
4067
+ type: 'number',
4068
+ description: 'Pagination offset',
4069
+ },
4070
+ },
4071
+ },
4072
+ },
4073
+ {
4074
+ name: 'context_list_channels',
4075
+ description: 'List all channels with metadata (counts, activity, categories)',
4076
+ inputSchema: {
4077
+ type: 'object',
4078
+ properties: {
4079
+ sessionId: {
4080
+ type: 'string',
4081
+ description: 'Filter by specific session (shows accessible items)',
4082
+ },
4083
+ sessionIds: {
4084
+ type: 'array',
4085
+ items: { type: 'string' },
4086
+ description: 'Filter by multiple sessions',
4087
+ },
4088
+ sort: {
4089
+ type: 'string',
4090
+ enum: ['name', 'count', 'activity'],
4091
+ description: 'Sort order for results',
4092
+ default: 'name',
4093
+ },
4094
+ includeEmpty: {
4095
+ type: 'boolean',
4096
+ description: 'Include channels with no items',
4097
+ default: false,
4098
+ },
4099
+ },
4100
+ },
4101
+ },
4102
+ {
4103
+ name: 'context_channel_stats',
4104
+ description: 'Get detailed statistics for a specific channel or all channels',
4105
+ inputSchema: {
4106
+ type: 'object',
4107
+ properties: {
4108
+ channel: {
4109
+ type: 'string',
4110
+ description: 'Specific channel name (omit for all channels overview)',
4111
+ },
4112
+ sessionId: {
4113
+ type: 'string',
4114
+ description: 'Session context for privacy filtering',
4115
+ },
4116
+ includeTimeSeries: {
4117
+ type: 'boolean',
4118
+ description: 'Include hourly/daily activity data',
4119
+ default: false,
4120
+ },
4121
+ includeInsights: {
4122
+ type: 'boolean',
4123
+ description: 'Include AI-generated insights',
4124
+ default: false,
4125
+ },
4126
+ },
4127
+ },
4128
+ },
4129
+ // Context Watch - Real-time monitoring
4130
+ {
4131
+ name: 'context_watch',
4132
+ description: 'Create and manage watchers for real-time context change monitoring',
4133
+ inputSchema: {
4134
+ type: 'object',
4135
+ properties: {
4136
+ action: {
4137
+ type: 'string',
4138
+ enum: ['create', 'poll', 'stop', 'list'],
4139
+ description: 'Action to perform',
4140
+ },
4141
+ watcherId: {
4142
+ type: 'string',
4143
+ description: 'Watcher ID (required for poll/stop actions)',
4144
+ },
4145
+ filters: {
4146
+ type: 'object',
4147
+ description: 'Filters for watching specific changes (for create action)',
4148
+ properties: {
4149
+ keys: {
4150
+ type: 'array',
4151
+ items: { type: 'string' },
4152
+ description: 'Key patterns to watch (supports wildcards: *, ?)',
4153
+ },
4154
+ categories: {
4155
+ type: 'array',
4156
+ items: {
4157
+ type: 'string',
4158
+ enum: ['task', 'decision', 'progress', 'note', 'error', 'warning'],
4159
+ },
4160
+ description: 'Categories to watch',
4161
+ },
4162
+ channels: {
4163
+ type: 'array',
4164
+ items: { type: 'string' },
4165
+ description: 'Channels to watch',
4166
+ },
4167
+ priorities: {
4168
+ type: 'array',
4169
+ items: {
4170
+ type: 'string',
4171
+ enum: ['high', 'normal', 'low'],
4172
+ },
4173
+ description: 'Priority levels to watch',
4174
+ },
4175
+ },
4176
+ },
4177
+ pollTimeout: {
4178
+ type: 'number',
4179
+ description: 'Polling timeout in seconds (default: 30)',
4180
+ default: 30,
4181
+ },
4182
+ },
4183
+ required: ['action'],
4184
+ },
4185
+ },
4186
+ // Context Reassign Channel
4187
+ {
4188
+ name: 'context_reassign_channel',
4189
+ description: 'Move context items between channels based on keys, patterns, or entire channel',
4190
+ inputSchema: {
4191
+ type: 'object',
4192
+ properties: {
4193
+ keys: {
4194
+ type: 'array',
4195
+ items: { type: 'string' },
4196
+ description: 'Specific keys to reassign',
4197
+ },
4198
+ keyPattern: {
4199
+ type: 'string',
4200
+ description: 'Pattern to match keys (supports wildcards: *, ?)',
4201
+ },
4202
+ fromChannel: {
4203
+ type: 'string',
4204
+ description: 'Source channel to move all items from',
4205
+ },
4206
+ toChannel: {
4207
+ type: 'string',
4208
+ description: 'Target channel to move items to',
4209
+ },
4210
+ sessionId: {
4211
+ type: 'string',
4212
+ description: 'Session ID (defaults to current)',
4213
+ },
4214
+ category: {
4215
+ type: 'string',
4216
+ enum: ['task', 'decision', 'progress', 'note', 'error', 'warning'],
4217
+ description: 'Filter by category',
4218
+ },
4219
+ priorities: {
4220
+ type: 'array',
4221
+ items: {
4222
+ type: 'string',
4223
+ enum: ['high', 'normal', 'low'],
4224
+ },
4225
+ description: 'Filter by priority levels',
4226
+ },
4227
+ dryRun: {
4228
+ type: 'boolean',
4229
+ description: 'Preview changes without applying them',
4230
+ default: false,
4231
+ },
4232
+ },
4233
+ required: ['toChannel'],
4234
+ },
4235
+ },
4236
+ // Batch Operations
4237
+ {
4238
+ name: 'context_batch_save',
4239
+ description: 'Save multiple context items in a single atomic operation',
4240
+ inputSchema: {
4241
+ type: 'object',
4242
+ properties: {
4243
+ items: {
4244
+ type: 'array',
4245
+ description: 'Array of items to save',
4246
+ items: {
4247
+ type: 'object',
4248
+ properties: {
4249
+ key: { type: 'string', description: 'Unique key for the context item' },
4250
+ value: { type: 'string', description: 'Context value to save' },
4251
+ category: {
4252
+ type: 'string',
4253
+ description: 'Category (e.g., task, decision, progress)',
4254
+ enum: ['task', 'decision', 'progress', 'note', 'error', 'warning'],
4255
+ },
4256
+ priority: {
4257
+ type: 'string',
4258
+ description: 'Priority level',
4259
+ enum: ['high', 'normal', 'low'],
4260
+ },
4261
+ channel: {
4262
+ type: 'string',
4263
+ description: 'Channel to organize this item',
4264
+ },
4265
+ },
4266
+ required: ['key', 'value'],
4267
+ },
4268
+ },
4269
+ updateExisting: {
4270
+ type: 'boolean',
4271
+ description: 'Update existing items with same key (default: true)',
4272
+ default: true,
4273
+ },
4274
+ },
4275
+ required: ['items'],
4276
+ },
4277
+ },
4278
+ {
4279
+ name: 'context_batch_delete',
4280
+ description: 'Delete multiple context items by keys or pattern in a single atomic operation',
4281
+ inputSchema: {
4282
+ type: 'object',
4283
+ properties: {
4284
+ keys: {
4285
+ type: 'array',
4286
+ items: { type: 'string' },
4287
+ description: 'Array of specific keys to delete',
4288
+ },
4289
+ keyPattern: {
4290
+ type: 'string',
4291
+ description: 'Pattern to match keys for deletion (supports wildcards: *, ?)',
4292
+ },
4293
+ sessionId: {
4294
+ type: 'string',
4295
+ description: 'Session ID (defaults to current)',
4296
+ },
4297
+ dryRun: {
4298
+ type: 'boolean',
4299
+ description: 'Preview items to be deleted without actually deleting',
4300
+ default: false,
4301
+ },
4302
+ },
4303
+ },
4304
+ },
4305
+ {
4306
+ name: 'context_batch_update',
4307
+ description: 'Update multiple context items with partial updates in a single atomic operation',
4308
+ inputSchema: {
4309
+ type: 'object',
4310
+ properties: {
4311
+ updates: {
4312
+ type: 'array',
4313
+ description: 'Array of updates to apply',
4314
+ items: {
4315
+ type: 'object',
4316
+ properties: {
4317
+ key: { type: 'string', description: 'Key of the item to update' },
4318
+ value: { type: 'string', description: 'New value (optional)' },
4319
+ category: {
4320
+ type: 'string',
4321
+ description: 'New category (optional)',
4322
+ enum: ['task', 'decision', 'progress', 'note', 'error', 'warning'],
4323
+ },
4324
+ priority: {
4325
+ type: 'string',
4326
+ description: 'New priority (optional)',
4327
+ enum: ['high', 'normal', 'low'],
4328
+ },
4329
+ channel: {
4330
+ type: 'string',
4331
+ description: 'New channel (optional)',
4332
+ },
4333
+ },
4334
+ required: ['key'],
4335
+ },
4336
+ },
4337
+ sessionId: {
4338
+ type: 'string',
4339
+ description: 'Session ID (defaults to current)',
4340
+ },
4341
+ },
4342
+ required: ['updates'],
4343
+ },
4344
+ },
4345
+ // Context Relationships
4346
+ {
4347
+ name: 'context_link',
4348
+ description: 'Create a relationship between two context items',
4349
+ inputSchema: {
4350
+ type: 'object',
4351
+ properties: {
4352
+ sourceKey: {
4353
+ type: 'string',
4354
+ description: 'Key of the source context item',
4355
+ },
4356
+ targetKey: {
4357
+ type: 'string',
4358
+ description: 'Key of the target context item',
4359
+ },
4360
+ relationship: {
4361
+ type: 'string',
4362
+ description: 'Type of relationship',
4363
+ enum: [
4364
+ 'contains',
4365
+ 'depends_on',
4366
+ 'references',
4367
+ 'implements',
4368
+ 'extends',
4369
+ 'related_to',
4370
+ 'blocks',
4371
+ 'blocked_by',
4372
+ 'parent_of',
4373
+ 'child_of',
4374
+ 'has_task',
4375
+ 'documented_in',
4376
+ 'serves',
4377
+ 'leads_to',
4378
+ ],
4379
+ },
4380
+ metadata: {
4381
+ type: 'object',
4382
+ properties: {},
4383
+ description: 'Optional metadata for the relationship',
4384
+ },
4385
+ },
4386
+ required: ['sourceKey', 'targetKey', 'relationship'],
4387
+ },
4388
+ },
4389
+ {
4390
+ name: 'context_get_related',
4391
+ description: 'Get items related to a given context item',
4392
+ inputSchema: {
4393
+ type: 'object',
4394
+ properties: {
4395
+ key: {
4396
+ type: 'string',
4397
+ description: 'Key of the context item to find relationships for',
4398
+ },
4399
+ relationship: {
4400
+ type: 'string',
4401
+ description: 'Filter by specific relationship type',
4402
+ },
4403
+ depth: {
4404
+ type: 'number',
4405
+ description: 'Traversal depth for multi-level relationships (default: 1)',
4406
+ default: 1,
4407
+ },
4408
+ direction: {
4409
+ type: 'string',
4410
+ description: 'Direction of relationships to retrieve',
4411
+ enum: ['outgoing', 'incoming', 'both'],
4412
+ default: 'both',
4413
+ },
4414
+ },
4415
+ required: ['key'],
4416
+ },
4417
+ },
4418
+ ];
4419
+ return {
4420
+ tools: allTools.filter(tool => enabledTools.has(tool.name)),
4421
+ };
4422
+ });
4423
+ // Start server
4424
+ const transport = new stdio_js_1.StdioServerTransport();
4425
+ server.connect(transport);