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