claude-mem-lite 4.0.2 → 4.0.4

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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "claude-mem-lite",
13
- "version": "4.0.2",
13
+ "version": "4.0.4",
14
14
  "source": "./",
15
15
  "description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark)."
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "4.0.2",
3
+ "version": "4.0.4",
4
4
  "description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark).",
5
5
  "author": {
6
6
  "name": "sdsrss"
package/hook-context.mjs CHANGED
@@ -60,11 +60,16 @@ function mdCell(s) {
60
60
 
61
61
  export function computeAdaptiveWindows(db, project) {
62
62
  const sevenDaysAgo = Date.now() - 7 * DAY_MS;
63
+ // liveObsFilterSql, not a hand-written half of it (A20260906-R8-§11.3). This COUNT is a
64
+ // POOL-SIZING input: the windows returned below are handed to the recall queries at :201 /
65
+ // :468 / :502, all three of which filter live rows. Counting a strictly larger population
66
+ // than the window is applied to inverts the documented intent — 80 superseded rows read as
67
+ // >10 obs/day and earned the TIGHTEST window (12h) for finding the few live rows left.
63
68
  const row = db
64
69
  .prepare(
65
70
  `
66
71
  SELECT COUNT(*) as c FROM observations
67
- WHERE project = ? AND created_at_epoch > ? AND COALESCE(compressed_into, 0) = 0
72
+ WHERE project = ? AND created_at_epoch > ? AND ${liveObsFilterSql('')}
68
73
  `,
69
74
  )
70
75
  .get(project, sevenDaysAgo);
package/hook-handoff.mjs CHANGED
@@ -136,6 +136,15 @@ export function buildAndSaveHandoff(db, sessionId, project, type, episodeSnapsho
136
136
  const obsWindowParams = ccWindowStart !== null ? [ccWindowStart] : [];
137
137
 
138
138
  // 2. Completed — from observations (include narrative for richer handoff)
139
+ //
140
+ // `compressed_into` ONLY, deliberately — not a half-written liveObsFilterSql. Audit
141
+ // 2026-08-14 F4 ruled on exactly this line: `completed` is the session's own history, and a
142
+ // decision a later save overturned still happened, so erasing it here would misreport the
143
+ // session. Only key_decisions (:242) is re-presented to a LATER session as standing policy,
144
+ // and that one does filter superseded_at. The scope guard is the third case of "F4 — handoff
145
+ // key_decisions excludes a retracted decision" in tests/audit-silent-20260814.test.mjs.
146
+ // Audit R8 §11.3 proposed adding the filter here from a repo-wide regex sweep of the
147
+ // predicate shape; it was rejected on this reasoning, and the guard catches it.
139
148
  const completed = db
140
149
  .prepare(
141
150
  `
package/hook-update.mjs CHANGED
@@ -23,6 +23,7 @@ import { pathToFileURL } from 'node:url';
23
23
  import { tmpdir, homedir } from 'node:os';
24
24
  import { DB_DIR, CODE_DIR } from './schema.mjs';
25
25
  import { debugCatch, debugLog } from './utils.mjs';
26
+ import { NATIVE_BINDING_SOURCE_BUILD_CMD } from './lib/binding-probe.mjs';
26
27
  // Local manifest is fallback only — the active manifest is loaded from the
27
28
  // extracted tarball's own source-files.mjs inside installExtractedRelease.
28
29
  // See loadReleaseManifest below.
@@ -877,7 +878,27 @@ function smokeInstalledRelease(targetDir) {
877
878
  } catch {
878
879
  execSync('npm rebuild better-sqlite3', { cwd: targetDir, timeout: 120000, stdio: 'ignore' });
879
880
  }
880
- execSync(probeCmd, { timeout: 20000, stdio: 'ignore' });
881
+ try {
882
+ execSync(probeCmd, { timeout: 20000, stdio: 'ignore' });
883
+ } catch {
884
+ // Both npm rebuilds healed nothing — which is what better-sqlite3 13 does on a
885
+ // platform it ships no prebuild for, because there is no install script for them to
886
+ // run. Without this step the re-probe threw, smoke failed, and the caller rolled the
887
+ // whole update back, so auto-update could never land again on those platforms
888
+ // (A20260906-R8b-P2-1, found by independent review of v4.0.0).
889
+ //
890
+ // The source build is `node-gyp clean && node-gyp rebuild`, i.e. it deletes build/
891
+ // first — which is why the SessionStart probe path must NOT run it. Here it is safe
892
+ // for two independent reasons: the binding is ALREADY unusable (the probe above just
893
+ // threw), so clean destroys nothing; and this whole function is a smoke gate whose
894
+ // failure rolls back to the previous, working install.
895
+ execSync(NATIVE_BINDING_SOURCE_BUILD_CMD, {
896
+ cwd: targetDir,
897
+ timeout: 300000,
898
+ stdio: 'ignore',
899
+ });
900
+ execSync(probeCmd, { timeout: 20000, stdio: 'ignore' });
901
+ }
881
902
  }
882
903
  }
883
904
  return true;
package/install.mjs CHANGED
@@ -420,6 +420,12 @@ export function patchClaudeMdVersion(text, version) {
420
420
  * removes the link and leaves the target intact (a link to a populated dir, target still
421
421
  * readable afterwards) — following it would delete the developer's own scripts/.
422
422
  *
423
+ * It is `recursive`, so it also removes a REAL directory at `p`, not only a link to one.
424
+ * That is deliberate and pre-existing (the dev-mode sites already did this): running
425
+ * `install --dev` after a normal install finds a real `DATA_DIR/node_modules` where a link
426
+ * belongs. Named here because the one-line summary above says "before a symlink is written
427
+ * over it", which undersells what the call can delete.
428
+ *
423
429
  * @param {string} p
424
430
  * @returns {boolean}
425
431
  */
@@ -1581,13 +1587,9 @@ async function uninstall() {
1581
1587
  // 1b. Remove CLI symlink
1582
1588
  for (const binDir of [join(homedir(), '.local', 'bin'), '/usr/local/bin']) {
1583
1589
  const cliLink = join(binDir, 'claude-mem-lite');
1584
- try {
1585
- if (clearLinkPath(cliLink)) {
1586
- ok(`CLI symlink removed: ${cliLink}`);
1587
- }
1588
- } catch {
1589
- /* may not have permissions */
1590
- }
1590
+ // No try/catch: clearLinkPath swallows a permissions failure and returns false, so the
1591
+ // wrapper this used to have was unreachable once the existsSync gate moved inside it.
1592
+ if (clearLinkPath(cliLink)) ok(`CLI symlink removed: ${cliLink}`);
1591
1593
  }
1592
1594
 
1593
1595
  // 2. Remove hooks from settings.json (match both npx and git-clone install paths)
@@ -48,6 +48,9 @@ export const NATIVE_BINDING_SOURCE_BUILD_CMD = 'npm run --prefix node_modules/be
48
48
  * `&&`, never `||`: step 1 exits 0 whether or not it did anything, so an `||` chain would
49
49
  * never reach step 2 — the no-op is the whole trap. Sequencing unconditionally costs a
50
50
  * recompile in the case step 1 already fixed, which is the right trade for a manual repair.
51
+ * Precisely: `build-release` is `node-gyp clean && node-gyp rebuild`, so step 2 DISCARDS a
52
+ * binding step 1 had just produced and rebuilds it. `prebuilds/` is untouched either way, so
53
+ * the end state is a usable binding; the cost is time, not correctness.
51
54
  *
52
55
  * @param {string} root Directory whose node_modules holds better-sqlite3
53
56
  * @returns {string}
@@ -41,6 +41,17 @@ export const FAST_SUMMARY_LIMITS = {
41
41
  /**
42
42
  * The two reads every fast summary is built from: the session's opening prompt, and the
43
43
  * titles of its most recent observations.
44
+ *
45
+ * The observation filter is `compressed_into` ONLY, deliberately — it is NOT a half-written
46
+ * `liveObsFilterSql` waiting to be completed. `completed` is the session's own history, and a
47
+ * lesson a later save overturned still happened; dropping it here would misreport the session
48
+ * that did the work. This is the same ruling audit 2026-08-14 F4 made for the sibling field
49
+ * `session_handoffs.completed`, where the scope guard lives in
50
+ * `tests/audit-silent-20260814.test.mjs`. Audit R8 §11.3 proposed adding `superseded_at IS
51
+ * NULL` to both; both were rejected on this reasoning. The filter that DOES belong on a
52
+ * superseded row is the one guarding standing policy re-presented to a later session
53
+ * (`key_decisions`), not history.
54
+ *
44
55
  * @returns {{request: string, completed: string}} raw (unscrubbed, untruncated) values
45
56
  */
46
57
  export function readFastSummarySource(db, sessionId) {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "4.0.2",
3
+ "version": "4.0.4",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "claude-mem-lite",
9
- "version": "4.0.2",
9
+ "version": "4.0.4",
10
10
  "dependencies": {
11
11
  "@modelcontextprotocol/sdk": "^1.30.0",
12
12
  "better-sqlite3": "^13.0.3",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "4.0.2",
3
+ "version": "4.0.4",
4
4
  "description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark).",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",