autosnippet 3.2.8 → 3.2.9

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.
Files changed (113) hide show
  1. package/bin/cli.js +6 -5
  2. package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
  3. package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
  4. package/dashboard/dist/index.html +2 -2
  5. package/lib/cli/AiScanService.js +23 -26
  6. package/lib/cli/SetupService.js +1 -1
  7. package/lib/core/AstAnalyzer.js +1 -1
  8. package/lib/core/discovery/index.js +2 -2
  9. package/lib/external/ai/AiProvider.js +66 -172
  10. package/lib/external/ai/providers/GoogleGeminiProvider.js +23 -1
  11. package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
  12. package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
  13. package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +1 -1
  14. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
  15. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
  16. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
  17. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +287 -204
  18. package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +7 -6
  19. package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
  20. package/lib/external/mcp/handlers/bootstrap-internal.js +2 -2
  21. package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
  22. package/lib/http/HttpServer.js +1 -1
  23. package/lib/http/middleware/requestLogger.js +1 -0
  24. package/lib/http/routes/ai.js +240 -35
  25. package/lib/http/routes/candidates.js +2 -3
  26. package/lib/http/routes/extract.js +13 -11
  27. package/lib/http/routes/modules.js +2 -2
  28. package/lib/http/routes/recipes.js +9 -5
  29. package/lib/http/routes/remote.js +134 -255
  30. package/lib/http/routes/violations.js +0 -54
  31. package/lib/http/utils/sse-sessions.js +1 -1
  32. package/lib/infrastructure/logging/Logger.js +5 -4
  33. package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
  34. package/lib/injection/ServiceContainer.js +64 -17
  35. package/lib/platform/ScreenCaptureService.js +177 -0
  36. package/lib/platform/ios/routes/spm.js +2 -2
  37. package/lib/service/agent/AgentEventBus.js +207 -0
  38. package/lib/service/agent/AgentFactory.js +490 -0
  39. package/lib/service/agent/AgentMessage.js +240 -0
  40. package/lib/service/agent/AgentRouter.js +228 -0
  41. package/lib/service/agent/AgentRuntime.js +1016 -0
  42. package/lib/service/agent/AgentState.js +217 -0
  43. package/lib/service/agent/IntentClassifier.js +331 -0
  44. package/lib/service/agent/LarkTransport.js +389 -0
  45. package/lib/service/agent/capabilities.js +408 -0
  46. package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
  47. package/lib/service/{chat → agent/context}/ExplorationTracker.js +25 -14
  48. package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +1 -1
  49. package/lib/service/agent/core/LoopContext.js +170 -0
  50. package/lib/service/agent/core/MessageAdapter.js +223 -0
  51. package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
  52. package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +19 -98
  53. package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
  54. package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
  55. package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
  56. package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +85 -135
  57. package/lib/service/agent/domain/insight-producer.js +267 -0
  58. package/lib/service/agent/domain/scan-prompts.js +105 -0
  59. package/lib/service/agent/forced-summary.js +266 -0
  60. package/lib/service/agent/index.js +91 -0
  61. package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
  62. package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
  63. package/lib/service/{chat → agent}/memory/SessionStore.js +1 -1
  64. package/lib/service/{chat → agent}/memory/index.js +1 -1
  65. package/lib/service/agent/policies.js +442 -0
  66. package/lib/service/agent/presets.js +303 -0
  67. package/lib/service/agent/strategies.js +717 -0
  68. package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
  69. package/lib/service/agent/tools/ai-analysis.js +75 -0
  70. package/lib/service/{chat → agent}/tools/composite.js +2 -1
  71. package/lib/service/{chat → agent}/tools/guard.js +1 -121
  72. package/lib/service/{chat → agent}/tools/index.js +27 -21
  73. package/lib/service/{chat → agent}/tools/infrastructure.js +1 -1
  74. package/lib/service/agent/tools/knowledge-graph.js +112 -0
  75. package/lib/service/agent/tools/scan-recipe.js +189 -0
  76. package/lib/service/agent/tools/system-interaction.js +476 -0
  77. package/lib/service/automation/DirectiveDetector.js +0 -1
  78. package/lib/service/automation/FileWatcher.js +0 -8
  79. package/lib/service/automation/handlers/CreateHandler.js +7 -3
  80. package/lib/service/automation/handlers/DraftHandler.js +7 -6
  81. package/lib/service/module/ModuleService.js +40 -73
  82. package/lib/service/skills/SignalCollector.js +26 -19
  83. package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
  84. package/lib/shared/FieldSpec.js +1 -1
  85. package/lib/shared/StyleGuide.js +1 -1
  86. package/package.json +4 -1
  87. package/resources/native-ui/screenshot.swift +228 -0
  88. package/dashboard/dist/assets/index-D5jiDBQG.css +0 -1
  89. package/dashboard/dist/assets/index-e5OKj-Ni.js +0 -128
  90. package/lib/core/discovery/SpmDiscoverer.js +0 -5
  91. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -750
  92. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
  93. package/lib/http/routes/spm.js +0 -5
  94. package/lib/infrastructure/external/XcodeAutomation.js +0 -15
  95. package/lib/service/chat/ChatAgent.js +0 -1602
  96. package/lib/service/chat/Memory.js +0 -161
  97. package/lib/service/chat/ProducerAgent.js +0 -431
  98. package/lib/service/chat/ReasoningTrace.js +0 -523
  99. package/lib/service/chat/TaskPipeline.js +0 -357
  100. package/lib/service/chat/WorkingMemory.js +0 -359
  101. package/lib/service/chat/memory/PersistentMemory.js +0 -450
  102. package/lib/service/chat/tools/ai-analysis.js +0 -267
  103. package/lib/service/chat/tools/knowledge-graph.js +0 -234
  104. package/lib/service/chat/tools.js +0 -18
  105. package/lib/service/snippet/PlaceholderConverter.js +0 -5
  106. package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
  107. /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
  108. /package/lib/service/{chat → agent}/memory/ActiveContext.js +0 -0
  109. /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
  110. /package/lib/service/{chat → agent}/tools/ast-graph.js +0 -0
  111. /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
  112. /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
  113. /package/lib/service/{chat → agent}/tools/query.js +0 -0
