nexusmem 0.3.2 → 0.3.3
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 +28 -1
- package/dist/cli/index.js +39 -7
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,32 @@ built from, matched by publish timestamp: `v0.1.0` → `67a4776`, `v0.1.1` → `
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
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
|
+
|
|
12
38
|
## [0.3.2] — 2026-08-16
|
|
13
39
|
|
|
14
40
|
### Added
|
|
@@ -250,7 +276,8 @@ First public release.
|
|
|
250
276
|
there is no local-model summarization pass, and the conversation collector has never been audited
|
|
251
277
|
for the stale-node bug that was found and fixed in the docs collector.
|
|
252
278
|
|
|
253
|
-
[Unreleased]: https://github.com/yaminbkk/NexusMem/compare/v0.3.
|
|
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
|
|
254
281
|
[0.3.2]: https://github.com/yaminbkk/NexusMem/compare/v0.3.1...v0.3.2
|
|
255
282
|
[0.3.1]: https://github.com/yaminbkk/NexusMem/compare/v0.3.0...v0.3.1
|
|
256
283
|
[0.3.0]: https://github.com/yaminbkk/NexusMem/compare/v0.2.0...v0.3.0
|
package/dist/cli/index.js
CHANGED
|
@@ -710,11 +710,6 @@ function toMatchQuery(input) {
|
|
|
710
710
|
if (kept.length === 0) return null;
|
|
711
711
|
return kept.map((t) => `"${t}"*`).join(" OR ");
|
|
712
712
|
}
|
|
713
|
-
function toStrictMatchQuery(input) {
|
|
714
|
-
const kept = significantTokens(input);
|
|
715
|
-
if (kept.length === 0) return null;
|
|
716
|
-
return kept.map((t) => `"${t}"*`).join(" AND ");
|
|
717
|
-
}
|
|
718
713
|
|
|
719
714
|
// src/store/schema.ts
|
|
720
715
|
var V1 = `
|
|
@@ -1521,6 +1516,21 @@ var RESOLVED_BY_DISCUSSION = "resolved_by:discussion";
|
|
|
1521
1516
|
function normalizeCommand(command) {
|
|
1522
1517
|
return command.trim().replace(/\s+/g, " ").toLowerCase();
|
|
1523
1518
|
}
|
|
1519
|
+
var MAX_TOKEN_DOC_FREQUENCY = 0.2;
|
|
1520
|
+
var MIN_CORPUS_FOR_FREQUENCY_FILTER = 10;
|
|
1521
|
+
function filterBoilerplateTokens(db, projectId, tokens) {
|
|
1522
|
+
if (tokens.length === 0) return tokens;
|
|
1523
|
+
const total = db.prepare(`SELECT COUNT(*) AS c FROM nodes WHERE project_id = ? AND kind IN ('conversation_turn', 'session_summary')`).get(projectId).c;
|
|
1524
|
+
if (total < MIN_CORPUS_FOR_FREQUENCY_FILTER) return tokens;
|
|
1525
|
+
const countMatching = db.prepare(
|
|
1526
|
+
`SELECT COUNT(*) AS c FROM nodes_fts JOIN nodes n ON n.rowid = nodes_fts.rowid
|
|
1527
|
+
WHERE nodes_fts MATCH ? AND n.project_id = ? AND n.kind IN ('conversation_turn', 'session_summary')`
|
|
1528
|
+
);
|
|
1529
|
+
return tokens.filter((t) => {
|
|
1530
|
+
const matching = countMatching.get(`"${t}"*`, projectId).c;
|
|
1531
|
+
return matching / total <= MAX_TOKEN_DOC_FREQUENCY;
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1524
1534
|
function correlateFailures(store, projectId, opts = {}) {
|
|
1525
1535
|
const retryWindowMs = opts.retryWindowMs ?? DEFAULT_RETRY_WINDOW_MS;
|
|
1526
1536
|
const discussionWindowMs = opts.discussionWindowMs ?? DEFAULT_DISCUSSION_WINDOW_MS;
|
|
@@ -1565,7 +1575,8 @@ function correlateFailures(store, projectId, opts = {}) {
|
|
|
1565
1575
|
store.linkNodes(failure.id, retry.id, RESOLVED_BY_RETRY);
|
|
1566
1576
|
linkedByRetry += 1;
|
|
1567
1577
|
}
|
|
1568
|
-
const
|
|
1578
|
+
const tokens = filterBoilerplateTokens(db, projectId, significantTokens(failure.command));
|
|
1579
|
+
const match = tokens.length > 0 ? tokens.map((t) => `"${t}"*`).join(" AND ") : null;
|
|
1569
1580
|
if (match) {
|
|
1570
1581
|
const discussion = findDiscussion.get(match, projectId, failure.ts_epoch, failure.ts_epoch + discussionWindowMs);
|
|
1571
1582
|
if (discussion) {
|
|
@@ -1576,6 +1587,25 @@ function correlateFailures(store, projectId, opts = {}) {
|
|
|
1576
1587
|
}
|
|
1577
1588
|
return { failuresExamined: failures.length, linkedByRetry, linkedByDiscussion };
|
|
1578
1589
|
}
|
|
1590
|
+
function getChainStats(store, projectId) {
|
|
1591
|
+
const db = store.raw;
|
|
1592
|
+
const failuresTotal = db.prepare(
|
|
1593
|
+
`SELECT COUNT(*) AS c FROM nodes
|
|
1594
|
+
WHERE project_id = ? AND kind = 'shell_command'
|
|
1595
|
+
AND json_extract(meta, '$.exitCode') IS NOT NULL AND json_extract(meta, '$.exitCode') != 0`
|
|
1596
|
+
).get(projectId).c;
|
|
1597
|
+
const countDistinctLinked = (relations) => db.prepare(
|
|
1598
|
+
`SELECT COUNT(DISTINCT nl.from_node_id) AS c
|
|
1599
|
+
FROM node_links nl JOIN nodes n ON n.id = nl.from_node_id
|
|
1600
|
+
WHERE n.project_id = ? AND nl.relation IN (${relations.map(() => "?").join(", ")})`
|
|
1601
|
+
).get(projectId, ...relations).c;
|
|
1602
|
+
return {
|
|
1603
|
+
failuresTotal,
|
|
1604
|
+
resolvedByRetry: countDistinctLinked([RESOLVED_BY_RETRY]),
|
|
1605
|
+
resolvedByDiscussion: countDistinctLinked([RESOLVED_BY_DISCUSSION]),
|
|
1606
|
+
resolvedTotal: countDistinctLinked([RESOLVED_BY_RETRY, RESOLVED_BY_DISCUSSION])
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1579
1609
|
|
|
1580
1610
|
// src/retrieval/fuse.ts
|
|
1581
1611
|
var RRF_K = 60;
|
|
@@ -4293,6 +4323,7 @@ async function runStatus(opts) {
|
|
|
4293
4323
|
const sources = store.listSyncState(projectId);
|
|
4294
4324
|
const gitCursor = sources.find((s) => s.source === "git")?.cursor ?? null;
|
|
4295
4325
|
const schema = currentSchemaVersion(store.raw);
|
|
4326
|
+
const chains = getChainStats(store, projectId);
|
|
4296
4327
|
const dbBytes = fileSize(ws.dbPath) + fileSize(`${ws.dbPath}-wal`);
|
|
4297
4328
|
const kinds = Object.entries(stats.byKind).sort((a, b) => b[1] - a[1]).map(([kind, n]) => ` ${String(n).padStart(6)} ${kind}`);
|
|
4298
4329
|
process.stdout.write(
|
|
@@ -4314,7 +4345,8 @@ async function runStatus(opts) {
|
|
|
4314
4345
|
return ` ${s.source.padEnd(14)} ${pc13.dim(`last run ${when}`)} ${pc13.dim(`cursor ${cursorLabel}`)}`;
|
|
4315
4346
|
}),
|
|
4316
4347
|
gitCursor && gitCursor !== repo.head ? `${pc13.yellow("git behind HEAD")} \u2014 run ${pc13.bold("nexusmem sync")}` : "",
|
|
4317
|
-
""
|
|
4348
|
+
"",
|
|
4349
|
+
chains.failuresTotal ? `${pc13.dim("chains ")} ${pc13.bold(String(chains.resolvedTotal))}/${chains.failuresTotal} failure(s) resolved ${pc13.dim(`(${chains.resolvedByRetry} retry, ${chains.resolvedByDiscussion} discussion)`)}${chains.resolvedTotal < chains.failuresTotal ? ` \u2014 run ${pc13.bold("nexusmem sync --link-failures")} to link more` : ""}` : ""
|
|
4318
4350
|
].filter((line) => line !== "").join("\n").concat("\n")
|
|
4319
4351
|
);
|
|
4320
4352
|
return 0;
|