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 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
- lines.push(` Category: ${action.category ?? 'unknown'} | ID: ${action.id}`);
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
- lines.push(` Category: ${m.category} | ID: ${m.id}`);
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
- lines.push(` - ${m.memory} [${m.id.slice(0, 8)}...]${updatedDate}`);
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
@@ -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
@@ -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, created_at, updated_at
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
- return items;
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 processFact(fact, source) {
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
  };
@@ -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
  }
@@ -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
  };
@@ -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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-eidetic",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Semantic code search MCP server — lean, correct, fast",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",