@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/src/embeddings.js DELETED
@@ -1,305 +0,0 @@
1
- /**
2
- * MAMA (Memory-Augmented MCP Architecture) - Embedding Generation
3
- *
4
- * Story M1.4: Configurable embedding model selection
5
- * Generates embeddings using configurable model (default: multilingual-e5-small)
6
- * Supports: Korean-English cross-lingual similarity, enhanced metadata
7
- *
8
- * @module embeddings
9
- * @version 1.1
10
- * @date 2025-11-20
11
- */
12
-
13
- // eslint-disable-next-line no-unused-vars
14
- const { info, error: logError } = require('./debug-logger');
15
- const { logProgress: _logProgress, logComplete, logLoading } = require('./progress-indicator');
16
- const os = require('os');
17
- const path = require('path');
18
- // Lazy-load @huggingface/transformers to avoid loading sharp at module load time (Story 014.12.7)
19
- // const { pipeline } = require('@huggingface/transformers');
20
- const { embeddingCache } = require('./embedding-cache');
21
- const { loadConfig, getModelName, getEmbeddingDim } = require('./config-loader');
22
-
23
- // Shared cache directory (not in node_modules)
24
- const DEFAULT_CACHE_DIR = path.join(os.homedir(), '.cache', 'huggingface', 'transformers');
25
-
26
- // Singleton pattern for model loading
27
- let embeddingPipeline = null;
28
- let currentModelName = null;
29
-
30
- /**
31
- * Load embedding model (configurable)
32
- *
33
- * Story M1.4 AC #2: Transformers.js singleton initialization
34
- * Story M1.4 AC #3: Changing model via config triggers informative log + resets caches
35
- *
36
- * @returns {Promise<Function>} Embedding pipeline
37
- */
38
- async function loadModel() {
39
- const modelName = getModelName();
40
-
41
- // Check if model has changed (Story M1.4 AC #3)
42
- if (embeddingPipeline && currentModelName && currentModelName !== modelName) {
43
- info('[MAMA] ⚠️ Embedding model changed - resetting pipeline');
44
- info(`[MAMA] Old model: ${currentModelName}`);
45
- info(`[MAMA] New model: ${modelName}`);
46
-
47
- // Reset pipeline and cache
48
- embeddingPipeline = null;
49
- currentModelName = null;
50
- embeddingCache.clear();
51
-
52
- info('[MAMA] ⚡ Model cache cleared');
53
- }
54
-
55
- // Load model if not already loaded
56
- if (!embeddingPipeline) {
57
- logLoading(`Loading embedding model: ${modelName}...`);
58
- const startTime = Date.now();
59
-
60
- // Dynamic import for ES Module compatibility (Railway deployment)
61
- const transformers = await import('@huggingface/transformers');
62
- const { pipeline, env } = transformers;
63
-
64
- // Set shared cache directory (not in node_modules)
65
- // This prevents re-downloading models on every npm install
66
- const cacheDir = process.env.HF_HOME || process.env.TRANSFORMERS_CACHE || DEFAULT_CACHE_DIR;
67
- env.cacheDir = cacheDir;
68
- info(`[MAMA] Model cache directory: ${cacheDir}`);
69
-
70
- embeddingPipeline = await pipeline('feature-extraction', modelName);
71
- currentModelName = modelName;
72
-
73
- const loadTime = Date.now() - startTime;
74
- const config = loadConfig();
75
- logComplete(`Embedding model ready (${loadTime}ms, ${config.embeddingDim}-dim)`);
76
- }
77
-
78
- return embeddingPipeline;
79
- }
80
-
81
- /**
82
- * Generate embedding vector from text
83
- *
84
- * Story M1.4 AC #1: Uses configurable embeddingDim from config
85
- * Target: < 30ms latency
86
- *
87
- * @param {string} text - Input text to embed
88
- * @returns {Promise<Float32Array>} Embedding vector (dimension from config)
89
- * @throws {Error} If text is empty or embedding fails
90
- */
91
- async function generateEmbedding(text) {
92
- if (!text || text.trim().length === 0) {
93
- throw new Error('Text cannot be empty');
94
- }
95
-
96
- // Task 2: Check cache first (AC #3)
97
- const cached = embeddingCache.get(text);
98
- if (cached) {
99
- return cached;
100
- }
101
-
102
- const startTime = Date.now();
103
-
104
- try {
105
- const model = await loadModel();
106
- const expectedDim = getEmbeddingDim();
107
-
108
- // Generate embedding
109
- const output = await model(text, {
110
- pooling: 'mean', // Mean pooling over tokens
111
- normalize: true, // L2 normalization
112
- });
113
-
114
- // Extract Float32Array
115
- const embedding = output.data;
116
-
117
- // Verify dimensions match config
118
- if (embedding.length !== expectedDim) {
119
- throw new Error(`Expected ${expectedDim}-dim, got ${embedding.length}-dim`);
120
- }
121
-
122
- // eslint-disable-next-line no-unused-vars
123
- const latency = Date.now() - startTime;
124
-
125
- // Task 2: Store in cache (AC #3)
126
- embeddingCache.set(text, embedding);
127
-
128
- return embedding;
129
- } catch (error) {
130
- throw new Error(`Failed to generate embedding: ${error.message}`);
131
- }
132
- }
133
-
134
- /**
135
- * Generate enhanced embedding with content + metadata
136
- *
137
- * Task 3.4: Implement enhanced embedding format
138
- * Inspired by A-mem: Content + Metadata for richer semantic representation
139
- *
140
- * @param {Object} decision - Decision object
141
- * @param {string} decision.topic - Decision topic
142
- * @param {string} decision.decision - Decision value
143
- * @param {string} decision.reasoning - Decision reasoning
144
- * @param {string} decision.outcome - Decision outcome (optional)
145
- * @param {number} decision.confidence - Confidence score (optional)
146
- * @param {string} decision.user_involvement - User involvement (optional)
147
- * @returns {Promise<Float32Array>} 384-dim enhanced embedding
148
- */
149
- async function generateEnhancedEmbedding(decision) {
150
- // Construct enriched text representation with narrative fields (Story 2.2)
151
- const parts = [
152
- `Topic: ${decision.topic}`,
153
- `Decision: ${decision.decision}`,
154
- `Reasoning: ${decision.reasoning || 'N/A'}`,
155
- `Outcome: ${decision.outcome || 'ONGOING'}`,
156
- `Confidence: ${decision.confidence !== undefined ? decision.confidence : 0.5}`,
157
- `User Involvement: ${decision.user_involvement || 'N/A'}`,
158
- ];
159
-
160
- // Add narrative fields if present (Story 2.2: Narrative-Based Search)
161
- if (decision.evidence) {
162
- const evidenceText = Array.isArray(decision.evidence)
163
- ? decision.evidence.join('; ')
164
- : typeof decision.evidence === 'string'
165
- ? decision.evidence
166
- : JSON.stringify(decision.evidence);
167
- parts.push(`Evidence: ${evidenceText}`);
168
- }
169
-
170
- if (decision.alternatives) {
171
- const alternativesText = Array.isArray(decision.alternatives)
172
- ? decision.alternatives.join('; ')
173
- : typeof decision.alternatives === 'string'
174
- ? decision.alternatives
175
- : JSON.stringify(decision.alternatives);
176
- parts.push(`Alternatives: ${alternativesText}`);
177
- }
178
-
179
- if (decision.risks) {
180
- parts.push(`Risks: ${decision.risks}`);
181
- }
182
-
183
- const enrichedText = parts.join('\n').trim();
184
-
185
- return generateEmbedding(enrichedText);
186
- }
187
-
188
- /**
189
- * Batch generate embeddings (optimized)
190
- *
191
- * Task 1: Implement Batch Embedding Generation
192
- * AC #3: Target - 30ms for 10 embeddings (vs 300ms sequential)
193
- *
194
- * Strategy: Use native transformer batch processing for parallel inference
195
- *
196
- * @param {string[]} texts - Array of texts to embed (max 10 per batch)
197
- * @returns {Promise<Float32Array[]>} Array of embeddings
198
- */
199
- async function generateBatchEmbeddings(texts) {
200
- if (!Array.isArray(texts) || texts.length === 0) {
201
- throw new Error('Texts must be a non-empty array');
202
- }
203
-
204
- // Validate all texts
205
- for (const text of texts) {
206
- if (!text || text.trim().length === 0) {
207
- throw new Error('All texts must be non-empty');
208
- }
209
- }
210
-
211
- const startTime = Date.now();
212
-
213
- try {
214
- const model = await loadModel();
215
- const expectedDim = getEmbeddingDim();
216
-
217
- // Native batch processing - single model forward pass
218
- // This is significantly faster than sequential calls
219
- const outputs = await model(texts, {
220
- pooling: 'mean',
221
- normalize: true,
222
- });
223
-
224
- // Extract embeddings from batch output
225
- const embeddings = [];
226
- const batchSize = texts.length;
227
-
228
- for (let i = 0; i < batchSize; i++) {
229
- // Each embedding is expectedDim consecutive elements
230
- const start = i * expectedDim;
231
- const end = start + expectedDim;
232
- const embedding = outputs.data.slice(start, end);
233
-
234
- // Verify dimensions
235
- if (embedding.length !== expectedDim) {
236
- throw new Error(`Expected ${expectedDim}-dim, got ${embedding.length}-dim at index ${i}`);
237
- }
238
-
239
- embeddings.push(embedding);
240
- }
241
-
242
- const latency = Date.now() - startTime;
243
- const avgLatency = latency / batchSize;
244
-
245
- // Log for performance tracking
246
- if (process.env.MAMA_DEBUG) {
247
- info(
248
- `[MAMA] Batch(${batchSize}) embeddings: ${latency}ms total (${avgLatency.toFixed(1)}ms avg)`
249
- );
250
- }
251
-
252
- return embeddings;
253
- } catch (error) {
254
- throw new Error(`Failed to generate batch embeddings: ${error.message}`);
255
- }
256
- }
257
-
258
- /**
259
- * Calculate cosine similarity between two embeddings
260
- *
261
- * Utility for testing and validation
262
- *
263
- * @param {Float32Array} embA - First embedding
264
- * @param {Float32Array} embB - Second embedding
265
- * @returns {number} Cosine similarity (0-1)
266
- */
267
- function cosineSimilarity(embA, embB) {
268
- if (embA.length !== embB.length) {
269
- throw new Error('Embeddings must have same dimension');
270
- }
271
-
272
- let dotProduct = 0;
273
- let normA = 0;
274
- let normB = 0;
275
-
276
- for (let i = 0; i < embA.length; i++) {
277
- dotProduct += embA[i] * embB[i];
278
- normA += embA[i] * embA[i];
279
- normB += embB[i] * embB[i];
280
- }
281
-
282
- const similarity = dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
283
-
284
- return similarity;
285
- }
286
-
287
- // Export API
288
- module.exports = {
289
- generateEmbedding,
290
- generateEnhancedEmbedding,
291
- generateBatchEmbeddings,
292
- cosineSimilarity,
293
- embeddingCache,
294
- // Dynamic getters for config values (Story M1.4)
295
- get EMBEDDING_DIM() {
296
- return getEmbeddingDim();
297
- },
298
- get MODEL_NAME() {
299
- return getModelName();
300
- },
301
- // Expose config functions for external use
302
- loadConfig,
303
- getModelName,
304
- getEmbeddingDim,
305
- };
package/src/errors.js DELETED
@@ -1,326 +0,0 @@
1
- /**
2
- * MAMA Error Classes - Typed Error Handling
3
- *
4
- * Story 8.3: Typed Error Classes
5
- * Provides consistent error handling across MCP tools and core modules
6
- *
7
- * Error codes follow MCP standard response format:
8
- * {error: {code: 'ERROR_CODE', message: '...', details: {}}}
9
- *
10
- * @module errors
11
- * @version 1.0
12
- * @date 2025-11-25
13
- */
14
-
15
- /**
16
- * Base error class for all MAMA errors
17
- *
18
- * @class MAMAError
19
- * @extends Error
20
- */
21
- class MAMAError extends Error {
22
- /**
23
- * @param {string} message - Error message
24
- * @param {string} code - Error code (e.g., 'DECISION_NOT_FOUND')
25
- * @param {Object} details - Additional error details
26
- */
27
- constructor(message, code = 'MAMA_ERROR', details = {}) {
28
- super(message);
29
- this.name = 'MAMAError';
30
- this.code = code;
31
- this.details = details;
32
- this.timestamp = new Date().toISOString();
33
-
34
- // Capture stack trace
35
- if (Error.captureStackTrace) {
36
- Error.captureStackTrace(this, this.constructor);
37
- }
38
- }
39
-
40
- /**
41
- * Convert to MCP-compatible error response format
42
- *
43
- * @returns {Object} {error: {code, message, details}}
44
- */
45
- toResponse() {
46
- return {
47
- error: {
48
- code: this.code,
49
- message: this.message,
50
- details: this.details,
51
- },
52
- };
53
- }
54
-
55
- /**
56
- * Convert to JSON for logging
57
- *
58
- * @returns {Object} JSON representation
59
- */
60
- toJSON() {
61
- return {
62
- name: this.name,
63
- code: this.code,
64
- message: this.message,
65
- details: this.details,
66
- timestamp: this.timestamp,
67
- stack: this.stack,
68
- };
69
- }
70
- }
71
-
72
- /**
73
- * Error thrown when a decision is not found
74
- *
75
- * @class NotFoundError
76
- * @extends MAMAError
77
- */
78
- class NotFoundError extends MAMAError {
79
- /**
80
- * @param {string} resourceType - Type of resource (e.g., 'decision', 'checkpoint')
81
- * @param {string} identifier - Resource identifier
82
- * @param {Object} details - Additional details
83
- */
84
- constructor(resourceType, identifier, details = {}) {
85
- super(`${resourceType} not found: ${identifier}`, `${resourceType.toUpperCase()}_NOT_FOUND`, {
86
- resourceType,
87
- identifier,
88
- ...details,
89
- });
90
- this.name = 'NotFoundError';
91
- }
92
- }
93
-
94
- /**
95
- * Error thrown when input validation fails
96
- *
97
- * @class ValidationError
98
- * @extends MAMAError
99
- */
100
- class ValidationError extends MAMAError {
101
- /**
102
- * @param {string} field - Field that failed validation
103
- * @param {string} message - Validation error message
104
- * @param {*} received - Received value
105
- * @param {Object} details - Additional details
106
- */
107
- constructor(field, message, received = undefined, details = {}) {
108
- super(`Validation failed for '${field}': ${message}`, 'INVALID_INPUT', {
109
- field,
110
- received: received !== undefined ? String(received).substring(0, 100) : undefined,
111
- ...details,
112
- });
113
- this.name = 'ValidationError';
114
- this.field = field;
115
- }
116
- }
117
-
118
- /**
119
- * Error thrown when database operations fail
120
- *
121
- * @class DatabaseError
122
- * @extends MAMAError
123
- */
124
- class DatabaseError extends MAMAError {
125
- /**
126
- * @param {string} operation - Database operation (e.g., 'insert', 'query', 'update')
127
- * @param {string} message - Error message
128
- * @param {Object} details - Additional details
129
- */
130
- constructor(operation, message, details = {}) {
131
- super(`Database ${operation} failed: ${message}`, 'DATABASE_ERROR', {
132
- operation,
133
- ...details,
134
- });
135
- this.name = 'DatabaseError';
136
- this.operation = operation;
137
- }
138
- }
139
-
140
- /**
141
- * Error thrown when embedding generation fails
142
- *
143
- * @class EmbeddingError
144
- * @extends MAMAError
145
- */
146
- class EmbeddingError extends MAMAError {
147
- /**
148
- * @param {string} message - Error message
149
- * @param {Object} details - Additional details (model, input length, etc.)
150
- */
151
- constructor(message, details = {}) {
152
- super(`Embedding generation failed: ${message}`, 'EMBEDDING_ERROR', details);
153
- this.name = 'EmbeddingError';
154
- }
155
- }
156
-
157
- /**
158
- * Error thrown when configuration is invalid
159
- *
160
- * @class ConfigurationError
161
- * @extends MAMAError
162
- */
163
- class ConfigurationError extends MAMAError {
164
- /**
165
- * @param {string} configKey - Configuration key
166
- * @param {string} message - Error message
167
- * @param {Object} details - Additional details
168
- */
169
- constructor(configKey, message, details = {}) {
170
- super(`Configuration error for '${configKey}': ${message}`, 'CONFIG_ERROR', {
171
- configKey,
172
- ...details,
173
- });
174
- this.name = 'ConfigurationError';
175
- this.configKey = configKey;
176
- }
177
- }
178
-
179
- /**
180
- * Error thrown when a link operation fails
181
- *
182
- * @class LinkError
183
- * @extends MAMAError
184
- */
185
- class LinkError extends MAMAError {
186
- /**
187
- * @param {string} operation - Link operation (e.g., 'propose', 'approve', 'reject')
188
- * @param {string} message - Error message
189
- * @param {Object} details - Additional details (from_id, to_id, etc.)
190
- */
191
- constructor(operation, message, details = {}) {
192
- super(`Link ${operation} failed: ${message}`, 'LINK_ERROR', {
193
- operation,
194
- ...details,
195
- });
196
- this.name = 'LinkError';
197
- this.operation = operation;
198
- }
199
- }
200
-
201
- /**
202
- * Error thrown when rate limit is exceeded
203
- *
204
- * @class RateLimitError
205
- * @extends MAMAError
206
- */
207
- class RateLimitError extends MAMAError {
208
- /**
209
- * @param {string} operation - Operation that was rate limited
210
- * @param {number} retryAfterMs - Time to wait before retry (ms)
211
- * @param {Object} details - Additional details
212
- */
213
- constructor(operation, retryAfterMs, details = {}) {
214
- super(`Rate limit exceeded for ${operation}. Retry after ${retryAfterMs}ms`, 'RATE_LIMITED', {
215
- operation,
216
- retryAfterMs,
217
- ...details,
218
- });
219
- this.name = 'RateLimitError';
220
- this.retryAfterMs = retryAfterMs;
221
- }
222
- }
223
-
224
- /**
225
- * Error thrown when operation times out
226
- *
227
- * @class TimeoutError
228
- * @extends MAMAError
229
- */
230
- class TimeoutError extends MAMAError {
231
- /**
232
- * @param {string} operation - Operation that timed out
233
- * @param {number} timeoutMs - Timeout duration (ms)
234
- * @param {Object} details - Additional details
235
- */
236
- constructor(operation, timeoutMs, details = {}) {
237
- super(`Operation '${operation}' timed out after ${timeoutMs}ms`, 'TIMEOUT', {
238
- operation,
239
- timeoutMs,
240
- ...details,
241
- });
242
- this.name = 'TimeoutError';
243
- this.timeoutMs = timeoutMs;
244
- }
245
- }
246
-
247
- /**
248
- * Error codes enum for reference
249
- */
250
- const ErrorCodes = {
251
- // Resource errors
252
- DECISION_NOT_FOUND: 'DECISION_NOT_FOUND',
253
- CHECKPOINT_NOT_FOUND: 'CHECKPOINT_NOT_FOUND',
254
- LINK_NOT_FOUND: 'LINK_NOT_FOUND',
255
-
256
- // Validation errors
257
- INVALID_INPUT: 'INVALID_INPUT',
258
- MISSING_REQUIRED_FIELD: 'MISSING_REQUIRED_FIELD',
259
- INVALID_FORMAT: 'INVALID_FORMAT',
260
-
261
- // Database errors
262
- DATABASE_ERROR: 'DATABASE_ERROR',
263
- CONNECTION_FAILED: 'CONNECTION_FAILED',
264
- QUERY_FAILED: 'QUERY_FAILED',
265
-
266
- // Processing errors
267
- EMBEDDING_ERROR: 'EMBEDDING_ERROR',
268
- CONFIG_ERROR: 'CONFIG_ERROR',
269
- LINK_ERROR: 'LINK_ERROR',
270
-
271
- // Operational errors
272
- RATE_LIMITED: 'RATE_LIMITED',
273
- TIMEOUT: 'TIMEOUT',
274
- INTERNAL_ERROR: 'INTERNAL_ERROR',
275
- };
276
-
277
- /**
278
- * Helper function to wrap unknown errors
279
- *
280
- * @param {Error|unknown} error - Error to wrap
281
- * @param {string} context - Context for the error
282
- * @returns {MAMAError} Wrapped MAMA error
283
- */
284
- function wrapError(error, context = 'Unknown operation') {
285
- if (error instanceof MAMAError) {
286
- return error;
287
- }
288
-
289
- const message = error instanceof Error ? error.message : String(error);
290
- const stack = error instanceof Error ? error.stack : undefined;
291
-
292
- return new MAMAError(`${context}: ${message}`, 'INTERNAL_ERROR', {
293
- originalError: message,
294
- originalStack: stack,
295
- });
296
- }
297
-
298
- /**
299
- * Helper function to check if an error is a MAMA error
300
- *
301
- * @param {unknown} error - Error to check
302
- * @returns {boolean} True if MAMA error
303
- */
304
- function isMAMAError(error) {
305
- return error instanceof MAMAError;
306
- }
307
-
308
- module.exports = {
309
- // Base class
310
- MAMAError,
311
-
312
- // Specific error types
313
- NotFoundError,
314
- ValidationError,
315
- DatabaseError,
316
- EmbeddingError,
317
- ConfigurationError,
318
- LinkError,
319
- RateLimitError,
320
- TimeoutError,
321
-
322
- // Utilities
323
- ErrorCodes,
324
- wrapError,
325
- isMAMAError,
326
- };
package/src/index.js DELETED
@@ -1,41 +0,0 @@
1
- /**
2
- * MAMA Core - Main exports
3
- *
4
- * Shared modules for Memory-Augmented MCP Assistant.
5
- * Used by mcp-server, claude-code-plugin, and standalone packages.
6
- *
7
- * @module mama-core
8
- * @version 1.0.0
9
- */
10
-
11
- const embeddings = require('./embeddings');
12
- const embeddingCache = require('./embedding-cache');
13
- const embeddingClient = require('./embedding-client');
14
-
15
- const dbManager = require('./db-manager');
16
- const dbAdapter = require('./db-adapter');
17
- const memoryStore = require('./memory-store');
18
-
19
- const mamaApi = require('./mama-api');
20
- const configLoader = require('./config-loader');
21
- const relevanceScorer = require('./relevance-scorer');
22
- const decisionTracker = require('./decision-tracker');
23
- const tierValidator = require('./tier-validator');
24
- const progressIndicator = require('./progress-indicator');
25
-
26
- module.exports = {
27
- ...embeddings,
28
- embeddingCache,
29
- ...embeddingClient,
30
-
31
- ...dbManager,
32
- ...dbAdapter,
33
- ...memoryStore,
34
-
35
- ...mamaApi,
36
- ...configLoader,
37
- ...relevanceScorer,
38
- ...decisionTracker,
39
- ...tierValidator,
40
- ...progressIndicator,
41
- };