@jungjaehoon/mama-core 1.0.2 → 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/package.json +33 -21
- package/src/config-loader.js +0 -218
- package/src/db-adapter/README.md +0 -110
- package/src/db-adapter/base-adapter.js +0 -91
- package/src/db-adapter/index.js +0 -31
- package/src/db-adapter/sqlite-adapter.js +0 -364
- package/src/db-adapter/statement.js +0 -127
- package/src/db-manager.js +0 -671
- package/src/debug-logger.js +0 -86
- package/src/decision-formatter.js +0 -1276
- package/src/decision-tracker.js +0 -621
- package/src/embedding-cache.js +0 -222
- package/src/embedding-client.js +0 -141
- package/src/embedding-server/index.js +0 -424
- package/src/embedding-server/mobile/auth.js +0 -160
- package/src/embedding-server/mobile/daemon.js +0 -313
- package/src/embedding-server/mobile/output-parser.js +0 -281
- package/src/embedding-server/mobile/session-api.js +0 -279
- package/src/embedding-server/mobile/session-manager.js +0 -377
- package/src/embedding-server/mobile/websocket-handler.js +0 -443
- package/src/embeddings.js +0 -305
- package/src/errors.js +0 -326
- package/src/index.js +0 -41
- package/src/mama-api.js +0 -2614
- package/src/memory-inject.js +0 -174
- package/src/memory-store.js +0 -89
- package/src/notification-manager.js +0 -3
- package/src/ollama-client.js +0 -391
- package/src/outcome-tracker.js +0 -351
- package/src/progress-indicator.js +0 -110
- package/src/query-intent.js +0 -237
- package/src/relevance-scorer.js +0 -286
- package/src/tier-validator.js +0 -269
- package/src/time-formatter.js +0 -98
package/src/embedding-cache.js
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MAMA (Memory-Augmented MCP Architecture) - Embedding Cache
|
|
3
|
-
*
|
|
4
|
-
* LRU cache for embedding vectors to avoid re-computation
|
|
5
|
-
*
|
|
6
|
-
* Task 2: Implement Embedding Cache
|
|
7
|
-
* AC #3: Cache embeddings to avoid re-computation
|
|
8
|
-
* Target: > 80% cache hit ratio
|
|
9
|
-
*
|
|
10
|
-
* @module embedding-cache
|
|
11
|
-
* @version 1.0
|
|
12
|
-
* @date 2025-11-14
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const crypto = require('crypto');
|
|
16
|
-
|
|
17
|
-
// Cache configuration
|
|
18
|
-
const MAX_CACHE_SIZE = 1000; // Max 1000 entries (story req 2.2)
|
|
19
|
-
const CLEANUP_THRESHOLD = 1100; // Trigger cleanup at 110%
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* LRU Cache for embeddings
|
|
23
|
-
*
|
|
24
|
-
* Key: SHA-256 hash of decision text
|
|
25
|
-
* Value: {embedding: Float32Array, timestamp: number, hits: number}
|
|
26
|
-
*
|
|
27
|
-
* Eviction: Least Recently Used when size > MAX_CACHE_SIZE
|
|
28
|
-
*/
|
|
29
|
-
class EmbeddingCache {
|
|
30
|
-
constructor() {
|
|
31
|
-
this.cache = new Map();
|
|
32
|
-
this.stats = {
|
|
33
|
-
hits: 0,
|
|
34
|
-
misses: 0,
|
|
35
|
-
evictions: 0,
|
|
36
|
-
totalSize: 0,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Generate cache key from text
|
|
42
|
-
*
|
|
43
|
-
* Task 2.3: Key = decision text hash (SHA-256)
|
|
44
|
-
*
|
|
45
|
-
* @param {string} text - Input text
|
|
46
|
-
* @returns {string} SHA-256 hex hash
|
|
47
|
-
*/
|
|
48
|
-
generateKey(text) {
|
|
49
|
-
if (!text || typeof text !== 'string') {
|
|
50
|
-
throw new Error('Text must be a non-empty string');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return crypto.createHash('sha256').update(text).digest('hex');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Get embedding from cache
|
|
58
|
-
*
|
|
59
|
-
* Task 2.2: Cache hit updates LRU position
|
|
60
|
-
*
|
|
61
|
-
* @param {string} text - Input text
|
|
62
|
-
* @returns {Float32Array|null} Cached embedding or null
|
|
63
|
-
*/
|
|
64
|
-
get(text) {
|
|
65
|
-
const key = this.generateKey(text);
|
|
66
|
-
const entry = this.cache.get(key);
|
|
67
|
-
|
|
68
|
-
if (entry) {
|
|
69
|
-
// Cache hit - update LRU position
|
|
70
|
-
this.stats.hits++;
|
|
71
|
-
entry.hits++;
|
|
72
|
-
entry.lastAccessed = Date.now();
|
|
73
|
-
|
|
74
|
-
// Move to end (most recently used)
|
|
75
|
-
this.cache.delete(key);
|
|
76
|
-
this.cache.set(key, entry);
|
|
77
|
-
|
|
78
|
-
return entry.embedding;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Cache miss
|
|
82
|
-
this.stats.misses++;
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Store embedding in cache
|
|
88
|
-
*
|
|
89
|
-
* Task 2.2: Add to cache with LRU tracking
|
|
90
|
-
* Task 2.5: Implement cache eviction (LRU)
|
|
91
|
-
*
|
|
92
|
-
* @param {string} text - Input text
|
|
93
|
-
* @param {Float32Array} embedding - Embedding vector
|
|
94
|
-
*/
|
|
95
|
-
set(text, embedding) {
|
|
96
|
-
const key = this.generateKey(text);
|
|
97
|
-
|
|
98
|
-
// Check if already exists (update case)
|
|
99
|
-
if (this.cache.has(key)) {
|
|
100
|
-
const entry = this.cache.get(key);
|
|
101
|
-
entry.embedding = embedding;
|
|
102
|
-
entry.lastAccessed = Date.now();
|
|
103
|
-
|
|
104
|
-
// Move to end (most recently used)
|
|
105
|
-
this.cache.delete(key);
|
|
106
|
-
this.cache.set(key, entry);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// New entry
|
|
111
|
-
const entry = {
|
|
112
|
-
embedding,
|
|
113
|
-
timestamp: Date.now(),
|
|
114
|
-
lastAccessed: Date.now(),
|
|
115
|
-
hits: 0,
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
this.cache.set(key, entry);
|
|
119
|
-
this.stats.totalSize++;
|
|
120
|
-
|
|
121
|
-
// Trigger cleanup if threshold exceeded
|
|
122
|
-
if (this.cache.size > CLEANUP_THRESHOLD) {
|
|
123
|
-
this.evictLRU();
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Evict least recently used entries
|
|
129
|
-
*
|
|
130
|
-
* Task 2.5: Implement cache eviction (LRU)
|
|
131
|
-
* Strategy: Remove entries until size <= MAX_CACHE_SIZE
|
|
132
|
-
*
|
|
133
|
-
* Eviction order:
|
|
134
|
-
* 1. Oldest lastAccessed (LRU)
|
|
135
|
-
* 2. If tied, lowest hits
|
|
136
|
-
*/
|
|
137
|
-
evictLRU() {
|
|
138
|
-
const targetEvictions = this.cache.size - MAX_CACHE_SIZE;
|
|
139
|
-
|
|
140
|
-
if (targetEvictions <= 0) {
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Convert to array for sorting
|
|
145
|
-
const entries = Array.from(this.cache.entries());
|
|
146
|
-
|
|
147
|
-
// Sort by LRU (oldest first), then by hits (lowest first)
|
|
148
|
-
entries.sort((a, b) => {
|
|
149
|
-
const [, entryA] = a;
|
|
150
|
-
const [, entryB] = b;
|
|
151
|
-
|
|
152
|
-
// Primary: lastAccessed (ascending - oldest first)
|
|
153
|
-
if (entryA.lastAccessed !== entryB.lastAccessed) {
|
|
154
|
-
return entryA.lastAccessed - entryB.lastAccessed;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Secondary: hits (ascending - lowest first)
|
|
158
|
-
return entryA.hits - entryB.hits;
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// Evict oldest entries
|
|
162
|
-
for (let i = 0; i < targetEvictions; i++) {
|
|
163
|
-
const [key] = entries[i];
|
|
164
|
-
this.cache.delete(key);
|
|
165
|
-
this.stats.evictions++;
|
|
166
|
-
this.stats.totalSize--;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Get cache hit ratio
|
|
172
|
-
*
|
|
173
|
-
* Task 2.4: Cache hit ratio target: > 80%
|
|
174
|
-
*
|
|
175
|
-
* @returns {number} Hit ratio (0.0 - 1.0)
|
|
176
|
-
*/
|
|
177
|
-
getHitRatio() {
|
|
178
|
-
const total = this.stats.hits + this.stats.misses;
|
|
179
|
-
|
|
180
|
-
if (total === 0) {
|
|
181
|
-
return 0;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return this.stats.hits / total;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Get cache statistics
|
|
189
|
-
*
|
|
190
|
-
* @returns {Object} Cache stats
|
|
191
|
-
*/
|
|
192
|
-
getStats() {
|
|
193
|
-
return {
|
|
194
|
-
...this.stats,
|
|
195
|
-
hitRatio: this.getHitRatio(),
|
|
196
|
-
size: this.cache.size,
|
|
197
|
-
maxSize: MAX_CACHE_SIZE,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Clear cache
|
|
203
|
-
*/
|
|
204
|
-
clear() {
|
|
205
|
-
this.cache.clear();
|
|
206
|
-
this.stats = {
|
|
207
|
-
hits: 0,
|
|
208
|
-
misses: 0,
|
|
209
|
-
evictions: 0,
|
|
210
|
-
totalSize: 0,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Singleton instance
|
|
216
|
-
const embeddingCache = new EmbeddingCache();
|
|
217
|
-
|
|
218
|
-
module.exports = {
|
|
219
|
-
embeddingCache,
|
|
220
|
-
EmbeddingCache,
|
|
221
|
-
MAX_CACHE_SIZE,
|
|
222
|
-
};
|
package/src/embedding-client.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MAMA Embedding Client
|
|
3
|
-
*
|
|
4
|
-
* HTTP client for the embedding server running in MCP server.
|
|
5
|
-
* Used by hooks for fast embedding generation.
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Auto-discovery via port file
|
|
9
|
-
* - Timeout handling
|
|
10
|
-
* - Fallback to local embedding when server unavailable
|
|
11
|
-
*
|
|
12
|
-
* @module embedding-client
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
const path = require('path');
|
|
17
|
-
const { info, warn } = require('./debug-logger');
|
|
18
|
-
|
|
19
|
-
// Configuration
|
|
20
|
-
const DEFAULT_PORT = 3847;
|
|
21
|
-
const HOST = '127.0.0.1';
|
|
22
|
-
const TIMEOUT_MS = 500; // 500ms timeout for fast response
|
|
23
|
-
const PORT_FILE = path.join(process.env.HOME || '/tmp', '.mama-embedding-port');
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Get server port from port file or default
|
|
27
|
-
*/
|
|
28
|
-
function getServerPort() {
|
|
29
|
-
try {
|
|
30
|
-
if (fs.existsSync(PORT_FILE)) {
|
|
31
|
-
const port = parseInt(fs.readFileSync(PORT_FILE, 'utf8').trim(), 10);
|
|
32
|
-
if (port > 0 && port < 65536) {
|
|
33
|
-
return port;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
} catch (e) {
|
|
37
|
-
// Ignore errors, use default
|
|
38
|
-
}
|
|
39
|
-
return DEFAULT_PORT;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Check if embedding server is running
|
|
44
|
-
*
|
|
45
|
-
* @returns {Promise<boolean>} True if server is healthy
|
|
46
|
-
*/
|
|
47
|
-
async function isServerRunning() {
|
|
48
|
-
const port = getServerPort();
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
const controller = new AbortController();
|
|
52
|
-
const timeout = setTimeout(() => controller.abort(), 200);
|
|
53
|
-
|
|
54
|
-
const response = await fetch(`http://${HOST}:${port}/health`, {
|
|
55
|
-
signal: controller.signal,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
clearTimeout(timeout);
|
|
59
|
-
return response.ok;
|
|
60
|
-
} catch (e) {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Generate embedding via HTTP server
|
|
67
|
-
*
|
|
68
|
-
* @param {string} text - Text to embed
|
|
69
|
-
* @returns {Promise<Float32Array|null>} Embedding or null if failed
|
|
70
|
-
*/
|
|
71
|
-
async function getEmbeddingFromServer(text) {
|
|
72
|
-
const port = getServerPort();
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const controller = new AbortController();
|
|
76
|
-
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
77
|
-
|
|
78
|
-
const response = await fetch(`http://${HOST}:${port}/embed`, {
|
|
79
|
-
method: 'POST',
|
|
80
|
-
headers: { 'Content-Type': 'application/json' },
|
|
81
|
-
body: JSON.stringify({ text }),
|
|
82
|
-
signal: controller.signal,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
clearTimeout(timeout);
|
|
86
|
-
|
|
87
|
-
if (!response.ok) {
|
|
88
|
-
const error = await response.json();
|
|
89
|
-
throw new Error(error.error || 'Server error');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const result = await response.json();
|
|
93
|
-
info(`[EmbeddingClient] Got embedding in ${result.latency}ms from server`);
|
|
94
|
-
return new Float32Array(result.embedding);
|
|
95
|
-
} catch (error) {
|
|
96
|
-
if (error.name === 'AbortError') {
|
|
97
|
-
warn('[EmbeddingClient] Server timeout, will use fallback');
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
warn(`[EmbeddingClient] Server error: ${error.message}`);
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Get server status
|
|
107
|
-
*
|
|
108
|
-
* @returns {Promise<Object|null>} Server status or null if not running
|
|
109
|
-
*/
|
|
110
|
-
async function getServerStatus() {
|
|
111
|
-
const port = getServerPort();
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
const controller = new AbortController();
|
|
115
|
-
const timeout = setTimeout(() => controller.abort(), 200);
|
|
116
|
-
|
|
117
|
-
const response = await fetch(`http://${HOST}:${port}/health`, {
|
|
118
|
-
signal: controller.signal,
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
clearTimeout(timeout);
|
|
122
|
-
|
|
123
|
-
if (response.ok) {
|
|
124
|
-
return await response.json();
|
|
125
|
-
}
|
|
126
|
-
return null;
|
|
127
|
-
} catch (e) {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
module.exports = {
|
|
133
|
-
isServerRunning,
|
|
134
|
-
getEmbeddingFromServer,
|
|
135
|
-
getServerStatus,
|
|
136
|
-
getServerPort,
|
|
137
|
-
PORT_FILE,
|
|
138
|
-
DEFAULT_PORT,
|
|
139
|
-
HOST,
|
|
140
|
-
TIMEOUT_MS,
|
|
141
|
-
};
|