@jungjaehoon/mama-server 1.11.1 → 1.12.1
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 +1 -1
- package/src/server.js +175 -23
package/package.json
CHANGED
package/src/server.js
CHANGED
|
@@ -198,19 +198,69 @@ class MAMAServer {
|
|
|
198
198
|
? `${this.getLegacyMigrationNotice()}\n\n`
|
|
199
199
|
: '';
|
|
200
200
|
|
|
201
|
-
const
|
|
201
|
+
const tools = [
|
|
202
|
+
// 1. SAVE — decisions, checkpoints, conversation ingestion
|
|
202
203
|
{
|
|
203
204
|
name: 'save',
|
|
204
|
-
description: `${legacyNotice}
|
|
205
|
+
description: `${legacyNotice}Save to MAMA memory. Use type parameter to choose what to save.
|
|
206
|
+
|
|
207
|
+
**type='decision'** — Save architectural decisions, lessons learned, insights.
|
|
208
|
+
Required: topic, decision, reasoning. Optional: confidence, scopes, event_date.
|
|
209
|
+
Triggers: user says "기억해", "remember", "decided". Reuse same topic to create evolution chain.
|
|
210
|
+
|
|
211
|
+
**type='checkpoint'** — Save session state for resumption.
|
|
212
|
+
Required: summary (4-section: Goal, Evidence, Unfinished, Next Briefing).
|
|
213
|
+
Optional: next_steps, open_files. Triggers: session ending, "체크포인트", "save progress".
|
|
214
|
+
|
|
215
|
+
**type='ingest'** — Import conversation messages into memory with optional extraction.
|
|
216
|
+
Required: messages (array of {role, content}). Optional: scopes, session_date, extract.
|
|
217
|
+
|
|
218
|
+
**Scopes**: Isolate memories per project/channel. Example: [{"kind":"project","id":"/my/app"}]
|
|
219
|
+
**event_date**: ISO 8601 date when event occurred (e.g. "2024-01-15"), not when saved.`,
|
|
205
220
|
inputSchema: {
|
|
206
221
|
type: 'object',
|
|
207
222
|
properties: {
|
|
208
|
-
...memoryTools.save_decision.inputSchema.properties,
|
|
209
223
|
type: {
|
|
210
224
|
type: 'string',
|
|
211
|
-
enum: ['decision', 'checkpoint'],
|
|
212
|
-
description: "What to save: 'decision' or '
|
|
225
|
+
enum: ['decision', 'checkpoint', 'ingest'],
|
|
226
|
+
description: "What to save: 'decision', 'checkpoint', or 'ingest'",
|
|
227
|
+
},
|
|
228
|
+
// Decision fields
|
|
229
|
+
topic: {
|
|
230
|
+
type: 'string',
|
|
231
|
+
description: '[Decision] Topic identifier. Reuse same topic = supersedes previous.',
|
|
232
|
+
},
|
|
233
|
+
decision: {
|
|
234
|
+
type: 'string',
|
|
235
|
+
description: '[Decision] The decision made.',
|
|
236
|
+
},
|
|
237
|
+
reasoning: {
|
|
238
|
+
type: 'string',
|
|
239
|
+
description: "[Decision] Why. End with 'builds_on: <id>' or 'debates: <id>' to link.",
|
|
240
|
+
},
|
|
241
|
+
confidence: {
|
|
242
|
+
type: 'number',
|
|
243
|
+
description: '[Decision] 0.0-1.0. Default: 0.5',
|
|
244
|
+
minimum: 0,
|
|
245
|
+
maximum: 1,
|
|
246
|
+
},
|
|
247
|
+
scopes: {
|
|
248
|
+
type: 'array',
|
|
249
|
+
items: {
|
|
250
|
+
type: 'object',
|
|
251
|
+
properties: {
|
|
252
|
+
kind: { type: 'string', enum: ['global', 'user', 'channel', 'project'] },
|
|
253
|
+
id: { type: 'string' },
|
|
254
|
+
},
|
|
255
|
+
required: ['kind', 'id'],
|
|
256
|
+
},
|
|
257
|
+
description: 'Memory scopes for isolation.',
|
|
258
|
+
},
|
|
259
|
+
event_date: {
|
|
260
|
+
type: 'string',
|
|
261
|
+
description: 'ISO 8601 date when event occurred (e.g. "2024-01-15").',
|
|
213
262
|
},
|
|
263
|
+
// Checkpoint fields
|
|
214
264
|
summary: {
|
|
215
265
|
type: 'string',
|
|
216
266
|
description: '[Checkpoint] Session state summary.',
|
|
@@ -224,45 +274,123 @@ class MAMAServer {
|
|
|
224
274
|
items: { type: 'string' },
|
|
225
275
|
description: '[Checkpoint] Currently relevant files.',
|
|
226
276
|
},
|
|
277
|
+
// Ingest fields
|
|
278
|
+
messages: {
|
|
279
|
+
type: 'array',
|
|
280
|
+
items: {
|
|
281
|
+
type: 'object',
|
|
282
|
+
properties: {
|
|
283
|
+
role: { type: 'string', enum: ['user', 'assistant', 'system'] },
|
|
284
|
+
content: { type: 'string' },
|
|
285
|
+
},
|
|
286
|
+
required: ['role', 'content'],
|
|
287
|
+
},
|
|
288
|
+
description: '[Ingest] Conversation messages to import.',
|
|
289
|
+
},
|
|
290
|
+
session_date: {
|
|
291
|
+
type: 'string',
|
|
292
|
+
description: '[Ingest] ISO 8601 date when conversation occurred.',
|
|
293
|
+
},
|
|
294
|
+
extract: {
|
|
295
|
+
type: 'boolean',
|
|
296
|
+
description: '[Ingest] Extract structured memories via LLM. Default: false',
|
|
297
|
+
},
|
|
227
298
|
},
|
|
228
299
|
required: ['type'],
|
|
229
300
|
},
|
|
230
301
|
},
|
|
302
|
+
// 2. SEARCH — unified search across decisions, checkpoints, load latest checkpoint
|
|
231
303
|
{
|
|
232
304
|
name: 'search',
|
|
233
|
-
description:
|
|
305
|
+
description: `Search MAMA memory. Returns results ranked by semantic similarity.
|
|
306
|
+
|
|
307
|
+
**With query** — Semantic search across decisions and checkpoints. Cross-lingual (Korean + English).
|
|
308
|
+
Triggers: "뭐였더라", "what did we decide", making architectural choices, debugging.
|
|
309
|
+
|
|
310
|
+
**Without query** — List recent items sorted by time.
|
|
311
|
+
|
|
312
|
+
**Resume session**: type='checkpoint' without query → loads latest checkpoint with full context (narrative, links, next steps).
|
|
313
|
+
Triggers: "이어서", "continue", "where were we", session start.
|
|
314
|
+
|
|
315
|
+
**type parameter**: 'decision' (choices/lessons only), 'checkpoint' (session states / resume), 'all' (both, default).
|
|
316
|
+
**scopes**: Filter by project/channel. Omit for global search.
|
|
317
|
+
**limit**: Max results (default: 10).
|
|
318
|
+
|
|
319
|
+
⚠️ REQUIRED: Call search BEFORE save to find related decisions and avoid orphans.`,
|
|
234
320
|
inputSchema: {
|
|
235
321
|
type: 'object',
|
|
236
322
|
properties: {
|
|
237
323
|
query: {
|
|
238
324
|
type: 'string',
|
|
239
|
-
description: 'Search query.
|
|
325
|
+
description: 'Search query. Omit to list recent items.',
|
|
240
326
|
},
|
|
241
327
|
type: {
|
|
242
328
|
type: 'string',
|
|
243
329
|
enum: ['all', 'decision', 'checkpoint'],
|
|
244
330
|
description: "Filter by type. Default: 'all'",
|
|
245
331
|
},
|
|
246
|
-
limit: { type: 'number', description: '
|
|
247
|
-
scopes:
|
|
332
|
+
limit: { type: 'number', description: 'Max results. Default: 10' },
|
|
333
|
+
scopes: {
|
|
334
|
+
type: 'array',
|
|
335
|
+
items: {
|
|
336
|
+
type: 'object',
|
|
337
|
+
properties: {
|
|
338
|
+
kind: { type: 'string', enum: ['global', 'user', 'channel', 'project'] },
|
|
339
|
+
id: { type: 'string' },
|
|
340
|
+
},
|
|
341
|
+
required: ['kind', 'id'],
|
|
342
|
+
},
|
|
343
|
+
description: 'Filter by scope.',
|
|
344
|
+
},
|
|
248
345
|
},
|
|
249
346
|
},
|
|
250
347
|
},
|
|
348
|
+
// 3. UPDATE — decision outcome tracking
|
|
251
349
|
{
|
|
252
350
|
name: 'update',
|
|
253
|
-
description:
|
|
254
|
-
|
|
351
|
+
description: `Update decision outcome after real-world validation.
|
|
352
|
+
|
|
353
|
+
Triggers: "이거 안됐어", "this worked", days later when issues discovered.
|
|
354
|
+
outcome: 'success', 'failed', 'partial' (case-insensitive).
|
|
355
|
+
After failure → save a NEW decision with same topic to create evolution history.`,
|
|
356
|
+
inputSchema: {
|
|
357
|
+
type: 'object',
|
|
358
|
+
properties: {
|
|
359
|
+
id: { type: 'string', description: 'Decision ID to update.' },
|
|
360
|
+
outcome: {
|
|
361
|
+
type: 'string',
|
|
362
|
+
description: "'success', 'failed', or 'partial' (case-insensitive).",
|
|
363
|
+
},
|
|
364
|
+
reason: {
|
|
365
|
+
type: 'string',
|
|
366
|
+
description: 'Why it succeeded/failed/was partial. Include evidence.',
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
required: ['id', 'outcome'],
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
// 4. SEARCH_DECISIONS_AND_CONTRACTS — PreToolUse hook RPC
|
|
373
|
+
{
|
|
374
|
+
name: 'search_decisions_and_contracts',
|
|
375
|
+
description: 'Search decisions and contracts for PreToolUse hook injection.',
|
|
376
|
+
inputSchema: {
|
|
377
|
+
type: 'object',
|
|
378
|
+
properties: {
|
|
379
|
+
query: { type: 'string', description: 'Search query for decisions.' },
|
|
380
|
+
filePath: { type: 'string', description: 'File path context.' },
|
|
381
|
+
toolName: { type: 'string', description: 'Tool name (Edit/Write/apply_patch).' },
|
|
382
|
+
decisionLimit: { type: 'number', description: 'Max decisions (default: 5).' },
|
|
383
|
+
contractLimit: { type: 'number', description: 'Max contracts (default: 3).' },
|
|
384
|
+
similarityThreshold: {
|
|
385
|
+
type: 'number',
|
|
386
|
+
description: 'Similarity threshold (default: 0.7).',
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
},
|
|
255
390
|
},
|
|
256
391
|
];
|
|
257
392
|
|
|
258
|
-
|
|
259
|
-
const v2Tools = Object.values(memoryTools)
|
|
260
|
-
.filter((t) => t.name && t.inputSchema)
|
|
261
|
-
.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema }));
|
|
262
|
-
|
|
263
|
-
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
264
|
-
tools: [...legacyTools, ...v2Tools],
|
|
265
|
-
}));
|
|
393
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
266
394
|
|
|
267
395
|
// Handle tool execution — legacy wrappers + v2 tools from src/tools/
|
|
268
396
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -377,7 +505,11 @@ class MAMAServer {
|
|
|
377
505
|
};
|
|
378
506
|
}
|
|
379
507
|
|
|
380
|
-
|
|
508
|
+
if (type === 'ingest') {
|
|
509
|
+
return await memoryTools.ingest_conversation.handler(args);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return { success: false, message: "❌ type must be 'decision', 'checkpoint', or 'ingest'" };
|
|
381
513
|
}
|
|
382
514
|
|
|
383
515
|
/**
|
|
@@ -386,6 +518,11 @@ class MAMAServer {
|
|
|
386
518
|
async handleSearch(args) {
|
|
387
519
|
const { query, type = 'all', limit = 10, scopes } = args;
|
|
388
520
|
|
|
521
|
+
// type='checkpoint' without query → load latest checkpoint (resume session)
|
|
522
|
+
if (type === 'checkpoint' && !query) {
|
|
523
|
+
return await memoryTools.load_checkpoint.handler(args);
|
|
524
|
+
}
|
|
525
|
+
|
|
389
526
|
const results = [];
|
|
390
527
|
|
|
391
528
|
// Search decisions
|
|
@@ -400,7 +537,6 @@ class MAMAServer {
|
|
|
400
537
|
} else {
|
|
401
538
|
decisions = await mama.list({ limit, ...(scopes && { scopes }) });
|
|
402
539
|
}
|
|
403
|
-
// Ensure decisions is an array
|
|
404
540
|
if (Array.isArray(decisions)) {
|
|
405
541
|
results.push(
|
|
406
542
|
...decisions.map((d) => ({
|
|
@@ -411,8 +547,24 @@ class MAMAServer {
|
|
|
411
547
|
}
|
|
412
548
|
}
|
|
413
549
|
|
|
414
|
-
// Search checkpoints
|
|
415
|
-
if (type === 'all' || type === 'checkpoint') {
|
|
550
|
+
// Search checkpoints (with query = search, without = handled above as load)
|
|
551
|
+
if ((type === 'all' || type === 'checkpoint') && query) {
|
|
552
|
+
const checkpoints = await mama.listCheckpoints(limit);
|
|
553
|
+
results.push(
|
|
554
|
+
...checkpoints
|
|
555
|
+
.filter((c) => c.summary && c.summary.toLowerCase().includes(query.toLowerCase()))
|
|
556
|
+
.map((c) => ({
|
|
557
|
+
id: `checkpoint_${c.id}`,
|
|
558
|
+
summary: c.summary,
|
|
559
|
+
next_steps: c.next_steps,
|
|
560
|
+
created_at: c.timestamp,
|
|
561
|
+
_type: 'checkpoint',
|
|
562
|
+
}))
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// type='all' without query — include recent checkpoints
|
|
567
|
+
if (type === 'all' && !query) {
|
|
416
568
|
const checkpoints = await mama.listCheckpoints(limit);
|
|
417
569
|
results.push(
|
|
418
570
|
...checkpoints.map((c) => ({
|