claude-mem-lite 2.9.1 → 2.9.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/dispatch.mjs
CHANGED
|
@@ -216,7 +216,7 @@ const NEGATION_CJK = /(?:不要|别|不用|先别|暂时不|不需要|跳过|停
|
|
|
216
216
|
|
|
217
217
|
// Test-run vs test-write disambiguation (module-scoped for performance)
|
|
218
218
|
const _RUN_TEST = /\b(run\w*\s+(?:the\s+)?tests?|npm\s+test|npx\s+(?:vitest|jest|mocha|pytest)|yarn\s+test|pnpm\s+test|make\s+test|cargo\s+test|go\s+test|check\s+(?:if\s+)?tests?\s+pass|execute\s+(?:the\s+)?tests?)\b/i;
|
|
219
|
-
const _RUN_TEST_CJK = /(
|
|
219
|
+
const _RUN_TEST_CJK = /(?:运行测试|跑测试|跑一下测试|跑单测|跑一下单测|执行测试|执行单测|测试跑|看测试|看单测)/;
|
|
220
220
|
const _WRITE_TEST = /\b(write\s+tests?|add\s+tests?|create\s+tests?|need\s+tests?|missing\s+tests?|tdd|test.?driven|red.?green|increase\s+coverage|improve\s+coverage)\b/i;
|
|
221
221
|
const _WRITE_TEST_CJK = /(?:写测试|加测试|补测试|补单测|缺测试|测试覆盖)/;
|
|
222
222
|
|
|
@@ -259,7 +259,7 @@ const _INTENT_PATTERNS = (() => {
|
|
|
259
259
|
// ── Chinese patterns ──
|
|
260
260
|
[/(测试|写测试|单测|单元测试|用例|覆盖率)/, 'test'],
|
|
261
261
|
[/(修复|修bug|改bug|找bug|有bug|调试|排错|报错|出错|有问题|不工作|跑不起来|不能用|挂了|崩溃)/, 'fix'],
|
|
262
|
-
[/(
|
|
262
|
+
[/(审查|审核|审计|代码审查|评审|代码审核|看看代码|review)/, 'review'],
|
|
263
263
|
[/(提交|推送|上传)/, 'commit'],
|
|
264
264
|
[/(部署|上线|发布|回滚)/, 'deploy'],
|
|
265
265
|
[/(规划|架构|方案|设计方案)/, 'plan'],
|
|
@@ -273,6 +273,10 @@ const _INTENT_PATTERNS = (() => {
|
|
|
273
273
|
[/(优化|性能|卡顿|耗时|太慢|慢死了|好慢|缓存)/, 'fast'],
|
|
274
274
|
[/(格式化|代码风格|代码规范|类型检查)/, 'lint'],
|
|
275
275
|
[/(界面|前端|样式|页面|组件|布局)/, 'design'],
|
|
276
|
+
// search: only unambiguous web/info search indicators — NOT code search (grep/find).
|
|
277
|
+
// "搜索" alone is ambiguous (code search vs web search), so require context modifiers.
|
|
278
|
+
[/(联网搜索|网上搜索|在线搜索|上网查|搜索.{0,2}最新|搜一下.{0,2}最新|查.{0,2}最新|查资料|找资料|搜索资料|搜索文档)/, 'search'],
|
|
279
|
+
[/\b(google|search\s+online|web\s+search|look\s+up\s+(?:the\s+)?(?:latest|newest|recent|docs?|documentation))\b/i, 'search'],
|
|
276
280
|
];
|
|
277
281
|
// Pre-compile global variants for matchAll — avoids creating new RegExp on every extractIntent call
|
|
278
282
|
return raw.map(([p, tag]) => [p, new RegExp(p.source, p.flags.includes('g') ? p.flags : p.flags + 'g'), tag]);
|
|
@@ -311,15 +315,19 @@ function extractIntent(prompt) {
|
|
|
311
315
|
}
|
|
312
316
|
|
|
313
317
|
const found = [];
|
|
318
|
+
const suppressed = [];
|
|
314
319
|
for (const tag of tagMatched) {
|
|
315
320
|
if (tagHasAffirmative.get(tag) && !found.includes(tag)) {
|
|
316
321
|
found.push(tag);
|
|
322
|
+
} else if (!tagHasAffirmative.get(tag)) {
|
|
323
|
+
// Tag was matched but ALL instances were negated → suppress it.
|
|
324
|
+
// This feeds the text-fallback filter to prevent recommending negated resources.
|
|
325
|
+
suppressed.push(tag);
|
|
317
326
|
}
|
|
318
327
|
}
|
|
319
328
|
|
|
320
329
|
// Distinguish test-running from test-writing: "run tests" / "npm test" / "运行测试" should NOT
|
|
321
330
|
// trigger TDD recommendations. Only keep 'test' intent when the prompt implies *writing* tests.
|
|
322
|
-
const suppressed = [];
|
|
323
331
|
if (found.includes('test')) {
|
|
324
332
|
const isRunning = _RUN_TEST.test(prompt) || _RUN_TEST_CJK.test(prompt);
|
|
325
333
|
const isWriting = _WRITE_TEST.test(prompt) || _WRITE_TEST_CJK.test(prompt);
|
|
@@ -810,8 +818,13 @@ function applyAdoptionDecay(results, db) {
|
|
|
810
818
|
*/
|
|
811
819
|
function passesConfidenceGate(results, signals) {
|
|
812
820
|
// BM25 absolute minimum: filter weak text matches.
|
|
813
|
-
//
|
|
814
|
-
|
|
821
|
+
// Threshold is relative to the top result's score to handle varying corpus sizes:
|
|
822
|
+
// small corpora (< 50 resources) naturally produce lower BM25 IDF values,
|
|
823
|
+
// so an absolute threshold would over-filter genuine matches.
|
|
824
|
+
const baseThreshold = results.length >= 3 ? BM25_MIN_THRESHOLD : 0.5;
|
|
825
|
+
const topScore = results.length > 0 ? Math.abs(results[0].composite_score ?? results[0].relevance ?? 0) : 0;
|
|
826
|
+
// Use the lower of: absolute threshold OR 30% of top score (corpus-size-adaptive floor)
|
|
827
|
+
const minThreshold = topScore > 0 ? Math.min(baseThreshold, topScore * 0.3) : baseThreshold;
|
|
815
828
|
results = results.filter(r => {
|
|
816
829
|
const raw = r.composite_score ?? r.relevance;
|
|
817
830
|
if (raw === null || raw === undefined) return true; // no score → pass (pre-scored or synthetic result)
|
|
@@ -821,10 +834,15 @@ function passesConfidenceGate(results, signals) {
|
|
|
821
834
|
// Gap check: if top-2 results are too close in score, the query is ambiguous.
|
|
822
835
|
// This prevents recommending when multiple resources match equally well,
|
|
823
836
|
// which usually means the match is incidental rather than precise.
|
|
837
|
+
// Skip the gap check when rawKeywords promoted #1 (keyword re-ranking changes order,
|
|
838
|
+
// so the BM25 gap no longer reflects true relevance — the keyword match is extra signal).
|
|
824
839
|
if (results.length >= 2) {
|
|
825
840
|
const top1 = Math.abs(results[0].composite_score ?? results[0].relevance ?? 0);
|
|
826
841
|
const top2 = Math.abs(results[1].composite_score ?? results[1].relevance ?? 0);
|
|
827
|
-
|
|
842
|
+
// After keyword re-ranking, #1 may have lower raw BM25 than #2.
|
|
843
|
+
// The keyword match provides additional confidence, so skip the gap check.
|
|
844
|
+
const wasReRanked = signals?.rawKeywords?.length > 0 && top1 < top2;
|
|
845
|
+
if (!wasReRanked && top1 > 0) {
|
|
828
846
|
const gapRatio = (top1 - top2) / top1;
|
|
829
847
|
if (gapRatio < 0.2) {
|
|
830
848
|
// Top-1 has no clear lead — ambiguous match, suppress recommendation
|
|
@@ -976,7 +994,24 @@ function decideTier(resource, signals) {
|
|
|
976
994
|
// Normalize: typical good matches score 5-50, great matches 20+
|
|
977
995
|
// Sigmoid-like mapping to 0-1 range
|
|
978
996
|
const normalized = raw / (raw + 5.0); // 5→0.5, 10→0.67, 20→0.8, 50→0.91
|
|
979
|
-
|
|
997
|
+
|
|
998
|
+
// Signal-based confidence floor: if the result passed structured intent matching
|
|
999
|
+
// + keyword re-ranking, BM25 score alone shouldn't downgrade to 'silent'.
|
|
1000
|
+
// Small corpora produce low BM25 scores even for strong matches.
|
|
1001
|
+
let signalBoost = 0;
|
|
1002
|
+
if (signals?.primaryIntent) {
|
|
1003
|
+
const tags = (resource.intent_tags || '').toLowerCase().split(/[\s,]+/);
|
|
1004
|
+
// Direct intent match: resource's intent_tags contain the detected primary intent.
|
|
1005
|
+
// Strong boost (0.3) ensures small-corpus matches still reach 'hint' tier.
|
|
1006
|
+
if (tags.includes(signals.primaryIntent)) signalBoost += 0.3;
|
|
1007
|
+
else signalBoost += 0.1;
|
|
1008
|
+
}
|
|
1009
|
+
if (signals?.rawKeywords?.length > 0) {
|
|
1010
|
+
const tags = (resource.intent_tags || '').toLowerCase();
|
|
1011
|
+
if (signals.rawKeywords.some(kw => tags.includes(kw))) signalBoost += 0.2;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const confidence = Math.min(1.0, normalized + patternBoost * 0.3 + signalBoost);
|
|
980
1015
|
|
|
981
1016
|
if (confidence >= 0.55) return 'full';
|
|
982
1017
|
if (confidence >= 0.3) return 'hint';
|
package/package.json
CHANGED
package/registry-retriever.mjs
CHANGED
|
@@ -23,6 +23,7 @@ export const DISPATCH_SYNONYMS = {
|
|
|
23
23
|
'plan': ['planning', 'architecture', 'spec', 'blueprint', 'rfc', 'proposal', 'roadmap'],
|
|
24
24
|
'build': ['compile', 'bundle', 'webpack', 'vite', 'typescript', 'tsc', 'esbuild', 'rollup', 'parcel', 'babel', 'swc', 'transpile'],
|
|
25
25
|
'lint': ['eslint', 'prettier', 'biome', 'stylelint', 'format', 'style'],
|
|
26
|
+
'search': ['lookup', 'latest', 'best-practices', 'perplexity'],
|
|
26
27
|
// Chinese intent mappings
|
|
27
28
|
'清理': ['refactor', 'clean', 'lint', 'format', 'simplify'],
|
|
28
29
|
'测试': ['test', 'testing', 'tdd', 'qa', 'spec', 'jest', 'vitest', 'pytest'],
|
|
@@ -44,6 +45,7 @@ export const DISPATCH_SYNONYMS = {
|
|
|
44
45
|
'打包': ['bundle', 'build', 'webpack', 'vite'],
|
|
45
46
|
'容器': ['docker', 'container', 'kubernetes', 'infrastructure'],
|
|
46
47
|
'运维': ['devops', 'infrastructure', 'deploy', 'docker'],
|
|
48
|
+
'搜索': ['search', 'lookup', 'latest', 'perplexity'],
|
|
47
49
|
};
|
|
48
50
|
|
|
49
51
|
// ─── CJK Tokenization ───────────────────────────────────────────────────────
|
|
@@ -98,6 +100,9 @@ const CJK_INTENT_MAP = {
|
|
|
98
100
|
'接口': 'api', '路由': 'route',
|
|
99
101
|
// plan
|
|
100
102
|
'规划': 'planning', '架构': 'architecture', '方案': 'plan', '设计方案': 'architecture',
|
|
103
|
+
// search — only web/info search, NOT code search (grep/find)
|
|
104
|
+
'联网搜索': 'search', '网上搜索': 'search', '查资料': 'search', '找资料': 'search',
|
|
105
|
+
'搜索最新': 'search', '搜索资料': 'search', '搜索文档': 'search',
|
|
101
106
|
};
|
|
102
107
|
|
|
103
108
|
// Merge all CJK keys from both maps, longest-first to avoid partial matches
|