claude-mem-lite 2.59.0 → 2.60.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.mjs +1 -1
- package/hook-memory.mjs +40 -1
- package/hook.mjs +8 -7
- package/lib/mem-override.mjs +31 -0
- package/mem-cli.mjs +69 -2
- package/memdir.mjs +65 -1
- package/package.json +2 -1
- package/scripts/prompt-search-utils.mjs +6 -0
- package/scripts/user-prompt-search.js +7 -1
- package/source-files.mjs +5 -0
package/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const CLI_COMMANDS = new Set(['search', 'recent', 'recall', 'get', 'timeline', 'save', 'stats', 'context', 'browse', 'delete', 'update', 'export', 'compress', 'maintain', 'optimize', 'fts-check', 'registry', 'import', 'enrich', 'activity', 'adopt', 'unadopt', 'help']);
|
|
2
|
+
const CLI_COMMANDS = new Set(['search', 'recent', 'recall', 'get', 'timeline', 'save', 'stats', 'context', 'browse', 'delete', 'update', 'export', 'compress', 'maintain', 'optimize', 'fts-check', 'registry', 'import', 'enrich', 'activity', 'adopt', 'unadopt', 'memdir-audit', 'help']);
|
|
3
3
|
const INSTALL_COMMANDS = new Set(['install', 'uninstall', 'status', 'doctor', 'cleanup', 'cleanup-hooks', 'self-update', 'release']);
|
|
4
4
|
|
|
5
5
|
const cmd = process.argv[2];
|
package/hook-memory.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// claude-mem-lite — Semantic Memory Injection
|
|
2
2
|
// Search past observations for relevant memories to inject as context at user-prompt time.
|
|
3
3
|
|
|
4
|
-
import { sanitizeFtsQuery, relaxFtsQueryToOr, debugCatch, OBS_BM25, notLowSignalTitleClause, noisePenaltyClause, tokenizeHandoff, HANDOFF_STOP_WORDS, extractCjkKeywords } from './utils.mjs';
|
|
4
|
+
import { sanitizeFtsQuery, relaxFtsQueryToOr, debugCatch, truncate, OBS_BM25, notLowSignalTitleClause, noisePenaltyClause, tokenizeHandoff, HANDOFF_STOP_WORDS, extractCjkKeywords } from './utils.mjs';
|
|
5
5
|
import { recordMetric } from './lib/metrics.mjs';
|
|
6
6
|
import { DB_DIR } from './schema.mjs';
|
|
7
7
|
|
|
@@ -78,6 +78,44 @@ function candidateCoverage(row, queryTerms) {
|
|
|
78
78
|
const FILE_RECALL_LOOKBACK_MS = 60 * 86400000; // 60 days
|
|
79
79
|
const MAX_FILE_RECALL = 2;
|
|
80
80
|
|
|
81
|
+
// P1: stale-obs verify-before-use threshold. An injected obs older than this
|
|
82
|
+
// AND carrying file paths is flagged so Claude is reminded to grep/Read the
|
|
83
|
+
// referenced code before applying the lesson — code may have moved or been
|
|
84
|
+
// renamed since capture. Pure-decision/architecture obs (no file_paths)
|
|
85
|
+
// don't get the hint: their drift is text-only and Claude already verifies
|
|
86
|
+
// at consumption time per the project mem-usage contract.
|
|
87
|
+
const STALE_OBS_THRESHOLD_MS = 30 * 86400000;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Format a single line for the <memory-context> block emitted by
|
|
91
|
+
* handleUserPrompt. Pure function — exported for unit testing.
|
|
92
|
+
*
|
|
93
|
+
* @param {object} obs Row with {id, type, title, lesson_learned,
|
|
94
|
+
* created_at_epoch, files_modified}. files_modified is a JSON-encoded
|
|
95
|
+
* string array (column shape) or null.
|
|
96
|
+
* @returns {string} `- [type] title[ | Lesson: X] (#id)[ [verify-before-use]]`
|
|
97
|
+
*/
|
|
98
|
+
export function formatMemoryLine(obs) {
|
|
99
|
+
const lessonTag = obs.lesson_learned ? ` | Lesson: ${obs.lesson_learned}` : '';
|
|
100
|
+
let staleHint = '';
|
|
101
|
+
if (typeof obs.created_at_epoch === 'number'
|
|
102
|
+
&& Date.now() - obs.created_at_epoch > STALE_OBS_THRESHOLD_MS
|
|
103
|
+
&& hasFilePaths(obs.files_modified)) {
|
|
104
|
+
staleHint = ' [verify-before-use]';
|
|
105
|
+
}
|
|
106
|
+
return `- [${obs.type}] ${truncate(obs.title, 80)}${lessonTag} (#${obs.id})${staleHint}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function hasFilePaths(filesModified) {
|
|
110
|
+
if (!filesModified || typeof filesModified !== 'string') return false;
|
|
111
|
+
try {
|
|
112
|
+
const arr = JSON.parse(filesModified);
|
|
113
|
+
return Array.isArray(arr) && arr.length > 0;
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
81
119
|
/**
|
|
82
120
|
* Search for relevant past observations to inject as memory context.
|
|
83
121
|
* Quality gates: importance>=1 (with 0.6x penalty), type-boosted, lesson-boosted, BM25-thresholded (adaptive: 0 for <5 obs, 1.5 otherwise).
|
|
@@ -124,6 +162,7 @@ export function searchRelevantMemories(db, userPrompt, project, excludeIds = [])
|
|
|
124
162
|
// penalty factor (for the final JS score).
|
|
125
163
|
const selectStmt = db.prepare(`
|
|
126
164
|
SELECT o.id, o.type, o.title, o.subtitle, o.narrative, o.importance, o.lesson_learned, o.project,
|
|
165
|
+
o.created_at_epoch, o.files_modified,
|
|
127
166
|
${OBS_BM25} as relevance,
|
|
128
167
|
${noisePenaltyClause('o')} as noise_penalty
|
|
129
168
|
FROM observations_fts
|
package/hook.mjs
CHANGED
|
@@ -45,7 +45,8 @@ import {
|
|
|
45
45
|
import { handleLLMEpisode, handleLLMSummary, saveObservation, buildImmediateObservation } from './hook-llm.mjs';
|
|
46
46
|
import { extractCitationsFromTranscript, bumpCitationAccess } from './lib/citation-tracker.mjs';
|
|
47
47
|
import { extractTailAssistantText, extractStructuredSummary } from './lib/summary-extractor.mjs';
|
|
48
|
-
import { searchRelevantMemories } from './hook-memory.mjs';
|
|
48
|
+
import { searchRelevantMemories, formatMemoryLine } from './hook-memory.mjs';
|
|
49
|
+
import { detectMemOverride } from './lib/mem-override.mjs';
|
|
49
50
|
import { buildAndSaveHandoff, detectContinuationIntent, renderHandoffInjection, pickHandoffToInject, extractUnfinishedSummary } from './hook-handoff.mjs';
|
|
50
51
|
import { checkForUpdate } from './hook-update.mjs';
|
|
51
52
|
import { handleLLMOptimize } from './hook-optimize.mjs';
|
|
@@ -1111,8 +1112,11 @@ async function handleUserPrompt() {
|
|
|
1111
1112
|
} catch (e) { debugCatch(e, 'handleUserPrompt-handoff'); }
|
|
1112
1113
|
}
|
|
1113
1114
|
|
|
1114
|
-
// Semantic memory injection: search past observations for the user's prompt
|
|
1115
|
-
|
|
1115
|
+
// Semantic memory injection: search past observations for the user's prompt.
|
|
1116
|
+
// P0 short-circuit on user-explicit "ignore memory" / "不要用记忆" override
|
|
1117
|
+
// (mirrors CC built-in memoryTypes.ts:215). Skip both Key Context lookup
|
|
1118
|
+
// and the <memory-context> emission for this turn.
|
|
1119
|
+
if (!detectMemOverride(promptText)) try {
|
|
1116
1120
|
const keyObs = db.prepare(`
|
|
1117
1121
|
SELECT id FROM observations
|
|
1118
1122
|
WHERE project = ? AND COALESCE(compressed_into, 0) = 0
|
|
@@ -1135,10 +1139,7 @@ async function handleUserPrompt() {
|
|
|
1135
1139
|
const memories = searchRelevantMemories(db, promptText, project, keyContextIds);
|
|
1136
1140
|
if (memories.length > 0) {
|
|
1137
1141
|
const lines = ['<memory-context relevance="high">'];
|
|
1138
|
-
for (const m of memories)
|
|
1139
|
-
const lessonTag = m.lesson_learned ? ` | Lesson: ${m.lesson_learned}` : '';
|
|
1140
|
-
lines.push(`- [${m.type}] ${truncate(m.title, 80)}${lessonTag} (#${m.id})`);
|
|
1141
|
-
}
|
|
1142
|
+
for (const m of memories) lines.push(formatMemoryLine(m));
|
|
1142
1143
|
lines.push('</memory-context>');
|
|
1143
1144
|
process.stdout.write(lines.join('\n') + '\n');
|
|
1144
1145
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// User-explicit "ignore memory" override detector. Mirrors CC built-in
|
|
2
|
+
// memoryTypes.ts:215 ("If the user says to *ignore* or *not use* memory:
|
|
3
|
+
// Do not apply remembered facts"). Tight regexes — must require both an
|
|
4
|
+
// "ignore-class" verb AND the memory token, so phrases like "memory leak",
|
|
5
|
+
// "记忆中的事件", "MEM-1234" pass through unaffected.
|
|
6
|
+
//
|
|
7
|
+
// Two parallel patterns:
|
|
8
|
+
// EN — ignore|skip|forget|disable|drop|reject + (optional qualifier)
|
|
9
|
+
// + memor(y|ies) | memory-context | past context | recall;
|
|
10
|
+
// plus the negated form: do not / don't + use|read|inject|apply.
|
|
11
|
+
// CN — 1) ignore-class verbs (无视|忽略|忽视|跳过|拒绝|不再[用|看|读|参考])
|
|
12
|
+
// + (optional qualifier) + 记忆
|
|
13
|
+
// 2) 不要|别|不需|不必 + use-class verb (用|看|读|参考|...) + 记忆.
|
|
14
|
+
//
|
|
15
|
+
// Lives under lib/ (not scripts/) because hook.mjs imports it directly
|
|
16
|
+
// for the handleUserPrompt short-circuit. install.mjs/hook-update.mjs
|
|
17
|
+
// rename scripts/ as a directory; an individual `scripts/<file>.mjs`
|
|
18
|
+
// entry in SOURCE_FILES would collide with that rename.
|
|
19
|
+
|
|
20
|
+
const MEM_OVERRIDE_EN = /\b(?:ignore|skip|forget|disable|drop|reject)\s+(?:(?:any|all|the|past|prior|previous|recalled?|injected|stored)\s+){0,3}(?:memor(?:y|ies)|memory-?context|mem[\s-]context|past\s+context|recall)\b|\b(?:do\s+not|don['’`]?t)\s+(?:use|read|inject|apply)\s+(?:(?:any|all|the|past|prior|previous|recalled?|injected|stored)\s+){0,3}(?:memor(?:y|ies)|memory-?context|mem[\s-]context|past\s+context|recall)\b/i;
|
|
21
|
+
|
|
22
|
+
const MEM_OVERRIDE_CN = /(?:无视|忽略|忽视|跳过|拒绝|不再用|不再看|不再读|不再参考)\s*(?:任何|所有|过去|先前|之前|历史|相关|这次|本次|过往|注入|的){0,3}\s*记忆|(?:不要|别|不需|不必)\s*(?:再)?\s*(?:用|看|读|查|参考|使用|启用|采用|采纳|读取|加载|应用|注入|带上)\s*(?:任何|所有|过去|先前|之前|历史|相关|这次|本次|过往|注入|的){0,3}\s*记忆/;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns true if the prompt explicitly tells Claude to ignore memory.
|
|
26
|
+
* UPS hook + handleUserPrompt memory injection MUST short-circuit on true.
|
|
27
|
+
*/
|
|
28
|
+
export function detectMemOverride(text) {
|
|
29
|
+
if (!text || typeof text !== 'string') return false;
|
|
30
|
+
return MEM_OVERRIDE_EN.test(text) || MEM_OVERRIDE_CN.test(text);
|
|
31
|
+
}
|
package/mem-cli.mjs
CHANGED
|
@@ -17,9 +17,10 @@ import { searchResources } from './registry-retriever.mjs';
|
|
|
17
17
|
import { optimizePreview, optimizeRun } from './hook-optimize.mjs';
|
|
18
18
|
import { buildSessionContextLines } from './hook-context.mjs';
|
|
19
19
|
import { cmdAdopt, cmdUnadopt } from './adopt-cli.mjs';
|
|
20
|
+
import { auditMemdir, memdirPath } from './memdir.mjs';
|
|
20
21
|
import { probeOtherSources as probeIdSources, bucketIdTokens } from './lib/id-routing.mjs';
|
|
21
|
-
import { basename } from 'path';
|
|
22
|
-
import { readFileSync } from 'fs';
|
|
22
|
+
import { basename, join } from 'path';
|
|
23
|
+
import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
23
24
|
|
|
24
25
|
// v2.41: shared CLI helpers extracted to cli/common.mjs. Keep this file as the
|
|
25
26
|
// router + remaining-command bodies during the incremental split. Future work:
|
|
@@ -1905,6 +1906,65 @@ function cmdRegistry(_memDb, args) {
|
|
|
1905
1906
|
}
|
|
1906
1907
|
}
|
|
1907
1908
|
|
|
1909
|
+
// ─── memdir-audit ────────────────────────────────────────────────────────────
|
|
1910
|
+
// Body-structure audit for ~/.claude/projects/<encoded>/memory/feedback_*.md
|
|
1911
|
+
// and project_*.md. CLI-only by design — running this every session would be
|
|
1912
|
+
// noise; it's a one-shot governance pass. Exit code 0 = 100% compliant,
|
|
1913
|
+
// 1 = at least one file is non-compliant (so it can gate CI if a project
|
|
1914
|
+
// wants to enforce structure).
|
|
1915
|
+
|
|
1916
|
+
function _formatAuditResult(memdir, result) {
|
|
1917
|
+
const lines = [`[mem] memdir audit: ${memdir}`];
|
|
1918
|
+
const fmt = (label, list) =>
|
|
1919
|
+
list.length ? `${label} (${list.length}):\n - ${list.join('\n - ')}` : `${label} (0)`;
|
|
1920
|
+
lines.push(fmt('Compliant', result.compliant));
|
|
1921
|
+
lines.push(fmt('Missing **Why:**', result.missingWhy));
|
|
1922
|
+
lines.push(fmt('Missing **How to apply:**', result.missingHowToApply));
|
|
1923
|
+
lines.push(fmt('Missing both', result.missingBoth));
|
|
1924
|
+
lines.push(`Total: ${result.total} file(s) (${result.compliant.length} compliant)`);
|
|
1925
|
+
return lines.join('\n');
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
function _resolveMemdirsForAudit(flags) {
|
|
1929
|
+
if (typeof flags.memdir === 'string' && flags.memdir.length > 0) {
|
|
1930
|
+
return [flags.memdir];
|
|
1931
|
+
}
|
|
1932
|
+
if (flags.all === true || flags.all === 'true') {
|
|
1933
|
+
const projectsRoot = join(homedir(), '.claude', 'projects');
|
|
1934
|
+
if (!existsSync(projectsRoot)) return [];
|
|
1935
|
+
let entries;
|
|
1936
|
+
try { entries = readdirSync(projectsRoot); } catch { return []; }
|
|
1937
|
+
return entries
|
|
1938
|
+
.map(name => join(projectsRoot, name, 'memory'))
|
|
1939
|
+
.filter(p => existsSync(p))
|
|
1940
|
+
.sort();
|
|
1941
|
+
}
|
|
1942
|
+
return [memdirPath(process.cwd())];
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
function cmdMemdirAudit(args) {
|
|
1946
|
+
const { flags } = parseArgs(args);
|
|
1947
|
+
const memdirs = _resolveMemdirsForAudit(flags);
|
|
1948
|
+
if (memdirs.length === 0) {
|
|
1949
|
+
out('[mem] No memdirs to audit (use --memdir <path> or run inside a Claude Code project).');
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1952
|
+
let nonCompliant = 0;
|
|
1953
|
+
let totalScanned = 0;
|
|
1954
|
+
for (const md of memdirs) {
|
|
1955
|
+
const result = auditMemdir(md);
|
|
1956
|
+
out(_formatAuditResult(md, result));
|
|
1957
|
+
totalScanned += result.total;
|
|
1958
|
+
nonCompliant +=
|
|
1959
|
+
result.missingWhy.length + result.missingHowToApply.length + result.missingBoth.length;
|
|
1960
|
+
if (memdirs.length > 1) out('');
|
|
1961
|
+
}
|
|
1962
|
+
if (memdirs.length > 1) {
|
|
1963
|
+
out(`[mem] Scanned ${memdirs.length} memdir(s), ${totalScanned} memory file(s), ${nonCompliant} non-compliant.`);
|
|
1964
|
+
}
|
|
1965
|
+
if (nonCompliant > 0) process.exitCode = 1;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1908
1968
|
// ─── Help ────────────────────────────────────────────────────────────────────
|
|
1909
1969
|
|
|
1910
1970
|
function cmdHelp() {
|
|
@@ -2046,6 +2106,12 @@ Commands:
|
|
|
2046
2106
|
unadopt Precise removal of the sentinel block + plugin_claude_mem_lite.md.
|
|
2047
2107
|
--all Unadopt every project
|
|
2048
2108
|
|
|
2109
|
+
memdir-audit Audit memdir feedback_*.md / project_*.md for the
|
|
2110
|
+
body-structure contract (**Why:** + **How to apply:**).
|
|
2111
|
+
Exit 0 if every file is compliant, 1 otherwise.
|
|
2112
|
+
--memdir <path> Audit an explicit memdir path (escape hatch)
|
|
2113
|
+
--all Audit every project under ~/.claude/projects/*/memory/
|
|
2114
|
+
|
|
2049
2115
|
DB: ${DB_PATH}`);
|
|
2050
2116
|
}
|
|
2051
2117
|
|
|
@@ -2240,6 +2306,7 @@ export async function run(argv) {
|
|
|
2240
2306
|
// no DB needed. Route them before ensureDb() so an unbootable DB doesn't block.
|
|
2241
2307
|
if (cmd === 'adopt') { cmdAdopt(cmdArgs); return; }
|
|
2242
2308
|
if (cmd === 'unadopt') { cmdUnadopt(cmdArgs); return; }
|
|
2309
|
+
if (cmd === 'memdir-audit') { cmdMemdirAudit(cmdArgs); return; }
|
|
2243
2310
|
|
|
2244
2311
|
let db;
|
|
2245
2312
|
try {
|
package/memdir.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
//
|
|
10
10
|
// See docs/plans/2026-04-16-invited-memory-pattern.md for rationale.
|
|
11
11
|
|
|
12
|
-
import { readFileSync, writeFileSync, existsSync, renameSync, unlinkSync, mkdirSync } from 'fs';
|
|
12
|
+
import { readFileSync, writeFileSync, existsSync, renameSync, unlinkSync, mkdirSync, readdirSync } from 'fs';
|
|
13
13
|
import { join } from 'path';
|
|
14
14
|
import { homedir } from 'os';
|
|
15
15
|
import { createHash } from 'crypto';
|
|
@@ -268,3 +268,67 @@ export function removePluginDoc(memdir, slug) {
|
|
|
268
268
|
if (!existsSync(path)) return;
|
|
269
269
|
try { unlinkSync(path); } catch { /* best-effort */ }
|
|
270
270
|
}
|
|
271
|
+
|
|
272
|
+
// ─── P2: body-structure audit ────────────────────────────────────────────────
|
|
273
|
+
// CC's CLAUDE.md memory contract requires feedback_*.md and project_*.md to
|
|
274
|
+
// carry **Why:** + **How to apply:** lines. user_*.md and reference_*.md
|
|
275
|
+
// have no body-structure requirement (per memoryTypes.ts <body_structure>
|
|
276
|
+
// blocks). MEMORY.md (the index) is excluded too — it lists pointers, not
|
|
277
|
+
// memory content. This is intentionally a CLI-only tool (not a hook): it
|
|
278
|
+
// is a one-shot governance pass, running it on every session would just be
|
|
279
|
+
// noise.
|
|
280
|
+
|
|
281
|
+
const AUDIT_FILE_RE = /^(feedback|project)_[A-Za-z0-9_-]+\.md$/;
|
|
282
|
+
const WHY_RE = /^\s*\*\*Why:\*\*/m;
|
|
283
|
+
const HOW_RE = /^\s*\*\*How to apply:\*\*/m;
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Strip the leading YAML frontmatter block (between `---` fences) so audit
|
|
287
|
+
* checks run only against body content. Returns input unchanged if no
|
|
288
|
+
* frontmatter is present.
|
|
289
|
+
*/
|
|
290
|
+
function stripFrontmatter(content) {
|
|
291
|
+
if (!content.startsWith('---\n') && !content.startsWith('---\r\n')) return content;
|
|
292
|
+
const m = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n/);
|
|
293
|
+
return m ? content.slice(m[0].length) : content;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Scan a memdir for feedback_* and project_* files and bucket them by
|
|
298
|
+
* body-structure compliance. Pure function — IO is read-only and bounded
|
|
299
|
+
* to the directory listing + per-file Reads.
|
|
300
|
+
*
|
|
301
|
+
* @param {string} memdir Absolute path to memdir
|
|
302
|
+
* @returns {{
|
|
303
|
+
* compliant: string[],
|
|
304
|
+
* missingWhy: string[],
|
|
305
|
+
* missingHowToApply: string[],
|
|
306
|
+
* missingBoth: string[],
|
|
307
|
+
* total: number,
|
|
308
|
+
* }}
|
|
309
|
+
*/
|
|
310
|
+
export function auditMemdir(memdir) {
|
|
311
|
+
const result = { compliant: [], missingWhy: [], missingHowToApply: [], missingBoth: [], total: 0 };
|
|
312
|
+
if (!memdir || !existsSync(memdir)) return result;
|
|
313
|
+
|
|
314
|
+
let entries;
|
|
315
|
+
try { entries = readdirSync(memdir); } catch { return result; }
|
|
316
|
+
|
|
317
|
+
const targets = entries.filter(n => AUDIT_FILE_RE.test(n)).sort();
|
|
318
|
+
for (const name of targets) {
|
|
319
|
+
let body = '';
|
|
320
|
+
try {
|
|
321
|
+
const raw = readFileSync(join(memdir, name), 'utf8');
|
|
322
|
+
body = stripFrontmatter(raw);
|
|
323
|
+
} catch { /* unreadable — count as missingBoth */ }
|
|
324
|
+
|
|
325
|
+
const hasWhy = WHY_RE.test(body);
|
|
326
|
+
const hasHow = HOW_RE.test(body);
|
|
327
|
+
if (hasWhy && hasHow) result.compliant.push(name);
|
|
328
|
+
else if (!hasWhy && !hasHow) result.missingBoth.push(name);
|
|
329
|
+
else if (!hasWhy) result.missingWhy.push(name);
|
|
330
|
+
else result.missingHowToApply.push(name);
|
|
331
|
+
}
|
|
332
|
+
result.total = targets.length;
|
|
333
|
+
return result;
|
|
334
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.60.0",
|
|
4
4
|
"description": "Lightweight persistent memory system for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"lib/id-routing.mjs",
|
|
59
59
|
"lib/err-sampler.mjs",
|
|
60
60
|
"lib/metrics.mjs",
|
|
61
|
+
"lib/mem-override.mjs",
|
|
61
62
|
"cli/common.mjs",
|
|
62
63
|
"cli/fts-check.mjs",
|
|
63
64
|
"cli/doctor.mjs",
|
|
@@ -101,6 +101,12 @@ export function detectIntent(text) {
|
|
|
101
101
|
return first;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
// detectMemOverride lives in lib/mem-override.mjs (importable from hook.mjs
|
|
105
|
+
// without dragging the scripts/ tree into SOURCE_FILES). Re-exported here so
|
|
106
|
+
// scripts/user-prompt-search.js and existing tests can keep importing it
|
|
107
|
+
// from the same module as the rest of the prompt-side helpers.
|
|
108
|
+
export { detectMemOverride } from '../lib/mem-override.mjs';
|
|
109
|
+
|
|
104
110
|
// ─── Error Signature Extraction ─────────────────────────────────────────────
|
|
105
111
|
|
|
106
112
|
/**
|
|
@@ -9,7 +9,7 @@ import { cjkPrecisionOk } from '../nlp.mjs';
|
|
|
9
9
|
import { writeFileSync, readFileSync, existsSync, renameSync } from 'fs';
|
|
10
10
|
import { join } from 'path';
|
|
11
11
|
import Database from 'better-sqlite3';
|
|
12
|
-
import { shouldSkip, computeEffectiveLen, detectIntent, shouldSkipByDedup, extractFiles, extractErrorSignature, DEDUP_STALE_MS, matchRegistrySkillName } from './prompt-search-utils.mjs';
|
|
12
|
+
import { shouldSkip, computeEffectiveLen, detectIntent, shouldSkipByDedup, extractFiles, extractErrorSignature, DEDUP_STALE_MS, matchRegistrySkillName, detectMemOverride } from './prompt-search-utils.mjs';
|
|
13
13
|
|
|
14
14
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
15
15
|
|
|
@@ -491,6 +491,12 @@ async function main() {
|
|
|
491
491
|
// Skip short/confirmation/slash-command/simple-op prompts
|
|
492
492
|
if (shouldSkip(promptText)) return;
|
|
493
493
|
|
|
494
|
+
// P0: User-explicit "ignore memory" override (mirrors CC built-in
|
|
495
|
+
// memoryTypes.ts:215). When the prompt directly tells Claude to skip
|
|
496
|
+
// memory recall, we short-circuit before FTS — no FTS budget burn,
|
|
497
|
+
// no .claude-mem-injected-* state churn, no surface emission.
|
|
498
|
+
if (detectMemOverride(promptText)) return;
|
|
499
|
+
|
|
494
500
|
// T3 (v2.31): additional raw-length gate on top of shouldSkip's CJK-weighted
|
|
495
501
|
// effective-length check. Suppresses medium-short Latin prompts ("run tests",
|
|
496
502
|
// "fix bug now") that carry too few content tokens for a meaningful FTS lookup.
|
package/source-files.mjs
CHANGED
|
@@ -53,6 +53,11 @@ export const SOURCE_FILES = [
|
|
|
53
53
|
'memdir.mjs',
|
|
54
54
|
'adopt-content.mjs',
|
|
55
55
|
'adopt-cli.mjs',
|
|
56
|
+
// P0 (v2.59.x): user-explicit "ignore memory" override detector. Lives
|
|
57
|
+
// under lib/ (not scripts/) so hook.mjs can statically import it without
|
|
58
|
+
// colliding with the scripts/ directory rename in installExtractedRelease
|
|
59
|
+
// — see the SWITCHABLE_PATHS loop in hook-update.mjs.
|
|
60
|
+
'lib/mem-override.mjs',
|
|
56
61
|
];
|
|
57
62
|
|
|
58
63
|
/**
|