@@ -1,17 +1,17 @@
1
1
  /**
2
- * @deprecated Phase 5: 已由 PersistentMemory 继承增强。
3
- * 新代码请使用 `import { PersistentMemory } from './memory/PersistentMemory.js'`
4
- * 本文件保留作为 PersistentMemory 的父类,暂不删除。
2
+ * PersistentMemory 持久化语义记忆 (Tier 3)
5
3
  *
6
- * ProjectSemanticMemory 项目级永久语义记忆 (Tier 3)
7
- *
8
- * 替代/增强 Memory.js 的 JSONL 存储,使用 SQLite 提供:
4
+ * 统一的项目级永久记忆存储,使用 SQLite 提供:
9
5
  * - 重要性评分 (importance 1.0-10.0)
10
6
  * - 综合检索 (recency × importance × relevance)
11
7
  * - Extract-Update 模式固化 (ADD / UPDATE / MERGE / NOOP)
8
+ * - Mem0 风格冲突解决 (矛盾检测 + 自动替换)
12
9
  * - TTL 自动过期 + 访问计数
13
10
  * - 关联实体 + 关联记忆
14
11
  * - 来源追溯 (sourceDimension + sourceEvidence)
12
+ * - Legacy JSONL 迁移
13
+ * - 向量嵌入预留接口
14
+ * - 预算感知 toPromptSection
15
15
  *
16
16
  * 记忆类型:
17
17
  * - fact: 项目事实 ("使用 BD 前缀命名", "有 200 个 ObjC 文件")
@@ -23,13 +23,21 @@
23
23
  * - user: 用户对话中产生
24
24
  * - system: SignalCollector 等后台
25
25
  *
26
- * 兼容: 保留 toPromptSection() 接口,可无缝替代 Memory.js
26
+ * 算法参数 (Generative Agents 三维打分):
27
+ * - 检索权重: RECENCY=0.2, IMPORTANCE=0.3, RELEVANCE=0.5
28
+ * - 半衰期: RECENCY_HALF_LIFE_DAYS=7
29
+ * - 固化阈值: SIMILARITY_UPDATE=0.85 (同义→UPDATE), SIMILARITY_MERGE=0.6 (相关→MERGE)
30
+ * - 遗忘策略: ARCHIVE_DAYS=30 (降级), FORGET_DAYS=90 (删除), MAX_MEMORIES=500
31
+ *
32
+ * 兼容: 保留 toPromptSection() / load() / append() / size() 接口,可无缝替代 Memory.js
27
33
  *
28
- * @module ProjectSemanticMemory
34
+ * @module PersistentMemory
29
35
  */
30
36
 
37
+ import fs from 'node:fs';
38
+ import path from 'node:path';
31
39
  import { randomUUID } from 'node:crypto';
32
- import { jaccardSimilarity, tokenizeForSimilarity } from '../../shared/similarity.js';
40
+ import { jaccardSimilarity, tokenizeForSimilarity } from '../../../shared/similarity.js';
33
41
 
34
42
  // ──────────────────────────────────────────────────────────────
