claude-mem-lite 3.59.0 → 3.59.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/lib/activity.mjs +4 -2
- package/lib/low-signal-patterns.mjs +18 -2
- package/lib/stats-quality.mjs +6 -4
- package/memdir.mjs +45 -8
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/scoring-sql.mjs +10 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "3.59.
|
|
13
|
+
"version": "3.59.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.59.
|
|
3
|
+
"version": "3.59.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/lib/activity.mjs
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
import { sanitizeFtsQuery } from '../utils.mjs';
|
|
8
8
|
import { scrubRecord } from './scrub-record.mjs';
|
|
9
9
|
import { saveObservation } from './save-observation.mjs';
|
|
10
|
-
|
|
10
|
+
// Pure title-only builder: this query runs on the EVENTS table, which has no
|
|
11
|
+
// lesson_learned column — the lesson-escape variant would be a SQL error here.
|
|
12
|
+
import { buildNotLowSignalSql } from './low-signal-patterns.mjs';
|
|
11
13
|
import { OBS_TYPE_SET } from './obs-types.mjs';
|
|
12
14
|
|
|
13
15
|
// Observation types (mirrors the observations.type enum) — events carry a wider
|
|
@@ -150,7 +152,7 @@ export function promoteInsightEvents(db, { project = null, minImportance = 2, ex
|
|
|
150
152
|
FROM events
|
|
151
153
|
WHERE body IS NOT NULL AND TRIM(body) != '' AND importance >= ?
|
|
152
154
|
AND superseded_at_epoch IS NULL
|
|
153
|
-
AND ${
|
|
155
|
+
AND ${buildNotLowSignalSql('')}
|
|
154
156
|
${projClause}
|
|
155
157
|
ORDER BY created_at_epoch DESC LIMIT ?
|
|
156
158
|
`;
|
|
@@ -50,13 +50,29 @@ export function buildLowSignalRegex() {
|
|
|
50
50
|
* Build the SQL NOT LIKE clause chain, optionally prefixed with a table alias.
|
|
51
51
|
* Output is a single parenthesized AND-chain — safe to combine with other AND/OR.
|
|
52
52
|
*
|
|
53
|
+
* `lessonEscape` (2026-07-24 audit P1, D#11): retrieval consumers on the
|
|
54
|
+
* observations table pass `{ lessonEscape: true }` to admit rows whose title
|
|
55
|
+
* matches a LOW_SIGNAL pattern but which carry a real lesson_learned — the
|
|
56
|
+
* read-side counterpart of the isNoiseObservation/capNoiseImportance write-side
|
|
57
|
+
* signal escapes. Without it, a substantive obs titled "npm pack drops …" is
|
|
58
|
+
* unsearchable forever. Escape is lesson-only by design: an importance>=2
|
|
59
|
+
* escape would resurrect pre-v2.47 Haiku-inflated noise on legacy DBs.
|
|
60
|
+
* Default stays title-only because two consumers query the events table
|
|
61
|
+
* (no lesson_learned column) and two are noise-title METRICS, not filters.
|
|
62
|
+
*
|
|
53
63
|
* @param {string} [alias=''] Table alias (e.g. 'o') — empty for unqualified.
|
|
64
|
+
* @param {object} [opts]
|
|
65
|
+
* @param {boolean} [opts.lessonEscape=false] Admit rows with non-empty, non-'none' lesson_learned.
|
|
54
66
|
* @returns {string} SQL boolean expression
|
|
55
67
|
*/
|
|
56
|
-
export function buildNotLowSignalSql(alias = '') {
|
|
68
|
+
export function buildNotLowSignalSql(alias = '', { lessonEscape = false } = {}) {
|
|
57
69
|
const p = alias ? `${alias}.` : '';
|
|
58
70
|
const clauses = LOW_SIGNAL_PATTERNS.map(({ like }) => `${p}title NOT LIKE '${like}'`);
|
|
59
|
-
|
|
71
|
+
const chain = '(\n ' + clauses.join('\n AND ') + '\n )';
|
|
72
|
+
if (!lessonEscape) return chain;
|
|
73
|
+
// Mirrors the write-side lesson test: String(lesson).trim().toLowerCase() not in ('', 'none').
|
|
74
|
+
const escape = `(${p}lesson_learned IS NOT NULL AND LOWER(TRIM(${p}lesson_learned)) NOT IN ('', 'none'))`;
|
|
75
|
+
return `(${chain} OR ${escape})`;
|
|
60
76
|
}
|
|
61
77
|
|
|
62
78
|
// Cached singleton — isNoiseObservation is called once per observation insert.
|
package/lib/stats-quality.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Splits pure data aggregation from text rendering so MCP handlers don't
|
|
4
4
|
// collide with CLI's `out()` stdout-write pattern.
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { buildNotLowSignalSql } from './low-signal-patterns.mjs';
|
|
7
7
|
import { truncate } from '../format-utils.mjs';
|
|
8
8
|
import { COMPRESSED_PENDING_PURGE } from '../utils.mjs';
|
|
9
9
|
|
|
@@ -27,9 +27,11 @@ export function computeQualityStats(db, { project, days }) {
|
|
|
27
27
|
const baseParams = project ? [project] : [];
|
|
28
28
|
const cutoff = Date.now() - days * 86400000;
|
|
29
29
|
|
|
30
|
-
// LOW_SIGNAL match = NOT notLowSignal.
|
|
31
|
-
//
|
|
32
|
-
|
|
30
|
+
// LOW_SIGNAL match = NOT notLowSignal. Pure title-only builder: this is a
|
|
31
|
+
// METRIC counting pattern-titled rows ("Low-signal titles" in stats output),
|
|
32
|
+
// not a retrieval filter — the lesson-escape variant would silently exclude
|
|
33
|
+
// lesson-bearing rows from the count and understate title degradation.
|
|
34
|
+
const lowSignalIsMatchExpr = `NOT ${buildNotLowSignalSql('')}`;
|
|
33
35
|
|
|
34
36
|
// Narrative-text proxy for bugfix investigations that never landed a fix.
|
|
35
37
|
const unresolvedNarrativeExpr = `(
|
package/memdir.mjs
CHANGED
|
@@ -304,9 +304,30 @@ export function removePluginDoc(memdir, slug) {
|
|
|
304
304
|
// noise.
|
|
305
305
|
|
|
306
306
|
const AUDIT_FILE_RE = /^(feedback|project)_[A-Za-z0-9_-]+\.md$/;
|
|
307
|
+
// Legacy skip prefixes: user_*/reference_* have no Why/How requirement. A known
|
|
308
|
+
// filename prefix wins over frontmatter type (a user_*.md stays excluded even if
|
|
309
|
+
// its frontmatter says feedback).
|
|
310
|
+
const SKIP_FILE_RE = /^(user|reference)_[A-Za-z0-9_-]+\.md$/;
|
|
307
311
|
const WHY_RE = /^\s*\*\*Why:\*\*/m;
|
|
308
312
|
const HOW_RE = /^\s*\*\*How to apply:\*\*/m;
|
|
309
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Extract the memory type from a file's YAML frontmatter (2026-07-24 audit P2).
|
|
316
|
+
* The current CC harness writes kebab-case filenames (ship-runbook.md) and puts
|
|
317
|
+
* the type in frontmatter — either top-level `type: X` or nested under
|
|
318
|
+
* `metadata:` as ` type: X`. Anchored `^\s*type:` cannot match `node_type:`.
|
|
319
|
+
*
|
|
320
|
+
* @param {string} raw Full file content
|
|
321
|
+
* @returns {string|null} Lowercased type, or null when absent/no frontmatter
|
|
322
|
+
*/
|
|
323
|
+
function frontmatterType(raw) {
|
|
324
|
+
if (!raw.startsWith('---\n') && !raw.startsWith('---\r\n')) return null;
|
|
325
|
+
const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/);
|
|
326
|
+
if (!m) return null;
|
|
327
|
+
const t = m[1].match(/^\s*type:\s*([A-Za-z]+)\s*$/m);
|
|
328
|
+
return t ? t[1].toLowerCase() : null;
|
|
329
|
+
}
|
|
330
|
+
|
|
310
331
|
/**
|
|
311
332
|
* Strip the leading YAML frontmatter block (between `---` fences) so audit
|
|
312
333
|
* checks run only against body content. Returns input unchanged if no
|
|
@@ -339,13 +360,29 @@ export function auditMemdir(memdir) {
|
|
|
339
360
|
let entries;
|
|
340
361
|
try { entries = readdirSync(memdir); } catch { return result; }
|
|
341
362
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
363
|
+
// Two selection paths (2026-07-24 audit P2):
|
|
364
|
+
// 1. legacy filename prefix — feedback_*/project_* audited, user_*/reference_* skipped
|
|
365
|
+
// 2. kebab-case (current harness) — frontmatter type ∈ {feedback, project}
|
|
366
|
+
// Path 2 needs the file content; read once and reuse for the body check.
|
|
367
|
+
const candidates = entries
|
|
368
|
+
.filter(n => n.endsWith('.md') && n !== 'MEMORY.md' && !n.startsWith('.'))
|
|
369
|
+
.sort();
|
|
370
|
+
let total = 0;
|
|
371
|
+
for (const name of candidates) {
|
|
372
|
+
const legacyAudit = AUDIT_FILE_RE.test(name);
|
|
373
|
+
if (!legacyAudit && SKIP_FILE_RE.test(name)) continue;
|
|
374
|
+
|
|
375
|
+
let raw = null;
|
|
376
|
+
try { raw = readFileSync(join(memdir, name), 'utf8'); } catch { /* unreadable */ }
|
|
377
|
+
|
|
378
|
+
if (!legacyAudit) {
|
|
379
|
+
// Frontmatter decides; unreadable or untyped files carry no Why/How contract.
|
|
380
|
+
const type = raw === null ? null : frontmatterType(raw);
|
|
381
|
+
if (type !== 'feedback' && type !== 'project') continue;
|
|
382
|
+
}
|
|
383
|
+
// Legacy-prefixed unreadable file keeps the old behavior: counted as missingBoth.
|
|
384
|
+
const body = raw === null ? '' : stripFrontmatter(raw);
|
|
385
|
+
total += 1;
|
|
349
386
|
|
|
350
387
|
const hasWhy = WHY_RE.test(body);
|
|
351
388
|
const hasHow = HOW_RE.test(body);
|
|
@@ -354,6 +391,6 @@ export function auditMemdir(memdir) {
|
|
|
354
391
|
else if (!hasWhy) result.missingWhy.push(name);
|
|
355
392
|
else result.missingHowToApply.push(name);
|
|
356
393
|
}
|
|
357
|
-
result.total =
|
|
394
|
+
result.total = total;
|
|
358
395
|
return result;
|
|
359
396
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.59.
|
|
3
|
+
"version": "3.59.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "claude-mem-lite",
|
|
9
|
-
"version": "3.59.
|
|
9
|
+
"version": "3.59.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.59.
|
|
3
|
+
"version": "3.59.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",
|
package/scoring-sql.mjs
CHANGED
|
@@ -152,8 +152,17 @@ export function noisePenaltyClause(alias = 'o') {
|
|
|
152
152
|
// The SQL path (this), the regex path (utils.mjs::LOW_SIGNAL_TITLE), and the
|
|
153
153
|
// pre-tool-recall.js inline SQL now all derive from one authoritative
|
|
154
154
|
// pattern list. Previously hand-mirrored with "keep in sync" comments.
|
|
155
|
+
//
|
|
156
|
+
// lessonEscape (2026-07-24 audit P1, D#11): every consumer of THIS clause is an
|
|
157
|
+
// observations-table retrieval surface (search, recall, error-recall, context/
|
|
158
|
+
// handoff/UPS injection, optimize candidates), so all get the read-side lesson
|
|
159
|
+
// escape — a low-signal TITLE no longer hides a row with a real lesson_learned.
|
|
160
|
+
// Consumers that must stay title-only import buildNotLowSignalSql directly:
|
|
161
|
+
// events-table queries (lib/activity.mjs, pre-tool-recall.js events fallback —
|
|
162
|
+
// no lesson_learned column) and noise-title metrics (lib/stats-core.mjs,
|
|
163
|
+
// lib/stats-quality.mjs — they COUNT pattern-titled rows, not filter them).
|
|
155
164
|
export function notLowSignalTitleClause(alias = 'o') {
|
|
156
|
-
return buildNotLowSignalSql(alias);
|
|
165
|
+
return buildNotLowSignalSql(alias, { lessonEscape: true });
|
|
157
166
|
}
|
|
158
167
|
|
|
159
168
|
// ─── Cite-history factor (A1, v2.83) ────────────────────────────────────────
|