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.
- package/.claude-plugin/commands/memory-forget.md +42 -0
- package/.claude-plugin/commands/memory-history.md +34 -0
- package/.claude-plugin/commands/memory-import.md +56 -0
- package/.claude-plugin/commands/memory-list.md +37 -0
- package/.claude-plugin/commands/memory-search.md +36 -0
- package/.claude-plugin/commands/memory-stats.md +34 -0
- package/.claude-plugin/hooks.json +59 -0
- package/.claude-plugin/plugin.json +24 -0
- package/.history/package_20260201112328.json +45 -0
- package/.history/package_20260201113602.json +45 -0
- package/.history/package_20260201113713.json +45 -0
- package/.history/package_20260201114110.json +45 -0
- package/Memo.txt +558 -0
- package/README.md +520 -0
- package/context.md +636 -0
- package/dist/.claude-plugin/commands/memory-forget.md +42 -0
- package/dist/.claude-plugin/commands/memory-history.md +34 -0
- package/dist/.claude-plugin/commands/memory-import.md +56 -0
- package/dist/.claude-plugin/commands/memory-list.md +37 -0
- package/dist/.claude-plugin/commands/memory-search.md +36 -0
- package/dist/.claude-plugin/commands/memory-stats.md +34 -0
- package/dist/.claude-plugin/hooks.json +59 -0
- package/dist/.claude-plugin/plugin.json +24 -0
- package/dist/cli/index.js +3539 -0
- package/dist/cli/index.js.map +7 -0
- package/dist/core/index.js +4408 -0
- package/dist/core/index.js.map +7 -0
- package/dist/hooks/session-end.js +2971 -0
- package/dist/hooks/session-end.js.map +7 -0
- package/dist/hooks/session-start.js +2969 -0
- package/dist/hooks/session-start.js.map +7 -0
- package/dist/hooks/stop.js +3123 -0
- package/dist/hooks/stop.js.map +7 -0
- package/dist/hooks/user-prompt-submit.js +2960 -0
- package/dist/hooks/user-prompt-submit.js.map +7 -0
- package/dist/services/memory-service.js +2931 -0
- package/dist/services/memory-service.js.map +7 -0
- package/package.json +45 -0
- package/plan.md +1642 -0
- package/scripts/build.ts +102 -0
- package/spec.md +624 -0
- package/specs/citations-system/context.md +243 -0
- package/specs/citations-system/plan.md +495 -0
- package/specs/citations-system/spec.md +371 -0
- package/specs/endless-mode/context.md +305 -0
- package/specs/endless-mode/plan.md +620 -0
- package/specs/endless-mode/spec.md +455 -0
- package/specs/entity-edge-model/context.md +401 -0
- package/specs/entity-edge-model/plan.md +459 -0
- package/specs/entity-edge-model/spec.md +391 -0
- package/specs/evidence-aligner-v2/context.md +401 -0
- package/specs/evidence-aligner-v2/plan.md +303 -0
- package/specs/evidence-aligner-v2/spec.md +312 -0
- package/specs/mcp-desktop-integration/context.md +278 -0
- package/specs/mcp-desktop-integration/plan.md +550 -0
- package/specs/mcp-desktop-integration/spec.md +494 -0
- package/specs/post-tool-use-hook/context.md +319 -0
- package/specs/post-tool-use-hook/plan.md +469 -0
- package/specs/post-tool-use-hook/spec.md +364 -0
- package/specs/private-tags/context.md +288 -0
- package/specs/private-tags/plan.md +412 -0
- package/specs/private-tags/spec.md +345 -0
- package/specs/progressive-disclosure/context.md +346 -0
- package/specs/progressive-disclosure/plan.md +663 -0
- package/specs/progressive-disclosure/spec.md +415 -0
- package/specs/task-entity-system/context.md +297 -0
- package/specs/task-entity-system/plan.md +301 -0
- package/specs/task-entity-system/spec.md +314 -0
- package/specs/vector-outbox-v2/context.md +470 -0
- package/specs/vector-outbox-v2/plan.md +562 -0
- package/specs/vector-outbox-v2/spec.md +466 -0
- package/specs/web-viewer-ui/context.md +384 -0
- package/specs/web-viewer-ui/plan.md +797 -0
- package/specs/web-viewer-ui/spec.md +516 -0
- package/src/cli/index.ts +570 -0
- package/src/core/canonical-key.ts +186 -0
- package/src/core/citation-generator.ts +63 -0
- package/src/core/consolidated-store.ts +279 -0
- package/src/core/consolidation-worker.ts +384 -0
- package/src/core/context-formatter.ts +276 -0
- package/src/core/continuity-manager.ts +336 -0
- package/src/core/edge-repo.ts +324 -0
- package/src/core/embedder.ts +124 -0
- package/src/core/entity-repo.ts +342 -0
- package/src/core/event-store.ts +672 -0
- package/src/core/evidence-aligner.ts +635 -0
- package/src/core/graduation.ts +365 -0
- package/src/core/index.ts +32 -0
- package/src/core/matcher.ts +210 -0
- package/src/core/metadata-extractor.ts +203 -0
- package/src/core/privacy/filter.ts +179 -0
- package/src/core/privacy/index.ts +20 -0
- package/src/core/privacy/tag-parser.ts +145 -0
- package/src/core/progressive-retriever.ts +415 -0
- package/src/core/retriever.ts +235 -0
- package/src/core/task/blocker-resolver.ts +325 -0
- package/src/core/task/index.ts +9 -0
- package/src/core/task/task-matcher.ts +238 -0
- package/src/core/task/task-projector.ts +345 -0
- package/src/core/task/task-resolver.ts +414 -0
- package/src/core/types.ts +841 -0
- package/src/core/vector-outbox.ts +295 -0
- package/src/core/vector-store.ts +182 -0
- package/src/core/vector-worker.ts +488 -0
- package/src/core/working-set-store.ts +244 -0
- package/src/hooks/post-tool-use.ts +127 -0
- package/src/hooks/session-end.ts +78 -0
- package/src/hooks/session-start.ts +57 -0
- package/src/hooks/stop.ts +78 -0
- package/src/hooks/user-prompt-submit.ts +54 -0
- package/src/mcp/handlers.ts +212 -0
- package/src/mcp/index.ts +47 -0
- package/src/mcp/tools.ts +78 -0
- package/src/server/api/citations.ts +101 -0
- package/src/server/api/events.ts +101 -0
- package/src/server/api/index.ts +18 -0
- package/src/server/api/search.ts +98 -0
- package/src/server/api/sessions.ts +111 -0
- package/src/server/api/stats.ts +97 -0
- package/src/server/index.ts +91 -0
- package/src/services/memory-service.ts +626 -0
- package/src/services/session-history-importer.ts +367 -0
- package/tests/canonical-key.test.ts +101 -0
- package/tests/evidence-aligner.test.ts +152 -0
- package/tests/matcher.test.ts +112 -0
- package/tsconfig.json +24 -0
- 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
|
+
}
|
package/src/mcp/index.ts
ADDED
|
@@ -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);
|