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.
- package/LICENSE +21 -0
- package/README.md +281 -0
- package/dist/cli/generate-summary.d.ts +1 -0
- package/dist/cli/generate-summary.js +13 -0
- package/dist/cli/generate-summary.js.map +1 -0
- package/dist/cli/viewer.d.ts +1 -0
- package/dist/cli/viewer.js +4 -0
- package/dist/cli/viewer.js.map +1 -0
- package/dist/embeddings/ollama.d.ts +3 -0
- package/dist/embeddings/ollama.js +63 -0
- package/dist/embeddings/ollama.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/search/hybrid.d.ts +8 -0
- package/dist/search/hybrid.js +54 -0
- package/dist/search/hybrid.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +399 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/knowledge-graph.d.ts +32 -0
- package/dist/storage/knowledge-graph.js +259 -0
- package/dist/storage/knowledge-graph.js.map +1 -0
- package/dist/storage/lance-store.d.ts +21 -0
- package/dist/storage/lance-store.js +288 -0
- package/dist/storage/lance-store.js.map +1 -0
- package/dist/storage/markdown-store.d.ts +7 -0
- package/dist/storage/markdown-store.js +63 -0
- package/dist/storage/markdown-store.js.map +1 -0
- package/dist/storage/types.d.ts +19 -0
- package/dist/storage/types.js +13 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/utils/chunking.d.ts +1 -0
- package/dist/utils/chunking.js +55 -0
- package/dist/utils/chunking.js.map +1 -0
- package/dist/utils/privacy.d.ts +13 -0
- package/dist/utils/privacy.js +23 -0
- package/dist/utils/privacy.js.map +1 -0
- package/dist/utils/project.d.ts +3 -0
- package/dist/utils/project.js +11 -0
- package/dist/utils/project.js.map +1 -0
- package/dist/utils/summarize.d.ts +12 -0
- package/dist/utils/summarize.js +123 -0
- package/dist/utils/summarize.js.map +1 -0
- package/dist/viewer/index.html +328 -0
- package/dist/viewer/server.d.ts +1 -0
- package/dist/viewer/server.js +203 -0
- package/dist/viewer/server.js.map +1 -0
- package/hooks/on-post-tool.sh +17 -0
- package/hooks/on-pre-compact.sh +20 -0
- package/hooks/on-session-end.sh +46 -0
- package/hooks/on-session-start.sh +14 -0
- package/hooks/on-stop.sh +6 -0
- package/package.json +60 -0
- package/scripts/install.sh +125 -0
- 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 @@
|
|
|
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,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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"}
|
package/dist/index.d.ts
ADDED
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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"}
|
package/dist/server.d.ts
ADDED