persisted-memory 1.0.0

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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +281 -0
  3. package/dist/cli/generate-summary.d.ts +1 -0
  4. package/dist/cli/generate-summary.js +13 -0
  5. package/dist/cli/generate-summary.js.map +1 -0
  6. package/dist/cli/viewer.d.ts +1 -0
  7. package/dist/cli/viewer.js +4 -0
  8. package/dist/cli/viewer.js.map +1 -0
  9. package/dist/embeddings/ollama.d.ts +3 -0
  10. package/dist/embeddings/ollama.js +63 -0
  11. package/dist/embeddings/ollama.js.map +1 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +25 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/search/hybrid.d.ts +8 -0
  16. package/dist/search/hybrid.js +54 -0
  17. package/dist/search/hybrid.js.map +1 -0
  18. package/dist/server.d.ts +2 -0
  19. package/dist/server.js +399 -0
  20. package/dist/server.js.map +1 -0
  21. package/dist/storage/knowledge-graph.d.ts +32 -0
  22. package/dist/storage/knowledge-graph.js +259 -0
  23. package/dist/storage/knowledge-graph.js.map +1 -0
  24. package/dist/storage/lance-store.d.ts +21 -0
  25. package/dist/storage/lance-store.js +288 -0
  26. package/dist/storage/lance-store.js.map +1 -0
  27. package/dist/storage/markdown-store.d.ts +7 -0
  28. package/dist/storage/markdown-store.js +63 -0
  29. package/dist/storage/markdown-store.js.map +1 -0
  30. package/dist/storage/types.d.ts +19 -0
  31. package/dist/storage/types.js +13 -0
  32. package/dist/storage/types.js.map +1 -0
  33. package/dist/utils/chunking.d.ts +1 -0
  34. package/dist/utils/chunking.js +55 -0
  35. package/dist/utils/chunking.js.map +1 -0
  36. package/dist/utils/privacy.d.ts +13 -0
  37. package/dist/utils/privacy.js +23 -0
  38. package/dist/utils/privacy.js.map +1 -0
  39. package/dist/utils/project.d.ts +3 -0
  40. package/dist/utils/project.js +11 -0
  41. package/dist/utils/project.js.map +1 -0
  42. package/dist/utils/summarize.d.ts +12 -0
  43. package/dist/utils/summarize.js +123 -0
  44. package/dist/utils/summarize.js.map +1 -0
  45. package/dist/viewer/index.html +328 -0
  46. package/dist/viewer/server.d.ts +1 -0
  47. package/dist/viewer/server.js +203 -0
  48. package/dist/viewer/server.js.map +1 -0
  49. package/hooks/on-post-tool.sh +17 -0
  50. package/hooks/on-pre-compact.sh +20 -0
  51. package/hooks/on-session-end.sh +46 -0
  52. package/hooks/on-session-start.sh +14 -0
  53. package/hooks/on-stop.sh +6 -0
  54. package/package.json +60 -0
  55. package/scripts/install.sh +125 -0
  56. package/scripts/uninstall.sh +59 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 saharat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,281 @@
