claude-eidetic 0.1.4 → 0.1.5
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/dist/format.js +10 -3
- package/dist/hooks/post-tool-extract.d.ts +12 -0
- package/dist/hooks/post-tool-extract.js +210 -0
- package/dist/memory/store.d.ts +4 -3
- package/dist/memory/store.js +60 -8
- package/dist/memory/types.d.ts +5 -0
- package/dist/precompact/memory-inject.js +1 -1
- package/dist/tool-schemas.d.ts +14 -2
- package/dist/tool-schemas.js +14 -2
- package/dist/tools.js +6 -3
- package/package.json +1 -1
package/dist/format.js
CHANGED
|
@@ -156,7 +156,8 @@ export function formatMemoryActions(actions) {
|
|
|
156
156
|
for (const action of actions) {
|
|
157
157
|
const icon = action.event === 'ADD' ? '+' : '~';
|
|
158
158
|
lines.push(` ${icon} [${action.event}] ${action.memory}`);
|
|
159
|
-
|
|
159
|
+
const projectTag = action.project && action.project !== 'global' ? ` | Project: ${action.project}` : '';
|
|
160
|
+
lines.push(` Category: ${action.category ?? 'unknown'} | ID: ${action.id}${projectTag}`);
|
|
160
161
|
if (action.previous) {
|
|
161
162
|
lines.push(` Previous: ${action.previous}`);
|
|
162
163
|
}
|
|
@@ -171,9 +172,13 @@ export function formatMemorySearchResults(items, query) {
|
|
|
171
172
|
for (let i = 0; i < items.length; i++) {
|
|
172
173
|
const m = items[i];
|
|
173
174
|
lines.push(`${i + 1}. ${m.memory}`);
|
|
174
|
-
|
|
175
|
+
const projectTag = m.project && m.project !== 'global' ? ` | Project: ${m.project}` : '';
|
|
176
|
+
lines.push(` Category: ${m.category} | ID: ${m.id}${projectTag}`);
|
|
175
177
|
if (m.source)
|
|
176
178
|
lines.push(` Source: ${m.source}`);
|
|
179
|
+
if (m.access_count > 0) {
|
|
180
|
+
lines.push(` Accessed: ${m.access_count}x | Last: ${m.last_accessed.slice(0, 10)}`);
|
|
181
|
+
}
|
|
177
182
|
if (m.created_at || m.updated_at) {
|
|
178
183
|
lines.push(` Created: ${m.created_at || 'unknown'} | Updated: ${m.updated_at || 'unknown'}`);
|
|
179
184
|
}
|
|
@@ -200,7 +205,9 @@ export function formatMemoryList(items) {
|
|
|
200
205
|
lines.push(`### ${category} (${memories.length})`);
|
|
201
206
|
for (const m of memories) {
|
|
202
207
|
const updatedDate = m.updated_at ? ` (updated: ${m.updated_at.slice(0, 10)})` : '';
|
|
203
|
-
|
|
208
|
+
const projectTag = m.project && m.project !== 'global' ? ` [${m.project}]` : '';
|
|
209
|
+
const accessTag = m.access_count > 0 ? ` (accessed: ${m.access_count}x)` : '';
|
|
210
|
+
lines.push(` - ${m.memory} [${m.id.slice(0, 8)}...]${updatedDate}${projectTag}${accessTag}`);
|
|
204
211
|
}
|
|
205
212
|
lines.push('');
|
|
206
213
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PostToolUse automatic memory extraction.
|
|
4
|
+
* Replaces the nudge-based memory-nudge.sh approach.
|
|
5
|
+
*
|
|
6
|
+
* Reads PostToolUse stdin JSON (includes tool_response), pattern-matches on outcomes,
|
|
7
|
+
* and stores facts directly via MemoryStore — no Claude involvement required.
|
|
8
|
+
*
|
|
9
|
+
* Matches: WebFetch | Bash
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=post-tool-extract.d.ts.map
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PostToolUse automatic memory extraction.
|
|
4
|
+
* Replaces the nudge-based memory-nudge.sh approach.
|
|
5
|
+
*
|
|
6
|
+
* Reads PostToolUse stdin JSON (includes tool_response), pattern-matches on outcomes,
|
|
7
|
+
* and stores facts directly via MemoryStore — no Claude involvement required.
|
|
8
|
+
*
|
|
9
|
+
* Matches: WebFetch | Bash
|
|
10
|
+
*/
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
const MAX_RESPONSE_SIZE = 10_000; // Skip extraction for large responses (data dumps)
|
|
14
|
+
async function main() {
|
|
15
|
+
let input;
|
|
16
|
+
try {
|
|
17
|
+
const raw = await readStdin();
|
|
18
|
+
input = JSON.parse(raw);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Can't parse stdin — exit silently
|
|
22
|
+
writeOutput();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const toolName = input.tool_name ?? '';
|
|
26
|
+
if (toolName !== 'WebFetch' && toolName !== 'Bash') {
|
|
27
|
+
writeOutput();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const responseStr = stringifyResponse(input.tool_response);
|
|
31
|
+
// Skip if response is too large (likely a successful data dump, not an error)
|
|
32
|
+
if (responseStr.length > MAX_RESPONSE_SIZE) {
|
|
33
|
+
writeOutput();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const facts = extractFacts(toolName, input.tool_input ?? {}, responseStr);
|
|
37
|
+
if (facts.length === 0) {
|
|
38
|
+
writeOutput();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const cwd = input.cwd ?? process.cwd();
|
|
43
|
+
const project = detectProject(cwd);
|
|
44
|
+
const [{ loadConfig }, { createEmbedding }, { MemoryHistory }, { MemoryStore }] = await Promise.all([
|
|
45
|
+
import('../config.js'),
|
|
46
|
+
import('../embedding/factory.js'),
|
|
47
|
+
import('../memory/history.js'),
|
|
48
|
+
import('../memory/store.js'),
|
|
49
|
+
]);
|
|
50
|
+
const config = loadConfig();
|
|
51
|
+
const embedding = createEmbedding(config);
|
|
52
|
+
await embedding.initialize();
|
|
53
|
+
let vectordb;
|
|
54
|
+
if (config.vectordbProvider === 'milvus') {
|
|
55
|
+
const { MilvusVectorDB } = await import('../vectordb/milvus.js');
|
|
56
|
+
vectordb = new MilvusVectorDB();
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const { QdrantVectorDB } = await import('../vectordb/qdrant.js');
|
|
60
|
+
vectordb = new QdrantVectorDB(config.qdrantUrl, config.qdrantApiKey);
|
|
61
|
+
}
|
|
62
|
+
// Quick-exit if memory collection doesn't exist yet
|
|
63
|
+
const exists = await vectordb.hasCollection('eidetic_memory');
|
|
64
|
+
if (!exists) {
|
|
65
|
+
writeOutput();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const { getMemoryDbPath } = await import('../paths.js');
|
|
69
|
+
const history = new MemoryHistory(getMemoryDbPath());
|
|
70
|
+
const store = new MemoryStore(embedding, vectordb, history);
|
|
71
|
+
await store.addMemory(facts, 'post-tool-extract', project);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
process.stderr.write(`post-tool-extract failed: ${String(err)}\n`);
|
|
75
|
+
}
|
|
76
|
+
writeOutput();
|
|
77
|
+
}
|
|
78
|
+
function extractFacts(toolName, toolInput, responseStr) {
|
|
79
|
+
const facts = [];
|
|
80
|
+
if (toolName === 'WebFetch') {
|
|
81
|
+
const url = String(toolInput.url ?? '');
|
|
82
|
+
facts.push(...extractWebFetchFacts(url, responseStr));
|
|
83
|
+
}
|
|
84
|
+
else if (toolName === 'Bash') {
|
|
85
|
+
const command = String(toolInput.command ?? '');
|
|
86
|
+
facts.push(...extractBashFacts(command, responseStr));
|
|
87
|
+
}
|
|
88
|
+
return facts;
|
|
89
|
+
}
|
|
90
|
+
function extractWebFetchFacts(url, responseStr) {
|
|
91
|
+
const facts = [];
|
|
92
|
+
const lower = responseStr.toLowerCase();
|
|
93
|
+
// Detect 404 / not found
|
|
94
|
+
if (/\b404\b/.test(responseStr) ||
|
|
95
|
+
lower.includes('page not found') ||
|
|
96
|
+
lower.includes('not found')) {
|
|
97
|
+
if (url) {
|
|
98
|
+
facts.push({
|
|
99
|
+
fact: `URL ${url} returned 404 / not found`,
|
|
100
|
+
category: 'debugging',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return facts;
|
|
104
|
+
}
|
|
105
|
+
// Detect redirect notice
|
|
106
|
+
const redirectMatch = /redirected?\s+to\s+(https?:\/\/\S+)/i.exec(responseStr);
|
|
107
|
+
if (redirectMatch) {
|
|
108
|
+
facts.push({
|
|
109
|
+
fact: `URL ${url} redirects to ${redirectMatch[1]}`,
|
|
110
|
+
category: 'tools',
|
|
111
|
+
});
|
|
112
|
+
return facts;
|
|
113
|
+
}
|
|
114
|
+
// Detect other errors
|
|
115
|
+
if (/(?:error|fail|403|ENOENT|EACCES)/i.test(responseStr)) {
|
|
116
|
+
const snippet = responseStr.slice(0, 150).replace(/\n/g, ' ').trim();
|
|
117
|
+
facts.push({
|
|
118
|
+
fact: `Fetching ${url} failed: ${snippet}`,
|
|
119
|
+
category: 'debugging',
|
|
120
|
+
});
|
|
121
|
+
return facts;
|
|
122
|
+
}
|
|
123
|
+
// Successful fetch — extract URL + key finding (first 200 chars)
|
|
124
|
+
if (url) {
|
|
125
|
+
const snippet = responseStr.slice(0, 200).replace(/\n/g, ' ').trim();
|
|
126
|
+
if (snippet.length > 20) {
|
|
127
|
+
facts.push({
|
|
128
|
+
fact: `Docs at ${url}: ${snippet}`,
|
|
129
|
+
category: 'tools',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return facts;
|
|
134
|
+
}
|
|
135
|
+
function extractBashFacts(command, responseStr) {
|
|
136
|
+
const facts = [];
|
|
137
|
+
const lower = responseStr.toLowerCase();
|
|
138
|
+
const isError = /(?:error|fail|not.found|command not found|ENOENT|EACCES|no such file)/i.test(responseStr) ||
|
|
139
|
+
lower.includes('exit code') ||
|
|
140
|
+
lower.includes('permission denied');
|
|
141
|
+
if (isError) {
|
|
142
|
+
const snippet = responseStr.slice(0, 200).replace(/\n/g, ' ').trim();
|
|
143
|
+
const shortCmd = command.slice(0, 100);
|
|
144
|
+
facts.push({
|
|
145
|
+
fact: `Command '${shortCmd}' failed: ${snippet}`,
|
|
146
|
+
category: 'debugging',
|
|
147
|
+
});
|
|
148
|
+
return facts;
|
|
149
|
+
}
|
|
150
|
+
// Detect successful installs
|
|
151
|
+
const installMatch = /(?:npm|yarn|pnpm|pip|pip3|brew|apt|apt-get|cargo)\s+(?:install|add|i)\s+(.+)/.exec(command);
|
|
152
|
+
if (installMatch) {
|
|
153
|
+
const pkg = installMatch[1].trim().slice(0, 80);
|
|
154
|
+
const manager = command.split(' ')[0];
|
|
155
|
+
facts.push({
|
|
156
|
+
fact: `Installed ${pkg} via ${manager}`,
|
|
157
|
+
category: 'tools',
|
|
158
|
+
});
|
|
159
|
+
return facts;
|
|
160
|
+
}
|
|
161
|
+
// Detect config commands (git config, npm config, etc.)
|
|
162
|
+
if (/\bconfig\b/.test(command) && !isError) {
|
|
163
|
+
const shortCmd = command.slice(0, 120).replace(/\n/g, ' ').trim();
|
|
164
|
+
facts.push({
|
|
165
|
+
fact: `Configured: ${shortCmd}`,
|
|
166
|
+
category: 'workflow',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return facts;
|
|
170
|
+
}
|
|
171
|
+
function detectProject(cwd) {
|
|
172
|
+
try {
|
|
173
|
+
const result = execSync('git rev-parse --show-toplevel', {
|
|
174
|
+
cwd,
|
|
175
|
+
encoding: 'utf-8',
|
|
176
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
177
|
+
});
|
|
178
|
+
return path.basename(result.trim());
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return 'global';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function stringifyResponse(response) {
|
|
185
|
+
if (typeof response === 'string')
|
|
186
|
+
return response;
|
|
187
|
+
if (response === null || response === undefined)
|
|
188
|
+
return '';
|
|
189
|
+
try {
|
|
190
|
+
return JSON.stringify(response);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return String(response);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function readStdin() {
|
|
197
|
+
return new Promise((resolve, reject) => {
|
|
198
|
+
const chunks = [];
|
|
199
|
+
process.stdin.on('data', (chunk) => chunks.push(chunk));
|
|
200
|
+
process.stdin.on('end', () => {
|
|
201
|
+
resolve(Buffer.concat(chunks).toString('utf-8'));
|
|
202
|
+
});
|
|
203
|
+
process.stdin.on('error', reject);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function writeOutput() {
|
|
207
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: {} }) + '\n');
|
|
208
|
+
}
|
|
209
|
+
void main();
|
|
210
|
+
//# sourceMappingURL=post-tool-extract.js.map
|
package/dist/memory/store.d.ts
CHANGED
|
@@ -9,11 +9,12 @@ export declare class MemoryStore {
|
|
|
9
9
|
private initialized;
|
|
10
10
|
constructor(embedding: Embedding, vectordb: VectorDB, history: MemoryHistory);
|
|
11
11
|
private ensureCollection;
|
|
12
|
-
addMemory(facts: ExtractedFact[], source?: string): Promise<MemoryAction[]>;
|
|
13
|
-
searchMemory(query: string, limit?: number, category?: string): Promise<MemoryItem[]>;
|
|
14
|
-
listMemories(category?: string, limit?: number): Promise<MemoryItem[]>;
|
|
12
|
+
addMemory(facts: ExtractedFact[], source?: string, project?: string): Promise<MemoryAction[]>;
|
|
13
|
+
searchMemory(query: string, limit?: number, category?: string, project?: string): Promise<MemoryItem[]>;
|
|
14
|
+
listMemories(category?: string, limit?: number, project?: string): Promise<MemoryItem[]>;
|
|
15
15
|
deleteMemory(id: string): Promise<boolean>;
|
|
16
16
|
getHistory(memoryId: string): import("./history.js").HistoryEntry[];
|
|
17
|
+
private bumpAccessCounts;
|
|
17
18
|
private processFact;
|
|
18
19
|
}
|
|
19
20
|
//# sourceMappingURL=store.d.ts.map
|
package/dist/memory/store.js
CHANGED
|
@@ -2,12 +2,14 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { hashMemory, reconcile } from './reconciler.js';
|
|
3
3
|
const COLLECTION_NAME = 'eidetic_memory';
|
|
4
4
|
const SEARCH_CANDIDATES = 5;
|
|
5
|
+
const ACCESS_BUMP_COUNT = 5;
|
|
5
6
|
// Data model mapping (reuses existing VectorDB/SearchResult fields):
|
|
6
7
|
// content → memory text (full-text search)
|
|
7
8
|
// relativePath → memory UUID (enables deleteByPath for single-memory deletion)
|
|
8
9
|
// fileExtension→ category (enables extensionFilter for category filtering)
|
|
9
10
|
// language → source
|
|
10
|
-
// Additional payload: hash, memory, category, source,
|
|
11
|
+
// Additional payload: hash, memory, category, source, project,
|
|
12
|
+
// access_count, last_accessed, created_at, updated_at
|
|
11
13
|
export class MemoryStore {
|
|
12
14
|
embedding;
|
|
13
15
|
vectordb;
|
|
@@ -27,25 +29,27 @@ export class MemoryStore {
|
|
|
27
29
|
}
|
|
28
30
|
this.initialized = true;
|
|
29
31
|
}
|
|
30
|
-
async addMemory(facts, source) {
|
|
32
|
+
async addMemory(facts, source, project = 'global') {
|
|
31
33
|
await this.ensureCollection();
|
|
32
34
|
if (facts.length === 0)
|
|
33
35
|
return [];
|
|
34
36
|
const actions = [];
|
|
35
37
|
for (const fact of facts) {
|
|
36
|
-
const action = await this.processFact(fact, source);
|
|
38
|
+
const action = await this.processFact(fact, source, project);
|
|
37
39
|
if (action)
|
|
38
40
|
actions.push(action);
|
|
39
41
|
}
|
|
40
42
|
return actions;
|
|
41
43
|
}
|
|
42
|
-
async searchMemory(query, limit = 10, category) {
|
|
44
|
+
async searchMemory(query, limit = 10, category, project) {
|
|
43
45
|
await this.ensureCollection();
|
|
44
46
|
const queryVector = await this.embedding.embed(query);
|
|
47
|
+
// Fetch extra candidates for project re-ranking when project is specified
|
|
48
|
+
const fetchLimit = project ? limit * 2 : limit;
|
|
45
49
|
const results = await this.vectordb.search(COLLECTION_NAME, {
|
|
46
50
|
queryVector,
|
|
47
51
|
queryText: query,
|
|
48
|
-
limit,
|
|
52
|
+
limit: fetchLimit,
|
|
49
53
|
...(category ? { extensionFilter: [category] } : {}),
|
|
50
54
|
});
|
|
51
55
|
// Enrich with full payload from getById
|
|
@@ -57,9 +61,19 @@ export class MemoryStore {
|
|
|
57
61
|
continue;
|
|
58
62
|
items.push(payloadToMemoryItem(id, point.payload));
|
|
59
63
|
}
|
|
60
|
-
|
|
64
|
+
// Project re-ranking: boost project-matching items to the front
|
|
65
|
+
let ranked = items;
|
|
66
|
+
if (project) {
|
|
67
|
+
const projectItems = items.filter((m) => m.project === project);
|
|
68
|
+
const otherItems = items.filter((m) => m.project !== project);
|
|
69
|
+
ranked = [...projectItems, ...otherItems].slice(0, limit);
|
|
70
|
+
}
|
|
71
|
+
// Fire-and-forget: bump access_count and last_accessed for top results
|
|
72
|
+
const topIds = ranked.slice(0, ACCESS_BUMP_COUNT).map((m) => m.id);
|
|
73
|
+
void this.bumpAccessCounts(topIds);
|
|
74
|
+
return ranked;
|
|
61
75
|
}
|
|
62
|
-
async listMemories(category, limit = 50) {
|
|
76
|
+
async listMemories(category, limit = 50, project) {
|
|
63
77
|
await this.ensureCollection();
|
|
64
78
|
const queryVector = await this.embedding.embed('developer knowledge');
|
|
65
79
|
const results = await this.vectordb.search(COLLECTION_NAME, {
|
|
@@ -76,6 +90,10 @@ export class MemoryStore {
|
|
|
76
90
|
continue;
|
|
77
91
|
items.push(payloadToMemoryItem(id, point.payload));
|
|
78
92
|
}
|
|
93
|
+
// Filter by project if specified
|
|
94
|
+
if (project) {
|
|
95
|
+
return items.filter((m) => m.project === project || m.project === 'global');
|
|
96
|
+
}
|
|
79
97
|
return items;
|
|
80
98
|
}
|
|
81
99
|
async deleteMemory(id) {
|
|
@@ -91,7 +109,26 @@ export class MemoryStore {
|
|
|
91
109
|
getHistory(memoryId) {
|
|
92
110
|
return this.history.getHistory(memoryId);
|
|
93
111
|
}
|
|
94
|
-
async
|
|
112
|
+
async bumpAccessCounts(ids) {
|
|
113
|
+
const now = new Date().toISOString();
|
|
114
|
+
for (const id of ids) {
|
|
115
|
+
try {
|
|
116
|
+
const point = await this.vectordb.getById(COLLECTION_NAME, id);
|
|
117
|
+
if (!point)
|
|
118
|
+
continue;
|
|
119
|
+
const currentCount = Number(point.payload.access_count ?? 0);
|
|
120
|
+
await this.vectordb.updatePoint(COLLECTION_NAME, id, point.vector, {
|
|
121
|
+
...point.payload,
|
|
122
|
+
access_count: currentCount + 1,
|
|
123
|
+
last_accessed: now,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Silently ignore — access tracking is a best-effort utility signal
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async processFact(fact, source, project = 'global') {
|
|
95
132
|
const hash = hashMemory(fact.fact);
|
|
96
133
|
const vector = await this.embedding.embed(fact.fact);
|
|
97
134
|
const searchResults = await this.vectordb.search(COLLECTION_NAME, {
|
|
@@ -119,9 +156,13 @@ export class MemoryStore {
|
|
|
119
156
|
if (decision.action === 'NONE')
|
|
120
157
|
return null;
|
|
121
158
|
const now = new Date().toISOString();
|
|
159
|
+
const effectiveProject = fact.project ?? project;
|
|
122
160
|
if (decision.action === 'UPDATE' && decision.existingId) {
|
|
123
161
|
const existingPoint = await this.vectordb.getById(COLLECTION_NAME, decision.existingId);
|
|
124
162
|
const createdAt = String(existingPoint?.payload.created_at ?? now);
|
|
163
|
+
// Preserve existing access tracking
|
|
164
|
+
const existingAccessCount = Number(existingPoint?.payload.access_count ?? 0);
|
|
165
|
+
const existingLastAccessed = String(existingPoint?.payload.last_accessed ?? '');
|
|
125
166
|
await this.vectordb.updatePoint(COLLECTION_NAME, decision.existingId, vector, {
|
|
126
167
|
content: fact.fact,
|
|
127
168
|
relativePath: decision.existingId,
|
|
@@ -133,6 +174,9 @@ export class MemoryStore {
|
|
|
133
174
|
memory: fact.fact,
|
|
134
175
|
category: fact.category,
|
|
135
176
|
source: source ?? '',
|
|
177
|
+
project: effectiveProject,
|
|
178
|
+
access_count: existingAccessCount,
|
|
179
|
+
last_accessed: existingLastAccessed,
|
|
136
180
|
created_at: createdAt,
|
|
137
181
|
updated_at: now,
|
|
138
182
|
});
|
|
@@ -144,6 +188,7 @@ export class MemoryStore {
|
|
|
144
188
|
previous: decision.existingMemory,
|
|
145
189
|
category: fact.category,
|
|
146
190
|
source,
|
|
191
|
+
project: effectiveProject,
|
|
147
192
|
};
|
|
148
193
|
}
|
|
149
194
|
// ADD
|
|
@@ -159,6 +204,9 @@ export class MemoryStore {
|
|
|
159
204
|
memory: fact.fact,
|
|
160
205
|
category: fact.category,
|
|
161
206
|
source: source ?? '',
|
|
207
|
+
project: effectiveProject,
|
|
208
|
+
access_count: 0,
|
|
209
|
+
last_accessed: '',
|
|
162
210
|
created_at: now,
|
|
163
211
|
updated_at: now,
|
|
164
212
|
});
|
|
@@ -169,6 +217,7 @@ export class MemoryStore {
|
|
|
169
217
|
memory: fact.fact,
|
|
170
218
|
category: fact.category,
|
|
171
219
|
source,
|
|
220
|
+
project: effectiveProject,
|
|
172
221
|
};
|
|
173
222
|
}
|
|
174
223
|
}
|
|
@@ -179,6 +228,9 @@ function payloadToMemoryItem(id, payload) {
|
|
|
179
228
|
hash: String(payload.hash ?? ''),
|
|
180
229
|
category: String(payload.category ?? payload.fileExtension ?? ''),
|
|
181
230
|
source: String(payload.source ?? payload.language ?? ''),
|
|
231
|
+
project: String(payload.project ?? 'global'),
|
|
232
|
+
access_count: Number(payload.access_count ?? 0),
|
|
233
|
+
last_accessed: String(payload.last_accessed ?? ''),
|
|
182
234
|
created_at: String(payload.created_at ?? ''),
|
|
183
235
|
updated_at: String(payload.updated_at ?? ''),
|
|
184
236
|
};
|
package/dist/memory/types.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ export interface MemoryItem {
|
|
|
4
4
|
hash: string;
|
|
5
5
|
category: string;
|
|
6
6
|
source: string;
|
|
7
|
+
project: string;
|
|
8
|
+
access_count: number;
|
|
9
|
+
last_accessed: string;
|
|
7
10
|
created_at: string;
|
|
8
11
|
updated_at: string;
|
|
9
12
|
}
|
|
@@ -15,6 +18,7 @@ export interface MemoryAction {
|
|
|
15
18
|
previous?: string;
|
|
16
19
|
category?: string;
|
|
17
20
|
source?: string;
|
|
21
|
+
project?: string;
|
|
18
22
|
}
|
|
19
23
|
export interface ReconcileResult {
|
|
20
24
|
action: 'ADD' | 'UPDATE' | 'NONE';
|
|
@@ -24,5 +28,6 @@ export interface ReconcileResult {
|
|
|
24
28
|
export interface ExtractedFact {
|
|
25
29
|
fact: string;
|
|
26
30
|
category: string;
|
|
31
|
+
project?: string;
|
|
27
32
|
}
|
|
28
33
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -46,7 +46,7 @@ async function main() {
|
|
|
46
46
|
const { getMemoryDbPath } = await import('../paths.js');
|
|
47
47
|
const history = new MemoryHistory(getMemoryDbPath());
|
|
48
48
|
const store = new MemoryStore(embedding, vectordb, history);
|
|
49
|
-
const memories = await store.searchMemory(`${projectName} development knowledge`, 7);
|
|
49
|
+
const memories = await store.searchMemory(`${projectName} development knowledge`, 7, undefined, projectName);
|
|
50
50
|
if (memories.length === 0) {
|
|
51
51
|
return;
|
|
52
52
|
}
|
package/dist/tool-schemas.d.ts
CHANGED
|
@@ -256,12 +256,16 @@ export declare const TOOL_DEFINITIONS: readonly [{
|
|
|
256
256
|
readonly type: "string";
|
|
257
257
|
readonly description: "Optional source identifier (e.g., \"conversation\", \"claude-code\", \"user-note\").";
|
|
258
258
|
};
|
|
259
|
+
readonly project: {
|
|
260
|
+
readonly type: "string";
|
|
261
|
+
readonly description: "Optional project name to scope this memory (e.g., \"my-app\"). Defaults to \"global\" for cross-project memories.";
|
|
262
|
+
};
|
|
259
263
|
};
|
|
260
264
|
readonly required: readonly ["facts"];
|
|
261
265
|
};
|
|
262
266
|
}, {
|
|
263
267
|
readonly name: "search_memory";
|
|
264
|
-
readonly description: "Search stored developer memories using natural language. Returns semantically similar memories ranked by relevance.";
|
|
268
|
+
readonly description: "Search stored developer memories using natural language. Returns semantically similar memories ranked by relevance. When a project is specified, project-specific memories are ranked higher.";
|
|
265
269
|
readonly inputSchema: {
|
|
266
270
|
readonly type: "object";
|
|
267
271
|
readonly properties: {
|
|
@@ -280,12 +284,16 @@ export declare const TOOL_DEFINITIONS: readonly [{
|
|
|
280
284
|
readonly description: "Filter by category: coding_style, tools, architecture, conventions, debugging, workflow, preferences.";
|
|
281
285
|
readonly enum: readonly ["coding_style", "tools", "architecture", "conventions", "debugging", "workflow", "preferences"];
|
|
282
286
|
};
|
|
287
|
+
readonly project: {
|
|
288
|
+
readonly type: "string";
|
|
289
|
+
readonly description: "Optional project name to boost project-specific memories in results (e.g., \"my-app\"). Cross-project memories still appear but ranked lower.";
|
|
290
|
+
};
|
|
283
291
|
};
|
|
284
292
|
readonly required: readonly ["query"];
|
|
285
293
|
};
|
|
286
294
|
}, {
|
|
287
295
|
readonly name: "list_memories";
|
|
288
|
-
readonly description: "List all stored developer memories, optionally filtered by category.";
|
|
296
|
+
readonly description: "List all stored developer memories, optionally filtered by category or project.";
|
|
289
297
|
readonly inputSchema: {
|
|
290
298
|
readonly type: "object";
|
|
291
299
|
readonly properties: {
|
|
@@ -300,6 +308,10 @@ export declare const TOOL_DEFINITIONS: readonly [{
|
|
|
300
308
|
readonly default: 50;
|
|
301
309
|
readonly maximum: 100;
|
|
302
310
|
};
|
|
311
|
+
readonly project: {
|
|
312
|
+
readonly type: "string";
|
|
313
|
+
readonly description: "Optional project name to filter memories. Returns only project-specific and global memories.";
|
|
314
|
+
};
|
|
303
315
|
};
|
|
304
316
|
readonly required: readonly [];
|
|
305
317
|
};
|
package/dist/tool-schemas.js
CHANGED
|
@@ -324,13 +324,17 @@ export const TOOL_DEFINITIONS = [
|
|
|
324
324
|
type: 'string',
|
|
325
325
|
description: 'Optional source identifier (e.g., "conversation", "claude-code", "user-note").',
|
|
326
326
|
},
|
|
327
|
+
project: {
|
|
328
|
+
type: 'string',
|
|
329
|
+
description: 'Optional project name to scope this memory (e.g., "my-app"). Defaults to "global" for cross-project memories.',
|
|
330
|
+
},
|
|
327
331
|
},
|
|
328
332
|
required: ['facts'],
|
|
329
333
|
},
|
|
330
334
|
},
|
|
331
335
|
{
|
|
332
336
|
name: 'search_memory',
|
|
333
|
-
description: 'Search stored developer memories using natural language. Returns semantically similar memories ranked by relevance.',
|
|
337
|
+
description: 'Search stored developer memories using natural language. Returns semantically similar memories ranked by relevance. When a project is specified, project-specific memories are ranked higher.',
|
|
334
338
|
inputSchema: {
|
|
335
339
|
type: 'object',
|
|
336
340
|
properties: {
|
|
@@ -357,13 +361,17 @@ export const TOOL_DEFINITIONS = [
|
|
|
357
361
|
'preferences',
|
|
358
362
|
],
|
|
359
363
|
},
|
|
364
|
+
project: {
|
|
365
|
+
type: 'string',
|
|
366
|
+
description: 'Optional project name to boost project-specific memories in results (e.g., "my-app"). Cross-project memories still appear but ranked lower.',
|
|
367
|
+
},
|
|
360
368
|
},
|
|
361
369
|
required: ['query'],
|
|
362
370
|
},
|
|
363
371
|
},
|
|
364
372
|
{
|
|
365
373
|
name: 'list_memories',
|
|
366
|
-
description: 'List all stored developer memories, optionally filtered by category.',
|
|
374
|
+
description: 'List all stored developer memories, optionally filtered by category or project.',
|
|
367
375
|
inputSchema: {
|
|
368
376
|
type: 'object',
|
|
369
377
|
properties: {
|
|
@@ -386,6 +394,10 @@ export const TOOL_DEFINITIONS = [
|
|
|
386
394
|
default: 50,
|
|
387
395
|
maximum: 100,
|
|
388
396
|
},
|
|
397
|
+
project: {
|
|
398
|
+
type: 'string',
|
|
399
|
+
description: 'Optional project name to filter memories. Returns only project-specific and global memories.',
|
|
400
|
+
},
|
|
389
401
|
},
|
|
390
402
|
required: [],
|
|
391
403
|
},
|
package/dist/tools.js
CHANGED
|
@@ -261,8 +261,9 @@ export class ToolHandlers {
|
|
|
261
261
|
if (!facts || !Array.isArray(facts) || facts.length === 0)
|
|
262
262
|
return textResult('Error: "facts" is required. Provide an array of pre-extracted facts with fact and category fields.');
|
|
263
263
|
const source = args.source;
|
|
264
|
+
const project = args.project;
|
|
264
265
|
try {
|
|
265
|
-
const actions = await this.memoryStore.addMemory(facts, source);
|
|
266
|
+
const actions = await this.memoryStore.addMemory(facts, source, project);
|
|
266
267
|
return textResult(formatMemoryActions(actions));
|
|
267
268
|
}
|
|
268
269
|
catch (err) {
|
|
@@ -278,8 +279,9 @@ export class ToolHandlers {
|
|
|
278
279
|
return textResult('Error: "query" is required. Provide a natural language search query.');
|
|
279
280
|
const limit = args.limit ?? 10;
|
|
280
281
|
const category = args.category;
|
|
282
|
+
const project = args.project;
|
|
281
283
|
try {
|
|
282
|
-
const results = await this.memoryStore.searchMemory(query, limit, category);
|
|
284
|
+
const results = await this.memoryStore.searchMemory(query, limit, category, project);
|
|
283
285
|
return textResult(formatMemorySearchResults(results, query));
|
|
284
286
|
}
|
|
285
287
|
catch (err) {
|
|
@@ -292,8 +294,9 @@ export class ToolHandlers {
|
|
|
292
294
|
return textResult('Error: Memory system not initialized.');
|
|
293
295
|
const category = args.category;
|
|
294
296
|
const limit = args.limit ?? 50;
|
|
297
|
+
const project = args.project;
|
|
295
298
|
try {
|
|
296
|
-
const results = await this.memoryStore.listMemories(category, limit);
|
|
299
|
+
const results = await this.memoryStore.listMemories(category, limit, project);
|
|
297
300
|
return textResult(formatMemoryList(results));
|
|
298
301
|
}
|
|
299
302
|
catch (err) {
|