nexusmem 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -11,6 +11,29 @@ 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.10.1] — 2026-08-30
15
+
16
+ ### Added
17
+
18
+ - `nodes.retrieved_count`/`nodes.last_retrieved_at` (schema V12): retrieval outcomes are now recorded
19
+ instead of silently discarded. Bumped once per completed `nexusmem query`/MCP `search_memory` call,
20
+ for every node actually packed into the returned context (both CLI and MCP share one pipeline, so
21
+ both are covered from a single call site). Not yet folded into `rank.ts`'s score formula — every
22
+ existing ranking factor was tuned against real dogfooded queries and validated with `npm run eval`
23
+ before being trusted, and a new factor needs the same treatment. This release is the instrumentation
24
+ half only; confirmed eval-neutral (MRR 0.943 / Recall@5 0.964, unchanged).
25
+
26
+ ### Fixed
27
+
28
+ - `nexusmem precheck`'s "What already failed here" warning implied the failure was located in the
29
+ flagged file, but the match is against basename word-tokens (`tokensForFile`) — a failing `npm run
30
+ precheck` flagged every file whose name contained that word, not just the one actually responsible.
31
+ Reworded to state what's actually true ("commands naming this file failed").
32
+ - The high-churn warning in `nexusmem precheck` was rendered as a `WARN`, the same severity as the
33
+ dogfooded failure-correlation warning, despite `HIGH_CHURN_THRESHOLD` being an admitted, untuned
34
+ guess. Demoted to a lower-severity `note`, explicitly labeled as an untuned heuristic, so it no
35
+ longer reads as equally trustworthy.
36
+
14
37
  ## [0.10.0] — 2026-08-29
15
38
 
16
39
  ### Added
@@ -575,7 +598,8 @@ First public release.
575
598
  there is no local-model summarization pass, and the conversation collector has never been audited
576
599
  for the stale-node bug that was found and fixed in the docs collector.
577
600
 
578
- [Unreleased]: https://github.com/yaminbkk/NexusMem/compare/v0.10.0...HEAD
601
+ [Unreleased]: https://github.com/yaminbkk/NexusMem/compare/v0.10.1...HEAD
602
+ [0.10.1]: https://github.com/yaminbkk/NexusMem/compare/v0.10.0...v0.10.1
579
603
  [0.10.0]: https://github.com/yaminbkk/NexusMem/compare/v0.9.1...v0.10.0
580
604
  [0.9.1]: https://github.com/yaminbkk/NexusMem/compare/v0.9.0...v0.9.1