1
+ # Persisted Memory for Claude Code
2
+
3
+ A persistent memory system that survives Claude Code's context compaction. Memories are stored as **Markdown files** (source of truth) and indexed in **LanceDB** (vector search). Uses **Ollama** for local embeddings and AI-powered summarization with graceful degradation to keyword-only search.
4
+
5
+ ## How It Works
6
+
7
+ ```
8
+ Claude Code Session (any project)
9
+
10
+ ├─ Hooks capture context automatically
11
+ │ SessionStart → injects SUMMARY.md
12
+ │ PreCompact → saves context before compaction
13
+ │ PostToolUse → logs file changes
14
+ │ Stop → marks turn completion
15
+ │ SessionEnd → archives session + AI summary
16
+
17
+ └─ MCP Server provides 10 tools
18
+ memory_store, memory_search, memory_recall,
19
+ memory_list, memory_status, memory_forget,
20
+ memory_consolidate, memory_graph, memory_viewer
21
+ ```
22
+
23
+ Memories are stored **per-project** at `<project>/.claude/memory/`. The MCP server and hooks are installed **once globally** and work with any project in any language.
24
+
25
+ ## Features
26
+
27
+ - **Dual-write architecture** -- Markdown (human-readable, git-friendly) + LanceDB (fast vector search)
28
+ - **Hybrid search** -- 70% semantic + 30% keyword, with automatic fallback
29
+ - **Knowledge graph** -- automatic entity/relationship extraction across memories
30
+ - **Progressive disclosure** -- token-efficient recall with summary/detailed/ids_only modes
31
+ - **Privacy tags** -- wrap sensitive content in `<private>...</private>` to exclude from outputs
32
+ - **AI-powered summaries** -- Ollama-generated session summaries (falls back to rule-based)
33
+ - **Web viewer** -- browser-based UI for exploring and searching memories
34
+ - **Zero external APIs** -- everything runs locally with Ollama
35
+
36
+ ## Prerequisites
37
+
38
+ - **Node.js** 20+ (LTS)
39
+ - **Ollama** with `nomic-embed-text` model (optional -- falls back to keyword search)
40
+
41
+ ```bash
42
+ # Install Ollama (if not already installed)
43
+ brew install ollama
44
+
45
+ # Pull the embedding model
46
+ ollama pull nomic-embed-text
47
+
48
+ # Optional: pull the summarization model
49
+ ollama pull llama3.2
50
+ ```
51
+
52
+ ## Installation
53
+
54
+ ### From npm
55
+
56
+ ```bash
57
+ npm install -g persisted-memory
58
+ ```
59
+
60
+ ### From source
61
+
62
+ ```bash
63
+ git clone <this-repo> ~/Works/persisted_memory
64
+ cd ~/Works/persisted_memory
65
+ npm install && npm run build
66
+ bash scripts/install.sh
67
+ ```
68
+
69
+ The install script will:
70
+ 1. Install dependencies and build
71
+ 2. Register the MCP server at user-level (`claude mcp add`)
72
+ 3. Register hooks in `~/.claude/settings.json`
73
+ 4. Add memory instructions to `~/.claude/CLAUDE.md`
74
+
75
+ **Restart Claude Code** after installation.
76
+
77
+ ## Verification
78
+
79
+ After restarting Claude Code:
80
+
81
+ ```
82
+ # Check MCP server is connected
83
+ /mcp
84
+
85
+ # Check system status (inside any Claude Code session)
86
+ Ask: "Check memory status"
87
+ → Claude calls memory_status → shows total entries, Ollama status, memory dir
88
+ ```
89
+
90
+ ## Usage
91
+
92
+ ### Automatic (via hooks)
93
+
94
+ Hooks fire automatically during every Claude Code session:
95
+ - **Session start**: Previous session's SUMMARY.md is injected into context
96
+ - **File changes**: Edit/Write operations are logged to daily Markdown
97
+ - **Context compaction**: Transcript is saved before compaction occurs
98
+ - **Session end**: AI-powered summary is generated (or rule-based fallback)
99
+
100
+ ### Manual (via MCP tools)
101
+
102
+ Ask Claude to use these tools naturally:
103
+
104
+ | What you say | Tool called |
105
+ |---|---|
106
+ | "Remember that we use Yarn, not npm" | `memory_store` |
107
+ | "What did we decide about authentication?" | `memory_search` |
108
+ | "Recall everything about the database schema" | `memory_recall` |
109
+ | "Show me all stored memories" | `memory_list` |
110
+ | "Check memory system status" | `memory_status` |
111
+ | "Forget memory abc-123" | `memory_forget` |
112
+ | "Clean up duplicate memories" | `memory_consolidate` |
113
+ | "What concepts are related to Docker?" | `memory_graph` |
114
+ | "Open the memory viewer" | `memory_viewer` |
115
+
116
+ ### Privacy Tags
117
+
118
+ Wrap sensitive content in `<private>` tags to keep it out of summaries, public views, and the web viewer:
119
+
120
+ ```
121
+ memory_store: "API key is <private>sk-abc123</private>, use it for auth"
122
+ ```
123
+
124
+ The private content is stored in LanceDB for search but stripped from all outputs. Tags are case-insensitive.
125
+
126
+ ### Progressive Disclosure
127
+
128
+ The `memory_recall` tool supports three detail levels to optimize token usage:
129
+
130
+ | Level | Description |
131
+ |---|---|
132
+ | `summary` (default) | Truncated text (200 chars), most token-efficient |
133
+ | `detailed` | Full text of each memory |
134
+ | `ids_only` | Just IDs, types, and scores -- minimal tokens |
135
+
136
+ ### Knowledge Graph
137
+
138
+ Every stored memory automatically extracts entities (files, technologies, concepts) and creates relationships between them. Explore the graph:
139
+
140
+ ```
141
+ "What entities are related to TypeScript?" → memory_graph entity: "typescript"
142
+ "Show me all known entities" → memory_graph list_entities: true
143
+ "How big is the knowledge graph?" → memory_graph (default)
144
+ ```
145
+
146
+ ### Web Viewer
147
+
148
+ Launch a browser-based memory explorer:
149
+
150
+ ```
151
+ "Open the memory viewer" → memory_viewer
152
+ ```
153
+
154
+ Or from the command line:
155
+ ```bash
156
+ npm run viewer
157
+ # Opens at http://localhost:3777
158
+ ```
159
+
160
+ Features: dark theme, search with debounce, type/importance filter chips, expandable cards, pagination.
161
+
162
+ ### Memory Types
163
+
164
+ When storing memories, specify a type:
165
+
166
+ | Type | Use for |
167
+ |---|---|
168
+ | `decision` | Architecture/design decisions |
169
+ | `architecture` | System design notes |
170
+ | `code_change` | File modifications |
171
+ | `error_fix` | Problem + solution pairs |
172
+ | `pattern` | Reusable code patterns |
173
+ | `context` | General session context |
174
+ | `preference` | User/project preferences |
175
+
176
+ ### Importance Levels
177
+
178
+ | Level | Use for |
179
+ |---|---|
180
+ | `critical` | Must never be forgotten |
181
+ | `high` | Important decisions and patterns |
182
+ | `medium` | Useful context (default) |
183
+ | `low` | Minor notes |
184
+
185
+ ## Per-Project Data
186
+
187
+ Each project gets its own memory directory:
188
+
189
+ ```
190
+ <your-project>/.claude/memory/
191
+ ├── .lance/ # LanceDB vector index (add to .gitignore)
192
+ ├── SUMMARY.md # Auto-generated context summary
193
+ ├── knowledge-graph.json # Entity/relationship graph
194
+ ├── daily/ # Daily session logs
195
+ │ └── 2026-02-17.md
196
+ ├── sessions/ # Archived session summaries
197
+ │ └── 2026-02-17-10-30.md
198
+ └── decisions.md # Append-only decisions log
199
+ ```
200
+
201
+ Add to your project's `.gitignore`:
202
+ ```
203
+ .claude/memory/.lance/
204
+ ```
205
+
206
+ The `.md` and `.json` files are optionally committable -- they provide shareable context for team members.
207
+
208
+ ## Search
209
+
210
+ Search uses a **hybrid approach**:
211
+ - **70% semantic** (vector cosine similarity via Ollama embeddings)
212
+ - **30% keyword** (LanceDB full-text search)
213
+
214
+ If Ollama is unavailable, search falls back to **100% keyword** automatically.
215
+
216
+ ## Disaster Recovery
217
+
218
+ Markdown files are the source of truth. If LanceDB data corrupts:
219
+
220
+ 1. Delete `.claude/memory/.lance/`
221
+ 2. The index will be rebuilt from Markdown on next use
222
+
223
+ ## Uninstallation
224
+
225
+ ```bash
226
+ bash ~/Works/persisted_memory/scripts/uninstall.sh
227
+ ```
228
+
229
+ This removes the MCP server and hooks. Per-project memory data at `<project>/.claude/memory/` is **preserved** -- delete manually if no longer needed.
230
+
231
+ ## Project Structure
232
+
233
+ ```
234
+ persisted-memory/
235
+ ├── src/
236
+ │ ├── index.ts # Entry point (stdio MCP transport)
237
+ │ ├── server.ts # MCP server with 10 tool definitions
238
+ │ ├── storage/
239
+ │ │ ├── types.ts # MemoryEntry, MemoryType, Importance
240
+ │ │ ├── lance-store.ts # LanceDB CRUD + vector/FTS search
241
+ │ │ ├── markdown-store.ts # Markdown file read/write/append
242
+ │ │ └── knowledge-graph.ts # Entity/relationship extraction + graph
243
+ │ ├── embeddings/
244
+ │ │ └── ollama.ts # Ollama nomic-embed-text client
245
+ │ ├── search/
246
+ │ │ └── hybrid.ts # Merge vector + keyword results
247
+ │ ├── utils/
248
+ │ │ ├── chunking.ts # Split text into embeddable chunks
249
+ │ │ ├── project.ts # Resolve CLAUDE_PROJECT_DIR
250
+ │ │ ├── privacy.ts # Private tag stripping
251
+ │ │ └── summarize.ts # AI-powered summary generation
252
+ │ ├── cli/
253
+ │ │ ├── viewer.ts # Web viewer CLI entry point
254
+ │ │ └── generate-summary.ts # Summary generation CLI
255
+ │ └── viewer/
256
+ │ ├── server.ts # HTTP server for web viewer
257
+ │ └── index.html # Self-contained web UI
258
+ ├── hooks/ # Shell scripts for Claude Code hooks
259
+ ├── scripts/
260
+ │ ├── install.sh # Build + register MCP + hooks
261
+ │ └── uninstall.sh # Clean removal
262
+ ├── package.json
263
+ └── tsconfig.json
264
+ ```
265
+
266
+ ## Dependencies
267
+
268
+ Only 4 runtime packages:
269
+
270
+ | Package | Purpose |
271
+ |---|---|
272
+ | `@modelcontextprotocol/sdk` | MCP server protocol |
273
+ | `@lancedb/lancedb` | Embedded vector database |
274
+ | `apache-arrow` | Columnar data format (LanceDB dep) |
275
+ | `zod` | Schema validation for tool parameters |
276
+
277
+ No Python. No Docker. No background daemons. No external APIs.
278
+
279
+ ## License
280
+
281
+ MIT
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import { generateSummary } from "../utils/summarize.js";
2
+ import { getMemoryDir } from "../utils/project.js";
3
+ import { writeSummary } from "../storage/markdown-store.js";
4
+ async function main() {
5
+ const memoryDir = process.env.MEMORY_DIR || getMemoryDir();
6
+ const summary = await generateSummary(memoryDir);
7
+ writeSummary(memoryDir, summary);
8
+ }
9
+ main().catch((err) => {
10
+ console.error("Failed to generate summary:", err);
11
+ process.exit(1);
12
+ });
13
+ //# sourceMappingURL=generate-summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-summary.js","sourceRoot":"","sources":["../../src/cli/generate-summary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAE5D,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,YAAY,EAAE,CAAC;IAC3D,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,SAAS,CAAC,CAAC;IACjD,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AACnC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;IAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import { startViewer } from "../viewer/server.js";
2
+ const port = parseInt(process.env.PORT || "3777", 10);
3
+ startViewer(port);
4
+ //# sourceMappingURL=viewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewer.js","sourceRoot":"","sources":["../../src/cli/viewer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;AACtD,WAAW,CAAC,IAAI,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function embed(text: string): Promise<number[] | null>;
2
+ export declare function embedBatch(texts: string[]): Promise<(number[] | null)[]>;
3
+ export declare function isAvailable(): Promise<boolean>;
@@ -0,0 +1,63 @@
1
+ const OLLAMA_URL = process.env.OLLAMA_URL || "http://localhost:11434";
2
+ const MODEL = "nomic-embed-text";
3
+ const TIMEOUT_MS = 10_000;
4
+ const HEALTH_TIMEOUT_MS = 3_000;
5
+ export async function embed(text) {
6
+ try {
7
+ const controller = new AbortController();
8
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
9
+ const res = await fetch(`${OLLAMA_URL}/api/embed`, {
10
+ method: "POST",
11
+ headers: { "Content-Type": "application/json" },
12
+ body: JSON.stringify({ model: MODEL, input: text }),
13
+ signal: controller.signal,
14
+ });
15
+ clearTimeout(timer);
16
+ if (!res.ok)
17
+ return null;
18
+ const data = (await res.json());
19
+ return data.embeddings?.[0] ?? null;
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ export async function embedBatch(texts) {
26
+ if (texts.length === 0)
27
+ return [];
28
+ try {
29
+ const controller = new AbortController();
30
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS * 2);
31
+ const res = await fetch(`${OLLAMA_URL}/api/embed`, {
32
+ method: "POST",
33
+ headers: { "Content-Type": "application/json" },
34
+ body: JSON.stringify({ model: MODEL, input: texts }),
35
+ signal: controller.signal,
36
+ });
37
+ clearTimeout(timer);
38
+ if (!res.ok)
39
+ return texts.map(() => null);
40
+ const data = (await res.json());
41
+ if (!data.embeddings)
42
+ return texts.map(() => null);
43
+ return texts.map((_, i) => data.embeddings[i] ?? null);
44
+ }
45
+ catch {
46
+ return texts.map(() => null);
47
+ }
48
+ }
49
+ export async function isAvailable() {
50
+ try {
51
+ const controller = new AbortController();
52
+ const timer = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS);
53
+ const res = await fetch(`${OLLAMA_URL}/api/tags`, {
54
+ signal: controller.signal,
55
+ });
56
+ clearTimeout(timer);
57
+ return res.ok;
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ }
63
+ //# sourceMappingURL=ollama.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../src/embeddings/ollama.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { createServer } from "./server.js";
4
+ async function main() {
5
+ const server = createServer();
6
+ const transport = new StdioServerTransport();
7
+ await server.connect(transport);
8
+ // Graceful shutdown
9
+ const shutdown = async () => {
10
+ try {
11
+ await server.close();
12
+ }
13
+ catch {
14
+ // Ignore errors during shutdown
15
+ }
16
+ process.exit(0);
17
+ };
18
+ process.on("SIGINT", shutdown);
19
+ process.on("SIGTERM", shutdown);
20
+ }
21
+ main().catch((err) => {
22
+ console.error("Failed to start memory server:", err);
23
+ process.exit(1);
24
+ });
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAE7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,oBAAoB;IACpB,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QAC1B,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,gCAAgC;QAClC,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,GAAG,CAAC,CAAC;IACrD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { MemoryEntry, MemoryType, Importance } from "../storage/types.js";
2
+ export interface SearchResult extends MemoryEntry {
3
+ score: number;
4
+ }
5
+ export declare function hybridSearch(query: string, limit?: number, filters?: {
6
+ type?: MemoryType;
7
+ importance?: Importance;
8
+ }): Promise<SearchResult[]>;
@@ -0,0 +1,54 @@
1
+ import * as lanceStore from "../storage/lance-store.js";
2
+ import { embed, isAvailable } from "../embeddings/ollama.js";
3
+ const VECTOR_WEIGHT = 0.7;
4
+ const KEYWORD_WEIGHT = 0.3;
5
+ export async function hybridSearch(query, limit = 10, filters) {
6
+ const ollamaUp = await isAvailable();
7
+ if (!ollamaUp) {
8
+ // Fallback: keyword-only search
9
+ return lanceStore.fullTextSearch(query, limit);
10
+ }
11
+ const queryVector = await embed(query);
12
+ if (!queryVector) {
13
+ // Embedding failed, fallback to keyword
14
+ return lanceStore.fullTextSearch(query, limit);
15
+ }
16
+ // Run both searches in parallel
17
+ const [vectorResults, keywordResults] = await Promise.all([
18
+ lanceStore.vectorSearch(queryVector, limit * 2, filters),
19
+ lanceStore.fullTextSearch(query, limit * 2),
20
+ ]);
21
+ // Merge results by ID
22
+ const merged = new Map();
23
+ for (const result of vectorResults) {
24
+ const existing = merged.get(result.id);
25
+ const vectorScore = result.score * VECTOR_WEIGHT;
26
+ if (existing) {
27
+ existing.score += vectorScore;
28
+ }
29
+ else {
30
+ merged.set(result.id, { ...result, score: vectorScore });
31
+ }
32
+ }
33
+ for (const result of keywordResults) {
34
+ const existing = merged.get(result.id);
35
+ const keywordScore = result.score * KEYWORD_WEIGHT;
36
+ if (existing) {
37
+ existing.score += keywordScore;
38
+ }
39
+ else {
40
+ merged.set(result.id, { ...result, score: keywordScore });
41
+ }
42
+ }
43
+ // Apply type/importance filters to keyword-only results
44
+ let results = Array.from(merged.values());
45
+ if (filters?.type) {
46
+ results = results.filter((r) => r.type === filters.type);
47
+ }
48
+ if (filters?.importance) {
49
+ results = results.filter((r) => r.importance === filters.importance);
50
+ }
51
+ // Sort by score descending, return top N
52
+ return results.sort((a, b) => b.score - a.score).slice(0, limit);
53
+ }
54
+ //# sourceMappingURL=hybrid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hybrid.js","sourceRoot":"","sources":["../../src/search/hybrid.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function createServer(): McpServer;