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.
Files changed (97) hide show
  1. package/README.md +206 -206
  2. package/agent_card.py +186 -0
  3. package/bin/cli.js +327 -185
  4. package/bin/lib/banner.js +39 -0
  5. package/bin/lib/environment.js +166 -0
  6. package/bin/lib/installer.js +291 -0
  7. package/bin/lib/models.js +95 -0
  8. package/bin/lib/steps/advanced.js +101 -0
  9. package/bin/lib/steps/confirm.js +87 -0
  10. package/bin/lib/steps/model.js +57 -0
  11. package/bin/lib/steps/provider.js +65 -0
  12. package/bin/lib/steps/scope.js +59 -0
  13. package/bin/lib/steps/server.js +74 -0
  14. package/bin/lib/ui.js +75 -0
  15. package/bin/onboarding.js +164 -0
  16. package/bin/postinstall.js +35 -270
  17. package/config.py +103 -4
  18. package/dashboard.html +4902 -2689
  19. package/hooks/extract_memories.py +439 -0
  20. package/hooks/grounding-hook.py +422 -348
  21. package/hooks/pre_compact_hook.py +76 -0
  22. package/hooks/session_end.py +293 -192
  23. package/hooks/session_end_hook.py +149 -0
  24. package/hooks/session_start.py +227 -227
  25. package/hooks/stop_hook.py +372 -0
  26. package/install.py +972 -902
  27. package/main.py +5240 -2859
  28. package/mcp_server.py +451 -0
  29. package/package.json +58 -47
  30. package/requirements.txt +12 -8
  31. package/services/__init__.py +50 -50
  32. package/services/adaptive_ranker.py +272 -0
  33. package/services/agent_catalog.json +153 -0
  34. package/services/agent_registry.py +245 -730
  35. package/services/claude_md_sync.py +320 -4
  36. package/services/consolidation.py +417 -0
  37. package/services/curator.py +1606 -0
  38. package/services/database.py +4118 -2485
  39. package/services/embedding_pipeline.py +262 -0
  40. package/services/embeddings.py +493 -85
  41. package/services/memory_decay.py +408 -0
  42. package/services/native_memory_paths.py +86 -0
  43. package/services/native_memory_sync.py +496 -0
  44. package/services/response_manager.py +183 -0
  45. package/services/terminal_ui.py +199 -0
  46. package/services/tier_manager.py +235 -0
  47. package/services/websocket.py +26 -6
  48. package/skills/__init__.py +21 -1
  49. package/skills/confidence_tracker.py +441 -0
  50. package/skills/context.py +675 -0
  51. package/skills/curator.py +348 -0
  52. package/skills/search.py +444 -213
  53. package/skills/session_review.py +605 -0
  54. package/skills/store.py +484 -179
  55. package/terminal_dashboard.py +474 -0
  56. package/update_system.py +829 -817
  57. package/hooks/__pycache__/auto-detect-response.cpython-312.pyc +0 -0
  58. package/hooks/__pycache__/auto_capture.cpython-312.pyc +0 -0
  59. package/hooks/__pycache__/session_end.cpython-312.pyc +0 -0
  60. package/hooks/__pycache__/session_start.cpython-312.pyc +0 -0
  61. package/services/__pycache__/__init__.cpython-312.pyc +0 -0
  62. package/services/__pycache__/agent_registry.cpython-312.pyc +0 -0
  63. package/services/__pycache__/auth.cpython-312.pyc +0 -0
  64. package/services/__pycache__/auto_inject.cpython-312.pyc +0 -0
  65. package/services/__pycache__/claude_md_sync.cpython-312.pyc +0 -0
  66. package/services/__pycache__/cleanup.cpython-312.pyc +0 -0
  67. package/services/__pycache__/compaction_flush.cpython-312.pyc +0 -0
  68. package/services/__pycache__/confidence.cpython-312.pyc +0 -0
  69. package/services/__pycache__/daily_log.cpython-312.pyc +0 -0
  70. package/services/__pycache__/database.cpython-312.pyc +0 -0
  71. package/services/__pycache__/embeddings.cpython-312.pyc +0 -0
  72. package/services/__pycache__/insights.cpython-312.pyc +0 -0
  73. package/services/__pycache__/llm_analyzer.cpython-312.pyc +0 -0
  74. package/services/__pycache__/memory_md_sync.cpython-312.pyc +0 -0
  75. package/services/__pycache__/retry_queue.cpython-312.pyc +0 -0
  76. package/services/__pycache__/timeline.cpython-312.pyc +0 -0
  77. package/services/__pycache__/vector_index.cpython-312.pyc +0 -0
  78. package/services/__pycache__/websocket.cpython-312.pyc +0 -0
  79. package/skills/__pycache__/__init__.cpython-312.pyc +0 -0
  80. package/skills/__pycache__/admin.cpython-312.pyc +0 -0
  81. package/skills/__pycache__/checkpoint.cpython-312.pyc +0 -0
  82. package/skills/__pycache__/claude_md.cpython-312.pyc +0 -0
  83. package/skills/__pycache__/cleanup.cpython-312.pyc +0 -0
  84. package/skills/__pycache__/grounding.cpython-312.pyc +0 -0
  85. package/skills/__pycache__/insights.cpython-312.pyc +0 -0
  86. package/skills/__pycache__/natural_language.cpython-312.pyc +0 -0
  87. package/skills/__pycache__/retrieve.cpython-312.pyc +0 -0
  88. package/skills/__pycache__/search.cpython-312.pyc +0 -0
  89. package/skills/__pycache__/state.cpython-312.pyc +0 -0
  90. package/skills/__pycache__/store.cpython-312.pyc +0 -0
  91. package/skills/__pycache__/summarize.cpython-312.pyc +0 -0
  92. package/skills/__pycache__/timeline.cpython-312.pyc +0 -0
  93. package/skills/__pycache__/verification.cpython-312.pyc +0 -0
  94. package/test_automation.py +0 -221
  95. package/test_complete.py +0 -338
  96. package/test_full.py +0 -322
  97. package/verify_db.py +0 -134
