@exaudeus/memory-mcp 1.0.0 → 1.1.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/dist/formatters.d.ts +15 -0
- package/dist/formatters.js +90 -1
- package/dist/index.js +318 -192
- package/dist/normalize.js +4 -0
- package/dist/store.d.ts +4 -1
- package/dist/store.js +66 -11
- package/dist/text-analyzer.d.ts +28 -5
- package/dist/text-analyzer.js +82 -10
- package/dist/thresholds.d.ts +14 -0
- package/dist/thresholds.js +14 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.js +32 -0
- package/package.json +2 -2
package/dist/formatters.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { MemoryStats, StaleEntry, ConflictPair, BehaviorConfig } from './types.js';
|
|
2
|
+
import { type FilterGroup } from './text-analyzer.js';
|
|
3
|
+
import type { MarkdownMemoryStore } from './store.js';
|
|
2
4
|
/** Format the stale entries section for briefing/context responses */
|
|
3
5
|
export declare function formatStaleSection(staleDetails: readonly StaleEntry[]): string;
|
|
4
6
|
/** Format the conflict detection warning for query/context responses */
|
|
@@ -8,3 +10,16 @@ export declare function formatStats(lobe: string, result: MemoryStats): string;
|
|
|
8
10
|
/** Format the active behavior config section for diagnostics.
|
|
9
11
|
* Shows effective values and marks overrides vs defaults clearly. */
|
|
10
12
|
export declare function formatBehaviorConfigSection(behavior?: BehaviorConfig): string;
|
|
13
|
+
/** Merge tag frequencies from multiple stores — pure function over a collection */
|
|
14
|
+
export declare function mergeTagFrequencies(stores: Iterable<MarkdownMemoryStore>): ReadonlyMap<string, number>;
|
|
15
|
+
/** Build query footer — pure function, same inputs → same output.
|
|
16
|
+
* Accepts parsed FilterGroup[] to avoid reparsing. */
|
|
17
|
+
export declare function buildQueryFooter(opts: {
|
|
18
|
+
readonly filterGroups: readonly FilterGroup[];
|
|
19
|
+
readonly rawFilter: string | undefined;
|
|
20
|
+
readonly tagFreq: ReadonlyMap<string, number>;
|
|
21
|
+
readonly resultCount: number;
|
|
22
|
+
readonly scope: string;
|
|
23
|
+
}): string;
|
|
24
|
+
/** Build tag primer section for session briefing — pure function */
|
|
25
|
+
export declare function buildTagPrimerSection(tagFreq: ReadonlyMap<string, number>): string;
|
package/dist/formatters.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Pure functions — no side effects, no state. Each takes structured data
|
|
4
4
|
// and returns a formatted string for the tool response.
|
|
5
|
-
import { DEFAULT_STALE_DAYS_STANDARD, DEFAULT_STALE_DAYS_PREFERENCES, DEFAULT_MAX_STALE_IN_BRIEFING, DEFAULT_MAX_DEDUP_SUGGESTIONS, DEFAULT_MAX_CONFLICT_PAIRS, } from './thresholds.js';
|
|
5
|
+
import { DEFAULT_STALE_DAYS_STANDARD, DEFAULT_STALE_DAYS_PREFERENCES, DEFAULT_MAX_STALE_IN_BRIEFING, DEFAULT_MAX_DEDUP_SUGGESTIONS, DEFAULT_MAX_CONFLICT_PAIRS, MAX_FOOTER_TAGS, } from './thresholds.js';
|
|
6
|
+
import { analyzeFilterGroups } from './text-analyzer.js';
|
|
6
7
|
/** Format the stale entries section for briefing/context responses */
|
|
7
8
|
export function formatStaleSection(staleDetails) {
|
|
8
9
|
const lines = [
|
|
@@ -43,6 +44,12 @@ export function formatStats(lobe, result) {
|
|
|
43
44
|
const trustLines = Object.entries(result.byTrust)
|
|
44
45
|
.map(([trust, count]) => ` - ${trust}: ${count}`)
|
|
45
46
|
.join('\n');
|
|
47
|
+
const tagLines = Object.entries(result.byTag).length > 0
|
|
48
|
+
? Object.entries(result.byTag)
|
|
49
|
+
.sort((a, b) => b[1] - a[1])
|
|
50
|
+
.map(([tag, count]) => ` - ${tag}: ${count}`)
|
|
51
|
+
.join('\n')
|
|
52
|
+
: ' (none)';
|
|
46
53
|
const corruptLine = result.corruptFiles > 0 ? `\n**Corrupt files:** ${result.corruptFiles}` : '';
|
|
47
54
|
return [
|
|
48
55
|
`## [${lobe}] Memory Stats`,
|
|
@@ -57,6 +64,9 @@ export function formatStats(lobe, result) {
|
|
|
57
64
|
`### By Trust Level`,
|
|
58
65
|
trustLines,
|
|
59
66
|
``,
|
|
67
|
+
`### By Tag`,
|
|
68
|
+
tagLines,
|
|
69
|
+
``,
|
|
60
70
|
`### Freshness`,
|
|
61
71
|
` - Fresh: ${result.byFreshness.fresh}`,
|
|
62
72
|
` - Stale: ${result.byFreshness.stale}`,
|
|
@@ -90,3 +100,82 @@ export function formatBehaviorConfigSection(behavior) {
|
|
|
90
100
|
}
|
|
91
101
|
return lines.join('\n');
|
|
92
102
|
}
|
|
103
|
+
/** Merge tag frequencies from multiple stores — pure function over a collection */
|
|
104
|
+
export function mergeTagFrequencies(stores) {
|
|
105
|
+
const merged = new Map();
|
|
106
|
+
for (const store of stores) {
|
|
107
|
+
const freq = store.getTagFrequency();
|
|
108
|
+
for (const [tag, count] of freq) {
|
|
109
|
+
merged.set(tag, (merged.get(tag) ?? 0) + count);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return merged;
|
|
113
|
+
}
|
|
114
|
+
/** Build query footer — pure function, same inputs → same output.
|
|
115
|
+
* Accepts parsed FilterGroup[] to avoid reparsing. */
|
|
116
|
+
export function buildQueryFooter(opts) {
|
|
117
|
+
const { filterGroups, rawFilter, tagFreq, resultCount, scope } = opts;
|
|
118
|
+
const mode = analyzeFilterGroups(filterGroups);
|
|
119
|
+
const lines = [];
|
|
120
|
+
// 1. Query mode explanation
|
|
121
|
+
switch (mode.kind) {
|
|
122
|
+
case 'no-filter':
|
|
123
|
+
lines.push(`Showing all entries in scope "${scope}"`);
|
|
124
|
+
break;
|
|
125
|
+
case 'keyword-only':
|
|
126
|
+
lines.push(`Searched keywords: ${mode.terms.join(', ')} (stemmed)`);
|
|
127
|
+
break;
|
|
128
|
+
case 'tag-only':
|
|
129
|
+
lines.push(`Filtered by tags: ${mode.tags.map(t => `#${t}`).join(', ')} (exact match)`);
|
|
130
|
+
break;
|
|
131
|
+
case 'complex':
|
|
132
|
+
const features = [];
|
|
133
|
+
if (mode.hasTags)
|
|
134
|
+
features.push('#tags');
|
|
135
|
+
if (mode.hasExact)
|
|
136
|
+
features.push('=exact');
|
|
137
|
+
if (mode.hasNot)
|
|
138
|
+
features.push('-NOT');
|
|
139
|
+
if (mode.hasOr)
|
|
140
|
+
features.push('|OR');
|
|
141
|
+
lines.push(`Complex filter: ${features.join(', ')}`);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
// 2. Available tags (always shown, capped for readability)
|
|
145
|
+
if (tagFreq.size > 0) {
|
|
146
|
+
const topTags = [...tagFreq.entries()]
|
|
147
|
+
.sort((a, b) => b[1] - a[1])
|
|
148
|
+
.slice(0, MAX_FOOTER_TAGS)
|
|
149
|
+
.map(([tag, count]) => `${tag}(${count})`)
|
|
150
|
+
.join(', ');
|
|
151
|
+
const remainder = tagFreq.size > MAX_FOOTER_TAGS ? ` + ${tagFreq.size - MAX_FOOTER_TAGS} more` : '';
|
|
152
|
+
lines.push(`Available tags: ${topTags}${remainder}`);
|
|
153
|
+
}
|
|
154
|
+
// 3. Zero-results suggestion (adaptive) — only when using keywords and tags exist
|
|
155
|
+
if (resultCount === 0 && mode.kind === 'keyword-only' && tagFreq.size > 0) {
|
|
156
|
+
const topTag = [...tagFreq.entries()].sort((a, b) => b[1] - a[1])[0][0];
|
|
157
|
+
lines.push(`→ No keyword matches. Try: filter: "#${topTag}" for exact category.`);
|
|
158
|
+
}
|
|
159
|
+
// 4. Syntax reference — show on failure or complex queries (not on simple successful queries)
|
|
160
|
+
if (resultCount === 0 || mode.kind === 'complex') {
|
|
161
|
+
lines.push(`Syntax: #tag | =exact | -NOT | word (stemmed) | A B (AND) | A|B (OR)`);
|
|
162
|
+
}
|
|
163
|
+
return lines.join('\n');
|
|
164
|
+
}
|
|
165
|
+
/** Build tag primer section for session briefing — pure function */
|
|
166
|
+
export function buildTagPrimerSection(tagFreq) {
|
|
167
|
+
if (tagFreq.size === 0)
|
|
168
|
+
return '';
|
|
169
|
+
const allTags = [...tagFreq.entries()]
|
|
170
|
+
.sort((a, b) => b[1] - a[1])
|
|
171
|
+
.map(([tag, count]) => `${tag}(${count})`)
|
|
172
|
+
.join(', ');
|
|
173
|
+
return [
|
|
174
|
+
`### Tag Vocabulary (${tagFreq.size} tags)`,
|
|
175
|
+
allTags,
|
|
176
|
+
``,
|
|
177
|
+
`Filter by tags: memory_query(filter: "#auth") — exact match`,
|
|
178
|
+
`Combine: memory_query(filter: "#auth middleware") — tag + keyword`,
|
|
179
|
+
`Multiple: memory_query(filter: "#auth|#security") — OR logic`,
|
|
180
|
+
].join('\n');
|
|
181
|
+
}
|