remem-mcp 0.5.17

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/sdk.js ADDED
@@ -0,0 +1,41 @@
1
+ import {
2
+ LocalEmbedder,
3
+ Memory,
4
+ SQLiteBackend,
5
+ findOutdatedPages,
6
+ getWikiPage,
7
+ ingestDirectory,
8
+ ingestFile,
9
+ searchWiki
10
+ } from "./chunk-34TEZ5U4.js";
11
+ import {
12
+ SUPPORTED_LANGUAGES,
13
+ detectLanguage,
14
+ findCallees,
15
+ findCallers,
16
+ impactAnalysis,
17
+ indexDirectory,
18
+ indexFile,
19
+ listSymbols,
20
+ searchSymbols
21
+ } from "./chunk-RITPZHIB.js";
22
+ export {
23
+ LocalEmbedder,
24
+ Memory,
25
+ SQLiteBackend,
26
+ SUPPORTED_LANGUAGES,
27
+ detectLanguage,
28
+ findCallees,
29
+ findCallers,
30
+ findOutdatedPages,
31
+ getWikiPage,
32
+ impactAnalysis,
33
+ indexDirectory,
34
+ indexFile,
35
+ listSymbols,
36
+ searchSymbols,
37
+ searchWiki,
38
+ ingestDirectory as wikiIngestDirectory,
39
+ ingestFile as wikiIngestFile
40
+ };
41
+ //# sourceMappingURL=sdk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,292 @@
1
+ -- Schema for tdai-memory-mcp
2
+ -- Version: 6
3
+ --
4
+ -- This file runs on the first start. It creates all tables, triggers, and indexes.
5
+ -- It uses CREATE TABLE IF NOT EXISTS and CREATE INDEX IF NOT EXISTS.
6
+ -- The migration is idempotent. You can run it more than once without side effects.
7
+
8
+ -- Schema version tracking
9
+ CREATE TABLE IF NOT EXISTS schema_version (
10
+ version INTEGER NOT NULL,
11
+ applied_at INTEGER NOT NULL
12
+ );
13
+
14
+ -- L0: Raw captures (always populated)
15
+ CREATE TABLE IF NOT EXISTS captures (
16
+ id TEXT PRIMARY KEY,
17
+ session_key TEXT NOT NULL,
18
+ agent_id TEXT NOT NULL,
19
+ type TEXT NOT NULL,
20
+ content TEXT NOT NULL,
21
+ content_hash TEXT,
22
+ tags TEXT,
23
+ created_at INTEGER NOT NULL,
24
+ metadata TEXT,
25
+ team_id TEXT,
26
+ user_id TEXT,
27
+ task_id TEXT,
28
+ deleted_at INTEGER,
29
+ trust_state TEXT NOT NULL DEFAULT 'candidate',
30
+ rejection_reason TEXT,
31
+ superseded_by TEXT REFERENCES captures(id)
32
+ );
33
+
34
+ -- L0 messages: role-based conversation messages linked to a capture.
35
+ -- Populated when capture is called with messages: [{role, content}].
36
+ CREATE TABLE IF NOT EXISTS messages (
37
+ id TEXT PRIMARY KEY,
38
+ capture_id TEXT NOT NULL REFERENCES captures(id) ON DELETE CASCADE,
39
+ role TEXT NOT NULL,
40
+ content TEXT NOT NULL,
41
+ seq INTEGER NOT NULL,
42
+ created_at INTEGER NOT NULL
43
+ );
44
+
45
+ -- L1: Atomic facts (populated by atom-extract pipeline, or CLI extract command)
46
+ CREATE TABLE IF NOT EXISTS atoms (
47
+ id TEXT PRIMARY KEY,
48
+ capture_id TEXT NOT NULL REFERENCES captures(id) ON DELETE CASCADE,
49
+ fact TEXT NOT NULL,
50
+ confidence REAL NOT NULL DEFAULT 1.0,
51
+ created_at INTEGER NOT NULL,
52
+ team_id TEXT,
53
+ agent_id TEXT,
54
+ user_id TEXT
55
+ );
56
+
57
+ -- L2: Scenario blocks (populated by scenario pipeline)
58
+ CREATE TABLE IF NOT EXISTS scenarios (
59
+ id TEXT PRIMARY KEY,
60
+ atom_ids TEXT NOT NULL,
61
+ summary TEXT NOT NULL,
62
+ persona_tags TEXT,
63
+ created_at INTEGER NOT NULL,
64
+ team_id TEXT,
65
+ agent_id TEXT,
66
+ user_id TEXT
67
+ );
68
+
69
+ -- L3: Persona (long-term user profile, one per team/agent/user)
70
+ CREATE TABLE IF NOT EXISTS persona (
71
+ team_id TEXT NOT NULL,
72
+ agent_id TEXT NOT NULL,
73
+ user_id TEXT NOT NULL,
74
+ content TEXT NOT NULL,
75
+ updated_at INTEGER NOT NULL,
76
+ PRIMARY KEY (team_id, agent_id, user_id)
77
+ );
78
+
79
+ -- Knowledge assets (wiki, code-graph) registered by the team
80
+ CREATE TABLE IF NOT EXISTS knowledge (
81
+ id TEXT PRIMARY KEY,
82
+ team_id TEXT NOT NULL,
83
+ name TEXT NOT NULL,
84
+ type TEXT NOT NULL,
85
+ summary TEXT,
86
+ service_url TEXT,
87
+ repo_url TEXT,
88
+ branch TEXT,
89
+ created_at INTEGER NOT NULL
90
+ );
91
+
92
+ -- Skills: reusable workflows extracted from conversations
93
+ CREATE TABLE IF NOT EXISTS skills (
94
+ id TEXT PRIMARY KEY,
95
+ team_id TEXT NOT NULL,
96
+ agent_id TEXT,
97
+ name TEXT NOT NULL,
98
+ description TEXT,
99
+ content TEXT,
100
+ version INTEGER NOT NULL DEFAULT 1,
101
+ created_at INTEGER NOT NULL,
102
+ updated_at INTEGER NOT NULL
103
+ );
104
+
105
+ -- Audit log
106
+ CREATE TABLE IF NOT EXISTS audit_log (
107
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
108
+ ts INTEGER NOT NULL,
109
+ tool TEXT NOT NULL,
110
+ args_hash TEXT NOT NULL,
111
+ result_len INTEGER,
112
+ quota_hit INTEGER NOT NULL DEFAULT 0,
113
+ redacted INTEGER NOT NULL DEFAULT 0
114
+ );
115
+
116
+ -- Full-text search (BM25 via FTS5)
117
+ -- External content table: the FTS5 index links to the captures table by rowid.
118
+ CREATE VIRTUAL TABLE IF NOT EXISTS captures_fts USING fts5(
119
+ id UNINDEXED,
120
+ content,
121
+ tags,
122
+ type UNINDEXED,
123
+ content='captures',
124
+ content_rowid='rowid'
125
+ );
126
+
127
+ -- Vector search (sqlite-vec)
128
+ -- Dimension 384 for all-MiniLM-L6-v2. Change to 1536 for OpenAI text-embedding-3-small.
129
+ CREATE VIRTUAL TABLE IF NOT EXISTS captures_vec USING vec0(
130
+ id TEXT PRIMARY KEY,
131
+ embedding float[384]
132
+ );
133
+
134
+ -- Triggers: keep FTS5 index in sync with captures table
135
+ -- FTS5 external content tables require the special 'delete' command syntax
136
+ -- to remove entries from the index.
137
+ CREATE TRIGGER IF NOT EXISTS captures_ai AFTER INSERT ON captures BEGIN
138
+ INSERT INTO captures_fts (rowid, id, content, tags, type)
139
+ VALUES (new.rowid, new.id, new.content, new.tags, new.type);
140
+ END;
141
+
142
+ CREATE TRIGGER IF NOT EXISTS captures_au AFTER UPDATE ON captures BEGIN
143
+ INSERT INTO captures_fts(captures_fts, rowid, content, tags, type) VALUES('delete', old.rowid, old.content, old.tags, old.type);
144
+ INSERT INTO captures_fts (rowid, id, content, tags, type)
145
+ VALUES (new.rowid, new.id, new.content, new.tags, new.type);
146
+ END;
147
+
148
+ CREATE TRIGGER IF NOT EXISTS captures_ad AFTER DELETE ON captures BEGIN
149
+ INSERT INTO captures_fts(captures_fts, rowid, content, tags, type) VALUES('delete', old.rowid, old.content, old.tags, old.type);
150
+ END;
151
+
152
+ -- ─────────────────────────────────────────────
153
+ -- CodeGraph: code symbols, call relationships, impact analysis
154
+ -- ─────────────────────────────────────────────
155
+
156
+ -- Symbols: functions, classes, methods, interfaces, etc.
157
+ CREATE TABLE IF NOT EXISTS symbols (
158
+ id TEXT PRIMARY KEY,
159
+ name TEXT NOT NULL,
160
+ kind TEXT NOT NULL, -- function, class, method, interface, type, variable, import
161
+ file_path TEXT NOT NULL,
162
+ line_start INTEGER NOT NULL,
163
+ line_end INTEGER NOT NULL,
164
+ language TEXT NOT NULL, -- typescript, javascript, python, go, rust, java, c, cpp, csharp
165
+ signature TEXT, -- function signature or type declaration
166
+ docstring TEXT, -- JSDoc, docstring, or comment above symbol
167
+ parent_id TEXT REFERENCES symbols(id), -- enclosing class or module
168
+ team_id TEXT,
169
+ repo_path TEXT, -- root path of the indexed repo
170
+ content_hash TEXT, -- hash of the symbol body for change detection
171
+ created_at INTEGER NOT NULL,
172
+ updated_at INTEGER NOT NULL
173
+ );
174
+
175
+ -- Call relationships: who calls whom
176
+ CREATE TABLE IF NOT EXISTS calls (
177
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
178
+ caller_id TEXT NOT NULL REFERENCES symbols(id) ON DELETE CASCADE,
179
+ callee_name TEXT NOT NULL, -- name of the called symbol (resolved later)
180
+ callee_id TEXT REFERENCES symbols(id), -- resolved callee (null if unresolved)
181
+ line INTEGER NOT NULL, -- line where the call occurs
182
+ kind TEXT NOT NULL DEFAULT 'call', -- call, import, reference
183
+ team_id TEXT
184
+ );
185
+
186
+ -- Import relationships: what a file imports
187
+ CREATE TABLE IF NOT EXISTS imports (
188
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
189
+ file_path TEXT NOT NULL,
190
+ symbol_name TEXT NOT NULL, -- imported symbol name
191
+ source_path TEXT, -- source module/path
192
+ line INTEGER NOT NULL,
193
+ language TEXT NOT NULL,
194
+ team_id TEXT,
195
+ repo_path TEXT
196
+ );
197
+
198
+ -- ─────────────────────────────────────────────
199
+ -- Wiki: structured documentation pages with link graph
200
+ -- ─────────────────────────────────────────────
201
+
202
+ -- Wiki pages: parsed from markdown/docs
203
+ CREATE TABLE IF NOT EXISTS wiki_pages (
204
+ id TEXT PRIMARY KEY,
205
+ title TEXT NOT NULL,
206
+ content TEXT NOT NULL, -- full page content (markdown)
207
+ source_file TEXT NOT NULL, -- original file path
208
+ section TEXT, -- heading path (e.g., "Getting Started > Install")
209
+ tags TEXT, -- comma-separated tags from frontmatter
210
+ frontmatter TEXT, -- JSON of frontmatter metadata
211
+ content_hash TEXT, -- hash for change detection
212
+ team_id TEXT,
213
+ created_at INTEGER NOT NULL,
214
+ updated_at INTEGER NOT NULL
215
+ );
216
+
217
+ -- Wiki links: adjacency list for the link graph
218
+ CREATE TABLE IF NOT EXISTS wiki_links (
219
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
220
+ from_page_id TEXT NOT NULL REFERENCES wiki_pages(id) ON DELETE CASCADE,
221
+ to_page_id TEXT REFERENCES wiki_pages(id), -- null if link target not found
222
+ to_title TEXT, -- target title (for unresolved links)
223
+ link_text TEXT, -- display text of the link
224
+ link_type TEXT NOT NULL DEFAULT 'wikilink', -- wikilink, markdown, heading
225
+ line INTEGER NOT NULL
226
+ );
227
+
228
+ -- FTS5 for wiki pages
229
+ CREATE VIRTUAL TABLE IF NOT EXISTS wiki_fts USING fts5(
230
+ id UNINDEXED,
231
+ title,
232
+ content,
233
+ tags,
234
+ content='wiki_pages',
235
+ content_rowid='rowid'
236
+ );
237
+
238
+ -- Triggers: keep wiki FTS5 in sync
239
+ CREATE TRIGGER IF NOT EXISTS wiki_ai AFTER INSERT ON wiki_pages BEGIN
240
+ INSERT INTO wiki_fts (rowid, id, title, content, tags)
241
+ VALUES (new.rowid, new.id, new.title, new.content, new.tags);
242
+ END;
243
+
244
+ CREATE TRIGGER IF NOT EXISTS wiki_au AFTER UPDATE ON wiki_pages BEGIN
245
+ INSERT INTO wiki_fts(wiki_fts, rowid, title, content, tags) VALUES('delete', old.rowid, old.title, old.content, old.tags);
246
+ INSERT INTO wiki_fts (rowid, id, title, content, tags)
247
+ VALUES (new.rowid, new.id, new.title, new.content, new.tags);
248
+ END;
249
+
250
+ CREATE TRIGGER IF NOT EXISTS wiki_ad AFTER DELETE ON wiki_pages BEGIN
251
+ INSERT INTO wiki_fts(wiki_fts, rowid, title, content, tags) VALUES('delete', old.rowid, old.title, old.content, old.tags);
252
+ END;
253
+
254
+ -- Indexes
255
+ CREATE INDEX IF NOT EXISTS idx_captures_session ON captures (session_key, created_at DESC);
256
+ CREATE INDEX IF NOT EXISTS idx_captures_agent ON captures (agent_id, created_at DESC);
257
+ CREATE INDEX IF NOT EXISTS idx_captures_hash ON captures (content_hash);
258
+ CREATE INDEX IF NOT EXISTS idx_captures_team ON captures (team_id, created_at DESC);
259
+ CREATE INDEX IF NOT EXISTS idx_captures_user ON captures (team_id, user_id, created_at DESC);
260
+ CREATE INDEX IF NOT EXISTS idx_captures_task ON captures (task_id, created_at DESC);
261
+ CREATE INDEX IF NOT EXISTS idx_captures_trust ON captures (trust_state);
262
+ CREATE INDEX IF NOT EXISTS idx_captures_rejected_hash ON captures (content_hash) WHERE trust_state = 'rejected';
263
+ CREATE INDEX IF NOT EXISTS idx_atoms_capture ON atoms (capture_id);
264
+ CREATE INDEX IF NOT EXISTS idx_atoms_team ON atoms (team_id, created_at DESC);
265
+ CREATE INDEX IF NOT EXISTS idx_messages_capture ON messages (capture_id, seq);
266
+ CREATE INDEX IF NOT EXISTS idx_scenarios_team ON scenarios (team_id, created_at DESC);
267
+ CREATE INDEX IF NOT EXISTS idx_audit_ts ON audit_log (ts DESC);
268
+ CREATE INDEX IF NOT EXISTS idx_knowledge_team ON knowledge (team_id, created_at DESC);
269
+ CREATE INDEX IF NOT EXISTS idx_skills_team ON skills (team_id, updated_at DESC);
270
+
271
+ -- CodeGraph indexes
272
+ CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols (name);
273
+ CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols (file_path);
274
+ CREATE INDEX IF NOT EXISTS idx_symbols_kind ON symbols (kind);
275
+ CREATE INDEX IF NOT EXISTS idx_symbols_team ON symbols (team_id);
276
+ CREATE INDEX IF NOT EXISTS idx_symbols_repo ON symbols (repo_path);
277
+ CREATE INDEX IF NOT EXISTS idx_symbols_parent ON symbols (parent_id);
278
+ CREATE INDEX IF NOT EXISTS idx_calls_caller ON calls (caller_id);
279
+ CREATE INDEX IF NOT EXISTS idx_calls_callee ON calls (callee_name);
280
+ CREATE INDEX IF NOT EXISTS idx_calls_callee_id ON calls (callee_id);
281
+ CREATE INDEX IF NOT EXISTS idx_calls_team ON calls (team_id);
282
+ CREATE INDEX IF NOT EXISTS idx_imports_file ON imports (file_path);
283
+ CREATE INDEX IF NOT EXISTS idx_imports_symbol ON imports (symbol_name);
284
+ CREATE INDEX IF NOT EXISTS idx_imports_team ON imports (team_id);
285
+
286
+ -- Wiki indexes
287
+ CREATE INDEX IF NOT EXISTS idx_wiki_pages_source ON wiki_pages (source_file);
288
+ CREATE INDEX IF NOT EXISTS idx_wiki_pages_team ON wiki_pages (team_id);
289
+ CREATE INDEX IF NOT EXISTS idx_wiki_pages_hash ON wiki_pages (content_hash);
290
+ CREATE INDEX IF NOT EXISTS idx_wiki_links_from ON wiki_links (from_page_id);
291
+ CREATE INDEX IF NOT EXISTS idx_wiki_links_to ON wiki_links (to_page_id);
292
+ CREATE INDEX IF NOT EXISTS idx_wiki_links_title ON wiki_links (to_title);
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "remem-mcp",
3
+ "mcpName": "io.github.tinhien11/remem-mcp",
4
+ "version": "0.5.17",
5
+ "description": "Long-term memory for AI coding agents. Memory + CodeGraph + Wiki in one SQLite file. Auto-recall, auto-capture, no API key, no cloud.",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "author": "tin",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/tinhien11/remem-mcp.git"
12
+ },
13
+ "homepage": "https://github.com/tinhien11/remem-mcp#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/tinhien11/remem-mcp/issues"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "model-context-protocol",
20
+ "memory",
21
+ "ai-agent",
22
+ "agent-memory",
23
+ "long-term-memory",
24
+ "claude-code",
25
+ "cursor",
26
+ "devin",
27
+ "codex",
28
+ "sqlite",
29
+ "sqlite-vec",
30
+ "local-first",
31
+ "codegraph",
32
+ "tree-sitter",
33
+ "knowledge-graph",
34
+ "persistent-memory",
35
+ "agent-context"
36
+ ],
37
+ "bin": {
38
+ "remem-mcp": "dist/index.js"
39
+ },
40
+ "exports": {
41
+ ".": {
42
+ "import": "./dist/sdk.js"
43
+ }
44
+ },
45
+ "files": [
46
+ "dist",
47
+ "skills",
48
+ "scripts"
49
+ ],
50
+ "engines": {
51
+ "node": ">=22"
52
+ },
53
+ "scripts": {
54
+ "build": "tsup && mkdir -p dist/storage && cp src/storage/schema.sql dist/storage/schema.sql",
55
+ "dev": "tsup --watch",
56
+ "test": "vitest run",
57
+ "test:watch": "vitest",
58
+ "lint": "biome check src",
59
+ "format": "biome format --write src",
60
+ "start": "node dist/index.js",
61
+ "postinstall": "node scripts/postinstall.js",
62
+ "prepublishOnly": "npm run build"
63
+ },
64
+ "dependencies": {
65
+ "@huggingface/transformers": "^4.2.0",
66
+ "@kreuzberg/tree-sitter-language-pack": "^1.10.9",
67
+ "@modelcontextprotocol/sdk": "^1.30.0",
68
+ "better-sqlite3": "^13.0.3",
69
+ "gpt-tokenizer": "^3.4.0",
70
+ "sqlite-vec": "^0.1.9",
71
+ "tree-sitter": "^0.25.1",
72
+ "ulid": "^3.0.2"
73
+ },
74
+ "devDependencies": {
75
+ "@biomejs/biome": "^2.5.7",
76
+ "@types/better-sqlite3": "^7.6.13",
77
+ "@types/node": "^22.0.0",
78
+ "tsup": "^8.5.1",
79
+ "typescript": "^5.5.0",
80
+ "vitest": "^4.1.10"
81
+ }
82
+ }
@@ -0,0 +1,147 @@
1
+ #!/bin/bash
2
+ # bench-all.sh — Run AMB L1-L3 + LoCoMo, output scores in parseable format.
3
+ # Usage: ./scripts/bench-all.sh [--quick]
4
+ # --quick: skip LoCoMo (AMB only, ~2 min)
5
+ #
6
+ # Output format (last line, parseable):
7
+ # BENCH_RESULT L1=<n> L2=<n> L3=<n> LOCOMO=<n>
8
+ #
9
+ # Exit 0 if all targets met, exit 1 if any below target.
10
+
11
+ set -uo pipefail
12
+
13
+ # ─── Targets ───────────────────────────────────────────────────
14
+ TARGET_L1="${TARGET_L1:-100}"
15
+ TARGET_L2="${TARGET_L2:-100}"
16
+ TARGET_L3="${TARGET_L3:-100}"
17
+ TARGET_LOCOMO="${TARGET_LOCOMO:-76}"
18
+ TARGET_PERSONAMEM="${TARGET_PERSONAMEM:-76}"
19
+
20
+ # ─── Paths ─────────────────────────────────────────────────────
21
+ PROJECT_ROOT="/data/projects/tdai-memory-mcp"
22
+ AMB_REPO="/tmp/amb-repo"
23
+ LOCOMO_BENCH="/tmp/locomo-bench"
24
+ LOCOMO_DATA="/tmp/locomo/data/locomo10.json"
25
+ AMB_RESULTS="/tmp/amb-results"
26
+ LOCOMO_RESULTS="$LOCOMO_BENCH/results.json"
27
+ PERSONAMEM_BENCH="/tmp/personamem"
28
+ PERSONAMEM_RESULTS="$PERSONAMEM_BENCH/results.json"
29
+
30
+ # ─── Helpers ───────────────────────────────────────────────────
31
+ log() { echo "[bench] $*" >&2; }
32
+
33
+ # ─── Build tdai-memory-mcp ─────────────────────────────────────
34
+ log "Building tdai-memory-mcp..."
35
+ cd "$PROJECT_ROOT"
36
+ npm run build 2>&1 | tail -1
37
+ log "Build OK"
38
+
39
+ # ─── AMB Layer 1 ───────────────────────────────────────────────
40
+ log "Running AMB Layer 1..."
41
+ cd "$AMB_REPO"
42
+ L1_OUTPUT=$(npx tsx src/cli.ts --provider tdai-memory --layer 1 --no-delay --verbose --output "$AMB_RESULTS" 2>&1)
43
+ L1=$(echo "$L1_OUTPUT" | grep '^🏆 Layer 1 Score:' | grep -oP 'Score: \K[0-9]+' || echo "0")
44
+ log "L1=$L1"
45
+
46
+ # ─── AMB Layer 2 ───────────────────────────────────────────────
47
+ log "Running AMB Layer 2..."
48
+ L2_OUTPUT=$(npx tsx src/cli.ts --provider tdai-memory --layer 2 --no-delay --verbose --output "$AMB_RESULTS" 2>&1)
49
+ L2=$(echo "$L2_OUTPUT" | grep '^🏆 Layer 2 Score:' | grep -oP 'Score: \K[0-9]+' || echo "0")
50
+ log "L2=$L2"
51
+
52
+ # ─── AMB Layer 3 ───────────────────────────────────────────────
53
+ log "Running AMB Layer 3 (1K memories)..."
54
+ L3_OUTPUT=$(npx tsx src/cli.ts --provider tdai-memory --layer 3 --no-delay --verbose --output "$AMB_RESULTS" 2>&1)
55
+ L3=$(echo "$L3_OUTPUT" | grep '^🏆 Layer 3 Score' | grep -oP 'Score.*?: \K[0-9]+' | head -1 || echo "0")
56
+ log "L3=$L3"
57
+
58
+ # ─── LoCoMo ────────────────────────────────────────────────────
59
+ LOCOMO="N/A"
60
+ if [ "${1:-}" != "--quick" ] && [ -f "$LOCOMO_DATA" ]; then
61
+ log "Running LoCoMo benchmark..."
62
+ cd "$LOCOMO_BENCH"
63
+
64
+ # Clean previous data to avoid stale memories
65
+ rm -rf "$LOCOMO_BENCH/tdai-data"
66
+ mkdir -p "$LOCOMO_BENCH/tdai-data"
67
+
68
+ # Run LoCoMo ingest + search
69
+ npx tsx run.ts 2>&1 | tail -5
70
+
71
+ # Judge results using simple keyword matching (fallback if no LLM judge)
72
+ if [ -f "$LOCOMO_RESULTS" ]; then
73
+ LOCOMO=$(python3 -c "
74
+ import json, sys
75
+ results = json.load(open('$LOCOMO_RESULTS'))
76
+ if not results:
77
+ print(0)
78
+ sys.exit()
79
+ correct = 0
80
+ for r in results:
81
+ gt = str(r.get('groundTruth', '')).lower()
82
+ hits = r.get('searchResults', [])
83
+ # Simple heuristic: if any search result contains a keyword from ground truth
84
+ gt_words = [w for w in gt.split() if len(w) > 3 and w not in ('what', 'when', 'where', 'which', 'about', 'because', 'would', 'could', 'should', 'their', 'there', 'these', 'those', 'after', 'before')]
85
+ found = False
86
+ for h in hits:
87
+ content = h.get('content', '').lower()
88
+ if any(w in content for w in gt_words[:3]):
89
+ found = True
90
+ break
91
+ if found:
92
+ correct += 1
93
+ score = round(correct / len(results) * 100)
94
+ print(score)
95
+ " 2>/dev/null || echo "0")
96
+ log "LOCOMO=$LOCOMO (keyword heuristic — for LLM judge, set BENCH_LLM_JUDGE=1)"
97
+ fi
98
+ fi
99
+
100
+ # ─── PersonaMem ────────────────────────────────────────────────
101
+ PERSONAMEM="N/A"
102
+ if [ "${1:-}" != "--quick" ] && [ -f "$PERSONAMEM_BENCH/personamem-bench.ts" ]; then
103
+ log "Running PersonaMem benchmark..."
104
+ cd "$PERSONAMEM_BENCH"
105
+ PERSONAMEM_SAMPLE="${PERSONAMEM_SAMPLE:-50}"
106
+ npx tsx personamem-bench.ts --sample=$PERSONAMEM_SAMPLE 2>&1 | tail -20
107
+ PERSONAMEM=$(grep "PERSONAMEM_SCORE" /tmp/personamem-bench-output.log 2>/dev/null | grep -oP '\d+' || echo "0")
108
+ # Parse from results.json if score line not captured
109
+ if [ "$PERSONAMEM" = "0" ] && [ -f "$PERSONAMEM_RESULTS" ]; then
110
+ PERSONAMEM=$(python3 -c "import json; print(json.load(open('$PERSONAMEM_RESULTS'))['score'])" 2>/dev/null || echo "0")
111
+ fi
112
+ log "PERSONAMEM=$PERSONAMEM (sample=$PERSONAMEM_SAMPLE, TencentDB=76)"
113
+ fi
114
+
115
+ # ─── Output ────────────────────────────────────────────────────
116
+ echo ""
117
+ echo "═══════════════════════════════════════════════════════════"
118
+ echo " BENCHMARK RESULTS — tdai-memory-mcp"
119
+ echo "═══════════════════════════════════════════════════════════"
120
+ echo " AMB Layer 1: $L1 / 100 (target: $TARGET_L1)"
121
+ echo " AMB Layer 2: $L2 / 100 (target: $TARGET_L2)"
122
+ echo " AMB Layer 3: $L3 / 100 (target: $TARGET_L3)"
123
+ echo " LoCoMo: $LOCOMO / 100 (target: $TARGET_LOCOMO)"
124
+ echo " PersonaMem: $PERSONAMEM / 100 (target: $TARGET_PERSONAMEM, TencentDB=76)"
125
+ echo "═══════════════════════════════════════════════════════════"
126
+ echo ""
127
+ echo "BENCH_RESULT L1=$L1 L2=$L2 L3=$L3 LOCOMO=$LOCOMO PERSONAMEM=$PERSONAMEM"
128
+
129
+ # ─── Check targets ─────────────────────────────────────────────
130
+ PASS=true
131
+ [ "${L1:-0}" -lt "$TARGET_L1" ] && PASS=false && echo "FAIL: L1=$L1 < target=$TARGET_L1"
132
+ [ "${L2:-0}" -lt "$TARGET_L2" ] && PASS=false && echo "FAIL: L2=$L2 < target=$TARGET_L2"
133
+ [ "${L3:-0}" -lt "$TARGET_L3" ] && PASS=false && echo "FAIL: L3=$L3 < target=$TARGET_L3"
134
+ if [ "$LOCOMO" != "N/A" ]; then
135
+ [ "${LOCOMO:-0}" -lt "$TARGET_LOCOMO" ] && PASS=false && echo "FAIL: LOCOMO=$LOCOMO < target=$TARGET_LOCOMO"
136
+ fi
137
+ if [ "$PERSONAMEM" != "N/A" ]; then
138
+ [ "${PERSONAMEM:-0}" -lt "$TARGET_PERSONAMEM" ] && PASS=false && echo "FAIL: PERSONAMEM=$PERSONAMEM < target=$TARGET_PERSONAMEM"
139
+ fi
140
+
141
+ if [ "$PASS" = true ]; then
142
+ echo "ALL PASS ✓"
143
+ exit 0
144
+ else
145
+ echo "SOME FAIL ✗"
146
+ exit 1
147
+ fi