mindforge-memory 6.4.1__py3-none-any.whl

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 (64) hide show
  1. MindForge.py +20 -0
  2. mindforge/__init__.py +111 -0
  3. mindforge/adapters/__init__.py +18 -0
  4. mindforge/adapters/claude_adapter.py +84 -0
  5. mindforge/adapters/crewai_memory.py +76 -0
  6. mindforge/adapters/generic_api.py +183 -0
  7. mindforge/adapters/langgraph_store.py +463 -0
  8. mindforge/adapters/openclaw_adapter.py +91 -0
  9. mindforge/api/__init__.py +1 -0
  10. mindforge/api/server.py +1567 -0
  11. mindforge/cli/__init__.py +1 -0
  12. mindforge/cli/main.py +13607 -0
  13. mindforge/core/__init__.py +37 -0
  14. mindforge/core/embedding.py +847 -0
  15. mindforge/core/encryption.py +597 -0
  16. mindforge/core/indexer.py +363 -0
  17. mindforge/core/mindforge.py +6298 -0
  18. mindforge/core/paths.py +43 -0
  19. mindforge/core/query.py +501 -0
  20. mindforge/core/storage.py +18656 -0
  21. mindforge/core/types.py +314 -0
  22. mindforge/core/version.py +56 -0
  23. mindforge/daemon/__init__.py +0 -0
  24. mindforge/daemon/server.py +277 -0
  25. mindforge/mcp/__init__.py +4 -0
  26. mindforge/mcp/server.py +2559 -0
  27. mindforge/modules/__init__.py +138 -0
  28. mindforge/modules/categorizer.py +151 -0
  29. mindforge/modules/conflict_detector.py +356 -0
  30. mindforge/modules/connectors.py +331 -0
  31. mindforge/modules/context_window.py +337 -0
  32. mindforge/modules/event_bus.py +572 -0
  33. mindforge/modules/evolution.py +210 -0
  34. mindforge/modules/experience.py +402 -0
  35. mindforge/modules/federated.py +722 -0
  36. mindforge/modules/federated_acl.py +393 -0
  37. mindforge/modules/follow_up.py +315 -0
  38. mindforge/modules/hybrid_search.py +565 -0
  39. mindforge/modules/importer.py +374 -0
  40. mindforge/modules/integrator.py +196 -0
  41. mindforge/modules/intent_router.py +359 -0
  42. mindforge/modules/knowledge_graph.py +395 -0
  43. mindforge/modules/memory_compressor.py +592 -0
  44. mindforge/modules/memory_decay.py +474 -0
  45. mindforge/modules/memory_passport.py +250 -0
  46. mindforge/modules/multi_agent.py +534 -0
  47. mindforge/modules/multimodal.py +304 -0
  48. mindforge/modules/outcome_learning.py +186 -0
  49. mindforge/modules/personality.py +275 -0
  50. mindforge/modules/principled_forgetting.py +258 -0
  51. mindforge/modules/privacy.py +736 -0
  52. mindforge/modules/recall.py +263 -0
  53. mindforge/modules/session_focus.py +305 -0
  54. mindforge/modules/share_conflict.py +521 -0
  55. mindforge/modules/skill_extractor.py +340 -0
  56. mindforge/modules/skill_pack.py +205 -0
  57. mindforge/modules/timeline_viz.py +206 -0
  58. mindforge/modules/v6.py +931 -0
  59. mindforge_memory-6.4.1.dist-info/METADATA +281 -0
  60. mindforge_memory-6.4.1.dist-info/RECORD +64 -0
  61. mindforge_memory-6.4.1.dist-info/WHEEL +5 -0
  62. mindforge_memory-6.4.1.dist-info/entry_points.txt +3 -0
  63. mindforge_memory-6.4.1.dist-info/licenses/LICENSE +21 -0
  64. mindforge_memory-6.4.1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,138 @@
