@jungjaehoon/mama-server 1.2.0 → 1.2.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jungjaehoon/mama-server",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "MAMA MCP Server - Memory-Augmented MCP Assistant for Claude Code & Desktop",
5
5
  "main": "src/server.js",
6
6
  "bin": {
@@ -0,0 +1,43 @@
1
+ -- Migration: 008-fix-auto-link-defaults.sql
2
+ -- Description: Fix auto-generated links and update default values
3
+ -- Date: 2025-11-25
4
+ --
5
+ -- Problem: Migration 006 set incorrect defaults:
6
+ -- - created_by DEFAULT 'user' (should be 'llm' for auto-generated)
7
+ -- - approved_by_user DEFAULT 1 (should be 0 for pending approval)
8
+ --
9
+ -- This caused auto-generated links to be indistinguishable from user-approved links.
10
+ --
11
+ -- Solution:
12
+ -- 1. Identify auto-generated links by pattern matching reason field
13
+ -- 2. Mark them as created_by='llm', approved_by_user=0
14
+ -- 3. Note: Cannot change column defaults in SQLite, so application code must handle this
15
+
16
+ -- Step 1: Mark auto-generated links (identifiable by pattern in reason field)
17
+ -- Pattern: "Refines previous approach (similarity: X.XX)" or similar auto-generated reasons
18
+ UPDATE decision_edges
19
+ SET
20
+ created_by = 'llm',
21
+ approved_by_user = 0
22
+ WHERE reason LIKE '%similarity:%'
23
+ OR reason LIKE '%Refines previous approach%'
24
+ OR reason LIKE '%auto%'
25
+ OR reason LIKE '%Auto%';
26
+
27
+ -- Step 2: Log the migration action
28
+ INSERT INTO link_audit_log (from_id, to_id, relationship, action, actor, reason, created_at)
29
+ SELECT
30
+ from_id,
31
+ to_id,
32
+ relationship,
33
+ 'deprecated',
34
+ 'system',
35
+ 'Migration 008: Marked as auto-generated link requiring approval',
36
+ unixepoch()
37
+ FROM decision_edges
38
+ WHERE created_by = 'llm' AND approved_by_user = 0;
39
+
40
+ -- Note: After this migration:
41
+ -- - Auto-generated links will require explicit approval via approve_link tool
42
+ -- - Users can review pending links with get_pending_links tool
43
+ -- - New auto-generated links should set created_by='llm', approved_by_user=0
@@ -0,0 +1,40 @@
1
+ -- Migration: 009-remove-auto-links.sql
2
+ -- Description: Remove all auto-generated links (noise cleanup)
3
+ -- Date: 2025-11-25
4
+ --
5
+ -- Context: 2025-11-25 architecture decision
6
+ -- - Analyzed 462 links: 366 refines (100% cross-topic noise), 56 supersedes (valid), 40 contradicts (mostly noise)
7
+ -- - Key insight: LLM can infer decision evolution from time-ordered search results
8
+ -- - Auto-link generation removed from decision-tracker.js
9
+ -- - MCP tools consolidated from 11 to 4 (save, search, update, load_checkpoint)
10
+ --
11
+ -- This migration removes:
12
+ -- 1. All 'refines' links (100% cross-topic noise)
13
+ -- 2. All 'contradicts' links (unreliable without LLM judgment)
14
+ -- 3. Keeps 'supersedes' links (same-topic, reliable)
15
+
16
+ -- Step 1: Backup to audit log before deletion
17
+ INSERT INTO link_audit_log (from_id, to_id, relationship, action, actor, reason, created_at)
18
+ SELECT
19
+ from_id,
20
+ to_id,
21
+ relationship,
22
+ 'deprecated',
23
+ 'system',
24
+ 'Migration 009: Removed auto-generated noise links (refines, contradicts)',
25
+ unixepoch()
26
+ FROM decision_edges
27
+ WHERE relationship IN ('refines', 'contradicts');
28
+
29
+ -- Step 2: Delete noise links
30
+ DELETE FROM decision_edges
31
+ WHERE relationship IN ('refines', 'contradicts');
32
+
33
+ -- Step 3: Verify remaining links are valid (supersedes only)
34
+ -- This is a sanity check - should only have supersedes links remaining
35
+ -- SELECT relationship, COUNT(*) FROM decision_edges GROUP BY relationship;
36
+
37
+ -- Note: After this migration:
38
+ -- - Only 'supersedes' edges remain (same-topic, created when decision replaces previous)
39
+ -- - LLM will infer refines/contradicts from search results
40
+ -- - No more auto-link generation in decision-tracker.js
@@ -87,9 +87,11 @@ async function createSupersedesEdge(fromId, toId, reason) {
87
87
  const adapter = getAdapter();
88
88
 
89
89
  try {
90
+ // Auto-generated links: created_by='llm', approved_by_user=0 (pending approval)
91
+ // This ensures they appear in get_pending_links and require explicit approval
90
92
  const stmt = adapter.prepare(`
91
- INSERT INTO decision_edges (from_id, to_id, relationship, reason, created_at)
92
- VALUES (?, ?, 'supersedes', ?, ?)
93
+ INSERT INTO decision_edges (from_id, to_id, relationship, reason, created_at, created_by, approved_by_user)
94
+ VALUES (?, ?, 'supersedes', ?, ?, 'llm', 0)
93
95
  `);
94
96
 
95
97
  await stmt.run(fromId, toId, reason, Date.now());
@@ -173,154 +175,22 @@ function detectRefinement(_detection, _sessionContext) {
173
175
  return null;
174
176
  }
175
177
 
176
- /**
177
- * Create refines edge (multi-parent relationship)
178
- *
179
- * Task 5.3: Implement refines edge creation
180
- * AC #5: Multi-parent refinement
181
- *
182
- * @param {string} fromId - New decision ID
183
- * @param {string} toId - Parent decision ID
184
- * @param {string} reason - Reason for refinement
185
- */
186
- async function createRefinesEdge(fromId, toId, reason) {
187
- const adapter = getAdapter();
188
-
189
- try {
190
- const stmt = adapter.prepare(`
191
- INSERT INTO decision_edges (from_id, to_id, relationship, reason, created_at)
192
- VALUES (?, ?, 'refines', ?, ?)
193
- `);
194
-
195
- await stmt.run(fromId, toId, reason, Date.now());
196
- } catch (error) {
197
- throw new Error(`Failed to create refines edge: ${error.message}`);
198
- }
199
- }
200
-
201
- /**
202
- * Detect conflicting decisions (same topic, different decision)
203
- *
204
- * Task 5.4: Detect conflicting decisions
205
- * AC #2, #5: Relationship types
206
- *
207
- * @param {string} topic - Decision topic
208
- * @param {string} newDecision - New decision value
209
- * @param {string} newId - New decision ID (to exclude from search)
210
- * @returns {Promise<Array<Object>>} Conflicting decisions
211
- */
212
- async function detectConflicts(topic, newDecision, newId) {
213
- const adapter = getAdapter();
214
-
215
- try {
216
- // Find active decisions on same topic with different decision value
217
- const stmt = adapter.prepare(`
218
- SELECT * FROM decisions
219
- WHERE topic = ?
220
- AND decision != ?
221
- AND id != ?
222
- AND superseded_by IS NULL
223
- AND outcome IS NULL
224
- ORDER BY created_at DESC
225
- `);
226
-
227
- const conflicts = await stmt.all(topic, newDecision, newId);
228
- return conflicts || [];
229
- } catch (error) {
230
- throw new Error(`Failed to detect conflicts: ${error.message}`);
231
- }
232
- }
233
-
234
- /**
235
- * Create contradicts edge (conflicting relationship)
236
- *
237
- * Task 5.5: Create contradicts edges for conflicts
238
- * AC #2, #5: Relationship types
239
- *
240
- * @param {string} fromId - New decision ID
241
- * @param {string} toId - Conflicting decision ID
242
- * @param {string} reason - Reason for contradiction
243
- */
244
- async function createContradictsEdge(fromId, toId, reason) {
245
- const adapter = getAdapter();
246
-
247
- try {
248
- const stmt = adapter.prepare(`
249
- INSERT INTO decision_edges (from_id, to_id, relationship, reason, created_at)
250
- VALUES (?, ?, 'contradicts', ?, ?)
251
- `);
252
-
253
- await stmt.run(fromId, toId, reason, Date.now());
254
- } catch (error) {
255
- throw new Error(`Failed to create contradicts edge: ${error.message}`);
256
- }
257
- }
258
-
259
- /**
260
- * Find semantically related decisions using vector search
261
- *
262
- * Story 014.14: AC #1 - Vector Search for Related Decisions
263
- *
264
- * @param {string} decisionId - New decision ID (exclude from search)
265
- * @param {string} topic - Decision topic
266
- * @param {string} decision - Decision text
267
- * @param {string} reasoning - Decision reasoning
268
- * @returns {Promise<Array<Object>>} Related decisions with similarity scores
269
- */
270
- async function findRelatedDecisions(decisionId, topic, decision, reasoning) {
271
- const { queryVectorSearch } = require('./memory-store');
272
-
273
- try {
274
- // Combine decision + reasoning for semantic search
275
- const searchText = `${decision}. ${reasoning}`;
276
-
277
- // Vector search params
278
- const params = {
279
- query: searchText,
280
- limit: 10, // Get more candidates for filtering
281
- threshold: 0.75, // Minimum similarity (Story 014.14: AC #1)
282
- timeWindow: 90 * 24 * 60 * 60 * 1000, // Last 90 days (Story 014.14: AC #1)
283
- };
284
-
285
- // Query vector database
286
- const results = await queryVectorSearch(params);
287
-
288
- // Filter out self and return top 5
289
- return results.filter((r) => r.id !== decisionId).slice(0, 5);
290
- } catch (error) {
291
- info(`[decision-tracker] Vector search failed, returning empty: ${error.message}`);
292
- return []; // Graceful degradation
293
- }
294
- }
295
-
296
- /**
297
- * Detect if reasoning contains conflict keywords
298
- *
299
- * Story 014.14: AC #3 - Contradicts Edge Detection
300
- *
301
- * @param {string} newReasoning - New decision reasoning
302
- * @param {string} oldReasoning - Previous decision reasoning
303
- * @returns {boolean} True if conflicting
304
- */
305
- function isConflicting(newReasoning, oldReasoning) {
306
- const conflictKeywords = [
307
- 'instead of',
308
- 'replace',
309
- 'not',
310
- 'contrary to',
311
- 'different from',
312
- 'opposite',
313
- 'revert',
314
- 'undo',
315
- 'abandon',
316
- 'deprecate',
317
- 'remove',
318
- ];
319
-
320
- const combined = `${newReasoning} ${oldReasoning}`.toLowerCase();
321
-
322
- return conflictKeywords.some((keyword) => combined.includes(keyword));
323
- }
178
+ // ════════════════════════════════════════════════════════════════════════════
179
+ // NOTE: Auto-link functions REMOVED in v1.2.0
180
+ //
181
+ // Removed functions:
182
+ // - createRefinesEdge
183
+ // - detectConflicts
184
+ // - createContradictsEdge
185
+ // - findRelatedDecisions
186
+ // - isConflicting
187
+ //
188
+ // Reason: LLM can infer decision evolution from time-ordered search results.
189
+ // Auto-links created 366 noise edges (100% cross-topic).
190
+ // Only supersedes (same topic) is reliable.
191
+ //
192
+ // See: CHANGELOG.md v1.2.0 - 2025-11-25
193
+ // ════════════════════════════════════════════════════════════════════════════
324
194
 
325
195
  /**
326
196
  * Learn Decision Function (Main API)
@@ -447,62 +317,14 @@ async function learnDecision(detection, toolExecution, sessionContext) {
447
317
  }
448
318
 
449
319
  // ════════════════════════════════════════════════════════
450
- // Story 014.14: Semantic Similarity Edge Detection
451
- // ════════════════════════════════════════════════════════
452
- const relatedDecisions = await findRelatedDecisions(
453
- decisionId,
454
- detection.topic,
455
- detection.decision,
456
- detection.reasoning
457
- );
458
-
459
- for (const related of relatedDecisions) {
460
- const similarity = related.similarity;
461
-
462
- // Skip if already has supersedes relationship
463
- if (related.superseded_by || related.id === previous?.id) {
464
- continue;
465
- }
466
-
467
- // High similarity → refines edge (Story 014.14: AC #2)
468
- if (similarity > 0.85) {
469
- const reason = `Refines previous approach (similarity: ${similarity.toFixed(2)})`;
470
- await createRefinesEdge(decisionId, related.id, reason);
471
- }
472
-
473
- // Medium similarity + conflict keywords → contradicts edge (Story 014.14: AC #3)
474
- else if (similarity > 0.75 && isConflicting(detection.reasoning, related.reasoning)) {
475
- const reason = `Contradicts previous approach (similarity: ${similarity.toFixed(2)})`;
476
- await createContradictsEdge(decisionId, related.id, reason);
477
- }
478
- }
479
-
480
- // ════════════════════════════════════════════════════════
481
- // Task 5.3: Create Refines Edges (if multi-parent refinement)
482
- // ════════════════════════════════════════════════════════
483
- if (refinedFrom && refinedFrom.length > 0) {
484
- // AC #5: Multi-parent refinement
485
- await Promise.all(
486
- refinedFrom.map(async (parentId) => {
487
- const reason = `Refined decision from multiple parents`;
488
- await createRefinesEdge(decisionId, parentId, reason);
489
- })
490
- );
491
- }
492
-
493
- // ════════════════════════════════════════════════════════
494
- // Task 5.4, 5.5: Detect and Create Contradicts Edges
320
+ // NOTE: Auto-link generation (refines, contradicts) REMOVED
321
+ //
322
+ // Reason: LLM can infer decision evolution from time-ordered
323
+ // search results. Auto-links created 366 noise edges (100%
324
+ // cross-topic). Only supersedes (same topic) is reliable.
325
+ //
326
+ // See: 2025-11-25 discussion on decision tracking algorithm
495
327
  // ════════════════════════════════════════════════════════
496
- const conflicts = await detectConflicts(detection.topic, detection.decision, decisionId);
497
- if (conflicts.length > 0) {
498
- // AC #2, #5: Conflicting decisions
499
- await Promise.all(
500
- conflicts.map(async (conflict) => {
501
- const reason = `Conflicting decision: "${conflict.decision}" vs "${detection.decision}"`;
502
- await createContradictsEdge(decisionId, conflict.id, reason);
503
- })
504
- );
505
- }
506
328
 
507
329
  // ════════════════════════════════════════════════════════
508
330
  // Story 014.7.6: Generate notification if needs validation
@@ -562,6 +384,9 @@ function updateConfidence(prior, evidence) {
562
384
  }
563
385
 
564
386
  // Export API
387
+ // NOTE: Auto-link functions (createRefinesEdge, createContradictsEdge,
388
+ // findRelatedDecisions, isConflicting, detectConflicts) removed from exports.
389
+ // LLM infers relationships from search results instead.
565
390
  module.exports = {
566
391
  learnDecision,
567
392
  generateDecisionId,
@@ -571,9 +396,4 @@ module.exports = {
571
396
  calculateCombinedConfidence,
572
397
  detectRefinement,
573
398
  updateConfidence,
574
- createRefinesEdge,
575
- detectConflicts,
576
- createContradictsEdge,
577
- findRelatedDecisions, // Story 014.14: AC #1
578
- isConflicting, // Story 014.14: AC #3
579
399
  };
@@ -905,6 +905,36 @@ async function loadCheckpoint() {
905
905
  }
906
906
  }
907
907
 
908
+ /**
909
+ * List recent checkpoints (New Feature: Session Continuity)
910
+ *
911
+ * @param {number} limit - Max number of checkpoints to return
912
+ * @returns {Promise<Array>} Recent checkpoints
913
+ */
914
+ async function listCheckpoints(limit = 10) {
915
+ try {
916
+ const adapter = getAdapter();
917
+ const stmt = adapter.prepare(`
918
+ SELECT * FROM checkpoints
919
+ ORDER BY timestamp DESC
920
+ LIMIT ?
921
+ `);
922
+
923
+ const checkpoints = stmt.all(limit);
924
+
925
+ return checkpoints.map((c) => {
926
+ try {
927
+ c.open_files = JSON.parse(c.open_files);
928
+ } catch (e) {
929
+ c.open_files = [];
930
+ }
931
+ return c;
932
+ });
933
+ } catch (error) {
934
+ throw new Error(`Failed to list checkpoints: ${error.message}`);
935
+ }
936
+ }
937
+
908
938
  /**
909
939
  * Propose a new link between decisions (Epic 3 - Story 3.1)
910
940
  *
@@ -2307,35 +2337,42 @@ function formatQualityReportMarkdown(report) {
2307
2337
  * 3. Claude-First Design - Claude decides what to save
2308
2338
  * 4. Non-Intrusive - Silent failures for helpers (suggest)
2309
2339
  */
2340
+ // ════════════════════════════════════════════════════════════════════════════
2341
+ // MAMA API - Simplified to 4 MCP tools (2025-11-25)
2342
+ //
2343
+ // Design: LLM can infer decision evolution from time-ordered search results
2344
+ // More tools = more constraints = less LLM flexibility
2345
+ //
2346
+ // Retained internal functions for future use, but MCP exposes only:
2347
+ // save, search, update, load_checkpoint
2348
+ // ════════════════════════════════════════════════════════════════════════════
2310
2349
  const mama = {
2350
+ // Core functions (used by 4 MCP tools)
2311
2351
  save,
2312
- recall,
2313
- updateOutcome,
2314
2352
  suggest,
2315
2353
  list: listDecisions,
2354
+ listCheckpoints,
2355
+ updateOutcome,
2316
2356
  saveCheckpoint,
2317
2357
  loadCheckpoint,
2318
- // Epic 3: Link Collaboration & Governance
2358
+ // Legacy functions (retained for internal use, not exposed via MCP)
2359
+ recall,
2319
2360
  proposeLink,
2320
2361
  approveLink,
2321
2362
  rejectLink,
2322
2363
  getPendingLinks,
2323
2364
  deprecateAutoLinks,
2324
- // Epic 4: Quality Metrics & Observability
2325
2365
  calculateCoverage,
2326
2366
  calculateQuality,
2327
2367
  generateQualityReport,
2328
- // Epic 4: Restart Metrics (Story 4.2)
2329
2368
  logRestartAttempt,
2330
2369
  calculateRestartSuccessRate,
2331
2370
  calculateRestartLatency,
2332
2371
  getRestartMetrics,
2333
- // Epic 5: Migration & Cleanup (Story 5.1)
2334
2372
  scanAutoLinks,
2335
2373
  createLinkBackup,
2336
2374
  generatePreCleanupReport,
2337
2375
  restoreLinkBackup,
2338
- // Epic 5: Migration & Cleanup (Story 5.2)
2339
2376
  verifyBackupExists,
2340
2377
  deleteAutoLinks,
2341
2378
  validateCleanupResult,
package/src/server.js CHANGED
@@ -27,30 +27,10 @@ const {
27
27
  ListToolsRequestSchema,
28
28
  } = require('@modelcontextprotocol/sdk/types.js');
29
29
 
30
- // Import MAMA tools
31
- const { saveDecisionTool } = require('./tools/save-decision.js');
32
- const { recallDecisionTool } = require('./tools/recall-decision.js');
33
- const { suggestDecisionTool } = require('./tools/suggest-decision.js');
34
- const { listDecisionsTool } = require('./tools/list-decisions.js');
35
- const { updateOutcomeTool } = require('./tools/update-outcome.js');
36
- const { saveCheckpointTool, loadCheckpointTool } = require('./tools/checkpoint-tools.js');
37
- const {
38
- proposeLinkTool,
39
- approveLinkTool,
40
- rejectLinkTool,
41
- getPendingLinksTool,
42
- deprecateAutoLinksTool,
43
- scanAutoLinksTool,
44
- createLinkBackupTool,
45
- generateCleanupReportTool,
46
- restoreLinkBackupTool,
47
- executeLinkCleanupTool,
48
- validateCleanupResultTool,
49
- } = require('./tools/link-tools.js');
50
- const {
51
- generateQualityReportTool,
52
- getRestartMetricsTool,
53
- } = require('./tools/quality-metrics-tools.js');
30
+ // Import MAMA tools - Simplified to 4 core tools (2025-11-25 refactor)
31
+ // Rationale: LLM can infer relationships from search results, fewer tools = more flexibility
32
+ const { loadCheckpointTool } = require('./tools/checkpoint-tools.js');
33
+ const mama = require('./mama/mama-api.js');
54
34
 
55
35
  // Import core modules
56
36
  const { initDB } = require('./mama/db-manager.js');
@@ -162,227 +142,129 @@ class MAMAServer {
162
142
  }
163
143
 
164
144
  setupHandlers() {
165
- // List available tools
145
+ // List available tools - Simplified to 4 core tools (2025-11-25)
146
+ // Design principle: LLM infers relationships from search results
147
+ // Fewer tools = more flexibility, less constraint
166
148
  this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
167
149
  tools: [
150
+ // 1. SAVE - Unified save for decisions and checkpoints
168
151
  {
169
- name: 'save_decision',
170
- description: "Save a decision or insight to MAMA's memory for future reference.",
152
+ name: 'save',
153
+ description:
154
+ "Save a decision or checkpoint to MAMA's memory. type='decision': architectural choices, lessons learned (same topic = newer supersedes older, tracking evolution). type='checkpoint': session state to resume later.",
171
155
  inputSchema: {
172
156
  type: 'object',
173
157
  properties: {
158
+ type: {
159
+ type: 'string',
160
+ enum: ['decision', 'checkpoint'],
161
+ description: "What to save: 'decision' or 'checkpoint'",
162
+ },
163
+ // Decision fields
174
164
  topic: {
175
165
  type: 'string',
176
166
  description:
177
- "Decision topic identifier (e.g., 'auth_strategy'). Use lowercase with underscores.",
167
+ "[Decision] Topic identifier (e.g., 'auth_strategy'). Same topic = new decision supersedes previous, creating evolution chain.",
178
168
  },
179
169
  decision: {
180
170
  type: 'string',
181
- description: "The decision made (e.g., 'Use JWT with refresh tokens').",
171
+ description: "[Decision] The decision made (e.g., 'Use JWT with refresh tokens').",
182
172
  },
183
173
  reasoning: {
184
174
  type: 'string',
185
175
  description:
186
- 'Why this decision was made. REQUIRED - explain the context and rationale.',
176
+ '[Decision] Why this decision was made. Include 5-layer narrative: (1) Context - what problem/situation; (2) Evidence - what proves this works (tests, benchmarks, prior experience); (3) Alternatives - what other options were considered and why rejected; (4) Risks - known limitations or failure modes; (5) Rationale - final reasoning for this choice.',
187
177
  },
188
178
  confidence: {
189
179
  type: 'number',
190
- description: 'Confidence score 0.0-1.0. Default: 0.5',
180
+ description: '[Decision] Confidence 0.0-1.0. Default: 0.5',
191
181
  minimum: 0,
192
182
  maximum: 1,
193
183
  },
194
- type: {
184
+ // Checkpoint fields
185
+ summary: {
195
186
  type: 'string',
196
- enum: ['user_decision', 'assistant_insight'],
197
- description: "Decision type. Default: 'user_decision'",
187
+ description:
188
+ '[Checkpoint] Session state summary. Use 4-section format: (1) 🎯 Goal & Progress - what was the goal, where did you stop; (2) ✅ Evidence - mark each item as Verified/Not run/Assumed with proof; (3) ⏳ Unfinished & Risks - incomplete work, blockers, unknowns; (4) 🚦 Next Agent Briefing - Definition of Done, quick health checks to run first.',
198
189
  },
199
- outcome: {
190
+ next_steps: {
200
191
  type: 'string',
201
- enum: ['pending', 'success', 'failure', 'partial', 'superseded'],
202
- description: "Outcome status. Default: 'pending'",
192
+ description:
193
+ '[Checkpoint] Instructions for next session: DoD (Definition of Done), quick verification commands (npm test, curl health), constraints/cautions.',
203
194
  },
204
- },
205
- required: ['topic', 'decision', 'reasoning'],
206
- },
207
- },
208
- {
209
- name: 'recall_decision',
210
- description: 'Recall full decision history for a specific topic.',
211
- inputSchema: {
212
- type: 'object',
213
- properties: {
214
- topic: {
215
- type: 'string',
216
- description: 'Decision topic to recall',
195
+ open_files: {
196
+ type: 'array',
197
+ items: { type: 'string' },
198
+ description: '[Checkpoint] Currently relevant files.',
217
199
  },
218
200
  },
219
- required: ['topic'],
201
+ required: ['type'],
220
202
  },
221
203
  },
204
+ // 2. SEARCH - Unified search across decisions and checkpoints
222
205
  {
223
- name: 'suggest_decision',
224
- description: 'Auto-suggest relevant past decisions based on user question.',
206
+ name: 'search',
207
+ description:
208
+ 'Search decisions and checkpoints. With query: semantic search. Without query: returns recent items by time.',
225
209
  inputSchema: {
226
210
  type: 'object',
227
211
  properties: {
228
- userQuestion: {
212
+ query: {
229
213
  type: 'string',
230
- description: "User's question or intent",
214
+ description:
215
+ 'Search query (optional). If empty, returns recent items sorted by time.',
231
216
  },
232
- recencyWeight: {
233
- type: 'number',
234
- description: 'Weight for recency (0-1). Default: 0.3',
235
- minimum: 0,
236
- maximum: 1,
217
+ type: {
218
+ type: 'string',
219
+ enum: ['all', 'decision', 'checkpoint'],
220
+ description: "Filter by type. Default: 'all'",
237
221
  },
238
- },
239
- required: ['userQuestion'],
240
- },
241
- },
242
- {
243
- name: 'list_decisions',
244
- description: 'List recent decisions with optional limit',
245
- inputSchema: {
246
- type: 'object',
247
- properties: {
248
222
  limit: {
249
223
  type: 'number',
250
- description: 'Maximum number of decisions (default: 10)',
224
+ description: 'Maximum results. Default: 10',
251
225
  },
252
226
  },
253
227
  },
254
228
  },
229
+ // 3. UPDATE - Update decision outcome
255
230
  {
256
- name: 'update_outcome',
257
- description: 'Update the outcome status of an existing decision',
231
+ name: 'update',
232
+ description:
233
+ 'Update an existing decision outcome (success/failure/partial). Use after trying a decision to track what worked.',
258
234
  inputSchema: {
259
235
  type: 'object',
260
236
  properties: {
261
- decisionId: {
237
+ id: {
262
238
  type: 'string',
263
- description: "Decision ID to update (e.g., 'decision_auth_strategy_123456_abc').",
239
+ description: 'Decision ID to update.',
264
240
  },
265
241
  outcome: {
266
242
  type: 'string',
267
- enum: ['SUCCESS', 'FAILED', 'PARTIAL'],
268
- description: 'New outcome status',
269
- },
270
- failure_reason: {
271
- type: 'string',
272
- description: "Why the decision failed (REQUIRED if outcome='FAILED').",
243
+ enum: ['success', 'failure', 'partial'],
244
+ description: 'New outcome status.',
273
245
  },
274
- limitation: {
246
+ reason: {
275
247
  type: 'string',
276
- description: "What limitations were discovered (OPTIONAL for outcome='PARTIAL').",
248
+ description: 'Why it succeeded/failed/was partial.',
277
249
  },
278
250
  },
279
- required: ['decisionId', 'outcome'],
280
- },
281
- },
282
- {
283
- name: 'save_checkpoint',
284
- description:
285
- 'Save the current session state (checkpoint). Format: 1) Goal & Progress (honest about unfinished), 2) Evidence with status [Verified/Not run/Assumed], 3) Unfinished & Risks, 4) Next Agent briefing (Definition of Done + quick health-check commands).',
286
- inputSchema: {
287
- type: 'object',
288
- properties: {
289
- summary: {
290
- type: 'string',
291
- description:
292
- 'Summary of the current session state, what was accomplished, and what is pending.',
293
- },
294
- open_files: {
295
- type: 'array',
296
- items: { type: 'string' },
297
- description: 'List of currently relevant or open files.',
298
- },
299
- next_steps: {
300
- type: 'string',
301
- description: 'Clear instructions for the next session on what to do next.',
302
- },
303
- },
304
- required: ['summary'],
251
+ required: ['id', 'outcome'],
305
252
  },
306
253
  },
254
+ // 4. LOAD_CHECKPOINT - Resume previous session
307
255
  {
308
256
  name: 'load_checkpoint',
309
257
  description:
310
- 'Load the latest active session checkpoint. Use this at the start of a new session to resume work seamlessly.',
258
+ 'Load the latest checkpoint to resume a previous session. Use at session start.',
311
259
  inputSchema: {
312
260
  type: 'object',
313
261
  properties: {},
314
262
  },
315
263
  },
316
- // Epic 3: Link Collaboration & Governance
317
- {
318
- name: 'propose_link',
319
- description: proposeLinkTool.description,
320
- inputSchema: proposeLinkTool.inputSchema,
321
- },
322
- {
323
- name: 'approve_link',
324
- description: approveLinkTool.description,
325
- inputSchema: approveLinkTool.inputSchema,
326
- },
327
- {
328
- name: 'reject_link',
329
- description: rejectLinkTool.description,
330
- inputSchema: rejectLinkTool.inputSchema,
331
- },
332
- {
333
- name: 'get_pending_links',
334
- description: getPendingLinksTool.description,
335
- inputSchema: getPendingLinksTool.inputSchema,
336
- },
337
- {
338
- name: 'deprecate_auto_links',
339
- description: deprecateAutoLinksTool.description,
340
- inputSchema: deprecateAutoLinksTool.inputSchema,
341
- },
342
- {
343
- name: 'scan_auto_links',
344
- description: scanAutoLinksTool.description,
345
- inputSchema: scanAutoLinksTool.inputSchema,
346
- },
347
- {
348
- name: 'create_link_backup',
349
- description: createLinkBackupTool.description,
350
- inputSchema: createLinkBackupTool.inputSchema,
351
- },
352
- {
353
- name: 'generate_cleanup_report',
354
- description: generateCleanupReportTool.description,
355
- inputSchema: generateCleanupReportTool.inputSchema,
356
- },
357
- {
358
- name: 'restore_link_backup',
359
- description: restoreLinkBackupTool.description,
360
- inputSchema: restoreLinkBackupTool.inputSchema,
361
- },
362
- {
363
- name: 'generate_quality_report',
364
- description: generateQualityReportTool.description,
365
- inputSchema: generateQualityReportTool.inputSchema,
366
- },
367
- {
368
- name: 'get_restart_metrics',
369
- description: getRestartMetricsTool.description,
370
- inputSchema: getRestartMetricsTool.inputSchema,
371
- },
372
- {
373
- name: 'execute_link_cleanup',
374
- description: executeLinkCleanupTool.description,
375
- inputSchema: executeLinkCleanupTool.inputSchema,
376
- },
377
- {
378
- name: 'validate_cleanup_result',
379
- description: validateCleanupResultTool.description,
380
- inputSchema: validateCleanupResultTool.inputSchema,
381
- },
382
264
  ],
383
265
  }));
384
266
 
385
- // Handle tool execution
267
+ // Handle tool execution - 4 core tools only
386
268
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
387
269
  const { name, arguments: args } = request.params;
388
270
 
@@ -390,66 +272,18 @@ class MAMAServer {
390
272
  let result;
391
273
 
392
274
  switch (name) {
393
- case 'save_decision':
394
- result = await saveDecisionTool.handler(args);
395
- break;
396
- case 'recall_decision':
397
- result = await recallDecisionTool.handler(args);
275
+ case 'save':
276
+ result = await this.handleSave(args);
398
277
  break;
399
- case 'suggest_decision':
400
- result = await suggestDecisionTool.handler(args);
278
+ case 'search':
279
+ result = await this.handleSearch(args);
401
280
  break;
402
- case 'list_decisions':
403
- result = await listDecisionsTool.handler(args);
404
- break;
405
- case 'update_outcome':
406
- result = await updateOutcomeTool.handler(args);
407
- break;
408
- case 'save_checkpoint':
409
- result = await saveCheckpointTool.handler(args);
281
+ case 'update':
282
+ result = await this.handleUpdate(args);
410
283
  break;
411
284
  case 'load_checkpoint':
412
285
  result = await loadCheckpointTool.handler(args);
413
286
  break;
414
- case 'propose_link':
415
- result = await proposeLinkTool.handler(args);
416
- break;
417
- case 'approve_link':
418
- result = await approveLinkTool.handler(args);
419
- break;
420
- case 'reject_link':
421
- result = await rejectLinkTool.handler(args);
422
- break;
423
- case 'get_pending_links':
424
- result = await getPendingLinksTool.handler(args);
425
- break;
426
- case 'deprecate_auto_links':
427
- result = await deprecateAutoLinksTool.handler(args);
428
- break;
429
- case 'scan_auto_links':
430
- result = await scanAutoLinksTool.handler(args);
431
- break;
432
- case 'create_link_backup':
433
- result = await createLinkBackupTool.handler(args);
434
- break;
435
- case 'generate_cleanup_report':
436
- result = await generateCleanupReportTool.handler(args);
437
- break;
438
- case 'restore_link_backup':
439
- result = await restoreLinkBackupTool.handler(args);
440
- break;
441
- case 'generate_quality_report':
442
- result = await generateQualityReportTool.handler(args);
443
- break;
444
- case 'get_restart_metrics':
445
- result = await getRestartMetricsTool.handler(args);
446
- break;
447
- case 'execute_link_cleanup':
448
- result = await executeLinkCleanupTool.handler(args);
449
- break;
450
- case 'validate_cleanup_result':
451
- result = await validateCleanupResultTool.handler(args);
452
- break;
453
287
  default:
454
288
  throw new Error(`Unknown tool: ${name}`);
455
289
  }
@@ -477,6 +311,114 @@ class MAMAServer {
477
311
  });
478
312
  }
479
313
 
314
+ /**
315
+ * Handle unified save (decision or checkpoint)
316
+ */
317
+ async handleSave(args) {
318
+ const { type } = args;
319
+
320
+ if (type === 'decision') {
321
+ const { topic, decision, reasoning, confidence = 0.5 } = args;
322
+ if (!topic || !decision || !reasoning) {
323
+ return { success: false, message: '❌ Decision requires: topic, decision, reasoning' };
324
+ }
325
+ const id = await mama.save({ topic, decision, reasoning, confidence });
326
+ return {
327
+ success: true,
328
+ id,
329
+ type: 'decision',
330
+ message: `✅ Decision saved: ${topic}`,
331
+ };
332
+ }
333
+
334
+ if (type === 'checkpoint') {
335
+ const { summary, next_steps, open_files } = args;
336
+ if (!summary) {
337
+ return { success: false, message: '❌ Checkpoint requires: summary' };
338
+ }
339
+ const id = await mama.saveCheckpoint({ summary, next_steps, open_files });
340
+ return {
341
+ success: true,
342
+ id,
343
+ type: 'checkpoint',
344
+ message: '✅ Checkpoint saved',
345
+ };
346
+ }
347
+
348
+ return { success: false, message: "❌ type must be 'decision' or 'checkpoint'" };
349
+ }
350
+
351
+ /**
352
+ * Handle unified search (decisions + checkpoints)
353
+ */
354
+ async handleSearch(args) {
355
+ const { query, type = 'all', limit = 10 } = args;
356
+
357
+ const results = [];
358
+
359
+ // Search decisions
360
+ if (type === 'all' || type === 'decision') {
361
+ let decisions;
362
+ if (query) {
363
+ // suggest() returns { results: [...] } object or null
364
+ const suggestResult = await mama.suggest(query, limit);
365
+ decisions = suggestResult?.results || [];
366
+ } else {
367
+ decisions = await mama.list(limit);
368
+ }
369
+ // Ensure decisions is an array
370
+ if (Array.isArray(decisions)) {
371
+ results.push(
372
+ ...decisions.map((d) => ({
373
+ ...d,
374
+ _type: 'decision',
375
+ }))
376
+ );
377
+ }
378
+ }
379
+
380
+ // Search checkpoints
381
+ if (type === 'all' || type === 'checkpoint') {
382
+ const checkpoints = await mama.listCheckpoints(limit);
383
+ results.push(
384
+ ...checkpoints.map((c) => ({
385
+ id: `checkpoint_${c.id}`,
386
+ summary: c.summary,
387
+ next_steps: c.next_steps,
388
+ created_at: c.timestamp,
389
+ _type: 'checkpoint',
390
+ }))
391
+ );
392
+ }
393
+
394
+ // Sort by time (newest first) and limit
395
+ results.sort((a, b) => (b.created_at || 0) - (a.created_at || 0));
396
+ const limited = results.slice(0, limit);
397
+
398
+ return {
399
+ success: true,
400
+ count: limited.length,
401
+ results: limited,
402
+ };
403
+ }
404
+
405
+ /**
406
+ * Handle update (decision outcome)
407
+ */
408
+ async handleUpdate(args) {
409
+ const { id, outcome, reason } = args;
410
+
411
+ if (!id || !outcome) {
412
+ return { success: false, message: '❌ Update requires: id, outcome' };
413
+ }
414
+
415
+ await mama.updateOutcome(id, outcome.toUpperCase(), reason);
416
+ return {
417
+ success: true,
418
+ message: `✅ Updated ${id} → ${outcome}`,
419
+ };
420
+ }
421
+
480
422
  async start() {
481
423
  try {
482
424
  setupLogging();