nexusmem 0.1.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 +337 -0
- package/dist/cli/index.js +2686 -0
- package/dist/cli/index.js.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NexusMem Contributors
|
|
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,337 @@
|
|
|
1
|
+
# NexusMem
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
A local-first persistent memory engine for AI coding agents (Claude Code, Cursor, MCP-based agents).
|
|
7
|
+
|
|
8
|
+
AI coding assistants forget context once a session ends, and re-uploading the entire repository as
|
|
9
|
+
context on every request is slow and expensive. NexusMem records local machine events — git history,
|
|
10
|
+
shell commands, docs, and conversation transcripts — into an on-disk SQLite database, returning only
|
|
11
|
+
the relevant context slice within a strict token budget.
|
|
12
|
+
|
|
13
|
+
All data remains local on your machine. No cloud dependencies, accounts, or telemetry.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Design Principles
|
|
18
|
+
|
|
19
|
+
- **100% Local-First**: SQLite database stored in `.nexusmem/` inside your repository using
|
|
20
|
+
`sqlite-vec` and `FTS5`. Works fully offline.
|
|
21
|
+
- **Kind-Agnostic Core**: Every source normalizes to a single `MemoryNode` schema, allowing git
|
|
22
|
+
commits, shell commands, and documentation to be scored and ranked on an equal basis.
|
|
23
|
+
- **Hybrid Search (BM25 + Vector)**: Combines exact keyword matching via SQLite FTS5 (BM25) with
|
|
24
|
+
semantic vector search (`sqlite-vec` via a local Ollama model) using Reciprocal Rank Fusion (RRF).
|
|
25
|
+
RRF fuses on rank position only, never on raw scores, which is what makes it safe to combine a BM25
|
|
26
|
+
cost with a vector distance on an unrelated scale. Degrades gracefully to BM25-only if Ollama is
|
|
27
|
+
offline.
|
|
28
|
+
- **Ranked, Budgeted Retrieval**: Scores candidates using
|
|
29
|
+
`score = relevance × signal^a × recency^b`, then packs nodes into a caller-specified token budget.
|
|
30
|
+
Each factor is floored into `[floor, 1]` rather than `[0, 1]`, so no single low factor can zero out
|
|
31
|
+
a strong match. The exponents `a` and `b` are derived, not tuned: `relevance` is the only
|
|
32
|
+
query-derived factor, so each query-independent prior is raised to the power that caps its entire
|
|
33
|
+
range at overturning a 2× relevance gap (`span^exponent = 2`, giving `a ≈ 0.431`, `b ≈ 0.576`).
|
|
34
|
+
- **MCP Server Native**: Exposes `search_memory`, `sync_project`, and `get_status` as Model Context
|
|
35
|
+
Protocol (MCP) tools over stdio for Claude Desktop, Cursor, and Windsurf.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Architecture
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
git / shell / docs / transcripts
|
|
43
|
+
│
|
|
44
|
+
▼ collectors/ normalize to one MemoryNode shape
|
|
45
|
+
│
|
|
46
|
+
▼ store/ SQLite (FTS5 + sqlite-vec)
|
|
47
|
+
│
|
|
48
|
+
▼ retrieval/ RRF fuse -> rank -> pack to token budget
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Key Subsystems
|
|
52
|
+
|
|
53
|
+
1. **Git Collector**: Ingests commits, diff statistics, renames, and conventional commit signals
|
|
54
|
+
incrementally via stream iterators. Sync cursors are validated as ancestors of `HEAD` before being
|
|
55
|
+
trusted, so a rebase or amend widens the walk instead of silently skipping commits.
|
|
56
|
+
2. **Shell Collector**: Scrapes default history files (`PSReadLine`, `.bash_history`,
|
|
57
|
+
`.zsh_history`). An optional PowerShell profile hook upgrades capture to include exact timestamps,
|
|
58
|
+
working directories, and exit codes (where failed commands receive a higher structural signal).
|
|
59
|
+
3. **Docs Collector**: Indexes Markdown documentation (`.md` files) tracked by git. Line endings are
|
|
60
|
+
normalized to LF before chunking to prevent CRLF splitting failures on Windows. Scoped pruning
|
|
61
|
+
removes orphaned sections when headings are renamed or deleted, scoped by project and exact source
|
|
62
|
+
so it cannot affect git, shell, or conversation nodes.
|
|
63
|
+
4. **Conversation Collector** (opt-in): Indexes AI assistant transcripts, redacting secrets before
|
|
64
|
+
writing to disk. Replies are chunked at heading and bold-lead boundaries rather than stored as
|
|
65
|
+
whole exchanges.
|
|
66
|
+
|
|
67
|
+
### Storage Model
|
|
68
|
+
|
|
69
|
+
Node ids are content-addressed (`sha256(projectId + kind + naturalKey)`), so running `sync` twice
|
|
70
|
+
cannot produce duplicates and ingestion stays correct even if a cursor is lost. Project identity is
|
|
71
|
+
derived from the normalized origin URL when one exists, falling back to the absolute path, so two
|
|
72
|
+
clones of the same repository share one memory namespace.
|
|
73
|
+
|
|
74
|
+
`nodes_fts` is trigger-populated and stays consistent automatically. `nodes_vec` is not — computing
|
|
75
|
+
an embedding requires an async call to Ollama, which a synchronous SQL trigger cannot make — so it is
|
|
76
|
+
filled by an explicit pass after `sync` writes nodes, and a node whose content changes has its stale
|
|
77
|
+
embedding dropped for re-embedding.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Quickstart
|
|
82
|
+
|
|
83
|
+
### Prerequisites
|
|
84
|
+
|
|
85
|
+
- Node.js ≥ 22 — `better-sqlite3` publishes no prebuilt binary below this, so Node 20 would
|
|
86
|
+
have to compile the native addon from source (and on Windows that means a full Visual Studio
|
|
87
|
+
C++ toolchain). Node 20 also reached end of life in April 2026.
|
|
88
|
+
- Git
|
|
89
|
+
- Local Ollama instance with an embedding model (optional, for vector search)
|
|
90
|
+
|
|
91
|
+
### Installation
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
git clone https://github.com/yaminbakoh4-dot/NexusMem.git
|
|
95
|
+
cd NexusMem
|
|
96
|
+
npm install
|
|
97
|
+
npm run build
|
|
98
|
+
npm link
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`npm link` puts `nexusmem` on your `PATH`, so it runs against any repository on your machine.
|
|
102
|
+
|
|
103
|
+
### Basic Usage
|
|
104
|
+
|
|
105
|
+
Run from any git repository:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
nexusmem init
|
|
109
|
+
nexusmem sync
|
|
110
|
+
nexusmem query "why does the retry logic exist"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Optional: High-Precision Shell Hook
|
|
114
|
+
|
|
115
|
+
To capture exact working directory and exit status for shell history:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
nexusmem hook install
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
This wraps your existing PowerShell prompt rather than replacing it, is idempotent, and is undone
|
|
122
|
+
cleanly by `nexusmem hook remove`.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## MCP Server Configuration
|
|
127
|
+
|
|
128
|
+
Add the following to your MCP client configuration file:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"mcpServers": {
|
|
133
|
+
"nexusmem": {
|
|
134
|
+
"command": "nexusmem",
|
|
135
|
+
"args": ["mcp"]
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Available tools:
|
|
142
|
+
|
|
143
|
+
| Tool | Description |
|
|
144
|
+
| --- | --- |
|
|
145
|
+
| `search_memory` | Searches and ranks memory for a given prompt within a token budget. |
|
|
146
|
+
| `sync_project` | Runs ingestion and updates embeddings for the specified repository root. |
|
|
147
|
+
| `get_status` | Returns current ingestion counts and database state per source. |
|
|
148
|
+
|
|
149
|
+
Each tool takes an explicit `projectRoot`, because MCP tool calls carry no implicit shell working
|
|
150
|
+
directory. `sync_project` runs `init` first automatically if the repository has not been set up yet.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Benchmarks & Evaluation
|
|
155
|
+
|
|
156
|
+
NexusMem distinguishes between **packer efficiency** (internal packing performance against candidate
|
|
157
|
+
sets) and **end-to-end token savings** (real-world savings on the context bill). The two are not
|
|
158
|
+
interchangeable, and quoting the first as if it were the second is the specific overclaim this
|
|
159
|
+
section exists to prevent.
|
|
160
|
+
|
|
161
|
+
### Packer Efficiency
|
|
162
|
+
|
|
163
|
+
Measures how effectively the ranking packer drops low-scoring candidate nodes relative to the raw
|
|
164
|
+
candidate body sum within a strict token budget:
|
|
165
|
+
|
|
166
|
+
| Scenario | Candidate Corpus | Result |
|
|
167
|
+
| --- | --- | --- |
|
|
168
|
+
| Fixture repo (tight budget, 3 matches, 1 dropped) | 23 commits | 25% |
|
|
169
|
+
| Fixture repo (generous budget, 6 matches, all kept) | 23 commits | -15% (overhead exceeds trim) |
|
|
170
|
+
| Core repo design evaluation | 515 nodes | 81% – 84% |
|
|
171
|
+
|
|
172
|
+
Efficiency is derived from excluding irrelevant low-scoring candidates entirely, not from text
|
|
173
|
+
summarization. It increases with corpus size and goes negative on a tiny one, where fixed per-node
|
|
174
|
+
formatting overhead outweighs the little there is to trim.
|
|
175
|
+
|
|
176
|
+
The baseline it divides by is hypothetical: without NexusMem those candidate bodies would never have
|
|
177
|
+
entered the context window at all. This figure is useful for tuning the ranker, not as a claim about
|
|
178
|
+
a session's token bill.
|
|
179
|
+
|
|
180
|
+
### End-to-End Token Savings
|
|
181
|
+
|
|
182
|
+
Measures packed context size against reading the equivalent full source files into context.
|
|
183
|
+
|
|
184
|
+
**Measured result: ~40%** on design queries evaluated against this codebase (reading `README.md` +
|
|
185
|
+
`docs/phase-2-spec.md` in full, ~32k chars ≈ 8–9k tokens, versus retrieving relevant packed context).
|
|
186
|
+
Hand-tallied from one real session, not instrumented — treat it as an order-of-magnitude figure.
|
|
187
|
+
|
|
188
|
+
**The long-term >70% target is not met at this scale, and this repository cannot demonstrate it.**
|
|
189
|
+
The target describes large repositories (thousands of commits) where the win comes from omitting
|
|
190
|
+
hundreds of unrelated history items rather than shaving a handful. A benchmark against a repository
|
|
191
|
+
of that size is still outstanding.
|
|
192
|
+
|
|
193
|
+
One caveat in NexusMem's favour is not a percentage at all: the conversation turns and shell commands
|
|
194
|
+
in memory have no cheap `grep` equivalent. Without a collector recording them they are gone, not
|
|
195
|
+
merely more expensive to retrieve.
|
|
196
|
+
|
|
197
|
+
### Search Latency
|
|
198
|
+
|
|
199
|
+
Measured on this repository's corpus (~530 nodes), warm, p50 over 10 runs:
|
|
200
|
+
|
|
201
|
+
| Operation | Latency |
|
|
202
|
+
| --- | --- |
|
|
203
|
+
| BM25-only retrieval pipeline (FTS5) | ~1.1 ms |
|
|
204
|
+
| Vector search (`sqlite-vec` KNN) | ~3.2 ms |
|
|
205
|
+
| RRF fuse + rank + pack | ~0.6 ms |
|
|
206
|
+
| Query embedding (local Ollama call) | ~55–77 ms |
|
|
207
|
+
| End-to-end hybrid retrieval | ~56 ms |
|
|
208
|
+
|
|
209
|
+
All SQLite-side work totals roughly 5 ms. The end-to-end figure is dominated by the local embedding
|
|
210
|
+
call, which is the only meaningful latency target on this path.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Command Reference
|
|
215
|
+
|
|
216
|
+
| Command | Description |
|
|
217
|
+
| --- | --- |
|
|
218
|
+
| `nexusmem init` | Initializes `.nexusmem/` directory and SQLite schema. |
|
|
219
|
+
| `nexusmem sync` | Ingests new events (git, shell, docs; `--conversation` for transcripts). |
|
|
220
|
+
| `nexusmem status` | Prints memory counts per source and database status. |
|
|
221
|
+
| `nexusmem query <text>` | Executes hybrid search, ranks, and packs context to stdout. |
|
|
222
|
+
| `nexusmem scan-git` | Dry-run preview of git nodes and signal scores without writing to DB. |
|
|
223
|
+
| `nexusmem scan-shell` | Dry-run preview of shell history nodes without writing to DB. |
|
|
224
|
+
| `nexusmem scan-docs` | Dry-run preview of doc section nodes without writing to DB. |
|
|
225
|
+
| `nexusmem scan-conversation` | Dry-run preview of conversation nodes without writing to DB. |
|
|
226
|
+
| `nexusmem hook install` | Installs PowerShell profile wrapper for high-precision shell logs. |
|
|
227
|
+
| `nexusmem hook remove` | Removes the PowerShell profile wrapper. |
|
|
228
|
+
| `nexusmem hook status` | Reports whether the hook is installed. |
|
|
229
|
+
| `nexusmem mcp` | Starts the MCP stdio server. |
|
|
230
|
+
|
|
231
|
+
Every command accepts `-C, --cwd <path>` to target a repository other than the current directory.
|
|
232
|
+
Useful `sync` flags: `--conversation` opts the conversation source in for one run without persisting
|
|
233
|
+
it to config; `--no-embed` skips the vector-embedding pass; `--rebuild` drops the project's nodes and
|
|
234
|
+
re-ingests from scratch.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## On-Disk Layout
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
<repo>/.nexusmem/
|
|
242
|
+
.gitignore '*' — the workspace ignores itself, so init never edits a file it does not own
|
|
243
|
+
config.json validated on read; a corrupt config fails loudly, never silently
|
|
244
|
+
memory.db SQLite (WAL): nodes, node_files, nodes_fts, nodes_vec, sync_state
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Deleting `.nexusmem/` loses nothing that `sync` cannot rebuild.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Technical Limitations & Edge Cases
|
|
252
|
+
|
|
253
|
+
- **Windows Line Endings**: Markdown files are normalized from CRLF to LF prior to chunking.
|
|
254
|
+
Un-normalized CRLF causes the paragraph splitter (`\n{2,}`) to never fire — `\r\n\r\n` contains no
|
|
255
|
+
two consecutive `\n` — collapsing an entire file into a few coarse, heading-less blocks.
|
|
256
|
+
- **Git Rebase / Amend**: Rewriting git history leaves orphaned nodes for unreachable commits. These
|
|
257
|
+
are real events, so they are not wrong, but a targeted prune does not exist yet;
|
|
258
|
+
`sync --rebuild` forces a clean re-scan if required.
|
|
259
|
+
- **Non-Segmented Languages**: FTS5 `unicode61` tokenization splits on whitespace. Languages without
|
|
260
|
+
space boundaries (Thai, Japanese, Chinese) rely on the vector search pass for recall.
|
|
261
|
+
- **Unscoped Shell History**: Scraped shell history files without the PowerShell hook lack directory
|
|
262
|
+
context and are attributed to whichever repository `sync` was executed from. Bounded to the tail
|
|
263
|
+
window, and an approximation rather than a guarantee.
|
|
264
|
+
- **PSReadLine Multi-Line Entries**: A function typed across several lines at the prompt is read as
|
|
265
|
+
separate single-line commands, not reconstructed.
|
|
266
|
+
- **Scrape-Fallback Id Drift**: Position-based ids for the scrape fallbacks can drift if the
|
|
267
|
+
underlying history file is trimmed from the front between syncs. Installing the hook fixes this.
|
|
268
|
+
- **Conversation Retrieval Precision**: Chunking replies at heading boundaries improved precision on
|
|
269
|
+
long replies but has not been evaluated systematically.
|
|
270
|
+
- **Embedding Batch Size**: The embedding pass processes a bounded batch per `sync`; a large corpus
|
|
271
|
+
needs several runs to embed fully.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Roadmap
|
|
276
|
+
|
|
277
|
+
Phases 1 and 2 are shipped. Phase 3 is in progress.
|
|
278
|
+
|
|
279
|
+
### Phase 1 — Core ingestion and retrieval
|
|
280
|
+
|
|
281
|
+
- [x] `init` / `sync` / `query` command surface
|
|
282
|
+
- [x] Git collector (commits, diff stats, renames, conventional-commit signal)
|
|
283
|
+
- [x] Shell collector (PSReadLine, bash, zsh) with optional PowerShell hook
|
|
284
|
+
- [x] SQLite storage with FTS5/BM25
|
|
285
|
+
- [x] Token-budgeted context packing
|
|
286
|
+
|
|
287
|
+
### Phase 2 — Hybrid retrieval and MCP
|
|
288
|
+
|
|
289
|
+
- [x] `sqlite-vec` embeddings via a local Ollama model
|
|
290
|
+
- [x] Reciprocal Rank Fusion over BM25 + vector results
|
|
291
|
+
- [x] MCP server (stdio): `search_memory`, `sync_project`, `get_status`
|
|
292
|
+
- [x] Conversation collector (opt-in), chunked below whole-exchange granularity
|
|
293
|
+
|
|
294
|
+
### Phase 3 — In progress
|
|
295
|
+
|
|
296
|
+
- [x] Docs collector for tracked Markdown files
|
|
297
|
+
- [x] Scoped pruning of orphaned doc sections on re-sync
|
|
298
|
+
- [ ] Diff-level nodes (currently commit-level only)
|
|
299
|
+
- [ ] Session summarization via a local SLM
|
|
300
|
+
- [ ] Cross-project recall (queries are scoped to one project today)
|
|
301
|
+
- [ ] Batch the embedding pass (capped at 200 nodes per `sync`)
|
|
302
|
+
|
|
303
|
+
### Before a tagged release
|
|
304
|
+
|
|
305
|
+
- [ ] CI
|
|
306
|
+
- [ ] Retry on transient process-spawn failures on Windows
|
|
307
|
+
- [ ] Benchmark against a large repository — the >70% end-to-end target is
|
|
308
|
+
unproven at this corpus size, where ~40% is what was measured
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Development & Testing
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
npm install
|
|
316
|
+
npm run typecheck
|
|
317
|
+
npm test
|
|
318
|
+
npm run build
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
`scan-git`, `scan-shell`, `scan-docs` and `scan-conversation` write nothing — they print the
|
|
322
|
+
`MemoryNode`s ingestion would create, with their signal scores, which is the intended way to tune
|
|
323
|
+
scoring against a real repository before committing to a schema change. Add `--json` to pipe the
|
|
324
|
+
output elsewhere.
|
|
325
|
+
|
|
326
|
+
There is no CI configured yet.
|
|
327
|
+
|
|
328
|
+
## Development Note
|
|
329
|
+
This project was initially prototyped and built using **Claude Code** to test the viability of local context memory engines for AI agents.
|
|
330
|
+
|
|
331
|
+
While the codebase was generated through AI-assisted workflows, the architecture, system design, and product specifications were directed by human requirements. Contributions, code audits, and refactoring from the community are extremely welcome!
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## License
|
|
336
|
+
|
|
337
|
+
MIT
|