claude-mem-lite 2.54.0 → 2.58.2
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/doctor.mjs +30 -1
- package/cli.mjs +8 -4
- package/haiku-client.mjs +51 -13
- package/hook-llm.mjs +131 -34
- package/hook-shared.mjs +6 -2
- package/hook-update.mjs +70 -11
- package/hook.mjs +29 -7
- package/install.mjs +34 -32
- package/lib/low-signal-patterns.mjs +38 -0
- package/lib/private-strip.mjs +36 -0
- package/mem-cli.mjs +43 -1
- package/package.json +7 -2
- package/schema.mjs +132 -1
- package/scripts/setup.sh +58 -4
- package/scripts/user-prompt-search.js +124 -9
- package/source-files.mjs +21 -0
- package/utils.mjs +1 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Lightweight: only imports schema.mjs and utils.mjs, no MCP SDK
|
|
5
5
|
|
|
6
6
|
import { ensureDb, DB_DIR, REGISTRY_DB_PATH } from '../schema.mjs';
|
|
7
|
-
import { sanitizeFtsQuery, relaxFtsQueryToOr, truncate, typeIcon, inferProject, OBS_BM25, TYPE_DECAY_CASE, TYPE_QUALITY_CASE, notLowSignalTitleClause, noisePenaltyClause } from '../utils.mjs';
|
|
7
|
+
import { sanitizeFtsQuery, relaxFtsQueryToOr, truncate, typeIcon, inferProject, OBS_BM25, TYPE_DECAY_CASE, TYPE_QUALITY_CASE, notLowSignalTitleClause, noisePenaltyClause, stripPrivate } from '../utils.mjs';
|
|
8
8
|
import { cjkPrecisionOk } from '../nlp.mjs';
|
|
9
9
|
import { writeFileSync, readFileSync, existsSync, renameSync } from 'fs';
|
|
10
10
|
import { join } from 'path';
|
|
@@ -14,9 +14,24 @@ import { shouldSkip, computeEffectiveLen, detectIntent, shouldSkipByDedup, extra
|
|
|
14
14
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
15
15
|
|
|
16
16
|
const INJECTED_IDS_FILE = join(DB_DIR, 'runtime', `.claude-mem-injected-${inferProject()}`);
|
|
17
|
-
|
|
17
|
+
// Per-prompt UPS cap. Cut from 5 → 3 after the 2026-05-09 per-hook recall
|
|
18
|
+
// scan (#8255): UPS contributed 74% of silent injected IDs (131/177) at 26%
|
|
19
|
+
// recall, vs PreToolUse:Read at 94% recall on a tighter file-keyed set.
|
|
20
|
+
// Hypothesis: fewer candidates → each one more relevant → cite-rate up.
|
|
21
|
+
// useRecent intent path is unaffected (it uses intent.limit=5 directly,
|
|
22
|
+
// gated by explicit "before/previously/记得" prompts where breadth is the
|
|
23
|
+
// point). Env override for projects that want broader recall or to A/B.
|
|
24
|
+
const MAX_RESULTS = Number(process.env.CLAUDE_MEM_UPS_MAX_RESULTS || 3);
|
|
18
25
|
const LOOKBACK_MS = 60 * 86400000; // 60 days
|
|
19
26
|
|
|
27
|
+
// v2.56.x: Past-similar-questions fallback row cap. Cut from 3 → 1 after
|
|
28
|
+
// 30d transcript scan (#8062 follow-up, 2026-05-09) showed UPS prompt-fallback
|
|
29
|
+
// path contributing ~24% of session injection budget with near-zero cite-recall.
|
|
30
|
+
// Unlike the obs FTS path (TOP_REL_FLOOR + BM25 gates), prompt-fallback has no
|
|
31
|
+
// quality gate — only BM25 ordering — so additional rows inflate noise without
|
|
32
|
+
// improving signal. Env-overridable for projects that want broader prompt recall.
|
|
33
|
+
const PROMPT_FALLBACK_LIMIT = Number(process.env.CLAUDE_MEM_UPS_PROMPT_FALLBACK_LIMIT || 1);
|
|
34
|
+
|
|
20
35
|
// T3 (v2.31): per-row BM25 magnitude floor. OBS_BM25 (in scoring-sql.mjs)
|
|
21
36
|
// returns the raw bm25() value — negative, smaller = better. Multiplied by
|
|
22
37
|
// decay × type-quality × (0.5+0.5·importance), sign stays negative. We
|
|
@@ -104,6 +119,82 @@ function isFollowUpSession() {
|
|
|
104
119
|
} catch { return false; }
|
|
105
120
|
}
|
|
106
121
|
|
|
122
|
+
// ─── Explicit-signal gate (v2.57.x) ─────────────────────────────────────────
|
|
123
|
+
//
|
|
124
|
+
// Upstream gate that decides whether the FTS / prompt-fallback paths run at
|
|
125
|
+
// all. Per cite-recall baseline 2026-04-22 → 2026-05-09 (29 sessions),
|
|
126
|
+
// UserPromptSubmit injection cite-recall = 25.8% (132/178 silent injections)
|
|
127
|
+
// vs PreToolUse:Read/Edit at 94.1/94.2%. The gap is the always-search policy
|
|
128
|
+
// burning tokens on prompts the model never refers back to.
|
|
129
|
+
//
|
|
130
|
+
// Retreat: only inject when the prompt carries a signal that names something
|
|
131
|
+
// concrete. Four orthogonal channels:
|
|
132
|
+
// (1) error-signature — extractErrorSignature() typed exception match
|
|
133
|
+
// (2) file-reference — extractFiles() basename.ext or path separator
|
|
134
|
+
// (3) detected intent — detectIntent() catches recall words ("记得", "之前",
|
|
135
|
+
// "previously") + actionable keywords (bugfix/test/
|
|
136
|
+
// decision/refactor/perf/schema/implement/...)
|
|
137
|
+
// (4) tech identifier — CamelCase / snake_case / ALL_CAPS_CONST /
|
|
138
|
+
// kebab-case (≥3 segments). Conservative — drops
|
|
139
|
+
// single-lowercase-word identifiers ("mem", "fix")
|
|
140
|
+
// since those are 99% prose noise.
|
|
141
|
+
//
|
|
142
|
+
// "No signal" prompts ("does this work?", "how is it going") return no
|
|
143
|
+
// injection. PreToolUse file-keyed hook is independent (94% recall track,
|
|
144
|
+
// fires on Edit/Read/Write file paths) — not affected.
|
|
145
|
+
//
|
|
146
|
+
// Env override: CLAUDE_MEM_UPS_REQUIRE_SIGNAL=0 restores always-search.
|
|
147
|
+
// Default ON.
|
|
148
|
+
//
|
|
149
|
+
// Note for OR-fallback gate (#8144) interaction: this gate is upstream of
|
|
150
|
+
// score-quality gates (OR_TOP_BM25_FLOOR / TOP_REL_FLOOR). They compose:
|
|
151
|
+
// presence-gate decides whether to search at all; score-gate trims the
|
|
152
|
+
// returned set. Orthogonal layers — turning REQUIRE_SIGNAL off restores
|
|
153
|
+
// the previous behavior where score-gates alone control noise.
|
|
154
|
+
//
|
|
155
|
+
// Regex post-review (Important #1): bare-acronym ALL_CAPS arm `[A-Z]{2,}…`
|
|
156
|
+
// false-positived on common English prose (IBM, NPM, THE, BSD, ASCII).
|
|
157
|
+
// camelCase arm `[a-z][a-z0-9]*[A-Z]…` false-positived on iOS, eBay.
|
|
158
|
+
// Five-arm tightening:
|
|
159
|
+
// • snake_case — requires `_` between lowercase tokens
|
|
160
|
+
// • CONST_CASE — requires `_` between uppercase tokens (catches
|
|
161
|
+
// MAX_RESULTS, CLAUDE_MEM_DIR, OBS_BM25)
|
|
162
|
+
// • ACRONYM_w_digit — bare 2+-cap run with at least one digit (catches
|
|
163
|
+
// FTS5, MD5, HTML5, OAUTH2, HTTP2; rejects IBM/NPM/
|
|
164
|
+
// THE/BSD/ASCII which never carry digits in prose)
|
|
165
|
+
// • camelCase — requires ≥2 lowercase before the first cap
|
|
166
|
+
// (excludes iOS, eBay; allows getUserById, parseJsonFromLLM)
|
|
167
|
+
// • kebab-case — ≥3 segments (pre-tool-use; excludes "easy-to-use")
|
|
168
|
+
// Bare digitless acronyms (URL, JWT, JSON, HTTP) no longer match — they
|
|
169
|
+
// typically appear alongside intent keywords or files anyway, so the gate
|
|
170
|
+
// catches the prompt via those channels rather than the identifier itself.
|
|
171
|
+
const TECH_IDENTIFIER_RE = /\b(?:[a-z][a-z0-9]*_[a-z0-9_]+|[A-Z][A-Z0-9]*_[A-Z0-9_]+|[A-Z]{2,}[0-9][A-Z0-9_]*|[a-z]{2,}[A-Z][a-zA-Z0-9]+|[a-z]+(?:-[a-z]+){2,})\b/;
|
|
172
|
+
|
|
173
|
+
// CJK presence channel (Important #2): bilingual users (project memory
|
|
174
|
+
// `feedback_*` calls this out explicitly) ask CJK questions that may carry
|
|
175
|
+
// genuine debug intent without containing an English identifier. CJK is
|
|
176
|
+
// information-dense — an 8-effective-unit prompt rarely encodes "how is it
|
|
177
|
+
// going"-style noise. Threshold mirrors shouldSkip's CJK floor.
|
|
178
|
+
const CJK_CHAR_RE = /[一-鿿-ヿ]/;
|
|
179
|
+
const CJK_MIN_EFFECTIVE_LEN = 8;
|
|
180
|
+
|
|
181
|
+
const REQUIRE_EXPLICIT_SIGNAL = process.env.CLAUDE_MEM_UPS_REQUIRE_SIGNAL !== '0';
|
|
182
|
+
|
|
183
|
+
export function hasExplicitSignal(text, { errSig, files, intent } = {}) {
|
|
184
|
+
if (!text) return false;
|
|
185
|
+
if (errSig) return true;
|
|
186
|
+
if (Array.isArray(files) && files.length > 0) return true;
|
|
187
|
+
if (intent) return true;
|
|
188
|
+
// Recompute path — fires only when the caller passes `text` alone (test
|
|
189
|
+
// entry point); production caller in main() always pre-computes all three.
|
|
190
|
+
if (errSig === undefined && extractErrorSignature(text)) return true;
|
|
191
|
+
if (files === undefined && extractFiles(text).length > 0) return true;
|
|
192
|
+
if (intent === undefined && detectIntent(text)) return true;
|
|
193
|
+
if (TECH_IDENTIFIER_RE.test(text)) return true;
|
|
194
|
+
if (CJK_CHAR_RE.test(text) && computeEffectiveLen(text) >= CJK_MIN_EFFECTIVE_LEN) return true;
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
107
198
|
// ─── DB Query Functions ─────────────────────────────────────────────────────
|
|
108
199
|
|
|
109
200
|
// Returns { rows, mode } where mode is 'AND' (initial pass), 'OR' (fallback
|
|
@@ -385,11 +476,17 @@ async function main() {
|
|
|
385
476
|
let hookData;
|
|
386
477
|
try { hookData = JSON.parse(raw); } catch { return; }
|
|
387
478
|
|
|
388
|
-
const
|
|
389
|
-
if (!
|
|
479
|
+
const rawPrompt = hookData.prompt || hookData.user_prompt;
|
|
480
|
+
if (!rawPrompt || typeof rawPrompt !== 'string') return;
|
|
390
481
|
|
|
391
|
-
// Skip internal protocol messages
|
|
392
|
-
|
|
482
|
+
// Skip internal protocol messages (check on raw text — protocol sentinel
|
|
483
|
+
// would never legitimately be wrapped in <private>).
|
|
484
|
+
if (rawPrompt.startsWith('<task-notification>')) return;
|
|
485
|
+
|
|
486
|
+
// Strip <private>...</private> blocks before length gates and FTS query
|
|
487
|
+
// construction — private content must not pad effective length nor leak
|
|
488
|
+
// into the FTS MATCH query terms. Mirrors hook.mjs handleUserPrompt.
|
|
489
|
+
const promptText = stripPrivate(rawPrompt);
|
|
393
490
|
|
|
394
491
|
// Skip short/confirmation/slash-command/simple-op prompts
|
|
395
492
|
if (shouldSkip(promptText)) return;
|
|
@@ -426,12 +523,25 @@ async function main() {
|
|
|
426
523
|
)
|
|
427
524
|
: [];
|
|
428
525
|
|
|
526
|
+
// v2.57.x explicit-signal gate. Compute files once for both the gate and
|
|
527
|
+
// the file-recall path below — extractFiles is regex over the prompt,
|
|
528
|
+
// safe to call eagerly. errSig + intent already computed above.
|
|
529
|
+
const filesForGate = extractFiles(promptText);
|
|
530
|
+
const signalPresent = hasExplicitSignal(promptText, {
|
|
531
|
+
errSig, files: filesForGate, intent,
|
|
532
|
+
});
|
|
533
|
+
|
|
429
534
|
if (intent?.useRecent) {
|
|
430
535
|
// Recall intent: show recent observations
|
|
431
536
|
rows = searchRecent(db, project, intent.limit);
|
|
537
|
+
} else if (REQUIRE_EXPLICIT_SIGNAL && !signalPresent) {
|
|
538
|
+
// No explicit signal — skip FTS pipeline + prompt-fallback. sigRows
|
|
539
|
+
// is already empty (errSig was null else signalPresent would be true).
|
|
540
|
+
// Registry skill pointer below remains unaffected (its own name match).
|
|
541
|
+
rows = [];
|
|
432
542
|
} else {
|
|
433
543
|
// FTS search: use the prompt as query, optionally type-filtered
|
|
434
|
-
const files =
|
|
544
|
+
const files = filesForGate;
|
|
435
545
|
let ftsResult = searchByFts(db, promptText, project, intent?.limit || MAX_RESULTS, intent?.type || null);
|
|
436
546
|
// Fallback: if typed search returned nothing, retry without type filter
|
|
437
547
|
if (ftsResult.rows.length === 0 && intent?.type) {
|
|
@@ -497,9 +607,14 @@ async function main() {
|
|
|
497
607
|
// suppress the fallback to avoid noise). Namespace prompt IDs with
|
|
498
608
|
// a "P" prefix so shouldSkipByDedup's Set comparison doesn't collide
|
|
499
609
|
// with future observation IDs.
|
|
610
|
+
//
|
|
611
|
+
// v2.57.x: also gated by signalPresent. The prompt-fallback path has
|
|
612
|
+
// no quality gate (only BM25 ordering — see PROMPT_FALLBACK_LIMIT
|
|
613
|
+
// rationale at top), so injecting it on no-signal prompts is the
|
|
614
|
+
// single highest-noise UPS path. Restored when REQUIRE_SIGNAL=0.
|
|
500
615
|
let promptRows = [];
|
|
501
|
-
if (rows.length === 0) {
|
|
502
|
-
promptRows = searchByUserPrompts(db, promptText, project,
|
|
616
|
+
if (rows.length === 0 && (!REQUIRE_EXPLICIT_SIGNAL || signalPresent)) {
|
|
617
|
+
promptRows = searchByUserPrompts(db, promptText, project, PROMPT_FALLBACK_LIMIT);
|
|
503
618
|
}
|
|
504
619
|
|
|
505
620
|
const candidateIds = rows.length > 0
|
package/source-files.mjs
CHANGED
|
@@ -37,6 +37,7 @@ export const SOURCE_FILES = [
|
|
|
37
37
|
'lib/doctor-drift.mjs',
|
|
38
38
|
'lib/stats-quality.mjs',
|
|
39
39
|
'lib/low-signal-patterns.mjs',
|
|
40
|
+
'lib/private-strip.mjs',
|
|
40
41
|
'lib/citation-tracker.mjs',
|
|
41
42
|
'lib/summary-extractor.mjs',
|
|
42
43
|
'lib/id-routing.mjs',
|
|
@@ -53,3 +54,23 @@ export const SOURCE_FILES = [
|
|
|
53
54
|
'adopt-content.mjs',
|
|
54
55
|
'adopt-cli.mjs',
|
|
55
56
|
];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Hook scripts that direct-install (non-plugin) mode must materialize under
|
|
60
|
+
* ~/.claude-mem-lite/scripts/ — settings.json hook commands resolve to these
|
|
61
|
+
* absolute paths. Plugin mode does not consume this directory (it runs scripts
|
|
62
|
+
* from ${CLAUDE_PLUGIN_ROOT} instead).
|
|
63
|
+
*
|
|
64
|
+
* Single source of truth for both install.mjs (initial install) and
|
|
65
|
+
* hook-update.mjs (auto-update): pre-v2.55 hook-update copied the entire
|
|
66
|
+
* scripts/ tree from the GitHub Releases tarball, which silently shipped
|
|
67
|
+
* dev-only files (mock-claude.mjs, extract-repos.mjs, p0-forward-probe.mjs…)
|
|
68
|
+
* to every user's data dir on the first auto-update.
|
|
69
|
+
*/
|
|
70
|
+
export const HOOK_SCRIPT_FILES = [
|
|
71
|
+
'post-tool-use.sh',
|
|
72
|
+
'user-prompt-search.js',
|
|
73
|
+
'prompt-search-utils.mjs',
|
|
74
|
+
'pre-tool-recall.js',
|
|
75
|
+
'pre-skill-bridge.js',
|
|
76
|
+
];
|
package/utils.mjs
CHANGED
|
@@ -13,6 +13,7 @@ export { DECAY_HALF_LIFE_BY_TYPE, DEFAULT_DECAY_HALF_LIFE_MS, OBS_BM25, SESS_BM2
|
|
|
13
13
|
export { cjkBigrams, extractCjkSynonymTokens, extractCjkKeywords, extractCjkLikePatterns, SYNONYM_MAP, expandToken, sanitizeFtsQuery, relaxFtsQueryToOr, FTS_STOP_WORDS, CJK_COMPOUNDS } from './nlp.mjs';
|
|
14
14
|
export { resolveProject, _resetProjectCache } from './project-utils.mjs';
|
|
15
15
|
export { scrubSecrets, SECRET_PATTERNS } from './secret-scrub.mjs';
|
|
16
|
+
export { stripPrivate } from './lib/private-strip.mjs';
|
|
16
17
|
export { truncate, typeIcon, fmtDate, fmtTime, isoWeekKey } from './format-utils.mjs';
|
|
17
18
|
export { computeMinHash, estimateJaccardFromMinHash, jaccardSimilarity } from './hash-utils.mjs';
|
|
18
19
|
export { detectBashSignificance, extractErrorKeywords, extractFilePaths, stripTestSuffix } from './bash-utils.mjs';
|