claude-mem-lite 2.34.3 → 2.34.5
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/package.json
CHANGED
|
@@ -9,12 +9,23 @@ const CONFIRM_RE = /^(y(es)?|no?|ok|done|go|sure|lgtm|thanks?|ty|继续|确认|
|
|
|
9
9
|
const SLASH_CMD_RE = /^\//;
|
|
10
10
|
const PURE_OP_RE = /^(git\s+(commit|push|merge)|npm\s+(publish|deploy))\b/i;
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* CJK-weighted effective length. CJK characters (CJK Unified Ideographs
|
|
14
|
+
* main + extension A) carry ~3x the semantic token density of Latin
|
|
15
|
+
* characters — a 5-char Chinese phrase like "优化数据库" encodes roughly
|
|
16
|
+
* the same information as a 15-char English equivalent. Used by every
|
|
17
|
+
* length gate downstream of the prompt hook so Latin-calibrated
|
|
18
|
+
* thresholds (8 / 15) don't falsely reject substantive CJK prompts.
|
|
19
|
+
*/
|
|
20
|
+
export function computeEffectiveLen(text) {
|
|
21
|
+
if (!text) return 0;
|
|
22
|
+
const cjkCount = (text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []).length;
|
|
23
|
+
return (text.length - cjkCount) + cjkCount * 3;
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
export function shouldSkip(text) {
|
|
13
27
|
if (!text) return true;
|
|
14
|
-
|
|
15
|
-
const cjkCount = (text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []).length;
|
|
16
|
-
const effectiveLen = (text.length - cjkCount) + cjkCount * 3;
|
|
17
|
-
if (effectiveLen < 8) return true;
|
|
28
|
+
if (computeEffectiveLen(text) < 8) return true;
|
|
18
29
|
const trimmed = text.trim();
|
|
19
30
|
if (CONFIRM_RE.test(trimmed)) return true;
|
|
20
31
|
if (SLASH_CMD_RE.test(trimmed)) return true;
|
|
@@ -8,7 +8,7 @@ import { sanitizeFtsQuery, relaxFtsQueryToOr, truncate, typeIcon, inferProject,
|
|
|
8
8
|
import { writeFileSync, readFileSync, existsSync, renameSync } from 'fs';
|
|
9
9
|
import { join } from 'path';
|
|
10
10
|
import Database from 'better-sqlite3';
|
|
11
|
-
import { shouldSkip, detectIntent, shouldSkipByDedup, extractFiles, extractErrorSignature, DEDUP_STALE_MS, matchRegistrySkillName } from './prompt-search-utils.mjs';
|
|
11
|
+
import { shouldSkip, computeEffectiveLen, detectIntent, shouldSkipByDedup, extractFiles, extractErrorSignature, DEDUP_STALE_MS, matchRegistrySkillName } from './prompt-search-utils.mjs';
|
|
12
12
|
|
|
13
13
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
14
14
|
|
|
@@ -29,9 +29,13 @@ const LOOKBACK_MS = 60 * 86400000; // 60 days
|
|
|
29
29
|
// is TOP_REL_FLOOR below, which drops the whole FTS set when the best match
|
|
30
30
|
// is weak.
|
|
31
31
|
const BM25_MIN_SCORE = Number(process.env.CLAUDE_MEM_UPS_BM25_MIN || 1e-5);
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
32
|
+
// CJK-weighted minimum length for the prompt. Catches medium-short Latin
|
|
33
|
+
// prompts ("run tests", "fix bug now") that survive `shouldSkip`'s weaker 8-unit
|
|
34
|
+
// floor but carry too few tokens to justify an FTS lookup.
|
|
35
|
+
// v2.34.4: applied to `computeEffectiveLen(prompt)`, not raw char count — a
|
|
36
|
+
// 14-char CJK prompt ("优化 hook 性能降低延迟") scores 30 effective units and
|
|
37
|
+
// now reaches FTS, matching shouldSkip's CJK-weighted gate rather than silently
|
|
38
|
+
// failing the raw-char one.
|
|
35
39
|
const PROMPT_MIN_LENGTH = 15;
|
|
36
40
|
|
|
37
41
|
// v2.33.1: follow-up prompts ("前面那个", "继续 X", "再看看 Y") are short by
|
|
@@ -159,6 +163,43 @@ function searchByFile(db, files, project, limit) {
|
|
|
159
163
|
});
|
|
160
164
|
}
|
|
161
165
|
|
|
166
|
+
// v2.34.5 Gap 1: prompts-table fallback. When observations-based paths
|
|
167
|
+
// (FTS / file-recall / sigRows / recent) all return empty, scan the user's
|
|
168
|
+
// own past prompts — meta/UX/"did we discuss this" questions often match
|
|
169
|
+
// prior prompts even when no observation was saved. Uses a simpler BM25
|
|
170
|
+
// ranking with no scoring multipliers and no top-|rel| gate (prompts are
|
|
171
|
+
// sparser and more surface-form than observations; the gate would rarely
|
|
172
|
+
// fire and mostly kill real hits).
|
|
173
|
+
function searchByUserPrompts(db, queryText, project, limit) {
|
|
174
|
+
const ftsQuery = sanitizeFtsQuery(queryText);
|
|
175
|
+
if (!ftsQuery) return [];
|
|
176
|
+
|
|
177
|
+
const cutoff = Date.now() - LOOKBACK_MS;
|
|
178
|
+
const sql = `
|
|
179
|
+
SELECT up.id, up.prompt_text, up.created_at_epoch,
|
|
180
|
+
bm25(user_prompts_fts) as relevance
|
|
181
|
+
FROM user_prompts_fts
|
|
182
|
+
JOIN user_prompts up ON up.id = user_prompts_fts.rowid
|
|
183
|
+
JOIN sdk_sessions s ON s.content_session_id = up.content_session_id
|
|
184
|
+
WHERE user_prompts_fts MATCH ?
|
|
185
|
+
AND s.project = ?
|
|
186
|
+
AND up.created_at_epoch > ?
|
|
187
|
+
ORDER BY relevance
|
|
188
|
+
LIMIT ?
|
|
189
|
+
`;
|
|
190
|
+
|
|
191
|
+
let rows = db.prepare(sql).all(ftsQuery, project, cutoff, limit);
|
|
192
|
+
|
|
193
|
+
if (rows.length === 0) {
|
|
194
|
+
const orQuery = relaxFtsQueryToOr(ftsQuery);
|
|
195
|
+
if (orQuery) {
|
|
196
|
+
try { rows = db.prepare(sql).all(orQuery, project, cutoff, limit); } catch {}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return rows;
|
|
201
|
+
}
|
|
202
|
+
|
|
162
203
|
function searchRecent(db, project, limit) {
|
|
163
204
|
const cutoff = Date.now() - LOOKBACK_MS;
|
|
164
205
|
// R1: exclude LOW_SIGNAL degraded titles from "recent" recall intent
|
|
@@ -221,6 +262,20 @@ function formatResults(rows) {
|
|
|
221
262
|
return lines.join('\n');
|
|
222
263
|
}
|
|
223
264
|
|
|
265
|
+
// v2.34.5 Gap 1: distinct header signals to Claude that these are prior
|
|
266
|
+
// *user questions*, not codebase lessons — helps the reader interpret the
|
|
267
|
+
// row correctly (surface-form match, not a saved insight). Truncate to 80
|
|
268
|
+
// chars (slightly longer than obs titles because prompts carry more context).
|
|
269
|
+
function formatPromptResults(rows) {
|
|
270
|
+
if (!rows || rows.length === 0) return null;
|
|
271
|
+
const lines = ['[mem] Past similar questions:'];
|
|
272
|
+
for (const r of rows) {
|
|
273
|
+
const text = truncate((r.prompt_text || '').replace(/\s+/g, ' '), 80);
|
|
274
|
+
lines.push(`P#${r.id} 💬 ${text}`);
|
|
275
|
+
}
|
|
276
|
+
return lines.join('\n');
|
|
277
|
+
}
|
|
278
|
+
|
|
224
279
|
// ─── Registry Skill Pointer (T4 v2.31) ─────────────────────────────────────
|
|
225
280
|
// Formerly "auto-load": we used to read the full SKILL.md body (up to 16KB)
|
|
226
281
|
// and inject it into stdout on keyword match. Now we only emit a short
|
|
@@ -296,7 +351,7 @@ async function main() {
|
|
|
296
351
|
// short continuations ("前面那个?", "does it work?") depend on prior context.
|
|
297
352
|
const followUp = isFollowUpSession();
|
|
298
353
|
const promptMinLen = followUp ? FOLLOWUP_PROMPT_MIN_LENGTH : PROMPT_MIN_LENGTH;
|
|
299
|
-
if (promptText.trim()
|
|
354
|
+
if (computeEffectiveLen(promptText.trim()) < promptMinLen) return;
|
|
300
355
|
const bm25Floor = followUp ? FOLLOWUP_BM25_MIN_SCORE : BM25_MIN_SCORE;
|
|
301
356
|
|
|
302
357
|
let db;
|
|
@@ -372,10 +427,24 @@ async function main() {
|
|
|
372
427
|
rows = [...sigRows, ...rows.filter(r => !sigIds.has(r.id))].slice(0, MAX_RESULTS);
|
|
373
428
|
}
|
|
374
429
|
|
|
375
|
-
|
|
430
|
+
// v2.34.5 Gap 1: if observations-based search drew a blank, try the
|
|
431
|
+
// user_prompts corpus. Only fires when `rows` is empty (obs hits
|
|
432
|
+
// suppress the fallback to avoid noise). Namespace prompt IDs with
|
|
433
|
+
// a "P" prefix so shouldSkipByDedup's Set comparison doesn't collide
|
|
434
|
+
// with future observation IDs.
|
|
435
|
+
let promptRows = [];
|
|
436
|
+
if (rows.length === 0) {
|
|
437
|
+
promptRows = searchByUserPrompts(db, promptText, project, 3);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const candidateIds = rows.length > 0
|
|
441
|
+
? rows.map(r => r.id)
|
|
442
|
+
: promptRows.map(r => `P${r.id}`);
|
|
376
443
|
const dedupSkip = shouldSkipByDedup(candidateIds, INJECTED_IDS_FILE);
|
|
377
444
|
|
|
378
|
-
const output = !dedupSkip
|
|
445
|
+
const output = !dedupSkip
|
|
446
|
+
? (rows.length > 0 ? formatResults(rows) : formatPromptResults(promptRows))
|
|
447
|
+
: null;
|
|
379
448
|
if (output) {
|
|
380
449
|
process.stdout.write(output + '\n');
|
|
381
450
|
// Write injected IDs for dedup with hook.mjs handleUserPrompt + self-dedup
|