claude-mem-lite 3.33.0 → 3.34.0
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/cli/common.mjs +66 -0
- package/mem-cli.mjs +10 -1
- package/package.json +1 -1
- package/project-utils.mjs +11 -0
- package/scripts/pre-agent-inject.js +11 -2
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "3.
|
|
13
|
+
"version": "3.34.0",
|
|
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.
|
|
3
|
+
"version": "3.34.0",
|
|
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/cli/common.mjs
CHANGED
|
@@ -89,6 +89,72 @@ export function rejectBareStringFlags(flags, keys) {
|
|
|
89
89
|
return false;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// ─── Unknown-flag typo guard ─────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Union of every flag name any CLI command reads (parseArgs silently drops the rest).
|
|
96
|
+
* Over-inclusive BY DESIGN: a flag listed here that a given command ignores just means
|
|
97
|
+
* "no typo warning for it" — harmless. The only real risk is OMITTING a valid flag, and
|
|
98
|
+
* the edit-distance gate in suggestUnknownFlags() makes even that non-fatal (a distinct
|
|
99
|
+
* real flag rarely lands within distance 2 of another). Add new flags here when adding
|
|
100
|
+
* them to a command — same maintenance contract as JSON_SUPPORTED_CMDS in mem-cli.
|
|
101
|
+
*/
|
|
102
|
+
export const KNOWN_CLI_FLAGS = new Set([
|
|
103
|
+
'after', 'age-days', 'all', 'anchor', 'batch', 'before', 'benchmark', 'body', 'branch',
|
|
104
|
+
'capability-summary', 'category', 'closes-deferred', 'concepts', 'confirm', 'days', 'deep',
|
|
105
|
+
'detail', 'domain-tags', 'dry-run', 'enrich', 'execute', 'fields', 'file', 'files', 'floors',
|
|
106
|
+
'force', 'format', 'from', 'has', 'help', 'importance', 'include-compressed', 'include-noise',
|
|
107
|
+
'intent-tags', 'invocation-name', 'json', 'key', 'keywords', 'lesson', 'lesson-learned', 'limit',
|
|
108
|
+
'local-path', 'margins', 'max', 'memdir', 'merge-ids', 'metrics', 'name', 'narrative', 'no-deep',
|
|
109
|
+
'offset', 'ops', 'or', 'out', 'priority', 'project', 'quality', 'query', 'reason', 'repo-url',
|
|
110
|
+
'rerank', 'resource-type', 'retain-days', 'retry', 'run', 'run-all', 'scope', 'session-audit',
|
|
111
|
+
'sidechain', 'since', 'sort', 'source', 'status', 'sweep', 'task', 'tech-stack', 'tier', 'title',
|
|
112
|
+
'to', 'trigger-patterns', 'type', 'use-cases', 'verbose',
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
/** Levenshtein distance, early-exit past `max` (cheap enough for a handful of flags). */
|
|
116
|
+
function editDistance(a, b, max = 2) {
|
|
117
|
+
const m = a.length, n = b.length;
|
|
118
|
+
if (Math.abs(m - n) > max) return max + 1;
|
|
119
|
+
let prev = Array.from({ length: n + 1 }, (_, j) => j);
|
|
120
|
+
for (let i = 1; i <= m; i++) {
|
|
121
|
+
const cur = [i];
|
|
122
|
+
let rowMin = i;
|
|
123
|
+
for (let j = 1; j <= n; j++) {
|
|
124
|
+
const d = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] !== b[j - 1] ? 1 : 0));
|
|
125
|
+
cur[j] = d;
|
|
126
|
+
if (d < rowMin) rowMin = d;
|
|
127
|
+
}
|
|
128
|
+
if (rowMin > max) return max + 1; // whole row already past budget → give up
|
|
129
|
+
prev = cur;
|
|
130
|
+
}
|
|
131
|
+
return prev[n];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Detect likely-typo flags: names NOT in KNOWN_CLI_FLAGS but within edit distance 2 of
|
|
136
|
+
* a known flag. parseArgs silently drops unknown flags, so `save --improtance 3` used to
|
|
137
|
+
* persist the DEFAULT importance and `recent --projcte X` silently queried the inferred
|
|
138
|
+
* project — a typo produced a wrong result with zero signal. Returns [{flag, suggestion}].
|
|
139
|
+
* Unknown flags with NO close match are omitted: they may be a valid flag we didn't
|
|
140
|
+
* catalog, so silence beats a false alarm. Warning-only by contract — never fails.
|
|
141
|
+
* @param {object} flags Parsed flags from parseArgs.
|
|
142
|
+
* @returns {Array<{flag: string, suggestion: string}>}
|
|
143
|
+
*/
|
|
144
|
+
export function suggestUnknownFlags(flags) {
|
|
145
|
+
const result = [];
|
|
146
|
+
for (const key of Object.keys(flags)) {
|
|
147
|
+
if (!key || KNOWN_CLI_FLAGS.has(key)) continue;
|
|
148
|
+
let best = null, bestDist = 3;
|
|
149
|
+
for (const known of KNOWN_CLI_FLAGS) {
|
|
150
|
+
const d = editDistance(key, known);
|
|
151
|
+
if (d < bestDist) { bestDist = d; best = known; }
|
|
152
|
+
}
|
|
153
|
+
if (best && bestDist <= 2) result.push({ flag: key, suggestion: best });
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
|
|
92
158
|
// ─── Time Formatting ─────────────────────────────────────────────────────────
|
|
93
159
|
|
|
94
160
|
/** "just now" / "5m ago" / "3h ago" / "2d ago" relative to now. */
|
package/mem-cli.mjs
CHANGED
|
@@ -37,7 +37,7 @@ import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
|
37
37
|
// v2.41: shared CLI helpers extracted to cli/common.mjs. Keep this file as the
|
|
38
38
|
// router + remaining-command bodies during the incremental split. Future work:
|
|
39
39
|
// move each cmdXxx into its own cli/<cmd>.mjs; mem-cli.mjs becomes pure dispatch.
|
|
40
|
-
import { parseArgs, out, fail, relativeTime, fmtDateShort, parseIdToken, formatProbeHints, rejectBareStringFlags, OBS_TIME_FIELDS, formatObsFieldValue } from './cli/common.mjs';
|
|
40
|
+
import { parseArgs, out, fail, relativeTime, fmtDateShort, parseIdToken, formatProbeHints, rejectBareStringFlags, suggestUnknownFlags, OBS_TIME_FIELDS, formatObsFieldValue } from './cli/common.mjs';
|
|
41
41
|
import { saveObservation } from './lib/save-observation.mjs';
|
|
42
42
|
import { rebuildObservationDerived } from './lib/observation-write.mjs';
|
|
43
43
|
import { recallByFile } from './lib/recall-core.mjs';
|
|
@@ -3007,6 +3007,15 @@ export async function run(argv) {
|
|
|
3007
3007
|
return;
|
|
3008
3008
|
}
|
|
3009
3009
|
|
|
3010
|
+
// Typo guard: parseArgs silently DROPS unknown flags, so `save --improtance 3` used to
|
|
3011
|
+
// persist the DEFAULT importance and `recent --projcte X` silently queried the inferred
|
|
3012
|
+
// project — a misspelled flag changed results with zero signal. Warn (stderr, non-fatal)
|
|
3013
|
+
// when a flag looks like a misspelling of a real one; stdout + exit code stay untouched,
|
|
3014
|
+
// so JSON/text consumers are unaffected. Mirrors the unknown-COMMAND suggester in cli.mjs.
|
|
3015
|
+
for (const { flag, suggestion } of suggestUnknownFlags(parseArgs(cmdArgs).flags)) {
|
|
3016
|
+
process.stderr.write(`[mem] Unknown flag --${flag}; did you mean --${suggestion}?\n`);
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3010
3019
|
// adopt / unadopt do pure filesystem work on ~/.claude/projects/<encoded>/memory/ —
|
|
3011
3020
|
// no DB needed. Route them before ensureDb() so an unbootable DB doesn't block.
|
|
3012
3021
|
if (cmd === 'adopt') { cmdAdopt(cmdArgs); return; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.34.0",
|
|
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/project-utils.mjs
CHANGED
|
@@ -32,6 +32,17 @@ export function resolveProject(db, name) {
|
|
|
32
32
|
).get(`%--${name}`);
|
|
33
33
|
if (suffixed) { _cache.set(name, suffixed.project); return suffixed.project; }
|
|
34
34
|
|
|
35
|
+
// 1.5) Exact-name match: a project literally named "p" (e.g. inferProject() at a
|
|
36
|
+
// filesystem-root cwd yields no "--", or a manually-saved bare name). MUST beat the
|
|
37
|
+
// fuzzy prefix/substring fallbacks below — otherwise `%p%` matches every "projects--*"
|
|
38
|
+
// row and ORDER BY COUNT(*) returns the biggest UNRELATED project, making the exact
|
|
39
|
+
// project permanently unreachable via --project. Ranks below step 1 only, preserving
|
|
40
|
+
// the documented "prefer canonical parent--name over a stray short name" intent.
|
|
41
|
+
const exact = db.prepare(
|
|
42
|
+
'SELECT project FROM observations WHERE project = ? LIMIT 1'
|
|
43
|
+
).get(name);
|
|
44
|
+
if (exact) { _cache.set(name, exact.project); return exact.project; }
|
|
45
|
+
|
|
35
46
|
// 2) Prefix-in-suffix match: "code-graph" → "projects--code-graph-mcp"
|
|
36
47
|
const prefixed = db.prepare(
|
|
37
48
|
'SELECT project FROM observations WHERE project LIKE ? GROUP BY project ORDER BY COUNT(*) DESC LIMIT 1'
|
|
@@ -22,7 +22,10 @@ function readStdin() {
|
|
|
22
22
|
process.stdin.setEncoding('utf8');
|
|
23
23
|
process.stdin.on('data', (c) => {
|
|
24
24
|
data += c;
|
|
25
|
-
|
|
25
|
+
// cap: agent prompts can be large. destroy() so the loop can drain and exit on
|
|
26
|
+
// its own (see the no-forced-exit note at the bottom) rather than streaming to
|
|
27
|
+
// the 1.5s timeout.
|
|
28
|
+
if (data.length > 262144) { clearTimeout(timer); try { process.stdin.destroy(); } catch { /* */ } resolve(data.slice(0, 262144)); }
|
|
26
29
|
});
|
|
27
30
|
process.stdin.on('end', () => { clearTimeout(timer); resolve(data); });
|
|
28
31
|
process.stdin.on('error', () => { clearTimeout(timer); resolve(data); });
|
|
@@ -60,4 +63,10 @@ async function main() {
|
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
// No forced process.exit(0): every readStdin path ends/destroys stdin and db.close()
|
|
67
|
+
// runs in main's finally, so the event loop drains and the process exits 0 on its own —
|
|
68
|
+
// which FLUSHES stdout. The emitted updatedInput echoes the whole prompt back, so the
|
|
69
|
+
// payload can exceed the ~64KB pipe buffer; a forced process.exit() would drop that
|
|
70
|
+
// pending async write and truncate the JSON (the gotcha every sibling hook avoids).
|
|
71
|
+
// Swallow any rejection so the exit code can never go non-zero.
|
|
72
|
+
main().catch(() => {});
|