memory-pulse-mcp-server 0.1.16 → 0.1.17

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
@@ -270,6 +270,93 @@ function deserializeVector(s) {
270
270
  return Array.from(buffer);
271
271
  }
272
272
 
273
+ // ../core/dist/encoding.js
274
+ var TYPE_IMPORTANCE = {
275
+ decision: 0.9,
276
+ solution: 0.8,
277
+ error: 0.7,
278
+ config: 0.6,
279
+ code: 0.5,
280
+ session: 0.4
281
+ };
282
+ function getTypeImportance(type) {
283
+ return TYPE_IMPORTANCE[type] ?? 0.5;
284
+ }
285
+ function flattenObject(obj, prefix, depth = 0) {
286
+ if (depth > 3)
287
+ return "";
288
+ const parts = [];
289
+ for (const [key, value] of Object.entries(obj)) {
290
+ const fullKey = prefix ? `${prefix}.${key}` : key;
291
+ if (value === null || value === void 0)
292
+ continue;
293
+ if (typeof value === "string") {
294
+ if (value.length > 500)
295
+ continue;
296
+ parts.push(key, value);
297
+ } else if (typeof value === "number" || typeof value === "boolean") {
298
+ parts.push(key, String(value));
299
+ } else if (Array.isArray(value)) {
300
+ for (const item of value) {
301
+ if (typeof item === "string" && item.length <= 200) {
302
+ parts.push(item);
303
+ } else if (typeof item === "number") {
304
+ parts.push(String(item));
305
+ } else if (item && typeof item === "object" && !Array.isArray(item)) {
306
+ parts.push(flattenObject(item, fullKey, depth + 1));
307
+ }
308
+ }
309
+ } else if (typeof value === "object") {
310
+ parts.push(flattenObject(value, fullKey, depth + 1));
311
+ }
312
+ }
313
+ return parts.filter(Boolean).join(" ");
314
+ }
315
+ function extractTextFromContext(rawContext, maxDepth = 3) {
316
+ const texts = [];
317
+ function walk(obj, depth) {
318
+ if (depth > maxDepth)
319
+ return;
320
+ for (const value of Object.values(obj)) {
321
+ if (value === null || value === void 0)
322
+ continue;
323
+ if (typeof value === "string") {
324
+ if (value.length > 0 && value.length <= 1e3) {
325
+ texts.push(value);
326
+ }
327
+ } else if (typeof value === "number") {
328
+ texts.push(String(value));
329
+ } else if (Array.isArray(value)) {
330
+ for (const item of value) {
331
+ if (typeof item === "string" && item.length <= 500) {
332
+ texts.push(item);
333
+ } else if (item && typeof item === "object" && !Array.isArray(item)) {
334
+ walk(item, depth + 1);
335
+ }
336
+ }
337
+ } else if (typeof value === "object") {
338
+ walk(value, depth + 1);
339
+ }
340
+ }
341
+ }
342
+ walk(rawContext, 0);
343
+ return texts.join(" ");
344
+ }
345
+ function encodeMemoryText(params) {
346
+ const parts = [params.summary];
347
+ const dataText = flattenObject(params.data);
348
+ if (dataText) {
349
+ parts.push(dataText);
350
+ }
351
+ if (params.rawContext) {
352
+ const contextText = extractTextFromContext(params.rawContext);
353
+ if (contextText) {
354
+ parts.push(contextText);
355
+ }
356
+ }
357
+ return parts.filter(Boolean).join(" ");
358
+ }
359
+
273
360
  // ../core/dist/keywords.js
