@danielmarbach/mnemonic-mcp 0.19.4 → 0.20.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/CHANGELOG.md +17 -0
- package/README.md +2 -0
- package/build/index.js +109 -7
- package/build/index.js.map +1 -1
- package/build/lexical.d.ts +53 -0
- package/build/lexical.d.ts.map +1 -0
- package/build/lexical.js +112 -0
- package/build/lexical.js.map +1 -0
- package/build/recall.d.ts +21 -0
- package/build/recall.d.ts.map +1 -1
- package/build/recall.js +34 -1
- package/build/recall.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize text for lightweight lexical matching.
|
|
3
|
+
* Lowercases, strips punctuation, and collapses whitespace.
|
|
4
|
+
*/
|
|
5
|
+
export declare function normalizeText(text: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Tokenize normalized text into individual tokens.
|
|
8
|
+
*/
|
|
9
|
+
export declare function tokenize(text: string): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Compute Jaccard similarity between two token sets.
|
|
12
|
+
* Returns 0 when both sets are empty.
|
|
13
|
+
*/
|
|
14
|
+
export declare function jaccardSimilarity(a: Set<string>, b: Set<string>): number;
|
|
15
|
+
/**
|
|
16
|
+
* Compute bigram Jaccard similarity between two strings.
|
|
17
|
+
* Better for phrase-level matching than unigram Jaccard.
|
|
18
|
+
*/
|
|
19
|
+
export declare function bigramJaccardSimilarity(a: string, b: string): number;
|
|
20
|
+
/**
|
|
21
|
+
* Check if one string contains another as a substring (case-insensitive).
|
|
22
|
+
*/
|
|
23
|
+
export declare function containsSubstring(haystack: string, needle: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Compute a composite lexical score between a query and projection text.
|
|
26
|
+
*
|
|
27
|
+
* Returns a value in [0, 1] combining:
|
|
28
|
+
* - substring match bonus (0.4 weight) — exact phrase containment
|
|
29
|
+
* - bigram Jaccard (0.35 weight) — phrase-level overlap
|
|
30
|
+
* - unigram Jaccard (0.25 weight) — token-level overlap
|
|
31
|
+
*/
|
|
32
|
+
export declare function computeLexicalScore(query: string, projectionText: string): number;
|
|
33
|
+
/**
|
|
34
|
+
* Maximum number of candidates to consider for lexical rescue.
|
|
35
|
+
*/
|
|
36
|
+
export declare const LEXICAL_RESCUE_CANDIDATE_LIMIT = 20;
|
|
37
|
+
/**
|
|
38
|
+
* Minimum lexical score to consider a rescue candidate.
|
|
39
|
+
*/
|
|
40
|
+
export declare const LEXICAL_RESCUE_THRESHOLD = 0.15;
|
|
41
|
+
/**
|
|
42
|
+
* Maximum number of rescued candidates to return.
|
|
43
|
+
*/
|
|
44
|
+
export declare const LEXICAL_RESCUE_RESULT_LIMIT = 3;
|
|
45
|
+
/**
|
|
46
|
+
* Confidence gate: determines whether semantic results are weak enough
|
|
47
|
+
* to warrant lexical rescue.
|
|
48
|
+
*
|
|
49
|
+
* Returns true when the top semantic score is below the threshold
|
|
50
|
+
* OR when there are no semantic results.
|
|
51
|
+
*/
|
|
52
|
+
export declare function shouldTriggerLexicalRescue(topSemanticScore: number | undefined, semanticResultCount: number): boolean;
|
|
53
|
+
//# sourceMappingURL=lexical.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lexical.d.ts","sourceRoot":"","sources":["../src/lexical.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAE/C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAQxE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAIpE;AAeD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAE3E;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAkBjF;AAED;;GAEG;AACH,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAEjD;;GAEG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C;;GAEG;AACH,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,mBAAmB,EAAE,MAAM,GAC1B,OAAO,CAKT"}
|
package/build/lexical.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// ── Lexical normalization and scoring for hybrid recall ───────────────────────
|
|
2
|
+
/**
|
|
3
|
+
* Normalize text for lightweight lexical matching.
|
|
4
|
+
* Lowercases, strips punctuation, and collapses whitespace.
|
|
5
|
+
*/
|
|
6
|
+
export function normalizeText(text) {
|
|
7
|
+
return text
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.replace(/[^\p{L}\p{N}\s]/gu, " ")
|
|
10
|
+
.replace(/\s+/g, " ")
|
|
11
|
+
.trim();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Tokenize normalized text into individual tokens.
|
|
15
|
+
*/
|
|
16
|
+
export function tokenize(text) {
|
|
17
|
+
return normalizeText(text).split(" ").filter(Boolean);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Compute Jaccard similarity between two token sets.
|
|
21
|
+
* Returns 0 when both sets are empty.
|
|
22
|
+
*/
|
|
23
|
+
export function jaccardSimilarity(a, b) {
|
|
24
|
+
if (a.size === 0 && b.size === 0)
|
|
25
|
+
return 0;
|
|
26
|
+
let intersection = 0;
|
|
27
|
+
for (const token of a) {
|
|
28
|
+
if (b.has(token))
|
|
29
|
+
intersection++;
|
|
30
|
+
}
|
|
31
|
+
const union = a.size + b.size - intersection;
|
|
32
|
+
return union === 0 ? 0 : intersection / union;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Compute bigram Jaccard similarity between two strings.
|
|
36
|
+
* Better for phrase-level matching than unigram Jaccard.
|
|
37
|
+
*/
|
|
38
|
+
export function bigramJaccardSimilarity(a, b) {
|
|
39
|
+
const aBigrams = new Set(bigrams(normalizeText(a)));
|
|
40
|
+
const bBigrams = new Set(bigrams(normalizeText(b)));
|
|
41
|
+
return jaccardSimilarity(aBigrams, bBigrams);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Generate character bigrams from a string.
|
|
45
|
+
*/
|
|
46
|
+
function bigrams(text) {
|
|
47
|
+
const normalized = text.replace(/\s+/g, "");
|
|
48
|
+
if (normalized.length < 2)
|
|
49
|
+
return [];
|
|
50
|
+
const result = [];
|
|
51
|
+
for (let i = 0; i < normalized.length - 1; i++) {
|
|
52
|
+
result.push(normalized.slice(i, i + 2));
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if one string contains another as a substring (case-insensitive).
|
|
58
|
+
*/
|
|
59
|
+
export function containsSubstring(haystack, needle) {
|
|
60
|
+
return haystack.toLowerCase().includes(needle.toLowerCase());
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Compute a composite lexical score between a query and projection text.
|
|
64
|
+
*
|
|
65
|
+
* Returns a value in [0, 1] combining:
|
|
66
|
+
* - substring match bonus (0.4 weight) — exact phrase containment
|
|
67
|
+
* - bigram Jaccard (0.35 weight) — phrase-level overlap
|
|
68
|
+
* - unigram Jaccard (0.25 weight) — token-level overlap
|
|
69
|
+
*/
|
|
70
|
+
export function computeLexicalScore(query, projectionText) {
|
|
71
|
+
const queryNorm = normalizeText(query);
|
|
72
|
+
const projNorm = normalizeText(projectionText);
|
|
73
|
+
if (!queryNorm || !projNorm)
|
|
74
|
+
return 0;
|
|
75
|
+
// Substring bonus: does the query appear in the projection?
|
|
76
|
+
const substringScore = containsSubstring(projNorm, queryNorm) ? 1.0 : 0;
|
|
77
|
+
// Bigram Jaccard for phrase-level matching
|
|
78
|
+
const bigramScore = bigramJaccardSimilarity(query, projectionText);
|
|
79
|
+
// Unigram Jaccard for token-level matching
|
|
80
|
+
const queryTokens = new Set(tokenize(query));
|
|
81
|
+
const projTokens = new Set(tokenize(projectionText));
|
|
82
|
+
const unigramScore = jaccardSimilarity(queryTokens, projTokens);
|
|
83
|
+
return 0.4 * substringScore + 0.35 * bigramScore + 0.25 * unigramScore;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Maximum number of candidates to consider for lexical rescue.
|
|
87
|
+
*/
|
|
88
|
+
export const LEXICAL_RESCUE_CANDIDATE_LIMIT = 20;
|
|
89
|
+
/**
|
|
90
|
+
* Minimum lexical score to consider a rescue candidate.
|
|
91
|
+
*/
|
|
92
|
+
export const LEXICAL_RESCUE_THRESHOLD = 0.15;
|
|
93
|
+
/**
|
|
94
|
+
* Maximum number of rescued candidates to return.
|
|
95
|
+
*/
|
|
96
|
+
export const LEXICAL_RESCUE_RESULT_LIMIT = 3;
|
|
97
|
+
/**
|
|
98
|
+
* Confidence gate: determines whether semantic results are weak enough
|
|
99
|
+
* to warrant lexical rescue.
|
|
100
|
+
*
|
|
101
|
+
* Returns true when the top semantic score is below the threshold
|
|
102
|
+
* OR when there are no semantic results.
|
|
103
|
+
*/
|
|
104
|
+
export function shouldTriggerLexicalRescue(topSemanticScore, semanticResultCount) {
|
|
105
|
+
if (semanticResultCount === 0)
|
|
106
|
+
return true;
|
|
107
|
+
if (topSemanticScore === undefined)
|
|
108
|
+
return true;
|
|
109
|
+
// Trigger rescue when the best semantic match is weak
|
|
110
|
+
return topSemanticScore < 0.35;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=lexical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lexical.js","sourceRoot":"","sources":["../src/lexical.ts"],"names":[],"mappings":"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"}
|
package/build/recall.d.ts
CHANGED
|
@@ -6,7 +6,28 @@ export interface ScoredRecallCandidate {
|
|
|
6
6
|
boosted: number;
|
|
7
7
|
vault: Vault;
|
|
8
8
|
isCurrentProject: boolean;
|
|
9
|
+
/** Lexical overlap score in [0, 1]. Undefined when not computed. */
|
|
10
|
+
lexicalScore?: number;
|
|
9
11
|
}
|
|
10
12
|
export declare function computeRecallMetadataBoost(metadata?: EffectiveNoteMetadata): number;
|
|
13
|
+
/**
|
|
14
|
+
* Compute a hybrid score that combines semantic similarity with lexical overlap.
|
|
15
|
+
*
|
|
16
|
+
* The formula is: boosted + LEXICAL_HYBRID_WEIGHT * lexicalScore
|
|
17
|
+
*
|
|
18
|
+
* Lexical score acts as a tiebreaker and small reranking signal — it cannot
|
|
19
|
+
* overcome a large semantic gap but can reorder close candidates.
|
|
20
|
+
*/
|
|
21
|
+
export declare function computeHybridScore(candidate: ScoredRecallCandidate): number;
|
|
22
|
+
/**
|
|
23
|
+
* Apply lexical reranking to a set of semantic candidates.
|
|
24
|
+
*
|
|
25
|
+
* For each candidate, compute lexical overlap against the query using the
|
|
26
|
+
* provided projection text, then re-sort by hybrid score.
|
|
27
|
+
*
|
|
28
|
+
* When projection text is unavailable for a candidate, lexicalScore stays
|
|
29
|
+
* undefined and contributes 0 to the hybrid score.
|
|
30
|
+
*/
|
|
31
|
+
export declare function applyLexicalReranking(candidates: ScoredRecallCandidate[], query: string, getProjectionText: (id: string) => string | undefined): ScoredRecallCandidate[];
|
|
11
32
|
export declare function selectRecallResults(scored: ScoredRecallCandidate[], limit: number, scope: "project" | "global" | "all"): ScoredRecallCandidate[];
|
|
12
33
|
//# sourceMappingURL=recall.d.ts.map
|
package/build/recall.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recall.d.ts","sourceRoot":"","sources":["../src/recall.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"recall.d.ts","sourceRoot":"","sources":["../src/recall.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAWnE,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;IACb,gBAAgB,EAAE,OAAO,CAAC;IAC1B,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAsBnF;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,qBAAqB,GAAG,MAAM,CAG3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,qBAAqB,EAAE,EACnC,KAAK,EAAE,MAAM,EACb,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACpD,qBAAqB,EAAE,CASzB;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,qBAAqB,EAAE,EAC/B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,SAAS,GAAG,QAAQ,GAAG,KAAK,GAClC,qBAAqB,EAAE,CAoBzB"}
|
package/build/recall.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { computeLexicalScore } from "./lexical.js";
|
|
1
2
|
const RECALL_ALWAYS_LOAD_BOOST = 0.01;
|
|
2
3
|
const RECALL_SUMMARY_BOOST = 0.012;
|
|
3
4
|
const RECALL_DECISION_BOOST = 0.009;
|
|
4
5
|
const RECALL_HIGH_IMPORTANCE_BOOST = 0.006;
|
|
6
|
+
/** Weight applied to lexical score when computing hybrid boosted score. */
|
|
7
|
+
const LEXICAL_HYBRID_WEIGHT = 0.12;
|
|
5
8
|
export function computeRecallMetadataBoost(metadata) {
|
|
6
9
|
if (!metadata) {
|
|
7
10
|
return 0;
|
|
@@ -21,8 +24,38 @@ export function computeRecallMetadataBoost(metadata) {
|
|
|
21
24
|
}
|
|
22
25
|
return boost;
|
|
23
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Compute a hybrid score that combines semantic similarity with lexical overlap.
|
|
29
|
+
*
|
|
30
|
+
* The formula is: boosted + LEXICAL_HYBRID_WEIGHT * lexicalScore
|
|
31
|
+
*
|
|
32
|
+
* Lexical score acts as a tiebreaker and small reranking signal — it cannot
|
|
33
|
+
* overcome a large semantic gap but can reorder close candidates.
|
|
34
|
+
*/
|
|
35
|
+
export function computeHybridScore(candidate) {
|
|
36
|
+
const lexical = candidate.lexicalScore ?? 0;
|
|
37
|
+
return candidate.boosted + LEXICAL_HYBRID_WEIGHT * lexical;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Apply lexical reranking to a set of semantic candidates.
|
|
41
|
+
*
|
|
42
|
+
* For each candidate, compute lexical overlap against the query using the
|
|
43
|
+
* provided projection text, then re-sort by hybrid score.
|
|
44
|
+
*
|
|
45
|
+
* When projection text is unavailable for a candidate, lexicalScore stays
|
|
46
|
+
* undefined and contributes 0 to the hybrid score.
|
|
47
|
+
*/
|
|
48
|
+
export function applyLexicalReranking(candidates, query, getProjectionText) {
|
|
49
|
+
for (const candidate of candidates) {
|
|
50
|
+
const projText = getProjectionText(candidate.id);
|
|
51
|
+
if (projText) {
|
|
52
|
+
candidate.lexicalScore = computeLexicalScore(query, projText);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return [...candidates].sort((a, b) => computeHybridScore(b) - computeHybridScore(a));
|
|
56
|
+
}
|
|
24
57
|
export function selectRecallResults(scored, limit, scope) {
|
|
25
|
-
const sorted = [...scored].sort((a, b) => b
|
|
58
|
+
const sorted = [...scored].sort((a, b) => computeHybridScore(b) - computeHybridScore(a));
|
|
26
59
|
if (scope !== "all") {
|
|
27
60
|
return sorted.slice(0, limit);
|
|
28
61
|
}
|
package/build/recall.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recall.js","sourceRoot":"","sources":["../src/recall.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"recall.js","sourceRoot":"","sources":["../src/recall.ts"],"names":[],"mappings":"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"}
|