claude-memory-agent 2.0.1 → 2.2.0
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/README.md +206 -206
- package/agent_card.py +186 -0
- package/bin/cli.js +327 -185
- package/bin/lib/banner.js +39 -0
- package/bin/lib/environment.js +166 -0
- package/bin/lib/installer.js +291 -0
- package/bin/lib/models.js +95 -0
- package/bin/lib/steps/advanced.js +101 -0
- package/bin/lib/steps/confirm.js +87 -0
- package/bin/lib/steps/model.js +57 -0
- package/bin/lib/steps/provider.js +65 -0
- package/bin/lib/steps/scope.js +59 -0
- package/bin/lib/steps/server.js +74 -0
- package/bin/lib/ui.js +75 -0
- package/bin/onboarding.js +164 -0
- package/bin/postinstall.js +35 -270
- package/config.py +103 -4
- package/dashboard.html +4902 -2689
- package/hooks/extract_memories.py +439 -0
- package/hooks/grounding-hook.py +422 -348
- package/hooks/pre_compact_hook.py +76 -0
- package/hooks/session_end.py +293 -192
- package/hooks/session_end_hook.py +149 -0
- package/hooks/session_start.py +227 -227
- package/hooks/stop_hook.py +372 -0
- package/install.py +972 -902
- package/main.py +5240 -2859
- package/mcp_server.py +451 -0
- package/package.json +58 -47
- package/requirements.txt +12 -8
- package/services/__init__.py +50 -50
- package/services/adaptive_ranker.py +272 -0
- package/services/agent_catalog.json +153 -0
- package/services/agent_registry.py +245 -730
- package/services/claude_md_sync.py +320 -4
- package/services/consolidation.py +417 -0
- package/services/curator.py +1606 -0
- package/services/database.py +4118 -2485
- package/services/embedding_pipeline.py +262 -0
- package/services/embeddings.py +493 -85
- package/services/memory_decay.py +408 -0
- package/services/native_memory_paths.py +86 -0
- package/services/native_memory_sync.py +496 -0
- package/services/response_manager.py +183 -0
- package/services/terminal_ui.py +199 -0
- package/services/tier_manager.py +235 -0
- package/services/websocket.py +26 -6
- package/skills/__init__.py +21 -1
- package/skills/confidence_tracker.py +441 -0
- package/skills/context.py +675 -0
- package/skills/curator.py +348 -0
- package/skills/search.py +444 -213
- package/skills/session_review.py +605 -0
- package/skills/store.py +484 -179
- package/terminal_dashboard.py +474 -0
- package/update_system.py +829 -817
- package/hooks/__pycache__/auto-detect-response.cpython-312.pyc +0 -0
- package/hooks/__pycache__/auto_capture.cpython-312.pyc +0 -0
- package/hooks/__pycache__/session_end.cpython-312.pyc +0 -0
- package/hooks/__pycache__/session_start.cpython-312.pyc +0 -0
- package/services/__pycache__/__init__.cpython-312.pyc +0 -0
- package/services/__pycache__/agent_registry.cpython-312.pyc +0 -0
- package/services/__pycache__/auth.cpython-312.pyc +0 -0
- package/services/__pycache__/auto_inject.cpython-312.pyc +0 -0
- package/services/__pycache__/claude_md_sync.cpython-312.pyc +0 -0
- package/services/__pycache__/cleanup.cpython-312.pyc +0 -0
- package/services/__pycache__/compaction_flush.cpython-312.pyc +0 -0
- package/services/__pycache__/confidence.cpython-312.pyc +0 -0
- package/services/__pycache__/daily_log.cpython-312.pyc +0 -0
- package/services/__pycache__/database.cpython-312.pyc +0 -0
- package/services/__pycache__/embeddings.cpython-312.pyc +0 -0
- package/services/__pycache__/insights.cpython-312.pyc +0 -0
- package/services/__pycache__/llm_analyzer.cpython-312.pyc +0 -0
- package/services/__pycache__/memory_md_sync.cpython-312.pyc +0 -0
- package/services/__pycache__/retry_queue.cpython-312.pyc +0 -0
- package/services/__pycache__/timeline.cpython-312.pyc +0 -0
- package/services/__pycache__/vector_index.cpython-312.pyc +0 -0
- package/services/__pycache__/websocket.cpython-312.pyc +0 -0
- package/skills/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/__pycache__/admin.cpython-312.pyc +0 -0
- package/skills/__pycache__/checkpoint.cpython-312.pyc +0 -0
- package/skills/__pycache__/claude_md.cpython-312.pyc +0 -0
- package/skills/__pycache__/cleanup.cpython-312.pyc +0 -0
- package/skills/__pycache__/grounding.cpython-312.pyc +0 -0
- package/skills/__pycache__/insights.cpython-312.pyc +0 -0
- package/skills/__pycache__/natural_language.cpython-312.pyc +0 -0
- package/skills/__pycache__/retrieve.cpython-312.pyc +0 -0
- package/skills/__pycache__/search.cpython-312.pyc +0 -0
- package/skills/__pycache__/state.cpython-312.pyc +0 -0
- package/skills/__pycache__/store.cpython-312.pyc +0 -0
- package/skills/__pycache__/summarize.cpython-312.pyc +0 -0
- package/skills/__pycache__/timeline.cpython-312.pyc +0 -0
- package/skills/__pycache__/verification.cpython-312.pyc +0 -0
- package/test_automation.py +0 -221
- package/test_complete.py +0 -338
- package/test_full.py +0 -322
- package/verify_db.py +0 -134
package/skills/store.py
CHANGED
|
@@ -1,179 +1,484 @@
|
|
|
1
|
-
"""Store memory skill with rich context support."""
|
|
2
|
-
|
|
3
|
-
from
|
|
4
|
-
from services.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
1
|
+
"""Store memory skill with rich context support."""
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Dict, Any, Optional, List
|
|
4
|
+
from services.database import DatabaseService
|
|
5
|
+
from services.embeddings import EmbeddingService, EmbeddingError
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# ============================================================
|
|
11
|
+
# INTERNAL HELPER - Auto-infer relationships
|
|
12
|
+
# ============================================================
|
|
13
|
+
|
|
14
|
+
async def _auto_infer_relationships(
|
|
15
|
+
db: DatabaseService,
|
|
16
|
+
embeddings: EmbeddingService,
|
|
17
|
+
memory_id: int,
|
|
18
|
+
content: str,
|
|
19
|
+
memory_type: str,
|
|
20
|
+
outcome: str,
|
|
21
|
+
session_id: str,
|
|
22
|
+
project_path: str = None
|
|
23
|
+
) -> List[str]:
|
|
24
|
+
"""
|
|
25
|
+
Automatically infer and create relationships based on content analysis.
|
|
26
|
+
Called internally after storing a memory.
|
|
27
|
+
|
|
28
|
+
This is NOT a skill - it's an internal helper function.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
db: Database service instance
|
|
32
|
+
embeddings: Embedding service instance
|
|
33
|
+
memory_id: ID of the newly stored memory
|
|
34
|
+
content: The content of the memory
|
|
35
|
+
memory_type: Type of memory (decision, code, error, etc.)
|
|
36
|
+
outcome: Outcome status (success, partial, failed, pending)
|
|
37
|
+
session_id: Current session ID
|
|
38
|
+
project_path: Optional project path filter
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
List of relationship descriptions that were created
|
|
42
|
+
"""
|
|
43
|
+
relationships_created = []
|
|
44
|
+
content_lower = content.lower()
|
|
45
|
+
|
|
46
|
+
# Generate embedding once for all similarity-based detections
|
|
47
|
+
cached_embedding = None
|
|
48
|
+
if embeddings:
|
|
49
|
+
try:
|
|
50
|
+
cached_embedding = await embeddings.generate_embedding(content)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
logger.debug(f"Embedding generation for relationship inference failed: {e}")
|
|
53
|
+
|
|
54
|
+
# 1. Fix Detection: If this is a successful decision/code after a recent error
|
|
55
|
+
if outcome == 'success' and memory_type in ['decision', 'code']:
|
|
56
|
+
if session_id:
|
|
57
|
+
try:
|
|
58
|
+
recent_errors = await db.get_memories_by_type(
|
|
59
|
+
memory_type='error',
|
|
60
|
+
session_id=session_id,
|
|
61
|
+
limit=3
|
|
62
|
+
)
|
|
63
|
+
for error in recent_errors:
|
|
64
|
+
if error['id'] != memory_id:
|
|
65
|
+
result = await db.create_relationship(
|
|
66
|
+
memory_id, error['id'], 'fixes', strength=0.9
|
|
67
|
+
)
|
|
68
|
+
if result.get('success'):
|
|
69
|
+
relationships_created.append(f"fixes error #{error['id']}")
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.debug(f"Fix detection failed: {e}")
|
|
72
|
+
|
|
73
|
+
# 2. Causal Keyword Detection
|
|
74
|
+
causal_keywords = ['because', 'due to', 'caused by', 'result of', 'since']
|
|
75
|
+
if any(kw in content_lower for kw in causal_keywords):
|
|
76
|
+
if cached_embedding is not None:
|
|
77
|
+
try:
|
|
78
|
+
similar = await db.search_similar(
|
|
79
|
+
cached_embedding, limit=3, threshold=0.7, project_path=project_path
|
|
80
|
+
)
|
|
81
|
+
for mem in similar:
|
|
82
|
+
if mem['id'] != memory_id:
|
|
83
|
+
result = await db.create_relationship(
|
|
84
|
+
memory_id, mem['id'], 'caused_by', strength=0.7
|
|
85
|
+
)
|
|
86
|
+
if result.get('success'):
|
|
87
|
+
relationships_created.append(f"caused_by #{mem['id']}")
|
|
88
|
+
except Exception as e:
|
|
89
|
+
logger.debug(f"Causal detection failed: {e}")
|
|
90
|
+
|
|
91
|
+
# 3. Support Detection
|
|
92
|
+
support_keywords = ['supports', 'evidence for', 'proves', 'confirms', 'validates']
|
|
93
|
+
if any(kw in content_lower for kw in support_keywords):
|
|
94
|
+
if cached_embedding is not None:
|
|
95
|
+
try:
|
|
96
|
+
similar = await db.search_similar(
|
|
97
|
+
cached_embedding, limit=2, threshold=0.75, project_path=project_path
|
|
98
|
+
)
|
|
99
|
+
for mem in similar:
|
|
100
|
+
if mem['id'] != memory_id:
|
|
101
|
+
result = await db.create_relationship(
|
|
102
|
+
memory_id, mem['id'], 'supports', strength=0.8
|
|
103
|
+
)
|
|
104
|
+
if result.get('success'):
|
|
105
|
+
relationships_created.append(f"supports #{mem['id']}")
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.debug(f"Support detection failed: {e}")
|
|
108
|
+
|
|
109
|
+
# 4. Contradiction Detection
|
|
110
|
+
contradiction_keywords = ['but actually', 'wrong', 'incorrect', 'not true', 'instead', 'actually']
|
|
111
|
+
if any(kw in content_lower for kw in contradiction_keywords):
|
|
112
|
+
if cached_embedding is not None:
|
|
113
|
+
try:
|
|
114
|
+
similar = await db.search_similar(
|
|
115
|
+
cached_embedding, limit=2, threshold=0.8, project_path=project_path
|
|
116
|
+
)
|
|
117
|
+
for mem in similar:
|
|
118
|
+
if mem['id'] != memory_id:
|
|
119
|
+
result = await db.create_relationship(
|
|
120
|
+
memory_id, mem['id'], 'contradicts', strength=0.85
|
|
121
|
+
)
|
|
122
|
+
if result.get('success'):
|
|
123
|
+
relationships_created.append(f"contradicts #{mem['id']}")
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.debug(f"Contradiction detection failed: {e}")
|
|
126
|
+
|
|
127
|
+
# 5. Temporal Proximity: Link to recent memories in same session
|
|
128
|
+
if session_id:
|
|
129
|
+
try:
|
|
130
|
+
recent = await db.get_memories_by_type(
|
|
131
|
+
memory_type=memory_type,
|
|
132
|
+
session_id=session_id,
|
|
133
|
+
limit=3
|
|
134
|
+
)
|
|
135
|
+
for mem in recent:
|
|
136
|
+
if mem['id'] != memory_id:
|
|
137
|
+
result = await db.create_relationship(
|
|
138
|
+
memory_id, mem['id'], 'related', strength=0.5
|
|
139
|
+
)
|
|
140
|
+
if result.get('success'):
|
|
141
|
+
relationships_created.append(f"related to #{mem['id']}")
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.debug(f"Temporal proximity detection failed: {e}")
|
|
144
|
+
|
|
145
|
+
# 6. High Semantic Similarity: Strong related link
|
|
146
|
+
if cached_embedding is not None:
|
|
147
|
+
try:
|
|
148
|
+
very_similar = await db.search_similar(
|
|
149
|
+
cached_embedding, limit=2, threshold=0.85, project_path=project_path
|
|
150
|
+
)
|
|
151
|
+
for mem in very_similar:
|
|
152
|
+
if mem['id'] != memory_id:
|
|
153
|
+
strength = mem.get('score', 0.85)
|
|
154
|
+
result = await db.create_relationship(
|
|
155
|
+
memory_id, mem['id'], 'related', strength=strength
|
|
156
|
+
)
|
|
157
|
+
if result.get('success'):
|
|
158
|
+
relationships_created.append(f"highly related to #{mem['id']}")
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.debug(f"Semantic similarity detection failed: {e}")
|
|
161
|
+
|
|
162
|
+
return relationships_created
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
async def store_memory(
|
|
166
|
+
db: DatabaseService,
|
|
167
|
+
embeddings: EmbeddingService,
|
|
168
|
+
content: str,
|
|
169
|
+
memory_type: str = "chunk",
|
|
170
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
171
|
+
session_id: Optional[str] = None,
|
|
172
|
+
# Project context
|
|
173
|
+
project_path: Optional[str] = None,
|
|
174
|
+
project_name: Optional[str] = None,
|
|
175
|
+
project_type: Optional[str] = None,
|
|
176
|
+
tech_stack: Optional[List[str]] = None,
|
|
177
|
+
# Session context
|
|
178
|
+
chat_id: Optional[str] = None,
|
|
179
|
+
# Agent context
|
|
180
|
+
agent_type: Optional[str] = None,
|
|
181
|
+
skill_used: Optional[str] = None,
|
|
182
|
+
tools_used: Optional[List[str]] = None,
|
|
183
|
+
# Outcome (legacy)
|
|
184
|
+
outcome: Optional[str] = None,
|
|
185
|
+
success: Optional[bool] = None,
|
|
186
|
+
# Classification
|
|
187
|
+
tags: Optional[List[str]] = None,
|
|
188
|
+
importance: int = 5,
|
|
189
|
+
confidence: float = 0.5,
|
|
190
|
+
# Outcome spectrum
|
|
191
|
+
outcome_status: str = 'pending',
|
|
192
|
+
fixed: Optional[List[str]] = None,
|
|
193
|
+
did_not_fix: Optional[List[str]] = None,
|
|
194
|
+
caused: Optional[List[str]] = None
|
|
195
|
+
) -> Dict[str, Any]:
|
|
196
|
+
"""
|
|
197
|
+
Store a memory with semantic embedding and rich context.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
db: Database service instance
|
|
201
|
+
embeddings: Embedding service instance
|
|
202
|
+
content: The text content to store
|
|
203
|
+
memory_type: Type of memory:
|
|
204
|
+
- 'session': Session summaries
|
|
205
|
+
- 'decision': Architectural/design decisions
|
|
206
|
+
- 'code': Code patterns and snippets
|
|
207
|
+
- 'chunk': General conversation chunks
|
|
208
|
+
- 'error': Error patterns and solutions
|
|
209
|
+
- 'preference': User preferences
|
|
210
|
+
metadata: Optional additional metadata
|
|
211
|
+
session_id: Session identifier
|
|
212
|
+
project_path: Full path to the project
|
|
213
|
+
project_name: Human-readable project name
|
|
214
|
+
project_type: Type (wordpress, react, python, etc.)
|
|
215
|
+
tech_stack: List of technologies used
|
|
216
|
+
chat_id: Specific chat/conversation ID
|
|
217
|
+
agent_type: Agent that processed this (Explore, Plan, etc.)
|
|
218
|
+
skill_used: Skill that was invoked
|
|
219
|
+
tools_used: List of tools that were called
|
|
220
|
+
outcome: Description of what happened (legacy field)
|
|
221
|
+
success: Whether the operation succeeded (legacy field)
|
|
222
|
+
tags: Classification tags
|
|
223
|
+
importance: 1-10 scale of importance (default 5)
|
|
224
|
+
confidence: Reliability score 0.0 (unreliable) to 1.0 (proven), default 0.5
|
|
225
|
+
outcome_status: Status of the solution:
|
|
226
|
+
- 'pending': Not yet verified (default)
|
|
227
|
+
- 'success': Fully worked
|
|
228
|
+
- 'partial': Partially worked
|
|
229
|
+
- 'failed': Did not work
|
|
230
|
+
- 'superseded': Replaced by another solution
|
|
231
|
+
fixed: List of what this solution fixed
|
|
232
|
+
did_not_fix: List of what remains unfixed
|
|
233
|
+
caused: List of side effects this solution caused
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Dict with stored memory ID and status
|
|
237
|
+
"""
|
|
238
|
+
# Consolidate legacy outcome/success into outcome_status
|
|
239
|
+
# If caller uses legacy params, derive outcome_status from them
|
|
240
|
+
if outcome_status == 'pending':
|
|
241
|
+
if success is True:
|
|
242
|
+
outcome_status = 'success'
|
|
243
|
+
elif success is False:
|
|
244
|
+
outcome_status = 'failed'
|
|
245
|
+
elif outcome and isinstance(outcome, str):
|
|
246
|
+
# Map common outcome text values
|
|
247
|
+
outcome_lower = outcome.lower().strip()
|
|
248
|
+
if outcome_lower in ('success', 'worked', 'fixed', 'resolved'):
|
|
249
|
+
outcome_status = 'success'
|
|
250
|
+
elif outcome_lower in ('failed', 'broken', 'error'):
|
|
251
|
+
outcome_status = 'failed'
|
|
252
|
+
elif outcome_lower in ('partial', 'partially'):
|
|
253
|
+
outcome_status = 'partial'
|
|
254
|
+
|
|
255
|
+
# Generate embedding for the content with status tracking
|
|
256
|
+
embed_result = await embeddings.generate_embedding_with_status(content)
|
|
257
|
+
embedding = embed_result.embedding
|
|
258
|
+
|
|
259
|
+
if not embed_result.ok:
|
|
260
|
+
logger.warning(
|
|
261
|
+
f"Embedding failed ({embed_result.error.value}): {embed_result.error_message}. "
|
|
262
|
+
f"Memory will be stored without embedding (not semantically searchable)."
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# === Dedup check: find near-duplicates before storing ===
|
|
266
|
+
link_to_id = None # Set if we find a near-duplicate to link after storing
|
|
267
|
+
if embedding:
|
|
268
|
+
try:
|
|
269
|
+
duplicates = await db.find_similar_for_dedup(
|
|
270
|
+
embedding=embedding,
|
|
271
|
+
project_path=project_path,
|
|
272
|
+
threshold=0.92,
|
|
273
|
+
limit=3
|
|
274
|
+
)
|
|
275
|
+
if duplicates:
|
|
276
|
+
best_match = duplicates[0]
|
|
277
|
+
if best_match['similarity'] >= 0.95:
|
|
278
|
+
# Very high similarity - merge into existing memory
|
|
279
|
+
# Keeps longer content, higher importance/confidence
|
|
280
|
+
updated_id = await db.merge_memory(
|
|
281
|
+
existing_id=best_match['id'],
|
|
282
|
+
new_content=content,
|
|
283
|
+
new_importance=importance,
|
|
284
|
+
new_confidence=confidence
|
|
285
|
+
)
|
|
286
|
+
logger.info(
|
|
287
|
+
f"Dedup: merged with memory #{best_match['id']} "
|
|
288
|
+
f"(similarity: {best_match['similarity']:.3f})"
|
|
289
|
+
)
|
|
290
|
+
return {
|
|
291
|
+
"success": True,
|
|
292
|
+
"memory_id": updated_id,
|
|
293
|
+
"action": "merged",
|
|
294
|
+
"merged_with": best_match['id'],
|
|
295
|
+
"similarity": best_match['similarity'],
|
|
296
|
+
"type": memory_type,
|
|
297
|
+
"importance": importance,
|
|
298
|
+
"confidence": confidence,
|
|
299
|
+
"outcome_status": outcome_status,
|
|
300
|
+
"project": project_path,
|
|
301
|
+
"relationships_created": [],
|
|
302
|
+
"message": (
|
|
303
|
+
f"Merged with existing memory #{best_match['id']} "
|
|
304
|
+
f"(similarity: {best_match['similarity']:.2f})"
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
elif best_match['similarity'] >= 0.92:
|
|
308
|
+
# High similarity but not identical - store new but link as related
|
|
309
|
+
# We'll create the relationship after storing below
|
|
310
|
+
link_to_id = best_match['id']
|
|
311
|
+
link_similarity = best_match['similarity']
|
|
312
|
+
logger.info(
|
|
313
|
+
f"Dedup: will link to memory #{link_to_id} "
|
|
314
|
+
f"(similarity: {link_similarity:.3f})"
|
|
315
|
+
)
|
|
316
|
+
except Exception as e:
|
|
317
|
+
# Dedup is best-effort; never block a store operation
|
|
318
|
+
logger.warning(f"Dedup check failed (non-fatal): {e}")
|
|
319
|
+
link_to_id = None
|
|
320
|
+
# === End dedup check ===
|
|
321
|
+
|
|
322
|
+
# Store in database with full context
|
|
323
|
+
memory_id = await db.store_memory(
|
|
324
|
+
memory_type=memory_type,
|
|
325
|
+
content=content,
|
|
326
|
+
embedding=embedding,
|
|
327
|
+
metadata=metadata,
|
|
328
|
+
session_id=session_id,
|
|
329
|
+
project_path=project_path,
|
|
330
|
+
project_name=project_name,
|
|
331
|
+
project_type=project_type,
|
|
332
|
+
tech_stack=tech_stack,
|
|
333
|
+
chat_id=chat_id,
|
|
334
|
+
agent_type=agent_type,
|
|
335
|
+
skill_used=skill_used,
|
|
336
|
+
tools_used=tools_used,
|
|
337
|
+
outcome=outcome,
|
|
338
|
+
success=success,
|
|
339
|
+
tags=tags,
|
|
340
|
+
importance=importance,
|
|
341
|
+
confidence=confidence,
|
|
342
|
+
outcome_status=outcome_status,
|
|
343
|
+
fixed=fixed,
|
|
344
|
+
did_not_fix=did_not_fix,
|
|
345
|
+
caused=caused
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# Auto-infer relationships (silent, internal)
|
|
349
|
+
relationships_created = []
|
|
350
|
+
try:
|
|
351
|
+
relationships_created = await _auto_infer_relationships(
|
|
352
|
+
db=db,
|
|
353
|
+
embeddings=embeddings,
|
|
354
|
+
memory_id=memory_id,
|
|
355
|
+
content=content,
|
|
356
|
+
memory_type=memory_type,
|
|
357
|
+
outcome=outcome_status,
|
|
358
|
+
session_id=session_id,
|
|
359
|
+
project_path=project_path
|
|
360
|
+
)
|
|
361
|
+
if relationships_created:
|
|
362
|
+
logger.info(f"Auto-created {len(relationships_created)} relationships for memory #{memory_id}")
|
|
363
|
+
except Exception as e:
|
|
364
|
+
logger.warning(f"Failed to auto-infer relationships: {e}")
|
|
365
|
+
# Don't fail the store operation if relationship inference fails
|
|
366
|
+
|
|
367
|
+
# If dedup found a near-duplicate (0.92-0.95 range), link as related
|
|
368
|
+
dedup_linked = False
|
|
369
|
+
if link_to_id is not None:
|
|
370
|
+
try:
|
|
371
|
+
result = await db.create_relationship(
|
|
372
|
+
memory_id, link_to_id, 'related', strength=0.9
|
|
373
|
+
)
|
|
374
|
+
if result.get('success'):
|
|
375
|
+
relationships_created.append(f"near-duplicate of #{link_to_id}")
|
|
376
|
+
dedup_linked = True
|
|
377
|
+
logger.info(f"Dedup: linked memory #{memory_id} to near-duplicate #{link_to_id}")
|
|
378
|
+
except Exception as e:
|
|
379
|
+
logger.warning(f"Failed to create dedup relationship: {e}")
|
|
380
|
+
|
|
381
|
+
response = {
|
|
382
|
+
"success": True,
|
|
383
|
+
"memory_id": memory_id,
|
|
384
|
+
"type": memory_type,
|
|
385
|
+
"importance": importance,
|
|
386
|
+
"confidence": confidence,
|
|
387
|
+
"outcome_status": outcome_status,
|
|
388
|
+
"project": project_path,
|
|
389
|
+
"relationships_created": relationships_created,
|
|
390
|
+
"has_embedding": embedding is not None,
|
|
391
|
+
"message": f"Memory stored successfully with ID {memory_id}"
|
|
392
|
+
}
|
|
393
|
+
if not embed_result.ok:
|
|
394
|
+
response["embedding_error"] = embed_result.error.value
|
|
395
|
+
response["embedding_error_detail"] = embed_result.error_message
|
|
396
|
+
if dedup_linked:
|
|
397
|
+
response["action"] = "stored_and_linked"
|
|
398
|
+
response["linked_to"] = link_to_id
|
|
399
|
+
|
|
400
|
+
return response
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
async def store_project(
|
|
404
|
+
db: DatabaseService,
|
|
405
|
+
path: str,
|
|
406
|
+
name: Optional[str] = None,
|
|
407
|
+
project_type: Optional[str] = None,
|
|
408
|
+
tech_stack: Optional[List[str]] = None,
|
|
409
|
+
conventions: Optional[Dict[str, Any]] = None,
|
|
410
|
+
preferences: Optional[Dict[str, Any]] = None
|
|
411
|
+
) -> Dict[str, Any]:
|
|
412
|
+
"""
|
|
413
|
+
Store or update project-level information.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
db: Database service instance
|
|
417
|
+
path: Full path to the project
|
|
418
|
+
name: Human-readable project name
|
|
419
|
+
project_type: Type (wordpress, react, python, etc.)
|
|
420
|
+
tech_stack: List of technologies
|
|
421
|
+
conventions: Coding conventions (naming, structure, etc.)
|
|
422
|
+
preferences: User preferences for this project
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
Dict with project info
|
|
426
|
+
"""
|
|
427
|
+
project_id = await db.store_project(
|
|
428
|
+
path=path,
|
|
429
|
+
name=name,
|
|
430
|
+
project_type=project_type,
|
|
431
|
+
tech_stack=tech_stack,
|
|
432
|
+
conventions=conventions,
|
|
433
|
+
preferences=preferences
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
"success": True,
|
|
438
|
+
"project_id": project_id,
|
|
439
|
+
"path": path,
|
|
440
|
+
"message": f"Project info stored/updated for {path}"
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
async def store_pattern(
|
|
445
|
+
db: DatabaseService,
|
|
446
|
+
embeddings: EmbeddingService,
|
|
447
|
+
name: str,
|
|
448
|
+
solution: str,
|
|
449
|
+
problem_type: Optional[str] = None,
|
|
450
|
+
tech_context: Optional[List[str]] = None,
|
|
451
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
452
|
+
) -> Dict[str, Any]:
|
|
453
|
+
"""
|
|
454
|
+
Store a reusable solution pattern.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
db: Database service instance
|
|
458
|
+
embeddings: Embedding service instance
|
|
459
|
+
name: Pattern name
|
|
460
|
+
solution: The solution/approach
|
|
461
|
+
problem_type: Category (bug_fix, feature, refactor, config, etc.)
|
|
462
|
+
tech_context: Technologies this applies to
|
|
463
|
+
metadata: Additional info
|
|
464
|
+
|
|
465
|
+
Returns:
|
|
466
|
+
Dict with pattern info
|
|
467
|
+
"""
|
|
468
|
+
embedding = await embeddings.generate_embedding(f"{name}: {solution}")
|
|
469
|
+
|
|
470
|
+
pattern_id = await db.store_pattern(
|
|
471
|
+
name=name,
|
|
472
|
+
solution=solution,
|
|
473
|
+
embedding=embedding,
|
|
474
|
+
problem_type=problem_type,
|
|
475
|
+
tech_context=tech_context,
|
|
476
|
+
metadata=metadata
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
return {
|
|
480
|
+
"success": True,
|
|
481
|
+
"pattern_id": pattern_id,
|
|
482
|
+
"name": name,
|
|
483
|
+
"message": f"Pattern '{name}' stored successfully"
|
|
484
|
+
}
|