claude-memory-layer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/.claude-plugin/commands/memory-forget.md +42 -0
  2. package/.claude-plugin/commands/memory-history.md +34 -0
  3. package/.claude-plugin/commands/memory-import.md +56 -0
  4. package/.claude-plugin/commands/memory-list.md +37 -0
  5. package/.claude-plugin/commands/memory-search.md +36 -0
  6. package/.claude-plugin/commands/memory-stats.md +34 -0
  7. package/.claude-plugin/hooks.json +59 -0
  8. package/.claude-plugin/plugin.json +24 -0
  9. package/.history/package_20260201112328.json +45 -0
  10. package/.history/package_20260201113602.json +45 -0
  11. package/.history/package_20260201113713.json +45 -0
  12. package/.history/package_20260201114110.json +45 -0
  13. package/Memo.txt +558 -0
  14. package/README.md +520 -0
  15. package/context.md +636 -0
  16. package/dist/.claude-plugin/commands/memory-forget.md +42 -0
  17. package/dist/.claude-plugin/commands/memory-history.md +34 -0
  18. package/dist/.claude-plugin/commands/memory-import.md +56 -0
  19. package/dist/.claude-plugin/commands/memory-list.md +37 -0
  20. package/dist/.claude-plugin/commands/memory-search.md +36 -0
  21. package/dist/.claude-plugin/commands/memory-stats.md +34 -0
  22. package/dist/.claude-plugin/hooks.json +59 -0
  23. package/dist/.claude-plugin/plugin.json +24 -0
  24. package/dist/cli/index.js +3539 -0
  25. package/dist/cli/index.js.map +7 -0
  26. package/dist/core/index.js +4408 -0
  27. package/dist/core/index.js.map +7 -0
  28. package/dist/hooks/session-end.js +2971 -0
  29. package/dist/hooks/session-end.js.map +7 -0
  30. package/dist/hooks/session-start.js +2969 -0
  31. package/dist/hooks/session-start.js.map +7 -0
  32. package/dist/hooks/stop.js +3123 -0
  33. package/dist/hooks/stop.js.map +7 -0
  34. package/dist/hooks/user-prompt-submit.js +2960 -0
  35. package/dist/hooks/user-prompt-submit.js.map +7 -0
  36. package/dist/services/memory-service.js +2931 -0
  37. package/dist/services/memory-service.js.map +7 -0
  38. package/package.json +45 -0
  39. package/plan.md +1642 -0
  40. package/scripts/build.ts +102 -0
  41. package/spec.md +624 -0
  42. package/specs/citations-system/context.md +243 -0
  43. package/specs/citations-system/plan.md +495 -0
  44. package/specs/citations-system/spec.md +371 -0
  45. package/specs/endless-mode/context.md +305 -0
  46. package/specs/endless-mode/plan.md +620 -0
  47. package/specs/endless-mode/spec.md +455 -0
  48. package/specs/entity-edge-model/context.md +401 -0
  49. package/specs/entity-edge-model/plan.md +459 -0
  50. package/specs/entity-edge-model/spec.md +391 -0
  51. package/specs/evidence-aligner-v2/context.md +401 -0
  52. package/specs/evidence-aligner-v2/plan.md +303 -0
  53. package/specs/evidence-aligner-v2/spec.md +312 -0
  54. package/specs/mcp-desktop-integration/context.md +278 -0
  55. package/specs/mcp-desktop-integration/plan.md +550 -0
  56. package/specs/mcp-desktop-integration/spec.md +494 -0
  57. package/specs/post-tool-use-hook/context.md +319 -0
  58. package/specs/post-tool-use-hook/plan.md +469 -0
  59. package/specs/post-tool-use-hook/spec.md +364 -0
  60. package/specs/private-tags/context.md +288 -0
  61. package/specs/private-tags/plan.md +412 -0
  62. package/specs/private-tags/spec.md +345 -0
  63. package/specs/progressive-disclosure/context.md +346 -0
  64. package/specs/progressive-disclosure/plan.md +663 -0
  65. package/specs/progressive-disclosure/spec.md +415 -0
  66. package/specs/task-entity-system/context.md +297 -0
  67. package/specs/task-entity-system/plan.md +301 -0
  68. package/specs/task-entity-system/spec.md +314 -0
  69. package/specs/vector-outbox-v2/context.md +470 -0
  70. package/specs/vector-outbox-v2/plan.md +562 -0
  71. package/specs/vector-outbox-v2/spec.md +466 -0
  72. package/specs/web-viewer-ui/context.md +384 -0
  73. package/specs/web-viewer-ui/plan.md +797 -0
  74. package/specs/web-viewer-ui/spec.md +516 -0
  75. package/src/cli/index.ts +570 -0
  76. package/src/core/canonical-key.ts +186 -0
  77. package/src/core/citation-generator.ts +63 -0
  78. package/src/core/consolidated-store.ts +279 -0
  79. package/src/core/consolidation-worker.ts +384 -0
  80. package/src/core/context-formatter.ts +276 -0
  81. package/src/core/continuity-manager.ts +336 -0
  82. package/src/core/edge-repo.ts +324 -0
  83. package/src/core/embedder.ts +124 -0
  84. package/src/core/entity-repo.ts +342 -0
  85. package/src/core/event-store.ts +672 -0
  86. package/src/core/evidence-aligner.ts +635 -0
  87. package/src/core/graduation.ts +365 -0
  88. package/src/core/index.ts +32 -0
  89. package/src/core/matcher.ts +210 -0
  90. package/src/core/metadata-extractor.ts +203 -0
  91. package/src/core/privacy/filter.ts +179 -0
  92. package/src/core/privacy/index.ts +20 -0
  93. package/src/core/privacy/tag-parser.ts +145 -0
  94. package/src/core/progressive-retriever.ts +415 -0
  95. package/src/core/retriever.ts +235 -0
  96. package/src/core/task/blocker-resolver.ts +325 -0
  97. package/src/core/task/index.ts +9 -0
  98. package/src/core/task/task-matcher.ts +238 -0
  99. package/src/core/task/task-projector.ts +345 -0
  100. package/src/core/task/task-resolver.ts +414 -0
  101. package/src/core/types.ts +841 -0
  102. package/src/core/vector-outbox.ts +295 -0
  103. package/src/core/vector-store.ts +182 -0
  104. package/src/core/vector-worker.ts +488 -0
  105. package/src/core/working-set-store.ts +244 -0
  106. package/src/hooks/post-tool-use.ts +127 -0
  107. package/src/hooks/session-end.ts +78 -0
  108. package/src/hooks/session-start.ts +57 -0
  109. package/src/hooks/stop.ts +78 -0
  110. package/src/hooks/user-prompt-submit.ts +54 -0
  111. package/src/mcp/handlers.ts +212 -0
  112. package/src/mcp/index.ts +47 -0
  113. package/src/mcp/tools.ts +78 -0
  114. package/src/server/api/citations.ts +101 -0
  115. package/src/server/api/events.ts +101 -0
  116. package/src/server/api/index.ts +18 -0
  117. package/src/server/api/search.ts +98 -0
  118. package/src/server/api/sessions.ts +111 -0
  119. package/src/server/api/stats.ts +97 -0
  120. package/src/server/index.ts +91 -0
  121. package/src/services/memory-service.ts +626 -0
  122. package/src/services/session-history-importer.ts +367 -0
  123. package/tests/canonical-key.test.ts +101 -0
  124. package/tests/evidence-aligner.test.ts +152 -0
  125. package/tests/matcher.test.ts +112 -0
  126. package/tsconfig.json +24 -0
  127. package/vitest.config.ts +15 -0
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PostToolUse Hook
4
+ * Called after each tool execution - stores tool observations
5
+ */
6
+
7
+ import { getDefaultMemoryService } from '../services/memory-service.js';
8
+ import { applyPrivacyFilter, maskSensitiveInput, truncateOutput } from '../core/privacy/index.js';
9
+ import { extractMetadata, createToolObservationEmbedding } from '../core/metadata-extractor.js';
10
+ import type { PostToolUseInput, ToolObservationPayload, Config } from '../core/types.js';
11
+
12
+ // Default config (will be overridden by actual config when available)
13
+ const DEFAULT_CONFIG: Config['toolObservation'] = {
14
+ enabled: true,
15
+ excludedTools: ['TodoWrite', 'TodoRead'],
16
+ maxOutputLength: 10000,
17
+ maxOutputLines: 100,
18
+ storeOnlyOnSuccess: false
19
+ };
20
+
21
+ const DEFAULT_PRIVACY_CONFIG: Config['privacy'] = {
22
+ excludePatterns: ['password', 'secret', 'api_key', 'token', 'bearer'],
23
+ anonymize: false,
24
+ privateTags: {
25
+ enabled: true,
26
+ marker: '[PRIVATE]',
27
+ preserveLineCount: false,
28
+ supportedFormats: ['xml']
29
+ }
30
+ };
31
+
32
+ /**
33
+ * Calculate duration from ISO timestamps
34
+ */
35
+ function calculateDuration(startedAt: string, endedAt: string): number {
36
+ const start = new Date(startedAt).getTime();
37
+ const end = new Date(endedAt).getTime();
38
+ return end - start;
39
+ }
40
+
41
+ async function main(): Promise<void> {
42
+ // Read input from stdin
43
+ const inputData = await readStdin();
44
+ const input: PostToolUseInput = JSON.parse(inputData);
45
+
46
+ const config = DEFAULT_CONFIG;
47
+ const privacyConfig = DEFAULT_PRIVACY_CONFIG;
48
+
49
+ // 1. Check if tool observation is enabled
50
+ if (!config.enabled) {
51
+ console.log(JSON.stringify({}));
52
+ return;
53
+ }
54
+
55
+ // 2. Check if tool is excluded
56
+ if (config.excludedTools?.includes(input.tool_name)) {
57
+ console.log(JSON.stringify({}));
58
+ return;
59
+ }
60
+
61
+ // 3. Check success filter
62
+ const success = !input.tool_error;
63
+ if (!success && config.storeOnlyOnSuccess) {
64
+ console.log(JSON.stringify({}));
65
+ return;
66
+ }
67
+
68
+ try {
69
+ const memoryService = getDefaultMemoryService();
70
+
71
+ // 4. Mask sensitive data in input
72
+ const maskedInput = maskSensitiveInput(input.tool_input);
73
+
74
+ // 5. Apply privacy filter to output
75
+ const filterResult = applyPrivacyFilter(input.tool_output, privacyConfig);
76
+ const maskedOutput = filterResult.content;
77
+
78
+ // 6. Truncate output
79
+ const truncatedOutput = truncateOutput(maskedOutput, {
80
+ maxLength: config.maxOutputLength,
81
+ maxLines: config.maxOutputLines
82
+ });
83
+
84
+ // 7. Extract metadata
85
+ const metadata = extractMetadata(
86
+ input.tool_name,
87
+ maskedInput,
88
+ input.tool_output,
89
+ success
90
+ );
91
+
92
+ // 8. Create payload
93
+ const payload: ToolObservationPayload = {
94
+ toolName: input.tool_name,
95
+ toolInput: maskedInput,
96
+ toolOutput: truncatedOutput,
97
+ durationMs: calculateDuration(input.started_at, input.ended_at),
98
+ success,
99
+ errorMessage: input.tool_error,
100
+ metadata
101
+ };
102
+
103
+ // 9. Store observation
104
+ await memoryService.storeToolObservation(input.session_id, payload);
105
+
106
+ // Output empty (hook doesn't return context)
107
+ console.log(JSON.stringify({}));
108
+ } catch (error) {
109
+ console.error('PostToolUse hook error:', error);
110
+ console.log(JSON.stringify({}));
111
+ }
112
+ }
113
+
114
+ function readStdin(): Promise<string> {
115
+ return new Promise((resolve) => {
116
+ let data = '';
117
+ process.stdin.setEncoding('utf8');
118
+ process.stdin.on('data', (chunk) => {
119
+ data += chunk;
120
+ });
121
+ process.stdin.on('end', () => {
122
+ resolve(data);
123
+ });
124
+ });
125
+ }
126
+
127
+ main().catch(console.error);
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Session End Hook
4
+ * Called when session ends - generates and stores session summary
5
+ */
6
+
7
+ import { getDefaultMemoryService } from '../services/memory-service.js';
8
+ import type { SessionEndInput } from '../core/types.js';
9
+
10
+ async function main(): Promise<void> {
11
+ // Read input from stdin
12
+ const inputData = await readStdin();
13
+ const input: SessionEndInput = JSON.parse(inputData);
14
+
15
+ const memoryService = getDefaultMemoryService();
16
+
17
+ try {
18
+ // Get session history
19
+ const sessionEvents = await memoryService.getSessionHistory(input.session_id);
20
+
21
+ if (sessionEvents.length > 0) {
22
+ // Generate a simple session summary
23
+ const summary = generateSummary(sessionEvents);
24
+
25
+ // Store session summary
26
+ await memoryService.storeSessionSummary(input.session_id, summary);
27
+
28
+ // End session with summary
29
+ await memoryService.endSession(input.session_id, summary);
30
+
31
+ // Process any pending embeddings
32
+ await memoryService.processPendingEmbeddings();
33
+ }
34
+
35
+ console.log(JSON.stringify({}));
36
+ } catch (error) {
37
+ console.error('Memory hook error:', error);
38
+ console.log(JSON.stringify({}));
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Generate a simple session summary from events
44
+ */
45
+ function generateSummary(events: Array<{ eventType: string; content: string }>): string {
46
+ const userPrompts = events.filter(e => e.eventType === 'user_prompt');
47
+ const responses = events.filter(e => e.eventType === 'agent_response');
48
+
49
+ const parts: string[] = [];
50
+
51
+ parts.push(`Session with ${userPrompts.length} user prompts and ${responses.length} responses.`);
52
+
53
+ // Add first few user prompts as topics
54
+ if (userPrompts.length > 0) {
55
+ parts.push('Topics discussed:');
56
+ for (const prompt of userPrompts.slice(0, 3)) {
57
+ const topic = prompt.content.slice(0, 100).replace(/\n/g, ' ');
58
+ parts.push(`- ${topic}${prompt.content.length > 100 ? '...' : ''}`);
59
+ }
60
+ }
61
+
62
+ return parts.join('\n');
63
+ }
64
+
65
+ function readStdin(): Promise<string> {
66
+ return new Promise((resolve) => {
67
+ let data = '';
68
+ process.stdin.setEncoding('utf8');
69
+ process.stdin.on('data', (chunk) => {
70
+ data += chunk;
71
+ });
72
+ process.stdin.on('end', () => {
73
+ resolve(data);
74
+ });
75
+ });
76
+ }
77
+
78
+ main().catch(console.error);
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Session Start Hook
4
+ * Called when a new Claude Code session starts
5
+ */
6
+
7
+ import { getDefaultMemoryService } from '../services/memory-service.js';
8
+ import type { SessionStartInput, SessionStartOutput } from '../core/types.js';
9
+
10
+ async function main(): Promise<void> {
11
+ // Read input from stdin
12
+ const inputData = await readStdin();
13
+ const input: SessionStartInput = JSON.parse(inputData);
14
+
15
+ const memoryService = getDefaultMemoryService();
16
+
17
+ try {
18
+ // Start session in memory service
19
+ await memoryService.startSession(input.session_id, input.cwd);
20
+
21
+ // Get recent context for this project
22
+ const recentEvents = await memoryService.getRecentEvents(10);
23
+ const projectEvents = recentEvents.filter(e =>
24
+ e.metadata?.projectPath === input.cwd
25
+ );
26
+
27
+ let context = '';
28
+ if (projectEvents.length > 0) {
29
+ context = `## Previous Session Context\n\nYou have worked on this project before. Here are some relevant memories:\n\n`;
30
+ for (const event of projectEvents.slice(0, 3)) {
31
+ const date = event.timestamp.toISOString().split('T')[0];
32
+ context += `- **${date}**: ${event.content.slice(0, 150)}...\n`;
33
+ }
34
+ }
35
+
36
+ const output: SessionStartOutput = { context };
37
+ console.log(JSON.stringify(output));
38
+ } catch (error) {
39
+ console.error('Memory hook error:', error);
40
+ console.log(JSON.stringify({ context: '' }));
41
+ }
42
+ }
43
+
44
+ function readStdin(): Promise<string> {
45
+ return new Promise((resolve) => {
46
+ let data = '';
47
+ process.stdin.setEncoding('utf8');
48
+ process.stdin.on('data', (chunk) => {
49
+ data += chunk;
50
+ });
51
+ process.stdin.on('end', () => {
52
+ resolve(data);
53
+ });
54
+ });
55
+ }
56
+
57
+ main().catch(console.error);
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Stop Hook
4
+ * Called when agent stops - stores the conversation messages
5
+ */
6
+
7
+ import { getDefaultMemoryService } from '../services/memory-service.js';
8
+ import { applyPrivacyFilter } from '../core/privacy/index.js';
9
+ import type { StopInput, Config } from '../core/types.js';
10
+
11
+ // Default privacy config
12
+ const DEFAULT_PRIVACY_CONFIG: Config['privacy'] = {
13
+ excludePatterns: ['password', 'secret', 'api_key', 'token', 'bearer'],
14
+ anonymize: false,
15
+ privateTags: {
16
+ enabled: true,
17
+ marker: '[PRIVATE]',
18
+ preserveLineCount: false,
19
+ supportedFormats: ['xml']
20
+ }
21
+ };
22
+
23
+ async function main(): Promise<void> {
24
+ // Read input from stdin
25
+ const inputData = await readStdin();
26
+ const input: StopInput = JSON.parse(inputData);
27
+
28
+ const memoryService = getDefaultMemoryService();
29
+
30
+ try {
31
+ // Store agent responses from the conversation
32
+ for (const message of input.messages) {
33
+ if (message.role === 'assistant' && message.content) {
34
+ // Apply privacy filter
35
+ const filterResult = applyPrivacyFilter(message.content, DEFAULT_PRIVACY_CONFIG);
36
+ let content = filterResult.content;
37
+
38
+ // Truncate very long responses
39
+ if (content.length > 5000) {
40
+ content = content.slice(0, 5000) + '...[truncated]';
41
+ }
42
+
43
+ await memoryService.storeAgentResponse(
44
+ input.session_id,
45
+ content,
46
+ {
47
+ stopReason: input.stop_reason,
48
+ privacy: filterResult.metadata
49
+ }
50
+ );
51
+ }
52
+ }
53
+
54
+ // Process embeddings immediately
55
+ await memoryService.processPendingEmbeddings();
56
+
57
+ // Output empty (stop hook doesn't return context)
58
+ console.log(JSON.stringify({}));
59
+ } catch (error) {
60
+ console.error('Memory hook error:', error);
61
+ console.log(JSON.stringify({}));
62
+ }
63
+ }
64
+
65
+ function readStdin(): Promise<string> {
66
+ return new Promise((resolve) => {
67
+ let data = '';
68
+ process.stdin.setEncoding('utf8');
69
+ process.stdin.on('data', (chunk) => {
70
+ data += chunk;
71
+ });
72
+ process.stdin.on('end', () => {
73
+ resolve(data);
74
+ });
75
+ });
76
+ }
77
+
78
+ main().catch(console.error);
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * User Prompt Submit Hook
4
+ * Called when user submits a prompt - retrieves relevant memories
5
+ */
6
+
7
+ import { getDefaultMemoryService } from '../services/memory-service.js';
8
+ import type { UserPromptSubmitInput, UserPromptSubmitOutput } from '../core/types.js';
9
+
10
+ async function main(): Promise<void> {
11
+ // Read input from stdin
12
+ const inputData = await readStdin();
13
+ const input: UserPromptSubmitInput = JSON.parse(inputData);
14
+
15
+ const memoryService = getDefaultMemoryService();
16
+
17
+ try {
18
+ // Retrieve relevant memories for the prompt
19
+ const retrievalResult = await memoryService.retrieveMemories(input.prompt, {
20
+ topK: 5,
21
+ minScore: 0.7
22
+ });
23
+
24
+ // Store the user prompt for future retrieval
25
+ await memoryService.storeUserPrompt(
26
+ input.session_id,
27
+ input.prompt
28
+ );
29
+
30
+ // Format context for Claude
31
+ const context = memoryService.formatAsContext(retrievalResult);
32
+
33
+ const output: UserPromptSubmitOutput = { context };
34
+ console.log(JSON.stringify(output));
35
+ } catch (error) {
36
+ console.error('Memory hook error:', error);
37
+ console.log(JSON.stringify({ context: '' }));
38
+ }
39
+ }
40
+
41
+ function readStdin(): Promise<string> {
42
+ return new Promise((resolve) => {
43
+ let data = '';
44
+ process.stdin.setEncoding('utf8');
45
+ process.stdin.on('data', (chunk) => {
46
+ data += chunk;
47
+ });
48
+ process.stdin.on('end', () => {
49
+ resolve(data);
50
+ });
51
+ });
52
+ }
53
+
54
+ main().catch(console.error);
@@ -0,0 +1,212 @@
1
+ /**
2
+ * MCP Tool Handlers
3
+ * Implementation of tool calls
4
+ */
5
+
6
+ import { getDefaultMemoryService } from '../services/memory-service.js';
7
+ import { generateCitationId } from '../core/citation-generator.js';
8
+
9
+ interface ToolResult {
10
+ content: Array<{ type: string; text: string }>;
11
+ isError?: boolean;
12
+ }
13
+
14
+ export async function handleToolCall(
15
+ name: string,
16
+ args: Record<string, unknown>
17
+ ): Promise<ToolResult> {
18
+ try {
19
+ const memoryService = getDefaultMemoryService();
20
+ await memoryService.initialize();
21
+
22
+ switch (name) {
23
+ case 'mem-search':
24
+ return await handleMemSearch(args);
25
+
26
+ case 'mem-timeline':
27
+ return await handleMemTimeline(args);
28
+
29
+ case 'mem-details':
30
+ return await handleMemDetails(args);
31
+
32
+ case 'mem-stats':
33
+ return await handleMemStats();
34
+
35
+ default:
36
+ return {
37
+ content: [{ type: 'text', text: `Unknown tool: ${name}` }],
38
+ isError: true
39
+ };
40
+ }
41
+ } catch (error) {
42
+ return {
43
+ content: [{ type: 'text', text: `Error: ${(error as Error).message}` }],
44
+ isError: true
45
+ };
46
+ }
47
+ }
48
+
49
+ async function handleMemSearch(args: Record<string, unknown>): Promise<ToolResult> {
50
+ const query = args.query as string;
51
+ const topK = Math.min((args.topK as number) || 5, 20);
52
+
53
+ const memoryService = getDefaultMemoryService();
54
+ const result = await memoryService.retrieveMemories(query, {
55
+ topK,
56
+ sessionId: args.sessionId as string
57
+ });
58
+
59
+ const lines: string[] = [
60
+ '## Memory Search Results',
61
+ '',
62
+ `Found ${result.memories.length} relevant memories:`,
63
+ ''
64
+ ];
65
+
66
+ for (let i = 0; i < result.memories.length; i++) {
67
+ const m = result.memories[i];
68
+ const citationId = generateCitationId(m.event.id);
69
+ const date = m.event.timestamp.toISOString().split('T')[0];
70
+ const preview = m.event.content.slice(0, 100) + (m.event.content.length > 100 ? '...' : '');
71
+
72
+ lines.push(`### ${i + 1}. [mem:${citationId}] (score: ${m.score.toFixed(2)})`);
73
+ lines.push(`**Type**: ${m.event.eventType} | **Date**: ${date}`);
74
+ lines.push(`> ${preview}`);
75
+ lines.push('');
76
+ }
77
+
78
+ lines.push('---');
79
+ lines.push('*Use `mem-details` with IDs for full content.*');
80
+
81
+ return {
82
+ content: [{ type: 'text', text: lines.join('\n') }]
83
+ };
84
+ }
85
+
86
+ async function handleMemTimeline(args: Record<string, unknown>): Promise<ToolResult> {
87
+ const ids = args.ids as string[];
88
+ const windowSize = (args.windowSize as number) || 3;
89
+
90
+ const memoryService = getDefaultMemoryService();
91
+ const recentEvents = await memoryService.getRecentEvents(10000);
92
+
93
+ const lines: string[] = [
94
+ '## Timeline Context',
95
+ ''
96
+ ];
97
+
98
+ for (const targetId of ids) {
99
+ // Find the target event
100
+ const targetEvent = recentEvents.find(e =>
101
+ e.id === targetId || generateCitationId(e.id) === targetId
102
+ );
103
+
104
+ if (!targetEvent) {
105
+ lines.push(`Event ${targetId} not found.`);
106
+ continue;
107
+ }
108
+
109
+ // Get session events
110
+ const sessionEvents = recentEvents
111
+ .filter(e => e.sessionId === targetEvent.sessionId)
112
+ .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
113
+
114
+ const eventIndex = sessionEvents.findIndex(e => e.id === targetEvent.id);
115
+ const start = Math.max(0, eventIndex - windowSize);
116
+ const end = Math.min(sessionEvents.length, eventIndex + windowSize + 1);
117
+
118
+ lines.push(`### Session: ${targetEvent.sessionId.slice(0, 8)}`);
119
+ lines.push('');
120
+
121
+ for (let i = start; i < end; i++) {
122
+ const e = sessionEvents[i];
123
+ const isTarget = e.id === targetEvent.id;
124
+ const marker = isTarget ? '**→**' : ' ';
125
+ const time = e.timestamp.toLocaleTimeString();
126
+ const preview = e.content.slice(0, 60) + (e.content.length > 60 ? '...' : '');
127
+ const citationId = generateCitationId(e.id);
128
+
129
+ lines.push(`${marker} ${time} [${citationId}] ${e.eventType}: ${preview}`);
130
+ }
131
+
132
+ lines.push('');
133
+ }
134
+
135
+ return {
136
+ content: [{ type: 'text', text: lines.join('\n') }]
137
+ };
138
+ }
139
+
140
+ async function handleMemDetails(args: Record<string, unknown>): Promise<ToolResult> {
141
+ const ids = args.ids as string[];
142
+
143
+ const memoryService = getDefaultMemoryService();
144
+ const recentEvents = await memoryService.getRecentEvents(10000);
145
+
146
+ const lines: string[] = [];
147
+
148
+ for (const targetId of ids) {
149
+ const event = recentEvents.find(e =>
150
+ e.id === targetId || generateCitationId(e.id) === targetId
151
+ );
152
+
153
+ if (!event) {
154
+ lines.push(`## Event ${targetId} not found.`);
155
+ lines.push('');
156
+ continue;
157
+ }
158
+
159
+ const citationId = generateCitationId(event.id);
160
+ const date = event.timestamp.toISOString();
161
+
162
+ lines.push(`## Memory: [mem:${citationId}]`);
163
+ lines.push('');
164
+ lines.push(`**Session**: ${event.sessionId}`);
165
+ lines.push(`**Type**: ${event.eventType}`);
166
+ lines.push(`**Date**: ${date}`);
167
+ lines.push('');
168
+ lines.push('**Content**:');
169
+ lines.push('```');
170
+ lines.push(event.content);
171
+ lines.push('```');
172
+ lines.push('');
173
+ lines.push('---');
174
+ lines.push('');
175
+ }
176
+
177
+ return {
178
+ content: [{ type: 'text', text: lines.join('\n') }]
179
+ };
180
+ }
181
+
182
+ async function handleMemStats(): Promise<ToolResult> {
183
+ const memoryService = getDefaultMemoryService();
184
+ const stats = await memoryService.getStats();
185
+ const recentEvents = await memoryService.getRecentEvents(10000);
186
+
187
+ const uniqueSessions = new Set(recentEvents.map(e => e.sessionId));
188
+
189
+ const lines: string[] = [
190
+ '## Memory Statistics',
191
+ '',
192
+ `- **Total Events**: ${stats.totalEvents}`,
193
+ `- **Total Vectors**: ${stats.vectorCount}`,
194
+ `- **Sessions**: ${uniqueSessions.size}`,
195
+ '',
196
+ '### Events by Type',
197
+ ''
198
+ ];
199
+
200
+ const eventsByType = recentEvents.reduce((acc, e) => {
201
+ acc[e.eventType] = (acc[e.eventType] || 0) + 1;
202
+ return acc;
203
+ }, {} as Record<string, number>);
204
+
205
+ for (const [type, count] of Object.entries(eventsByType)) {
206
+ lines.push(`- ${type}: ${count}`);
207
+ }
208
+
209
+ return {
210
+ content: [{ type: 'text', text: lines.join('\n') }]
211
+ };
212
+ }
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server for Claude Desktop
4
+ * Provides memory search tools via Model Context Protocol
5
+ */
6
+
7
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
8
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
+ import {
10
+ ListToolsRequestSchema,
11
+ CallToolRequestSchema
12
+ } from '@modelcontextprotocol/sdk/types.js';
13
+
14
+ import { tools } from './tools.js';
15
+ import { handleToolCall } from './handlers.js';
16
+
17
+ const server = new Server(
18
+ {
19
+ name: 'code-memory-mcp',
20
+ version: '1.0.0'
21
+ },
22
+ {
23
+ capabilities: {
24
+ tools: {}
25
+ }
26
+ }
27
+ );
28
+
29
+ // Tool listing handler
30
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
31
+ return { tools };
32
+ });
33
+
34
+ // Tool call handler
35
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
36
+ const { name, arguments: args } = request.params;
37
+ return handleToolCall(name, args || {});
38
+ });
39
+
40
+ // Start server
41
+ async function main() {
42
+ const transport = new StdioServerTransport();
43
+ await server.connect(transport);
44
+ console.error('code-memory MCP server started');
45
+ }
46
+
47
+ main().catch(console.error);