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 +25 -1
- package/README.md +1 -1
- package/dist/cli/index.js +43 -3
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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)
|
|
7
7
|

|
|
8
8
|
|
|
9
|
-

|
|
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")}
|
|
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.
|
|
5931
|
+
out(` ${pc8.dim("note")} high churn (untuned heuristic): ${risk.commitsRecent} commits touched this file recently
|
|
5892
5932
|
`);
|
|
5893
5933
|
}
|
|
5894
5934
|
out("\n");
|