claude-eidetic 0.1.3 → 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/README.md +4 -13
- package/dist/config.d.ts +0 -25
- package/dist/config.js +0 -17
- package/dist/embedding/openai.js +1 -1
- 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/index.js +1 -1
- package/dist/memory/store.d.ts +5 -5
- package/dist/memory/store.js +58 -26
- package/dist/memory/types.d.ts +5 -0
- package/dist/precompact/hook.d.ts +1 -1
- package/dist/precompact/hook.js +1 -59
- package/dist/precompact/memory-inject.d.ts +10 -0
- package/dist/precompact/memory-inject.js +86 -0
- package/dist/tool-schemas.d.ts +34 -7
- package/dist/tool-schemas.js +42 -7
- package/dist/tools.js +9 -6
- package/package.json +2 -3
- package/dist/memory/llm.d.ts +0 -2
- package/dist/memory/llm.js +0 -56
- package/dist/memory/prompts.d.ts +0 -5
- package/dist/memory/prompts.js +0 -36
package/README.md
CHANGED
|
@@ -16,8 +16,7 @@ claude plugin install eidetics/claude-eidetic
|
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
export OPENAI_API_KEY=sk-...
|
|
20
|
-
export ANTHROPIC_API_KEY=sk-ant-... # for memory extraction (default)
|
|
19
|
+
export OPENAI_API_KEY=sk-... # for embeddings (default)
|
|
21
20
|
```
|
|
22
21
|
|
|
23
22
|
Index your codebase once, then search by meaning:
|
|
@@ -157,16 +156,13 @@ Add to your `.mcp.json`:
|
|
|
157
156
|
"command": "npx",
|
|
158
157
|
"args": ["-y", "claude-eidetic"],
|
|
159
158
|
"env": {
|
|
160
|
-
"OPENAI_API_KEY": "sk-..."
|
|
161
|
-
"ANTHROPIC_API_KEY": "sk-ant-..."
|
|
159
|
+
"OPENAI_API_KEY": "sk-..."
|
|
162
160
|
}
|
|
163
161
|
}
|
|
164
162
|
}
|
|
165
163
|
}
|
|
166
164
|
```
|
|
167
165
|
|
|
168
|
-
`ANTHROPIC_API_KEY` is needed for the memory LLM (default provider). Omit it if using `MEMORY_LLM_PROVIDER=openai` or `ollama`.
|
|
169
|
-
|
|
170
166
|
### Global install
|
|
171
167
|
|
|
172
168
|
```bash
|
|
@@ -184,7 +180,7 @@ npm install && npx tsc && npm start
|
|
|
184
180
|
### Requirements
|
|
185
181
|
|
|
186
182
|
- Node.js >= 20.0.0
|
|
187
|
-
- An API key (OpenAI for embeddings,
|
|
183
|
+
- An API key (OpenAI for embeddings, or Ollama for free local embeddings)
|
|
188
184
|
- Docker (optional): Qdrant auto-provisions via Docker if not already running
|
|
189
185
|
- C/C++ build tools: required by tree-sitter native bindings (`node-gyp`)
|
|
190
186
|
|
|
@@ -207,8 +203,7 @@ export MEMORY_LLM_PROVIDER=ollama
|
|
|
207
203
|
|
|
208
204
|
| Variable | Default | Description |
|
|
209
205
|
|---|---|---|
|
|
210
|
-
| `OPENAI_API_KEY` | _(required for openai)_ | OpenAI API key for embeddings
|
|
211
|
-
| `ANTHROPIC_API_KEY` | _(required for anthropic memory)_ | Anthropic API key for memory LLM |
|
|
206
|
+
| `OPENAI_API_KEY` | _(required for openai)_ | OpenAI API key for embeddings |
|
|
212
207
|
| `EMBEDDING_PROVIDER` | `openai` | `openai`, `ollama`, or `local` |
|
|
213
208
|
| `EMBEDDING_MODEL` | `text-embedding-3-small` (openai) / `nomic-embed-text` (ollama) | Embedding model name |
|
|
214
209
|
| `EMBEDDING_BATCH_SIZE` | `100` | Batch size for embedding requests (1-2048) |
|
|
@@ -223,10 +218,6 @@ export MEMORY_LLM_PROVIDER=ollama
|
|
|
223
218
|
| `EIDETIC_DATA_DIR` | `~/.eidetic/` | Data root for snapshots, memory DB, registry |
|
|
224
219
|
| `CUSTOM_EXTENSIONS` | `[]` | JSON array of extra file extensions to index (e.g., `[".dart",".arb"]`) |
|
|
225
220
|
| `CUSTOM_IGNORE_PATTERNS` | `[]` | JSON array of glob patterns to exclude |
|
|
226
|
-
| `MEMORY_LLM_PROVIDER` | `anthropic` | `anthropic`, `openai`, or `ollama` |
|
|
227
|
-
| `MEMORY_LLM_MODEL` | `claude-haiku-4-5-20251001` (anthropic) / `gpt-4o-mini` (openai) / `llama3.2` (ollama) | Model for memory extraction |
|
|
228
|
-
| `MEMORY_LLM_BASE_URL` | _(none)_ | Custom base URL for memory LLM |
|
|
229
|
-
| `MEMORY_LLM_API_KEY` | _(none)_ | API key override for memory LLM |
|
|
230
221
|
|
|
231
222
|
</details>
|
|
232
223
|
|
package/dist/config.d.ts
CHANGED
|
@@ -15,11 +15,6 @@ declare const configSchema: z.ZodEffects<z.ZodObject<{
|
|
|
15
15
|
eideticDataDir: z.ZodDefault<z.ZodString>;
|
|
16
16
|
customExtensions: z.ZodEffects<z.ZodDefault<z.ZodArray<z.ZodString, "many">>, string[], unknown>;
|
|
17
17
|
customIgnorePatterns: z.ZodEffects<z.ZodDefault<z.ZodArray<z.ZodString, "many">>, string[], unknown>;
|
|
18
|
-
memoryLlmProvider: z.ZodDefault<z.ZodEnum<["openai", "ollama", "anthropic"]>>;
|
|
19
|
-
memoryLlmModel: z.ZodOptional<z.ZodString>;
|
|
20
|
-
memoryLlmBaseUrl: z.ZodOptional<z.ZodString>;
|
|
21
|
-
memoryLlmApiKey: z.ZodOptional<z.ZodString>;
|
|
22
|
-
anthropicApiKey: z.ZodDefault<z.ZodString>;
|
|
23
18
|
}, "strip", z.ZodTypeAny, {
|
|
24
19
|
embeddingProvider: "openai" | "ollama" | "local";
|
|
25
20
|
openaiApiKey: string;
|
|
@@ -32,15 +27,10 @@ declare const configSchema: z.ZodEffects<z.ZodObject<{
|
|
|
32
27
|
eideticDataDir: string;
|
|
33
28
|
customExtensions: string[];
|
|
34
29
|
customIgnorePatterns: string[];
|
|
35
|
-
memoryLlmProvider: "openai" | "ollama" | "anthropic";
|
|
36
|
-
anthropicApiKey: string;
|
|
37
30
|
openaiBaseUrl?: string | undefined;
|
|
38
31
|
embeddingModel?: string | undefined;
|
|
39
32
|
qdrantApiKey?: string | undefined;
|
|
40
33
|
milvusToken?: string | undefined;
|
|
41
|
-
memoryLlmModel?: string | undefined;
|
|
42
|
-
memoryLlmBaseUrl?: string | undefined;
|
|
43
|
-
memoryLlmApiKey?: string | undefined;
|
|
44
34
|
}, {
|
|
45
35
|
embeddingProvider?: "openai" | "ollama" | "local" | undefined;
|
|
46
36
|
openaiApiKey?: string | undefined;
|
|
@@ -57,14 +47,8 @@ declare const configSchema: z.ZodEffects<z.ZodObject<{
|
|
|
57
47
|
eideticDataDir?: string | undefined;
|
|
58
48
|
customExtensions?: unknown;
|
|
59
49
|
customIgnorePatterns?: unknown;
|
|
60
|
-
memoryLlmProvider?: "openai" | "ollama" | "anthropic" | undefined;
|
|
61
|
-
memoryLlmModel?: string | undefined;
|
|
62
|
-
memoryLlmBaseUrl?: string | undefined;
|
|
63
|
-
memoryLlmApiKey?: string | undefined;
|
|
64
|
-
anthropicApiKey?: string | undefined;
|
|
65
50
|
}>, {
|
|
66
51
|
embeddingModel: string;
|
|
67
|
-
memoryLlmModel: string;
|
|
68
52
|
embeddingProvider: "openai" | "ollama" | "local";
|
|
69
53
|
openaiApiKey: string;
|
|
70
54
|
ollamaBaseUrl: string;
|
|
@@ -76,13 +60,9 @@ declare const configSchema: z.ZodEffects<z.ZodObject<{
|
|
|
76
60
|
eideticDataDir: string;
|
|
77
61
|
customExtensions: string[];
|
|
78
62
|
customIgnorePatterns: string[];
|
|
79
|
-
memoryLlmProvider: "openai" | "ollama" | "anthropic";
|
|
80
|
-
anthropicApiKey: string;
|
|
81
63
|
openaiBaseUrl?: string | undefined;
|
|
82
64
|
qdrantApiKey?: string | undefined;
|
|
83
65
|
milvusToken?: string | undefined;
|
|
84
|
-
memoryLlmBaseUrl?: string | undefined;
|
|
85
|
-
memoryLlmApiKey?: string | undefined;
|
|
86
66
|
}, {
|
|
87
67
|
embeddingProvider?: "openai" | "ollama" | "local" | undefined;
|
|
88
68
|
openaiApiKey?: string | undefined;
|
|
@@ -99,11 +79,6 @@ declare const configSchema: z.ZodEffects<z.ZodObject<{
|
|
|
99
79
|
eideticDataDir?: string | undefined;
|
|
100
80
|
customExtensions?: unknown;
|
|
101
81
|
customIgnorePatterns?: unknown;
|
|
102
|
-
memoryLlmProvider?: "openai" | "ollama" | "anthropic" | undefined;
|
|
103
|
-
memoryLlmModel?: string | undefined;
|
|
104
|
-
memoryLlmBaseUrl?: string | undefined;
|
|
105
|
-
memoryLlmApiKey?: string | undefined;
|
|
106
|
-
anthropicApiKey?: string | undefined;
|
|
107
82
|
}>;
|
|
108
83
|
export type Config = z.infer<typeof configSchema>;
|
|
109
84
|
export declare function loadConfig(): Config;
|
package/dist/config.js
CHANGED
|
@@ -19,24 +19,12 @@ const configSchema = z
|
|
|
19
19
|
eideticDataDir: z.string().default(path.join(os.homedir(), '.eidetic')),
|
|
20
20
|
customExtensions: z.preprocess((val) => (typeof val === 'string' ? JSON.parse(val) : val), z.array(z.string()).default([])),
|
|
21
21
|
customIgnorePatterns: z.preprocess((val) => (typeof val === 'string' ? JSON.parse(val) : val), z.array(z.string()).default([])),
|
|
22
|
-
memoryLlmProvider: z.enum(['openai', 'ollama', 'anthropic']).default('anthropic'),
|
|
23
|
-
memoryLlmModel: z.string().optional(),
|
|
24
|
-
memoryLlmBaseUrl: z.string().optional(),
|
|
25
|
-
memoryLlmApiKey: z.string().optional(),
|
|
26
|
-
anthropicApiKey: z.string().default(''),
|
|
27
22
|
})
|
|
28
23
|
.transform((cfg) => ({
|
|
29
24
|
...cfg,
|
|
30
25
|
// Default embedding model depends on provider
|
|
31
26
|
embeddingModel: cfg.embeddingModel ??
|
|
32
27
|
(cfg.embeddingProvider === 'ollama' ? 'nomic-embed-text' : 'text-embedding-3-small'),
|
|
33
|
-
// Default memory LLM model depends on provider
|
|
34
|
-
memoryLlmModel: cfg.memoryLlmModel ??
|
|
35
|
-
(cfg.memoryLlmProvider === 'ollama'
|
|
36
|
-
? 'llama3.2'
|
|
37
|
-
: cfg.memoryLlmProvider === 'anthropic'
|
|
38
|
-
? 'claude-haiku-4-5-20251001'
|
|
39
|
-
: 'gpt-4o-mini'),
|
|
40
28
|
}));
|
|
41
29
|
let cachedConfig = null;
|
|
42
30
|
export function loadConfig() {
|
|
@@ -56,11 +44,6 @@ export function loadConfig() {
|
|
|
56
44
|
eideticDataDir: process.env.EIDETIC_DATA_DIR,
|
|
57
45
|
customExtensions: process.env.CUSTOM_EXTENSIONS,
|
|
58
46
|
customIgnorePatterns: process.env.CUSTOM_IGNORE_PATTERNS,
|
|
59
|
-
memoryLlmProvider: process.env.MEMORY_LLM_PROVIDER,
|
|
60
|
-
memoryLlmModel: process.env.MEMORY_LLM_MODEL || undefined,
|
|
61
|
-
memoryLlmBaseUrl: process.env.MEMORY_LLM_BASE_URL || undefined,
|
|
62
|
-
memoryLlmApiKey: process.env.MEMORY_LLM_API_KEY?.trim().replace(/^["']|["']$/g, '') || undefined,
|
|
63
|
-
anthropicApiKey: (process.env.ANTHROPIC_API_KEY ?? '').trim().replace(/^["']|["']$/g, ''),
|
|
64
47
|
};
|
|
65
48
|
const result = configSchema.safeParse(raw);
|
|
66
49
|
if (!result.success) {
|
package/dist/embedding/openai.js
CHANGED
|
@@ -148,7 +148,7 @@ export class OpenAIEmbedding {
|
|
|
148
148
|
}
|
|
149
149
|
async callWithRetry(texts) {
|
|
150
150
|
let currentBatchSize = texts.length;
|
|
151
|
-
for (let attempt = 0; attempt
|
|
151
|
+
for (let attempt = 0; attempt < RETRY_DELAYS.length + 1; attempt++) {
|
|
152
152
|
try {
|
|
153
153
|
const allResults = [];
|
|
154
154
|
for (let offset = 0; offset < texts.length; offset += currentBatchSize) {
|
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/index.js
CHANGED
|
@@ -58,7 +58,7 @@ const WORKFLOW_GUIDANCE = `# Eidetic Code Search Workflow
|
|
|
58
58
|
- Stale docs (past TTL) still return results but are flagged \`[STALE]\`
|
|
59
59
|
|
|
60
60
|
**Persistent memory (cross-session developer knowledge):**
|
|
61
|
-
- \`add_memory(
|
|
61
|
+
- \`add_memory(facts=[{fact:"...", category:"..."}])\` → stores pre-extracted facts about coding style, tools, architecture, etc.
|
|
62
62
|
- \`search_memory(query="...")\` → find relevant memories by semantic search
|
|
63
63
|
- \`list_memories()\` → see all stored memories grouped by category
|
|
64
64
|
- \`delete_memory(id="...")\` → remove a specific memory
|
package/dist/memory/store.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Embedding } from '../embedding/types.js';
|
|
2
2
|
import type { VectorDB } from '../vectordb/types.js';
|
|
3
|
-
import type { MemoryItem, MemoryAction } from './types.js';
|
|
3
|
+
import type { MemoryItem, MemoryAction, ExtractedFact } from './types.js';
|
|
4
4
|
import { MemoryHistory } from './history.js';
|
|
5
5
|
export declare class MemoryStore {
|
|
6
6
|
private embedding;
|
|
@@ -9,12 +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(
|
|
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
|
|
17
|
+
private bumpAccessCounts;
|
|
18
18
|
private processFact;
|
|
19
19
|
}
|
|
20
20
|
//# sourceMappingURL=store.d.ts.map
|
package/dist/memory/store.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { chatCompletion } from './llm.js';
|
|
3
|
-
import { buildSystemPrompt, buildExtractionPrompt } from './prompts.js';
|
|
4
2
|
import { hashMemory, reconcile } from './reconciler.js';
|
|
5
3
|
const COLLECTION_NAME = 'eidetic_memory';
|
|
6
4
|
const SEARCH_CANDIDATES = 5;
|
|
5
|
+
const ACCESS_BUMP_COUNT = 5;
|
|
7
6
|
// Data model mapping (reuses existing VectorDB/SearchResult fields):
|
|
8
7
|
// content → memory text (full-text search)
|
|
9
8
|
// relativePath → memory UUID (enables deleteByPath for single-memory deletion)
|
|
10
9
|
// fileExtension→ category (enables extensionFilter for category filtering)
|
|
11
10
|
// language → source
|
|
12
|
-
// Additional payload: hash, memory, category, source,
|
|
11
|
+
// Additional payload: hash, memory, category, source, project,
|
|
12
|
+
// access_count, last_accessed, created_at, updated_at
|
|
13
13
|
export class MemoryStore {
|
|
14
14
|
embedding;
|
|
15
15
|
vectordb;
|
|
@@ -29,26 +29,27 @@ export class MemoryStore {
|
|
|
29
29
|
}
|
|
30
30
|
this.initialized = true;
|
|
31
31
|
}
|
|
32
|
-
async addMemory(
|
|
32
|
+
async addMemory(facts, source, project = 'global') {
|
|
33
33
|
await this.ensureCollection();
|
|
34
|
-
const facts = await this.extractFacts(content);
|
|
35
34
|
if (facts.length === 0)
|
|
36
35
|
return [];
|
|
37
36
|
const actions = [];
|
|
38
37
|
for (const fact of facts) {
|
|
39
|
-
const action = await this.processFact(fact, source);
|
|
38
|
+
const action = await this.processFact(fact, source, project);
|
|
40
39
|
if (action)
|
|
41
40
|
actions.push(action);
|
|
42
41
|
}
|
|
43
42
|
return actions;
|
|
44
43
|
}
|
|
45
|
-
async searchMemory(query, limit = 10, category) {
|
|
44
|
+
async searchMemory(query, limit = 10, category, project) {
|
|
46
45
|
await this.ensureCollection();
|
|
47
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;
|
|
48
49
|
const results = await this.vectordb.search(COLLECTION_NAME, {
|
|
49
50
|
queryVector,
|
|
50
51
|
queryText: query,
|
|
51
|
-
limit,
|
|
52
|
+
limit: fetchLimit,
|
|
52
53
|
...(category ? { extensionFilter: [category] } : {}),
|
|
53
54
|
});
|
|
54
55
|
// Enrich with full payload from getById
|
|
@@ -60,9 +61,19 @@ export class MemoryStore {
|
|
|
60
61
|
continue;
|
|
61
62
|
items.push(payloadToMemoryItem(id, point.payload));
|
|
62
63
|
}
|
|
63
|
-
|
|
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;
|
|
64
75
|
}
|
|
65
|
-
async listMemories(category, limit = 50) {
|
|
76
|
+
async listMemories(category, limit = 50, project) {
|
|
66
77
|
await this.ensureCollection();
|
|
67
78
|
const queryVector = await this.embedding.embed('developer knowledge');
|
|
68
79
|
const results = await this.vectordb.search(COLLECTION_NAME, {
|
|
@@ -79,6 +90,10 @@ export class MemoryStore {
|
|
|
79
90
|
continue;
|
|
80
91
|
items.push(payloadToMemoryItem(id, point.payload));
|
|
81
92
|
}
|
|
93
|
+
// Filter by project if specified
|
|
94
|
+
if (project) {
|
|
95
|
+
return items.filter((m) => m.project === project || m.project === 'global');
|
|
96
|
+
}
|
|
82
97
|
return items;
|
|
83
98
|
}
|
|
84
99
|
async deleteMemory(id) {
|
|
@@ -94,24 +109,26 @@ export class MemoryStore {
|
|
|
94
109
|
getHistory(memoryId) {
|
|
95
110
|
return this.history.getHistory(memoryId);
|
|
96
111
|
}
|
|
97
|
-
async
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
+
}
|
|
112
129
|
}
|
|
113
130
|
}
|
|
114
|
-
async processFact(fact, source) {
|
|
131
|
+
async processFact(fact, source, project = 'global') {
|
|
115
132
|
const hash = hashMemory(fact.fact);
|
|
116
133
|
const vector = await this.embedding.embed(fact.fact);
|
|
117
134
|
const searchResults = await this.vectordb.search(COLLECTION_NAME, {
|
|
@@ -139,9 +156,13 @@ export class MemoryStore {
|
|
|
139
156
|
if (decision.action === 'NONE')
|
|
140
157
|
return null;
|
|
141
158
|
const now = new Date().toISOString();
|
|
159
|
+
const effectiveProject = fact.project ?? project;
|
|
142
160
|
if (decision.action === 'UPDATE' && decision.existingId) {
|
|
143
161
|
const existingPoint = await this.vectordb.getById(COLLECTION_NAME, decision.existingId);
|
|
144
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 ?? '');
|
|
145
166
|
await this.vectordb.updatePoint(COLLECTION_NAME, decision.existingId, vector, {
|
|
146
167
|
content: fact.fact,
|
|
147
168
|
relativePath: decision.existingId,
|
|
@@ -153,6 +174,9 @@ export class MemoryStore {
|
|
|
153
174
|
memory: fact.fact,
|
|
154
175
|
category: fact.category,
|
|
155
176
|
source: source ?? '',
|
|
177
|
+
project: effectiveProject,
|
|
178
|
+
access_count: existingAccessCount,
|
|
179
|
+
last_accessed: existingLastAccessed,
|
|
156
180
|
created_at: createdAt,
|
|
157
181
|
updated_at: now,
|
|
158
182
|
});
|
|
@@ -164,6 +188,7 @@ export class MemoryStore {
|
|
|
164
188
|
previous: decision.existingMemory,
|
|
165
189
|
category: fact.category,
|
|
166
190
|
source,
|
|
191
|
+
project: effectiveProject,
|
|
167
192
|
};
|
|
168
193
|
}
|
|
169
194
|
// ADD
|
|
@@ -179,6 +204,9 @@ export class MemoryStore {
|
|
|
179
204
|
memory: fact.fact,
|
|
180
205
|
category: fact.category,
|
|
181
206
|
source: source ?? '',
|
|
207
|
+
project: effectiveProject,
|
|
208
|
+
access_count: 0,
|
|
209
|
+
last_accessed: '',
|
|
182
210
|
created_at: now,
|
|
183
211
|
updated_at: now,
|
|
184
212
|
});
|
|
@@ -189,6 +217,7 @@ export class MemoryStore {
|
|
|
189
217
|
memory: fact.fact,
|
|
190
218
|
category: fact.category,
|
|
191
219
|
source,
|
|
220
|
+
project: effectiveProject,
|
|
192
221
|
};
|
|
193
222
|
}
|
|
194
223
|
}
|
|
@@ -199,6 +228,9 @@ function payloadToMemoryItem(id, payload) {
|
|
|
199
228
|
hash: String(payload.hash ?? ''),
|
|
200
229
|
category: String(payload.category ?? payload.fileExtension ?? ''),
|
|
201
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 ?? ''),
|
|
202
234
|
created_at: String(payload.created_at ?? ''),
|
|
203
235
|
updated_at: String(payload.updated_at ?? ''),
|
|
204
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
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Hook entry point for PreCompact and SessionEnd events.
|
|
4
4
|
*
|
|
5
5
|
* PreCompact: Parses transcript, writes session note, updates index, spawns background indexer.
|
|
6
|
-
* SessionEnd: Same as PreCompact
|
|
6
|
+
* SessionEnd: Same as PreCompact (writes session note if not already captured by PreCompact).
|
|
7
7
|
*/
|
|
8
8
|
export {};
|
|
9
9
|
//# sourceMappingURL=hook.d.ts.map
|
package/dist/precompact/hook.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Hook entry point for PreCompact and SessionEnd events.
|
|
4
4
|
*
|
|
5
5
|
* PreCompact: Parses transcript, writes session note, updates index, spawns background indexer.
|
|
6
|
-
* SessionEnd: Same as PreCompact
|
|
6
|
+
* SessionEnd: Same as PreCompact (writes session note if not already captured by PreCompact).
|
|
7
7
|
*/
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
import path from 'node:path';
|
|
@@ -68,14 +68,11 @@ async function main() {
|
|
|
68
68
|
updateSessionIndex(notesDir, session, noteFile);
|
|
69
69
|
spawnBackgroundIndexer(notesDir, INDEX_RUNNER_PATH);
|
|
70
70
|
}
|
|
71
|
-
// Run memory extraction (best-effort — graceful failure if Qdrant unavailable)
|
|
72
|
-
const memoryActions = await extractMemories(session);
|
|
73
71
|
outputSuccess({
|
|
74
72
|
noteFile,
|
|
75
73
|
skippedNote,
|
|
76
74
|
filesModified: session.filesModified.length,
|
|
77
75
|
tasksCreated: session.tasksCreated.length,
|
|
78
|
-
memoriesExtracted: memoryActions,
|
|
79
76
|
});
|
|
80
77
|
}
|
|
81
78
|
else {
|
|
@@ -94,61 +91,6 @@ async function main() {
|
|
|
94
91
|
outputError(err instanceof Error ? err.message : String(err));
|
|
95
92
|
}
|
|
96
93
|
}
|
|
97
|
-
/**
|
|
98
|
-
* Build content string for memory extraction from an ExtractedSession.
|
|
99
|
-
*/
|
|
100
|
-
function buildMemoryContent(session) {
|
|
101
|
-
const parts = [];
|
|
102
|
-
if (session.userMessages.length > 0) {
|
|
103
|
-
parts.push('User messages:');
|
|
104
|
-
session.userMessages.forEach((msg, i) => {
|
|
105
|
-
parts.push(`${i + 1}. ${msg}`);
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
if (session.filesModified.length > 0) {
|
|
109
|
-
parts.push(`\nFiles modified: ${session.filesModified.join(', ')}`);
|
|
110
|
-
}
|
|
111
|
-
if (session.tasksCreated.length > 0) {
|
|
112
|
-
parts.push(`Tasks: ${session.tasksCreated.join(', ')}`);
|
|
113
|
-
}
|
|
114
|
-
if (session.branch) {
|
|
115
|
-
parts.push(`Branch: ${session.branch}`);
|
|
116
|
-
}
|
|
117
|
-
return parts.join('\n');
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Run memory extraction pipeline. Returns count of actions taken.
|
|
121
|
-
* Fails gracefully — logs to stderr if Qdrant or LLM unavailable.
|
|
122
|
-
*/
|
|
123
|
-
async function extractMemories(session) {
|
|
124
|
-
const content = buildMemoryContent(session);
|
|
125
|
-
if (!content.trim())
|
|
126
|
-
return 0;
|
|
127
|
-
try {
|
|
128
|
-
// Dynamic imports to avoid loading heavy deps on every hook invocation
|
|
129
|
-
const [{ loadConfig }, { createEmbedding }, { QdrantVectorDB }, { MemoryHistory }, { MemoryStore }, { getMemoryDbPath },] = await Promise.all([
|
|
130
|
-
import('../config.js'),
|
|
131
|
-
import('../embedding/factory.js'),
|
|
132
|
-
import('../vectordb/qdrant.js'),
|
|
133
|
-
import('../memory/history.js'),
|
|
134
|
-
import('../memory/store.js'),
|
|
135
|
-
import('../paths.js'),
|
|
136
|
-
]);
|
|
137
|
-
const config = loadConfig();
|
|
138
|
-
const embedding = createEmbedding(config);
|
|
139
|
-
await embedding.initialize();
|
|
140
|
-
const vectordb = new QdrantVectorDB();
|
|
141
|
-
const history = new MemoryHistory(getMemoryDbPath());
|
|
142
|
-
const memoryStore = new MemoryStore(embedding, vectordb, history);
|
|
143
|
-
const actions = await memoryStore.addMemory(content, 'session-end-hook');
|
|
144
|
-
process.stderr.write(`[eidetic] Memory extraction: ${actions.length} action(s) (${actions.map((a) => a.event).join(', ') || 'none'})\n`);
|
|
145
|
-
return actions.length;
|
|
146
|
-
}
|
|
147
|
-
catch (err) {
|
|
148
|
-
process.stderr.write(`[eidetic] Memory extraction failed (non-fatal): ${err instanceof Error ? err.message : String(err)}\n`);
|
|
149
|
-
return 0;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
94
|
async function readStdin() {
|
|
153
95
|
const chunks = [];
|
|
154
96
|
for await (const chunk of process.stdin) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Inject stored memories at SessionStart.
|
|
4
|
+
* Called by session-start hook to surface previously learned knowledge.
|
|
5
|
+
*
|
|
6
|
+
* Outputs markdown to stdout for hook to capture and inject into session.
|
|
7
|
+
*/
|
|
8
|
+
import type { MemoryItem } from '../memory/types.js';
|
|
9
|
+
export declare function formatMemoryContext(memories: MemoryItem[]): string;
|
|
10
|
+
//# sourceMappingURL=memory-inject.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Inject stored memories at SessionStart.
|
|
4
|
+
* Called by session-start hook to surface previously learned knowledge.
|
|
5
|
+
*
|
|
6
|
+
* Outputs markdown to stdout for hook to capture and inject into session.
|
|
7
|
+
*/
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
async function main() {
|
|
11
|
+
try {
|
|
12
|
+
// Get cwd from environment (set by Claude Code) or detect from git
|
|
13
|
+
const cwd = process.env.CLAUDE_CWD || process.cwd();
|
|
14
|
+
// Detect project root from git
|
|
15
|
+
const projectPath = detectProjectRoot(cwd);
|
|
16
|
+
if (!projectPath) {
|
|
17
|
+
// Not in a git repo, nothing to inject
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const projectName = path.basename(projectPath);
|
|
21
|
+
// Dynamic imports — avoid loading heavy deps if not needed
|
|
22
|
+
const [{ loadConfig }, { createEmbedding }, { MemoryHistory }, { MemoryStore }] = await Promise.all([
|
|
23
|
+
import('../config.js'),
|
|
24
|
+
import('../embedding/factory.js'),
|
|
25
|
+
import('../memory/history.js'),
|
|
26
|
+
import('../memory/store.js'),
|
|
27
|
+
]);
|
|
28
|
+
const config = loadConfig();
|
|
29
|
+
const embedding = createEmbedding(config);
|
|
30
|
+
await embedding.initialize();
|
|
31
|
+
// Respect VECTORDB_PROVIDER — connect without bootstrapping (hook assumes DB is already running)
|
|
32
|
+
let vectordb;
|
|
33
|
+
if (config.vectordbProvider === 'milvus') {
|
|
34
|
+
const { MilvusVectorDB } = await import('../vectordb/milvus.js');
|
|
35
|
+
vectordb = new MilvusVectorDB();
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const { QdrantVectorDB } = await import('../vectordb/qdrant.js');
|
|
39
|
+
vectordb = new QdrantVectorDB(config.qdrantUrl, config.qdrantApiKey);
|
|
40
|
+
}
|
|
41
|
+
// Quick-exit if no memory collection exists
|
|
42
|
+
const exists = await vectordb.hasCollection('eidetic_memory');
|
|
43
|
+
if (!exists) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const { getMemoryDbPath } = await import('../paths.js');
|
|
47
|
+
const history = new MemoryHistory(getMemoryDbPath());
|
|
48
|
+
const store = new MemoryStore(embedding, vectordb, history);
|
|
49
|
+
const memories = await store.searchMemory(`${projectName} development knowledge`, 7, undefined, projectName);
|
|
50
|
+
if (memories.length === 0) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
process.stdout.write(formatMemoryContext(memories));
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
// Write to stderr for debugging, but don't break session start
|
|
57
|
+
process.stderr.write(`Memory inject failed: ${String(err)}\n`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function detectProjectRoot(cwd) {
|
|
61
|
+
try {
|
|
62
|
+
const result = execSync('git rev-parse --show-toplevel', {
|
|
63
|
+
cwd,
|
|
64
|
+
encoding: 'utf-8',
|
|
65
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
66
|
+
});
|
|
67
|
+
return result.trim();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function formatMemoryContext(memories) {
|
|
74
|
+
const lines = [];
|
|
75
|
+
lines.push('## Remembered Knowledge');
|
|
76
|
+
for (const m of memories) {
|
|
77
|
+
const category = m.category ? `[${m.category}] ` : '';
|
|
78
|
+
lines.push(`- ${category}${m.memory}`);
|
|
79
|
+
}
|
|
80
|
+
lines.push('');
|
|
81
|
+
lines.push('_search_memory(query) for more. add_memory(facts) to save new findings._');
|
|
82
|
+
lines.push('');
|
|
83
|
+
return lines.join('\n');
|
|
84
|
+
}
|
|
85
|
+
void main();
|
|
86
|
+
//# sourceMappingURL=memory-inject.js.map
|
package/dist/tool-schemas.d.ts
CHANGED
|
@@ -229,24 +229,43 @@ export declare const TOOL_DEFINITIONS: readonly [{
|
|
|
229
229
|
};
|
|
230
230
|
}, {
|
|
231
231
|
readonly name: "add_memory";
|
|
232
|
-
readonly description: "
|
|
232
|
+
readonly description: "Store pre-extracted developer knowledge facts. Before calling, extract facts yourself from the relevant content. Each fact should be a concise, self-contained statement about coding style, tools, architecture, conventions, debugging insights, or workflow preferences. Automatically deduplicates against existing memories.";
|
|
233
233
|
readonly inputSchema: {
|
|
234
234
|
readonly type: "object";
|
|
235
235
|
readonly properties: {
|
|
236
|
-
readonly
|
|
237
|
-
readonly type: "
|
|
238
|
-
readonly description: "
|
|
236
|
+
readonly facts: {
|
|
237
|
+
readonly type: "array";
|
|
238
|
+
readonly description: "Array of facts to store. Extract these yourself before calling. Each fact must be a concise, self-contained statement.";
|
|
239
|
+
readonly items: {
|
|
240
|
+
readonly type: "object";
|
|
241
|
+
readonly properties: {
|
|
242
|
+
readonly fact: {
|
|
243
|
+
readonly type: "string";
|
|
244
|
+
readonly description: "A concise, self-contained statement of a developer preference or convention.";
|
|
245
|
+
};
|
|
246
|
+
readonly category: {
|
|
247
|
+
readonly type: "string";
|
|
248
|
+
readonly description: "Category: coding_style, tools, architecture, conventions, debugging, workflow, or preferences.";
|
|
249
|
+
readonly enum: readonly ["coding_style", "tools", "architecture", "conventions", "debugging", "workflow", "preferences"];
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
readonly required: readonly ["fact", "category"];
|
|
253
|
+
};
|
|
239
254
|
};
|
|
240
255
|
readonly source: {
|
|
241
256
|
readonly type: "string";
|
|
242
257
|
readonly description: "Optional source identifier (e.g., \"conversation\", \"claude-code\", \"user-note\").";
|
|
243
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
|
+
};
|
|
244
263
|
};
|
|
245
|
-
readonly required: readonly ["
|
|
264
|
+
readonly required: readonly ["facts"];
|
|
246
265
|
};
|
|
247
266
|
}, {
|
|
248
267
|
readonly name: "search_memory";
|
|
249
|
-
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.";
|
|
250
269
|
readonly inputSchema: {
|
|
251
270
|
readonly type: "object";
|
|
252
271
|
readonly properties: {
|
|
@@ -265,12 +284,16 @@ export declare const TOOL_DEFINITIONS: readonly [{
|
|
|
265
284
|
readonly description: "Filter by category: coding_style, tools, architecture, conventions, debugging, workflow, preferences.";
|
|
266
285
|
readonly enum: readonly ["coding_style", "tools", "architecture", "conventions", "debugging", "workflow", "preferences"];
|
|
267
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
|
+
};
|
|
268
291
|
};
|
|
269
292
|
readonly required: readonly ["query"];
|
|
270
293
|
};
|
|
271
294
|
}, {
|
|
272
295
|
readonly name: "list_memories";
|
|
273
|
-
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.";
|
|
274
297
|
readonly inputSchema: {
|
|
275
298
|
readonly type: "object";
|
|
276
299
|
readonly properties: {
|
|
@@ -285,6 +308,10 @@ export declare const TOOL_DEFINITIONS: readonly [{
|
|
|
285
308
|
readonly default: 50;
|
|
286
309
|
readonly maximum: 100;
|
|
287
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
|
+
};
|
|
288
315
|
};
|
|
289
316
|
readonly required: readonly [];
|
|
290
317
|
};
|
package/dist/tool-schemas.js
CHANGED
|
@@ -289,25 +289,52 @@ export const TOOL_DEFINITIONS = [
|
|
|
289
289
|
},
|
|
290
290
|
{
|
|
291
291
|
name: 'add_memory',
|
|
292
|
-
description: '
|
|
292
|
+
description: 'Store pre-extracted developer knowledge facts. Before calling, extract facts yourself from the relevant content. Each fact should be a concise, self-contained statement about coding style, tools, architecture, conventions, debugging insights, or workflow preferences. Automatically deduplicates against existing memories.',
|
|
293
293
|
inputSchema: {
|
|
294
294
|
type: 'object',
|
|
295
295
|
properties: {
|
|
296
|
-
|
|
297
|
-
type: '
|
|
298
|
-
description: '
|
|
296
|
+
facts: {
|
|
297
|
+
type: 'array',
|
|
298
|
+
description: 'Array of facts to store. Extract these yourself before calling. Each fact must be a concise, self-contained statement.',
|
|
299
|
+
items: {
|
|
300
|
+
type: 'object',
|
|
301
|
+
properties: {
|
|
302
|
+
fact: {
|
|
303
|
+
type: 'string',
|
|
304
|
+
description: 'A concise, self-contained statement of a developer preference or convention.',
|
|
305
|
+
},
|
|
306
|
+
category: {
|
|
307
|
+
type: 'string',
|
|
308
|
+
description: 'Category: coding_style, tools, architecture, conventions, debugging, workflow, or preferences.',
|
|
309
|
+
enum: [
|
|
310
|
+
'coding_style',
|
|
311
|
+
'tools',
|
|
312
|
+
'architecture',
|
|
313
|
+
'conventions',
|
|
314
|
+
'debugging',
|
|
315
|
+
'workflow',
|
|
316
|
+
'preferences',
|
|
317
|
+
],
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
required: ['fact', 'category'],
|
|
321
|
+
},
|
|
299
322
|
},
|
|
300
323
|
source: {
|
|
301
324
|
type: 'string',
|
|
302
325
|
description: 'Optional source identifier (e.g., "conversation", "claude-code", "user-note").',
|
|
303
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
|
+
},
|
|
304
331
|
},
|
|
305
|
-
required: ['
|
|
332
|
+
required: ['facts'],
|
|
306
333
|
},
|
|
307
334
|
},
|
|
308
335
|
{
|
|
309
336
|
name: 'search_memory',
|
|
310
|
-
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.',
|
|
311
338
|
inputSchema: {
|
|
312
339
|
type: 'object',
|
|
313
340
|
properties: {
|
|
@@ -334,13 +361,17 @@ export const TOOL_DEFINITIONS = [
|
|
|
334
361
|
'preferences',
|
|
335
362
|
],
|
|
336
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
|
+
},
|
|
337
368
|
},
|
|
338
369
|
required: ['query'],
|
|
339
370
|
},
|
|
340
371
|
},
|
|
341
372
|
{
|
|
342
373
|
name: 'list_memories',
|
|
343
|
-
description: 'List all stored developer memories, optionally filtered by category.',
|
|
374
|
+
description: 'List all stored developer memories, optionally filtered by category or project.',
|
|
344
375
|
inputSchema: {
|
|
345
376
|
type: 'object',
|
|
346
377
|
properties: {
|
|
@@ -363,6 +394,10 @@ export const TOOL_DEFINITIONS = [
|
|
|
363
394
|
default: 50,
|
|
364
395
|
maximum: 100,
|
|
365
396
|
},
|
|
397
|
+
project: {
|
|
398
|
+
type: 'string',
|
|
399
|
+
description: 'Optional project name to filter memories. Returns only project-specific and global memories.',
|
|
400
|
+
},
|
|
366
401
|
},
|
|
367
402
|
required: [],
|
|
368
403
|
},
|
package/dist/tools.js
CHANGED
|
@@ -257,12 +257,13 @@ export class ToolHandlers {
|
|
|
257
257
|
async handleAddMemory(args) {
|
|
258
258
|
if (!this.memoryStore)
|
|
259
259
|
return textResult('Error: Memory system not initialized.');
|
|
260
|
-
const
|
|
261
|
-
if (!
|
|
262
|
-
return textResult('Error: "
|
|
260
|
+
const facts = args.facts;
|
|
261
|
+
if (!facts || !Array.isArray(facts) || facts.length === 0)
|
|
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(
|
|
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.
|
|
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",
|
|
@@ -37,7 +37,6 @@
|
|
|
37
37
|
"release": "bash scripts/release.sh"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@anthropic-ai/sdk": "^0.78.0",
|
|
41
40
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
42
41
|
"@qdrant/js-client-rest": "^1.13.0",
|
|
43
42
|
"better-sqlite3": "^12.6.2",
|
|
@@ -81,7 +80,7 @@
|
|
|
81
80
|
},
|
|
82
81
|
"repository": {
|
|
83
82
|
"type": "git",
|
|
84
|
-
"url": "https://github.com/eidetics/claude-eidetic"
|
|
83
|
+
"url": "https://github.com/eidetics/claude-eidetic.git"
|
|
85
84
|
},
|
|
86
85
|
"license": "MIT"
|
|
87
86
|
}
|
package/dist/memory/llm.d.ts
DELETED
package/dist/memory/llm.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import OpenAI from 'openai';
|
|
2
|
-
import Anthropic from '@anthropic-ai/sdk';
|
|
3
|
-
import { getConfig } from '../config.js';
|
|
4
|
-
import { MemoryError } from '../errors.js';
|
|
5
|
-
export async function chatCompletion(systemPrompt, userMessage) {
|
|
6
|
-
const config = getConfig();
|
|
7
|
-
if (config.memoryLlmProvider === 'anthropic') {
|
|
8
|
-
const apiKey = config.memoryLlmApiKey ?? config.anthropicApiKey ?? config.openaiApiKey;
|
|
9
|
-
if (!apiKey) {
|
|
10
|
-
throw new MemoryError('No API key configured for memory LLM. Set MEMORY_LLM_API_KEY, ANTHROPIC_API_KEY, or OPENAI_API_KEY.');
|
|
11
|
-
}
|
|
12
|
-
const client = new Anthropic({ apiKey });
|
|
13
|
-
try {
|
|
14
|
-
const response = await client.messages.create({
|
|
15
|
-
model: config.memoryLlmModel,
|
|
16
|
-
max_tokens: 2048,
|
|
17
|
-
system: systemPrompt,
|
|
18
|
-
messages: [{ role: 'user', content: userMessage }],
|
|
19
|
-
});
|
|
20
|
-
const block = response.content[0];
|
|
21
|
-
return block?.type === 'text' ? block.text : '{}';
|
|
22
|
-
}
|
|
23
|
-
catch (err) {
|
|
24
|
-
throw new MemoryError('Memory LLM call failed', err);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
// OpenAI / Ollama path
|
|
28
|
-
const apiKey = config.memoryLlmApiKey ?? config.openaiApiKey;
|
|
29
|
-
if (!apiKey) {
|
|
30
|
-
throw new MemoryError('No API key configured for memory LLM. Set MEMORY_LLM_API_KEY or OPENAI_API_KEY.');
|
|
31
|
-
}
|
|
32
|
-
let baseURL;
|
|
33
|
-
if (config.memoryLlmBaseUrl) {
|
|
34
|
-
baseURL = config.memoryLlmBaseUrl;
|
|
35
|
-
}
|
|
36
|
-
else if (config.memoryLlmProvider === 'ollama') {
|
|
37
|
-
baseURL = config.ollamaBaseUrl;
|
|
38
|
-
}
|
|
39
|
-
const client = new OpenAI({ apiKey, ...(baseURL ? { baseURL } : {}) });
|
|
40
|
-
try {
|
|
41
|
-
const response = await client.chat.completions.create({
|
|
42
|
-
model: config.memoryLlmModel,
|
|
43
|
-
messages: [
|
|
44
|
-
{ role: 'system', content: systemPrompt },
|
|
45
|
-
{ role: 'user', content: userMessage },
|
|
46
|
-
],
|
|
47
|
-
temperature: 0,
|
|
48
|
-
response_format: { type: 'json_object' },
|
|
49
|
-
});
|
|
50
|
-
return response.choices[0]?.message?.content ?? '{}';
|
|
51
|
-
}
|
|
52
|
-
catch (err) {
|
|
53
|
-
throw new MemoryError('Memory LLM call failed', err);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
//# sourceMappingURL=llm.js.map
|
package/dist/memory/prompts.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export declare const FACT_EXTRACTION_SYSTEM_PROMPT = "You are a developer knowledge extractor. Your job is to extract discrete, factual statements from conversations about software development.\n\nExtract facts about:\n- **coding_style**: Formatting preferences (tabs/spaces, naming conventions, line length), code style rules\n- **tools**: Preferred tools, frameworks, libraries, test runners, bundlers, linters, editors\n- **architecture**: Design patterns, architectural decisions, system design preferences\n- **conventions**: Project conventions, commit message formats, branch naming, PR workflows\n- **debugging**: Solutions to specific bugs, debugging techniques, known issues\n- **workflow**: Development habits, deployment processes, review preferences\n- **preferences**: General preferences, opinions, requirements that don't fit other categories\n\nRules:\n1. Extract only factual, concrete statements \u2014 not vague observations\n2. Each fact should be a single, self-contained statement\n3. Use third person (\"The user prefers...\" or state the fact directly)\n4. If the input contains no extractable facts, return an empty array\n5. Do NOT extract facts about the conversation itself (e.g., \"the user asked about...\")\n6. Do NOT extract temporary or session-specific information\n7. Prefer specific facts over general ones\n\nRespond with JSON: { \"facts\": [{ \"fact\": \"...\", \"category\": \"...\" }] }\n\nCategories: coding_style, tools, architecture, conventions, debugging, workflow, preferences";
|
|
2
|
-
export declare const FACT_EXTRACTION_USER_TEMPLATE = "Extract developer knowledge facts from this text:\n\n<text>\n{content}\n</text>";
|
|
3
|
-
export declare function buildSystemPrompt(): string;
|
|
4
|
-
export declare function buildExtractionPrompt(content: string): string;
|
|
5
|
-
//# sourceMappingURL=prompts.d.ts.map
|
package/dist/memory/prompts.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export const FACT_EXTRACTION_SYSTEM_PROMPT = `You are a developer knowledge extractor. Your job is to extract discrete, factual statements from conversations about software development.
|
|
2
|
-
|
|
3
|
-
Extract facts about:
|
|
4
|
-
- **coding_style**: Formatting preferences (tabs/spaces, naming conventions, line length), code style rules
|
|
5
|
-
- **tools**: Preferred tools, frameworks, libraries, test runners, bundlers, linters, editors
|
|
6
|
-
- **architecture**: Design patterns, architectural decisions, system design preferences
|
|
7
|
-
- **conventions**: Project conventions, commit message formats, branch naming, PR workflows
|
|
8
|
-
- **debugging**: Solutions to specific bugs, debugging techniques, known issues
|
|
9
|
-
- **workflow**: Development habits, deployment processes, review preferences
|
|
10
|
-
- **preferences**: General preferences, opinions, requirements that don't fit other categories
|
|
11
|
-
|
|
12
|
-
Rules:
|
|
13
|
-
1. Extract only factual, concrete statements — not vague observations
|
|
14
|
-
2. Each fact should be a single, self-contained statement
|
|
15
|
-
3. Use third person ("The user prefers..." or state the fact directly)
|
|
16
|
-
4. If the input contains no extractable facts, return an empty array
|
|
17
|
-
5. Do NOT extract facts about the conversation itself (e.g., "the user asked about...")
|
|
18
|
-
6. Do NOT extract temporary or session-specific information
|
|
19
|
-
7. Prefer specific facts over general ones
|
|
20
|
-
|
|
21
|
-
Respond with JSON: { "facts": [{ "fact": "...", "category": "..." }] }
|
|
22
|
-
|
|
23
|
-
Categories: coding_style, tools, architecture, conventions, debugging, workflow, preferences`;
|
|
24
|
-
export const FACT_EXTRACTION_USER_TEMPLATE = `Extract developer knowledge facts from this text:
|
|
25
|
-
|
|
26
|
-
<text>
|
|
27
|
-
{content}
|
|
28
|
-
</text>`;
|
|
29
|
-
export function buildSystemPrompt() {
|
|
30
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
31
|
-
return `${FACT_EXTRACTION_SYSTEM_PROMPT}\n- Today's date is ${today}.`;
|
|
32
|
-
}
|
|
33
|
-
export function buildExtractionPrompt(content) {
|
|
34
|
-
return FACT_EXTRACTION_USER_TEMPLATE.replace('{content}', content);
|
|
35
|
-
}
|
|
36
|
-
//# sourceMappingURL=prompts.js.map
|