nexusmem 0.7.0 → 0.9.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
@@ -11,6 +11,63 @@ built from, matched by publish timestamp: `v0.1.0` → `67a4776`, `v0.1.1` → `
11
11
 
12
12
  No unreleased changes yet.
13
13
 
14
+ ## [0.9.0] — 2026-08-27
15
+
16
+ Closes the three remaining mechanisms from a recurring external review (Simon Strandgaard, Agent
17
+ Memory Atlas): trust_state, bi-temporal reads, and a dismiss verb for standing suggestions. Also
18
+ fixes two real Windows-specific correctness bugs found dogfooding.
19
+
20
+ ### Added
21
+
22
+ - `nodes.trust_state` (`candidate` | `verified` | `rejected`, schema V10) and `nexusmem review
23
+ <nodeId> --verify|--reject`: a human verdict on a node, independent of the SLM contradiction
24
+ checker. `--reject` down-weights ranking (harsher than the existing supersede penalty, since a
25
+ human said no directly); `--verify` is a label only, no ranking boost. Both render as a tag in
26
+ packed context. Never deletes — same demote-not-delete rule as `mark-stale`.
27
+ - `--as-of <date>` on `nexusmem query` and the MCP `search_memory` tool: restricts both the BM25
28
+ and vector arms to nodes recorded at or before that instant via `created_at` (record time),
29
+ independent of how old the events themselves (`ts`, event time) are. Read-only — there is no
30
+ equivalent write, and nodes stay write-once.
31
+ - `nexusmem stale --dismiss`: silences a contradiction suggestion the user disagrees with, without
32
+ fabricating a `supersedes` relationship. Previously a wrong `--check-contradictions` verdict had
33
+ no way to be rejected — it re-decorated every future `stale`/`sync` run forever.
34
+ - `--prune-source`/`--prune-stale-shell` now record a `mutation_audit` row on their `--yes` path,
35
+ matching what `forget` already did. The dry-run preview stays a pure read.
36
+
37
+ ### Fixed
38
+
39
+ - `git rev-parse` failures reported as "error launching git: Access is denied." (Git for Windows'
40
+ launcher shim failing to exec `git.exe` under handle/AV contention) were treated as git's own
41
+ verdict instead of a transient spawn failure, turning a one-off environment hiccup into a hard
42
+ failure. Now retried the same as the other known transient-spawn classes.
43
+ - The PowerShell prompt hook read only `$LASTEXITCODE`, which cmdlets (`Remove-Item`, `Copy-Item`,
44
+ …) never set — every failing cmdlet was silently logged as a success, and a stale
45
+ `$LASTEXITCODE` from an earlier native command could misattribute to later successful cmdlet
46
+ runs. Now reads `$?` alongside `$LASTEXITCODE`, before anything else can overwrite `$?`.
47
+
48
+ ## [0.8.0] — 2026-08-22
49
+
50
+ ### Added
51
+
52
+ - Provenance widened from 2 tiers to a 4-tier trust hierarchy: `observed` (commits, diffs, shell
53
+ exit codes) > `authored` (doc sections — human-written claims) > `recorded` (conversation turns —
54
+ verbatim discourse) > `derived` (session summaries — a model's distillation). Schema V7 backfills
55
+ existing databases by kind. The ranker decays each tier at its own rate (lower trust fades
56
+ faster); the ordering is the design claim, the exact ratios are documented judgment calls.
57
+ - Contradiction checking now runs automatically during `sync`: at most 3 new SLM judgments per run
58
+ (`contradictions.maxPerSync`; `contradictions.autoCheck: false` turns it off), gated on the
59
+ embedding provider already being reachable. Every judgment — either verdict — is memoized in a new
60
+ `contradiction_checks` table (schema V8), so a judged pair is never sent to the model again and
61
+ repeat syncs converge to zero model calls. Measured live against this repo's own database: 5.4s
62
+ for the first 10 judgments, 0.5s for the identical re-run. Still suggest-only: nothing ever writes
63
+ `supersedes` automatically.
64
+ - Standing suggestions now surface everywhere without a model in the loop: plain `nexusmem stale`
65
+ decorates flagged candidates from the memoized judgments (instant, offline), `nexusmem status`
66
+ gains a `flagged` line, and the sync summary reports new/open suggestion counts.
67
+ - `stale --check-contradictions` reuses each candidate's stored embedding instead of re-embedding
68
+ it, and stops after two consecutive null SLM replies so a down provider costs at most two timeouts
69
+ rather than one per candidate.
70
+
14
71
  ## [0.7.0] — 2026-08-21
15
72
 
16
73
  ### Added
@@ -481,7 +538,9 @@ First public release.
481
538
  there is no local-model summarization pass, and the conversation collector has never been audited
482
539
  for the stale-node bug that was found and fixed in the docs collector.
483
540
 
484
- [Unreleased]: https://github.com/yaminbkk/NexusMem/compare/v0.7.0...HEAD
541
+ [Unreleased]: https://github.com/yaminbkk/NexusMem/compare/v0.9.0...HEAD
542
+ [0.9.0]: https://github.com/yaminbkk/NexusMem/compare/v0.8.0...v0.9.0
543
+ [0.8.0]: https://github.com/yaminbkk/NexusMem/compare/v0.7.0...v0.8.0
485
544
  [0.7.0]: https://github.com/yaminbkk/NexusMem/compare/v0.6.0...v0.7.0
486
545
  [0.6.0]: https://github.com/yaminbkk/NexusMem/compare/v0.5.4...v0.6.0
487
546
  [0.5.4]: https://github.com/yaminbkk/NexusMem/compare/v0.5.3...v0.5.4
package/README.md CHANGED
@@ -24,7 +24,7 @@ exists nowhere else, and it disappears when your terminal scrollback rolls over.
24
24
  **Contents:** [Try it](#try-it) · [Exact shell capture](#optional-exact-shell-capture) ·
25
25
  [Failure → fix chains](#failure--fix-chains-opt-in) · [How retrieval works](#how-retrieval-works) ·
26
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) ·
27
+ · [What it costs you](#what-it-costs-you) · [Staleness & provenance](#staleness--provenance) ·
28
28
  [Where it breaks](#where-it-breaks) · [Commands](#commands) · [Cross-project recall](#recall-across-projects)
29
29
  · [On disk](#on-disk) · [Development](#development)
30
30
 
@@ -48,11 +48,11 @@ Relevant history for: windows spawn failure
48
48
  readRepoInfo collapsed three unrelated failures into one error: git running and reporting
49
49
  the path is not a work tree, git not being installed, and the process failing to spawn at
50
50
  all. Dogfooding hit the third case in two separate sessions...
51
- - 2026-08-09 [inferred] README.md — Before a tagged release
51
+ - 2026-08-09 [authored] README.md — Before a tagged release
52
52
  - [ ] Retry on transient process-spawn failures on Windows
53
53
  ```
