claude-memory-layer 1.0.1 → 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/.claude/settings.local.json +3 -1
- package/.history/package_20260201133143.json +45 -0
- package/dist/cli/index.js +836 -9
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +782 -3
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/session-end.js +836 -9
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +836 -9
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +836 -9
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +839 -10
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/services/memory-service.js +836 -9
- package/dist/services/memory-service.js.map +4 -4
- package/package.json +1 -2
- package/src/core/index.ts +6 -0
- package/src/core/retriever.ts +118 -2
- package/src/core/shared-event-store.ts +114 -0
- package/src/core/shared-promoter.ts +249 -0
- package/src/core/shared-store.ts +289 -0
- package/src/core/shared-vector-store.ts +203 -0
- package/src/core/types.ts +69 -2
- package/src/hooks/user-prompt-submit.ts +6 -2
- package/src/services/memory-service.ts +145 -6
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharedStore - Cross-project troubleshooting knowledge store
|
|
3
|
+
* Manages promotion from verified entries to shared storage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { randomUUID } from 'crypto';
|
|
7
|
+
import { dbRun, dbAll, toDate, type Database } from './db-wrapper.js';
|
|
8
|
+
import type {
|
|
9
|
+
SharedTroubleshootingEntry,
|
|
10
|
+
SharedTroubleshootingInput
|
|
11
|
+
} from './types.js';
|
|
12
|
+
import { SharedEventStore } from './shared-event-store.js';
|
|
13
|
+
|
|
14
|
+
export class SharedStore {
|
|
15
|
+
constructor(private sharedEventStore: SharedEventStore) {}
|
|
16
|
+
|
|
17
|
+
private get db(): Database {
|
|
18
|
+
return this.sharedEventStore.getDatabase();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Promote a verified troubleshooting entry to shared storage
|
|
23
|
+
*/
|
|
24
|
+
async promoteEntry(
|
|
25
|
+
input: SharedTroubleshootingInput
|
|
26
|
+
): Promise<string> {
|
|
27
|
+
const entryId = randomUUID();
|
|
28
|
+
|
|
29
|
+
await dbRun(
|
|
30
|
+
this.db,
|
|
31
|
+
`INSERT INTO shared_troubleshooting (
|
|
32
|
+
entry_id, source_project_hash, source_entry_id,
|
|
33
|
+
title, symptoms, root_cause, solution, topics,
|
|
34
|
+
technologies, confidence, promoted_at
|
|
35
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
36
|
+
ON CONFLICT (source_project_hash, source_entry_id)
|
|
37
|
+
DO UPDATE SET
|
|
38
|
+
title = excluded.title,
|
|
39
|
+
symptoms = excluded.symptoms,
|
|
40
|
+
root_cause = excluded.root_cause,
|
|
41
|
+
solution = excluded.solution,
|
|
42
|
+
topics = excluded.topics,
|
|
43
|
+
technologies = excluded.technologies,
|
|
44
|
+
confidence = CASE
|
|
45
|
+
WHEN excluded.confidence > shared_troubleshooting.confidence
|
|
46
|
+
THEN excluded.confidence
|
|
47
|
+
ELSE shared_troubleshooting.confidence
|
|
48
|
+
END`,
|
|
49
|
+
[
|
|
50
|
+
entryId,
|
|
51
|
+
input.sourceProjectHash,
|
|
52
|
+
input.sourceEntryId,
|
|
53
|
+
input.title,
|
|
54
|
+
JSON.stringify(input.symptoms),
|
|
55
|
+
input.rootCause,
|
|
56
|
+
input.solution,
|
|
57
|
+
JSON.stringify(input.topics),
|
|
58
|
+
JSON.stringify(input.technologies || []),
|
|
59
|
+
input.confidence
|
|
60
|
+
]
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return entryId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Search troubleshooting entries by text query
|
|
68
|
+
*/
|
|
69
|
+
async search(
|
|
70
|
+
query: string,
|
|
71
|
+
options?: { topK?: number; minConfidence?: number }
|
|
72
|
+
): Promise<SharedTroubleshootingEntry[]> {
|
|
73
|
+
const topK = options?.topK || 5;
|
|
74
|
+
const minConfidence = options?.minConfidence || 0.5;
|
|
75
|
+
const searchPattern = `%${query}%`;
|
|
76
|
+
|
|
77
|
+
const rows = await dbAll<Record<string, unknown>>(
|
|
78
|
+
this.db,
|
|
79
|
+
`SELECT * FROM shared_troubleshooting
|
|
80
|
+
WHERE (title LIKE ? OR root_cause LIKE ? OR solution LIKE ?)
|
|
81
|
+
AND confidence >= ?
|
|
82
|
+
ORDER BY confidence DESC, usage_count DESC
|
|
83
|
+
LIMIT ?`,
|
|
84
|
+
[searchPattern, searchPattern, searchPattern, minConfidence, topK]
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return rows.map(this.rowToEntry);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Search by topics
|
|
92
|
+
*/
|
|
93
|
+
async searchByTopics(
|
|
94
|
+
topics: string[],
|
|
95
|
+
options?: { topK?: number; excludeProjectHash?: string }
|
|
96
|
+
): Promise<SharedTroubleshootingEntry[]> {
|
|
97
|
+
const topK = options?.topK || 5;
|
|
98
|
+
|
|
99
|
+
if (topics.length === 0) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const topicConditions = topics.map(() => `topics LIKE ?`).join(' OR ');
|
|
104
|
+
const topicParams = topics.map(t => `%"${t}"%`);
|
|
105
|
+
|
|
106
|
+
let query = `SELECT * FROM shared_troubleshooting WHERE (${topicConditions})`;
|
|
107
|
+
const params: unknown[] = [...topicParams];
|
|
108
|
+
|
|
109
|
+
if (options?.excludeProjectHash) {
|
|
110
|
+
query += ` AND source_project_hash != ?`;
|
|
111
|
+
params.push(options.excludeProjectHash);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
query += ` ORDER BY confidence DESC, usage_count DESC LIMIT ?`;
|
|
115
|
+
params.push(topK);
|
|
116
|
+
|
|
117
|
+
const rows = await dbAll<Record<string, unknown>>(this.db, query, params);
|
|
118
|
+
return rows.map(this.rowToEntry);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Record usage of a shared entry (for ranking)
|
|
123
|
+
*/
|
|
124
|
+
async recordUsage(entryId: string): Promise<void> {
|
|
125
|
+
await dbRun(
|
|
126
|
+
this.db,
|
|
127
|
+
`UPDATE shared_troubleshooting
|
|
128
|
+
SET usage_count = usage_count + 1,
|
|
129
|
+
last_used_at = CURRENT_TIMESTAMP
|
|
130
|
+
WHERE entry_id = ?`,
|
|
131
|
+
[entryId]
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get entry by ID
|
|
137
|
+
*/
|
|
138
|
+
async get(entryId: string): Promise<SharedTroubleshootingEntry | null> {
|
|
139
|
+
const rows = await dbAll<Record<string, unknown>>(
|
|
140
|
+
this.db,
|
|
141
|
+
`SELECT * FROM shared_troubleshooting WHERE entry_id = ?`,
|
|
142
|
+
[entryId]
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
if (rows.length === 0) return null;
|
|
146
|
+
return this.rowToEntry(rows[0]);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get entry by source (project hash + entry ID)
|
|
151
|
+
*/
|
|
152
|
+
async getBySource(
|
|
153
|
+
projectHash: string,
|
|
154
|
+
sourceEntryId: string
|
|
155
|
+
): Promise<SharedTroubleshootingEntry | null> {
|
|
156
|
+
const rows = await dbAll<Record<string, unknown>>(
|
|
157
|
+
this.db,
|
|
158
|
+
`SELECT * FROM shared_troubleshooting
|
|
159
|
+
WHERE source_project_hash = ? AND source_entry_id = ?`,
|
|
160
|
+
[projectHash, sourceEntryId]
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
if (rows.length === 0) return null;
|
|
164
|
+
return this.rowToEntry(rows[0]);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Check if an entry already exists in shared store
|
|
169
|
+
*/
|
|
170
|
+
async exists(projectHash: string, sourceEntryId: string): Promise<boolean> {
|
|
171
|
+
const result = await dbAll<{ count: number }>(
|
|
172
|
+
this.db,
|
|
173
|
+
`SELECT COUNT(*) as count FROM shared_troubleshooting
|
|
174
|
+
WHERE source_project_hash = ? AND source_entry_id = ?`,
|
|
175
|
+
[projectHash, sourceEntryId]
|
|
176
|
+
);
|
|
177
|
+
return (result[0]?.count || 0) > 0;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get all entries (with limit for safety)
|
|
182
|
+
*/
|
|
183
|
+
async getAll(options?: { limit?: number }): Promise<SharedTroubleshootingEntry[]> {
|
|
184
|
+
const limit = options?.limit || 100;
|
|
185
|
+
const rows = await dbAll<Record<string, unknown>>(
|
|
186
|
+
this.db,
|
|
187
|
+
`SELECT * FROM shared_troubleshooting
|
|
188
|
+
ORDER BY confidence DESC, usage_count DESC
|
|
189
|
+
LIMIT ?`,
|
|
190
|
+
[limit]
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
return rows.map(this.rowToEntry);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get total count
|
|
198
|
+
*/
|
|
199
|
+
async count(): Promise<number> {
|
|
200
|
+
const result = await dbAll<{ count: number }>(
|
|
201
|
+
this.db,
|
|
202
|
+
`SELECT COUNT(*) as count FROM shared_troubleshooting`
|
|
203
|
+
);
|
|
204
|
+
return result[0]?.count || 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get statistics
|
|
209
|
+
*/
|
|
210
|
+
async getStats(): Promise<{
|
|
211
|
+
total: number;
|
|
212
|
+
averageConfidence: number;
|
|
213
|
+
topTopics: Array<{ topic: string; count: number }>;
|
|
214
|
+
totalUsageCount: number;
|
|
215
|
+
}> {
|
|
216
|
+
const countResult = await dbAll<{ count: number }>(
|
|
217
|
+
this.db,
|
|
218
|
+
`SELECT COUNT(*) as count FROM shared_troubleshooting`
|
|
219
|
+
);
|
|
220
|
+
const total = countResult[0]?.count || 0;
|
|
221
|
+
|
|
222
|
+
const avgResult = await dbAll<{ avg: number | null }>(
|
|
223
|
+
this.db,
|
|
224
|
+
`SELECT AVG(confidence) as avg FROM shared_troubleshooting`
|
|
225
|
+
);
|
|
226
|
+
const averageConfidence = avgResult[0]?.avg || 0;
|
|
227
|
+
|
|
228
|
+
const usageResult = await dbAll<{ total: number }>(
|
|
229
|
+
this.db,
|
|
230
|
+
`SELECT SUM(usage_count) as total FROM shared_troubleshooting`
|
|
231
|
+
);
|
|
232
|
+
const totalUsageCount = usageResult[0]?.total || 0;
|
|
233
|
+
|
|
234
|
+
// Get topic counts
|
|
235
|
+
const entries = await this.getAll({ limit: 1000 });
|
|
236
|
+
const topicCounts: Record<string, number> = {};
|
|
237
|
+
for (const entry of entries) {
|
|
238
|
+
for (const topic of entry.topics) {
|
|
239
|
+
topicCounts[topic] = (topicCounts[topic] || 0) + 1;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const topTopics = Object.entries(topicCounts)
|
|
244
|
+
.map(([topic, count]) => ({ topic, count }))
|
|
245
|
+
.sort((a, b) => b.count - a.count)
|
|
246
|
+
.slice(0, 10);
|
|
247
|
+
|
|
248
|
+
return { total, averageConfidence, topTopics, totalUsageCount };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Delete an entry
|
|
253
|
+
*/
|
|
254
|
+
async delete(entryId: string): Promise<boolean> {
|
|
255
|
+
const before = await this.count();
|
|
256
|
+
await dbRun(
|
|
257
|
+
this.db,
|
|
258
|
+
`DELETE FROM shared_troubleshooting WHERE entry_id = ?`,
|
|
259
|
+
[entryId]
|
|
260
|
+
);
|
|
261
|
+
const after = await this.count();
|
|
262
|
+
return before > after;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private rowToEntry(row: Record<string, unknown>): SharedTroubleshootingEntry {
|
|
266
|
+
return {
|
|
267
|
+
entryId: row.entry_id as string,
|
|
268
|
+
sourceProjectHash: row.source_project_hash as string,
|
|
269
|
+
sourceEntryId: row.source_entry_id as string,
|
|
270
|
+
title: row.title as string,
|
|
271
|
+
symptoms: JSON.parse(row.symptoms as string || '[]'),
|
|
272
|
+
rootCause: row.root_cause as string,
|
|
273
|
+
solution: row.solution as string,
|
|
274
|
+
topics: JSON.parse(row.topics as string || '[]'),
|
|
275
|
+
technologies: JSON.parse(row.technologies as string || '[]'),
|
|
276
|
+
confidence: row.confidence as number,
|
|
277
|
+
usageCount: row.usage_count as number || 0,
|
|
278
|
+
lastUsedAt: row.last_used_at ? toDate(row.last_used_at) : undefined,
|
|
279
|
+
promotedAt: toDate(row.promoted_at),
|
|
280
|
+
createdAt: toDate(row.created_at)
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function createSharedStore(
|
|
286
|
+
sharedEventStore: SharedEventStore
|
|
287
|
+
): SharedStore {
|
|
288
|
+
return new SharedStore(sharedEventStore);
|
|
289
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharedVectorStore - Vector store for cross-project semantic search
|
|
3
|
+
* Location: ~/.claude-code/memory/shared/vectors/
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as lancedb from '@lancedb/lancedb';
|
|
7
|
+
import type { SharedEntryType, SharedSearchResult } from './types.js';
|
|
8
|
+
|
|
9
|
+
export interface SharedVectorRecord {
|
|
10
|
+
id: string;
|
|
11
|
+
entryId: string;
|
|
12
|
+
entryType: SharedEntryType;
|
|
13
|
+
content: string;
|
|
14
|
+
vector: number[];
|
|
15
|
+
topics: string[];
|
|
16
|
+
sourceProjectHash?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class SharedVectorStore {
|
|
20
|
+
private db: lancedb.Connection | null = null;
|
|
21
|
+
private table: lancedb.Table | null = null;
|
|
22
|
+
private readonly tableName = 'shared_knowledge';
|
|
23
|
+
|
|
24
|
+
constructor(private dbPath: string) {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Initialize LanceDB connection
|
|
28
|
+
*/
|
|
29
|
+
async initialize(): Promise<void> {
|
|
30
|
+
if (this.db) return;
|
|
31
|
+
|
|
32
|
+
this.db = await lancedb.connect(this.dbPath);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const tables = await this.db.tableNames();
|
|
36
|
+
if (tables.includes(this.tableName)) {
|
|
37
|
+
this.table = await this.db.openTable(this.tableName);
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
this.table = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Add or update a shared vector record
|
|
46
|
+
*/
|
|
47
|
+
async upsert(record: SharedVectorRecord): Promise<void> {
|
|
48
|
+
await this.initialize();
|
|
49
|
+
|
|
50
|
+
if (!this.db) {
|
|
51
|
+
throw new Error('Database not initialized');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const data = {
|
|
55
|
+
id: record.id,
|
|
56
|
+
entryId: record.entryId,
|
|
57
|
+
entryType: record.entryType,
|
|
58
|
+
content: record.content,
|
|
59
|
+
vector: record.vector,
|
|
60
|
+
topics: JSON.stringify(record.topics),
|
|
61
|
+
sourceProjectHash: record.sourceProjectHash || ''
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (!this.table) {
|
|
65
|
+
this.table = await this.db.createTable(this.tableName, [data]);
|
|
66
|
+
} else {
|
|
67
|
+
// Delete existing entry before adding (upsert behavior)
|
|
68
|
+
try {
|
|
69
|
+
await this.table.delete(`entryId = '${record.entryId}'`);
|
|
70
|
+
} catch {
|
|
71
|
+
// Entry might not exist, ignore
|
|
72
|
+
}
|
|
73
|
+
await this.table.add([data]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Add multiple records in batch
|
|
79
|
+
*/
|
|
80
|
+
async upsertBatch(records: SharedVectorRecord[]): Promise<void> {
|
|
81
|
+
if (records.length === 0) return;
|
|
82
|
+
|
|
83
|
+
await this.initialize();
|
|
84
|
+
|
|
85
|
+
if (!this.db) {
|
|
86
|
+
throw new Error('Database not initialized');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const data = records.map(record => ({
|
|
90
|
+
id: record.id,
|
|
91
|
+
entryId: record.entryId,
|
|
92
|
+
entryType: record.entryType,
|
|
93
|
+
content: record.content,
|
|
94
|
+
vector: record.vector,
|
|
95
|
+
topics: JSON.stringify(record.topics),
|
|
96
|
+
sourceProjectHash: record.sourceProjectHash || ''
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
if (!this.table) {
|
|
100
|
+
this.table = await this.db.createTable(this.tableName, data);
|
|
101
|
+
} else {
|
|
102
|
+
await this.table.add(data);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Search for similar vectors
|
|
108
|
+
*/
|
|
109
|
+
async search(
|
|
110
|
+
queryVector: number[],
|
|
111
|
+
options: {
|
|
112
|
+
limit?: number;
|
|
113
|
+
minScore?: number;
|
|
114
|
+
excludeProjectHash?: string;
|
|
115
|
+
entryType?: SharedEntryType;
|
|
116
|
+
} = {}
|
|
117
|
+
): Promise<SharedSearchResult[]> {
|
|
118
|
+
await this.initialize();
|
|
119
|
+
|
|
120
|
+
if (!this.table) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const { limit = 5, minScore = 0.7, excludeProjectHash, entryType } = options;
|
|
125
|
+
|
|
126
|
+
let query = this.table
|
|
127
|
+
.search(queryVector)
|
|
128
|
+
.distanceType('cosine')
|
|
129
|
+
.limit(limit * 2);
|
|
130
|
+
|
|
131
|
+
// Apply filters
|
|
132
|
+
const filters: string[] = [];
|
|
133
|
+
if (excludeProjectHash) {
|
|
134
|
+
filters.push(`sourceProjectHash != '${excludeProjectHash}'`);
|
|
135
|
+
}
|
|
136
|
+
if (entryType) {
|
|
137
|
+
filters.push(`entryType = '${entryType}'`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (filters.length > 0) {
|
|
141
|
+
query = query.where(filters.join(' AND '));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const results = await query.toArray();
|
|
145
|
+
|
|
146
|
+
return results
|
|
147
|
+
.filter(r => {
|
|
148
|
+
const distance = r._distance || 0;
|
|
149
|
+
const score = 1 - (distance / 2);
|
|
150
|
+
return score >= minScore;
|
|
151
|
+
})
|
|
152
|
+
.slice(0, limit)
|
|
153
|
+
.map(r => {
|
|
154
|
+
const distance = r._distance || 0;
|
|
155
|
+
const score = 1 - (distance / 2);
|
|
156
|
+
return {
|
|
157
|
+
id: r.id as string,
|
|
158
|
+
entryId: r.entryId as string,
|
|
159
|
+
content: r.content as string,
|
|
160
|
+
score,
|
|
161
|
+
entryType: r.entryType as SharedEntryType
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Delete vector by entry ID
|
|
168
|
+
*/
|
|
169
|
+
async delete(entryId: string): Promise<void> {
|
|
170
|
+
if (!this.table) return;
|
|
171
|
+
await this.table.delete(`entryId = '${entryId}'`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get total count
|
|
176
|
+
*/
|
|
177
|
+
async count(): Promise<number> {
|
|
178
|
+
if (!this.table) return 0;
|
|
179
|
+
return this.table.countRows();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Check if vector exists for entry
|
|
184
|
+
*/
|
|
185
|
+
async exists(entryId: string): Promise<boolean> {
|
|
186
|
+
if (!this.table) return false;
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const results = await this.table
|
|
190
|
+
.search([])
|
|
191
|
+
.where(`entryId = '${entryId}'`)
|
|
192
|
+
.limit(1)
|
|
193
|
+
.toArray();
|
|
194
|
+
return results.length > 0;
|
|
195
|
+
} catch {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function createSharedVectorStore(dbPath: string): SharedVectorStore {
|
|
202
|
+
return new SharedVectorStore(dbPath);
|
|
203
|
+
}
|
package/src/core/types.ts
CHANGED
|
@@ -194,7 +194,14 @@ export const ConfigSchema = z.object({
|
|
|
194
194
|
sessionSummary: z.boolean().default(true),
|
|
195
195
|
insightExtraction: z.boolean().default(true),
|
|
196
196
|
crossProjectLearning: z.boolean().default(false),
|
|
197
|
-
singleWriterMode: z.boolean().default(true)
|
|
197
|
+
singleWriterMode: z.boolean().default(true),
|
|
198
|
+
sharedStore: z.object({
|
|
199
|
+
enabled: z.boolean().default(true),
|
|
200
|
+
autoPromote: z.boolean().default(true),
|
|
201
|
+
searchShared: z.boolean().default(true),
|
|
202
|
+
minConfidenceForPromotion: z.number().default(0.8),
|
|
203
|
+
sharedStoragePath: z.string().default('~/.claude-code/memory/shared')
|
|
204
|
+
}).default({})
|
|
198
205
|
}).default({}),
|
|
199
206
|
mode: z.enum(['session', 'endless']).default('session'),
|
|
200
207
|
endless: z.object({
|
|
@@ -499,7 +506,8 @@ export const EntryTypeSchema = z.enum([
|
|
|
499
506
|
'task_note',
|
|
500
507
|
'reference',
|
|
501
508
|
'preference',
|
|
502
|
-
'pattern'
|
|
509
|
+
'pattern',
|
|
510
|
+
'troubleshooting'
|
|
503
511
|
]);
|
|
504
512
|
export type EntryType = z.infer<typeof EntryTypeSchema>;
|
|
505
513
|
|
|
@@ -839,3 +847,62 @@ export interface EndlessModeStatus {
|
|
|
839
847
|
consolidatedCount: number;
|
|
840
848
|
lastConsolidation: Date | null;
|
|
841
849
|
}
|
|
850
|
+
|
|
851
|
+
// ============================================================
|
|
852
|
+
// Shared Store Types (Cross-Project Knowledge)
|
|
853
|
+
// ============================================================
|
|
854
|
+
|
|
855
|
+
export const SharedEntryTypeSchema = z.enum([
|
|
856
|
+
'troubleshooting',
|
|
857
|
+
'best_practice',
|
|
858
|
+
'common_error'
|
|
859
|
+
]);
|
|
860
|
+
export type SharedEntryType = z.infer<typeof SharedEntryTypeSchema>;
|
|
861
|
+
|
|
862
|
+
export const SharedTroubleshootingEntrySchema = z.object({
|
|
863
|
+
entryId: z.string(),
|
|
864
|
+
sourceProjectHash: z.string(),
|
|
865
|
+
sourceEntryId: z.string(),
|
|
866
|
+
title: z.string(),
|
|
867
|
+
symptoms: z.array(z.string()),
|
|
868
|
+
rootCause: z.string(),
|
|
869
|
+
solution: z.string(),
|
|
870
|
+
topics: z.array(z.string()),
|
|
871
|
+
technologies: z.array(z.string()).optional(),
|
|
872
|
+
confidence: z.number().min(0).max(1),
|
|
873
|
+
usageCount: z.number().default(0),
|
|
874
|
+
lastUsedAt: z.date().optional(),
|
|
875
|
+
promotedAt: z.date(),
|
|
876
|
+
createdAt: z.date()
|
|
877
|
+
});
|
|
878
|
+
export type SharedTroubleshootingEntry = z.infer<typeof SharedTroubleshootingEntrySchema>;
|
|
879
|
+
|
|
880
|
+
export interface SharedTroubleshootingInput {
|
|
881
|
+
sourceProjectHash: string;
|
|
882
|
+
sourceEntryId: string;
|
|
883
|
+
title: string;
|
|
884
|
+
symptoms: string[];
|
|
885
|
+
rootCause: string;
|
|
886
|
+
solution: string;
|
|
887
|
+
topics: string[];
|
|
888
|
+
technologies?: string[];
|
|
889
|
+
confidence: number;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
export const SharedStoreConfigSchema = z.object({
|
|
893
|
+
enabled: z.boolean().default(true),
|
|
894
|
+
autoPromote: z.boolean().default(true),
|
|
895
|
+
searchShared: z.boolean().default(true),
|
|
896
|
+
minConfidenceForPromotion: z.number().default(0.8),
|
|
897
|
+
sharedStoragePath: z.string().default('~/.claude-code/memory/shared')
|
|
898
|
+
});
|
|
899
|
+
export type SharedStoreConfig = z.infer<typeof SharedStoreConfigSchema>;
|
|
900
|
+
|
|
901
|
+
// Shared search result
|
|
902
|
+
export interface SharedSearchResult {
|
|
903
|
+
id: string;
|
|
904
|
+
entryId: string;
|
|
905
|
+
content: string;
|
|
906
|
+
score: number;
|
|
907
|
+
entryType: SharedEntryType;
|
|
908
|
+
}
|
|
@@ -16,10 +16,14 @@ async function main(): Promise<void> {
|
|
|
16
16
|
const memoryService = getMemoryServiceForSession(input.session_id);
|
|
17
17
|
|
|
18
18
|
try {
|
|
19
|
-
//
|
|
19
|
+
// Check if shared store is enabled
|
|
20
|
+
const includeShared = memoryService.isSharedStoreEnabled();
|
|
21
|
+
|
|
22
|
+
// Retrieve relevant memories for the prompt (including shared if enabled)
|
|
20
23
|
const retrievalResult = await memoryService.retrieveMemories(input.prompt, {
|
|
21
24
|
topK: 5,
|
|
22
|
-
minScore: 0.7
|
|
25
|
+
minScore: 0.7,
|
|
26
|
+
includeShared
|
|
23
27
|
});
|
|
24
28
|
|
|
25
29
|
// Store the user prompt for future retrieval
|