package/skills/store.py CHANGED
@@ -1,179 +1,484 @@
1
- """Store memory skill with rich context support."""
2
- from typing import Dict, Any, Optional, List
3
- from services.database import DatabaseService
4
- from services.embeddings import EmbeddingService
5
-
6
-
7
- async def store_memory(
8
- db: DatabaseService,
9
- embeddings: EmbeddingService,
10
- content: str,
11
- memory_type: str = "chunk",
12
- metadata: Optional[Dict[str, Any]] = None,
13
- session_id: Optional[str] = None,
14
- # Project context
15
- project_path: Optional[str] = None,
16
- project_name: Optional[str] = None,
17
- project_type: Optional[str] = None,
18
- tech_stack: Optional[List[str]] = None,
19
- # Session context
20
- chat_id: Optional[str] = None,
21
- # Agent context
22
- agent_type: Optional[str] = None,
23
- skill_used: Optional[str] = None,
24
- tools_used: Optional[List[str]] = None,
25
- # Outcome
26
- outcome: Optional[str] = None,
27
- success: Optional[bool] = None,
28
- # Classification
29
- tags: Optional[List[str]] = None,
30
- importance: int = 5
31
- ) -> Dict[str, Any]:
32
- """
33
- Store a memory with semantic embedding and rich context.
34
-
35
- Args:
36
- db: Database service instance
37
- embeddings: Embedding service instance
38
- content: The text content to store
39
- memory_type: Type of memory:
40
- - 'session': Session summaries
41
- - 'decision': Architectural/design decisions
42
- - 'code': Code patterns and snippets
43
- - 'chunk': General conversation chunks
44
- - 'error': Error patterns and solutions
45
- - 'preference': User preferences
46
- metadata: Optional additional metadata
47
- session_id: Session identifier
48
- project_path: Full path to the project
49
- project_name: Human-readable project name
50
- project_type: Type (wordpress, react, python, etc.)
51
- tech_stack: List of technologies used
52
- chat_id: Specific chat/conversation ID
53
- agent_type: Agent that processed this (Explore, Plan, etc.)
54
- skill_used: Skill that was invoked
55
- tools_used: List of tools that were called
56
- outcome: Description of what happened
57
- success: Whether the operation succeeded
58
- tags: Classification tags
59
- importance: 1-10 scale of importance (default 5)
60
-
61
- Returns:
62
- Dict with stored memory ID and status
63
- """
64
- # Generate embedding for the content
65
- embedding = await embeddings.generate_embedding(content)
66
-
67
- # Store in database with full context
68
- memory_id = await db.store_memory(
69
- memory_type=memory_type,
70
- content=content,
71
- embedding=embedding,
72
- metadata=metadata,
73
- session_id=session_id,
74
- project_path=project_path,
75
- project_name=project_name,
76
- project_type=project_type,
77
- tech_stack=tech_stack,
78
- chat_id=chat_id,
79
- agent_type=agent_type,
80
- skill_used=skill_used,
81
- tools_used=tools_used,
82
- outcome=outcome,
83
- success=success,
84
- tags=tags,
85
- importance=importance
86
- )
87
-
88
- return {
89
- "success": True,
90
- "memory_id": memory_id,
91
- "type": memory_type,
92
- "importance": importance,
93
- "project": project_path,
94
- "message": f"Memory stored successfully with ID {memory_id}"
95
- }
96
-
97
-
98
- async def store_project(
99
- db: DatabaseService,
100
- path: str,
101
- name: Optional[str] = None,
102
- project_type: Optional[str] = None,
103
- tech_stack: Optional[List[str]] = None,
104
- conventions: Optional[Dict[str, Any]] = None,
105
- preferences: Optional[Dict[str, Any]] = None
106
- ) -> Dict[str, Any]:
107
- """
108
- Store or update project-level information.
109
-
110
- Args:
111
- db: Database service instance
112
- path: Full path to the project
113
- name: Human-readable project name
114
- project_type: Type (wordpress, react, python, etc.)
115
- tech_stack: List of technologies
116
- conventions: Coding conventions (naming, structure, etc.)
117
- preferences: User preferences for this project
118
-
119
- Returns:
120
- Dict with project info
121
- """
122
- project_id = await db.store_project(
123
- path=path,
124
- name=name,
125
- project_type=project_type,
126
- tech_stack=tech_stack,
127
- conventions=conventions,
128
- preferences=preferences
129
- )
130
-
131
- return {
132
- "success": True,
133
- "project_id": project_id,
134
- "path": path,
135
- "message": f"Project info stored/updated for {path}"
136
- }
137
-
138
-
139
- async def store_pattern(
140
- db: DatabaseService,
141
- embeddings: EmbeddingService,
142
- name: str,
143
- solution: str,
144
- problem_type: Optional[str] = None,
145
- tech_context: Optional[List[str]] = None,
146
- metadata: Optional[Dict[str, Any]] = None
147
- ) -> Dict[str, Any]:
148
- """
149
- Store a reusable solution pattern.
150
-
151
- Args:
152
- db: Database service instance
153
- embeddings: Embedding service instance
154
- name: Pattern name
155
- solution: The solution/approach
156
- problem_type: Category (bug_fix, feature, refactor, config, etc.)
157
- tech_context: Technologies this applies to
158
- metadata: Additional info
159
-
160
- Returns:
161
- Dict with pattern info
162
- """
163
- embedding = await embeddings.generate_embedding(f"{name}: {solution}")
164
-
165
- pattern_id = await db.store_pattern(
166
- name=name,
167
- solution=solution,
168
- embedding=embedding,
169
- problem_type=problem_type,
170
- tech_context=tech_context,
171
- metadata=metadata
172
- )
173
-
174
- return {
175
- "success": True,
176
- "pattern_id": pattern_id,
177
- "name": name,
178
- "message": f"Pattern '{name}' stored successfully"
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
+ }