memory-pulse-mcp-server 0.1.14 → 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
@@ -1169,6 +1624,157 @@ var SQLiteStorage = class {
1169
1624
  recentCount: recentResult.count
1170
1625
  };
1171
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
+ }
1172
1778
  };
1173
1779
 
1174
1780
  // ../storage/dist/postgresql-storage.js
@@ -1752,6 +2358,170 @@ var PostgreSQLStorage = class {
1752
2358
  async close() {
1753
2359
  await this.prisma.$disconnect();
1754
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
+ }
1755
2525
  };
1756
2526
 
1757
2527
  // src/index.ts
@@ -1838,6 +2608,121 @@ function createStorage() {
1838
2608
  return new SQLiteStorage(dbPath);
1839
2609
  }
1840
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
+ }
1841
2726
  var tools = [
1842
2727
  {
1843
2728
  name: "mpulse_store",
@@ -2031,13 +2916,19 @@ var tools = [
2031
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"
2032
2917
  4. \u5206\u6790\u8FD4\u56DE\u7ED3\u679C\u540E\u518D\u6267\u884C\u5176\u4ED6\u64CD\u4F5C
2033
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
+
2034
2924
  \u8C03\u7528\u793A\u4F8B\uFF1A
2035
2925
  mpulse_recall({
2036
2926
  query: "\u9879\u76EE\u4E0A\u4E0B\u6587 \u67B6\u6784\u51B3\u7B56 \u672A\u5B8C\u6210\u4EFB\u52A1",
2037
- 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
2038
2929
  })
2039
2930
 
2040
- \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`,
2041
2932
  inputSchema: {
2042
2933
  type: "object",
2043
2934
  properties: {
@@ -2047,7 +2938,7 @@ mpulse_recall({
2047
2938
  },
2048
2939
  projectId: {
2049
2940
  type: "string",
2050
- 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"
2051
2942
  },
2052
2943
  type: {
2053
2944
  type: "string",
@@ -2067,6 +2958,14 @@ mpulse_recall({
2067
2958
  limit: {
2068
2959
  type: "number",
2069
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"
2070
2969
  }
2071
2970
  },
2072
2971
  required: ["query"]
@@ -2121,6 +3020,50 @@ mpulse_recall({
2121
3020
  },
2122
3021
  required: ["memoryId"]
2123
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
+ }
2124
3067
  }
2125
3068
  ];
2126
3069
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
@@ -2444,80 +3387,141 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2444
3387
  try {
2445
3388
  switch (name) {
2446
3389
  case "mpulse_store": {
3390
+ const content = args.content;
3391
+ const rawContext = args.rawContext;
3392
+ const projectId = args.projectId;
2447
3393
  const result = await storage.store({
2448
- content: args.content,
2449
- rawContext: args.rawContext,
2450
- projectId: args.projectId,
3394
+ content,
3395
+ rawContext,
3396
+ projectId,
2451
3397
  type: args.type,
2452
3398
  tags: args.tags,
2453
3399
  sessionId: args.sessionId
2454
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
+ }
2455
3410
  return {
2456
3411
  content: [
2457
3412
  {
2458
3413
  type: "text",
2459
- text: JSON.stringify(result, null, 2)
3414
+ text: JSON.stringify({
3415
+ ...result,
3416
+ autoRelations: autoRelationResult.relationsCreated
3417
+ }, null, 2)
2460
3418
  }
2461
3419
  ]
2462
3420
  };
2463
3421
  }
2464
3422
  case "mpulse_store_decision": {
3423
+ const projectId = args.projectId;
2465
3424
  const decisionParams = {
2466
3425
  question: args.question,
2467
3426
  options: args.options,
2468
3427
  chosen: args.chosen,
2469
3428
  reason: args.reason,
2470
- projectId: args.projectId,
3429
+ projectId,
2471
3430
  tags: args.tags,
2472
3431
  sessionId: args.sessionId
2473
3432
  };
2474
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
+ }
2475
3444
  return {
2476
3445
  content: [
2477
3446
  {
2478
3447
  type: "text",
2479
- text: JSON.stringify(result, null, 2)
3448
+ text: JSON.stringify({
3449
+ ...result,
3450
+ autoRelations: autoRelationResult.relationsCreated
3451
+ }, null, 2)
2480
3452
  }
2481
3453
  ]
2482
3454
  };
2483
3455
  }
2484
3456
  case "mpulse_store_solution": {
3457
+ const projectId = args.projectId;
2485
3458
  const solutionParams = {
2486
3459
  problem: args.problem,
2487
3460
  rootCause: args.rootCause,
2488
3461
  solution: args.solution,
2489
3462
  prevention: args.prevention,
2490
3463
  relatedIssues: args.relatedIssues,
2491
- projectId: args.projectId,
3464
+ projectId,
2492
3465
  tags: args.tags,
2493
3466
  sessionId: args.sessionId,
2494
3467
  artifacts: args.artifacts
2495
3468
  };
2496
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
+ }
2497
3480
  return {
2498
3481
  content: [
2499
3482
  {
2500
3483
  type: "text",
2501
- text: JSON.stringify(result, null, 2)
3484
+ text: JSON.stringify({
3485
+ ...result,
3486
+ autoRelations: autoRelationResult.relationsCreated
3487
+ }, null, 2)
2502
3488
  }
2503
3489
  ]
2504
3490
  };
2505
3491
  }
2506
3492
  case "mpulse_store_session": {
3493
+ const projectId = args.projectId;
2507
3494
  const sessionParams = {
2508
3495
  summary: args.summary,
2509
3496
  decisions: args.decisions,
2510
3497
  unfinishedTasks: args.unfinishedTasks,
2511
3498
  nextSteps: args.nextSteps,
2512
- projectId: args.projectId,
3499
+ projectId,
2513
3500
  sessionId: args.sessionId
2514
3501
  };
2515
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
+ }
2516
3517
  return {
2517
3518
  content: [
2518
3519
  {
2519
3520
  type: "text",
2520
- text: JSON.stringify(result, null, 2)
3521
+ text: JSON.stringify({
3522
+ ...result,
3523
+ autoRelations: autoRelationResult.relationsCreated
3524
+ }, null, 2)
2521
3525
  }
2522
3526
  ]
2523
3527
  };
@@ -2525,20 +3529,38 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2525
3529
  case "mpulse_recall": {
2526
3530
  const query = args.query;
2527
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
+ }
2528
3539
  const filters = {
2529
3540
  query,
2530
- projectId: args.projectId,
3541
+ // 如果有多个项目,使用 projectIds;否则使用 projectId
3542
+ ...projectIds && projectIds.length > 1 ? { projectIds } : { projectId },
2531
3543
  type: args.type,
2532
3544
  tags: args.tags,
2533
3545
  strategy,
2534
- limit: args.limit
3546
+ limit: args.limit,
3547
+ expandRelations,
3548
+ relationDepth
2535
3549
  };
2536
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
+ }
2537
3555
  return {
2538
3556
  content: [
2539
3557
  {
2540
3558
  type: "text",
2541
- text: JSON.stringify(result, null, 2)
3559
+ text: JSON.stringify({
3560
+ ...result,
3561
+ relatedMemories,
3562
+ projectsSearched: projectIds
3563
+ }, null, 2)
2542
3564
  }
2543
3565
  ]
2544
3566
  };
@@ -2576,6 +3598,64 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2576
3598
  ]
2577
3599
  };
2578
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
+ }
2579
3659
  default:
2580
3660
  throw new Error(`Unknown tool: ${name}`);
2581
3661
  }