nexusmem 0.5.1 → 0.5.2
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 +32 -0
- package/README.md +72 -55
- package/dist/cli/index.js +262 -151
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,38 @@ built from, matched by publish timestamp: `v0.1.0` → `67a4776`, `v0.1.1` → `
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.5.2] — 2026-08-18
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`nexusmem mark-stale <nodeId> --supersedes <newNodeId>`, `provenance`, and manual staleness
|
|
17
|
+
tracking.** A pre-Show-HN pass answering a public critique that this couldn't tell an observed
|
|
18
|
+
fact from a guess, or retire a stale conclusion. Every node now carries `provenance`
|
|
19
|
+
(`observed`/`inferred`, set per-collector: git commits, diffs, and shell commands are `observed`;
|
|
20
|
+
conversation turns, session summaries, and doc sections are `inferred`) and an optional
|
|
21
|
+
`supersedes` link. `mark-stale` writes that link; the ranker applies a flat down-weight to whatever
|
|
22
|
+
it points at, but never deletes it -- the old node stays queryable, just usually loses to its
|
|
23
|
+
replacement. `provenance` is shown as a `[observed]`/`[inferred]` tag on every query result (CLI
|
|
24
|
+
and MCP `search_memory` share the same renderer). This is deliberately not automatic: nothing
|
|
25
|
+
detects staleness on its own, a human or agent still has to notice the contradiction and run the
|
|
26
|
+
command. See the README's new "Manual staleness & provenance" section.
|
|
27
|
+
|
|
28
|
+
### Verified
|
|
29
|
+
|
|
30
|
+
- **`forget` does not leak through vector search.** Checked whether `MemoryStore.forget` excludes a
|
|
31
|
+
tombstoned node from `sqlite-vec` results only via a query-time filter (which every vector query
|
|
32
|
+
path would need to apply consistently) or by deleting the embedding row outright. It already does
|
|
33
|
+
the latter -- `dropEmbedding` runs before the node row is deleted, in the same transaction, on both
|
|
34
|
+
query paths (`runHybridQuery` and `runCrossProjectQuery`, which both call the same
|
|
35
|
+
`MemoryStore.vectorSearch`). No fix was needed; added a regression test
|
|
36
|
+
(`tests/vector.test.ts`) that forgets an embedded node and asserts both `vectorSearch` returns
|
|
37
|
+
nothing and the `nodes_vec` row count drops to zero, so this can't silently regress.
|
|
38
|
+
- **`forget` survives `sync --rebuild`.** This was the point of v0.5.0 and was already covered
|
|
39
|
+
end-to-end in `tests/forget.test.ts`. Added a lower-level `tests/store.test.ts` case pinning the
|
|
40
|
+
exact mechanism: `clearProject` (what `--rebuild` calls before re-ingesting) only touches
|
|
41
|
+
`nodes`/`nodes_vec`/`sync_state`, never `deny_list`, so a re-ingest of the same content is denied
|
|
42
|
+
again rather than resurrected.
|
|
43
|
+
|
|
12
44
|
## [0.5.1] — 2026-08-17
|
|
13
45
|
|
|
14
46
|
### Added
|
package/README.md
CHANGED
|
@@ -21,6 +21,13 @@ The shell history is the part worth caring about. Git tells an agent what shippe
|
|
|
21
21
|
tells it what was attempted, in what order, and which commands exited non-zero. That information
|
|
22
22
|
exists nowhere else, and it disappears when your terminal scrollback rolls over.
|
|
23
23
|
|
|
24
|
+
**Contents:** [Try it](#try-it) · [Exact shell capture](#optional-exact-shell-capture) ·
|
|
25
|
+
[Failure → fix chains](#failure--fix-chains-opt-in) · [How retrieval works](#how-retrieval-works) ·
|
|
26
|
+
[Session summaries](#session-summaries-optional-local-model) · [Use it from an agent](#use-it-from-an-agent)
|
|
27
|
+
· [What it costs you](#what-it-costs-you) · [Manual staleness & provenance](#manual-staleness--provenance) ·
|
|
28
|
+
[Where it breaks](#where-it-breaks) · [Commands](#commands) · [Cross-project recall](#recall-across-projects)
|
|
29
|
+
· [On disk](#on-disk) · [Development](#development)
|
|
30
|
+
|
|
24
31
|
## Try it
|
|
25
32
|
|
|
26
33
|
From inside any git repository:
|
|
@@ -37,14 +44,17 @@ $ nexusmem query "windows spawn failure"
|
|
|
37
44
|
|
|
38
45
|
Relevant history for: windows spawn failure
|
|
39
46
|
|
|
40
|
-
- 2026-08-09 fix: distinguish a failed git spawn from "not a git repository"
|
|
47
|
+
- 2026-08-09 [observed] fix: distinguish a failed git spawn from "not a git repository"
|
|
41
48
|
readRepoInfo collapsed three unrelated failures into one error: git running and reporting
|
|
42
49
|
the path is not a work tree, git not being installed, and the process failing to spawn at
|
|
43
50
|
all. Dogfooding hit the third case in two separate sessions...
|
|
44
|
-
- 2026-08-09 README.md — Before a tagged release
|
|
51
|
+
- 2026-08-09 [inferred] README.md — Before a tagged release
|
|
45
52
|
- [ ] Retry on transient process-spawn failures on Windows
|
|
46
53
|
```
|
|
47
54
|
|
|
55
|
+
`[observed]`/`[inferred]` is the provenance tag (see [Manual staleness & provenance](#manual-staleness--provenance))
|
|
56
|
+
— a commit is a directly observed event, a doc section is a written claim that could go stale.
|
|
57
|
+
|
|
48
58
|
A commit and a docs section, ranked against each other, inside whatever token budget you gave it.
|
|
49
59
|
Nothing was summarized by a model on the way out; the ranker just decided what not to send. (One
|
|
50
60
|
optional source, session summaries, does run a local model — but at ingest time, never on the way
|
|
@@ -117,19 +127,17 @@ project they were found in.
|
|
|
117
127
|
```
|
|
118
128
|
$ nexusmem query "why did npm whoami fail"
|
|
119
129
|
|
|
120
|
-
- 2026-08-12 shell: npm whoami (exit 1)
|
|
121
|
-
- 2026-08-12 shell: npm login (exit 0) -- linked as the fix
|
|
130
|
+
- 2026-08-12 [observed] shell: npm whoami (exit 1)
|
|
131
|
+
- 2026-08-12 [observed] shell: npm login (exit 0) -- linked as the fix
|
|
122
132
|
```
|
|
123
133
|
|
|
124
134
|
## How retrieval works
|
|
125
135
|
|
|
126
136
|
Every source normalizes to the same `MemoryNode` shape, so a commit, a shell command and a docs
|
|
127
137
|
section compete on equal terms. Retrieval runs BM25 over FTS5 and, if an embedding model is
|
|
128
|
-
reachable, a vector search over `sqlite-vec`,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
BM25 cost and a vector distance live on unrelated, unbounded scales, and position is the only thing
|
|
132
|
-
they agree on. No hand-tuned normalization constant sits between them.
|
|
138
|
+
reachable, a vector search over `sqlite-vec`, fused with Reciprocal Rank Fusion on rank *position*
|
|
139
|
+
only, never raw scores — a BM25 cost and a vector distance live on unrelated, unbounded scales, and
|
|
140
|
+
position is the only thing they agree on.
|
|
133
141
|
|
|
134
142
|
Ranking then multiplies three factors:
|
|
135
143
|
|
|
@@ -137,27 +145,19 @@ Ranking then multiplies three factors:
|
|
|
137
145
|
score = relevance × signal^0.215 × recency^0.288
|
|
138
146
|
```
|
|
139
147
|
|
|
140
|
-
`relevance` comes from the query. `signal` (a `fix:` commit outranks a `chore:`; a command
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
zero out a strong match.
|
|
148
|
+
`relevance` comes from the query. `signal` (a `fix:` commit outranks a `chore:`; a failed command
|
|
149
|
+
outranks a successful one) and `recency` are priors that hold before any query exists. Each factor is
|
|
150
|
+
floored into `[floor, 1]` rather than `[0, 1]`, so one weak dimension can't zero out a strong match.
|
|
144
151
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
The exponents bound how far signal and recency, *together*, may overturn relevance: at most a 2× gap
|
|
153
|
+
across their whole range, applied jointly rather than per-prior. That's deliberate — the score
|
|
154
|
+
*multiplies* the two priors, so capping each at 2× separately still let the pair overturn 4×, and
|
|
155
|
+
that hit hardest on fresh, high-signal commits made during an active working day. The bug that
|
|
156
|
+
exposed this: two unrelated same-day `fix:` commits outranked the docs section that actually answered
|
|
157
|
+
the query. See [`retrieval/rank.ts`](src/retrieval/rank.ts) for the full derivation.
|
|
151
158
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
overturn 4×. That describes every commit made during an active working day — fresh and high-signal at
|
|
155
|
-
once — so the failure landed on precisely the days with the most worth remembering. A query about the
|
|
156
|
-
PowerShell hook returned two unrelated same-day `fix:` commits at ranks 3 and 4 while the section that
|
|
157
|
-
answered it sat at rank 6. Adding a third prior now re-divides the same budget instead of enlarging it.
|
|
158
|
-
|
|
159
|
-
Without Ollama, vector search is skipped and you get BM25 only. That path is fully supported, not a
|
|
160
|
-
degraded error state; `sync` and `query` both succeed and simply do less.
|
|
159
|
+
Without Ollama, vector search is skipped and you get BM25 only — fully supported, not a degraded
|
|
160
|
+
state; `sync` and `query` both succeed and simply do less.
|
|
161
161
|
|
|
162
162
|
## Session summaries (optional, local model)
|
|
163
163
|
|
|
@@ -228,27 +228,21 @@ and points it at a synced corpus can re-run from scratch:
|
|
|
228
228
|
Both clear the original >70% target ("cut API token spend versus sending full context"), and the vite
|
|
229
229
|
run is the first measurement at the scale that target was always described as applying to.
|
|
230
230
|
|
|
231
|
-
**Read the methodology before quoting either number
|
|
232
|
-
|
|
233
|
-
-
|
|
234
|
-
packed nodes for that query touch
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
noise: at this scale, "just read the file's history instead" stops being a viable alternative at
|
|
247
|
-
all, which is a big part of why that column rounds to ~100%.
|
|
248
|
-
- **This supersedes the previous ~40% figure**, which was hand-tallied from two hand-picked queries
|
|
249
|
-
against this repo alone, never instrumented, and used an unstated baseline. It was not wrong so much
|
|
250
|
-
as underspecified — this number replaces it with a stated method and a script that reproduces it,
|
|
251
|
-
rather than being a claim that the product got better.
|
|
231
|
+
**Read the methodology before quoting either number — it's a narrower claim than it looks:**
|
|
232
|
+
|
|
233
|
+
- **Graded against NexusMem's own ranking**, not an outside answer key: the file set is whichever
|
|
234
|
+
files the packed nodes for that query touch. This measures what the pack step saves once retrieval
|
|
235
|
+
already picked a candidate set; it doesn't independently verify that set was the right one.
|
|
236
|
+
- **Query sets are mechanical, not cherry-picked** (see `scripts/benchmark.ts`): vite's is an even
|
|
237
|
+
sample of well-explained `fix`/`feat`/`perf`/`refactor` commits plus rationale-bearing doc headings;
|
|
238
|
+
this repo's reuses real historical prompts verbatim, several of which are broad task instructions
|
|
239
|
+
rather than narrow questions — part of why its number sits below vite's.
|
|
240
|
+
- **`git log -p` baselines can be enormous** — one vite query's baseline hit 7.5M tokens because a
|
|
241
|
+
file in its resolved set has that much history. At that scale, "just read the file's history
|
|
242
|
+
instead" stops being a viable alternative at all.
|
|
243
|
+
- **Supersedes the old ~40% figure**, which was hand-tallied from two hand-picked queries against
|
|
244
|
+
this repo alone with an unstated baseline. Not wrong, just underspecified — this replaces it with a
|
|
245
|
+
stated method and a script that reproduces it.
|
|
252
246
|
|
|
253
247
|
One thing that is not a percentage: shell commands and conversation turns have no cheap `grep`
|
|
254
248
|
equivalent. Without something recording them, they are gone, not merely more expensive to find.
|
|
@@ -269,6 +263,29 @@ Latency on a ~530-node corpus, warm, p50 over 10 runs:
|
|
|
269
263
|
All the SQLite work totals about 5 ms. The embedding call is the only thing on this path worth
|
|
270
264
|
optimizing, and it is somebody else's process.
|
|
271
265
|
|
|
266
|
+
## Manual staleness & provenance
|
|
267
|
+
|
|
268
|
+
Two things a memory layer needs and this one only partly has: a way to tell an observed fact from a
|
|
269
|
+
guess, and a way to retire a conclusion once something contradicts it. Neither is automatic here —
|
|
270
|
+
this section is what exists and what doesn't.
|
|
271
|
+
|
|
272
|
+
Every node carries a `provenance`: `observed` (a commit that landed, a shell command's real exit
|
|
273
|
+
code) or `inferred` (a conversation turn, a session summary, a doc section — all readable as claims
|
|
274
|
+
that could be wrong or go stale). Set once per collector at ingest time, and shown as a `[observed]`
|
|
275
|
+
/ `[inferred]` tag on every query result.
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
nexusmem mark-stale <oldNodeId> --supersedes <newNodeId>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Links `newNodeId` as the replacement for `oldNodeId`. The ranker down-weights the old node from then
|
|
282
|
+
on (it stays queryable, just usually loses to its replacement) — nothing is deleted, unlike `forget`.
|
|
283
|
+
|
|
284
|
+
**What this doesn't do:** nothing here detects staleness on its own. If a later commit contradicts an
|
|
285
|
+
earlier doc section, NexusMem has no way to notice that and flag it — a human or an agent has to spot
|
|
286
|
+
the contradiction and run `mark-stale` themselves. Automatic staleness detection is still an open
|
|
287
|
+
problem.
|
|
288
|
+
|
|
272
289
|
## Where it breaks
|
|
273
290
|
|
|
274
291
|
- **Shell history without the hook is unscoped.** Scraped history has no directory context, so it is
|
|
@@ -334,8 +351,8 @@ optimizing, and it is somebody else's process.
|
|
|
334
351
|
|
|
335
352
|
## Commands
|
|
336
353
|
|
|
337
|
-
`init`, `sync`, `query <text>`, `status`, `projects`, `mcp`, `forget <value>`,
|
|
338
|
-
`hook install|remove|status`.
|
|
354
|
+
`init`, `sync`, `query <text>`, `status`, `projects`, `mcp`, `forget <value>`,
|
|
355
|
+
`mark-stale <nodeId> --supersedes <newNodeId>`, and `hook install|remove|status`.
|
|
339
356
|
|
|
340
357
|
There are also five dry-run previews (`scan-git`, `scan-diff`, `scan-shell`, `scan-docs`,
|
|
341
358
|
`scan-conversation`)
|
|
@@ -366,12 +383,12 @@ and tags each result with the repository it came from:
|
|
|
366
383
|
$ nexusmem query --all-projects "why was the retry budget raised"
|
|
367
384
|
scope 2 project(s): NexusMem, uploader
|
|
368
385
|
|
|
369
|
-
- 2026-08-12 [uploader] fix: raise the retry budget after the S3 upload timeouts
|
|
370
|
-
- 2026-08-12 [uploader] retry.ts @ 8d0f98b — fix: raise the retry budget after the S3 upload timeouts
|
|
386
|
+
- 2026-08-12 [observed] [uploader] fix: raise the retry budget after the S3 upload timeouts
|
|
387
|
+
- 2026-08-12 [observed] [uploader] retry.ts @ 8d0f98b — fix: raise the retry budget after the S3 upload timeouts
|
|
371
388
|
@@ -1 +1 @@
|
|
372
389
|
-export const RETRY_BUDGET = 3;
|
|
373
390
|
+export const RETRY_BUDGET = 5;
|
|
374
|
-
- 2026-08-09 [NexusMem] fix(git): retry a transient failure to spawn git
|
|
391
|
+
- 2026-08-09 [observed] [NexusMem] fix(git): retry a transient failure to spawn git
|
|
375
392
|
```
|
|
376
393
|
|
|
377
394
|
Databases stay per-repository — there is no shared global store, and deleting one repo's
|
|
@@ -409,7 +426,7 @@ Deleting `.nexusmem/` loses nothing that `sync` cannot rebuild.
|
|
|
409
426
|
|
|
410
427
|
## Status
|
|
411
428
|
|
|
412
|
-
Ingestion, hybrid retrieval, budgeted packing and the MCP server all work and are covered by
|
|
429
|
+
Ingestion, hybrid retrieval, budgeted packing and the MCP server all work and are covered by 493
|
|
413
430
|
tests running on Linux and Windows across Node 22 and 24.
|
|
414
431
|
|
|
415
432
|
## Development
|