@jungjaehoon/mama-core 1.0.4 → 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.
@@ -1,351 +0,0 @@
1
- /**
2
- * MAMA (Memory-Augmented MCP Architecture) - Outcome Tracker
3
- *
4
- * Track decision outcomes from user feedback
5
- * Tasks: 4.1-4.8 (Failure/success indicators, UserPromptSubmit analysis, outcome marking)
6
- * AC #3: Failure tracking (user feedback → outcome marked, failure_reason extracted, duration calculated)
7
- *
8
- * @module outcome-tracker
9
- * @version 1.0
10
- * @date 2025-11-14
11
- */
12
-
13
- const { info, error: logError } = require('./debug-logger');
14
- const { getDB, updateDecisionOutcome } = require('./memory-store');
15
- const { updateConfidence } = require('./decision-tracker');
16
-
17
- /**
18
- * Failure indicators
19
- * Task 4.2: Define failure indicators
20
- */
21
- const FAILURE_INDICATORS = [
22
- /doesn't\s*work/i,
23
- /failed/i,
24
- /error/i,
25
- /slow/i,
26
- /broken/i,
27
- /bug/i,
28
- /wrong/i,
29
- /not\s*working/i,
30
- ];
31
-
32
- /**
33
- * Success indicators
34
- * Task 4.3: Define success indicators
35
- */
36
- const SUCCESS_INDICATORS = [
37
- /works/i,
38
- /perfect/i,
39
- /great/i,
40
- /success/i,
41
- /excellent/i,
42
- /fast/i,
43
- /good/i,
44
- ];
45
-
46
- /**
47
- * Partial success indicators
48
- * Task 4.3: Define partial success indicators
49
- */
50
- const PARTIAL_INDICATORS = [/okay/i, /acceptable/i, /improved/i, /better/i];
51
-
52
- /**
53
- * Recent decision time window (1 hour in milliseconds)
54
- * Task 4.5: Only mark outcome if decision is recent (< 1 hour)
55
- */
56
- const RECENT_WINDOW_MS = 60 * 60 * 1000; // 1 hour
57
-
58
- /**
59
- * Check if message matches failure indicators
60
- *
61
- * Task 4.2: Match failure patterns
62
- *
63
- * @param {string} message - User message
64
- * @returns {boolean} True if failure detected
65
- */
66
- function matchesFailureIndicators(message) {
67
- return FAILURE_INDICATORS.some((pattern) => pattern.test(message));
68
- }
69
-
70
- /**
71
- * Check if message matches success indicators
72
- *
73
- * Task 4.3: Match success patterns
74
- *
75
- * @param {string} message - User message
76
- * @returns {boolean} True if success detected
77
- */
78
- function matchesSuccessIndicators(message) {
79
- return SUCCESS_INDICATORS.some((pattern) => pattern.test(message));
80
- }
81
-
82
- /**
83
- * Check if message matches partial success indicators
84
- *
85
- * Task 4.3: Match partial success patterns
86
- *
87
- * @param {string} message - User message
88
- * @returns {boolean} True if partial success detected
89
- */
90
- function matchesPartialIndicators(message) {
91
- return PARTIAL_INDICATORS.some((pattern) => pattern.test(message));
92
- }
93
-
94
- /**
95
- * Determine outcome from user message
96
- *
97
- * Task 4.4, 4.5: Analyze user message for indicators
98
- * AC #3: Failure tracking from user feedback
99
- *
100
- * @param {string} message - User message
101
- * @returns {string|null} Outcome type ('FAILED', 'SUCCESS', 'PARTIAL') or null
102
- */
103
- function analyzeOutcome(message) {
104
- if (matchesFailureIndicators(message)) {
105
- return 'FAILED';
106
- }
107
-
108
- if (matchesSuccessIndicators(message)) {
109
- return 'SUCCESS';
110
- }
111
-
112
- if (matchesPartialIndicators(message)) {
113
- return 'PARTIAL';
114
- }
115
-
116
- return null; // No clear outcome
117
- }
118
-
119
- /**
120
- * Extract failure reason from user message
121
- *
122
- * Task 4.6: Extract failure_reason from user message
123
- * AC #3: failure_reason extracted
124
- *
125
- * Simple extraction: First sentence or first 200 characters
126
- * Future: Use LLM for better extraction
127
- *
128
- * @param {string} message - User message
129
- * @param {string} outcome - Outcome type
130
- * @returns {string|null} Failure reason
131
- */
132
- function extractFailureReason(message, outcome) {
133
- if (outcome !== 'FAILED') {
134
- return null;
135
- }
136
-
137
- // Extract first sentence
138
- const firstSentence = message.split(/[.!?]/)[0].trim();
139
-
140
- // Limit to 200 characters
141
- const reason = firstSentence.substring(0, 200);
142
-
143
- return reason || 'User indicated failure';
144
- }
145
-
146
- /**
147
- * Get recent decision (within 1 hour)
148
- *
149
- * Task 4.5: Find recent decision (< 1 hour)
150
- * AC #3: Recent decision (< 1 hour) marked
151
- *
152
- * @param {string} sessionId - Session ID
153
- * @returns {Object|null} Recent decision or null
154
- */
155
- function getRecentDecision(sessionId) {
156
- const db = getDB();
157
-
158
- try {
159
- const now = Date.now();
160
- const cutoffTime = now - RECENT_WINDOW_MS;
161
-
162
- const recent = db
163
- .prepare(
164
- `
165
- SELECT * FROM decisions
166
- WHERE session_id = ?
167
- AND outcome IS NULL
168
- AND created_at > ?
169
- ORDER BY created_at DESC
170
- LIMIT 1
171
- `
172
- )
173
- .get(sessionId, cutoffTime);
174
-
175
- return recent || null;
176
- } catch (error) {
177
- throw new Error(`Failed to query recent decision: ${error.message}`);
178
- }
179
- }
180
-
181
- /**
182
- * Calculate duration in days
183
- *
184
- * Task 4.7: Calculate duration_days
185
- * AC #3: duration_days calculated
186
- *
187
- * @param {number} createdAt - Decision created timestamp
188
- * @returns {number} Duration in days
189
- */
190
- function calculateDurationDays(createdAt) {
191
- const now = Date.now();
192
- const durationMs = now - createdAt;
193
- const durationDays = durationMs / (1000 * 60 * 60 * 24);
194
-
195
- // Round to 2 decimal places
196
- return Math.round(durationDays * 100) / 100;
197
- }
198
-
199
- /**
200
- * Get evidence impact for outcome
201
- *
202
- * Task 6: Confidence evolution - Calculate impact based on outcome
203
- * AC #5: Confidence score calculated based on history
204
- *
205
- * @param {string} outcome - Outcome type ('FAILED', 'SUCCESS', 'PARTIAL')
206
- * @param {number} durationDays - Duration in days
207
- * @returns {number} Impact on confidence
208
- */
209
- function getEvidenceImpact(outcome, durationDays) {
210
- // Task 6.3: Define evidence impacts
211
- let impact = 0;
212
-
213
- switch (outcome) {
214
- case 'SUCCESS':
215
- impact = 0.2; // +0.2 for success
216
- break;
217
- case 'FAILED':
218
- impact = -0.3; // -0.3 for failure
219
- break;
220
- case 'PARTIAL':
221
- impact = 0.1; // +0.1 for partial success
222
- break;
223
- }
224
-
225
- // Task 6.3: Temporal stability bonus (30+ days)
226
- if (outcome === 'SUCCESS' && durationDays >= 30) {
227
- impact += 0.1; // +0.1 for temporal stability
228
- }
229
-
230
- return impact;
231
- }
232
-
233
- /**
234
- * Mark decision outcome
235
- *
236
- * Task 4.8: Update decision row with outcome, failure_reason, duration_days
237
- * Task 6.5: Update confidence when outcome is marked
238
- * AC #3: Outcome marked with failure_reason and duration_days
239
- * AC #5: Confidence evolution
240
- *
241
- * @param {string} decisionId - Decision ID
242
- * @param {string} outcome - Outcome type ('FAILED', 'SUCCESS', 'PARTIAL')
243
- * @param {string} failureReason - Failure reason (if outcome=FAILED)
244
- * @param {number} durationDays - Duration in days
245
- */
246
- async function markOutcome(decisionId, outcome, failureReason, durationDays) {
247
- try {
248
- // Get current decision to read confidence
249
- const db = getDB();
250
- const decision = db.prepare('SELECT * FROM decisions WHERE id = ?').get(decisionId);
251
-
252
- if (!decision) {
253
- throw new Error(`Decision not found: ${decisionId}`);
254
- }
255
-
256
- // Guard against double-marking
257
- if (decision.outcome) {
258
- info(`[MAMA] Outcome already set for ${decisionId}: ${decision.outcome}`);
259
- return;
260
- }
261
-
262
- // Task 6.5: Calculate new confidence
263
- const evidenceImpact = getEvidenceImpact(outcome, durationDays);
264
- const evidence = [{ type: outcome, impact: evidenceImpact }];
265
- const prevConfidence = Number(decision.confidence ?? 0);
266
- const newConfidence = updateConfidence(prevConfidence, evidence);
267
-
268
- // Update decision with outcome and new confidence
269
- await updateDecisionOutcome(decisionId, {
270
- outcome,
271
- failure_reason: failureReason,
272
- duration_days: durationDays,
273
- confidence: newConfidence,
274
- });
275
-
276
- info(
277
- `[MAMA] Confidence updated: ${prevConfidence.toFixed(2)} → ${newConfidence.toFixed(2)} (${outcome})`
278
- );
279
- } catch (error) {
280
- throw new Error(`Failed to mark outcome: ${error.message}`);
281
- }
282
- }
283
-
284
- /**
285
- * UserPromptSubmit Handler
286
- *
287
- * Task 4.4: On UserPromptSubmit, analyze user message for indicators
288
- * Task 4.5: If matches + recent decision (< 1 hour), mark outcome
289
- * Task 4.6: Extract failure_reason from user message
290
- * Task 4.7: Calculate duration_days
291
- * Task 4.8: Update decision row
292
- *
293
- * AC #3: Failure tracking (user feedback → outcome marked)
294
- *
295
- * @param {Object} hookContext - Hook context from Claude Code
296
- * @param {string} hookContext.user_message - User's message
297
- * @param {string} hookContext.session_id - Session ID
298
- */
299
- async function onUserPromptSubmit(hookContext) {
300
- try {
301
- const userMessage = hookContext.user_message || '';
302
- const sessionId = hookContext.session_id || '';
303
-
304
- // Task 4.4: Analyze user message for outcome
305
- const outcome = analyzeOutcome(userMessage);
306
-
307
- if (!outcome) {
308
- // No clear outcome detected
309
- return;
310
- }
311
-
312
- // Task 4.5: Find recent decision (< 1 hour)
313
- const recentDecision = getRecentDecision(sessionId);
314
-
315
- if (!recentDecision) {
316
- // No recent decision to mark
317
- return;
318
- }
319
-
320
- // Task 4.6: Extract failure reason
321
- const failureReason = extractFailureReason(userMessage, outcome);
322
-
323
- // Task 4.7: Calculate duration
324
- const durationDays = calculateDurationDays(recentDecision.created_at);
325
-
326
- // Task 4.8: Mark outcome
327
- await markOutcome(recentDecision.id, outcome, failureReason, durationDays);
328
-
329
- info(`[MAMA] Outcome marked: ${recentDecision.id} → ${outcome} (${durationDays} days)`);
330
- } catch (error) {
331
- // Log error but don't crash hook
332
- logError(`[MAMA] Outcome tracking failed: ${error.message}`);
333
- }
334
- }
335
-
336
- // Export API
337
- module.exports = {
338
- onUserPromptSubmit,
339
- analyzeOutcome,
340
- extractFailureReason,
341
- getRecentDecision,
342
- calculateDurationDays,
343
- markOutcome,
344
- matchesFailureIndicators,
345
- matchesSuccessIndicators,
346
- matchesPartialIndicators,
347
- FAILURE_INDICATORS,
348
- SUCCESS_INDICATORS,
349
- PARTIAL_INDICATORS,
350
- RECENT_WINDOW_MS,
351
- };
@@ -1,110 +0,0 @@
1
- /**
2
- * MAMA Progress Indicator
3
- *
4
- * Text-based progress feedback for long-running operations.
5
- * Helps first-time users understand what's happening during initialization.
6
- *
7
- * Features:
8
- * - Logs to stderr (no stdout pollution)
9
- * - Emoji indicators: ⏳ (loading), ✅ (done), ❌ (error)
10
- * - Concise messages (<50 chars)
11
- *
12
- * @module progress-indicator
13
- * @version 1.0
14
- * @date 2026-01-30
15
- */
16
-
17
- /**
18
- * Log progress message to stderr
19
- *
20
- * Format: [MAMA] emoji message
21
- * Example: [MAMA] ⏳ Downloading embedding model (120MB)...
22
- *
23
- * @param {string} message - Progress message (without emoji or prefix)
24
- * @param {string} [emoji='⏳'] - Emoji indicator (⏳, ✅, ❌, 🔍, etc.)
25
- * @returns {void}
26
- */
27
- function logProgress(message, emoji = '⏳') {
28
- // Ensure message is a string - warn in development if not
29
- if (typeof message !== 'string') {
30
- if (process.env.NODE_ENV === 'development' || process.env.MAMA_DEBUG) {
31
- console.error(`[MAMA] ⚠️ logProgress expected string, got ${typeof message}`);
32
- }
33
- return;
34
- }
35
-
36
- // Log to stderr to avoid stdout pollution
37
- // stderr is used for progress/diagnostic output
38
- console.error(`[MAMA] ${emoji} ${message}`);
39
- }
40
-
41
- /**
42
- * Log completion message
43
- *
44
- * @param {string} message - Completion message
45
- * @returns {void}
46
- */
47
- function logComplete(message) {
48
- logProgress(message, '✅');
49
- }
50
-
51
- /**
52
- * Log failure/error message (user-facing progress indicator)
53
- *
54
- * Note: Named logFailed to avoid confusion with debug-logger's logError
55
- * which is used for internal debugging. This is for user-facing progress.
56
- *
57
- * @param {string} message - Failure message
58
- * @returns {void}
59
- */
60
- function logFailed(message) {
61
- logProgress(message, '❌');
62
- }
63
-
64
- // Alias for backward compatibility
65
- const logError = logFailed;
66
-
67
- /**
68
- * Log info message
69
- *
70
- * @param {string} message - Info message
71
- * @returns {void}
72
- */
73
- function logInfo(message) {
74
- logProgress(message, 'ℹ️');
75
- }
76
-
77
- /**
78
- * Log loading message
79
- *
80
- * @param {string} message - Loading message
81
- * @returns {void}
82
- */
83
- function logLoading(message) {
84
- logProgress(message, '⏳');
85
- }
86
-
87
- /**
88
- * Log searching message
89
- *
90
- * @param {string} message - Searching message
91
- * @returns {void}
92
- */
93
- function logSearching(message) {
94
- logProgress(message, '🔍');
95
- }
96
-
97
- // Note: Removed auto-registered SIGINT/SIGTERM handlers that called process.exit(0)
98
- // This was causing issues with host cleanup in parent processes.
99
- // If graceful shutdown is needed, the host application should handle it.
100
-
101
- // Export API
102
- module.exports = {
103
- logProgress,
104
- logComplete,
105
- logFailed,
106
- logError, // Alias for backward compatibility
107
- logInfo,
108
- logLoading,
109
- logSearching,
110
- };
@@ -1,237 +0,0 @@
1
- /**
2
- * MAMA (Memory-Augmented MCP Architecture) - Query Intent Analysis
3
- *
4
- * Analyzes user queries to detect decision-related intent using EXAONE 3.5
5
- * Tasks: 2.1-2.8 (LLM intent analysis with fallback chain)
6
- * AC #1: Query intent analysis (default 5s timeout for LLM latency)
7
- * AC #5: LLM fallback (EXAONE → Gemma → Qwen)
8
- *
9
- * @module query-intent
10
- * @version 1.0
11
- * @date 2025-11-14
12
- */
13
-
14
- const { info, error: logError } = require('./debug-logger');
15
- const { generate, DEFAULT_MODEL, FALLBACK_MODEL } = require('./ollama-client');
16
-
17
- /**
18
- * Analyze user message for decision-related intent
19
- *
20
- * Task 2.1-2.5: LLM intent analysis
21
- * AC #1: Detect if query involves decisions
22
- * AC #5: Fallback chain implemented
23
- *
24
- * @param {string} userMessage - User's message to analyze
25
- * @param {Object} options - Analysis options
26
- * @param {number} options.timeout - Timeout in ms (default: 100ms)
27
- * @param {number} options.threshold - Minimum confidence (default: 0.6)
28
- * @returns {Promise<Object>} Intent analysis result
29
- */
30
- async function analyzeIntent(userMessage, options = {}) {
31
- const {
32
- timeout = 5000, // Increased: LLM needs time, user accepts longer thinking
33
- threshold = 0.6,
34
- } = options;
35
-
36
- const startTime = Date.now();
37
-
38
- try {
39
- // Task 2.2: Build prompt for decision-making analysis
40
- const prompt = `
41
- Analyze if this query involves decision-making or past choices:
42
-
43
- User Message: "${userMessage}"
44
-
45
- Decision Indicators:
46
- 1. References to past decisions ("we chose X", "last time we did Y")
47
- 2. Questions about previous approaches ("why did we use X?")
48
- 3. Decision evolution queries ("should we change from X to Y?")
49
- 4. Architecture/strategy questions
50
- 5. Method/approach questions ("how do I...", "what's the way to...")
51
- 6. Best practice questions ("what should I use for...", "which one should I use...")
52
-
53
- Return JSON with "topic" as a short snake_case identifier (e.g., "mesh_structure", "database_choice", "auth_strategy", "coding_style", "error_handling"):
54
- {
55
- "involves_decision": boolean,
56
- "topic": string or null (extract main technical topic in snake_case),
57
- "confidence": 0.0-1.0,
58
- "reasoning": "brief explanation"
59
- }
60
-
61
- IMPORTANT: Generate "topic" freely based on the message content. Do NOT limit to predefined values.
62
-
63
- Examples:
64
- - "Why did we choose COMPLEX mesh structure?" → {"involves_decision": true, "topic": "mesh_structure", "confidence": 0.9}
65
- - "Let's use PostgreSQL for database" → {"involves_decision": true, "topic": "database_choice", "confidence": 0.9}
66
- - "How should we store workflow data?" → {"involves_decision": true, "topic": "workflow_storage", "confidence": 0.85}
67
- - "Read the file please" → {"involves_decision": false, "topic": null, "confidence": 0.1}
68
- `.trim();
69
-
70
- // Task 2.3: Call EXAONE 3.5 with Tier 1 fallback
71
- const result = await generateWithFallback(prompt, {
72
- format: 'json',
73
- temperature: 0.3,
74
- max_tokens: 200,
75
- timeout,
76
- });
77
-
78
- // eslint-disable-next-line no-unused-vars
79
- const latency = Date.now() - startTime;
80
-
81
- // Task 2.4: Parse response
82
- const parsed = typeof result === 'string' ? JSON.parse(result) : result;
83
-
84
- // Task 2.5: Threshold check
85
- const meetsThreshold = parsed.confidence >= threshold;
86
-
87
- if (!meetsThreshold) {
88
- info(`[MAMA] Intent confidence ${parsed.confidence} below threshold ${threshold}`);
89
- return {
90
- involves_decision: false,
91
- topic: null,
92
- confidence: parsed.confidence,
93
- reasoning: 'Confidence below threshold',
94
- };
95
- }
96
-
97
- return parsed;
98
- } catch (error) {
99
- // CLAUDE.md Rule #1: NO FALLBACK
100
- // Errors must be thrown for debugging
101
- logError(`[MAMA] Intent analysis FAILED: ${error.message}`);
102
- throw new Error(`Intent analysis failed: ${error.message}`);
103
- }
104
- }
105
-
106
- /**
107
- * Generate with tiered fallback chain
108
- *
109
- * Task 2.6-2.7: Implement fallback to Gemma 2B and Qwen 3B
110
- * AC #5: LLM fallback works
111
- *
112
- * @param {string} prompt - LLM prompt
113
- * @param {Object} options - Generation options
114
- * @returns {Promise<Object|string>} LLM response
115
- */
116
- async function generateWithFallback(prompt, options = {}) {
117
- const models = [
118
- DEFAULT_MODEL, // Tier 1: EXAONE 3.5 (2.4B)
119
- FALLBACK_MODEL, // Tier 2: Gemma 2B
120
- 'qwen:3b', // Tier 3: Qwen 3B
121
- ];
122
-
123
- for (let i = 0; i < models.length; i++) {
124
- const model = models[i];
125
-
126
- try {
127
- info(`[MAMA] Trying ${model}...`);
128
-
129
- const result = await generate(prompt, {
130
- ...options,
131
- model,
132
- });
133
-
134
- info(`[MAMA] ${model} succeeded`);
135
- return result;
136
- } catch (error) {
137
- console.warn(`[MAMA] ${model} failed: ${error.message}`);
138
-
139
- // Continue to next tier
140
- if (i === models.length - 1) {
141
- // All tiers failed
142
- throw new Error(`All LLM tiers failed. Last error: ${error.message}`);
143
- }
144
- }
145
- }
146
- }
147
-
148
- /**
149
- * Extract topic keywords from user message (fallback method)
150
- *
151
- * Task 2.8: Keyword-based fallback when all LLMs fail
152
- * Simple regex matching for common topics
153
- *
154
- * @param {string} userMessage - User's message
155
- * @returns {Object} Topic detection result
156
- */
157
- function extractTopicKeywords(userMessage) {
158
- const topicPatterns = {
159
- workflow_storage: /workflow|save|persist/i,
160
- mesh_structure: /mesh|structure/i,
161
- authentication: /auth|jwt|oauth|login/i,
162
- testing: /test|jest|spec/i,
163
- architecture: /architecture|design/i,
164
- coding_style: /style|format|coding/i,
165
- };
166
-
167
- for (const [topic, pattern] of Object.entries(topicPatterns)) {
168
- if (pattern.test(userMessage)) {
169
- return {
170
- involves_decision: true,
171
- topic,
172
- confidence: 0.5, // Lower confidence for keyword matching
173
- reasoning: 'Keyword-based detection (LLM fallback)',
174
- };
175
- }
176
- }
177
-
178
- return {
179
- involves_decision: false,
180
- topic: null,
181
- confidence: 0.0,
182
- reasoning: 'No topic keywords found',
183
- };
184
- }
185
-
186
- // Export API
187
- module.exports = {
188
- analyzeIntent,
189
- extractTopicKeywords,
190
- };
191
-
192
- // CLI execution for testing
193
- if (require.main === module) {
194
- info('🧠 MAMA Query Intent Analysis - Test\n');
195
-
196
- // Task 2.8: Test intent detection accuracy
197
- (async () => {
198
- const testQueries = [
199
- {
200
- message: 'Why did we choose COMPLEX mesh structure?',
201
- expected: { involves_decision: true, topic: 'mesh_structure' },
202
- },
203
- {
204
- message: 'Read the file please',
205
- expected: { involves_decision: false },
206
- },
207
- {
208
- message: 'We chose JWT for authentication, remember?',
209
- expected: { involves_decision: true, topic: 'authentication' },
210
- },
211
- ];
212
-
213
- for (const test of testQueries) {
214
- info(`📋 Testing: "${test.message}"`);
215
-
216
- try {
217
- const result = await analyzeIntent(test.message);
218
- info('✅ Result:', result);
219
-
220
- // Verify expectations
221
- if (result.involves_decision === test.expected.involves_decision) {
222
- info(' ✓ Decision detection matches');
223
- } else {
224
- info(' ✗ Decision detection MISMATCH');
225
- }
226
-
227
- info('');
228
- } catch (error) {
229
- logError(`❌ Error: ${error.message}\n`);
230
- }
231
- }
232
-
233
- info('═══════════════════════════');
234
- info('✅ Intent analysis tests complete');
235
- info('═══════════════════════════');
236
- })();
237
- }