35
43
  // 常量
@@ -55,37 +63,71 @@ const SIMILARITY_UPDATE = 0.85; // ≥85% 同义 → UPDATE
55
63
  const SIMILARITY_MERGE = 0.6; // ≥60% 相关 → MERGE
56
64
 
57
65
  // ──────────────────────────────────────────────────────────────
58
- // ProjectSemanticMemory
66
+ // 矛盾检测模式 (Mem0 风格冲突解决)
67
+ // ──────────────────────────────────────────────────────────────
68
+
69
+ /** 中文否定/禁止模式 */
70
+ const NEGATION_PATTERNS_ZH =
71
+ /不(再)?使用|不(再)?用|禁止|废弃|移除|取消|停止|不要|不采用|弃用|淘汰/;
72
+
73
+ /** 英文否定/禁止模式 */
74
+ const NEGATION_PATTERNS_EN =
75
+ /\b(don'?t|do\s+not|never|no\s+longer|removed?|deprecated?|stop|avoid|disable|abandon|drop)\b/i;
76
+
77
+ /** 共享词语最少匹配数 — 用于判断两条记忆是否讨论同一主题 */
78
+ const MIN_TOPIC_OVERLAP_WORDS = 2;
79
+
80
+ /** 共享词语比例阈值 — 低于此值视为不同主题 */
81
+ const MIN_TOPIC_OVERLAP_RATIO = 0.3;
82
+
83
+ // ──────────────────────────────────────────────────────────────
84
+ // PersistentMemory
59
85
  // ──────────────────────────────────────────────────────────────
60
86
 
61
- export class ProjectSemanticMemory {
87
+ export class PersistentMemory {
62
88
  /** @type {import('better-sqlite3').Database} */
63
89
  #db;
64
90
 
65
- /** @type {import('../../infrastructure/logging/Logger.js').default|null} */
91
+ /** @type {object|null} */
66
92
  #logger;
67
93
 
68
- // 预编译 SQL Statements (lazy init)
94
+ /** 预编译 SQL Statements */
69
95
  #stmts = null;
70
96
 
97
+ /**
98
+ * 向量嵌入函数 (预留接口)
99
+ *
100
+ * 签名: (queryText: string, contentText: string) => number (0.0-1.0)
101
+ * 当前: null → 使用 Jaccard + 子串匹配
102
+ * 未来: ADR-3 — 当嵌入模型可用时,通过 setEmbeddingFunction() 注入
103
+ *
104
+ * @type {Function|null}
105
+ */
106
+ #embeddingFn;
107
+
71
108
  /**
72
109
  * @param {import('better-sqlite3').Database} db — better-sqlite3 实例
73
110
  * @param {object} [opts]
74
- * @param {object} [opts.logger] — Logger 实例
111
+ * @param {object} [opts.logger] — Logger 实例
112
+ * @param {Function} [opts.embeddingFn] — 向量嵌入函数 (预留)
75
113
  */
76
- constructor(db, { logger } = {}) {
114
+ constructor(db, opts = {}) {
115
+ const { logger, embeddingFn } = typeof opts === 'object' && opts !== null ? opts : {};
77
116
  if (!db) {
78
- throw new Error('ProjectSemanticMemory requires a database instance');
117
+ throw new Error('PersistentMemory requires a database instance');
79
118
  }
80
119
  this.#db = typeof db?.getDb === 'function' ? db.getDb() : db;
81
120
  this.#logger = logger || null;
121
+ this.#embeddingFn = typeof embeddingFn === 'function' ? embeddingFn : null;
82
122
 
83
123
  // 确保表存在 (如果 migration 未运行)
84
124
  this.#ensureTable();
85
125
  this.#prepareStatements();
86
126
  }
87
127
 
88
- // ─── 基本 CRUD ──────────────────────────────────────────
128
+ // ═══════════════════════════════════════════════════════════
129
+ // 基本 CRUD
130
+ // ═══════════════════════════════════════════════════════════
89
131
 
90
132
  /**
91
133
  * 添加一条记忆
@@ -208,25 +250,32 @@ export class ProjectSemanticMemory {
208
250
  return row ? this.#deserialize(row) : null;
209
251
  }
210
252
 
211
- // ─── 智能存储: Extract-Update Consolidation ──────────
253
+ // ═══════════════════════════════════════════════════════════
254
+ // 智能存储: Extract-Update Consolidation + 冲突预解决
255
+ // ═══════════════════════════════════════════════════════════
212
256
 
213
257
  /**
214
- * 智能固化: 对候选记忆执行 ADD / UPDATE / MERGE / NOOP
258
+ * 智能固化: 先执行冲突检测 (Mem0 风格),再执行 ADD / UPDATE / MERGE / NOOP
215
259
  *
216
- * 借鉴 Mem0 的 Extract-Update pipeline:
217
- * 1. 对每条候选记忆,搜索相似的现有记忆
218
- * 2. 根据相似度决策: ADD / UPDATE / MERGE / NOOP
260
+ * 冲突解决逻辑:
261
+ * 1. 对每条候选记忆,在现有库中搜索相似记忆
262
+ * 2. 如果发现同类型 + 矛盾内容 直接 REPLACE (新信息更可信)
263
+ * 3. 未冲突的候选走正常 ADD/UPDATE/MERGE 流程
219
264
  *
220
265
  * @param {Array<object>} candidateMemories — 候选记忆列表
221
266
  * @param {object} [opts]
222
267
  * @param {string} [opts.bootstrapSession] — Bootstrap session ID
223
- * @returns {{ added: number, updated: number, merged: number, skipped: number }}
268
+ * @returns {{ added: number, updated: number, merged: number, skipped: number, replaced?: number }}
224
269
  */
225
270
  consolidate(candidateMemories, { bootstrapSession } = {}) {
271
+ // Phase 1: 冲突预解决
272
+ const { processed, replaced } = this.#preResolveConflicts(candidateMemories);
273
+
274
+ // Phase 2: 正常 consolidate 流程
226
275
  const stats = { added: 0, updated: 0, merged: 0, skipped: 0 };
227
276
 
228
277
  const runConsolidate = this.#db.transaction(() => {
229
- for (const candidate of candidateMemories) {
278
+ for (const candidate of processed) {
230
279
  const content = (candidate.content || '').trim();
231
280
  if (!content || content.length < 5) {
232
281
  stats.skipped++;
@@ -285,10 +334,16 @@ export class ProjectSemanticMemory {
285
334
  // 容量控制
286
335
  this.#enforceCapacity();
287
336
 
337
+ if (replaced > 0) {
338
+ stats.replaced = replaced;
339
+ }
340
+
288
341
  return stats;
289
342
  }
290
343
 
291
- // ─── 综合检索 ─────────────────────────────────────────
344
+ // ═══════════════════════════════════════════════════════════
345
+ // 综合检索
346
+ // ═══════════════════════════════════════════════════════════
292
347
 
293
348
  /**
294
349
  * 综合检索: recency × importance × relevance
@@ -363,23 +418,29 @@ export class ProjectSemanticMemory {
363
418
  return results.map((r) => this.#deserialize(r));
364
419
  }
365
420
 
366
- // ─── Prompt 生成 (兼容 Memory.js) ──────────────────────
421
+ // ═══════════════════════════════════════════════════════════
422
+ // Prompt 生成 (兼容 Memory.js) — 预算感知
423
+ // ═══════════════════════════════════════════════════════════
367
424
 
368
425
  /**
369
- * 生成供系统提示词的记忆摘要
370
- *
371
- * 兼容 Memory.toPromptSection() 接口:
372
- * memory.toPromptSection({ source: 'user' })
373
- *
374
- * 增强: 使用综合检索 (recency + importance + relevance)
426
+ * 生成供系统提示词的记忆摘要 (预算感知)
375
427
  *
376
428
  * @param {object} [opts]
377
429
  * @param {string} [opts.source] — 过滤 source (user/system/bootstrap)
378
430
  * @param {string} [opts.query] — 查询上下文 (用于 relevance 打分)
379
431
  * @param {number} [opts.limit=15]
432
+ * @param {number} [opts.tokenBudget] — token 预算 (由 MemoryCoordinator 分配)
380
433
  * @returns {string} Markdown 格式
381
434
  */
382
- toPromptSection({ source, query, limit = 15 } = {}) {
435
+ toPromptSection({ source, query, limit = 15, tokenBudget } = {}) {
436
+ // 预算感知: 根据 tokenBudget 限制条数
437
+ if (tokenBudget && tokenBudget > 0) {
438
+ const EST_TOKENS_PER_MEMORY = 30;
439
+ const HEADER_TOKENS = 15;
440
+ const maxByBudget = Math.max(3, Math.floor((tokenBudget - HEADER_TOKENS) / EST_TOKENS_PER_MEMORY));
441
+ limit = Math.min(limit, maxByBudget);
442
+ }
443
+
383
444
  let memories;
384
445
 
385
446
  if (query) {
@@ -409,7 +470,9 @@ export class ProjectSemanticMemory {
409
470
  return `\n## 项目记忆 (${memories.length} 条最相关)\n${lines.join('\n')}\n`;
410
471
  }
411
472
 
412
- // ─── Memory.js 兼容层 ──────────────────────────────────
473
+ // ═══════════════════════════════════════════════════════════
474
+ // Memory.js 兼容层
475
+ // ═══════════════════════════════════════════════════════════
413
476
 
414
477
  /**
415
478
  * 兼容 Memory.load() — 返回最近 N 条记忆
@@ -485,7 +548,9 @@ export class ProjectSemanticMemory {
485
548
  return this.#db.prepare('SELECT COUNT(*) as cnt FROM semantic_memories').get()?.cnt || 0;
486
549
  }
487
550
 
488
- // ─── 维护: 过期清理 + 容量控制 ────────────────────────
551
+ // ═══════════════════════════════════════════════════════════
552
+ // 维护: 过期清理 + 容量控制
553
+ // ═══════════════════════════════════════════════════════════
489
554
 
490
555
  /**
491
556
  * 执行维护: 清理过期记忆 + 容量控制
@@ -534,7 +599,9 @@ export class ProjectSemanticMemory {
534
599
  return stats;
535
600
  }
536
601
 
537
- // ─── 统计 ─────────────────────────────────────────────
602
+ // ═══════════════════════════════════════════════════════════
603
+ // 统计
604
+ // ═══════════════════════════════════════════════════════════
538
605
 
539
606
  /**
540
607
  * 获取统计信息
@@ -571,7 +638,256 @@ export class ProjectSemanticMemory {
571
638
  return result.changes;
572
639
  }
573
640
 
574
- // ─── 内部方法 ─────────────────────────────────────────
641
+ // ═══════════════════════════════════════════════════════════
642
+ // Legacy Migration (Memory.js JSONL → SQLite)
643
+ // ═══════════════════════════════════════════════════════════
644
+
645
+ /**
646
+ * 从旧版 Memory.js JSONL 文件迁移数据到 SQLite
647
+ *
648
+ * 流程:
649
+ * 1. 读取 .autosnippet/memory.jsonl (逐行 JSON)
650
+ * 2. 映射 type: preference→preference, decision→fact, 其他→fact
651
+ * 3. 通过 consolidate() 智能去重合并
652
+ * 4. 成功后将旧文件重命名为 .migrated
653
+ *
654
+ * @param {string} projectRoot — 用户项目根目录
655
+ * @returns {Promise<{ migrated: number, skipped: number, error?: string }>}
656
+ */
657
+ async migrateFromLegacy(projectRoot) {
658
+ const legacyPath = path.join(projectRoot, '.autosnippet', 'memory.jsonl');
659
+
660
+ if (!fs.existsSync(legacyPath)) {
661
+ return { migrated: 0, skipped: 0 };
662
+ }
663
+
664
+ try {
665
+ const raw = fs.readFileSync(legacyPath, 'utf-8').trim();
666
+ if (!raw) {
667
+ return { migrated: 0, skipped: 0 };
668
+ }
669
+
670
+ const lines = raw.split('\n').filter(Boolean);
671
+ const candidates = lines
672
+ .map((line) => {
673
+ try {
674
+ return JSON.parse(line);
675
+ } catch {
676
+ return null;
677
+ }
678
+ })
679
+ .filter(Boolean)
680
+ .map((m) => ({
681
+ type: PersistentMemory.#mapLegacyType(m.type),
682
+ content: (m.content || '').trim(),
683
+ source: m.source || 'user',
684
+ importance: m.type === 'decision' ? 7 : 5,
685
+ }))
686
+ .filter((m) => m.content.length >= 5);
687
+
688
+ if (candidates.length === 0) {
689
+ return { migrated: 0, skipped: lines.length };
690
+ }
691
+
692
+ const result = this.consolidate(candidates, {
693
+ bootstrapSession: 'legacy-migration',
694
+ });
695
+
696
+ // 迁移成功 → 重命名旧文件 (保留备份)
697
+ try {
698
+ fs.renameSync(legacyPath, `${legacyPath}.migrated`);
699
+ } catch {
700
+ // 重命名失败不影响迁移结果
701
+ }
702
+
703
+ const migrated = result.added + result.merged;
704
+ this.#log(
705
+ `Legacy migration: ${migrated} migrated (${result.added} added, ${result.merged} merged), ${result.skipped} skipped from ${legacyPath}`
706
+ );
707
+
708
+ return { migrated, skipped: result.skipped };
709
+ } catch (err) {
710
+ this.#log(`Legacy migration failed: ${err.message}`);
711
+ return { migrated: 0, skipped: 0, error: err.message };
712
+ }
713
+ }
714
+
715
+ // ═══════════════════════════════════════════════════════════
716
+ // 向量嵌入接口 (ADR-3 预留)
717
+ // ═══════════════════════════════════════════════════════════
718
+
719
+ /**
720
+ * 设置向量嵌入函数
721
+ * @param {Function|null} fn — (query: string, content: string) => number (0.0-1.0)
722
+ */
723
+ setEmbeddingFunction(fn) {
724
+ this.#embeddingFn = typeof fn === 'function' ? fn : null;
725
+ }
726
+
727
+ /**
728
+ * 获取当前嵌入函数 (用于检测是否已配置)
729
+ * @returns {Function|null}
730
+ */
731
+ getEmbeddingFunction() {
732
+ return this.#embeddingFn;
733
+ }
734
+
735
+ /**
736
+ * 使用嵌入函数计算语义相关性 (如已设置)
737
+ *
738
+ * @param {string} query
739
+ * @param {string} content
740
+ * @returns {number|null} — 0.0-1.0 或 null (无嵌入函数)
741
+ */
742
+ computeEmbeddingRelevance(query, content) {
743
+ if (!this.#embeddingFn) return null;
744
+ try {
745
+ return this.#embeddingFn(query, content);
746
+ } catch {
747
+ return null;
748
+ }
749
+ }
750
+
751
+ // ═══════════════════════════════════════════════════════════
752
+ // Private: 冲突预解决 (Mem0 风格)
753
+ // ═══════════════════════════════════════════════════════════
754
+
755
+ /**
756
+ * 在 consolidate 主流程前检测并解决矛盾
757
+ *
758
+ * @param {Array<object>} candidates
759
+ * @returns {{ processed: Array<object>, replaced: number }}
760
+ */
761
+ #preResolveConflicts(candidates) {
762
+ if (!candidates || candidates.length === 0) {
763
+ return { processed: [], replaced: 0 };
764
+ }
765
+
766
+ const processed = [];
767
+ let replaced = 0;
768
+
769
+ for (const candidate of candidates) {
770
+ const content = (candidate.content || '').trim();
771
+ if (!content || content.length < 5) {
772
+ processed.push(candidate);
773
+ continue;
774
+ }
775
+
776
+ try {
777
+ const similar = this.search(content, { limit: 3 });
778
+ let conflictResolved = false;
779
+
780
+ for (const existing of similar) {
781
+ if (existing.type === (candidate.type || 'fact')) {
782
+ const isContradiction = PersistentMemory.#detectContradiction(
783
+ existing.content,
784
+ content
785
+ );
786
+
787
+ if (isContradiction) {
788
+ this.update(existing.id, {
789
+ content: content.substring(0, 500),
790
+ importance: Math.max(existing.importance || 5, candidate.importance || 5),
791
+ });
792
+ conflictResolved = true;
793
+ replaced++;
794
+ this.#log(
795
+ `Conflict resolved: replaced "${existing.content.substring(0, 50)}..." with "${content.substring(0, 50)}..."`
796
+ );
797
+ break;
798
+ }
799
+ }
800
+ }
801
+
802
+ if (!conflictResolved) {
803
+ processed.push(candidate);
804
+ }
805
+ } catch {
806
+ processed.push(candidate);
807
+ }
808
+ }
809
+
810
+ return { processed, replaced };
811
+ }
812
+
813
+ /**
814
+ * 检测两段记忆内容是否矛盾
815
+ *
816
+ * @param {string} contentA — 现有记忆内容
817
+ * @param {string} contentB — 候选记忆内容
818
+ * @returns {boolean}
819
+ */
820
+ static #detectContradiction(contentA, contentB) {
821
+ if (!contentA || !contentB) return false;
822
+
823
+ const aNeg =
824
+ NEGATION_PATTERNS_ZH.test(contentA) || NEGATION_PATTERNS_EN.test(contentA);
825
+ const bNeg =
826
+ NEGATION_PATTERNS_ZH.test(contentB) || NEGATION_PATTERNS_EN.test(contentB);
827
+
828
+ if (aNeg === bNeg) return false;
829
+
830
+ const wordsA = PersistentMemory.#extractTopicWords(contentA);
831
+ const wordsB = PersistentMemory.#extractTopicWords(contentB);
832
+
833
+ let overlap = 0;
834
+ for (const w of wordsA) {
835
+ if (wordsB.has(w)) overlap++;
836
+ }
837
+
838
+ const minSize = Math.min(wordsA.size, wordsB.size);
839
+ if (minSize === 0) return false;
840
+
841
+ return overlap >= MIN_TOPIC_OVERLAP_WORDS || overlap / minSize >= MIN_TOPIC_OVERLAP_RATIO;
842
+ }
843
+
844
+ /**
845
+ * 提取主题词 (去停用词 + 短词)
846
+ * @param {string} text
847
+ * @returns {Set<string>}
848
+ */
849
+ static #extractTopicWords(text) {
850
+ if (!text) return new Set();
851
+
852
+ const tokens = text
853
+ .toLowerCase()
854
+ .split(/[\s,;:!?。,;:!?\-_/\\|()[\]{}'"<>·、]+/)
855
+ .filter((t) => t.length >= 2);
856
+
857
+ const stopWords = new Set([
858
+ '我们', '使用', '项目', '需要', '可以', '应该', '建议', '目前',
859
+ '已经', '这个', '那个', '一个', '进行', '通过', '对于',
860
+ 'the', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
861
+ 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would',
862
+ 'could', 'should', 'may', 'might', 'shall', 'can', 'this',
863
+ 'that', 'these', 'those', 'with', 'from', 'for', 'and',
864
+ 'but', 'not', 'all', 'any', 'each', 'every', 'some',
865
+ ]);
866
+
867
+ return new Set(tokens.filter((t) => !stopWords.has(t)));
868
+ }
869
+
870
+ /**
871
+ * 从 Memory.js 的 type 映射到 PersistentMemory 的 type
872
+ * @param {string} legacyType
873
+ * @returns {string}
874
+ */
875
+ static #mapLegacyType(legacyType) {
876
+ switch (legacyType) {
877
+ case 'preference':
878
+ return 'preference';
879
+ case 'decision':
880
+ return 'fact';
881
+ case 'context':
882
+ return 'fact';
883
+ default:
884
+ return 'fact';
885
+ }
886
+ }
887
+
888
+ // ═══════════════════════════════════════════════════════════
889
+ // Private: DB / SQL 基础设施
890
+ // ═══════════════════════════════════════════════════════════
575
891
 
576
892
  /**
577
893
  * 确保表存在 (用于 migration 未运行的情况)
@@ -679,9 +995,6 @@ export class ProjectSemanticMemory {
679
995
  };
680
996
  }
681
997
 
682
- /**
683
- * 获取所有活跃记忆 (未过期)
684
- */
685
998
  #getAllActive({ source, type } = {}) {
686
999
  const now = new Date().toISOString();
687
1000
  if (source && type) {
@@ -696,9 +1009,6 @@ export class ProjectSemanticMemory {
696
1009
  return this.#stmts.getAllActive.all({ now });
697
1010
  }
698
1011
 
699
- /**
700
- * 更新访问计数和最后访问时间
701
- */
702
1012
  #touchAccess(id) {
703
1013
  try {
704
1014
  this.#stmts.touchAccess.run({ id, now: new Date().toISOString() });
@@ -709,11 +1019,6 @@ export class ProjectSemanticMemory {
709
1019
 
710
1020
  /**
711
1021
  * 查找相似记忆 (基于 token overlap)
712
- *
713
- * @param {string} content
714
- * @param {string|null} type
715
- * @param {number} limit
716
- * @returns {Array<object & { similarity: number }>}
717
1022
  */
718
1023
  #findSimilar(content, type, limit) {
719
1024
  const now = new Date().toISOString();
@@ -735,14 +1040,6 @@ export class ProjectSemanticMemory {
735
1040
  return scored.slice(0, limit);
736
1041
  }
737
1042
 
738
- /**
739
- * 计算两段文本的相似度 (Jaccard + 子串) — 委托共享 similarity 模块
740
- *
741
- * @param {Set<string>} tokensA — 预分词的 token 集合
742
- * @param {string} lowerA — 小写原文
743
- * @param {string} contentB — 原始文本 B
744
- * @returns {number} 0.0-1.0
745
- */
746
1043
  #computeSimilarity(tokensA, lowerA, contentB) {
747
1044
  const lowerB = (contentB || '').toLowerCase();
748
1045
  const tokensB = tokenizeForSimilarity(lowerB);
@@ -755,21 +1052,11 @@ export class ProjectSemanticMemory {
755
1052
  }
756
1053
 
757
1054
  const jaccard = jaccardSimilarity(tokensA, tokensB);
758
-
759
- // 子串包含加分
760
1055
  const containsBonus = lowerA.includes(lowerB) || lowerB.includes(lowerA) ? 0.3 : 0;
761
1056
 
762
1057
  return Math.min(1.0, jaccard + containsBonus);
763
1058
  }
764
1059
 
765
- /**
766
- * 计算查询与记忆内容的相关性 (用于检索打分)
767
- *
768
- * @param {string} lowerQuery
769
- * @param {Set<string>} queryTokens
770
- * @param {string} content
771
- * @returns {number} 0.0-1.0
772
- */
773
1060
  #computeRelevance(lowerQuery, queryTokens, content) {
774
1061
  if (!lowerQuery || !content) {
775
1062
  return 0;
@@ -782,7 +1069,6 @@ export class ProjectSemanticMemory {
782
1069
  return 0;
783
1070
  }
784
1071
 
785
- // Token overlap
786
1072
  let matchCount = 0;
787
1073
  for (const t of queryTokens) {
788
1074
  if (contentTokens.has(t)) {
@@ -791,10 +1077,8 @@ export class ProjectSemanticMemory {
791
1077
  }
792
1078
  const tokenOverlap = matchCount / queryTokens.size;
793
1079
 
794
- // 子串匹配
795
1080
  const substringMatch = lowerContent.includes(lowerQuery) ? 0.4 : 0;
796
1081
 
797
- // 关键词部分匹配
798
1082
  let partialMatch = 0;
799
1083
  for (const qt of queryTokens) {
800
1084
  if (qt.length >= 3 && lowerContent.includes(qt)) {
@@ -806,11 +1090,6 @@ export class ProjectSemanticMemory {
806
1090
  return Math.min(1.0, tokenOverlap * 0.5 + substringMatch + partialMatch);
807
1091
  }
808
1092
 
809
- /**
810
- * 分词 (按空格/标点分割, 去短词) — 用于 relevance 计算
811
- * @param {string} text
812
- * @returns {Set<string>}
813
- */
814
1093
  #tokenizeWords(text) {
815
1094
  if (!text) {
816
1095
  return new Set();
@@ -823,9 +1102,6 @@ export class ProjectSemanticMemory {
823
1102
  );
824
1103
  }
825
1104
 
826
- /**
827
- * 反序列化 DB 行为 JS 对象
828
- */
829
1105
  #deserialize(row) {
830
1106
  return {
831
1107
  id: row.id,
@@ -847,9 +1123,6 @@ export class ProjectSemanticMemory {
847
1123
  };
848
1124
  }
849
1125
 
850
- /**
851
- * 安全 JSON 解析
852
- */
853
1126
  #safeParseJSON(str, fallback) {
854
1127
  try {
855
1128
  return str ? JSON.parse(str) : fallback;
@@ -858,9 +1131,6 @@ export class ProjectSemanticMemory {
858
1131
  }
859
1132
  }
860
1133
 
861
- /**
862
- * 容量控制: 超过 MAX_MEMORIES 时删除最不重要的
863
- */
864
1134
  #enforceCapacity() {
865
1135
  const count = this.#db.prepare('SELECT COUNT(*) as cnt FROM semantic_memories').get()?.cnt || 0;
866
1136
  if (count <= MAX_MEMORIES) {
@@ -881,15 +1151,15 @@ export class ProjectSemanticMemory {
881
1151
  this.#log(`Capacity enforced: removed ${excess} lowest-priority memories`);
882
1152
  }
883
1153
 
884
- /**
885
- * 日志输出
886
- */
887
1154
  #log(msg) {
888
- const formatted = `[SemanticMemory] ${msg}`;
889
- if (this.#logger) {
1155
+ const formatted = `[PersistentMemory] ${msg}`;
1156
+ if (this.#logger?.info) {
890
1157
  this.#logger.info(formatted);
891
1158
  }
892
1159
  }
893
1160
  }
894
1161
 
895
- export default ProjectSemanticMemory;
1162
+ // ── 向后兼容: 使用旧名称导入时仍可用 ──
1163
+ export { PersistentMemory as ProjectSemanticMemory };
1164
+
1165
+ export default PersistentMemory;
@@ -519,7 +519,7 @@ export class SessionStore {
519
519
  }
520
520
 
521
521
  // ═══════════════════════════════════════════════════════
522
- // §7: 蒸馏上下文 (新: for ProducerAgent, B2 fix)
522
+ // §7: 蒸馏上下文 (for PipelineStrategy produce 阶段)
523
523
  // ═══════════════════════════════════════════════════════
524
524
 
525
525
  /**
@@ -4,7 +4,7 @@
4
4
  * Phase 2: MemoryCoordinator + legacy module re-exports
5
5
  * Phase 3: ActiveContext (合并 WorkingMemory + ReasoningTrace)
6
6
  * Phase 4: SessionStore (合并 EpisodicMemory + ToolResultCache)
7
- * Phase 5: PersistentMemory (继承 ProjectSemanticMemory + 增强)
7
+ * Phase 5: PersistentMemory (统一的持久化语义记忆)
8
8
  */
9
9
 
10
10
  export { MemoryCoordinator } from './MemoryCoordinator.js';