581
605
  [0.9.0]: https://github.com/yaminbkk/NexusMem/compare/v0.8.0...v0.9.0
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![License: MIT](https://img.shields.io/badge/license-MIT-informational)](LICENSE)
7
7
  ![Node](https://img.shields.io/badge/node-%3E%3D22-brightgreen)
8
8
 
9
- ![NexusMem: init, sync, status, and a query against this repo's own history](docs/demo.gif)
9
+ ![NexusMem: init, sync --github, and a query against this repo's own history — surfacing a real issue, the PR that closed it, and the commits it shipped](docs/demo.gif)
10
10
 
11
11
  Your coding agent can read `git log`. It cannot read the four things you tried last Tuesday that
12
12
  didn't work.
package/dist/cli/index.js CHANGED
@@ -1033,6 +1033,10 @@ INSERT INTO nodes_vec (rowid, project_id, embedding)
1033
1033
 
1034
1034
  DROP TABLE nodes_vec_stage;
1035
1035
  `;
1036
+ var V12 = `
1037
+ ALTER TABLE nodes ADD COLUMN retrieved_count INTEGER NOT NULL DEFAULT 0;
1038
+ ALTER TABLE nodes ADD COLUMN last_retrieved_at INTEGER;
1039
+ `;
1036
1040
  var MIGRATIONS = [
1037
1041
  { version: 1, up: (db) => db.exec(V1) },
1038
1042
  { version: 2, up: (db) => db.exec(V2) },
@@ -1044,7 +1048,8 @@ var MIGRATIONS = [
1044
1048
  { version: 8, up: (db) => db.exec(V8) },
1045
1049
  { version: 9, up: (db) => db.exec(V9) },
1046
1050
  { version: 10, up: (db) => db.exec(V10) },
1047
- { version: 11, up: (db) => db.exec(V11) }
1051
+ { version: 11, up: (db) => db.exec(V11) },
1052
+ { version: 12, up: (db) => db.exec(V12) }
1048
1053
  ];
1049
1054
  var LATEST_SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1]?.version ?? 0;
1050
1055
  function currentSchemaVersion(db) {
@@ -1316,6 +1321,19 @@ function listStaleCandidates(db, projectId, opts = {}) {
1316
1321
  ageDays: Math.round((now.getTime() - r.tsEpoch) / 864e5)
1317
1322
  }));
1318
1323
  }
1324
+ function recordRetrievals(db, ids) {
1325
+ if (ids.length === 0) return;
1326
+ db.prepare(
1327
+ `UPDATE nodes SET retrieved_count = retrieved_count + 1, last_retrieved_at = @now
1328
+ WHERE id IN (SELECT value FROM json_each(@ids))`
1329
+ ).run({ now: Date.now(), ids: JSON.stringify(ids) });
1330
+ }
1331
+ function getRetrievalStats(db, id) {
1332
+ const row = db.prepare("SELECT retrieved_count AS retrievedCount, last_retrieved_at AS lastRetrievedAt FROM nodes WHERE id = ?").get(
1333
+ id
1334
+ );
1335
+ return row ?? null;
1336
+ }
1319
1337
  function countStaleCandidates(db, projectId, opts = {}) {
1320
1338
  const now = opts.now ?? /* @__PURE__ */ new Date();
1321
1339
  const minAgeDays = opts.minAgeDays ?? 45;
@@ -1845,6 +1863,14 @@ var MemoryStore = class _MemoryStore {
1845
1863
  countStaleCandidates(projectId, opts = {}) {
1846
1864
  return countStaleCandidates(this.db, projectId, opts);
1847
1865
  }
1866
+ /** Bump retrieval bookkeeping for nodes actually packed into a query result. See `nodes.ts`'s `recordRetrievals` for why this doesn't touch ranking. */
1867
+ recordRetrievals(ids) {
1868
+ recordRetrievals(this.db, ids);
1869
+ }
1870
+ /** Retrieval bookkeeping for one node, or null if it doesn't exist. Mainly for tests. */
1871
+ getRetrievalStats(id) {
1872
+ return getRetrievalStats(this.db, id);
1873
+ }
1848
1874
  /** Memoize one SLM contradiction judgment (either verdict). Suggest-only: never writes `supersedes`. */
1849
1875
  recordContradictionCheck(input) {
1850
1876
  recordContradictionCheck(this.db, input);
@@ -2812,8 +2838,21 @@ async function runCrossProjectQuery(sources, query, opts) {
2812
2838
  rankHits(hits, { halfLifeDays: opts.halfLifeDays, relevanceScores, supersededIds })
2813
2839
  );
2814
2840
  const packed = packContext(ranked, opts.budget, { query });
2841
+ recordRetrievalsByProject(storeByLabel, packed.nodes);
2815
2842
  return { bm25Count, vectorCount, hits, packed, perProject };
2816
2843
  }
2844
+ function recordRetrievalsByProject(storeByLabel, nodes) {
2845
+ const idsByLabel = /* @__PURE__ */ new Map();
2846
+ for (const node of nodes) {
2847
+ if (!node.project) continue;
2848
+ const ids = idsByLabel.get(node.project) ?? [];
2849
+ ids.push(node.id);
2850
+ idsByLabel.set(node.project, ids);
2851
+ }
2852
+ for (const [label, ids] of idsByLabel) {
2853
+ storeByLabel.get(label)?.recordRetrievals(ids);
2854
+ }
2855
+ }
2817
2856
  async function runHybridQuery(store, projectId, query, opts) {
2818
2857
  const bm25Hits = store.search(projectId, query, opts.candidates, { asOfEpoch: opts.asOfEpoch });
2819
2858
  let vectorHits = [];
@@ -2829,6 +2868,7 @@ async function runHybridQuery(store, projectId, query, opts) {
2829
2868
  rankHits(hits, { halfLifeDays: opts.halfLifeDays, relevanceScores, supersededIds })
2830
2869
  );
2831
2870
  const packed = packContext(ranked, opts.budget, { query });
2871
+ store.recordRetrievals(packed.nodes.map((n) => n.id));
2832
2872
  return { bm25Count: bm25Hits.length, vectorCount: vectorHits.length, hits, packed };
2833
2873
  }
2834
2874
 
@@ -5876,7 +5916,7 @@ ${pc8.dim("-".repeat(40))}
5876
5916
  out(` ${pc8.bold(risk.path)}
5877
5917
  `);
5878
5918
  if (risk.unresolvedFailures.length > 0) {
5879
- out(` ${pc8.yellow("WARN")} What already failed here (${risk.unresolvedFailures.length} unresolved):
5919
+ out(` ${pc8.yellow("WARN")} commands naming this file failed, still unresolved (${risk.unresolvedFailures.length}):
5880
5920
  `);
5881
5921
  for (const f of risk.unresolvedFailures.slice(0, 3)) {
5882
5922
  out(` ${pc8.dim(`${f.ts.slice(0, 10)} ${f.command.slice(0, 90)}`)}
@@ -5888,7 +5928,7 @@ ${pc8.dim("-".repeat(40))}
5888
5928
  }
5889
5929
  }
5890
5930
  if (risk.commitsRecent >= HIGH_CHURN_THRESHOLD) {
5891
- out(` ${pc8.yellow("WARN")} high churn: ${risk.commitsRecent} commits touched this file recently
5931
+ out(` ${pc8.dim("note")} high churn (untuned heuristic): ${risk.commitsRecent} commits touched this file recently
5892
5932
  `);
5893
5933
  }
5894
5934
  out("\n");