claude-memory-layer 1.0.0 → 1.0.1

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 (37) hide show
  1. package/.claude/settings.local.json +14 -0
  2. package/.history/package_20260201114632.json +46 -0
  3. package/dist/cli/index.js +360 -154
  4. package/dist/cli/index.js.map +4 -4
  5. package/dist/core/index.js +337 -161
  6. package/dist/core/index.js.map +3 -3
  7. package/dist/hooks/session-end.js +320 -130
  8. package/dist/hooks/session-end.js.map +4 -4
  9. package/dist/hooks/session-start.js +331 -138
  10. package/dist/hooks/session-start.js.map +4 -4
  11. package/dist/hooks/stop.js +320 -130
  12. package/dist/hooks/stop.js.map +4 -4
  13. package/dist/hooks/user-prompt-submit.js +320 -130
  14. package/dist/hooks/user-prompt-submit.js.map +4 -4
  15. package/dist/services/memory-service.js +349 -128
  16. package/dist/services/memory-service.js.map +4 -4
  17. package/package.json +2 -1
  18. package/src/cli/index.ts +84 -23
  19. package/src/core/consolidated-store.ts +33 -18
  20. package/src/core/continuity-manager.ts +12 -7
  21. package/src/core/db-wrapper.ts +112 -0
  22. package/src/core/edge-repo.ts +22 -13
  23. package/src/core/entity-repo.ts +23 -14
  24. package/src/core/event-store.ts +98 -72
  25. package/src/core/task/blocker-resolver.ts +17 -9
  26. package/src/core/task/task-matcher.ts +8 -6
  27. package/src/core/task/task-projector.ts +29 -16
  28. package/src/core/task/task-resolver.ts +17 -9
  29. package/src/core/vector-outbox.ts +29 -16
  30. package/src/core/vector-store.ts +23 -12
  31. package/src/core/vector-worker.ts +7 -4
  32. package/src/core/working-set-store.ts +31 -18
  33. package/src/hooks/session-end.ts +3 -2
  34. package/src/hooks/session-start.ts +12 -8
  35. package/src/hooks/stop.ts +3 -2
  36. package/src/hooks/user-prompt-submit.ts +3 -2
  37. 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 { getDefaultMemoryService } from '../services/memory-service.js';
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
- const memoryService = getDefaultMemoryService();
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 (projectEvents.length > 0) {
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 projectEvents.slice(0, 3)) {
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 { getDefaultMemoryService } from '../services/memory-service.js';
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
- const memoryService = getDefaultMemoryService();
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 { getDefaultMemoryService } from '../services/memory-service.js';
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
- const memoryService = getDefaultMemoryService();
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
- // Default instance
613
- let defaultService: MemoryService | null = null;
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 (!defaultService) {
617
- defaultService = new MemoryService({
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 defaultService;
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 {