nexusmem 0.3.2 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,260 +1,332 @@
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.2] — 2026-08-16
13
-
14
- ### Added
15
-
16
- - **`mcpName` field in `package.json`**, required by the official MCP registry
17
- (registry.modelcontextprotocol.io) to verify that whoever publishes `server.json` under
18
- `io.github.yaminbkk/nexusmem` also controls the `nexusmem` npm package itself the registry
19
- rejects a publish attempt otherwise. No behavior change for CLI/MCP users; this is purely a
20
- registry-ownership proof.
21
- - **`list_recent_memory` MCP tool** chronological listing of a repository's most recently
22
- remembered nodes (git commits, diffs, shell commands, docs, conversation, session summaries),
23
- newest first. Distinct from `search_memory`: no query, just "what has this project's memory
24
- recorded lately" built for the VS Code extension's sidebar view, which lists rather than
25
- searches. Backed by `MemoryStore.listRecentNodes`, reusing the existing `idx_nodes_project_ts`
26
- index.
27
- - **`sync --prune-source <name>` and `sync --prune-stale-shell`** drop one source's nodes without a
28
- full `--rebuild`, which loses history that can't be re-read from disk (the shell tail window, older
29
- conversation turns). `--prune-stale-shell` is a shortcut for the three dead pre-hook shell-scrape
30
- sources (`shell:pwsh`, `shell:bash`, `shell:zsh`) at once. Dry-run by default prints the matching
31
- count and does nothing until `--yes` is also given, since this is an irreversible full wipe of the
32
- named source(s), unlike `--rebuild`'s no-prompt full-project reset. Also sweeps any prior project
33
- identity of this same repo (the id a renamed git remote leaves behind after
34
- `fix(store): reconcile memory stranded by a changed git remote URL` migrates what it can) — a
35
- live-id-only prune could not reach nodes reconciliation deliberately left in place. Exposed on both
36
- the CLI and the MCP `sync_project` tool.
37
- - **Discussion-heuristic failure→fix chains now surface**, tightened to an AND-joined significant-
38
- token match instead of the original OR match. Re-verified against this repo's own real database:
39
- 5/5 discussion links correct (was ~half wrong when it shipped unsurfaced in 0.3.0). Chains now
40
- follow across projects in `query --all-projects` too.
41
-
42
- ### Fixed
43
-
44
- - **`sync_project`'s summary no longer contains raw ANSI color codes on Windows.** `runInit`/`runSync`
45
- format their output with picocolors for terminal display, and picocolors treats `platform ===
46
- 'win32'` as sufficient evidence of color support on its own, without checking `isTTY` — correct for
47
- a real terminal, wrong for the MCP JSON-RPC channel, which is piped on every platform. Found live: a
48
- real MCP client (the VS Code extension's Output channel) rendered the raw escape codes as literal
49
- text instead of color. Stripped at the MCP boundary in `syncProject`, leaving the CLI's own terminal
50
- output untouched.
51
-
52
- ## [0.3.1] — 2026-08-15
53
-
54
- ### Added
55
-
56
- - **`scripts/benchmark.ts` (`npm run bench`) a reproducible end-to-end token-saving benchmark.**
57
- Compares `packed.tokensUsed` against two baselines (full-file-read and `git log -p`) for the same
58
- files a query's packed nodes touch, over a query set derived mechanically from the corpus itself
59
- rather than hand-picked. Used to measure the README's `## What it costs you` numbers against both
60
- this repo (62 commits) and a 9,567-commit external corpus (`vitejs/vite`) see README for the
61
- numbers and their methodology caveats.
62
-
63
- ### Fixed
64
-
65
- - **A generic 2-3 letter local model title, "id" as a search token, and one node's chunks flooding a
66
- result set — three ranking/retrieval edge cases found by dogfooding, each verified with a red test
67
- before the fix.**
68
- - Session-summary titles: the local model sometimes wrote a role-framing line ("Role: Lead Systems
69
- Engineer...") instead of a summary, in Thai and English alike. The existing generic-title filter
70
- didn't catch either language, since it only matched English words like "summary"/"update".
71
- - Search: the token `id` alone was prefix-matching unrelated shell commands like
72
- `winget install --id ...`, because every query token was OR-ed with no floor and no stopword
73
- list, and bm25 gives a rare-but-generic token an inflated score purely from scarcity.
74
- - Packing: `conversation_turn` and `doc_section` both chunk one reply or file into several nodes
75
- sharing the same timestamp; up to 2 may now appear in one packed result, down from unlimited.
76
-
77
- - **A repo's memory could silently split in two if its git remote URL ever changed** (a GitHub
78
- account rename, an org transfer). Project identity is derived from the remote URL on purpose so
79
- the same repo re-cloned to a new path or machine keeps sharing memory but a changed URL on the
80
- *same* path minted a new id and stranded every node synced under the old one, invisible to
81
- `status`/`query`/MCP from then on. `sync` now detects a prior id already recorded in the repo's own
82
- database and reconciles it forward: recomputable node kinds (session summaries, hook-sourced shell
83
- history) are migrated under their correct new id and deduplicated against anything already synced;
84
- conversation turns, whose identity can't be recomputed, are reassigned in place. Git commits,
85
- diffs, and doc sections are left alone — a normal sync already re-derives them completely, so
86
- there is nothing to migrate.
87
-
88
- ## [0.3.0]2026-08-13
89
-
90
- **Upgrade note.** The first `sync` after upgrading drops every stored embedding and rebuilds it.
91
- This is not optional and it is not a bug: embeddings now come from Ollama's `/api/embed`, which
92
- returns L2-normalised vectors, while the previous `/api/embeddings` did not — measured norms of 1.0
93
- and 20.7 for the same input. `nodes_vec` ranks by Euclidean distance and records no per-row
94
- provenance, so a corpus holding both would separate by scale rather than by meaning. `sync` says
95
- what it dropped, nodes are untouched, and BM25 keeps working while the rebuild runs. On this repo
96
- the rebuild was 833 nodes in one pass, inside a 9-second sync.
97
-
98
- ### Added
99
-
100
- - **Session summaries via a local model** (`sources.session`, opt-in, off by default). Each
101
- finished session becomes one distilled `session_summary` node decisions and their reasons
102
- alongside the raw exchanges. Runs a local Ollama chat model (`qwen2.5:3b` by default); nothing is
103
- downloaded automatically and no transcript leaves the machine. New `scan-session` command, with
104
- `--dry-run` to print the exact prompt a session would produce without calling the model.
105
- Bounded three ways: a session must be quiet for `settleMinutes` (default 30) before it is
106
- eligible, the prompt is hashed so an unchanged session never reaches the model again, and
107
- `maxSessions` caps how many are summarized per sync. Every exchange is redacted before the model
108
- sees it, and the model's own output is redacted again before it is stored.
109
- - **`sync --embed-limit <n>`** to cap the embedding pass, for when draining the whole backlog is not
110
- wanted.
111
-
112
- ### Changed
113
-
114
- - **The embedding pass drains the backlog in one `sync`** instead of stopping after 200 nodes, and
115
- sends texts to Ollama in batches of 32 measured at 4.21x the throughput of one call per node
116
- (20.3ms 4.8ms per node, over 96 real nodes from this repo). Leaving it uncapped is safe because
117
- paging walks rowids monotonically, so a node the provider failed on is passed over rather than
118
- retried forever, and because three consecutive dead requests end the pass: an Ollama that is not
119
- running now costs three requests rather than one timeout per node.
120
- - **Embeddings carry a provider identity.** Changing the embedding model, or upgrading from a
121
- release that recorded no identity, drops the vectors and re-embeds rather than ranking across a
122
- mixture.
123
- - **Ranking priors now share one budget instead of getting one each.** `signal` and `recency` are
124
- query-independent, and each was separately capped at overturning a 2× relevance gap. The score
125
- multiplies them, so together they could overturn 4× — which is not a corner case but a description
126
- of every commit made during an active working day, fresh and high-signal at once. A query about the
127
- PowerShell hook returned two unrelated same-day `fix:` commits at ranks 3 and 4 while the section
128
- that answered it sat at rank 6. The budget is now the bound on the priors *jointly*, split
129
- between them (`signal^0.215 × recency^0.288`, down from `^0.431` and `^0.576`), and a third prior
130
- would re-divide the same budget rather than enlarge it. Measured on four real queries against this
131
- repository's memory: the answering section rose in three of them — the rationale for "why BM25
132
- before vector search" went from rank 4 to rank 1 — and no query's correct top hit was displaced.
133
-
134
- ### Known limitation
135
-
136
- - Session-summary *titles* depend on the model following a fixed output format, and a 3B model often
137
- does not. Measured over 14 real sessions, roughly a third came back usable; the rest were
138
- conversational preambles, stray bullets, or a bare "Summary of the Session". Those are rejected
139
- and the title falls back to the first line of the question that opened the session — always
140
- specific, not always elegant. Compliance was worst on long sessions and on transcripts not in
141
- English. `sources.session.model` takes a larger model if it matters.
142
-
143
- ## [0.2.0] 2026-08-12
144
-
145
- **Upgrade note.** Both new sources are on by default, so the first `sync` after upgrading an
146
- existing project ingests the patches of its 200 most recent commits and starts recording the
147
- repository in `~/.nexusmem/projects.json`. Set `sources.diff.enabled` to `false` in
148
- `.nexusmem/config.json` if you would rather not, and `NEXUSMEM_HOME` relocates the user-scoped
149
- directory. Nothing existing is rewritten or lost.
150
-
151
- ### Added
152
-
153
- - **Diff-level nodes.** Commit patches are now indexed, one node per changed file, so a question
154
- about *what the change looked like* reaches the lines themselves rather than the commit message
155
- and a `+41/-6` summary. New `code_diff` kind, `diff` source, `scan-diff` preview command, and a
156
- `sources.diff` config block. Read by a second `git log --patch` walk with its own cursor: folding
157
- it into the existing `--numstat` walk would put patch text and numstat rows in one field, where a
158
- diff line reading `-1\t2\tfoo` is indistinguishable from a real file entry.
159
- Bounded on purpose — 200 commits on a first sync, 20 files per commit, no merges (their patch
160
- exists only in a combined format this parser does not read), and binaries, lockfiles and build
161
- output skipped. Patches are redacted with the shape-matching rules only; the key/value rule that
162
- serves prose would rewrite `const apiKey = process.env.SERVICE_API_KEY` into a redaction marker.
163
- - **Cross-project recall.** `query --all-projects` (and `search_memory`'s `allProjects`) searches
164
- every repository NexusMem has been run in on this machine, tagging each result with the repository
165
- it came from. Databases stay per-repository a shared global store was rejected for giving up the
166
- property that deleting one repo's `.nexusmem/` removes that repo's memory and nothing else — so a
167
- plain index at `~/.nexusmem/projects.json`, written by `init` and refreshed by `sync`, is what
168
- makes the others findable. New `projects` command lists it; `--prune` forgets entries whose
169
- database is gone. A stale or corrupt registry degrades the query, never fails it.
170
- Ranking fuses each project's list by rank (RRF) instead of comparing raw BM25 costs, which are
171
- computed against their own corpus and are not comparable across databases. The bias this leaves
172
- every project's rank-1 hit is worth the same, so recall favours breadth is documented rather
173
- than hidden.
174
- - **Query-aware diff excerpts.** A packed summary is ~320 characters and a patch is thousands, so
175
- the packer now picks the hunk whose tokens match the query and starts the excerpt at the changed
176
- line. Found by dogfooding: "what flags are passed to every git invocation" retrieved the right
177
- file and then spent the whole summary on a class definition seventy lines above the answer.
178
- Matching splits identifiers on case and underscore boundaries, because `\bretry\b` does not match
179
- `RETRY_DELAYS_MS` and a natural-language question otherwise never meets the code it is about.
180
- - `CHANGELOG.md` now ships inside the npm tarball. npm's always-included list covers `package.json`,
181
- `README` and `LICENSE` but not the changelog, so it previously reached GitHub readers only.
182
-
183
- ### Internal
184
-
185
- - The test suite no longer writes to the developer's real `~/.nexusmem`. `sync` records the
186
- repository it ingested in the project registry, and the suite syncs temporary repositories in
187
- several places, so a green run left seven dead entries behind found by running `nexusmem
188
- projects` after the fact, not by any test. `tests/setup.ts` now points `NEXUSMEM_HOME` at a
189
- throwaway directory for the whole suite, and one test fails if that guard is ever removed.
190
- - `npm run smoke` drives the *packaged* artifact: build, pack, install into a throwaway directory,
191
- then run the installed CLI, an end-to-end ingest/query against a fixture repository, and an
192
- `initialize` handshake over real stdio. It also audits the manifest `npm publish` would send,
193
- which is a different artifact from the tarball. Both defects that ever reached npm users passed a
194
- green unit suite first; each is now pinned by a check verified to fail when the defect is
195
- reintroduced. Runs in CI on Linux and Windows as its own job.
196
-
197
- ## [0.1.2] 2026-08-10
198
-
199
- ### Fixed
200
-
201
- - `nexusmem --version` printed `0.1.0` on 0.1.1. The version string in `src/cli/index.ts` was a
202
- literal separate from `package.json`, and the 0.1.1 bump only touched the latter. `src/mcp/server.ts`
203
- had the same problem in its `McpServer` constructor, so an MCP client's `initialize` handshake
204
- would have reported the same stale version. Both now read the real version through
205
- `readOwnVersion()` in `src/core/version.ts`, which resolves `package.json` via `import.meta.url`.
206
- Found by running the published package end to end rather than trusting `npm publish --dry-run`
207
- and the registry API, neither of which executes a `--version` flag.
208
-
209
- The ingestion and retrieval pipeline was never affected only the two places that report a version
210
- independently of running a command.
211
-
212
- ## [0.1.1] — 2026-08-10
213
-
214
- ### Changed
215
-
216
- - README rewritten for someone deciding whether to read the source: what it does, how retrieval
217
- scores, what it costs, and where it breaks. `README.md` ships inside the package, so this is a
218
- real change to what npm delivers but no code changed between 0.1.0 and 0.1.1.
219
- - Documented the ranking flaw the tool found in itself, and the fact that the conversation source
220
- in the sample `status` output is opt-in rather than default.
221
- - Dropped `&&` from the quickstart, which Windows PowerShell 5.1 cannot parse.
222
-
223
- ## [0.1.0] 2026-08-10
224
-
225
- First public release.
226
-
227
- ### Added
228
-
229
- - **Collectors.** Git history (commit metadata and diff stats, not diff bodies), shell commands with
230
- exit codes via an opt-in PowerShell hook, tracked markdown docs via `git ls-files -- '*.md'`, and
231
- opt-in assistant transcripts.
232
- - **Hybrid retrieval.** SQLite FTS5 BM25 and `sqlite-vec` KNN over 768-dim embeddings, fused with
233
- reciprocal rank fusion, then ranked by relevance against signal and recency priors and packed into
234
- an explicit token budget.
235
- - **MCP server** over stdio (`nexusmem mcp`) exposing `search_memory`, `sync_project` and
236
- `get_status`, for Claude Desktop, Cursor, Windsurf and other MCP clients.
237
- - **CLI**: `init`, `sync`, `query`, `status`, `mcp`, and `hook install|remove|status`, plus four
238
- dry-run previews `scan-git`, `scan-shell`, `scan-docs`, `scan-conversation` that write nothing
239
- and print the nodes ingestion would create with their signal scores.
240
- - Content-addressed node ids (`sha256(projectId + kind + naturalKey)`), so `sync` is idempotent and
241
- two clones of one repository share a memory namespace.
242
- - Everything stays on the machine: one SQLite database in WAL mode under `<repo>/.nexusmem/`.
243
-
244
- ### Notes
245
-
246
- - Requires Node **>= 22**. `better-sqlite3` 12.11.1 publishes no prebuilt binary for Node 20 — its
247
- prebuilds start at ABI 127 so a lower floor would have been a promise the package could not keep.
248
- Do not lower it without checking upstream prebuilds first.
249
- - Not done at this release: diff bodies are not indexed, queries are scoped to a single project,
250
- there is no local-model summarization pass, and the conversation collector has never been audited
251
- for the stale-node bug that was found and fixed in the docs collector.
252
-
253
- [Unreleased]: https://github.com/yaminbkk/NexusMem/compare/v0.3.2...HEAD
254
- [0.3.2]: https://github.com/yaminbkk/NexusMem/compare/v0.3.1...v0.3.2
255
- [0.3.1]: https://github.com/yaminbkk/NexusMem/compare/v0.3.0...v0.3.1
256
- [0.3.0]: https://github.com/yaminbkk/NexusMem/compare/v0.2.0...v0.3.0
257
- [0.2.0]: https://github.com/yaminbkk/NexusMem/compare/v0.1.2...v0.2.0
258
- [0.1.2]: https://github.com/yaminbkk/NexusMem/compare/v0.1.1...v0.1.2
259
- [0.1.1]: https://github.com/yaminbkk/NexusMem/compare/v0.1.0...v0.1.1
260
- [0.1.0]: https://github.com/yaminbkk/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.4.0] — 2026-08-16
13
+
14
+ ### Added
15
+
16
+ - **`nexusmem status` now warns when prior project identities still hold nodes.** A renamed git
17
+ remote can leave stale data under the old identity; status reports both the identity and node
18
+ counts and points to `sync --prune-source <name>` instead of leaving that data discoverable only
19
+ through a raw SQLite query.
20
+ - **`docs/competitor-comparison.md`: an honest, source-level comparison against `riponcm/projectmem`** (705
21
+ GitHub stars vs. this project's 8, at time of writing) — a real, working competitor read in full (not
22
+ judged from its README), covering where the two tools' failure-tracking, import-graph coverage, and
23
+ token-savings claims genuinely differ. Cites a freshly re-run benchmark (`npm run bench`, not a stale
24
+ table) alongside projectmem's own `pjm score` constants, read directly from its source. Linked from the
25
+ README's `## What it costs you` section and a short website section near the existing stats.
26
+ - **`nexusmem hook git install|remove|status`: a real git pre-commit hook.** Installs a marked block into
27
+ `.git/hooks/pre-commit` that runs `nexusmem precheck` (no `--strict`, so it can never block a commit on
28
+ its own) before each commit the automatic counterpart to the advisory `precheck` command. Refuses to
29
+ touch a pre-existing foreign hook (husky, lint-staged, lefthook, ...) unless `--force` is passed, in which
30
+ case it appends after the existing content rather than before, so the foreign hook still runs first and
31
+ keeps deciding whatever it already decided. Idempotent (`nexusmem hook git install` twice is a no-op) and
32
+ removable cleanly, including restoring a foreign hook to its original content if one was appended onto.
33
+ Live-verified against a real scratch git repo on Windows: fresh install, a real `git commit` that
34
+ triggered the hook and printed a correct precheck report, clean removal, and both the refuse-without-force
35
+ and append-with-force foreign-hook paths.
36
+ - **`nexusmem precheck`: proactive pre-commit warnings.** Checks staged (or `--working`, or explicit `--files`)
37
+ files against project memory and warns about unresolved past failures and high recent churn *before* you
38
+ commit advisory by default (always exits 0; `--strict` turns an unresolved failure into a non-zero exit).
39
+ Matches a file's own basename tokens against still-unlinked `shell_command` failures (no
40
+ `resolved_by:*` link from `sync --link-failures`), reusing `filterBoilerplateTokens` from the
41
+ discussion-bridge heuristic — now exported and parameterized by node kind so it can be corpus-relative
42
+ against `shell_command` history instead of only `conversation_turn`/`session_summary`. Churn is scoped to
43
+ `git_commit`-kind `node_files` touches only, so it doesn't double-count the identical touches `code_diff`
44
+ nodes also record. Deliberately does not yet install as a real git hook (see the module comment in
45
+ `src/correlate/precheck.ts` for why capture-time `git status` diffing and past-commit correlation were both
46
+ rejected) that's a follow-up once this signal has been dogfooded.
47
+ - **JS/TS import-graph edges.** `nexusmem scan-structure` previews (and `sync` now ingests) file→file
48
+ import relationships across a project's tracked `.ts`/`.tsx`/`.js`/`.jsx` files a dependency-free
49
+ regex extractor (`src/structure/extract.ts`) resolves relative `import`/`export ... from`/`require`/
50
+ dynamic-`import` specifiers against the tracked-path set, correctly rewriting the common TS/ESM
51
+ `./foo.js` specifier back to its real `foo.ts` source. Stored in a new `file_edges` table (schema
52
+ v4), replaced wholesale on every sync since edges describe current tree state, not history. Surfaced
53
+ as a `structure` line in `nexusmem status`; not yet wired into `query` ranking or exposed as an MCP
54
+ tool — that's a follow-on design question, not this pass's job.
55
+
56
+ ## [0.3.3]2026-08-16
57
+
58
+ ### Added
59
+
60
+ - **`nexusmem status` now shows failure→fix chain counts** `N/M failure(s) resolved (X retry, Y
61
+ discussion)`, plus a hint to run `sync --link-failures` when failures remain unresolved. The
62
+ chain feature is this project's most distinctive capability, but was previously invisible to
63
+ anyone who didn't already know to query `node_links` directly. Backed by the new
64
+ `getChainStats` in `src/correlate/failure-fix.ts`, which dedupes failures resolved by both
65
+ heuristics rather than double-counting them.
66
+
67
+ ### Fixed
68
+
69
+ - **Discussion-bridge heuristic (`sync --link-failures`): corpus-relative boilerplate tokens no
70
+ longer produce false-positive failure→fix links.** Re-dogfooded at larger scale against a second
71
+ real project's history: a command made entirely of words that saturate a project's own corpus
72
+ (e.g. this repo's own name/verbs) could AND-match an unrelated turn that just happened to mention
73
+ the same words, and bm25 score alone could not separate that from a true positive (measured: the
74
+ false positive scored *stronger* than two real true positives). `filterBoilerplateTokens` in
75
+ `src/correlate/failure-fix.ts` drops any token that appears in over 20% of a project's own
76
+ `conversation_turn`/`session_summary` history before building the match query — measured against
77
+ real data, not guessed and skips the discussion-match attempt entirely (rather than falling back
78
+ unfiltered) when every token turns out to be boilerplate, since a missed link is preferred over a
79
+ false one for this heuristic. Below 10 discussable nodes the filter is skipped, since frequency
80
+ isn't a meaningful signal yet on a young project.
81
+
82
+ ## [0.3.2] 2026-08-16
83
+
84
+ ### Added
85
+
86
+ - **`mcpName` field in `package.json`**, required by the official MCP registry
87
+ (registry.modelcontextprotocol.io) to verify that whoever publishes `server.json` under
88
+ `io.github.yaminbkk/nexusmem` also controls the `nexusmem` npm package itself the registry
89
+ rejects a publish attempt otherwise. No behavior change for CLI/MCP users; this is purely a
90
+ registry-ownership proof.
91
+ - **`list_recent_memory` MCP tool** chronological listing of a repository's most recently
92
+ remembered nodes (git commits, diffs, shell commands, docs, conversation, session summaries),
93
+ newest first. Distinct from `search_memory`: no query, just "what has this project's memory
94
+ recorded lately" built for the VS Code extension's sidebar view, which lists rather than
95
+ searches. Backed by `MemoryStore.listRecentNodes`, reusing the existing `idx_nodes_project_ts`
96
+ index.
97
+ - **`sync --prune-source <name>` and `sync --prune-stale-shell`** — drop one source's nodes without a
98
+ full `--rebuild`, which loses history that can't be re-read from disk (the shell tail window, older
99
+ conversation turns). `--prune-stale-shell` is a shortcut for the three dead pre-hook shell-scrape
100
+ sources (`shell:pwsh`, `shell:bash`, `shell:zsh`) at once. Dry-run by default — prints the matching
101
+ count and does nothing until `--yes` is also given, since this is an irreversible full wipe of the
102
+ named source(s), unlike `--rebuild`'s no-prompt full-project reset. Also sweeps any prior project
103
+ identity of this same repo (the id a renamed git remote leaves behind after
104
+ `fix(store): reconcile memory stranded by a changed git remote URL` migrates what it can) — a
105
+ live-id-only prune could not reach nodes reconciliation deliberately left in place. Exposed on both
106
+ the CLI and the MCP `sync_project` tool.
107
+ - **Discussion-heuristic failure→fix chains now surface**, tightened to an AND-joined significant-
108
+ token match instead of the original OR match. Re-verified against this repo's own real database:
109
+ 5/5 discussion links correct (was ~half wrong when it shipped unsurfaced in 0.3.0). Chains now
110
+ follow across projects in `query --all-projects` too.
111
+
112
+ ### Fixed
113
+
114
+ - **`sync_project`'s summary no longer contains raw ANSI color codes on Windows.** `runInit`/`runSync`
115
+ format their output with picocolors for terminal display, and picocolors treats `platform ===
116
+ 'win32'` as sufficient evidence of color support on its own, without checking `isTTY` correct for
117
+ a real terminal, wrong for the MCP JSON-RPC channel, which is piped on every platform. Found live: a
118
+ real MCP client (the VS Code extension's Output channel) rendered the raw escape codes as literal
119
+ text instead of color. Stripped at the MCP boundary in `syncProject`, leaving the CLI's own terminal
120
+ output untouched.
121
+
122
+ ## [0.3.1] — 2026-08-15
123
+
124
+ ### Added
125
+
126
+ - **`scripts/benchmark.ts` (`npm run bench`) a reproducible end-to-end token-saving benchmark.**
127
+ Compares `packed.tokensUsed` against two baselines (full-file-read and `git log -p`) for the same
128
+ files a query's packed nodes touch, over a query set derived mechanically from the corpus itself
129
+ rather than hand-picked. Used to measure the README's `## What it costs you` numbers against both
130
+ this repo (62 commits) and a 9,567-commit external corpus (`vitejs/vite`) see README for the
131
+ numbers and their methodology caveats.
132
+
133
+ ### Fixed
134
+
135
+ - **A generic 2-3 letter local model title, "id" as a search token, and one node's chunks flooding a
136
+ result set three ranking/retrieval edge cases found by dogfooding, each verified with a red test
137
+ before the fix.**
138
+ - Session-summary titles: the local model sometimes wrote a role-framing line ("Role: Lead Systems
139
+ Engineer...") instead of a summary, in Thai and English alike. The existing generic-title filter
140
+ didn't catch either language, since it only matched English words like "summary"/"update".
141
+ - Search: the token `id` alone was prefix-matching unrelated shell commands like
142
+ `winget install --id ...`, because every query token was OR-ed with no floor and no stopword
143
+ list, and bm25 gives a rare-but-generic token an inflated score purely from scarcity.
144
+ - Packing: `conversation_turn` and `doc_section` both chunk one reply or file into several nodes
145
+ sharing the same timestamp; up to 2 may now appear in one packed result, down from unlimited.
146
+
147
+ - **A repo's memory could silently split in two if its git remote URL ever changed** (a GitHub
148
+ account rename, an org transfer). Project identity is derived from the remote URL on purpose — so
149
+ the same repo re-cloned to a new path or machine keeps sharing memory — but a changed URL on the
150
+ *same* path minted a new id and stranded every node synced under the old one, invisible to
151
+ `status`/`query`/MCP from then on. `sync` now detects a prior id already recorded in the repo's own
152
+ database and reconciles it forward: recomputable node kinds (session summaries, hook-sourced shell
153
+ history) are migrated under their correct new id and deduplicated against anything already synced;
154
+ conversation turns, whose identity can't be recomputed, are reassigned in place. Git commits,
155
+ diffs, and doc sections are left alone a normal sync already re-derives them completely, so
156
+ there is nothing to migrate.
157
+
158
+ ## [0.3.0] 2026-08-13
159
+
160
+ **Upgrade note.** The first `sync` after upgrading drops every stored embedding and rebuilds it.
161
+ This is not optional and it is not a bug: embeddings now come from Ollama's `/api/embed`, which
162
+ returns L2-normalised vectors, while the previous `/api/embeddings` did not measured norms of 1.0
163
+ and 20.7 for the same input. `nodes_vec` ranks by Euclidean distance and records no per-row
164
+ provenance, so a corpus holding both would separate by scale rather than by meaning. `sync` says
165
+ what it dropped, nodes are untouched, and BM25 keeps working while the rebuild runs. On this repo
166
+ the rebuild was 833 nodes in one pass, inside a 9-second sync.
167
+
168
+ ### Added
169
+
170
+ - **Session summaries via a local model** (`sources.session`, opt-in, off by default). Each
171
+ finished session becomes one distilled `session_summary` node decisions and their reasons
172
+ alongside the raw exchanges. Runs a local Ollama chat model (`qwen2.5:3b` by default); nothing is
173
+ downloaded automatically and no transcript leaves the machine. New `scan-session` command, with
174
+ `--dry-run` to print the exact prompt a session would produce without calling the model.
175
+ Bounded three ways: a session must be quiet for `settleMinutes` (default 30) before it is
176
+ eligible, the prompt is hashed so an unchanged session never reaches the model again, and
177
+ `maxSessions` caps how many are summarized per sync. Every exchange is redacted before the model
178
+ sees it, and the model's own output is redacted again before it is stored.
179
+ - **`sync --embed-limit <n>`** to cap the embedding pass, for when draining the whole backlog is not
180
+ wanted.
181
+
182
+ ### Changed
183
+
184
+ - **The embedding pass drains the backlog in one `sync`** instead of stopping after 200 nodes, and
185
+ sends texts to Ollama in batches of 32 — measured at 4.21x the throughput of one call per node
186
+ (20.3ms 4.8ms per node, over 96 real nodes from this repo). Leaving it uncapped is safe because
187
+ paging walks rowids monotonically, so a node the provider failed on is passed over rather than
188
+ retried forever, and because three consecutive dead requests end the pass: an Ollama that is not
189
+ running now costs three requests rather than one timeout per node.
190
+ - **Embeddings carry a provider identity.** Changing the embedding model, or upgrading from a
191
+ release that recorded no identity, drops the vectors and re-embeds rather than ranking across a
192
+ mixture.
193
+ - **Ranking priors now share one budget instead of getting one each.** `signal` and `recency` are
194
+ query-independent, and each was separately capped at overturning a relevance gap. The score
195
+ multiplies them, so together they could overturn which is not a corner case but a description
196
+ of every commit made during an active working day, fresh and high-signal at once. A query about the
197
+ PowerShell hook returned two unrelated same-day `fix:` commits at ranks 3 and 4 while the section
198
+ that answered it sat at rank 6. The 2× budget is now the bound on the priors *jointly*, split
199
+ between them (`signal^0.215 × recency^0.288`, down from `^0.431` and `^0.576`), and a third prior
200
+ would re-divide the same budget rather than enlarge it. Measured on four real queries against this
201
+ repository's memory: the answering section rose in three of them the rationale for "why BM25
202
+ before vector search" went from rank 4 to rank 1 and no query's correct top hit was displaced.
203
+
204
+ ### Known limitation
205
+
206
+ - Session-summary *titles* depend on the model following a fixed output format, and a 3B model often
207
+ does not. Measured over 14 real sessions, roughly a third came back usable; the rest were
208
+ conversational preambles, stray bullets, or a bare "Summary of the Session". Those are rejected
209
+ and the title falls back to the first line of the question that opened the session — always
210
+ specific, not always elegant. Compliance was worst on long sessions and on transcripts not in
211
+ English. `sources.session.model` takes a larger model if it matters.
212
+
213
+ ## [0.2.0] — 2026-08-12
214
+
215
+ **Upgrade note.** Both new sources are on by default, so the first `sync` after upgrading an
216
+ existing project ingests the patches of its 200 most recent commits and starts recording the
217
+ repository in `~/.nexusmem/projects.json`. Set `sources.diff.enabled` to `false` in
218
+ `.nexusmem/config.json` if you would rather not, and `NEXUSMEM_HOME` relocates the user-scoped
219
+ directory. Nothing existing is rewritten or lost.
220
+
221
+ ### Added
222
+
223
+ - **Diff-level nodes.** Commit patches are now indexed, one node per changed file, so a question
224
+ about *what the change looked like* reaches the lines themselves rather than the commit message
225
+ and a `+41/-6` summary. New `code_diff` kind, `diff` source, `scan-diff` preview command, and a
226
+ `sources.diff` config block. Read by a second `git log --patch` walk with its own cursor: folding
227
+ it into the existing `--numstat` walk would put patch text and numstat rows in one field, where a
228
+ diff line reading `-1\t2\tfoo` is indistinguishable from a real file entry.
229
+ Bounded on purpose 200 commits on a first sync, 20 files per commit, no merges (their patch
230
+ exists only in a combined format this parser does not read), and binaries, lockfiles and build
231
+ output skipped. Patches are redacted with the shape-matching rules only; the key/value rule that
232
+ serves prose would rewrite `const apiKey = process.env.SERVICE_API_KEY` into a redaction marker.
233
+ - **Cross-project recall.** `query --all-projects` (and `search_memory`'s `allProjects`) searches
234
+ every repository NexusMem has been run in on this machine, tagging each result with the repository
235
+ it came from. Databases stay per-repository a shared global store was rejected for giving up the
236
+ property that deleting one repo's `.nexusmem/` removes that repo's memory and nothing else — so a
237
+ plain index at `~/.nexusmem/projects.json`, written by `init` and refreshed by `sync`, is what
238
+ makes the others findable. New `projects` command lists it; `--prune` forgets entries whose
239
+ database is gone. A stale or corrupt registry degrades the query, never fails it.
240
+ Ranking fuses each project's list by rank (RRF) instead of comparing raw BM25 costs, which are
241
+ computed against their own corpus and are not comparable across databases. The bias this leaves —
242
+ every project's rank-1 hit is worth the same, so recall favours breadth is documented rather
243
+ than hidden.
244
+ - **Query-aware diff excerpts.** A packed summary is ~320 characters and a patch is thousands, so
245
+ the packer now picks the hunk whose tokens match the query and starts the excerpt at the changed
246
+ line. Found by dogfooding: "what flags are passed to every git invocation" retrieved the right
247
+ file and then spent the whole summary on a class definition seventy lines above the answer.
248
+ Matching splits identifiers on case and underscore boundaries, because `\bretry\b` does not match
249
+ `RETRY_DELAYS_MS` and a natural-language question otherwise never meets the code it is about.
250
+ - `CHANGELOG.md` now ships inside the npm tarball. npm's always-included list covers `package.json`,
251
+ `README` and `LICENSE` but not the changelog, so it previously reached GitHub readers only.
252
+
253
+ ### Internal
254
+
255
+ - The test suite no longer writes to the developer's real `~/.nexusmem`. `sync` records the
256
+ repository it ingested in the project registry, and the suite syncs temporary repositories in
257
+ several places, so a green run left seven dead entries behind — found by running `nexusmem
258
+ projects` after the fact, not by any test. `tests/setup.ts` now points `NEXUSMEM_HOME` at a
259
+ throwaway directory for the whole suite, and one test fails if that guard is ever removed.
260
+ - `npm run smoke` drives the *packaged* artifact: build, pack, install into a throwaway directory,
261
+ then run the installed CLI, an end-to-end ingest/query against a fixture repository, and an
262
+ `initialize` handshake over real stdio. It also audits the manifest `npm publish` would send,
263
+ which is a different artifact from the tarball. Both defects that ever reached npm users passed a
264
+ green unit suite first; each is now pinned by a check verified to fail when the defect is
265
+ reintroduced. Runs in CI on Linux and Windows as its own job.
266
+
267
+ ## [0.1.2] — 2026-08-10
268
+
269
+ ### Fixed
270
+
271
+ - `nexusmem --version` printed `0.1.0` on 0.1.1. The version string in `src/cli/index.ts` was a
272
+ literal separate from `package.json`, and the 0.1.1 bump only touched the latter. `src/mcp/server.ts`
273
+ had the same problem in its `McpServer` constructor, so an MCP client's `initialize` handshake
274
+ would have reported the same stale version. Both now read the real version through
275
+ `readOwnVersion()` in `src/core/version.ts`, which resolves `package.json` via `import.meta.url`.
276
+ Found by running the published package end to end rather than trusting `npm publish --dry-run`
277
+ and the registry API, neither of which executes a `--version` flag.
278
+
279
+ The ingestion and retrieval pipeline was never affected — only the two places that report a version
280
+ independently of running a command.
281
+
282
+ ## [0.1.1] — 2026-08-10
283
+
284
+ ### Changed
285
+
286
+ - README rewritten for someone deciding whether to read the source: what it does, how retrieval
287
+ scores, what it costs, and where it breaks. `README.md` ships inside the package, so this is a
288
+ real change to what npm delivers — but no code changed between 0.1.0 and 0.1.1.
289
+ - Documented the ranking flaw the tool found in itself, and the fact that the conversation source
290
+ in the sample `status` output is opt-in rather than default.
291
+ - Dropped `&&` from the quickstart, which Windows PowerShell 5.1 cannot parse.
292
+
293
+ ## [0.1.0] — 2026-08-10
294
+
295
+ First public release.
296
+
297
+ ### Added
298
+
299
+ - **Collectors.** Git history (commit metadata and diff stats, not diff bodies), shell commands with
300
+ exit codes via an opt-in PowerShell hook, tracked markdown docs via `git ls-files -- '*.md'`, and
301
+ opt-in assistant transcripts.
302
+ - **Hybrid retrieval.** SQLite FTS5 BM25 and `sqlite-vec` KNN over 768-dim embeddings, fused with
303
+ reciprocal rank fusion, then ranked by relevance against signal and recency priors and packed into
304
+ an explicit token budget.
305
+ - **MCP server** over stdio (`nexusmem mcp`) exposing `search_memory`, `sync_project` and
306
+ `get_status`, for Claude Desktop, Cursor, Windsurf and other MCP clients.
307
+ - **CLI**: `init`, `sync`, `query`, `status`, `mcp`, and `hook install|remove|status`, plus four
308
+ dry-run previews — `scan-git`, `scan-shell`, `scan-docs`, `scan-conversation` — that write nothing
309
+ and print the nodes ingestion would create with their signal scores.
310
+ - Content-addressed node ids (`sha256(projectId + kind + naturalKey)`), so `sync` is idempotent and
311
+ two clones of one repository share a memory namespace.
312
+ - Everything stays on the machine: one SQLite database in WAL mode under `<repo>/.nexusmem/`.
313
+
314
+ ### Notes
315
+
316
+ - Requires Node **>= 22**. `better-sqlite3` 12.11.1 publishes no prebuilt binary for Node 20 — its
317
+ prebuilds start at ABI 127 — so a lower floor would have been a promise the package could not keep.
318
+ Do not lower it without checking upstream prebuilds first.
319
+ - Not done at this release: diff bodies are not indexed, queries are scoped to a single project,
320
+ there is no local-model summarization pass, and the conversation collector has never been audited
321
+ for the stale-node bug that was found and fixed in the docs collector.
322
+
323
+ [Unreleased]: https://github.com/yaminbkk/NexusMem/compare/v0.4.0...HEAD
324
+ [0.4.0]: https://github.com/yaminbkk/NexusMem/compare/v0.3.3...v0.4.0
325
+ [0.3.3]: https://github.com/yaminbkk/NexusMem/compare/v0.3.2...v0.3.3
326
+ [0.3.2]: https://github.com/yaminbkk/NexusMem/compare/v0.3.1...v0.3.2
327
+ [0.3.1]: https://github.com/yaminbkk/NexusMem/compare/v0.3.0...v0.3.1
328
+ [0.3.0]: https://github.com/yaminbkk/NexusMem/compare/v0.2.0...v0.3.0
329
+ [0.2.0]: https://github.com/yaminbkk/NexusMem/compare/v0.1.2...v0.2.0
330
+ [0.1.2]: https://github.com/yaminbkk/NexusMem/compare/v0.1.1...v0.1.2
331
+ [0.1.1]: https://github.com/yaminbkk/NexusMem/compare/v0.1.0...v0.1.1
332
+ [0.1.0]: https://github.com/yaminbkk/NexusMem/releases/tag/v0.1.0