memory-pulse-mcp-server 0.1.13 → 0.1.15

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/dist/index.js CHANGED
@@ -27,11 +27,11 @@ var MemoryType;
27
27
  MemoryType2["SESSION"] = "session";
28
28
  })(MemoryType || (MemoryType = {}));
29
29
  var SearchStrategy;
30
- (function(SearchStrategy2) {
31
- SearchStrategy2["EXACT"] = "exact";
32
- SearchStrategy2["FULLTEXT"] = "fulltext";
33
- SearchStrategy2["SEMANTIC"] = "semantic";
34
- SearchStrategy2["AUTO"] = "auto";
30
+ (function(SearchStrategy3) {
31
+ SearchStrategy3["EXACT"] = "exact";
32
+ SearchStrategy3["FULLTEXT"] = "fulltext";
33
+ SearchStrategy3["SEMANTIC"] = "semantic";
34
+ SearchStrategy3["AUTO"] = "auto";
35
35
  })(SearchStrategy || (SearchStrategy = {}));
36
36
 
37
37
  // ../core/dist/features.js
@@ -270,6 +270,425 @@ function deserializeVector(s) {
270
270
  return Array.from(buffer);
271
271
  }
272
272
 
273
+ // ../core/dist/keywords.js
274
+ var CHINESE_STOP_WORDS = /* @__PURE__ */ new Set([
275
+ "\u7684",
276
+ "\u662F",
277
+ "\u5728",
278
+ "\u4E86",
279
+ "\u548C",
280
+ "\u4E0E",
281
+ "\u6216",
282
+ "\u6709",
283
+ "\u8FD9",
284
+ "\u90A3",
285
+ "\u6211",
286
+ "\u4F60",
287
+ "\u4ED6",
288
+ "\u5979",
289
+ "\u5B83",
290
+ "\u4EEC",
291
+ "\u4E0D",
292
+ "\u4E5F",
293
+ "\u5C31",
294
+ "\u90FD",
295
+ "\u4E3A",
296
+ "\u88AB",
297
+ "\u7740",
298
+ "\u8BA9",
299
+ "\u628A",
300
+ "\u7ED9",
301
+ "\u4ECE",
302
+ "\u5230",
303
+ "\u5BF9",
304
+ "\u4E8E",
305
+ "\u4F46",
306
+ "\u800C",
307
+ "\u53CA",
308
+ "\u8FD8",
309
+ "\u4EE5",
310
+ "\u6240",
311
+ "\u5982",
312
+ "\u5219",
313
+ "\u7B49",
314
+ "\u8BE5",
315
+ "\u8FD9\u4E2A",
316
+ "\u90A3\u4E2A",
317
+ "\u4E00\u4E2A",
318
+ "\u4EC0\u4E48",
319
+ "\u600E\u4E48",
320
+ "\u5982\u4F55",
321
+ "\u53EF\u4EE5",
322
+ "\u9700\u8981",
323
+ "\u4F7F\u7528",
324
+ "\u8FDB\u884C",
325
+ "\u901A\u8FC7",
326
+ "\u5DF2\u7ECF",
327
+ "\u7136\u540E",
328
+ "\u56E0\u4E3A",
329
+ "\u6240\u4EE5",
330
+ "\u5982\u679C",
331
+ "\u867D\u7136",
332
+ "\u4F46\u662F",
333
+ "\u800C\u4E14",
334
+ "\u6216\u8005",
335
+ "\u4EE5\u53CA",
336
+ "\u5173\u4E8E",
337
+ "\u5176\u4E2D",
338
+ "\u4E4B\u540E",
339
+ "\u4E4B\u524D",
340
+ "\u76EE\u524D"
341
+ ]);
342
+ var ENGLISH_STOP_WORDS = /* @__PURE__ */ new Set([
343
+ "the",
344
+ "a",
345
+ "an",
346
+ "is",
347
+ "are",
348
+ "was",
349
+ "were",
350
+ "be",
351
+ "been",
352
+ "being",
353
+ "have",
354
+ "has",
355
+ "had",
356
+ "do",
357
+ "does",
358
+ "did",
359
+ "will",
360
+ "would",
361
+ "could",
362
+ "should",
363
+ "may",
364
+ "might",
365
+ "must",
366
+ "shall",
367
+ "can",
368
+ "need",
369
+ "dare",
370
+ "ought",
371
+ "used",
372
+ "to",
373
+ "of",
374
+ "in",
375
+ "for",
376
+ "on",
377
+ "with",
378
+ "at",
379
+ "by",
380
+ "from",
381
+ "as",
382
+ "into",
383
+ "through",
384
+ "during",
385
+ "before",
386
+ "after",
387
+ "above",
388
+ "below",
389
+ "between",
390
+ "and",
391
+ "or",
392
+ "but",
393
+ "if",
394
+ "then",
395
+ "else",
396
+ "when",
397
+ "where",
398
+ "why",
399
+ "how",
400
+ "all",
401
+ "each",
402
+ "every",
403
+ "both",
404
+ "few",
405
+ "more",
406
+ "most",
407
+ "other",
408
+ "some",
409
+ "such",
410
+ "no",
411
+ "nor",
412
+ "not",
413
+ "only",
414
+ "own",
415
+ "same",
416
+ "so",
417
+ "than",
418
+ "too",
419
+ "very",
420
+ "just",
421
+ "also",
422
+ "now",
423
+ "here",
424
+ "there",
425
+ "this",
426
+ "that",
427
+ "these",
428
+ "those",
429
+ "i",
430
+ "you",
431
+ "he",
432
+ "she",
433
+ "it",
434
+ "we",
435
+ "they",
436
+ "me",
437
+ "him",
438
+ "her",
439
+ "us",
440
+ "them",
441
+ "my",
442
+ "your",
443
+ "his",
444
+ "its",
445
+ "our",
446
+ "their",
447
+ "mine",
448
+ "yours",
449
+ "hers",
450
+ "ours",
451
+ "theirs"
452
+ ]);
453
+ var STOP_WORDS = /* @__PURE__ */ new Set([...CHINESE_STOP_WORDS, ...ENGLISH_STOP_WORDS]);
454
+ var SPECIAL_PATTERNS = [
455
+ // API 路径: /api/users, /v1/auth/login
456
+ /\/[a-z][a-z0-9\-_\/]*/gi,
457
+ // HTTP 方法 + 路径: GET /api/users
458
+ /\b(GET|POST|PUT|DELETE|PATCH)\s+\/[a-z][a-z0-9\-_\/]*/gi,
459
+ // 驼峰命名: getUserById, handleSubmit
460
+ /\b[a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*\b/g,
461
+ // 帕斯卡命名: UserService, AuthController
462
+ /\b[A-Z][a-z]+(?:[A-Z][a-z]+)+\b/g,
463
+ // 常量命名: MAX_RETRY, API_KEY
464
+ /\b[A-Z][A-Z0-9_]{2,}\b/g,
465
+ // 版本号: 1.0.0, v2.3.1
466
+ /\bv?\d+\.\d+(?:\.\d+)?(?:-[a-z]+(?:\.\d+)?)?\b/gi,
467
+ // 端口号: :3000, :8080
468
+ /:\d{2,5}\b/g,
469
+ // 文件扩展名: .ts, .tsx, .vue
470
+ /\.[a-z]{2,4}\b/gi,
471
+ // 数据库/技术名词: PostgreSQL, MongoDB, Redis
472
+ /\b(?:PostgreSQL|MySQL|MongoDB|Redis|SQLite|Prisma|Docker|Kubernetes|K8s|Node\.js|React|Vue|Angular|Next\.js|Express|NestJS|GraphQL|REST|JWT|OAuth|WebSocket|CORS|HTTPS?|API|SDK|CLI|CI\/CD|AWS|GCP|Azure)\b/gi
473
+ ];
474
+ function isStopWord(word) {
475
+ return STOP_WORDS.has(word.toLowerCase());
476
+ }
477
+ function basicTokenize(text) {
478
+ const tokens = [];
479
+ const words = text.split(/[\s\n\r\t,,。.;;::!!??(())\[\]{}""''`<>《》【】、\-=+*\/\\|~@#$%^&]+/);
480
+ for (const word of words) {
481
+ if (word.length < 2)
482
+ continue;
483
+ if (/[\u4e00-\u9fa5]/.test(word)) {
484
+ const chineseChars = word.match(/[\u4e00-\u9fa5]+/g) || [];
485
+ for (const chars of chineseChars) {
486
+ if (chars.length >= 2 && chars.length <= 8) {
487
+ tokens.push(chars);
488
+ }
489
+ if (chars.length > 2) {
490
+ for (let i = 0; i < chars.length - 1; i++) {
491
+ tokens.push(chars.substring(i, i + 2));
492
+ }
493
+ }
494
+ }
495
+ const englishParts = word.match(/[a-zA-Z0-9_]+/g) || [];
496
+ tokens.push(...englishParts.filter((p) => p.length >= 2));
497
+ } else {
498
+ tokens.push(word);
499
+ }
500
+ }
501
+ return tokens;
502
+ }
503
+ function extractSpecialPatterns(text) {
504
+ const keywords = [];
505
+ for (const pattern of SPECIAL_PATTERNS) {
506
+ const matches = text.match(pattern) || [];
507
+ keywords.push(...matches);
508
+ }
509
+ return keywords;
510
+ }
511
+ function extractTextFromObject(obj, depth = 0) {
512
+ if (depth > 5)
513
+ return "";
514
+ if (typeof obj === "string") {
515
+ return obj;
516
+ }
517
+ if (Array.isArray(obj)) {
518
+ return obj.map((item) => extractTextFromObject(item, depth + 1)).join(" ");
519
+ }
520
+ if (obj && typeof obj === "object") {
521
+ const texts = [];
522
+ for (const [key, value] of Object.entries(obj)) {
523
+ if (["id", "createdAt", "updatedAt", "timestamp", "version"].includes(key)) {
524
+ continue;
525
+ }
526
+ if (typeof key === "string" && key.length > 2) {
527
+ texts.push(key);
528
+ }
529
+ texts.push(extractTextFromObject(value, depth + 1));
530
+ }
531
+ return texts.join(" ");
532
+ }
533
+ return "";
534
+ }
535
+ function getTopKeywords(words, limit = 30) {
536
+ const frequency = /* @__PURE__ */ new Map();
537
+ for (const word of words) {
538
+ const normalized = word.toLowerCase();
539
+ if (isStopWord(normalized))
540
+ continue;
541
+ if (normalized.length < 2)
542
+ continue;
543
+ frequency.set(normalized, (frequency.get(normalized) || 0) + 1);
544
+ }
545
+ return Array.from(frequency.entries()).sort((a, b) => b[1] - a[1]).slice(0, limit).map(([word]) => word);
546
+ }
547
+ function extractKeywords(content, rawContext) {
548
+ const allKeywords = [];
549
+ const contentText = content || "";
550
+ allKeywords.push(...basicTokenize(contentText));
551
+ allKeywords.push(...extractSpecialPatterns(contentText));
552
+ if (rawContext) {
553
+ const contextText = extractTextFromObject(rawContext);
554
+ allKeywords.push(...basicTokenize(contextText));
555
+ allKeywords.push(...extractSpecialPatterns(contextText));
556
+ }
557
+ const topKeywords = getTopKeywords(allKeywords, 30);
558
+ const specialKeywords = extractSpecialPatterns(contentText + " " + extractTextFromObject(rawContext));
559
+ const uniqueSpecial = [...new Set(specialKeywords)].slice(0, 10);
560
+ const result = [.../* @__PURE__ */ new Set([...uniqueSpecial, ...topKeywords])];
561
+ return result.slice(0, 40);
562
+ }
563
+ function jaccardSimilarity(keywords1, keywords2) {
564
+ if (keywords1.length === 0 && keywords2.length === 0)
565
+ return 0;
566
+ const set1 = new Set(keywords1.map((k) => k.toLowerCase()));
567
+ const set2 = new Set(keywords2.map((k) => k.toLowerCase()));
568
+ const intersection = new Set([...set1].filter((x) => set2.has(x)));
569
+ const union = /* @__PURE__ */ new Set([...set1, ...set2]);
570
+ return intersection.size / union.size;
571
+ }
572
+ function getKeywordIntersection(keywords1, keywords2) {
573
+ const set1 = new Set(keywords1.map((k) => k.toLowerCase()));
574
+ const set2 = new Set(keywords2.map((k) => k.toLowerCase()));
575
+ return [...set1].filter((x) => set2.has(x));
576
+ }
577
+
578
+ // ../core/dist/auto-relation.js
579
+ var DEFAULT_CONFIG = {
580
+ threshold: 0.3,
581
+ maxRelations: 10,
582
+ crossProject: true,
583
+ timeWindowDays: 30
584
+ };
585
+ var TYPE_RELATION_MATRIX = {
586
+ decision: { solution: 0.8, config: 0.7, code: 0.6, error: 0.5, session: 0.4, decision: 0.9 },
587
+ solution: { error: 0.9, decision: 0.8, code: 0.7, config: 0.6, session: 0.4, solution: 0.8 },
588
+ config: { code: 0.8, decision: 0.7, error: 0.6, solution: 0.5, session: 0.4, config: 0.9 },
589
+ code: { config: 0.8, error: 0.7, solution: 0.6, decision: 0.5, session: 0.4, code: 0.8 },
590
+ error: { solution: 0.9, code: 0.7, config: 0.6, decision: 0.5, session: 0.4, error: 0.7 },
591
+ session: { decision: 0.6, solution: 0.5, config: 0.4, code: 0.4, error: 0.4, session: 0.5 }
592
+ };
593
+ function getTypeRelationScore(type1, type2) {
594
+ return TYPE_RELATION_MATRIX[type1]?.[type2] ?? 0.5;
595
+ }
596
+ function getTimeProximityScore(time1, time2) {
597
+ const date1 = new Date(time1);
598
+ const date2 = new Date(time2);
599
+ const diffHours = Math.abs(date1.getTime() - date2.getTime()) / (1e3 * 60 * 60);
600
+ if (diffHours <= 24)
601
+ return 1;
602
+ if (diffHours <= 72)
603
+ return 0.8;
604
+ if (diffHours <= 168)
605
+ return 0.6;
606
+ if (diffHours <= 720)
607
+ return 0.4;
608
+ return 0.2;
609
+ }
610
+ function calculateSimilarity(memory1, memory2) {
611
+ const reasons = [];
612
+ const keywords1 = memory1.searchable?.keywords || [];
613
+ const keywords2 = memory2.searchable?.keywords || [];
614
+ const keywordScore = jaccardSimilarity(keywords1, keywords2);
615
+ if (keywordScore > 0.2) {
616
+ const overlap = getKeywordIntersection(keywords1, keywords2);
617
+ if (overlap.length > 0) {
618
+ reasons.push(`\u5173\u952E\u8BCD\u91CD\u53E0: ${overlap.slice(0, 5).join(", ")}`);
619
+ }
620
+ }
621
+ const tags1 = memory1.meta?.tags || [];
622
+ const tags2 = memory2.meta?.tags || [];
623
+ const tagScore = jaccardSimilarity(tags1, tags2);
624
+ if (tagScore > 0) {
625
+ const tagOverlap = getKeywordIntersection(tags1, tags2);
626
+ if (tagOverlap.length > 0) {
627
+ reasons.push(`\u6807\u7B7E\u91CD\u53E0: ${tagOverlap.join(", ")}`);
628
+ }
629
+ }
630
+ const type1 = memory1.meta?.type || "code";
631
+ const type2 = memory2.meta?.type || "code";
632
+ const typeScore = getTypeRelationScore(type1, type2);
633
+ const timeScore = getTimeProximityScore(memory1.createdAt || memory1.meta?.timestamp || /* @__PURE__ */ new Date(), memory2.createdAt || memory2.meta?.timestamp || /* @__PURE__ */ new Date());
634
+ const score = keywordScore * 0.4 + tagScore * 0.3 + typeScore * 0.2 + timeScore * 0.1;
635
+ return {
636
+ score,
637
+ reason: reasons.length > 0 ? reasons.join("; ") : "\u7EFC\u5408\u76F8\u4F3C",
638
+ details: {
639
+ keywordScore,
640
+ tagScore,
641
+ typeScore,
642
+ timeScore
643
+ }
644
+ };
645
+ }
646
+ function findRelatedMemories(newMemory, candidates, config = {}) {
647
+ const cfg = { ...DEFAULT_CONFIG, ...config };
648
+ const results = [];
649
+ for (const candidate of candidates) {
650
+ if (candidate.meta?.id === newMemory.meta?.id)
651
+ continue;
652
+ const similarity = calculateSimilarity(newMemory, candidate);
653
+ if (similarity.score >= cfg.threshold) {
654
+ results.push({ memory: candidate, similarity });
655
+ }
656
+ }
657
+ return results.sort((a, b) => b.similarity.score - a.similarity.score).slice(0, cfg.maxRelations);
658
+ }
659
+ function generateAutoRelations(newMemoryId, relatedCandidates) {
660
+ return relatedCandidates.map((candidate) => ({
661
+ sourceId: newMemoryId,
662
+ targetId: candidate.memory.meta?.id || "",
663
+ type: "relatedTo",
664
+ confidence: candidate.similarity.score,
665
+ isAutoGenerated: true,
666
+ reason: candidate.similarity.reason
667
+ }));
668
+ }
669
+ function inferProjectGroup(projectId) {
670
+ const suffixPatterns = [
671
+ /-backend$/i,
672
+ /-frontend$/i,
673
+ /-api$/i,
674
+ /-web$/i,
675
+ /-app$/i,
676
+ /-admin$/i,
677
+ /-client$/i,
678
+ /-server$/i,
679
+ /-service$/i,
680
+ /-shared$/i,
681
+ /-common$/i,
682
+ /-core$/i
683
+ ];
684
+ for (const pattern of suffixPatterns) {
685
+ if (pattern.test(projectId)) {
686
+ return projectId.replace(pattern, "");
687
+ }
688
+ }
689
+ return null;
690
+ }
691
+
273
692
  // ../storage/dist/schema.js
274
693
  function migrateEmbeddingColumn(db) {
275
694
  const columns = db.pragma("table_info(memories)");
@@ -280,6 +699,40 @@ function migrateEmbeddingColumn(db) {
280
699
  console.log("\u2705 \u8FC1\u79FB\u5B8C\u6210");
281
700
  }
282
701
  }
702
+ function initProjectGroupsTable(db) {
703
+ db.exec(`
704
+ CREATE TABLE IF NOT EXISTS project_groups (
705
+ id TEXT PRIMARY KEY,
706
+ name TEXT NOT NULL UNIQUE,
707
+ projects TEXT NOT NULL DEFAULT '[]', -- JSON\u6570\u7EC4
708
+ createdAt TEXT NOT NULL,
709
+ updatedAt TEXT NOT NULL
710
+ );
711
+ `);
712
+ db.exec(`
713
+ CREATE INDEX IF NOT EXISTS idx_project_groups_name ON project_groups(name);
714
+ `);
715
+ }
716
+ function initMemoryRelationsTable(db) {
717
+ db.exec(`
718
+ CREATE TABLE IF NOT EXISTS memory_relations (
719
+ id TEXT PRIMARY KEY,
720
+ sourceId TEXT NOT NULL,
721
+ targetId TEXT NOT NULL,
722
+ type TEXT NOT NULL, -- replaces | relatedTo | impacts
723
+ confidence REAL DEFAULT 1.0,
724
+ isAutoGenerated INTEGER DEFAULT 0, -- 0=false, 1=true
725
+ reason TEXT,
726
+ createdAt TEXT NOT NULL,
727
+ UNIQUE(sourceId, targetId, type)
728
+ );
729
+ `);
730
+ db.exec(`
731
+ CREATE INDEX IF NOT EXISTS idx_memory_relations_source ON memory_relations(sourceId);
732
+ CREATE INDEX IF NOT EXISTS idx_memory_relations_target ON memory_relations(targetId);
733
+ CREATE INDEX IF NOT EXISTS idx_memory_relations_type ON memory_relations(type);
734
+ `);
735
+ }
283
736
  function initSchema(db) {
284
737
  db.exec(`
285
738
  CREATE TABLE IF NOT EXISTS memories (
@@ -370,6 +823,8 @@ function initSchema(db) {
370
823
  END;
371
824
  `);
372
825
  migrateEmbeddingColumn(db);
826
+ initProjectGroupsTable(db);
827
+ initMemoryRelationsTable(db);
373
828
  }
374
829
 
375
830
  // ../storage/dist/cache.js
@@ -864,8 +1319,17 @@ var SQLiteStorage = class {
864
1319
  params.push(filters.sessionId);
865
1320
  }
866
1321
  if (filters.query) {
867
- sql += " AND (summary LIKE ? OR fullText LIKE ?)";
868
- params.push(`%${filters.query}%`, `%${filters.query}%`);
1322
+ const keywords = filters.query.split(/\s+/).filter((k) => k.length > 0);
1323
+ if (keywords.length === 1) {
1324
+ sql += " AND (summary LIKE ? OR fullText LIKE ?)";
1325
+ params.push(`%${keywords[0]}%`, `%${keywords[0]}%`);
1326
+ } else {
1327
+ const conditions = keywords.map(() => "(summary LIKE ? OR fullText LIKE ?)").join(" OR ");
1328
+ sql += ` AND (${conditions})`;
1329
+ keywords.forEach((keyword) => {
1330
+ params.push(`%${keyword}%`, `%${keyword}%`);
1331
+ });
1332
+ }
869
1333
  }
870
1334
  sql += " ORDER BY timestamp DESC LIMIT ? OFFSET ?";
871
1335
  params.push(limit, offset);
@@ -1160,6 +1624,157 @@ var SQLiteStorage = class {
1160
1624
  recentCount: recentResult.count
1161
1625
  };
1162
1626
  }
1627
+ // ========== 项目组管理 ==========
1628
+ /**
1629
+ * 创建或更新项目组
1630
+ */
1631
+ async setProjectGroup(group) {
1632
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1633
+ const stmt = this.db.prepare(`
1634
+ INSERT INTO project_groups (id, name, projects, createdAt, updatedAt)
1635
+ VALUES (?, ?, ?, ?, ?)
1636
+ ON CONFLICT(name) DO UPDATE SET
1637
+ projects = excluded.projects,
1638
+ updatedAt = excluded.updatedAt
1639
+ `);
1640
+ stmt.run(`pg_${nanoid()}`, group.name, JSON.stringify(group.projects), now, now);
1641
+ return { success: true };
1642
+ }
1643
+ /**
1644
+ * 获取项目组
1645
+ */
1646
+ async getProjectGroup(name) {
1647
+ const stmt = this.db.prepare("SELECT * FROM project_groups WHERE name = ?");
1648
+ const row = stmt.get(name);
1649
+ if (!row)
1650
+ return null;
1651
+ return {
1652
+ name: row.name,
1653
+ projects: JSON.parse(row.projects)
1654
+ };
1655
+ }
1656
+ /**
1657
+ * 获取项目所属的项目组
1658
+ */
1659
+ async getProjectGroupByProject(projectId) {
1660
+ const stmt = this.db.prepare("SELECT * FROM project_groups");
1661
+ const rows = stmt.all();
1662
+ for (const row of rows) {
1663
+ const projects = JSON.parse(row.projects);
1664
+ if (projects.includes(projectId)) {
1665
+ return {
1666
+ name: row.name,
1667
+ projects
1668
+ };
1669
+ }
1670
+ }
1671
+ return null;
1672
+ }
1673
+ /**
1674
+ * 获取所有项目组
1675
+ */
1676
+ async getAllProjectGroups() {
1677
+ const stmt = this.db.prepare("SELECT * FROM project_groups");
1678
+ const rows = stmt.all();
1679
+ return rows.map((row) => ({
1680
+ name: row.name,
1681
+ projects: JSON.parse(row.projects)
1682
+ }));
1683
+ }
1684
+ /**
1685
+ * 删除项目组
1686
+ */
1687
+ async deleteProjectGroup(name) {
1688
+ const stmt = this.db.prepare("DELETE FROM project_groups WHERE name = ?");
1689
+ const result = stmt.run(name);
1690
+ return { success: result.changes > 0 };
1691
+ }
1692
+ // ========== 记忆关系管理 ==========
1693
+ /**
1694
+ * 批量创建记忆关系
1695
+ */
1696
+ async createRelations(relations) {
1697
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1698
+ const stmt = this.db.prepare(`
1699
+ INSERT OR IGNORE INTO memory_relations (id, sourceId, targetId, type, confidence, isAutoGenerated, reason, createdAt)
1700
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1701
+ `);
1702
+ let count = 0;
1703
+ for (const rel of relations) {
1704
+ const result = stmt.run(`rel_${nanoid()}`, rel.sourceId, rel.targetId, rel.type, rel.confidence, rel.isAutoGenerated ? 1 : 0, rel.reason || null, now);
1705
+ if (result.changes > 0)
1706
+ count++;
1707
+ }
1708
+ return { success: true, count };
1709
+ }
1710
+ /**
1711
+ * 获取记忆的所有关系
1712
+ */
1713
+ async getMemoryRelations(memoryId, options) {
1714
+ let sql = "SELECT * FROM memory_relations WHERE (sourceId = ? OR targetId = ?)";
1715
+ const params = [memoryId, memoryId];
1716
+ if (options?.includeAutoGenerated === false) {
1717
+ sql += " AND isAutoGenerated = 0";
1718
+ }
1719
+ if (options?.type) {
1720
+ sql += " AND type = ?";
1721
+ params.push(options.type);
1722
+ }
1723
+ sql += " ORDER BY createdAt DESC";
1724
+ const stmt = this.db.prepare(sql);
1725
+ const rows = stmt.all(...params);
1726
+ return rows.map((row) => ({
1727
+ sourceId: row.sourceId,
1728
+ targetId: row.targetId,
1729
+ type: row.type,
1730
+ confidence: row.confidence,
1731
+ isAutoGenerated: row.isAutoGenerated === 1,
1732
+ reason: row.reason
1733
+ }));
1734
+ }
1735
+ /**
1736
+ * 删除记忆关系
1737
+ */
1738
+ async deleteRelation(sourceId, targetId, type) {
1739
+ let sql = "DELETE FROM memory_relations WHERE sourceId = ? AND targetId = ?";
1740
+ const params = [sourceId, targetId];
1741
+ if (type) {
1742
+ sql += " AND type = ?";
1743
+ params.push(type);
1744
+ }
1745
+ const stmt = this.db.prepare(sql);
1746
+ const result = stmt.run(...params);
1747
+ return { success: result.changes > 0 };
1748
+ }
1749
+ /**
1750
+ * 搜索候选关联记忆(用于自动关联)
1751
+ */
1752
+ async searchCandidates(params) {
1753
+ if (params.keywords.length === 0 || params.projectIds.length === 0) {
1754
+ return [];
1755
+ }
1756
+ const limit = params.limit || 50;
1757
+ let sql = "SELECT * FROM memories WHERE 1=1";
1758
+ const sqlParams = [];
1759
+ const projectPlaceholders = params.projectIds.map(() => "?").join(", ");
1760
+ sql += ` AND projectId IN (${projectPlaceholders})`;
1761
+ sqlParams.push(...params.projectIds);
1762
+ if (params.excludeId) {
1763
+ sql += " AND id != ?";
1764
+ sqlParams.push(params.excludeId);
1765
+ }
1766
+ const keywordConditions = params.keywords.map(() => "(summary LIKE ? OR fullText LIKE ? OR keywords LIKE ?)").join(" OR ");
1767
+ sql += ` AND (${keywordConditions})`;
1768
+ params.keywords.forEach((keyword) => {
1769
+ const pattern = `%${keyword}%`;
1770
+ sqlParams.push(pattern, pattern, pattern);
1771
+ });
1772
+ sql += " ORDER BY timestamp DESC LIMIT ?";
1773
+ sqlParams.push(limit);
1774
+ const stmt = this.db.prepare(sql);
1775
+ const rows = stmt.all(...sqlParams);
1776
+ return rows.map(this.rowToMemory);
1777
+ }
1163
1778
  };
1164
1779
 
1165
1780
  // ../storage/dist/postgresql-storage.js
@@ -1414,10 +2029,18 @@ var PostgreSQLStorage = class {
1414
2029
  where.sessionId = filters.sessionId;
1415
2030
  }
1416
2031
  if (filters.query) {
1417
- where.OR = [
1418
- { summary: { contains: filters.query, mode: "insensitive" } },
1419
- { fullText: { contains: filters.query, mode: "insensitive" } }
1420
- ];
2032
+ const keywords = filters.query.split(/\s+/).filter((k) => k.length > 0);
2033
+ if (keywords.length === 1) {
2034
+ where.OR = [
2035
+ { summary: { contains: keywords[0], mode: "insensitive" } },
2036
+ { fullText: { contains: keywords[0], mode: "insensitive" } }
2037
+ ];
2038
+ } else {
2039
+ where.OR = keywords.flatMap((keyword) => [
2040
+ { summary: { contains: keyword, mode: "insensitive" } },
2041
+ { fullText: { contains: keyword, mode: "insensitive" } }
2042
+ ]);
2043
+ }
1421
2044
  }
1422
2045
  const [rows, total] = await Promise.all([
1423
2046
  this.prisma.memory.findMany({
@@ -1452,10 +2075,18 @@ var PostgreSQLStorage = class {
1452
2075
  where.sessionId = filters.sessionId;
1453
2076
  }
1454
2077
  if (filters.query) {
1455
- where.OR = [
1456
- { summary: { contains: filters.query, mode: "insensitive" } },
1457
- { fullText: { contains: filters.query, mode: "insensitive" } }
1458
- ];
2078
+ const keywords = filters.query.split(/\s+/).filter((k) => k.length > 0);
2079
+ if (keywords.length === 1) {
2080
+ where.OR = [
2081
+ { summary: { contains: keywords[0], mode: "insensitive" } },
2082
+ { fullText: { contains: keywords[0], mode: "insensitive" } }
2083
+ ];
2084
+ } else {
2085
+ where.OR = keywords.flatMap((keyword) => [
2086
+ { summary: { contains: keyword, mode: "insensitive" } },
2087
+ { fullText: { contains: keyword, mode: "insensitive" } }
2088
+ ]);
2089
+ }
1459
2090
  }
1460
2091
  const [rows, total] = await Promise.all([
1461
2092
  this.prisma.memory.findMany({
@@ -1727,6 +2358,170 @@ var PostgreSQLStorage = class {
1727
2358
  async close() {
1728
2359
  await this.prisma.$disconnect();
1729
2360
  }
2361
+ // ========== 项目组管理 ==========
2362
+ /**
2363
+ * 创建或更新项目组
2364
+ */
2365
+ async setProjectGroup(group) {
2366
+ await this.prisma.projectGroup.upsert({
2367
+ where: { name: group.name },
2368
+ update: { projects: group.projects },
2369
+ create: {
2370
+ name: group.name,
2371
+ projects: group.projects
2372
+ }
2373
+ });
2374
+ return { success: true };
2375
+ }
2376
+ /**
2377
+ * 获取项目组
2378
+ */
2379
+ async getProjectGroup(name) {
2380
+ const row = await this.prisma.projectGroup.findUnique({
2381
+ where: { name }
2382
+ });
2383
+ if (!row)
2384
+ return null;
2385
+ return {
2386
+ name: row.name,
2387
+ projects: row.projects
2388
+ };
2389
+ }
2390
+ /**
2391
+ * 获取项目所属的项目组
2392
+ */
2393
+ async getProjectGroupByProject(projectId) {
2394
+ const rows = await this.prisma.projectGroup.findMany();
2395
+ for (const row of rows) {
2396
+ if (row.projects.includes(projectId)) {
2397
+ return {
2398
+ name: row.name,
2399
+ projects: row.projects
2400
+ };
2401
+ }
2402
+ }
2403
+ return null;
2404
+ }
2405
+ /**
2406
+ * 获取所有项目组
2407
+ */
2408
+ async getAllProjectGroups() {
2409
+ const rows = await this.prisma.projectGroup.findMany();
2410
+ return rows.map((row) => ({
2411
+ name: row.name,
2412
+ projects: row.projects
2413
+ }));
2414
+ }
2415
+ /**
2416
+ * 删除项目组
2417
+ */
2418
+ async deleteProjectGroup(name) {
2419
+ try {
2420
+ await this.prisma.projectGroup.delete({
2421
+ where: { name }
2422
+ });
2423
+ return { success: true };
2424
+ } catch {
2425
+ return { success: false };
2426
+ }
2427
+ }
2428
+ // ========== 记忆关系管理 ==========
2429
+ /**
2430
+ * 批量创建记忆关系
2431
+ */
2432
+ async createRelations(relations) {
2433
+ let count = 0;
2434
+ for (const rel of relations) {
2435
+ try {
2436
+ await this.prisma.memoryRelation.create({
2437
+ data: {
2438
+ sourceId: rel.sourceId,
2439
+ targetId: rel.targetId,
2440
+ type: rel.type,
2441
+ confidence: rel.confidence,
2442
+ isAutoGenerated: rel.isAutoGenerated,
2443
+ reason: rel.reason || null
2444
+ }
2445
+ });
2446
+ count++;
2447
+ } catch {
2448
+ }
2449
+ }
2450
+ return { success: true, count };
2451
+ }
2452
+ /**
2453
+ * 获取记忆的所有关系
2454
+ */
2455
+ async getMemoryRelations(memoryId, options) {
2456
+ const where = {
2457
+ OR: [
2458
+ { sourceId: memoryId },
2459
+ { targetId: memoryId }
2460
+ ]
2461
+ };
2462
+ if (options?.includeAutoGenerated === false) {
2463
+ where.isAutoGenerated = false;
2464
+ }
2465
+ if (options?.type) {
2466
+ where.type = options.type;
2467
+ }
2468
+ const rows = await this.prisma.memoryRelation.findMany({
2469
+ where,
2470
+ orderBy: { createdAt: "desc" }
2471
+ });
2472
+ return rows.map((row) => ({
2473
+ sourceId: row.sourceId,
2474
+ targetId: row.targetId,
2475
+ type: row.type,
2476
+ confidence: row.confidence,
2477
+ isAutoGenerated: row.isAutoGenerated,
2478
+ reason: row.reason || ""
2479
+ }));
2480
+ }
2481
+ /**
2482
+ * 删除记忆关系
2483
+ */
2484
+ async deleteRelation(sourceId, targetId, type) {
2485
+ try {
2486
+ const where = {
2487
+ sourceId,
2488
+ targetId
2489
+ };
2490
+ if (type) {
2491
+ where.type = type;
2492
+ }
2493
+ await this.prisma.memoryRelation.deleteMany({ where });
2494
+ return { success: true };
2495
+ } catch {
2496
+ return { success: false };
2497
+ }
2498
+ }
2499
+ /**
2500
+ * 搜索候选关联记忆(用于自动关联)
2501
+ */
2502
+ async searchCandidates(params) {
2503
+ if (params.keywords.length === 0 || params.projectIds.length === 0) {
2504
+ return [];
2505
+ }
2506
+ const limit = params.limit || 50;
2507
+ const keywordConditions = params.keywords.flatMap((keyword) => [
2508
+ { summary: { contains: keyword, mode: "insensitive" } },
2509
+ { fullText: { contains: keyword, mode: "insensitive" } }
2510
+ ]);
2511
+ const where = {
2512
+ projectId: { in: params.projectIds },
2513
+ OR: keywordConditions
2514
+ };
2515
+ if (params.excludeId) {
2516
+ where.id = { not: params.excludeId };
2517
+ }
2518
+ const rows = await this.prisma.memory.findMany({
2519
+ where,
2520
+ orderBy: { timestamp: "desc" },
2521
+ take: limit
2522
+ });
2523
+ return rows.map(this.rowToMemory);
2524
+ }
1730
2525
  };
1731
2526
 
1732
2527
  // src/index.ts
@@ -1813,6 +2608,121 @@ function createStorage() {
1813
2608
  return new SQLiteStorage(dbPath);
1814
2609
  }
1815
2610
  var storage = createStorage();
2611
+ async function processAutoRelations(newMemoryId, projectId, content, rawContext) {
2612
+ try {
2613
+ const keywords = extractKeywords(content, rawContext);
2614
+ if (keywords.length === 0) {
2615
+ return { relationsCreated: 0 };
2616
+ }
2617
+ let projectIds = [projectId];
2618
+ if (storage.getProjectGroupByProject) {
2619
+ const group = await storage.getProjectGroupByProject(projectId);
2620
+ if (group) {
2621
+ projectIds = group.projects;
2622
+ } else {
2623
+ const inferredGroup = inferProjectGroup(projectId);
2624
+ if (inferredGroup && storage.getAllProjectGroups) {
2625
+ const allGroups = await storage.getAllProjectGroups();
2626
+ const sameGroupProjects = allGroups.filter((g) => g.projects.some((p) => inferProjectGroup(p) === inferredGroup)).flatMap((g) => g.projects);
2627
+ if (sameGroupProjects.length > 0) {
2628
+ projectIds = [.../* @__PURE__ */ new Set([projectId, ...sameGroupProjects])];
2629
+ }
2630
+ }
2631
+ }
2632
+ }
2633
+ if (!storage.searchCandidates) {
2634
+ return { relationsCreated: 0 };
2635
+ }
2636
+ const candidates = await storage.searchCandidates({
2637
+ keywords,
2638
+ projectIds,
2639
+ excludeId: newMemoryId,
2640
+ limit: 50
2641
+ // 搜索更多候选以提高关联质量
2642
+ });
2643
+ if (candidates.length === 0) {
2644
+ return { relationsCreated: 0 };
2645
+ }
2646
+ const newMemory = await storage.getById(newMemoryId);
2647
+ if (!newMemory) {
2648
+ return { relationsCreated: 0 };
2649
+ }
2650
+ const relatedCandidates = findRelatedMemories(newMemory, candidates, {
2651
+ threshold: 0.3,
2652
+ // 相似度阈值
2653
+ maxRelations: 10,
2654
+ // 最多关联 10 条
2655
+ crossProject: true
2656
+ });
2657
+ if (relatedCandidates.length === 0) {
2658
+ return { relationsCreated: 0 };
2659
+ }
2660
+ const relations = generateAutoRelations(newMemoryId, relatedCandidates);
2661
+ if (storage.createRelations && relations.length > 0) {
2662
+ const result = await storage.createRelations(relations);
2663
+ return { relationsCreated: result.count };
2664
+ }
2665
+ return { relationsCreated: 0 };
2666
+ } catch (error) {
2667
+ console.error("\u81EA\u52A8\u5173\u8054\u5904\u7406\u5931\u8D25:", error);
2668
+ return { relationsCreated: 0 };
2669
+ }
2670
+ }
2671
+ async function getProjectGroupProjectIds(projectId) {
2672
+ try {
2673
+ if (storage.getProjectGroupByProject) {
2674
+ const group = await storage.getProjectGroupByProject(projectId);
2675
+ if (group) {
2676
+ return group.projects;
2677
+ }
2678
+ }
2679
+ const inferredGroup = inferProjectGroup(projectId);
2680
+ if (inferredGroup && storage.getAllProjectGroups) {
2681
+ const allGroups = await storage.getAllProjectGroups();
2682
+ for (const group of allGroups) {
2683
+ if (group.projects.some((p) => inferProjectGroup(p) === inferredGroup)) {
2684
+ return group.projects;
2685
+ }
2686
+ }
2687
+ }
2688
+ return [projectId];
2689
+ } catch (error) {
2690
+ console.error("\u83B7\u53D6\u9879\u76EE\u7EC4\u5931\u8D25:", error);
2691
+ return [projectId];
2692
+ }
2693
+ }
2694
+ async function expandRelationChain(memories, depth = 1) {
2695
+ if (depth <= 0 || memories.length === 0) {
2696
+ return [];
2697
+ }
2698
+ try {
2699
+ const relatedMemories = [];
2700
+ const seenIds = new Set(memories.map((m) => m.meta?.id));
2701
+ for (const memory of memories) {
2702
+ const memoryId = memory.meta?.id;
2703
+ if (!memoryId) continue;
2704
+ if (storage.getMemoryRelations) {
2705
+ const relations = await storage.getMemoryRelations(memoryId, {
2706
+ includeAutoGenerated: true
2707
+ });
2708
+ for (const relation of relations) {
2709
+ const targetId = relation.targetId;
2710
+ if (!seenIds.has(targetId)) {
2711
+ seenIds.add(targetId);
2712
+ const relatedMemory = await storage.getById(targetId);
2713
+ if (relatedMemory) {
2714
+ relatedMemories.push(relatedMemory);
2715
+ }
2716
+ }
2717
+ }
2718
+ }
2719
+ }
2720
+ return relatedMemories;
2721
+ } catch (error) {
2722
+ console.error("\u6269\u5C55\u5173\u7CFB\u94FE\u5931\u8D25:", error);
2723
+ return [];
2724
+ }
2725
+ }
1816
2726
  var tools = [
1817
2727
  {
1818
2728
  name: "mpulse_store",
@@ -2006,13 +2916,19 @@ var tools = [
2006
2916
  3. \u67E5\u8BE2\u5173\u952E\u8BCD\uFF1A"\u9879\u76EE\u4E0A\u4E0B\u6587 \u67B6\u6784\u51B3\u7B56 \u672A\u5B8C\u6210\u4EFB\u52A1 \u914D\u7F6E\u4FE1\u606F"
2007
2917
  4. \u5206\u6790\u8FD4\u56DE\u7ED3\u679C\u540E\u518D\u6267\u884C\u5176\u4ED6\u64CD\u4F5C
2008
2918
 
2919
+ \u{1F517} \u667A\u80FD\u5173\u8054\u529F\u80FD\uFF1A
2920
+ - \u81EA\u52A8\u8DE8\u9879\u76EE\u641C\u7D22\uFF1A\u5982\u679C\u9879\u76EE\u5728\u540C\u4E00\u9879\u76EE\u7EC4\uFF08\u5982 frontend/backend\uFF09\uFF0C\u4F1A\u81EA\u52A8\u641C\u7D22\u6240\u6709\u5173\u8054\u9879\u76EE
2921
+ - \u5173\u7CFB\u94FE\u6269\u5C55\uFF1A\u9ED8\u8BA4\u8FD4\u56DE\u76F8\u5173\u8054\u7684\u8BB0\u5FC6\uFF0C\u8BBE\u7F6E expandRelations: true \u53EF\u83B7\u53D6\u66F4\u6DF1\u5C42\u5173\u8054
2922
+ - \u7ED3\u679C\u8BF4\u660E\uFF1Amemories \u662F\u76F4\u63A5\u5339\u914D\uFF0CrelatedMemories \u662F\u901A\u8FC7\u5173\u7CFB\u94FE\u6269\u5C55\u7684\u5173\u8054\u8BB0\u5FC6
2923
+
2009
2924
  \u8C03\u7528\u793A\u4F8B\uFF1A
2010
2925
  mpulse_recall({
2011
2926
  query: "\u9879\u76EE\u4E0A\u4E0B\u6587 \u67B6\u6784\u51B3\u7B56 \u672A\u5B8C\u6210\u4EFB\u52A1",
2012
- projectId: "\u4ECE\u5DE5\u4F5C\u76EE\u5F55\u63D0\u53D6\u7684\u9879\u76EE\u540D"
2927
+ projectId: "\u4ECE\u5DE5\u4F5C\u76EE\u5F55\u63D0\u53D6\u7684\u9879\u76EE\u540D",
2928
+ expandRelations: true
2013
2929
  })
2014
2930
 
2015
- \u8FD4\u56DE\u5185\u5BB9\uFF1A\u5386\u53F2\u51B3\u7B56\u3001\u5DF2\u89E3\u51B3\u95EE\u9898\u3001\u914D\u7F6E\u4FE1\u606F\u3001\u672A\u5B8C\u6210\u4EFB\u52A1\u7B49`,
2931
+ \u8FD4\u56DE\u5185\u5BB9\uFF1A\u5386\u53F2\u51B3\u7B56\u3001\u5DF2\u89E3\u51B3\u95EE\u9898\u3001\u914D\u7F6E\u4FE1\u606F\u3001\u672A\u5B8C\u6210\u4EFB\u52A1\u3001\u5173\u8054\u8BB0\u5FC6\u7B49`,
2016
2932
  inputSchema: {
2017
2933
  type: "object",
2018
2934
  properties: {
@@ -2022,7 +2938,7 @@ mpulse_recall({
2022
2938
  },
2023
2939
  projectId: {
2024
2940
  type: "string",
2025
- description: "\u9879\u76EE ID"
2941
+ description: "\u9879\u76EE ID\uFF08\u4F1A\u81EA\u52A8\u6269\u5C55\u5230\u9879\u76EE\u7EC4\u5185\u6240\u6709\u9879\u76EE\uFF09"
2026
2942
  },
2027
2943
  type: {
2028
2944
  type: "string",
@@ -2042,6 +2958,14 @@ mpulse_recall({
2042
2958
  limit: {
2043
2959
  type: "number",
2044
2960
  description: "\u8FD4\u56DE\u6570\u91CF"
2961
+ },
2962
+ expandRelations: {
2963
+ type: "boolean",
2964
+ description: "\u662F\u5426\u6269\u5C55\u5173\u7CFB\u94FE\uFF08\u9ED8\u8BA4 true\uFF09"
2965
+ },
2966
+ relationDepth: {
2967
+ type: "number",
2968
+ description: "\u5173\u7CFB\u94FE\u6269\u5C55\u6DF1\u5EA6\uFF08\u9ED8\u8BA4 1\uFF09"
2045
2969
  }
2046
2970
  },
2047
2971
  required: ["query"]
@@ -2096,6 +3020,50 @@ mpulse_recall({
2096
3020
  },
2097
3021
  required: ["memoryId"]
2098
3022
  }
3023
+ },
3024
+ // ========== 项目组管理工具 ==========
3025
+ {
3026
+ name: "mpulse_set_project_group",
3027
+ description: `\u8BBE\u7F6E\u9879\u76EE\u7EC4\uFF0C\u5C06\u76F8\u5173\u9879\u76EE\u5173\u8054\u5728\u4E00\u8D77\u3002
3028
+ \u5173\u8054\u540E\uFF0C\u68C0\u7D22\u65F6\u4F1A\u81EA\u52A8\u8DE8\u9879\u76EE\u641C\u7D22\uFF0C\u5B58\u50A8\u65F6\u4F1A\u81EA\u52A8\u5EFA\u7ACB\u8DE8\u9879\u76EE\u5173\u8054\u3002
3029
+ \u4F8B\u5982\uFF1A\u5C06 myapp-frontend \u548C myapp-backend \u5173\u8054\u4E3A\u4E00\u4E2A\u9879\u76EE\u7EC4\u3002`,
3030
+ inputSchema: {
3031
+ type: "object",
3032
+ properties: {
3033
+ name: {
3034
+ type: "string",
3035
+ description: "\u9879\u76EE\u7EC4\u540D\u79F0\uFF08\u5982 myapp\uFF09"
3036
+ },
3037
+ projects: {
3038
+ type: "array",
3039
+ items: { type: "string" },
3040
+ description: '\u9879\u76EE ID \u5217\u8868\uFF08\u5982 ["myapp-frontend", "myapp-backend", "myapp-api"]\uFF09'
3041
+ }
3042
+ },
3043
+ required: ["name", "projects"]
3044
+ }
3045
+ },
3046
+ {
3047
+ name: "mpulse_get_project_groups",
3048
+ description: "\u83B7\u53D6\u6240\u6709\u9879\u76EE\u7EC4\u914D\u7F6E",
3049
+ inputSchema: {
3050
+ type: "object",
3051
+ properties: {}
3052
+ }
3053
+ },
3054
+ {
3055
+ name: "mpulse_delete_project_group",
3056
+ description: "\u5220\u9664\u9879\u76EE\u7EC4",
3057
+ inputSchema: {
3058
+ type: "object",
3059
+ properties: {
3060
+ name: {
3061
+ type: "string",
3062
+ description: "\u9879\u76EE\u7EC4\u540D\u79F0"
3063
+ }
3064
+ },
3065
+ required: ["name"]
3066
+ }
2099
3067
  }
2100
3068
  ];
2101
3069
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
@@ -2419,80 +3387,141 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2419
3387
  try {
2420
3388
  switch (name) {
2421
3389
  case "mpulse_store": {
3390
+ const content = args.content;
3391
+ const rawContext = args.rawContext;
3392
+ const projectId = args.projectId;
2422
3393
  const result = await storage.store({
2423
- content: args.content,
2424
- rawContext: args.rawContext,
2425
- projectId: args.projectId,
3394
+ content,
3395
+ rawContext,
3396
+ projectId,
2426
3397
  type: args.type,
2427
3398
  tags: args.tags,
2428
3399
  sessionId: args.sessionId
2429
3400
  });
3401
+ let autoRelationResult = { relationsCreated: 0 };
3402
+ if (result.success && result.id) {
3403
+ autoRelationResult = await processAutoRelations(
3404
+ result.id,
3405
+ projectId,
3406
+ content,
3407
+ rawContext
3408
+ );
3409
+ }
2430
3410
  return {
2431
3411
  content: [
2432
3412
  {
2433
3413
  type: "text",
2434
- text: JSON.stringify(result, null, 2)
3414
+ text: JSON.stringify({
3415
+ ...result,
3416
+ autoRelations: autoRelationResult.relationsCreated
3417
+ }, null, 2)
2435
3418
  }
2436
3419
  ]
2437
3420
  };
2438
3421
  }
2439
3422
  case "mpulse_store_decision": {
3423
+ const projectId = args.projectId;
2440
3424
  const decisionParams = {
2441
3425
  question: args.question,
2442
3426
  options: args.options,
2443
3427
  chosen: args.chosen,
2444
3428
  reason: args.reason,
2445
- projectId: args.projectId,
3429
+ projectId,
2446
3430
  tags: args.tags,
2447
3431
  sessionId: args.sessionId
2448
3432
  };
2449
3433
  const result = await storage.storeDecision(decisionParams);
3434
+ let autoRelationResult = { relationsCreated: 0 };
3435
+ if (result.success && result.id) {
3436
+ const content = `${decisionParams.question} ${decisionParams.chosen} ${decisionParams.reason}`;
3437
+ autoRelationResult = await processAutoRelations(
3438
+ result.id,
3439
+ projectId,
3440
+ content,
3441
+ { options: decisionParams.options }
3442
+ );
3443
+ }
2450
3444
  return {
2451
3445
  content: [
2452
3446
  {
2453
3447
  type: "text",
2454
- text: JSON.stringify(result, null, 2)
3448
+ text: JSON.stringify({
3449
+ ...result,
3450
+ autoRelations: autoRelationResult.relationsCreated
3451
+ }, null, 2)
2455
3452
  }
2456
3453
  ]
2457
3454
  };
2458
3455
  }
2459
3456
  case "mpulse_store_solution": {
3457
+ const projectId = args.projectId;
2460
3458
  const solutionParams = {
2461
3459
  problem: args.problem,
2462
3460
  rootCause: args.rootCause,
2463
3461
  solution: args.solution,
2464
3462
  prevention: args.prevention,
2465
3463
  relatedIssues: args.relatedIssues,
2466
- projectId: args.projectId,
3464
+ projectId,
2467
3465
  tags: args.tags,
2468
3466
  sessionId: args.sessionId,
2469
3467
  artifacts: args.artifacts
2470
3468
  };
2471
3469
  const result = await storage.storeSolution(solutionParams);
3470
+ let autoRelationResult = { relationsCreated: 0 };
3471
+ if (result.success && result.id) {
3472
+ const content = `${solutionParams.problem} ${solutionParams.rootCause} ${solutionParams.solution}`;
3473
+ autoRelationResult = await processAutoRelations(
3474
+ result.id,
3475
+ projectId,
3476
+ content,
3477
+ { artifacts: solutionParams.artifacts }
3478
+ );
3479
+ }
2472
3480
  return {
2473
3481
  content: [
2474
3482
  {
2475
3483
  type: "text",
2476
- text: JSON.stringify(result, null, 2)
3484
+ text: JSON.stringify({
3485
+ ...result,
3486
+ autoRelations: autoRelationResult.relationsCreated
3487
+ }, null, 2)
2477
3488
  }
2478
3489
  ]
2479
3490
  };
2480
3491
  }
2481
3492
  case "mpulse_store_session": {
3493
+ const projectId = args.projectId;
2482
3494
  const sessionParams = {
2483
3495
  summary: args.summary,
2484
3496
  decisions: args.decisions,
2485
3497
  unfinishedTasks: args.unfinishedTasks,
2486
3498
  nextSteps: args.nextSteps,
2487
- projectId: args.projectId,
3499
+ projectId,
2488
3500
  sessionId: args.sessionId
2489
3501
  };
2490
3502
  const result = await storage.storeSession(sessionParams);
3503
+ let autoRelationResult = { relationsCreated: 0 };
3504
+ if (result.success && result.id) {
3505
+ const decisionsText = (sessionParams.decisions || []).join(" ");
3506
+ const content = `${sessionParams.summary} ${decisionsText}`;
3507
+ autoRelationResult = await processAutoRelations(
3508
+ result.id,
3509
+ projectId,
3510
+ content,
3511
+ {
3512
+ unfinishedTasks: sessionParams.unfinishedTasks,
3513
+ nextSteps: sessionParams.nextSteps
3514
+ }
3515
+ );
3516
+ }
2491
3517
  return {
2492
3518
  content: [
2493
3519
  {
2494
3520
  type: "text",
2495
- text: JSON.stringify(result, null, 2)
3521
+ text: JSON.stringify({
3522
+ ...result,
3523
+ autoRelations: autoRelationResult.relationsCreated
3524
+ }, null, 2)
2496
3525
  }
2497
3526
  ]
2498
3527
  };
@@ -2500,20 +3529,38 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2500
3529
  case "mpulse_recall": {
2501
3530
  const query = args.query;
2502
3531
  const strategy = args.strategy || "fulltext";
3532
+ const projectId = args.projectId;
3533
+ const expandRelations = args.expandRelations !== false;
3534
+ const relationDepth = args.relationDepth || 1;
3535
+ let projectIds;
3536
+ if (projectId) {
3537
+ projectIds = await getProjectGroupProjectIds(projectId);
3538
+ }
2503
3539
  const filters = {
2504
3540
  query,
2505
- projectId: args.projectId,
3541
+ // 如果有多个项目,使用 projectIds;否则使用 projectId
3542
+ ...projectIds && projectIds.length > 1 ? { projectIds } : { projectId },
2506
3543
  type: args.type,
2507
3544
  tags: args.tags,
2508
3545
  strategy,
2509
- limit: args.limit
3546
+ limit: args.limit,
3547
+ expandRelations,
3548
+ relationDepth
2510
3549
  };
2511
3550
  const result = await storage.recall(filters);
3551
+ let relatedMemories = result.relatedMemories || [];
3552
+ if (expandRelations && relatedMemories.length === 0 && result.memories.length > 0) {
3553
+ relatedMemories = await expandRelationChain(result.memories, relationDepth);
3554
+ }
2512
3555
  return {
2513
3556
  content: [
2514
3557
  {
2515
3558
  type: "text",
2516
- text: JSON.stringify(result, null, 2)
3559
+ text: JSON.stringify({
3560
+ ...result,
3561
+ relatedMemories,
3562
+ projectsSearched: projectIds
3563
+ }, null, 2)
2517
3564
  }
2518
3565
  ]
2519
3566
  };
@@ -2551,6 +3598,64 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2551
3598
  ]
2552
3599
  };
2553
3600
  }
3601
+ // ========== 项目组管理 ==========
3602
+ case "mpulse_set_project_group": {
3603
+ const groupConfig = {
3604
+ name: args.name,
3605
+ projects: args.projects
3606
+ };
3607
+ if (!storage.setProjectGroup) {
3608
+ throw new Error("\u5F53\u524D\u5B58\u50A8\u4E0D\u652F\u6301\u9879\u76EE\u7EC4\u529F\u80FD");
3609
+ }
3610
+ const result = await storage.setProjectGroup(groupConfig);
3611
+ return {
3612
+ content: [
3613
+ {
3614
+ type: "text",
3615
+ text: JSON.stringify({
3616
+ ...result,
3617
+ group: groupConfig,
3618
+ message: `\u9879\u76EE\u7EC4 "${groupConfig.name}" \u5DF2\u8BBE\u7F6E\uFF0C\u5305\u542B ${groupConfig.projects.length} \u4E2A\u9879\u76EE`
3619
+ }, null, 2)
3620
+ }
3621
+ ]
3622
+ };
3623
+ }
3624
+ case "mpulse_get_project_groups": {
3625
+ if (!storage.getAllProjectGroups) {
3626
+ throw new Error("\u5F53\u524D\u5B58\u50A8\u4E0D\u652F\u6301\u9879\u76EE\u7EC4\u529F\u80FD");
3627
+ }
3628
+ const groups = await storage.getAllProjectGroups();
3629
+ return {
3630
+ content: [
3631
+ {
3632
+ type: "text",
3633
+ text: JSON.stringify({
3634
+ groups,
3635
+ total: groups.length
3636
+ }, null, 2)
3637
+ }
3638
+ ]
3639
+ };
3640
+ }
3641
+ case "mpulse_delete_project_group": {
3642
+ const name2 = args.name;
3643
+ if (!storage.deleteProjectGroup) {
3644
+ throw new Error("\u5F53\u524D\u5B58\u50A8\u4E0D\u652F\u6301\u9879\u76EE\u7EC4\u529F\u80FD");
3645
+ }
3646
+ const result = await storage.deleteProjectGroup(name2);
3647
+ return {
3648
+ content: [
3649
+ {
3650
+ type: "text",
3651
+ text: JSON.stringify({
3652
+ ...result,
3653
+ message: result.success ? `\u9879\u76EE\u7EC4 "${name2}" \u5DF2\u5220\u9664` : `\u9879\u76EE\u7EC4 "${name2}" \u5220\u9664\u5931\u8D25\u6216\u4E0D\u5B58\u5728`
3654
+ }, null, 2)
3655
+ }
3656
+ ]
3657
+ };
3658
+ }
2554
3659
  default:
2555
3660
  throw new Error(`Unknown tool: ${name}`);
2556
3661
  }