1
+ """
2
+ MindForge v5.0 功能模块
3
+ 知识图谱 · 记忆演化 · 人格化 · 多模态 · 联邦记忆 · 多Agent记忆空间
4
+ """
5
+
6
+ from .recall import RecallEngine, RecallConfig
7
+ from .knowledge_graph import KnowledgeGraph, KnowledgeEntity, KnowledgeRelation
8
+ from .evolution import MemoryEvolution, ForgettingCurve
9
+ from .personality import PersonalityEngine, UserProfile
10
+ from .multimodal import MultimodalMemory, MultimodalType
11
+ from .federated import FederatedMemory, FederatedPeer
12
+ from .categorizer import TaxonomyManager
13
+ from .privacy import PrivacyEngine, PrivacyScanResult
14
+ from .integrator import MemoryIntegrator
15
+ from .multi_agent import MultiAgentMemoryManager, AgentSpace, SpaceMember, SpaceRole
16
+ # v5.3.9 新增五大能力
17
+ from .intent_router import IntentRouter, IntentResult, INTENT_DEFS, DEFAULT_INTENT
18
+ from .conflict_detector import ConflictDetector, ConflictPair, DecayAction, ANTONYM_PAIRS
19
+ from .skill_extractor import SkillExtractor, SkillTemplate, SkillSlot
20
+ from .hybrid_search import (QueryExpander, ExpansionResult,
21
+ CrossEncoderReranker, RerankResult, RerankWeights)
22
+ from .session_focus import SessionFocus, TopicCluster, FocusSummary
23
+ # v5.4.2 新增两大能力
24
+ from .federated_acl import FederatedACLManager
25
+ from .share_conflict import SharedConflictResolver
26
+ # v6.0.0 新增:一键导入 / 时间线可视化 / 技能包 / 演变 / 结果学习 / 护照 / 遗忘 / 收尾
27
+ from .importer import import_chat_history, parse_export, detect_platform
28
+ from .timeline_viz import build_timeline_visual
29
+ from .skill_pack import export_skill_pack, validate_skill_pack, import_skill_pack
30
+ from .outcome_learning import apply_outcome, outcome_stats, search_weighted
31
+ from .memory_passport import (
32
+ export_passport, import_passport, revoke_passport, passport_status,
33
+ )
34
+ from .principled_forgetting import (
35
+ explain_forgetting, run_principled_gc, forgetting_stats,
36
+ )
37
+ from .follow_up import (
38
+ create_follow_up, list_due, resolve_follow_up, list_follow_ups,
39
+ )
40
+ # v6.0.0 扩展端口(多 Agent 协作 + 6G AI 记忆预览,正式发布)
41
+ from .v6 import (
42
+ V6_TARGET_VERSION,
43
+ V6Capability,
44
+ V6Status,
45
+ V6Port,
46
+ MultiAgentCollaborationPort,
47
+ MultiAgentAdapterPort,
48
+ MemoryPreviewPort,
49
+ create_multi_agent_adapter,
50
+ register_v6_port,
51
+ unregister_v6_port,
52
+ get_v6_port,
53
+ list_v6_ports,
54
+ v6_status,
55
+ )
56
+
57
+ __all__ = [
58
+ "RecallEngine",
59
+ "RecallConfig",
60
+ "KnowledgeGraph",
61
+ "KnowledgeEntity",
62
+ "KnowledgeRelation",
63
+ "MemoryEvolution",
64
+ "ForgettingCurve",
65
+ "PersonalityEngine",
66
+ "UserProfile",
67
+ "MultimodalMemory",
68
+ "MultimodalType",
69
+ "FederatedMemory",
70
+ "FederatedPeer",
71
+ "TaxonomyManager",
72
+ "PrivacyEngine",
73
+ "PrivacyScanResult",
74
+ "MemoryIntegrator",
75
+ "MultiAgentMemoryManager",
76
+ "AgentSpace",
77
+ "SpaceMember",
78
+ "SpaceRole",
79
+ # v5.3.9
80
+ "IntentRouter",
81
+ "IntentResult",
82
+ "INTENT_DEFS",
83
+ "DEFAULT_INTENT",
84
+ "ConflictDetector",
85
+ "ConflictPair",
86
+ "DecayAction",
87
+ "ANTONYM_PAIRS",
88
+ "SkillExtractor",
89
+ "SkillTemplate",
90
+ "SkillSlot",
91
+ "QueryExpander",
92
+ "ExpansionResult",
93
+ "CrossEncoderReranker",
94
+ "RerankResult",
95
+ "RerankWeights",
96
+ "SessionFocus",
97
+ "TopicCluster",
98
+ "FocusSummary",
99
+ # v5.4.2
100
+ "FederatedACLManager",
101
+ "SharedConflictResolver",
102
+ # v6.0.0 功能 1-8
103
+ "import_chat_history",
104
+ "parse_export",
105
+ "detect_platform",
106
+ "build_timeline_visual",
107
+ "export_skill_pack",
108
+ "validate_skill_pack",
109
+ "import_skill_pack",
110
+ "apply_outcome",
111
+ "outcome_stats",
112
+ "search_weighted",
113
+ "export_passport",
114
+ "import_passport",
115
+ "revoke_passport",
116
+ "passport_status",
117
+ "explain_forgetting",
118
+ "run_principled_gc",
119
+ "forgetting_stats",
120
+ "create_follow_up",
121
+ "list_due",
122
+ "resolve_follow_up",
123
+ "list_follow_ups",
124
+ # v6.0.0 端口预留
125
+ "V6_TARGET_VERSION",
126
+ "V6Capability",
127
+ "V6Status",
128
+ "V6Port",
129
+ "MultiAgentCollaborationPort",
130
+ "MultiAgentAdapterPort",
131
+ "MemoryPreviewPort",
132
+ "create_multi_agent_adapter",
133
+ "register_v6_port",
134
+ "unregister_v6_port",
135
+ "get_v6_port",
136
+ "list_v6_ports",
137
+ "v6_status",
138
+ ]
@@ -0,0 +1,151 @@
1
+ """
2
+ MindForge v5.0 分类管理模块
3
+ """
4
+
5
+ import re
6
+ from dataclasses import dataclass, field
7
+ from typing import List, Dict, Optional
8
+
9
+
10
+ @dataclass
11
+ class CategoryNode:
12
+ name: str
13
+ parent: Optional[str] = None
14
+ children: List[str] = field(default_factory=list)
15
+ keywords: List[str] = field(default_factory=list)
16
+ description: str = ""
17
+
18
+
19
+ DEFAULT_TAXONOMY = {
20
+ "work": {
21
+ "name": "工作",
22
+ "keywords": ["工作", "项目", "任务", "会议", "报告", "同事", "老板", "公司", "业务"],
23
+ "children": ["tech", "project", "meeting"],
24
+ },
25
+ "tech": {
26
+ "name": "技术",
27
+ "keywords": ["技术", "编程", "代码", "开发", "bug", "算法", "架构", "设计"],
28
+ "parent": "work",
29
+ "children": ["programming", "devops", "design"],
30
+ },
31
+ "programming": {
32
+ "name": "编程开发",
33
+ "keywords": ["python", "java", "javascript", "函数", "类", "接口", "api"],
34
+ "parent": "tech",
35
+ },
36
+ "life": {
37
+ "name": "生活",
38
+ "keywords": ["生活", "吃饭", "睡觉", "家庭", "朋友", "旅行", "美食", "电影"],
39
+ "children": ["health", "hobby"],
40
+ },
41
+ "health": {
42
+ "name": "健康",
43
+ "keywords": ["健康", "运动", "健身", "饮食", "睡眠", "体检", "疾病"],
44
+ "parent": "life",
45
+ },
46
+ "learning": {
47
+ "name": "学习",
48
+ "keywords": ["学习", "研究", "阅读", "书籍", "课程", "笔记", "知识"],
49
+ "children": [],
50
+ },
51
+ "idea": {
52
+ "name": "创意",
53
+ "keywords": ["创意", "想法", "灵感", "设计", "发明", "创新", "产品"],
54
+ "children": [],
55
+ },
56
+ "personal": {
57
+ "name": "个人",
58
+ "keywords": ["我", "自己", "个人", "隐私", "秘密", "想法", "感受"],
59
+ "children": [],
60
+ },
61
+ }
62
+
63
+
64
+ class TaxonomyManager:
65
+ """分类管理器"""
66
+
67
+ def __init__(self, taxonomy: Optional[Dict] = None):
68
+ self.taxonomy = taxonomy or DEFAULT_TAXONOMY
69
+ self._build_keyword_index()
70
+
71
+ def _build_keyword_index(self):
72
+ self._keyword_map: Dict[str, str] = {}
73
+ for cat_id, cat_data in self.taxonomy.items():
74
+ for keyword in cat_data.get("keywords", []):
75
+ self._keyword_map[keyword.lower()] = cat_id
76
+
77
+ def suggest_category(self, content: str) -> str:
78
+ """建议分类"""
79
+ content_lower = content.lower()
80
+ scores: Dict[str, int] = {}
81
+
82
+ for keyword, cat_id in self._keyword_map.items():
83
+ if keyword in content_lower:
84
+ scores[cat_id] = scores.get(cat_id, 0) + 1
85
+
86
+ if not scores:
87
+ return "general"
88
+
89
+ best_category = max(scores.items(), key=lambda x: x[1])[0]
90
+ return best_category
91
+
92
+ def suggest_tags(self, content: str, max_tags: int = 5) -> List[str]:
93
+ """建议标签"""
94
+ content_lower = content.lower()
95
+ tags = []
96
+
97
+ for keyword, cat_id in self._keyword_map.items():
98
+ if keyword in content_lower:
99
+ if keyword not in tags:
100
+ tags.append(keyword)
101
+
102
+ words = re.findall(r'[\w\u4e00-\u9fff]+', content_lower)
103
+ from collections import Counter
104
+ word_counts = Counter(words)
105
+ for word, count in word_counts.most_common(10):
106
+ if len(word) > 2 and word not in tags and count >= 1:
107
+ tags.append(word)
108
+ if len(tags) >= max_tags:
109
+ break
110
+
111
+ return tags[:max_tags]
112
+
113
+ def get_category_info(self, category_id: str) -> Optional[Dict]:
114
+ """获取分类信息"""
115
+ return self.taxonomy.get(category_id)
116
+
117
+ def get_all_categories(self) -> Dict[str, Dict]:
118
+ """获取所有分类"""
119
+ return self.taxonomy
120
+
121
+ def get_subcategories(self, parent_id: str) -> List[str]:
122
+ """获取子分类"""
123
+ parent = self.taxonomy.get(parent_id)
124
+ if parent:
125
+ return parent.get("children", [])
126
+ return []
127
+
128
+ def get_parent_category(self, category_id: str) -> Optional[str]:
129
+ """获取父分类"""
130
+ cat = self.taxonomy.get(category_id)
131
+ if cat:
132
+ return cat.get("parent")
133
+ return None
134
+
135
+ def add_category(self, category_id: str, name: str,
136
+ keywords: Optional[List[str]] = None,
137
+ parent: Optional[str] = None):
138
+ """添加分类"""
139
+ self.taxonomy[category_id] = {
140
+ "name": name,
141
+ "keywords": keywords or [],
142
+ "parent": parent,
143
+ "children": [],
144
+ }
145
+
146
+ if parent and parent in self.taxonomy:
147
+ if category_id not in self.taxonomy[parent]["children"]:
148
+ self.taxonomy[parent]["children"].append(category_id)
149
+
150
+ for keyword in keywords or []:
151
+ self._keyword_map[keyword.lower()] = category_id
@@ -0,0 +1,356 @@
1
+ """
2
+ MindForge 矛盾检测 + 自动衰减引擎
3
+ ========================================
4
+ 三类检测:
5
+ A. 直接反义对(词级别,如 启用 / 禁用)
6
+ B. 属性值冲突("价格是 X" vs "价格是 Y, X≠Y")
7
+ C. 时间线冲突("已于周一完成" vs "预计周三完成")
8
+
9
+ 自动衰减策略:
10
+ - 同主体冲突 → 老记忆重要性衰减、tag 标记 conflict
11
+ - 显式 resolve 后,胜出记忆 +0.08 重要性(构造参数
12
+ `auto_boost_resolve_winner`,v6.1.2 校正:文档原写 +0.1,与代码默认值不符),
13
+ 失败方 -0.20(`auto_decay_resolve_loser`)
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ from dataclasses import dataclass, field
19
+ from typing import Any, Dict, List, Optional, Tuple
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # 反义词典(内置常见技术/生活反义对)
23
+ # ---------------------------------------------------------------------------
24
+
25
+ ANTONYM_PAIRS: List[Tuple[str, str]] = [
26
+ ("启用", "禁用"), ("开启", "关闭"), ("打开", "关闭"), ("启动", "停止"),
27
+ ("允许", "禁止"), ("允许", "拒绝"), ("通过", "驳回"), ("成功", "失败"),
28
+ ("新增", "删除"), ("添加", "移除"), ("增加", "减少"), ("上升", "下降"),
29
+ ("提高", "降低"), ("支持", "反对"), ("兼容", "不兼容"), ("安全", "危险"),
30
+ ("公开", "私有"), ("加密", "明文"), ("同步", "异步"), ("阻塞", "非阻塞"),
31
+ ("真", "假"), ("是", "否"), ("对", "错"), ("正确", "错误"),
32
+ ("稳定", "不稳定"), ("可用", "不可用"), ("正常", "异常"), ("存在", "不存在"),
33
+ ("完成", "未完成"), ("已", "未"), ("可以", "不能"), ("应该", "不该"),
34
+ # v5.4.7 修复:添加常见技术偏好反义对
35
+ ("vim", "vscode"), ("vim", "vs code"), ("emacs", "vim"),
36
+ ("python", "java"), ("javascript", "typescript"), ("react", "vue"),
37
+ ("mac", "windows"), ("linux", "windows"), ("ios", "android"),
38
+ ("git", "svn"), ("docker", "vm"), ("kubernetes", "docker swarm"),
39
+ ("rest", "graphql"), ("sql", "nosql"), ("mongodb", "postgresql"),
40
+ ]
41
+
42
+ # 属性抽取正则:<属性名> 是/为/=/: <值>
43
+ _ATTR_RE = re.compile(
44
+ r"([\u4e00-\u9fa5A-Za-z][\u4e00-\u9fa5A-Za-z0-9_\+\-\.\*#]{0,19})"
45
+ r"\s*(?:是|为|=|等于|:|:)\s*"
46
+ r"([\u4e00-\u9fa5A-Za-z0-9_\-\+\.\*#/%@\$]{1,40})"
47
+ )
48
+
49
+ # v5.4.7 修复:添加偏好抽取正则,检测 "喜欢/偏好 X" 模式
50
+ _PREFERENCE_RE = re.compile(
51
+ r"(?:喜欢|偏好|爱用|常用|主要用|习惯用)\s*"
52
+ r"([\u4e00-\u9fa5A-Za-z0-9_\-\+\.\*#/%@\$]{1,40})"
53
+ )
54
+
55
+ # 时间词
56
+ _TIME_WORDS = {
57
+ "周一": 1, "周二": 2, "周三": 3, "周四": 4, "周五": 5, "周六": 6, "周日": 7, "周天": 7,
58
+ "星期一": 1, "星期二": 2, "星期三": 3, "星期四": 4, "星期五": 5, "星期六": 6, "星期日": 7,
59
+ "一月": 1, "二月": 2, "三月": 3, "四月": 4, "五月": 5, "六月": 6, "七月": 7,
60
+ "八月": 8, "九月": 9, "十月": 10, "十一月": 11, "十二月": 12,
61
+ "今天": 0, "明天": 1, "后天": 2, "昨天": -1, "前天": -2,
62
+ }
63
+ _STATUS_PATTERNS = [
64
+ (re.compile(r"(已|已经|早就|完成了|做完了|搞定了)\s*[^\s,,。;;]{0,10}(完成|做完|上线|发布|提交|合并)"), "done"),
65
+ (re.compile(r"(预计|计划|准备|将于|要|还没|尚未|未完成|待办|准备)\s*[^\s,,。;;]{0,10}(完成|上线|发布|提交|合并|做|弄)"), "pending"),
66
+ ]
67
+
68
+
69
+ # ---------------------------------------------------------------------------
70
+ # 数据类
71
+ # ---------------------------------------------------------------------------
72
+
73
+ @dataclass
74
+ class ConflictPair:
75
+ memory_id_a: str
76
+ memory_id_b: str
77
+ conflict_type: str # antonym / attribute / timeline
78
+ severity: float # 0.0 ~ 1.0
79
+ evidence: Dict[str, Any] = field(default_factory=dict)
80
+ suggestion: str = ""
81
+ resolution: str = "unresolved" # unresolved / a_wins / b_wins / both_deprecated
82
+
83
+ def to_dict(self) -> Dict[str, Any]:
84
+ return {
85
+ "memory_id_a": self.memory_id_a,
86
+ "memory_id_b": self.memory_id_b,
87
+ "conflict_type": self.conflict_type,
88
+ "severity": round(self.severity, 3),
89
+ "evidence": self.evidence,
90
+ "suggestion": self.suggestion,
91
+ "resolution": self.resolution,
92
+ }
93
+
94
+
95
+ @dataclass
96
+ class DecayAction:
97
+ memory_id: str
98
+ delta_importance: float # 例如 -0.15
99
+ added_tags: List[str] = field(default_factory=list)
100
+ reason: str = ""
101
+
102
+ def to_dict(self) -> Dict[str, Any]:
103
+ return {
104
+ "memory_id": self.memory_id,
105
+ "delta_importance": round(self.delta_importance, 3),
106
+ "added_tags": self.added_tags,
107
+ "reason": self.reason,
108
+ }
109
+
110
+
111
+ # ---------------------------------------------------------------------------
112
+ # 引擎
113
+ # ---------------------------------------------------------------------------
114
+
115
+ class ConflictDetector:
116
+ """纯 CPU 实现的矛盾检测 + 自动衰减。无外部依赖。"""
117
+
118
+ def __init__(self,
119
+ storage_or_antonym_pairs=None,
120
+ antonym_threshold: int = 1,
121
+ attr_match_min_length: int = 2,
122
+ auto_decay_old: float = 0.12,
123
+ auto_decay_resolve_loser: float = 0.20,
124
+ auto_boost_resolve_winner: float = 0.08):
125
+ # v5.4.7 修复:兼容 README 示例中的 ConflictDetector(storage) 用法
126
+ # 如果第一个参数是 StorageEngine 实例,则忽略它(当前版本不需要)
127
+ # 如果是 list/tuple,则作为反义词对列表
128
+ if storage_or_antonym_pairs is None:
129
+ antonym_pairs = None
130
+ elif isinstance(storage_or_antonym_pairs, (list, tuple)):
131
+ antonym_pairs = storage_or_antonym_pairs
132
+ else:
133
+ # 假设是 storage 或其他对象,忽略
134
+ antonym_pairs = None
135
+
136
+ self.antonyms = list(antonym_pairs or ANTONYM_PAIRS)
137
+ self.antonym_threshold = antonym_threshold
138
+ self.attr_min_len = attr_match_min_length
139
+ self.auto_decay_old = auto_decay_old
140
+ self.auto_decay_loser = auto_decay_resolve_loser
141
+ self.auto_boost_winner = auto_boost_resolve_winner
142
+
143
+ # -- detector primitives -----------------------------------------------
144
+
145
+ @staticmethod
146
+ def _tokens(text: str) -> set:
147
+ # 简单中文切词:按标点/空白 + 2-gram 组合 + 中文 2~4 字片段
148
+ raw = re.sub(r"[\s,,。;;::!!??、()()\[\]【】\"'《》\-—/\\]+", " ", text)
149
+ parts = [p for p in raw.split() if p]
150
+ bag = set(parts)
151
+ for p in parts:
152
+ if len(p) >= 2:
153
+ for n in (2, 3):
154
+ for i in range(0, len(p) - n + 1):
155
+ bag.add(p[i:i + n])
156
+ return bag
157
+
158
+ def _antonym_hit(self, a: str, b: str) -> Tuple[int, List[str]]:
159
+ count = 0
160
+ pairs_hit: List[str] = []
161
+ ta, tb = self._tokens(a), self._tokens(b)
162
+ for x, y in self.antonyms:
163
+ if (x in ta and y in tb) or (y in ta and x in tb):
164
+ count += 1
165
+ pairs_hit.append(f"{x}↔{y}")
166
+ return count, pairs_hit
167
+
168
+ def _extract_attrs(self, text: str) -> Dict[str, str]:
169
+ attrs: Dict[str, str] = {}
170
+ for m in _ATTR_RE.finditer(text):
171
+ name, value = m.group(1).strip(), m.group(2).strip()
172
+ if len(name) >= self.attr_min_len and len(value) >= 1:
173
+ attrs[name] = value
174
+ return attrs
175
+
176
+ def _extract_preferences(self, text: str) -> List[str]:
177
+ """v5.4.7 修复:抽取偏好项,如 '喜欢 Vim' -> ['vim']"""
178
+ prefs = []
179
+ for m in _PREFERENCE_RE.finditer(text):
180
+ pref = m.group(1).strip().lower()
181
+ if len(pref) >= 2:
182
+ prefs.append(pref)
183
+ return prefs
184
+
185
+ def _timeline_status(self, text: str) -> Optional[str]:
186
+ for pattern, status in _STATUS_PATTERNS:
187
+ if pattern.search(text):
188
+ return status
189
+ return None
190
+
191
+ # -- public API --------------------------------------------------------
192
+
193
+ def detect_antonym(self, text_a: str, text_b: str,
194
+ id_a: str, id_b: str) -> Optional[ConflictPair]:
195
+ cnt, pairs = self._antonym_hit(text_a, text_b)
196
+ if cnt < self.antonym_threshold:
197
+ return None
198
+ severity = min(0.35 + cnt * 0.20, 1.0)
199
+ return ConflictPair(
200
+ memory_id_a=id_a, memory_id_b=id_b,
201
+ conflict_type="antonym", severity=severity,
202
+ evidence={"antonym_pairs": pairs, "hit_count": cnt},
203
+ suggestion="反义词同现,建议人工核验以较新记忆或上下文一致者为准",
204
+ )
205
+
206
+ def detect_preference(self, text_a: str, text_b: str,
207
+ id_a: str, id_b: str) -> Optional[ConflictPair]:
208
+ """v5.4.7 修复:检测偏好冲突,如 '喜欢 Vim' vs '喜欢 VS Code'"""
209
+ prefs_a = self._extract_preferences(text_a)
210
+ prefs_b = self._extract_preferences(text_b)
211
+ if not prefs_a or not prefs_b:
212
+ return None
213
+ # 检查是否有不同的偏好项
214
+ set_a, set_b = set(prefs_a), set(prefs_b)
215
+ if set_a == set_b:
216
+ return None # 相同偏好,不冲突
217
+ # 检查是否有交集(同一类别但不同选择)
218
+ # 例如:都提到编辑器但选择不同
219
+ conflicts = []
220
+ for pa in set_a:
221
+ for pb in set_b:
222
+ if pa != pb:
223
+ # v5.4.8 P3-001 修复:收紧长度差阈值,添加额外启发式
224
+ # 1. 长度差 <= 2(更严格)
225
+ # 2. 首字母相同(同类事物的常见模式)
226
+ # 3. 一个包含另一个(如 vim/vimrc)
227
+ len_diff_ok = abs(len(pa) - len(pb)) <= 2
228
+ same_first_letter = pa[0] == pb[0] if pa and pb else False
229
+ substring_rel = pa in pb or pb in pa
230
+
231
+ if len_diff_ok or same_first_letter or substring_rel:
232
+ conflicts.append((pa, pb))
233
+ if not conflicts:
234
+ return None
235
+ severity = min(0.40 + 0.10 * len(conflicts), 0.90)
236
+ return ConflictPair(
237
+ memory_id_a=id_a, memory_id_b=id_b,
238
+ conflict_type="preference", severity=severity,
239
+ evidence={"preference_conflicts": conflicts, "count": len(conflicts)},
240
+ suggestion=f"偏好不一致:{', '.join(f'{a} vs {b}' for a, b in conflicts[:3])}",
241
+ )
242
+
243
+ def detect_attribute(self, text_a: str, text_b: str,
244
+ id_a: str, id_b: str) -> Optional[ConflictPair]:
245
+ a_attrs = self._extract_attrs(text_a)
246
+ b_attrs = self._extract_attrs(text_b)
247
+ shared = [k for k in a_attrs if k in b_attrs and a_attrs[k] != b_attrs[k]]
248
+ if not shared:
249
+ return None
250
+ conflicts = {k: (a_attrs[k], b_attrs[k]) for k in shared}
251
+ severity = min(0.30 + 0.12 * len(shared), 0.95)
252
+ return ConflictPair(
253
+ memory_id_a=id_a, memory_id_b=id_b,
254
+ conflict_type="attribute", severity=severity,
255
+ evidence={"attribute_conflicts": conflicts, "count": len(shared)},
256
+ suggestion=f"以下 {len(shared)} 个属性取值不一致:{', '.join(shared)}",
257
+ )
258
+
259
+ def detect_timeline(self, text_a: str, text_b: str,
260
+ id_a: str, id_b: str) -> Optional[ConflictPair]:
261
+ sa, sb = self._timeline_status(text_a), self._timeline_status(text_b)
262
+ if not sa or not sb or sa == sb:
263
+ return None
264
+ if {sa, sb} == {"done", "pending"}:
265
+ severity = 0.65
266
+ return ConflictPair(
267
+ memory_id_a=id_a, memory_id_b=id_b,
268
+ conflict_type="timeline", severity=severity,
269
+ evidence={"status_a": sa, "status_b": sb},
270
+ suggestion="同一件事出现『已完成』vs『待完成』矛盾,请以最近时间戳或显式更新者为准",
271
+ )
272
+ return None
273
+
274
+ # -- batch scan --------------------------------------------------------
275
+
276
+ def scan_memories(self,
277
+ memories: List[Dict[str, Any]],
278
+ max_pairs: int = 1000) -> List[ConflictPair]:
279
+ """扫描一批记忆并返回所有冲突对。
280
+ memories: 每一项必须至少含 id, content 字段,可选 created_at。
281
+ """
282
+ results: List[ConflictPair] = []
283
+ n = len(memories)
284
+ # 两两比较,O(n^2),对大样本先按标签/关键词分桶以减少比较
285
+ checked = 0
286
+ for i in range(n):
287
+ mi = memories[i]
288
+ for j in range(i + 1, n):
289
+ mj = memories[j]
290
+ checked += 1
291
+ if checked > max_pairs:
292
+ return results
293
+ ta, tb = str(mi.get("content", "")), str(mj.get("content", ""))
294
+ id_a, id_b = str(mi.get("id", i)), str(mj.get("id", j))
295
+ for detector in (self.detect_antonym, self.detect_attribute, self.detect_timeline, self.detect_preference):
296
+ pair = detector(ta, tb, id_a, id_b)
297
+ if pair is not None:
298
+ results.append(pair)
299
+ # 按严重度排序
300
+ results.sort(key=lambda p: p.severity, reverse=True)
301
+ return results
302
+
303
+ # -- auto decay --------------------------------------------------------
304
+
305
+ def plan_decay(self, conflicts: List[ConflictPair],
306
+ memories_by_id: Dict[str, Dict[str, Any]]) -> List[DecayAction]:
307
+ """基于冲突结果生成衰减动作。未 resolve 的双方,老的那条减去 auto_decay_old,并打上 conflict 标签。"""
308
+ actions: Dict[str, DecayAction] = {}
309
+
310
+ def _get(mid: str) -> Dict[str, Any]:
311
+ return memories_by_id.get(mid, {"id": mid, "created_at": None})
312
+
313
+ for c in conflicts:
314
+ a, b = c.memory_id_a, c.memory_id_b
315
+ if c.resolution == "unresolved":
316
+ # 谁老谁减
317
+ ca_time = _get(a).get("created_at") or 0
318
+ cb_time = _get(b).get("created_at") or 0
319
+ if ca_time and cb_time:
320
+ older = a if ca_time < cb_time else b
321
+ else:
322
+ older = a if len(a) < len(b) else b
323
+ self._merge_action(actions, older, -self.auto_decay_old, ["conflict"],
324
+ reason=f"与{('b' if older==a else 'a')}冲突类型={c.conflict_type}")
325
+ elif c.resolution == "a_wins":
326
+ self._merge_action(actions, a, +self.auto_boost_winner, [], reason=f"冲突胜出: {c.conflict_type}")
327
+ self._merge_action(actions, b, -self.auto_decay_loser, ["conflict-deprecated"],
328
+ reason=f"冲突失败: {c.conflict_type}")
329
+ elif c.resolution == "b_wins":
330
+ self._merge_action(actions, b, +self.auto_boost_winner, [], reason=f"冲突胜出: {c.conflict_type}")
331
+ self._merge_action(actions, a, -self.auto_decay_loser, ["conflict-deprecated"],
332
+ reason=f"冲突失败: {c.conflict_type}")
333
+ elif c.resolution == "both_deprecated":
334
+ self._merge_action(actions, a, -self.auto_decay_loser, ["conflict-deprecated"],
335
+ reason=f"双方均弃用: {c.conflict_type}")
336
+ self._merge_action(actions, b, -self.auto_decay_loser, ["conflict-deprecated"],
337
+ reason=f"双方均弃用: {c.conflict_type}")
338
+ return list(actions.values())
339
+
340
+ @staticmethod
341
+ def _merge_action(bucket: Dict[str, DecayAction], mid: str,
342
+ delta: float, tags: List[str], reason: str):
343
+ if mid not in bucket:
344
+ bucket[mid] = DecayAction(memory_id=mid, delta_importance=0.0, added_tags=[], reason="")
345
+ a = bucket[mid]
346
+ a.delta_importance += delta
347
+ for t in tags:
348
+ if t not in a.added_tags:
349
+ a.added_tags.append(t)
350
+ if a.reason:
351
+ a.reason += " | " + reason
352
+ else:
353
+ a.reason = reason
354
+
355
+
356
+ __all__ = ["ConflictDetector", "ConflictPair", "DecayAction", "ANTONYM_PAIRS"]