claude-memory-layer 1.0.0 → 1.0.2
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/settings.local.json +15 -0
- package/.history/package_20260201114632.json +46 -0
- package/dist/cli/index.js +360 -154
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +337 -161
- package/dist/core/index.js.map +3 -3
- package/dist/hooks/session-end.js +320 -130
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +331 -138
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +320 -130
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +320 -130
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/services/memory-service.js +349 -128
- package/dist/services/memory-service.js.map +4 -4
- package/package.json +1 -1
- package/src/cli/index.ts +84 -23
- package/src/core/consolidated-store.ts +33 -18
- package/src/core/continuity-manager.ts +12 -7
- package/src/core/db-wrapper.ts +112 -0
- package/src/core/edge-repo.ts +22 -13
- package/src/core/entity-repo.ts +23 -14
- package/src/core/event-store.ts +98 -72
- package/src/core/task/blocker-resolver.ts +17 -9
- package/src/core/task/task-matcher.ts +8 -6
- package/src/core/task/task-projector.ts +29 -16
- package/src/core/task/task-resolver.ts +17 -9
- package/src/core/vector-outbox.ts +29 -16
- package/src/core/vector-store.ts +23 -12
- package/src/core/vector-worker.ts +7 -4
- package/src/core/working-set-store.ts +31 -18
- package/src/hooks/session-end.ts +3 -2
- package/src/hooks/session-start.ts +12 -8
- package/src/hooks/stop.ts +3 -2
- package/src/hooks/user-prompt-submit.ts +3 -2
- package/src/services/memory-service.ts +158 -6
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
* Called when a new Claude Code session starts
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
getMemoryServiceForProject,
|
|
9
|
+
registerSession
|
|
10
|
+
} from '../services/memory-service.js';
|
|
8
11
|
import type { SessionStartInput, SessionStartOutput } from '../core/types.js';
|
|
9
12
|
|
|
10
13
|
async function main(): Promise<void> {
|
|
@@ -12,22 +15,23 @@ async function main(): Promise<void> {
|
|
|
12
15
|
const inputData = await readStdin();
|
|
13
16
|
const input: SessionStartInput = JSON.parse(inputData);
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
// Register session with project path for other hooks to find
|
|
19
|
+
registerSession(input.session_id, input.cwd);
|
|
20
|
+
|
|
21
|
+
// Get project-specific memory service
|
|
22
|
+
const memoryService = getMemoryServiceForProject(input.cwd);
|
|
16
23
|
|
|
17
24
|
try {
|
|
18
25
|
// Start session in memory service
|
|
19
26
|
await memoryService.startSession(input.session_id, input.cwd);
|
|
20
27
|
|
|
21
|
-
// Get recent context for this project
|
|
28
|
+
// Get recent context for this project (now automatically scoped)
|
|
22
29
|
const recentEvents = await memoryService.getRecentEvents(10);
|
|
23
|
-
const projectEvents = recentEvents.filter(e =>
|
|
24
|
-
e.metadata?.projectPath === input.cwd
|
|
25
|
-
);
|
|
26
30
|
|
|
27
31
|
let context = '';
|
|
28
|
-
if (
|
|
32
|
+
if (recentEvents.length > 0) {
|
|
29
33
|
context = `## Previous Session Context\n\nYou have worked on this project before. Here are some relevant memories:\n\n`;
|
|
30
|
-
for (const event of
|
|
34
|
+
for (const event of recentEvents.slice(0, 3)) {
|
|
31
35
|
const date = event.timestamp.toISOString().split('T')[0];
|
|
32
36
|
context += `- **${date}**: ${event.content.slice(0, 150)}...\n`;
|
|
33
37
|
}
|
package/src/hooks/stop.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Called when agent stops - stores the conversation messages
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { getMemoryServiceForSession } from '../services/memory-service.js';
|
|
8
8
|
import { applyPrivacyFilter } from '../core/privacy/index.js';
|
|
9
9
|
import type { StopInput, Config } from '../core/types.js';
|
|
10
10
|
|
|
@@ -25,7 +25,8 @@ async function main(): Promise<void> {
|
|
|
25
25
|
const inputData = await readStdin();
|
|
26
26
|
const input: StopInput = JSON.parse(inputData);
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
// Get project-specific memory service via session lookup
|
|
29
|
+
const memoryService = getMemoryServiceForSession(input.session_id);
|
|
29
30
|
|
|
30
31
|
try {
|
|
31
32
|
// Store agent responses from the conversation
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Called when user submits a prompt - retrieves relevant memories
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { getMemoryServiceForSession } from '../services/memory-service.js';
|
|
8
8
|
import type { UserPromptSubmitInput, UserPromptSubmitOutput } from '../core/types.js';
|
|
9
9
|
|
|
10
10
|
async function main(): Promise<void> {
|
|
@@ -12,7 +12,8 @@ async function main(): Promise<void> {
|
|
|
12
12
|
const inputData = await readStdin();
|
|
13
13
|
const input: UserPromptSubmitInput = JSON.parse(inputData);
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// Get project-specific memory service via session lookup
|
|
16
|
+
const memoryService = getMemoryServiceForSession(input.session_id);
|
|
16
17
|
|
|
17
18
|
try {
|
|
18
19
|
// Retrieve relevant memories for the prompt
|
|
@@ -42,6 +42,119 @@ export interface MemoryServiceConfig {
|
|
|
42
42
|
embeddingModel?: string;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// ============================================================
|
|
46
|
+
// Project Path Utilities
|
|
47
|
+
// ============================================================
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Normalize and resolve a project path, handling symlinks
|
|
51
|
+
*/
|
|
52
|
+
function normalizePath(projectPath: string): string {
|
|
53
|
+
const expanded = projectPath.startsWith('~')
|
|
54
|
+
? path.join(os.homedir(), projectPath.slice(1))
|
|
55
|
+
: projectPath;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
// Resolve symlinks for consistent paths
|
|
59
|
+
return fs.realpathSync(expanded);
|
|
60
|
+
} catch {
|
|
61
|
+
// Path doesn't exist yet, just resolve it
|
|
62
|
+
return path.resolve(expanded);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Generate a stable 8-character hash from a project path
|
|
68
|
+
*/
|
|
69
|
+
export function hashProjectPath(projectPath: string): string {
|
|
70
|
+
const normalizedPath = normalizePath(projectPath);
|
|
71
|
+
return crypto.createHash('sha256')
|
|
72
|
+
.update(normalizedPath)
|
|
73
|
+
.digest('hex')
|
|
74
|
+
.slice(0, 8);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get the storage path for a specific project
|
|
79
|
+
*/
|
|
80
|
+
export function getProjectStoragePath(projectPath: string): string {
|
|
81
|
+
const hash = hashProjectPath(projectPath);
|
|
82
|
+
return path.join(os.homedir(), '.claude-code', 'memory', 'projects', hash);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ============================================================
|
|
86
|
+
// Session Registry
|
|
87
|
+
// ============================================================
|
|
88
|
+
|
|
89
|
+
const REGISTRY_PATH = path.join(os.homedir(), '.claude-code', 'memory', 'session-registry.json');
|
|
90
|
+
|
|
91
|
+
interface SessionRegistryEntry {
|
|
92
|
+
projectPath: string;
|
|
93
|
+
projectHash: string;
|
|
94
|
+
registeredAt: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface SessionRegistry {
|
|
98
|
+
version: number;
|
|
99
|
+
sessions: Record<string, SessionRegistryEntry>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function loadSessionRegistry(): SessionRegistry {
|
|
103
|
+
try {
|
|
104
|
+
if (fs.existsSync(REGISTRY_PATH)) {
|
|
105
|
+
const data = fs.readFileSync(REGISTRY_PATH, 'utf-8');
|
|
106
|
+
return JSON.parse(data);
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('Failed to load session registry:', error);
|
|
110
|
+
}
|
|
111
|
+
return { version: 1, sessions: {} };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function saveSessionRegistry(registry: SessionRegistry): void {
|
|
115
|
+
const dir = path.dirname(REGISTRY_PATH);
|
|
116
|
+
if (!fs.existsSync(dir)) {
|
|
117
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Atomic write using temp file
|
|
121
|
+
const tempPath = REGISTRY_PATH + '.tmp';
|
|
122
|
+
fs.writeFileSync(tempPath, JSON.stringify(registry, null, 2));
|
|
123
|
+
fs.renameSync(tempPath, REGISTRY_PATH);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Register a session with its project path
|
|
128
|
+
*/
|
|
129
|
+
export function registerSession(sessionId: string, projectPath: string): void {
|
|
130
|
+
const registry = loadSessionRegistry();
|
|
131
|
+
|
|
132
|
+
registry.sessions[sessionId] = {
|
|
133
|
+
projectPath: normalizePath(projectPath),
|
|
134
|
+
projectHash: hashProjectPath(projectPath),
|
|
135
|
+
registeredAt: new Date().toISOString()
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Clean up old sessions (keep last 1000)
|
|
139
|
+
const entries = Object.entries(registry.sessions);
|
|
140
|
+
if (entries.length > 1000) {
|
|
141
|
+
const sorted = entries.sort((a, b) =>
|
|
142
|
+
new Date(b[1].registeredAt).getTime() - new Date(a[1].registeredAt).getTime()
|
|
143
|
+
);
|
|
144
|
+
registry.sessions = Object.fromEntries(sorted.slice(0, 1000));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
saveSessionRegistry(registry);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get the project path for a session
|
|
152
|
+
*/
|
|
153
|
+
export function getSessionProject(sessionId: string): SessionRegistryEntry | null {
|
|
154
|
+
const registry = loadSessionRegistry();
|
|
155
|
+
return registry.sessions[sessionId] || null;
|
|
156
|
+
}
|
|
157
|
+
|
|
45
158
|
export class MemoryService {
|
|
46
159
|
private readonly eventStore: EventStore;
|
|
47
160
|
private readonly vectorStore: VectorStore;
|
|
@@ -609,16 +722,55 @@ export class MemoryService {
|
|
|
609
722
|
}
|
|
610
723
|
}
|
|
611
724
|
|
|
612
|
-
//
|
|
613
|
-
|
|
725
|
+
// ============================================================
|
|
726
|
+
// Service Instance Management
|
|
727
|
+
// ============================================================
|
|
728
|
+
|
|
729
|
+
// Instance cache: Map from project hash (or '__global__') to MemoryService
|
|
730
|
+
const serviceCache = new Map<string, MemoryService>();
|
|
731
|
+
const GLOBAL_KEY = '__global__';
|
|
614
732
|
|
|
733
|
+
/**
|
|
734
|
+
* Get the global memory service (backward compatibility)
|
|
735
|
+
* Use this for operations not tied to a specific project
|
|
736
|
+
*/
|
|
615
737
|
export function getDefaultMemoryService(): MemoryService {
|
|
616
|
-
if (!
|
|
617
|
-
|
|
738
|
+
if (!serviceCache.has(GLOBAL_KEY)) {
|
|
739
|
+
serviceCache.set(GLOBAL_KEY, new MemoryService({
|
|
618
740
|
storagePath: '~/.claude-code/memory'
|
|
619
|
-
});
|
|
741
|
+
}));
|
|
620
742
|
}
|
|
621
|
-
return
|
|
743
|
+
return serviceCache.get(GLOBAL_KEY)!;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Get memory service for a specific project path
|
|
748
|
+
* Creates isolated storage at ~/.claude-code/memory/projects/{hash}/
|
|
749
|
+
*/
|
|
750
|
+
export function getMemoryServiceForProject(projectPath: string): MemoryService {
|
|
751
|
+
const hash = hashProjectPath(projectPath);
|
|
752
|
+
|
|
753
|
+
if (!serviceCache.has(hash)) {
|
|
754
|
+
const storagePath = getProjectStoragePath(projectPath);
|
|
755
|
+
serviceCache.set(hash, new MemoryService({ storagePath }));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return serviceCache.get(hash)!;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Get memory service for a session by looking up its project
|
|
763
|
+
* Falls back to global storage if session not found in registry
|
|
764
|
+
*/
|
|
765
|
+
export function getMemoryServiceForSession(sessionId: string): MemoryService {
|
|
766
|
+
const projectInfo = getSessionProject(sessionId);
|
|
767
|
+
|
|
768
|
+
if (projectInfo) {
|
|
769
|
+
return getMemoryServiceForProject(projectInfo.projectPath);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Fallback to global storage for unknown sessions (backward compat)
|
|
773
|
+
return getDefaultMemoryService();
|
|
622
774
|
}
|
|
623
775
|
|
|
624
776
|
export function createMemoryService(config: MemoryServiceConfig): MemoryService {
|