claude-mem-lite 3.74.0 → 3.74.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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "claude-mem-lite",
13
- "version": "3.74.0",
13
+ "version": "3.74.1",
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": "3.74.0",
3
+ "version": "3.74.1",
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/bash-utils.mjs CHANGED
@@ -137,7 +137,21 @@ function collectErrorTerms(cmd, response) {
137
137
  if (!ERROR_STOP_WORDS.has(lw) && !seen.has(lw)) { seen.add(lw); cmdWords.push(lw); }
138
138
  }
139
139
  const errWords = [];
140
- const errLines = String(response || '').split('\n').filter(l => ERROR_LINE_RE.test(l)).slice(0, 3);
140
+ // The line filter is the TRIGGER's pattern list OR'd with the prose one. Anything
141
+ // that made detectBashSignificance call this a hard error is, by construction, also
142
+ // something we will extract terms from — which closes the "trigger fired, extractor
143
+ // found nothing, so we queried the command's own words" class without enumerating
144
+ // failure shapes. ERROR_LINE_RE alone missed `npm ERR! code ENOENT` (no `error`, no
145
+ // `fail`, no `not found` — npm says "no such file") and `panic: assignment to entry
146
+ // in nil map`, while letting `panic: runtime error: …` through purely because that
147
+ // message happens to contain the substring `error`.
148
+ // Note HARD_ERROR_RE's `\n\s+at\s+\S` alternative cannot match a single line (it
149
+ // needs the preceding newline); that is fine — it is a stack-frame anchor, and the
150
+ // frames it guards are accompanied by a line the other alternatives do catch.
151
+ const errLines = String(response || '')
152
+ .split('\n')
153
+ .filter((l) => ERROR_LINE_RE.test(l) || HARD_ERROR_RE.test(l))
154
+ .slice(0, 3);
141
155
  for (const line of errLines) {
142
156
  const tokens = line.replace(/[^a-zA-Z0-9_.-]/g, ' ').split(/\s+/)
143
157
  .filter(w => w.length > 3 && !/^\d+$/.test(w));
@@ -168,22 +182,25 @@ export function extractErrorKeywords(cmd, response) {
168
182
  * Two defects this closes, both measured against the live DB on 2026-08-22 (obs
169
183
  * #10730 carries the readings):
170
184
  *
171
- * 1. NO ERROR SIGNAL NO INJECTION. This surface fires on detectBashSignificance's
172
- * isHardError, and that gate's HARD_ERROR_RE is NOT in sync with ERROR_LINE_RE
173
- * here: HARD_ERROR_RE accepts `ERR!`, `enoent` and `traceback`, while ERROR_LINE_RE
174
- * only matches the whole word `error`. So npm's own failure output clears the
175
- * trigger and then yields ZERO error lines — `npm ERR! code ENOENT / npm ERR!
176
- * enoent ENOENT: no such file or directory` contains no `error`, no `fail`, and no
177
- * `not found` (it says "no such file"). The keyword set then degraded to pure
178
- * command words literally ['npm','run','build'] and the surface searched the
179
- * COMMAND'S TOPIC instead of the failure. Same for a Python traceback whose head
180
- * lines carry `Traceback (most recent call last):` and a bare `File "x.py"`.
181
- * Both are among the most common failures a session produces.
182
- * With no error term there is nothing to recall ON, so the honest answer is
183
- * silence rather than a topic match. Widening ERROR_LINE_RE is NOT the fix
184
- * enumeration always misses one more shape, and this gate is correct for every
185
- * shape it misses. (Verified: a `grep` killed by seccomp does NOT reach here at
186
- * all isHardError is false for it so that shape is not evidence for this gate.)
185
+ * 1. THE SELECTION FILTER IS A SUPERSET OF THE TRIGGER. This surface fires on
186
+ * detectBashSignificance's isHardError (HARD_ERROR_RE), but term extraction used to
187
+ * keep only lines matching ERROR_LINE_RE a DIFFERENT list. The two diverge:
188
+ * HARD_ERROR_RE accepts `ERR!`, `enoent`, `panic`, `traceback`; ERROR_LINE_RE takes
189
+ * `error|fail|exception|cannot|not found|undefined|null` as SUBSTRINGS (no word
190
+ * boundaries `AssertionError` matches on `error`). npm's own output sits in the
191
+ * gap: `npm ERR! code ENOENT / npm ERR! enoent ENOENT: no such file or directory`
192
+ * has no `error`, no `fail`, no `not found` (npm says "no such file"), so it cleared
193
+ * the trigger and then yielded ZERO lines to extract from. The keyword set degraded
194
+ * to pure command words literally ['npm','run','build'] and the surface searched
195
+ * the COMMAND'S TOPIC instead of the failure.
196
+ * The sharpest symptom was Go: `panic: assignment to entry in nil map` was silenced
197
+ * while `panic: runtime error: index out of range` was not, purely because the
198
+ * second message happens to contain the substring `error`. Recall depending on the
199
+ * wording of a panic is the same divergence, relocated.
200
+ * OR-ing HARD_ERROR_RE into the line filter closes the class BY CONSTRUCTION rather
201
+ * than by enumerating shapes: whatever convinced the trigger this was a hard error
202
+ * is, by definition, also something we will read terms from. (Widening ERROR_LINE_RE
203
+ * ad hoc WOULD be enumeration; making it a superset of the trigger is not.)
187
204
  *
188
205
  * 2. COMMAND WORDS STAY IN THE QUERY — a demotion was TRIED AND REJECTED on data.
189
206
  * The obvious follow-up is to drop `npm` / `run` / `grep` from the query, since
@@ -196,7 +213,21 @@ export function extractErrorKeywords(cmd, response) {
196
213
  * fails locally) for a test failure. Command words are carrying domain anchoring,
197
214
  * not just noise. A demote-to-fallback variant measured byte-identical to
198
215
  * error-terms-only (12 rows either way): the primary query always filled its
199
- * LIMIT 3, so the fallback never ran. Net: gate only, selection unchanged.
216
+ * LIMIT 3, so the fallback never ran.
217
+ *
218
+ * 3. THE RESIDUAL GATE. With (1) in place this fires rarely, but it is not dead: a
219
+ * failure can still yield no usable term — empty output, or a line whose tokens are
220
+ * all stop words (`Error: it failed`). There is then nothing to recall ON, and
221
+ * silence beats querying the command's topic.
222
+ * Read the predicate precisely: `errWords` excludes anything ALREADY taken as a
223
+ * command word, because collectErrorTerms dedups across both classes with the
224
+ * command filled first. So this is "no error term that is not also in the command",
225
+ * not "no error term". `docker compose up -d` and `docker stack deploy` on the SAME
226
+ * output decide differently for exactly that reason — the first has `compose` in the
227
+ * command, the second does not. That asymmetry is inherited from the pre-split
228
+ * single-Set implementation and is preserved deliberately; it is documented here
229
+ * rather than silently "fixed" because changing it would change extractErrorKeywords
230
+ * for every caller, which is a separate decision from this one.
200
231
  *
201
232
  * @param {string} cmd The command that was executed
202
233
  * @param {string} response The error output text
package/hook.mjs CHANGED
@@ -473,14 +473,14 @@ function triggerErrorRecall(db, toolInput, response) {
473
473
  try {
474
474
  const project = inferProject();
475
475
 
476
- // Extract error keywords. planErrorRecall (D#136) returns null when the output
477
- // carried NO error-signal token. isHardError above and that check do NOT use the
478
- // same pattern list: HARD_ERROR_RE accepts `ERR!`/`enoent`/`traceback`, while the
479
- // line filter wants the whole word `error`. npm's own failure text clears the
480
- // former and yields zero lines to the latter, so `npm run build` failing on
481
- // ENOENT used to query ['npm','run','build'] — the command's topic, not the
482
- // failure. Silence is the honest answer there.
483
- // The term list itself is unchanged when the gate passes (rationale at the seam).
476
+ // Extract error keywords (D#136). The extractor's line filter is HARD_ERROR_RE —
477
+ // the very predicate isHardError above used OR'd with the prose one, so anything
478
+ // that reaches this line is also something we can read terms from. Before that,
479
+ // the two lists diverged and npm's own output fell in the gap: `npm ERR! code
480
+ // ENOENT` has no `error`/`fail`/`not found`, so extraction yielded nothing and the
481
+ // query degraded to ['npm','run','build'] — the command's topic, not the failure.
482
+ // planErrorRecall still returns null when nothing usable survives (empty output, or
483
+ // only stop words), and then we stay silent rather than query the command's topic.
484
484
  const cmd = toolInput.command || '';
485
485
  const plan = planErrorRecall(cmd, response);
486
486
  if (!plan) return;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "3.74.0",
3
+ "version": "3.74.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "claude-mem-lite",
9
- "version": "3.74.0",
9
+ "version": "3.74.1",
10
10
  "dependencies": {
11
11
  "@modelcontextprotocol/sdk": "^1.26.0",
12
12
  "better-sqlite3": "^12.6.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "3.74.0",
3
+ "version": "3.74.1",
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",