274
361
  var CHINESE_STOP_WORDS = /* @__PURE__ */ new Set([
275
362
  "\u7684",
@@ -544,7 +631,40 @@ function getTopKeywords(words, limit = 30) {
544
631
  }
545
632
  return Array.from(frequency.entries()).sort((a, b) => b[1] - a[1]).slice(0, limit).map(([word]) => word);
546
633
  }
547
- function extractKeywords(content, rawContext) {
634
+ function extractFromData(data, maxDepth = 2) {
635
+ const keywords = [];
636
+ function walk(obj, depth) {
637
+ if (depth > maxDepth)
638
+ return;
639
+ for (const [key, value] of Object.entries(obj)) {
640
+ if (["id", "createdAt", "updatedAt", "timestamp"].includes(key))
641
+ continue;
642
+ if (key.length >= 2)
643
+ keywords.push(key);
644
+ if (typeof value === "string" && value.length >= 1 && value.length <= 500)
645
+ keywords.push(value);
646
+ if (typeof value === "number" && isFinite(value))
647
+ keywords.push(String(value));
648
+ if (Array.isArray(value)) {
649
+ for (const item of value) {
650
+ if (typeof item === "string" && item.length >= 1 && item.length <= 200)
651
+ keywords.push(item);
652
+ if (typeof item === "number" && isFinite(item))
653
+ keywords.push(String(item));
654
+ if (item && typeof item === "object" && !Array.isArray(item)) {
655
+ walk(item, depth + 1);
656
+ }
657
+ }
658
+ }
659
+ if (value && typeof value === "object" && !Array.isArray(value)) {
660
+ walk(value, depth + 1);
661
+ }
662
+ }
663
+ }
664
+ walk(data, 0);
665
+ return keywords;
666
+ }
667
+ function extractKeywords(content, rawContext, data) {
548
668
  const allKeywords = [];
549
669
  const contentText = content || "";
550
670
  allKeywords.push(...basicTokenize(contentText));
@@ -554,6 +674,15 @@ function extractKeywords(content, rawContext) {
554
674
  allKeywords.push(...basicTokenize(contextText));
555
675
  allKeywords.push(...extractSpecialPatterns(contextText));
556
676
  }
677
+ if (data && Object.keys(data).length > 0) {
678
+ const dataKeywords = extractFromData(data);
679
+ for (const kw of dataKeywords) {
680
+ allKeywords.push(...basicTokenize(kw));
681
+ allKeywords.push(...extractSpecialPatterns(kw));
682
+ if (kw.length >= 2)
683
+ allKeywords.push(kw);
684
+ }
685
+ }
557
686
  const topKeywords = getTopKeywords(allKeywords, 30);
558
687
  const specialKeywords = extractSpecialPatterns(contentText + " " + extractTextFromObject(rawContext));
559
688
  const uniqueSpecial = [...new Set(specialKeywords)].slice(0, 10);
@@ -2100,6 +2229,13 @@ var PostgreSQLStorage = class {
2100
2229
  const id = `mem_${nanoid2()}`;
2101
2230
  const timestamp = /* @__PURE__ */ new Date();
2102
2231
  const memoryType = params.type || MemoryType.CODE;
2232
+ const keywords = extractKeywords(params.content, params.rawContext, params.data);
2233
+ const fullText = encodeMemoryText({
2234
+ summary: params.content,
2235
+ data: params.data,
2236
+ rawContext: params.rawContext
2237
+ });
2238
+ const importance = getTypeImportance(memoryType);
2103
2239
  await this.prisma.memory.create({
2104
2240
  data: {
2105
2241
  id,
@@ -2109,17 +2245,28 @@ var PostgreSQLStorage = class {
2109
2245
  type: memoryType,
2110
2246
  tags: params.tags || [],
2111
2247
  summary: params.content,
2112
- // data 存储关键结构化数据(精简版),context 存储完整原始数据(完整版)
2113
2248
  data: params.data,
2114
2249
  replaces: params.relations?.replaces || [],
2115
2250
  relatedTo: params.relations?.relatedTo || [],
2116
2251
  impacts: params.relations?.impacts || [],
2117
2252
  derivedFrom: params.relations?.derivedFrom || null,
2118
2253
  context: params.rawContext,
2119
- keywords: params.tags || [],
2120
- fullText: params.content
2254
+ keywords: [.../* @__PURE__ */ new Set([...params.tags || [], ...keywords])],
2255
+ fullText,
2256
+ importance
2121
2257
  }
2122
2258
  });
2259
+ if (params.relations) {
2260
+ const referencedIds = [
2261
+ ...params.relations.relatedTo || [],
2262
+ ...params.relations.replaces || [],
2263
+ ...params.relations.impacts || [],
2264
+ ...params.relations.derivedFrom ? [params.relations.derivedFrom] : []
2265
+ ];
2266
+ if (referencedIds.length > 0) {
2267
+ await this.onMemoryReferenced(referencedIds);
2268
+ }
2269
+ }
2123
2270
  if (params.relations) {
2124
2271
  const relationRecords = [];
2125
2272
  if (params.relations.relatedTo) {
@@ -2150,7 +2297,7 @@ var PostgreSQLStorage = class {
2150
2297
  if (this.cache) {
2151
2298
  this.cache.invalidateProject(params.projectId);
2152
2299
  }
2153
- await this.autoRelate(id, params.projectId, params.tags || []);
2300
+ await this.autoRelate(id, params.projectId, keywords);
2154
2301
  return { id, success: true };
2155
2302
  }
2156
2303
  /**
@@ -2160,8 +2307,17 @@ var PostgreSQLStorage = class {
2160
2307
  const id = `mem_${nanoid2()}`;
2161
2308
  const timestamp = /* @__PURE__ */ new Date();
2162
2309
  const summary = `[\u51B3\u7B56] ${params.question}`;
2163
- const fullText = `${params.question} ${params.options.map((o) => o.name).join(" ")} ${params.reason}`;
2164
- const keywords = [...params.tags || [], ...params.options.map((o) => o.name), params.chosen];
2310
+ const decisionData = { chosen: params.chosen, reason: params.reason };
2311
+ const rawContext = {
2312
+ question: params.question,
2313
+ analysis: params.analysis,
2314
+ options: params.options,
2315
+ chosen: params.chosen,
2316
+ reason: params.reason
2317
+ };
2318
+ const fullText = encodeMemoryText({ summary, data: decisionData, rawContext });
2319
+ const keywords = extractKeywords(summary, rawContext, decisionData);
2320
+ const importance = getTypeImportance(MemoryType.DECISION);
2165
2321
  await this.prisma.memory.create({
2166
2322
  data: {
2167
2323
  id,
@@ -2171,20 +2327,15 @@ var PostgreSQLStorage = class {
2171
2327
  type: MemoryType.DECISION,
2172
2328
  tags: params.tags || [],
2173
2329
  summary,
2174
- data: {},
2330
+ data: decisionData,
2175
2331
  replaces: params.relations?.replaces || [],
2176
2332
  relatedTo: params.relations?.relatedTo || [],
2177
2333
  impacts: params.relations?.impacts || [],
2178
2334
  derivedFrom: params.relations?.derivedFrom || null,
2179
- context: {
2180
- question: params.question,
2181
- analysis: params.analysis,
2182
- options: params.options,
2183
- chosen: params.chosen,
2184
- reason: params.reason
2185
- },
2186
- keywords,
2187
- fullText
2335
+ context: rawContext,
2336
+ keywords: [.../* @__PURE__ */ new Set([...params.tags || [], ...keywords])],
2337
+ fullText,
2338
+ importance
2188
2339
  }
2189
2340
  });
2190
2341
  if (params.relations) {
@@ -2227,8 +2378,17 @@ var PostgreSQLStorage = class {
2227
2378
  const id = `mem_${nanoid2()}`;
2228
2379
  const timestamp = /* @__PURE__ */ new Date();
2229
2380
  const summary = `[\u65B9\u6848] ${params.problem}`;
2230
- const fullText = `${params.problem} ${params.rootCause} ${params.solution} ${params.prevention || ""} ${params.relatedIssues?.join(" ") || ""}`;
2231
- const keywords = [...params.tags || [], ...params.relatedIssues || []];
2381
+ const solutionData = { problem: params.problem, solution: params.solution, ...params.artifacts || {} };
2382
+ const rawContext = {
2383
+ problem: params.problem,
2384
+ rootCause: params.rootCause,
2385
+ solution: params.solution,
2386
+ prevention: params.prevention,
2387
+ relatedIssues: params.relatedIssues
2388
+ };
2389
+ const fullText = encodeMemoryText({ summary, data: solutionData, rawContext });
2390
+ const keywords = extractKeywords(summary, rawContext, solutionData);
2391
+ const importance = getTypeImportance(MemoryType.SOLUTION);
2232
2392
  await this.prisma.memory.create({
2233
2393
  data: {
2234
2394
  id,
@@ -2238,20 +2398,15 @@ var PostgreSQLStorage = class {
2238
2398
  type: MemoryType.SOLUTION,
2239
2399
  tags: params.tags || [],
2240
2400
  summary,
2241
- data: params.artifacts || {},
2401
+ data: solutionData,
2242
2402
  replaces: params.relations?.replaces || [],
2243
2403
  relatedTo: params.relations?.relatedTo || [],
2244
2404
  impacts: params.relations?.impacts || [],
2245
2405
  derivedFrom: params.relations?.derivedFrom || null,
2246
- context: {
2247
- problem: params.problem,
2248
- rootCause: params.rootCause,
2249
- solution: params.solution,
2250
- prevention: params.prevention,
2251
- relatedIssues: params.relatedIssues
2252
- },
2253
- keywords,
2254
- fullText
2406
+ context: rawContext,
2407
+ keywords: [.../* @__PURE__ */ new Set([...params.tags || [], ...keywords])],
2408
+ fullText,
2409
+ importance
2255
2410
  }
2256
2411
  });
2257
2412
  if (params.relations) {
@@ -2294,8 +2449,20 @@ var PostgreSQLStorage = class {
2294
2449
  const id = `mem_${nanoid2()}`;
2295
2450
  const timestamp = /* @__PURE__ */ new Date();
2296
2451
  const summary = `[\u4F1A\u8BDD] ${params.summary}`;
2297
- const fullText = `${params.summary} ${params.decisions?.join(" ") || ""} ${params.unfinishedTasks?.join(" ") || ""} ${params.nextSteps?.join(" ") || ""}`;
2298
- const keywords = [...params.decisions || [], ...params.unfinishedTasks || []];
2452
+ const sessionData = {
2453
+ decisions: params.decisions || [],
2454
+ nextSteps: params.nextSteps || [],
2455
+ unfinishedTasks: params.unfinishedTasks || []
2456
+ };
2457
+ const rawContext = {
2458
+ summary: params.summary,
2459
+ decisions: params.decisions,
2460
+ unfinishedTasks: params.unfinishedTasks,
2461
+ nextSteps: params.nextSteps
2462
+ };
2463
+ const fullText = encodeMemoryText({ summary, data: sessionData, rawContext });
2464
+ const keywords = extractKeywords(summary, rawContext, sessionData);
2465
+ const importance = getTypeImportance(MemoryType.SESSION);
2299
2466
  await this.prisma.memory.create({
2300
2467
  data: {
2301
2468
  id,
@@ -2305,19 +2472,15 @@ var PostgreSQLStorage = class {
2305
2472
  type: MemoryType.SESSION,
2306
2473
  tags: [],
2307
2474
  summary,
2308
- data: {},
2475
+ data: sessionData,
2309
2476
  replaces: params.relations?.replaces || [],
2310
2477
  relatedTo: params.relations?.relatedTo || [],
2311
2478
  impacts: params.relations?.impacts || [],
2312
2479
  derivedFrom: params.relations?.derivedFrom || null,
2313
- context: {
2314
- summary: params.summary,
2315
- decisions: params.decisions,
2316
- unfinishedTasks: params.unfinishedTasks,
2317
- nextSteps: params.nextSteps
2318
- },
2319
- keywords,
2320
- fullText
2480
+ context: rawContext,
2481
+ keywords: [...new Set(keywords)],
2482
+ fullText,
2483
+ importance
2321
2484
  }
2322
2485
  });
2323
2486
  if (params.relations) {
@@ -2354,12 +2517,12 @@ var PostgreSQLStorage = class {
2354
2517
  return { id, success: true };
2355
2518
  }
2356
2519
  /**
2357
- * 检索记忆(新算法:多维度搜索 + 智能降级 + 关系链融合)
2520
+ * 检索记忆(仿人脑:线索触发 + 扩散激活 + 相关性排序)
2358
2521
  *
2359
- * 核心功能:
2360
- * 1. 多维度搜索:projectId/summary/fullText/tags/keywords
2361
- * 2. 智能降级:无结果时自动放宽条件
2362
- * 3. 关系链融合:自动扩展关联记忆
2522
+ * 1. tsvector 全文搜索(AND 优先,降级 OR)
2523
+ * 2. 综合评分 = 文本相关性 * 重要性 * 时间衰减 * 频率增强
2524
+ * 3. 扩散激活:关联记忆参与混合排序
2525
+ * 4. 用进废退:命中时更新 access_count
2363
2526
  */
2364
2527
  async recall(filters) {
2365
2528
  const startTime = Date.now();
@@ -2374,26 +2537,38 @@ var PostgreSQLStorage = class {
2374
2537
  const offset = filters.offset || 0;
2375
2538
  const expandRelations = filters.expandRelations !== false;
2376
2539
  const relationDepth = filters.relationDepth || 1;
2377
- let dbStartTime = Date.now();
2378
- const searchResult = await this.multiDimensionSearch(filters, limit, offset);
2379
- let results = searchResult.memories;
2380
- let total = searchResult.total;
2381
- if (results.length === 0 && filters.query) {
2382
- if (filters.projectId || filters.projectIds && filters.projectIds.length > 0) {
2383
- const degradedResult = await this.multiDimensionSearch({ ...filters, query: void 0 }, limit, offset);
2384
- results = degradedResult.memories;
2385
- total = degradedResult.total;
2540
+ const dbStartTime = Date.now();
2541
+ let results = [];
2542
+ let total = 0;
2543
+ if (filters.query && filters.query.trim()) {
2544
+ const tsvResult = await this.tsvectorSearch(filters, limit, offset, "and");
2545
+ if (tsvResult.memories.length > 0) {
2546
+ results = tsvResult.memories;
2547
+ total = tsvResult.total;
2548
+ } else {
2549
+ const orResult = await this.tsvectorSearch(filters, limit, offset, "or");
2550
+ results = orResult.memories;
2551
+ total = orResult.total;
2386
2552
  }
2387
2553
  if (results.length === 0) {
2388
- const globalResult = await this.multiDimensionSearch({ ...filters, projectId: void 0, projectIds: void 0 }, limit, offset);
2389
- results = globalResult.memories;
2390
- total = globalResult.total;
2554
+ const fallback = await this.multiDimensionSearch(filters, limit, offset);
2555
+ results = fallback.memories;
2556
+ total = fallback.total;
2391
2557
  }
2558
+ } else {
2559
+ const searchResult = await this.multiDimensionSearch(filters, limit, offset);
2560
+ results = searchResult.memories;
2561
+ total = searchResult.total;
2392
2562
  }
2393
2563
  let relatedMemories;
2394
2564
  if (expandRelations && results.length > 0) {
2395
2565
  relatedMemories = await this.expandRelatedMemories(results, relationDepth);
2396
2566
  }
2567
+ if (results.length > 0) {
2568
+ const hitIds = results.map((m) => m.meta.id);
2569
+ this.onMemoryAccessed(hitIds).catch(() => {
2570
+ });
2571
+ }
2397
2572
  const dbEndTime = Date.now();
2398
2573
  const took = Date.now() - startTime;
2399
2574
  const dbTime = dbEndTime - dbStartTime;
@@ -2415,6 +2590,122 @@ var PostgreSQLStorage = class {
2415
2590
  }
2416
2591
  return result;
2417
2592
  }
2593
+ /**
2594
+ * tsvector 全文搜索(核心检索引擎)
2595
+ * 使用 PostgreSQL ts_rank_cd + 重要性 + 时间衰减 + 频率增强 综合评分
2596
+ */
2597
+ async tsvectorSearch(filters, limit, offset, mode) {
2598
+ const query = filters.query?.trim();
2599
+ if (!query)
2600
+ return { memories: [], total: 0 };
2601
+ const words = query.split(/\s+/).filter((w) => w.length > 0);
2602
+ const operator = mode === "and" ? " & " : " | ";
2603
+ const tsTerms = [];
2604
+ for (const word of words) {
2605
+ if (/[\u4e00-\u9fa5]/.test(word)) {
2606
+ for (const char of word) {
2607
+ if (/[\u4e00-\u9fa5]/.test(char))
2608
+ tsTerms.push(char);
2609
+ }
2610
+ } else {
2611
+ tsTerms.push(word.toLowerCase());
2612
+ }
2613
+ }
2614
+ if (tsTerms.length === 0)
2615
+ return { memories: [], total: 0 };
2616
+ const tsqueryStr = tsTerms.join(operator);
2617
+ let projectFilter = "";
2618
+ const params = [tsqueryStr, limit, offset];
2619
+ let paramIdx = 4;
2620
+ if (filters.projectIds && filters.projectIds.length > 0) {
2621
+ projectFilter = `AND "projectId" = ANY($${paramIdx})`;
2622
+ params.push(filters.projectIds);
2623
+ paramIdx++;
2624
+ } else if (filters.projectId) {
2625
+ projectFilter = `AND "projectId" = $${paramIdx}`;
2626
+ params.push(filters.projectId);
2627
+ paramIdx++;
2628
+ }
2629
+ let typeFilter = "";
2630
+ if (filters.type) {
2631
+ typeFilter = `AND type = $${paramIdx}`;
2632
+ params.push(filters.type);
2633
+ paramIdx++;
2634
+ }
2635
+ const sql = `
2636
+ SELECT *,
2637
+ ts_rank_cd(tsv, to_tsquery('simple', $1)) AS text_score,
2638
+ (
2639
+ ts_rank_cd(tsv, to_tsquery('simple', $1))
2640
+ * COALESCE(importance, 0.5)
2641
+ * EXP(-0.693 * EXTRACT(EPOCH FROM (NOW() - timestamp)) / (30.0 * 86400.0))
2642
+ * (1.0 + LN(1.0 + COALESCE(access_count, 0) + COALESCE(reference_count, 0)) * 0.2)
2643
+ ) AS brain_score
2644
+ FROM memories
2645
+ WHERE tsv @@ to_tsquery('simple', $1)
2646
+ ${projectFilter}
2647
+ ${typeFilter}
2648
+ ORDER BY brain_score DESC
2649
+ LIMIT $2 OFFSET $3
2650
+ `;
2651
+ const countSql = `
2652
+ SELECT COUNT(*) as total
2653
+ FROM memories
2654
+ WHERE tsv @@ to_tsquery('simple', $1)
2655
+ ${projectFilter}
2656
+ ${typeFilter}
2657
+ `;
2658
+ try {
2659
+ const [rows, countResult] = await Promise.all([
2660
+ this.prisma.$queryRawUnsafe(sql, ...params),
2661
+ this.prisma.$queryRawUnsafe(countSql, ...params.slice(0, 1).concat(params.slice(3)))
2662
+ ]);
2663
+ const memories = rows.map((row) => this.rawRowToMemory(row));
2664
+ const total = Number(countResult[0]?.total || 0);
2665
+ return { memories, total };
2666
+ } catch (error) {
2667
+ console.warn("tsvector search failed, falling back:", error.message);
2668
+ return { memories: [], total: 0 };
2669
+ }
2670
+ }
2671
+ /**
2672
+ * 将原始 SQL 查询行转换为 Memory 对象
2673
+ * 与 rowToMemory 类似,但处理的是原始 SQL 返回的 snake_case 字段
2674
+ */
2675
+ rawRowToMemory(row) {
2676
+ return {
2677
+ meta: {
2678
+ id: row.id,
2679
+ projectId: row.projectId,
2680
+ sessionId: row.sessionId,
2681
+ timestamp: row.timestamp instanceof Date ? row.timestamp.toISOString() : row.timestamp,
2682
+ type: row.type,
2683
+ tags: row.tags || [],
2684
+ version: row.version,
2685
+ importance: row.importance ?? 0.5,
2686
+ accessCount: row.access_count ?? 0,
2687
+ referenceCount: row.reference_count ?? 0,
2688
+ lastAccessedAt: row.last_accessed_at ? row.last_accessed_at instanceof Date ? row.last_accessed_at.toISOString() : row.last_accessed_at : void 0
2689
+ },
2690
+ content: {
2691
+ summary: row.summary,
2692
+ data: (typeof row.data === "string" ? JSON.parse(row.data) : row.data) || {},
2693
+ rawContext: (typeof row.context === "string" ? JSON.parse(row.context) : row.context) || void 0
2694
+ },
2695
+ relations: {
2696
+ replaces: row.replaces || void 0,
2697
+ relatedTo: row.relatedTo || row.relatedto || void 0,
2698
+ impacts: row.impacts || void 0,
2699
+ derivedFrom: row.derivedFrom || row.derivedfrom || void 0
2700
+ },
2701
+ searchable: {
2702
+ keywords: row.keywords || [],
2703
+ fullText: row.fullText || row.fulltext || ""
2704
+ },
2705
+ createdAt: row.createdAt instanceof Date ? row.createdAt.toISOString() : row.createdat || row.createdAt || "",
2706
+ updatedAt: row.updatedAt instanceof Date ? row.updatedAt.toISOString() : row.updatedat || row.updatedAt || ""
2707
+ };
2708
+ }
2418
2709
  /**
2419
2710
  * 扩展关联记忆
2420
2711
  *
@@ -2822,7 +3113,11 @@ var PostgreSQLStorage = class {
2822
3113
  timestamp: row.timestamp.toISOString(),
2823
3114
  type: row.type,
2824
3115
  tags: row.tags || [],
2825
- version: row.version
3116
+ version: row.version,
3117
+ importance: row.importance ?? 0.5,
3118
+ accessCount: row.accessCount ?? 0,
3119
+ referenceCount: row.referenceCount ?? 0,
3120
+ lastAccessedAt: row.lastAccessedAt?.toISOString() ?? void 0
2826
3121
  },
2827
3122
  content: {
2828
3123
  summary: row.summary,
@@ -2892,6 +3187,31 @@ var PostgreSQLStorage = class {
2892
3187
  /**
2893
3188
  * 关闭数据库连接
2894
3189
  */
3190
+ // ========== 用进废退:访问/引用计数 ==========
3191
+ /**
3192
+ * 批量更新访问计数(查询命中时异步调用)
3193
+ */
3194
+ async onMemoryAccessed(memoryIds) {
3195
+ if (memoryIds.length === 0)
3196
+ return;
3197
+ try {
3198
+ await this.prisma.$executeRawUnsafe(`UPDATE memories SET access_count = access_count + 1, last_accessed_at = NOW() WHERE id = ANY($1)`, memoryIds);
3199
+ } catch (error) {
3200
+ console.warn("\u66F4\u65B0\u8BBF\u95EE\u8BA1\u6570\u5931\u8D25:", error.message);
3201
+ }
3202
+ }
3203
+ /**
3204
+ * 批量更新引用计数(被其他记忆引用时调用)
3205
+ */
3206
+ async onMemoryReferenced(memoryIds) {
3207
+ if (memoryIds.length === 0)
3208
+ return;
3209
+ try {
3210
+ await this.prisma.$executeRawUnsafe(`UPDATE memories SET reference_count = reference_count + 1 WHERE id = ANY($1)`, memoryIds);
3211
+ } catch (error) {
3212
+ console.warn("\u66F4\u65B0\u5F15\u7528\u8BA1\u6570\u5931\u8D25:", error.message);
3213
+ }
3214
+ }
2895
3215
  async close() {
2896
3216
  await this.prisma.$disconnect();
2897
3217
  }