claude-memory-layer 1.0.2 → 1.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-memory-layer",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Claude Code plugin that learns from conversations to provide personalized assistance",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/core/index.ts CHANGED
@@ -30,3 +30,9 @@ export * from './graduation.js';
30
30
 
31
31
  // Task Entity System
32
32
  export * from './task/index.js';
33
+
34
+ // Shared Store (Cross-Project Knowledge)
35
+ export * from './shared-event-store.js';
36
+ export * from './shared-store.js';
37
+ export * from './shared-vector-store.js';
38
+ export * from './shared-promoter.js';
@@ -7,7 +7,9 @@ import { EventStore } from './event-store.js';
7
7
  import { VectorStore, SearchResult } from './vector-store.js';
8
8
  import { Embedder } from './embedder.js';
9
9
  import { Matcher } from './matcher.js';
10
- import type { MemoryEvent, MatchResult, Config } from './types.js';
10
+ import { SharedStore } from './shared-store.js';
11
+ import { SharedVectorStore } from './shared-vector-store.js';
12
+ import type { MemoryEvent, MatchResult, Config, SharedTroubleshootingEntry } from './types.js';
11
13
 
12
14
  export interface RetrievalOptions {
13
15
  topK: number;
@@ -30,6 +32,15 @@ export interface MemoryWithContext {
30
32
  sessionContext?: string;
31
33
  }
32
34
 
35
+ export interface UnifiedRetrievalOptions extends RetrievalOptions {
36
+ includeShared?: boolean;
37
+ projectHash?: string;
38
+ }
39
+
40
+ export interface UnifiedRetrievalResult extends RetrievalResult {
41
+ sharedMemories?: SharedTroubleshootingEntry[];
42
+ }
43
+
33
44
  const DEFAULT_OPTIONS: RetrievalOptions = {
34
45
  topK: 5,
35
46
  minScore: 0.7,
@@ -37,22 +48,40 @@ const DEFAULT_OPTIONS: RetrievalOptions = {
37
48
  includeSessionContext: true
38
49
  };
39
50
 
51
+ export interface SharedStoreOptions {
52
+ sharedStore?: SharedStore;
53
+ sharedVectorStore?: SharedVectorStore;
54
+ }
55
+
40
56
  export class Retriever {
41
57
  private readonly eventStore: EventStore;
42
58
  private readonly vectorStore: VectorStore;
43
59
  private readonly embedder: Embedder;
44
60
  private readonly matcher: Matcher;
61
+ private sharedStore?: SharedStore;
62
+ private sharedVectorStore?: SharedVectorStore;
45
63
 
46
64
  constructor(
47
65
  eventStore: EventStore,
48
66
  vectorStore: VectorStore,
49
67
  embedder: Embedder,
50
- matcher: Matcher
68
+ matcher: Matcher,
69
+ sharedOptions?: SharedStoreOptions
51
70
  ) {
52
71
  this.eventStore = eventStore;
53
72
  this.vectorStore = vectorStore;
54
73
  this.embedder = embedder;
55
74
  this.matcher = matcher;
75
+ this.sharedStore = sharedOptions?.sharedStore;
76
+ this.sharedVectorStore = sharedOptions?.sharedVectorStore;
77
+ }
78
+
79
+ /**
80
+ * Set shared stores after construction
81
+ */
82
+ setSharedStores(sharedStore: SharedStore, sharedVectorStore: SharedVectorStore): void {
83
+ this.sharedStore = sharedStore;
84
+ this.sharedVectorStore = sharedVectorStore;
56
85
  }
57
86
 
58
87
  /**
@@ -94,6 +123,93 @@ export class Retriever {
94
123
  };
95
124
  }
96
125
 
126
+ /**
127
+ * Retrieve with unified search (project + shared)
128
+ */
129
+ async retrieveUnified(
130
+ query: string,
131
+ options: Partial<UnifiedRetrievalOptions> = {}
132
+ ): Promise<UnifiedRetrievalResult> {
133
+ // Get project-local results first
134
+ const projectResult = await this.retrieve(query, options);
135
+
136
+ // If shared search is not requested or stores not available, return project results only
137
+ if (!options.includeShared || !this.sharedStore || !this.sharedVectorStore) {
138
+ return projectResult;
139
+ }
140
+
141
+ try {
142
+ // Generate query embedding (reuse if possible)
143
+ const queryEmbedding = await this.embedder.embed(query);
144
+
145
+ // Vector search in shared store
146
+ const sharedVectorResults = await this.sharedVectorStore.search(
147
+ queryEmbedding.vector,
148
+ {
149
+ limit: options.topK || 5,
150
+ minScore: options.minScore || 0.7,
151
+ excludeProjectHash: options.projectHash
152
+ }
153
+ );
154
+
155
+ // Get full entries from shared store
156
+ const sharedMemories: SharedTroubleshootingEntry[] = [];
157
+ for (const result of sharedVectorResults) {
158
+ const entry = await this.sharedStore.get(result.entryId);
159
+ if (entry) {
160
+ // Exclude entries from current project if specified
161
+ if (!options.projectHash || entry.sourceProjectHash !== options.projectHash) {
162
+ sharedMemories.push(entry);
163
+ // Record usage for ranking
164
+ await this.sharedStore.recordUsage(entry.entryId);
165
+ }
166
+ }
167
+ }
168
+
169
+ // Build unified context
170
+ const unifiedContext = this.buildUnifiedContext(projectResult, sharedMemories);
171
+
172
+ return {
173
+ ...projectResult,
174
+ context: unifiedContext,
175
+ totalTokens: this.estimateTokens(unifiedContext),
176
+ sharedMemories
177
+ };
178
+ } catch (error) {
179
+ // If shared search fails, return project results only
180
+ console.error('Shared search failed:', error);
181
+ return projectResult;
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Build unified context combining project and shared memories
187
+ */
188
+ private buildUnifiedContext(
189
+ projectResult: RetrievalResult,
190
+ sharedMemories: SharedTroubleshootingEntry[]
191
+ ): string {
192
+ let context = projectResult.context;
193
+
194
+ if (sharedMemories.length > 0) {
195
+ context += '\n\n## Cross-Project Knowledge\n\n';
196
+ for (const memory of sharedMemories.slice(0, 3)) {
197
+ context += `### ${memory.title}\n`;
198
+ if (memory.symptoms.length > 0) {
199
+ context += `**Symptoms:** ${memory.symptoms.join(', ')}\n`;
200
+ }
201
+ context += `**Root Cause:** ${memory.rootCause}\n`;
202
+ context += `**Solution:** ${memory.solution}\n`;
203
+ if (memory.technologies && memory.technologies.length > 0) {
204
+ context += `**Technologies:** ${memory.technologies.join(', ')}\n`;
205
+ }
206
+ context += `_Confidence: ${(memory.confidence * 100).toFixed(0)}%_\n\n`;
207
+ }
208
+ }
209
+
210
+ return context;
211
+ }
212
+
97
213
  /**
98
214
  * Retrieve memories from a specific session
99
215
  */
@@ -0,0 +1,114 @@
1
+ /**
2
+ * SharedEventStore - Global database for cross-project knowledge
3
+ * Location: ~/.claude-code/memory/shared/
4
+ */
5
+
6
+ import {
7
+ createDatabase,
8
+ dbRun,
9
+ dbClose,
10
+ type Database
11
+ } from './db-wrapper.js';
12
+
13
+ export class SharedEventStore {
14
+ private db: Database;
15
+ private initialized = false;
16
+
17
+ constructor(private dbPath: string) {
18
+ this.db = createDatabase(dbPath);
19
+ }
20
+
21
+ async initialize(): Promise<void> {
22
+ if (this.initialized) return;
23
+
24
+ // Shared troubleshooting entries table
25
+ await dbRun(this.db, `
26
+ CREATE TABLE IF NOT EXISTS shared_troubleshooting (
27
+ entry_id VARCHAR PRIMARY KEY,
28
+ source_project_hash VARCHAR NOT NULL,
29
+ source_entry_id VARCHAR NOT NULL,
30
+ title VARCHAR NOT NULL,
31
+ symptoms JSON NOT NULL,
32
+ root_cause TEXT NOT NULL,
33
+ solution TEXT NOT NULL,
34
+ topics JSON NOT NULL,
35
+ technologies JSON,
36
+ confidence REAL NOT NULL DEFAULT 0.8,
37
+ usage_count INTEGER DEFAULT 0,
38
+ last_used_at TIMESTAMP,
39
+ promoted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
40
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
41
+ UNIQUE(source_project_hash, source_entry_id)
42
+ )
43
+ `);
44
+
45
+ // Future extensibility: best practices table
46
+ await dbRun(this.db, `
47
+ CREATE TABLE IF NOT EXISTS shared_best_practices (
48
+ entry_id VARCHAR PRIMARY KEY,
49
+ source_project_hash VARCHAR NOT NULL,
50
+ source_entry_id VARCHAR NOT NULL,
51
+ title VARCHAR NOT NULL,
52
+ content_json JSON NOT NULL,
53
+ topics JSON NOT NULL,
54
+ confidence REAL NOT NULL DEFAULT 0.8,
55
+ usage_count INTEGER DEFAULT 0,
56
+ promoted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
57
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
58
+ UNIQUE(source_project_hash, source_entry_id)
59
+ )
60
+ `);
61
+
62
+ // Future extensibility: common errors table
63
+ await dbRun(this.db, `
64
+ CREATE TABLE IF NOT EXISTS shared_common_errors (
65
+ entry_id VARCHAR PRIMARY KEY,
66
+ source_project_hash VARCHAR NOT NULL,
67
+ source_entry_id VARCHAR NOT NULL,
68
+ title VARCHAR NOT NULL,
69
+ error_pattern TEXT NOT NULL,
70
+ solution TEXT NOT NULL,
71
+ topics JSON NOT NULL,
72
+ technologies JSON,
73
+ confidence REAL NOT NULL DEFAULT 0.8,
74
+ usage_count INTEGER DEFAULT 0,
75
+ promoted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
76
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
77
+ UNIQUE(source_project_hash, source_entry_id)
78
+ )
79
+ `);
80
+
81
+ // Indexes for troubleshooting
82
+ await dbRun(this.db, `
83
+ CREATE INDEX IF NOT EXISTS idx_shared_ts_confidence
84
+ ON shared_troubleshooting(confidence DESC)
85
+ `);
86
+ await dbRun(this.db, `
87
+ CREATE INDEX IF NOT EXISTS idx_shared_ts_usage
88
+ ON shared_troubleshooting(usage_count DESC)
89
+ `);
90
+ await dbRun(this.db, `
91
+ CREATE INDEX IF NOT EXISTS idx_shared_ts_source
92
+ ON shared_troubleshooting(source_project_hash)
93
+ `);
94
+
95
+ this.initialized = true;
96
+ }
97
+
98
+ getDatabase(): Database {
99
+ return this.db;
100
+ }
101
+
102
+ isInitialized(): boolean {
103
+ return this.initialized;
104
+ }
105
+
106
+ async close(): Promise<void> {
107
+ await dbClose(this.db);
108
+ this.initialized = false;
109
+ }
110
+ }
111
+
112
+ export function createSharedEventStore(dbPath: string): SharedEventStore {
113
+ return new SharedEventStore(dbPath);
114
+ }
@@ -0,0 +1,249 @@
1
+ /**
2
+ * SharedPromoter - Handles auto-promotion of verified troubleshooting entries
3
+ * Promotes entries from project-local storage to cross-project shared storage
4
+ */
5
+
6
+ import { randomUUID } from 'crypto';
7
+ import { SharedStore } from './shared-store.js';
8
+ import { SharedVectorStore } from './shared-vector-store.js';
9
+ import { Embedder } from './embedder.js';
10
+ import type { Entry, SharedTroubleshootingInput, SharedStoreConfig } from './types.js';
11
+
12
+ export interface TroubleshootingContent {
13
+ symptoms?: string[];
14
+ rootCause?: string;
15
+ solution?: string;
16
+ technologies?: string[];
17
+ }
18
+
19
+ export interface PromotionResult {
20
+ success: boolean;
21
+ entryId?: string;
22
+ error?: string;
23
+ skipped?: boolean;
24
+ skipReason?: string;
25
+ }
26
+
27
+ export class SharedPromoter {
28
+ constructor(
29
+ private sharedStore: SharedStore,
30
+ private sharedVectorStore: SharedVectorStore,
31
+ private embedder: Embedder,
32
+ private config?: SharedStoreConfig
33
+ ) {}
34
+
35
+ /**
36
+ * Check if an entry is eligible for promotion
37
+ */
38
+ isEligibleForPromotion(entry: Entry): boolean {
39
+ // Must be troubleshooting type
40
+ if (entry.entryType !== 'troubleshooting') {
41
+ return false;
42
+ }
43
+
44
+ // Must be at least 'verified' stage
45
+ if (entry.stage !== 'verified' && entry.stage !== 'certified') {
46
+ return false;
47
+ }
48
+
49
+ // Must be active status
50
+ if (entry.status !== 'active') {
51
+ return false;
52
+ }
53
+
54
+ return true;
55
+ }
56
+
57
+ /**
58
+ * Promote a verified troubleshooting entry to shared storage
59
+ */
60
+ async promoteEntry(
61
+ entry: Entry,
62
+ projectHash: string
63
+ ): Promise<PromotionResult> {
64
+ // Validate eligibility
65
+ if (!this.isEligibleForPromotion(entry)) {
66
+ return {
67
+ success: false,
68
+ skipped: true,
69
+ skipReason: `Entry not eligible: type=${entry.entryType}, stage=${entry.stage}, status=${entry.status}`
70
+ };
71
+ }
72
+
73
+ // Check if already promoted
74
+ const exists = await this.sharedStore.exists(projectHash, entry.entryId);
75
+ if (exists) {
76
+ return {
77
+ success: true,
78
+ skipped: true,
79
+ skipReason: 'Entry already exists in shared store'
80
+ };
81
+ }
82
+
83
+ try {
84
+ const content = entry.contentJson as TroubleshootingContent;
85
+ const confidence = this.calculateConfidence(entry);
86
+
87
+ // Check minimum confidence threshold
88
+ const minConfidence = this.config?.minConfidenceForPromotion ?? 0.8;
89
+ if (confidence < minConfidence) {
90
+ return {
91
+ success: false,
92
+ skipped: true,
93
+ skipReason: `Confidence ${confidence} below threshold ${minConfidence}`
94
+ };
95
+ }
96
+
97
+ const input: SharedTroubleshootingInput = {
98
+ sourceProjectHash: projectHash,
99
+ sourceEntryId: entry.entryId,
100
+ title: entry.title,
101
+ symptoms: content.symptoms || [],
102
+ rootCause: content.rootCause || '',
103
+ solution: content.solution || '',
104
+ topics: this.extractTopics(entry),
105
+ technologies: content.technologies || [],
106
+ confidence
107
+ };
108
+
109
+ // Promote to shared store
110
+ const entryId = await this.sharedStore.promoteEntry(input);
111
+
112
+ // Create embedding for vector search
113
+ const embeddingContent = this.createEmbeddingContent(input);
114
+ const embedding = await this.embedder.embed(embeddingContent);
115
+
116
+ await this.sharedVectorStore.upsert({
117
+ id: randomUUID(),
118
+ entryId,
119
+ entryType: 'troubleshooting',
120
+ content: embeddingContent,
121
+ vector: embedding.vector,
122
+ topics: input.topics,
123
+ sourceProjectHash: projectHash
124
+ });
125
+
126
+ return {
127
+ success: true,
128
+ entryId
129
+ };
130
+ } catch (error) {
131
+ return {
132
+ success: false,
133
+ error: error instanceof Error ? error.message : String(error)
134
+ };
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Batch promote multiple entries
140
+ */
141
+ async promoteEntries(
142
+ entries: Entry[],
143
+ projectHash: string
144
+ ): Promise<Map<string, PromotionResult>> {
145
+ const results = new Map<string, PromotionResult>();
146
+
147
+ for (const entry of entries) {
148
+ const result = await this.promoteEntry(entry, projectHash);
149
+ results.set(entry.entryId, result);
150
+ }
151
+
152
+ return results;
153
+ }
154
+
155
+ /**
156
+ * Extract topics from entry
157
+ */
158
+ private extractTopics(entry: Entry): string[] {
159
+ const topics: string[] = [];
160
+
161
+ // Extract from title (meaningful words)
162
+ const titleWords = entry.title
163
+ .toLowerCase()
164
+ .split(/[\s\-_]+/)
165
+ .filter(w => w.length > 3 && !this.isStopWord(w));
166
+ topics.push(...titleWords);
167
+
168
+ // Extract from content if available
169
+ const content = entry.contentJson as Record<string, unknown>;
170
+ if (content.topics && Array.isArray(content.topics)) {
171
+ topics.push(...content.topics.map(t => String(t).toLowerCase()));
172
+ }
173
+
174
+ // Extract technologies as topics
175
+ if (content.technologies && Array.isArray(content.technologies)) {
176
+ topics.push(...content.technologies.map(t => String(t).toLowerCase()));
177
+ }
178
+
179
+ // Dedupe and return
180
+ return [...new Set(topics)];
181
+ }
182
+
183
+ /**
184
+ * Check if word is a stop word
185
+ */
186
+ private isStopWord(word: string): boolean {
187
+ const stopWords = new Set([
188
+ 'the', 'and', 'for', 'with', 'this', 'that', 'from', 'have', 'been',
189
+ 'were', 'are', 'was', 'had', 'has', 'will', 'would', 'could', 'should',
190
+ 'when', 'where', 'what', 'which', 'while', 'error', 'problem', 'issue'
191
+ ]);
192
+ return stopWords.has(word);
193
+ }
194
+
195
+ /**
196
+ * Calculate confidence score for entry
197
+ */
198
+ private calculateConfidence(entry: Entry): number {
199
+ let confidence = 0.8; // Base confidence for verified entries
200
+
201
+ // Boost if certified
202
+ if (entry.stage === 'certified') {
203
+ confidence = 0.95;
204
+ }
205
+
206
+ // Could add more factors:
207
+ // - Number of evidence items
208
+ // - Age of entry (older verified entries may be more reliable)
209
+ // - Usage count if tracked
210
+
211
+ return Math.min(confidence, 1.0);
212
+ }
213
+
214
+ /**
215
+ * Create embedding content from input
216
+ */
217
+ private createEmbeddingContent(input: SharedTroubleshootingInput): string {
218
+ const parts: string[] = [];
219
+
220
+ parts.push(`Problem: ${input.title}`);
221
+
222
+ if (input.symptoms.length > 0) {
223
+ parts.push(`Symptoms: ${input.symptoms.join(', ')}`);
224
+ }
225
+
226
+ if (input.rootCause) {
227
+ parts.push(`Root Cause: ${input.rootCause}`);
228
+ }
229
+
230
+ if (input.solution) {
231
+ parts.push(`Solution: ${input.solution}`);
232
+ }
233
+
234
+ if (input.technologies && input.technologies.length > 0) {
235
+ parts.push(`Technologies: ${input.technologies.join(', ')}`);
236
+ }
237
+
238
+ return parts.join('\n');
239
+ }
240
+ }
241
+
242
+ export function createSharedPromoter(
243
+ sharedStore: SharedStore,
244
+ sharedVectorStore: SharedVectorStore,
245
+ embedder: Embedder,
246
+ config?: SharedStoreConfig
247
+ ): SharedPromoter {
248
+ return new SharedPromoter(sharedStore, sharedVectorStore, embedder, config);
249
+ }