nexusmem 0.3.0 → 0.3.1

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/CHANGELOG.md CHANGED
@@ -1,182 +1,218 @@
1
- # Changelog
2
-
3
- Notable changes per published version. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
4
- this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
-
6
- Tags were added retroactively on 2026-08-11 and point at the exact commits the npm tarballs were
7
- built from, matched by publish timestamp: `v0.1.0` → `67a4776`, `v0.1.1` → `809e62c`,
8
- `v0.1.2` → `b22a3b0`.
9
-
10
- ## [Unreleased]
11
-
12
- ## [0.3.0] — 2026-08-13
13
-
14
- **Upgrade note.** The first `sync` after upgrading drops every stored embedding and rebuilds it.
15
- This is not optional and it is not a bug: embeddings now come from Ollama's `/api/embed`, which
16
- returns L2-normalised vectors, while the previous `/api/embeddings` did not measured norms of 1.0
17
- and 20.7 for the same input. `nodes_vec` ranks by Euclidean distance and records no per-row
18
- provenance, so a corpus holding both would separate by scale rather than by meaning. `sync` says
19
- what it dropped, nodes are untouched, and BM25 keeps working while the rebuild runs. On this repo
20
- the rebuild was 833 nodes in one pass, inside a 9-second sync.
21
-
22
- ### Added
23
-
24
- - **Session summaries via a local model** (`sources.session`, opt-in, off by default). Each
25
- finished session becomes one distilled `session_summary` node decisions and their reasons
26
- alongside the raw exchanges. Runs a local Ollama chat model (`qwen2.5:3b` by default); nothing is
27
- downloaded automatically and no transcript leaves the machine. New `scan-session` command, with
28
- `--dry-run` to print the exact prompt a session would produce without calling the model.
29
- Bounded three ways: a session must be quiet for `settleMinutes` (default 30) before it is
30
- eligible, the prompt is hashed so an unchanged session never reaches the model again, and
31
- `maxSessions` caps how many are summarized per sync. Every exchange is redacted before the model
32
- sees it, and the model's own output is redacted again before it is stored.
33
- - **`sync --embed-limit <n>`** to cap the embedding pass, for when draining the whole backlog is not
34
- wanted.
35
-
36
- ### Changed
37
-
38
- - **The embedding pass drains the backlog in one `sync`** instead of stopping after 200 nodes, and
39
- sends texts to Ollama in batches of 32 measured at 4.21x the throughput of one call per node
40
- (20.3ms 4.8ms per node, over 96 real nodes from this repo). Leaving it uncapped is safe because
41
- paging walks rowids monotonically, so a node the provider failed on is passed over rather than
42
- retried forever, and because three consecutive dead requests end the pass: an Ollama that is not
43
- running now costs three requests rather than one timeout per node.
44
- - **Embeddings carry a provider identity.** Changing the embedding model, or upgrading from a
45
- release that recorded no identity, drops the vectors and re-embeds rather than ranking across a
46
- mixture.
47
- - **Ranking priors now share one budget instead of getting one each.** `signal` and `recency` are
48
- query-independent, and each was separately capped at overturning a 2× relevance gap. The score
49
- multiplies them, so together they could overturn 4× — which is not a corner case but a description
50
- of every commit made during an active working day, fresh and high-signal at once. A query about the
51
- PowerShell hook returned two unrelated same-day `fix:` commits at ranks 3 and 4 while the section
52
- that answered it sat at rank 6. The budget is now the bound on the priors *jointly*, split
53
- between them (`signal^0.215 × recency^0.288`, down from `^0.431` and `^0.576`), and a third prior
54
- would re-divide the same budget rather than enlarge it. Measured on four real queries against this
55
- repository's memory: the answering section rose in three of them the rationale for "why BM25
56
- before vector search" went from rank 4 to rank 1 and no query's correct top hit was displaced.
57
-
58
- ### Known limitation
59
-
60
- - Session-summary *titles* depend on the model following a fixed output format, and a 3B model often
61
- does not. Measured over 14 real sessions, roughly a third came back usable; the rest were
62
- conversational preambles, stray bullets, or a bare "Summary of the Session". Those are rejected
63
- and the title falls back to the first line of the question that opened the session always
64
- specific, not always elegant. Compliance was worst on long sessions and on transcripts not in
65
- English. `sources.session.model` takes a larger model if it matters.
66
-
67
- ## [0.2.0] 2026-08-12
68
-
69
- **Upgrade note.** Both new sources are on by default, so the first `sync` after upgrading an
70
- existing project ingests the patches of its 200 most recent commits and starts recording the
71
- repository in `~/.nexusmem/projects.json`. Set `sources.diff.enabled` to `false` in
72
- `.nexusmem/config.json` if you would rather not, and `NEXUSMEM_HOME` relocates the user-scoped
73
- directory. Nothing existing is rewritten or lost.
74
-
75
- ### Added
76
-
77
- - **Diff-level nodes.** Commit patches are now indexed, one node per changed file, so a question
78
- about *what the change looked like* reaches the lines themselves rather than the commit message
79
- and a `+41/-6` summary. New `code_diff` kind, `diff` source, `scan-diff` preview command, and a
80
- `sources.diff` config block. Read by a second `git log --patch` walk with its own cursor: folding
81
- it into the existing `--numstat` walk would put patch text and numstat rows in one field, where a
82
- diff line reading `-1\t2\tfoo` is indistinguishable from a real file entry.
83
- Bounded on purpose 200 commits on a first sync, 20 files per commit, no merges (their patch
84
- exists only in a combined format this parser does not read), and binaries, lockfiles and build
85
- output skipped. Patches are redacted with the shape-matching rules only; the key/value rule that
86
- serves prose would rewrite `const apiKey = process.env.SERVICE_API_KEY` into a redaction marker.
87
- - **Cross-project recall.** `query --all-projects` (and `search_memory`'s `allProjects`) searches
88
- every repository NexusMem has been run in on this machine, tagging each result with the repository
89
- it came from. Databases stay per-repository a shared global store was rejected for giving up the
90
- property that deleting one repo's `.nexusmem/` removes that repo's memory and nothing else so a
91
- plain index at `~/.nexusmem/projects.json`, written by `init` and refreshed by `sync`, is what
92
- makes the others findable. New `projects` command lists it; `--prune` forgets entries whose
93
- database is gone. A stale or corrupt registry degrades the query, never fails it.
94
- Ranking fuses each project's list by rank (RRF) instead of comparing raw BM25 costs, which are
95
- computed against their own corpus and are not comparable across databases. The bias this leaves —
96
- every project's rank-1 hit is worth the same, so recall favours breadth is documented rather
97
- than hidden.
98
- - **Query-aware diff excerpts.** A packed summary is ~320 characters and a patch is thousands, so
99
- the packer now picks the hunk whose tokens match the query and starts the excerpt at the changed
100
- line. Found by dogfooding: "what flags are passed to every git invocation" retrieved the right
101
- file and then spent the whole summary on a class definition seventy lines above the answer.
102
- Matching splits identifiers on case and underscore boundaries, because `\bretry\b` does not match
103
- `RETRY_DELAYS_MS` and a natural-language question otherwise never meets the code it is about.
104
- - `CHANGELOG.md` now ships inside the npm tarball. npm's always-included list covers `package.json`,
105
- `README` and `LICENSE` but not the changelog, so it previously reached GitHub readers only.
106
-
107
- ### Internal
108
-
109
- - The test suite no longer writes to the developer's real `~/.nexusmem`. `sync` records the
110
- repository it ingested in the project registry, and the suite syncs temporary repositories in
111
- several places, so a green run left seven dead entries behind — found by running `nexusmem
112
- projects` after the fact, not by any test. `tests/setup.ts` now points `NEXUSMEM_HOME` at a
113
- throwaway directory for the whole suite, and one test fails if that guard is ever removed.
114
- - `npm run smoke` drives the *packaged* artifact: build, pack, install into a throwaway directory,
115
- then run the installed CLI, an end-to-end ingest/query against a fixture repository, and an
116
- `initialize` handshake over real stdio. It also audits the manifest `npm publish` would send,
117
- which is a different artifact from the tarball. Both defects that ever reached npm users passed a
118
- green unit suite first; each is now pinned by a check verified to fail when the defect is
119
- reintroduced. Runs in CI on Linux and Windows as its own job.
120
-
121
- ## [0.1.2] 2026-08-10
122
-
123
- ### Fixed
124
-
125
- - `nexusmem --version` printed `0.1.0` on 0.1.1. The version string in `src/cli/index.ts` was a
126
- literal separate from `package.json`, and the 0.1.1 bump only touched the latter. `src/mcp/server.ts`
127
- had the same problem in its `McpServer` constructor, so an MCP client's `initialize` handshake
128
- would have reported the same stale version. Both now read the real version through
129
- `readOwnVersion()` in `src/core/version.ts`, which resolves `package.json` via `import.meta.url`.
130
- Found by running the published package end to end rather than trusting `npm publish --dry-run`
131
- and the registry API, neither of which executes a `--version` flag.
132
-
133
- The ingestion and retrieval pipeline was never affected — only the two places that report a version
134
- independently of running a command.
135
-
136
- ## [0.1.1] 2026-08-10
137
-
138
- ### Changed
139
-
140
- - README rewritten for someone deciding whether to read the source: what it does, how retrieval
141
- scores, what it costs, and where it breaks. `README.md` ships inside the package, so this is a
142
- real change to what npm delivers — but no code changed between 0.1.0 and 0.1.1.
143
- - Documented the ranking flaw the tool found in itself, and the fact that the conversation source
144
- in the sample `status` output is opt-in rather than default.
145
- - Dropped `&&` from the quickstart, which Windows PowerShell 5.1 cannot parse.
146
-
147
- ## [0.1.0]2026-08-10
148
-
149
- First public release.
150
-
151
- ### Added
152
-
153
- - **Collectors.** Git history (commit metadata and diff stats, not diff bodies), shell commands with
154
- exit codes via an opt-in PowerShell hook, tracked markdown docs via `git ls-files -- '*.md'`, and
155
- opt-in assistant transcripts.
156
- - **Hybrid retrieval.** SQLite FTS5 BM25 and `sqlite-vec` KNN over 768-dim embeddings, fused with
157
- reciprocal rank fusion, then ranked by relevance against signal and recency priors and packed into
158
- an explicit token budget.
159
- - **MCP server** over stdio (`nexusmem mcp`) exposing `search_memory`, `sync_project` and
160
- `get_status`, for Claude Desktop, Cursor, Windsurf and other MCP clients.
161
- - **CLI**: `init`, `sync`, `query`, `status`, `mcp`, and `hook install|remove|status`, plus four
162
- dry-run previews `scan-git`, `scan-shell`, `scan-docs`, `scan-conversation` that write nothing
163
- and print the nodes ingestion would create with their signal scores.
164
- - Content-addressed node ids (`sha256(projectId + kind + naturalKey)`), so `sync` is idempotent and
165
- two clones of one repository share a memory namespace.
166
- - Everything stays on the machine: one SQLite database in WAL mode under `<repo>/.nexusmem/`.
167
-
168
- ### Notes
169
-
170
- - Requires Node **>= 22**. `better-sqlite3` 12.11.1 publishes no prebuilt binary for Node 20 — its
171
- prebuilds start at ABI 127 — so a lower floor would have been a promise the package could not keep.
172
- Do not lower it without checking upstream prebuilds first.
173
- - Not done at this release: diff bodies are not indexed, queries are scoped to a single project,
174
- there is no local-model summarization pass, and the conversation collector has never been audited
175
- for the stale-node bug that was found and fixed in the docs collector.
176
-
177
- [Unreleased]: https://github.com/yaminbakoh4-dot/NexusMem/compare/v0.3.0...HEAD
178
- [0.3.0]: https://github.com/yaminbakoh4-dot/NexusMem/compare/v0.2.0...v0.3.0
179
- [0.2.0]: https://github.com/yaminbakoh4-dot/NexusMem/compare/v0.1.2...v0.2.0
180
- [0.1.2]: https://github.com/yaminbakoh4-dot/NexusMem/compare/v0.1.1...v0.1.2
181
- [0.1.1]: https://github.com/yaminbakoh4-dot/NexusMem/compare/v0.1.0...v0.1.1
182
- [0.1.0]: https://github.com/yaminbakoh4-dot/NexusMem/releases/tag/v0.1.0
1
+ # Changelog
2
+
3
+ Notable changes per published version. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
4
+ this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+
6
+ Tags were added retroactively on 2026-08-11 and point at the exact commits the npm tarballs were
7
+ built from, matched by publish timestamp: `v0.1.0` → `67a4776`, `v0.1.1` → `809e62c`,
8
+ `v0.1.2` → `b22a3b0`.
9
+
10
+ ## [Unreleased]
11
+
12
+ ## [0.3.1] — 2026-08-15
13
+
14
+ ### Added
15
+
16
+ - **`scripts/benchmark.ts` (`npm run bench`) a reproducible end-to-end token-saving benchmark.**
17
+ Compares `packed.tokensUsed` against two baselines (full-file-read and `git log -p`) for the same
18
+ files a query's packed nodes touch, over a query set derived mechanically from the corpus itself
19
+ rather than hand-picked. Used to measure the README's `## What it costs you` numbers against both
20
+ this repo (62 commits) and a 9,567-commit external corpus (`vitejs/vite`) — see README for the
21
+ numbers and their methodology caveats.
22
+
23
+ ### Fixed
24
+
25
+ - **A generic 2-3 letter local model title, "id" as a search token, and one node's chunks flooding a
26
+ result set three ranking/retrieval edge cases found by dogfooding, each verified with a red test
27
+ before the fix.**
28
+ - Session-summary titles: the local model sometimes wrote a role-framing line ("Role: Lead Systems
29
+ Engineer...") instead of a summary, in Thai and English alike. The existing generic-title filter
30
+ didn't catch either language, since it only matched English words like "summary"/"update".
31
+ - Search: the token `id` alone was prefix-matching unrelated shell commands like
32
+ `winget install --id ...`, because every query token was OR-ed with no floor and no stopword
33
+ list, and bm25 gives a rare-but-generic token an inflated score purely from scarcity.
34
+ - Packing: `conversation_turn` and `doc_section` both chunk one reply or file into several nodes
35
+ sharing the same timestamp; up to 2 may now appear in one packed result, down from unlimited.
36
+
37
+ - **A repo's memory could silently split in two if its git remote URL ever changed** (a GitHub
38
+ account rename, an org transfer). Project identity is derived from the remote URL on purpose so
39
+ the same repo re-cloned to a new path or machine keeps sharing memory but a changed URL on the
40
+ *same* path minted a new id and stranded every node synced under the old one, invisible to
41
+ `status`/`query`/MCP from then on. `sync` now detects a prior id already recorded in the repo's own
42
+ database and reconciles it forward: recomputable node kinds (session summaries, hook-sourced shell
43
+ history) are migrated under their correct new id and deduplicated against anything already synced;
44
+ conversation turns, whose identity can't be recomputed, are reassigned in place. Git commits,
45
+ diffs, and doc sections are left alone a normal sync already re-derives them completely, so
46
+ there is nothing to migrate.
47
+
48
+ ## [0.3.0] 2026-08-13
49
+
50
+ **Upgrade note.** The first `sync` after upgrading drops every stored embedding and rebuilds it.
51
+ This is not optional and it is not a bug: embeddings now come from Ollama's `/api/embed`, which
52
+ returns L2-normalised vectors, while the previous `/api/embeddings` did not measured norms of 1.0
53
+ and 20.7 for the same input. `nodes_vec` ranks by Euclidean distance and records no per-row
54
+ provenance, so a corpus holding both would separate by scale rather than by meaning. `sync` says
55
+ what it dropped, nodes are untouched, and BM25 keeps working while the rebuild runs. On this repo
56
+ the rebuild was 833 nodes in one pass, inside a 9-second sync.
57
+
58
+ ### Added
59
+
60
+ - **Session summaries via a local model** (`sources.session`, opt-in, off by default). Each
61
+ finished session becomes one distilled `session_summary` node decisions and their reasons
62
+ alongside the raw exchanges. Runs a local Ollama chat model (`qwen2.5:3b` by default); nothing is
63
+ downloaded automatically and no transcript leaves the machine. New `scan-session` command, with
64
+ `--dry-run` to print the exact prompt a session would produce without calling the model.
65
+ Bounded three ways: a session must be quiet for `settleMinutes` (default 30) before it is
66
+ eligible, the prompt is hashed so an unchanged session never reaches the model again, and
67
+ `maxSessions` caps how many are summarized per sync. Every exchange is redacted before the model
68
+ sees it, and the model's own output is redacted again before it is stored.
69
+ - **`sync --embed-limit <n>`** to cap the embedding pass, for when draining the whole backlog is not
70
+ wanted.
71
+
72
+ ### Changed
73
+
74
+ - **The embedding pass drains the backlog in one `sync`** instead of stopping after 200 nodes, and
75
+ sends texts to Ollama in batches of 32 — measured at 4.21x the throughput of one call per node
76
+ (20.3ms → 4.8ms per node, over 96 real nodes from this repo). Leaving it uncapped is safe because
77
+ paging walks rowids monotonically, so a node the provider failed on is passed over rather than
78
+ retried forever, and because three consecutive dead requests end the pass: an Ollama that is not
79
+ running now costs three requests rather than one timeout per node.
80
+ - **Embeddings carry a provider identity.** Changing the embedding model, or upgrading from a
81
+ release that recorded no identity, drops the vectors and re-embeds rather than ranking across a
82
+ mixture.
83
+ - **Ranking priors now share one budget instead of getting one each.** `signal` and `recency` are
84
+ query-independent, and each was separately capped at overturning a relevance gap. The score
85
+ multiplies them, so together they could overturn which is not a corner case but a description
86
+ of every commit made during an active working day, fresh and high-signal at once. A query about the
87
+ PowerShell hook returned two unrelated same-day `fix:` commits at ranks 3 and 4 while the section
88
+ that answered it sat at rank 6. The budget is now the bound on the priors *jointly*, split
89
+ between them (`signal^0.215 × recency^0.288`, down from `^0.431` and `^0.576`), and a third prior
90
+ would re-divide the same budget rather than enlarge it. Measured on four real queries against this
91
+ repository's memory: the answering section rose in three of them the rationale for "why BM25
92
+ before vector search" went from rank 4 to rank 1 and no query's correct top hit was displaced.
93
+
94
+ ### Known limitation
95
+
96
+ - Session-summary *titles* depend on the model following a fixed output format, and a 3B model often
97
+ does not. Measured over 14 real sessions, roughly a third came back usable; the rest were
98
+ conversational preambles, stray bullets, or a bare "Summary of the Session". Those are rejected
99
+ and the title falls back to the first line of the question that opened the session always
100
+ specific, not always elegant. Compliance was worst on long sessions and on transcripts not in
101
+ English. `sources.session.model` takes a larger model if it matters.
102
+
103
+ ## [0.2.0] 2026-08-12
104
+
105
+ **Upgrade note.** Both new sources are on by default, so the first `sync` after upgrading an
106
+ existing project ingests the patches of its 200 most recent commits and starts recording the
107
+ repository in `~/.nexusmem/projects.json`. Set `sources.diff.enabled` to `false` in
108
+ `.nexusmem/config.json` if you would rather not, and `NEXUSMEM_HOME` relocates the user-scoped
109
+ directory. Nothing existing is rewritten or lost.
110
+
111
+ ### Added
112
+
113
+ - **Diff-level nodes.** Commit patches are now indexed, one node per changed file, so a question
114
+ about *what the change looked like* reaches the lines themselves rather than the commit message
115
+ and a `+41/-6` summary. New `code_diff` kind, `diff` source, `scan-diff` preview command, and a
116
+ `sources.diff` config block. Read by a second `git log --patch` walk with its own cursor: folding
117
+ it into the existing `--numstat` walk would put patch text and numstat rows in one field, where a
118
+ diff line reading `-1\t2\tfoo` is indistinguishable from a real file entry.
119
+ Bounded on purpose 200 commits on a first sync, 20 files per commit, no merges (their patch
120
+ exists only in a combined format this parser does not read), and binaries, lockfiles and build
121
+ output skipped. Patches are redacted with the shape-matching rules only; the key/value rule that
122
+ serves prose would rewrite `const apiKey = process.env.SERVICE_API_KEY` into a redaction marker.
123
+ - **Cross-project recall.** `query --all-projects` (and `search_memory`'s `allProjects`) searches
124
+ every repository NexusMem has been run in on this machine, tagging each result with the repository
125
+ it came from. Databases stay per-repository a shared global store was rejected for giving up the
126
+ property that deleting one repo's `.nexusmem/` removes that repo's memory and nothing else — so a
127
+ plain index at `~/.nexusmem/projects.json`, written by `init` and refreshed by `sync`, is what
128
+ makes the others findable. New `projects` command lists it; `--prune` forgets entries whose
129
+ database is gone. A stale or corrupt registry degrades the query, never fails it.
130
+ Ranking fuses each project's list by rank (RRF) instead of comparing raw BM25 costs, which are
131
+ computed against their own corpus and are not comparable across databases. The bias this leaves
132
+ every project's rank-1 hit is worth the same, so recall favours breadth — is documented rather
133
+ than hidden.
134
+ - **Query-aware diff excerpts.** A packed summary is ~320 characters and a patch is thousands, so
135
+ the packer now picks the hunk whose tokens match the query and starts the excerpt at the changed
136
+ line. Found by dogfooding: "what flags are passed to every git invocation" retrieved the right
137
+ file and then spent the whole summary on a class definition seventy lines above the answer.
138
+ Matching splits identifiers on case and underscore boundaries, because `\bretry\b` does not match
139
+ `RETRY_DELAYS_MS` and a natural-language question otherwise never meets the code it is about.
140
+ - `CHANGELOG.md` now ships inside the npm tarball. npm's always-included list covers `package.json`,
141
+ `README` and `LICENSE` but not the changelog, so it previously reached GitHub readers only.
142
+
143
+ ### Internal
144
+
145
+ - The test suite no longer writes to the developer's real `~/.nexusmem`. `sync` records the
146
+ repository it ingested in the project registry, and the suite syncs temporary repositories in
147
+ several places, so a green run left seven dead entries behind found by running `nexusmem
148
+ projects` after the fact, not by any test. `tests/setup.ts` now points `NEXUSMEM_HOME` at a
149
+ throwaway directory for the whole suite, and one test fails if that guard is ever removed.
150
+ - `npm run smoke` drives the *packaged* artifact: build, pack, install into a throwaway directory,
151
+ then run the installed CLI, an end-to-end ingest/query against a fixture repository, and an
152
+ `initialize` handshake over real stdio. It also audits the manifest `npm publish` would send,
153
+ which is a different artifact from the tarball. Both defects that ever reached npm users passed a
154
+ green unit suite first; each is now pinned by a check verified to fail when the defect is
155
+ reintroduced. Runs in CI on Linux and Windows as its own job.
156
+
157
+ ## [0.1.2] 2026-08-10
158
+
159
+ ### Fixed
160
+
161
+ - `nexusmem --version` printed `0.1.0` on 0.1.1. The version string in `src/cli/index.ts` was a
162
+ literal separate from `package.json`, and the 0.1.1 bump only touched the latter. `src/mcp/server.ts`
163
+ had the same problem in its `McpServer` constructor, so an MCP client's `initialize` handshake
164
+ would have reported the same stale version. Both now read the real version through
165
+ `readOwnVersion()` in `src/core/version.ts`, which resolves `package.json` via `import.meta.url`.
166
+ Found by running the published package end to end rather than trusting `npm publish --dry-run`
167
+ and the registry API, neither of which executes a `--version` flag.
168
+
169
+ The ingestion and retrieval pipeline was never affected — only the two places that report a version
170
+ independently of running a command.
171
+
172
+ ## [0.1.1] 2026-08-10
173
+
174
+ ### Changed
175
+
176
+ - README rewritten for someone deciding whether to read the source: what it does, how retrieval
177
+ scores, what it costs, and where it breaks. `README.md` ships inside the package, so this is a
178
+ real change to what npm delivers — but no code changed between 0.1.0 and 0.1.1.
179
+ - Documented the ranking flaw the tool found in itself, and the fact that the conversation source
180
+ in the sample `status` output is opt-in rather than default.
181
+ - Dropped `&&` from the quickstart, which Windows PowerShell 5.1 cannot parse.
182
+
183
+ ## [0.1.0] — 2026-08-10
184
+
185
+ First public release.
186
+
187
+ ### Added
188
+
189
+ - **Collectors.** Git history (commit metadata and diff stats, not diff bodies), shell commands with
190
+ exit codes via an opt-in PowerShell hook, tracked markdown docs via `git ls-files -- '*.md'`, and
191
+ opt-in assistant transcripts.
192
+ - **Hybrid retrieval.** SQLite FTS5 BM25 and `sqlite-vec` KNN over 768-dim embeddings, fused with
193
+ reciprocal rank fusion, then ranked by relevance against signal and recency priors and packed into
194
+ an explicit token budget.
195
+ - **MCP server** over stdio (`nexusmem mcp`) exposing `search_memory`, `sync_project` and
196
+ `get_status`, for Claude Desktop, Cursor, Windsurf and other MCP clients.
197
+ - **CLI**: `init`, `sync`, `query`, `status`, `mcp`, and `hook install|remove|status`, plus four
198
+ dry-run previews — `scan-git`, `scan-shell`, `scan-docs`, `scan-conversation` — that write nothing
199
+ and print the nodes ingestion would create with their signal scores.
200
+ - Content-addressed node ids (`sha256(projectId + kind + naturalKey)`), so `sync` is idempotent and
201
+ two clones of one repository share a memory namespace.
202
+ - Everything stays on the machine: one SQLite database in WAL mode under `<repo>/.nexusmem/`.
203
+
204
+ ### Notes
205
+
206
+ - Requires Node **>= 22**. `better-sqlite3` 12.11.1 publishes no prebuilt binary for Node 20 — its
207
+ prebuilds start at ABI 127 — so a lower floor would have been a promise the package could not keep.
208
+ Do not lower it without checking upstream prebuilds first.
209
+ - Not done at this release: diff bodies are not indexed, queries are scoped to a single project,
210
+ there is no local-model summarization pass, and the conversation collector has never been audited
211
+ for the stale-node bug that was found and fixed in the docs collector.
212
+
213
+ [Unreleased]: https://github.com/yaminbkk/NexusMem/compare/v0.3.0...HEAD
214
+ [0.3.0]: https://github.com/yaminbkk/NexusMem/compare/v0.2.0...v0.3.0
215
+ [0.2.0]: https://github.com/yaminbkk/NexusMem/compare/v0.1.2...v0.2.0
216
+ [0.1.2]: https://github.com/yaminbkk/NexusMem/compare/v0.1.1...v0.1.2
217
+ [0.1.1]: https://github.com/yaminbkk/NexusMem/compare/v0.1.0...v0.1.1
218
+ [0.1.0]: https://github.com/yaminbkk/NexusMem/releases/tag/v0.1.0
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # NexusMem
2
2
 
3
+ [![CI](https://github.com/yaminbkk/NexusMem/actions/workflows/ci.yml/badge.svg)](https://github.com/yaminbkk/NexusMem/actions/workflows/ci.yml)
3
4
  [![npm](https://img.shields.io/npm/v/nexusmem)](https://www.npmjs.com/package/nexusmem)
5
+ [![npm downloads](https://img.shields.io/npm/dm/nexusmem)](https://www.npmjs.com/package/nexusmem)
4
6
  [![License: MIT](https://img.shields.io/badge/license-MIT-informational)](LICENSE)
5
7
  ![Node](https://img.shields.io/badge/node-%3E%3D22-brightgreen)
6
8
 
@@ -70,6 +72,21 @@ Requirements: Node 22 or newer, and git. Node 20 will not work, because `better-
70
72
  prebuilt binary for it and Node 20 went end-of-life in April 2026. Ollama is optional and only
71
73
  affects semantic search (see below).
72
74
 
75
+ ## Optional: exact shell capture
76
+
77
+ Scraped history files (PSReadLine, `.bash_history`, `.zsh_history`) give you command text and not
78
+ much else. The hook gives you working directory, exit code and a real timestamp:
79
+
80
+ ```bash
81
+ nexusmem hook install
82
+ ```
83
+
84
+ It wraps your existing PowerShell prompt rather than replacing it, is idempotent, and
85
+ `nexusmem hook remove` undoes it cleanly.
86
+
87
+ Exit codes are what make this worth installing. A failed command is a stronger signal than a
88
+ successful one, and without the hook there is no way to tell them apart.
89
+
73
90
  ## How retrieval works
74
91
 
75
92
  Every source normalizes to the same `MemoryNode` shape, so a commit, a shell command and a docs
@@ -155,21 +172,6 @@ Three tools over stdio: `search_memory` returns the packed context block, `sync_
155
172
  MCP tool call carries no shell working directory. `sync_project` runs `init` for you if the
156
173
  repository has not been set up.
157
174
 
158
- ## Optional: exact shell capture
159
-
160
- Scraped history files (PSReadLine, `.bash_history`, `.zsh_history`) give you command text and not
161
- much else. The hook gives you working directory, exit code and a real timestamp:
162
-
163
- ```bash
164
- nexusmem hook install
165
- ```
166
-
167
- It wraps your existing PowerShell prompt rather than replacing it, is idempotent, and
168
- `nexusmem hook remove` undoes it cleanly.
169
-
170
- Exit codes are what make this worth installing. A failed command is a stronger signal than a
171
- successful one, and without the hook there is no way to tell them apart.
172
-
173
175
  ## What it costs you
174
176
 
175
177
  Two numbers get conflated in tools like this, so they are kept apart here.
@@ -179,14 +181,40 @@ corpus it runs 81–84%. It is useful for tuning the ranker and useless as a cla
179
181
  because the baseline is hypothetical: without NexusMem those candidates were never going into your
180
182
  context window in the first place.
181
183
 
182
- **End-to-end saving** compares packed context against reading the equivalent files in full. Measured
183
- at **~40%** on design queries against this codebase, hand-tallied from one real session rather than
184
- instrumented. Treat it as an order of magnitude.
185
-
186
- The long-term target is >70%, and this repository cannot demonstrate it. That figure describes repos
187
- with thousands of commits, where the win comes from omitting hundreds of unrelated items rather than
188
- shaving a handful. A benchmark at that size is still outstanding, and until it exists the honest
189
- number is 40%.
184
+ **End-to-end saving** compares the packed context NexusMem actually sends against reading, in full,
185
+ the same files its own ranking identified as relevant to the query. Measured with
186
+ [`scripts/benchmark.ts`](scripts/benchmark.ts) (`npm run bench`), which anyone who clones this repo
187
+ and points it at a synced corpus can re-run from scratch:
188
+
189
+ | Corpus | Commits | Query set | vs. full file content | vs. `git log -p` on those files |
190
+ | --- | --- | --- | --- | --- |
191
+ | This repo | 62 | 16 real prompts, verbatim from this project's own history | 95% (median 94%) | 98% (median 97%) |
192
+ | [`vitejs/vite`](https://github.com/vitejs/vite) | 9,567 | 16, mechanically sampled — see below | 99% (median 98%) | ~100% (median ~100%) — see caveat |
193
+
194
+ Both clear the original >70% target ("cut API token spend versus sending full context"), and the vite
195
+ run is the first measurement at the scale that target was always described as applying to.
196
+
197
+ **Read the methodology before quoting either number, because it is a narrower claim than it looks:**
198
+
199
+ - The file set each query is graded against comes from NexusMem's *own* ranking — whichever files the
200
+ packed nodes for that query touch, not an outside judge's idea of the right answer. This isolates
201
+ what the pack step (rank → budget → excerpt) saves once retrieval has already picked a candidate
202
+ set; it does not independently verify that the candidate set was the right one to pick.
203
+ - The vite query set is not hand-picked: an even sample, across the full commit history, of
204
+ well-explained `fix`/`feat`/`perf`/`refactor` commits turned into "why does vite `<description>`"
205
+ from the commit's own conventional-commit text, plus rationale-bearing doc section headings. This
206
+ repo's own query set instead reuses real historical prompts verbatim from `conversation_turn`
207
+ nodes — several are broad task instructions rather than narrow questions, which pulls a wider file
208
+ set into scope and is part of why its number, while still high, sits below vite's. Both derivations
209
+ are mechanical and disclosed in `scripts/benchmark.ts`, neither is cherry-picked per-query.
210
+ - `git log -p` on a file touched by thousands of commits is enormous — one vite query's baseline hit
211
+ 7.5M tokens because a file in its resolved set has that much history. That is itself a finding, not
212
+ noise: at this scale, "just read the file's history instead" stops being a viable alternative at
213
+ all, which is a big part of why that column rounds to ~100%.
214
+ - **This supersedes the previous ~40% figure**, which was hand-tallied from two hand-picked queries
215
+ against this repo alone, never instrumented, and used an unstated baseline. It was not wrong so much
216
+ as underspecified — this number replaces it with a stated method and a script that reproduces it,
217
+ rather than being a claim that the product got better.
190
218
 
191
219
  One thing that is not a percentage: shell commands and conversation turns have no cheap `grep`
192
220
  equivalent. Without something recording them, they are gone, not merely more expensive to find.
@@ -241,6 +269,11 @@ optimizing, and it is somebody else's process.
241
269
  unmounted drive is not a deleted project.
242
270
  - **Conversation chunking is unevaluated.** Splitting long replies at heading boundaries measurably
243
271
  helped, but it has never been tested systematically.
272
+ - **A chunked node's sibling count in one result is capped, not tuned.** `conversation_turn` and
273
+ `doc_section` both split one reply or file into several nodes; at most 2 of them may appear
274
+ together in a packed result. Found live: a query for "token" returned 9 of its top 12 hits as
275
+ different pieces of one heavily-sectioned reply, crowding out the node that actually answered it.
276
+ The cap of 2 is a judgement call, not a measured optimum, same as the ranking priors' budget above.
244
277
  - **The size of the prior budget is a judgement call, not a measured optimum.** Priors are now
245
278
  bounded jointly rather than one at a time, which closed a real 4× hole (see the ranking section),
246
279
  but the 2× budget itself has never been tuned against a labelled relevance set — there isn't one.
@@ -315,8 +348,8 @@ Deleting `.nexusmem/` loses nothing that `sync` cannot rebuild.
315
348
 
316
349
  ## Status
317
350
 
318
- Ingestion, hybrid retrieval, budgeted packing and the MCP server all work and are covered by 315
319
- tests running on Linux and Windows across Node 22 and 24. Phase 3 is complete.
351
+ Ingestion, hybrid retrieval, budgeted packing and the MCP server all work and are covered by 342
352
+ tests running on Linux and Windows across Node 22 and 24.
320
353
 
321
354
  ## Development
322
355