54
54
 
55
- `[observed]`/`[inferred]` is the provenance tag (see [Manual staleness & provenance](#manual-staleness--provenance))
55
+ `[observed]`/`[authored]` is the provenance tag (see [Staleness & provenance](#staleness--provenance))
56
56
  — a commit is a directly observed event, a doc section is a written claim that could go stale.
57
57
 
58
58
  A commit and a docs section, ranked against each other, inside whatever token budget you gave it.
@@ -248,7 +248,9 @@ One thing that is not a percentage: shell commands and conversation turns have n
248
248
  equivalent. Without something recording them, they are gone, not merely more expensive to find.
249
249
 
250
250
  For how these numbers compare to a similar tool's own claims, see
251
- [`docs/competitor-comparison.md`](docs/competitor-comparison.md).
251
+ [`docs/competitor-comparison.md`](docs/competitor-comparison.md) (vs. projectmem) and
252
+ [`docs/competitor-comparison-yesmem.md`](docs/competitor-comparison-yesmem.md) (vs. YesMem, including
253
+ native Windows support vs. its documented WSL2 requirement).
252
254
 
253
255
  Latency on a ~530-node corpus, warm, p50 over 10 runs:
254
256
 
@@ -269,19 +271,23 @@ Two things a memory layer needs and this one only partly has: a way to tell an o
269
271
  guess, and a way to retire a conclusion once something contradicts it. This section is what exists
270
272
  and what doesn't.
271
273
 
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, shown as a `[observed]` /
275
- `[inferred]` tag on every query result, and now used to decay retrieval weight too `inferred` nodes
276
- fade from ranking twice as fast as `observed` ones as they age.
274
+ Every node carries a `provenance`, a four-tier trust hierarchy set once per collector at ingest
275
+ time: `observed` (a commit that landed, a shell command's real exit code) > `authored` (a doc
276
+ section a human's own written claim) > `recorded` (a conversation turn verbatim, but talk about
277
+ events rather than the events) > `derived` (a session summary a model's distillation). The tier is
278
+ shown as a tag on every query result and decays retrieval weight — the lower the trust, the faster a
279
+ node fades from ranking as it ages. The ordering is the design claim; the exact decay ratios are
280
+ judgment calls, not measured optima.
277
281
 
278
282
  ```bash
279
283
  nexusmem stale
280
284
  ```
281
285
 
282
- Lists `inferred` nodes old enough (45+ days by default) that nothing has confirmed they still hold —
283
- a heuristic on age and provenance, not on content. It writes nothing; you decide which candidates are
284
- actually wrong.
286
+ Lists non-`observed` nodes old enough (45+ days by default) that nothing has confirmed they still
287
+ hold — a heuristic on age and provenance, not on content. It writes nothing; you decide which
288
+ candidates are actually wrong. Any candidate the SLM has already flagged (see below) is decorated
289
+ with its standing `likely superseded by` suggestion — reading those costs nothing, so the plain
290
+ command stays instant and offline.
285
291
 
286
292
  ```bash
287
293
  nexusmem mark-stale <oldNodeId> --supersedes <newNodeId>
@@ -297,15 +303,35 @@ nexusmem stale --check-contradictions
297
303
  For each candidate, finds the most similar newer node (local embedding search) and asks a local SLM
298
304
  (Ollama, `qwen2.5:3b` by default) whether it actually contradicts the older one — real content
299
305
  comparison, not just age. A match is printed as `likely superseded by <id> <title> -- <reason>`
300
- under the candidate; nothing is written, same as plain `stale`. Needs Ollama running; costs one
301
- embedding call and, when a plausible newer node exists, one chat completion per candidate (capped at
302
- 10 by default — pass `-n` to raise it).
306
+ under the candidate. Every judgment (either verdict) is memoized, so a judged pair is never sent to
307
+ the model again; nothing else is written `supersedes` stays yours to set via `mark-stale`.
308
+
309
+ **This also runs automatically during `sync`** — at most 3 new judgments per run (configurable via
310
+ the `contradictions` block in `.nexusmem/config.json`; set `autoCheck: false` to turn it off), only
311
+ when the embedding provider was reachable anyway, and free on repeat syncs thanks to the
312
+ memoization. New and open suggestions show up in the sync summary, `nexusmem status` (a `flagged`
313
+ line), and plain `nexusmem stale`.
303
314
 
304
315
  **What this doesn't do:** it is one small model's yes/no judgment on one older/newer pair, not a
305
316
  verified fact — treat a match as a lead to check, not a conclusion. It also only ever compares a
306
317
  candidate against nodes *found by embedding similarity*; a contradiction from an unrelated-sounding
307
- node would never surface. Real contradiction detection (comprehensively, not just for the pair the
308
- vector search happens to surface) is still an open problem.
318
+ node would never surface. Comprehensive contradiction detection (not just for the pair the vector
319
+ search happens to surface) is still an open problem, and nothing here supersedes a node on its own.
320
+
321
+ `provenance` is a separate question from `trust_state`: provenance says where a claim came from,
322
+ never whether anyone checked it.
323
+
324
+ ```bash
325
+ nexusmem review <nodeId> --verify
326
+ nexusmem review <nodeId> --reject
327
+ ```
328
+
329
+ Records your own verdict on one node, independent of the SLM contradiction checker above (which only
330
+ ever writes a suggestion, never a verdict). `--reject` down-weights the node in ranking — same
331
+ demote-not-delete rule as `mark-stale`, it stays queryable, just usually loses to better matches —
332
+ and both verdicts are shown as a `[verified]`/`[rejected]` tag on every query result that returns the
333
+ node afterward. `--verify` is a label only; it does not boost ranking. Every node starts `candidate`
334
+ (untagged) until reviewed, and a re-sync never overwrites a verdict once one is set.
309
335
 
310
336
  ## Where it breaks
311
337
 
@@ -372,16 +398,20 @@ vector search happens to surface) is still an open problem.
372
398
 
373
399
  ## Commands
374
400
 
375
- `init`, `sync`, `query <text>`, `status` (add `--share` for a plain-text summary worth pasting
376
- somewhere), `projects`, `mcp`, `forget <value>`, `stale` (add `--check-contradictions` for a local-SLM
377
- content check, see above), `mark-stale <nodeId> --supersedes <newNodeId>`, and
378
- `hook install|remove|status`.
379
-
380
- There are also five dry-run previews (`scan-git`, `scan-diff`, `scan-shell`, `scan-docs`,
381
- `scan-conversation`)
382
- that write nothing and print the nodes ingestion *would* create along with their signal scores. That
383
- is the intended way to tune scoring against a real repository before committing to a change. Add
384
- `--json` to pipe them somewhere.
401
+ `init`, `sync`, `query <text>` (add `--as-of <date>` for a bi-temporal read, see below), `status` (add
402
+ `--share` for a plain-text summary worth pasting somewhere), `projects`, `mcp`, `forget <value>`,
403
+ `stale` (add `--check-contradictions` for a local-SLM content check, see above), `mark-stale
404
+ <nodeId> --supersedes <newNodeId>`, `review <nodeId> --verify|--reject` (record a human verdict on
405
+ one node, see above), `precheck` (advisory — warns about staged files with an unresolved past
406
+ failure or high recent churn; exits 0 unless `--strict`), `hook install|remove|status` (the
407
+ PowerShell exit-code hook), and `hook git install|remove|status` (a git pre-commit hook that runs
408
+ `precheck` before each commit).
409
+
410
+ There are also seven dry-run previews (`scan-git`, `scan-diff`, `scan-shell`, `scan-docs`,
411
+ `scan-conversation`, `scan-session`, `scan-structure`) that write nothing and print what ingestion
412
+ *would* produce — nodes and their signal scores for the first six, import-graph edges for
413
+ `scan-structure`. That is the intended way to tune scoring against a real repository before
414
+ committing to a change. Add `--json` to pipe them somewhere.
385
415
 
386
416
  Every command takes `-C <path>` to target another repository. On `sync`, `--conversation` opts the
387
417
  transcript source in for one run without persisting it, `--no-embed` skips the vector pass,
@@ -414,6 +444,24 @@ scope 2 project(s): NexusMem, uploader
414
444
  - 2026-08-09 [observed] [NexusMem] fix(git): retry a transient failure to spawn git
415
445
  ```
416
446
 
447
+ ## Bi-temporal reads
448
+
449
+ Every node carries two clocks: `ts`, the event's own time ("what happened then"), and `created_at`,
450
+ the moment the store actually recorded it ("what did the store hold then") — normally the same
451
+ question, but not when a sync runs late, a backfill lands weeks after the events it describes, or a
452
+ teammate's clone catches up all at once. `query`/`search_memory` answer the first by default;
453
+ `--as-of <date>` switches to the second:
454
+
455
+ ```bash
456
+ nexusmem query "why does the ranker cap joint priors" --as-of 2026-08-10
457
+ ```
458
+
459
+ Only nodes recorded at or before that instant are considered, even if the events they describe are
460
+ older still. There is no equivalent write — this is a read-time filter over `created_at`, not a
461
+ snapshot or a way to query a value that has since changed, since nodes are write-once (see
462
+ [Staleness & provenance](#staleness--provenance) for what changes a node's *weight*, not its
463
+ record).
464
+
417
465
  Databases stay per-repository — there is no shared global store, and deleting one repo's
418
466
  `.nexusmem/` still removes exactly that repo's memory. What makes the others findable is a plain
419
467
  index at `~/.nexusmem/projects.json`, written by `init` and refreshed by every `sync`. `nexusmem
@@ -449,7 +497,7 @@ Deleting `.nexusmem/` loses nothing that `sync` cannot rebuild.
449
497
 
450
498
  ## Status
451
499
 
452
- Ingestion, hybrid retrieval, budgeted packing and the MCP server all work and are covered by 493
500
+ Ingestion, hybrid retrieval, budgeted packing and the MCP server all work and are covered by 701
453
501
  tests running on Linux and Windows across Node 22 and 24.
454
502
 
455
503
  ## Development