memorygraphMCP 0.11.7__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.
- memorygraph/__init__.py +50 -0
- memorygraph/__main__.py +12 -0
- memorygraph/advanced_tools.py +509 -0
- memorygraph/analytics/__init__.py +46 -0
- memorygraph/analytics/advanced_queries.py +727 -0
- memorygraph/backends/__init__.py +21 -0
- memorygraph/backends/base.py +179 -0
- memorygraph/backends/cloud.py +75 -0
- memorygraph/backends/cloud_backend.py +858 -0
- memorygraph/backends/factory.py +577 -0
- memorygraph/backends/falkordb_backend.py +749 -0
- memorygraph/backends/falkordblite_backend.py +746 -0
- memorygraph/backends/ladybugdb_backend.py +242 -0
- memorygraph/backends/memgraph_backend.py +327 -0
- memorygraph/backends/neo4j_backend.py +298 -0
- memorygraph/backends/sqlite_fallback.py +463 -0
- memorygraph/backends/turso.py +448 -0
- memorygraph/cli.py +743 -0
- memorygraph/cloud_database.py +297 -0
- memorygraph/config.py +295 -0
- memorygraph/database.py +933 -0
- memorygraph/graph_analytics.py +631 -0
- memorygraph/integration/__init__.py +69 -0
- memorygraph/integration/context_capture.py +426 -0
- memorygraph/integration/project_analysis.py +583 -0
- memorygraph/integration/workflow_tracking.py +492 -0
- memorygraph/intelligence/__init__.py +59 -0
- memorygraph/intelligence/context_retrieval.py +447 -0
- memorygraph/intelligence/entity_extraction.py +386 -0
- memorygraph/intelligence/pattern_recognition.py +420 -0
- memorygraph/intelligence/temporal.py +374 -0
- memorygraph/migration/__init__.py +27 -0
- memorygraph/migration/manager.py +579 -0
- memorygraph/migration/models.py +142 -0
- memorygraph/migration/scripts/__init__.py +17 -0
- memorygraph/migration/scripts/bitemporal_migration.py +595 -0
- memorygraph/migration/scripts/multitenancy_migration.py +452 -0
- memorygraph/migration_tools_module.py +146 -0
- memorygraph/models.py +684 -0
- memorygraph/proactive/__init__.py +46 -0
- memorygraph/proactive/outcome_learning.py +444 -0
- memorygraph/proactive/predictive.py +410 -0
- memorygraph/proactive/session_briefing.py +399 -0
- memorygraph/relationships.py +668 -0
- memorygraph/server.py +883 -0
- memorygraph/sqlite_database.py +1876 -0
- memorygraph/tools/__init__.py +59 -0
- memorygraph/tools/activity_tools.py +262 -0
- memorygraph/tools/memory_tools.py +315 -0
- memorygraph/tools/migration_tools.py +181 -0
- memorygraph/tools/relationship_tools.py +147 -0
- memorygraph/tools/search_tools.py +406 -0
- memorygraph/tools/temporal_tools.py +339 -0
- memorygraph/utils/__init__.py +10 -0
- memorygraph/utils/context_extractor.py +429 -0
- memorygraph/utils/error_handling.py +151 -0
- memorygraph/utils/export_import.py +425 -0
- memorygraph/utils/graph_algorithms.py +200 -0
- memorygraph/utils/pagination.py +149 -0
- memorygraph/utils/project_detection.py +133 -0
- memorygraphmcp-0.11.7.dist-info/METADATA +970 -0
- memorygraphmcp-0.11.7.dist-info/RECORD +65 -0
- memorygraphmcp-0.11.7.dist-info/WHEEL +4 -0
- memorygraphmcp-0.11.7.dist-info/entry_points.txt +2 -0
- memorygraphmcp-0.11.7.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session Start Intelligence for Claude Code Memory Server.
|
|
3
|
+
|
|
4
|
+
Provides automatic briefing when Claude Code starts, including:
|
|
5
|
+
- Recent project activity
|
|
6
|
+
- Unresolved problems
|
|
7
|
+
- Relevant patterns
|
|
8
|
+
- Deprecation warnings
|
|
9
|
+
- Recommended next steps
|
|
10
|
+
|
|
11
|
+
Phase 7 Implementation - Session Start Intelligence
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from datetime import datetime, timedelta
|
|
15
|
+
from typing import Optional, List, Dict, Any
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
import logging
|
|
18
|
+
|
|
19
|
+
from pydantic import BaseModel, Field
|
|
20
|
+
|
|
21
|
+
from ..backends.base import GraphBackend
|
|
22
|
+
from ..models import Memory, MemoryType, RelationshipType
|
|
23
|
+
from ..integration.project_analysis import detect_project, ProjectInfo
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class RecentActivity(BaseModel):
|
|
29
|
+
"""Recent activity in a project."""
|
|
30
|
+
|
|
31
|
+
memory_id: str
|
|
32
|
+
memory_type: str
|
|
33
|
+
title: str
|
|
34
|
+
summary: Optional[str] = None
|
|
35
|
+
timestamp: datetime
|
|
36
|
+
tags: List[str] = Field(default_factory=list)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class UnresolvedProblem(BaseModel):
|
|
40
|
+
"""Unresolved problem without a solution."""
|
|
41
|
+
|
|
42
|
+
problem_id: str
|
|
43
|
+
title: str
|
|
44
|
+
description: str
|
|
45
|
+
created_at: datetime
|
|
46
|
+
tags: List[str] = Field(default_factory=list)
|
|
47
|
+
related_memories: int = 0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class RelevantPattern(BaseModel):
|
|
51
|
+
"""Relevant pattern for current project."""
|
|
52
|
+
|
|
53
|
+
pattern_id: str
|
|
54
|
+
pattern_type: str
|
|
55
|
+
description: str
|
|
56
|
+
effectiveness: float = 0.5
|
|
57
|
+
usage_count: int = 0
|
|
58
|
+
last_used: Optional[datetime] = None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class DeprecationWarning(BaseModel):
|
|
62
|
+
"""Warning about deprecated approaches."""
|
|
63
|
+
|
|
64
|
+
deprecated_id: str
|
|
65
|
+
deprecated_title: str
|
|
66
|
+
reason: str
|
|
67
|
+
replacement_id: Optional[str] = None
|
|
68
|
+
replacement_title: Optional[str] = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class SessionBriefing(BaseModel):
|
|
72
|
+
"""
|
|
73
|
+
Complete session briefing for a project.
|
|
74
|
+
|
|
75
|
+
Provides developers with relevant context when starting work.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
project_name: str
|
|
79
|
+
project_path: str
|
|
80
|
+
project_type: str
|
|
81
|
+
briefing_timestamp: datetime = Field(default_factory=datetime.now)
|
|
82
|
+
|
|
83
|
+
# Activity
|
|
84
|
+
recent_activities: List[RecentActivity] = Field(default_factory=list)
|
|
85
|
+
total_memories: int = 0
|
|
86
|
+
|
|
87
|
+
# Issues
|
|
88
|
+
unresolved_problems: List[UnresolvedProblem] = Field(default_factory=list)
|
|
89
|
+
|
|
90
|
+
# Recommendations
|
|
91
|
+
relevant_patterns: List[RelevantPattern] = Field(default_factory=list)
|
|
92
|
+
|
|
93
|
+
# Warnings
|
|
94
|
+
deprecation_warnings: List[DeprecationWarning] = Field(default_factory=list)
|
|
95
|
+
|
|
96
|
+
# Summary
|
|
97
|
+
has_active_issues: bool = False
|
|
98
|
+
has_warnings: bool = False
|
|
99
|
+
|
|
100
|
+
def format_as_text(self, verbosity: str = "standard") -> str:
|
|
101
|
+
"""
|
|
102
|
+
Format briefing as human-readable text.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
verbosity: "minimal", "standard", or "detailed"
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Formatted briefing text
|
|
109
|
+
"""
|
|
110
|
+
lines = []
|
|
111
|
+
lines.append(f"# Session Briefing for {self.project_name}")
|
|
112
|
+
lines.append(f"Path: {self.project_path}")
|
|
113
|
+
lines.append(f"Type: {self.project_type}")
|
|
114
|
+
lines.append(f"Time: {self.briefing_timestamp.strftime('%Y-%m-%d %H:%M')}")
|
|
115
|
+
lines.append("")
|
|
116
|
+
|
|
117
|
+
# Recent Activity
|
|
118
|
+
if self.recent_activities:
|
|
119
|
+
lines.append("## Recent Activity")
|
|
120
|
+
count = 5 if verbosity == "minimal" else (10 if verbosity == "standard" else len(self.recent_activities))
|
|
121
|
+
for activity in self.recent_activities[:count]:
|
|
122
|
+
age = (datetime.now() - activity.timestamp).days
|
|
123
|
+
age_str = f"{age}d ago" if age > 0 else "today"
|
|
124
|
+
lines.append(f"- [{activity.memory_type}] {activity.title} ({age_str})")
|
|
125
|
+
if verbosity == "detailed" and activity.summary:
|
|
126
|
+
lines.append(f" {activity.summary}")
|
|
127
|
+
lines.append("")
|
|
128
|
+
|
|
129
|
+
# Active Issues
|
|
130
|
+
if self.unresolved_problems:
|
|
131
|
+
lines.append("## Active Issues ⚠️")
|
|
132
|
+
for problem in self.unresolved_problems:
|
|
133
|
+
age = (datetime.now() - problem.created_at).days
|
|
134
|
+
lines.append(f"- {problem.title} ({age}d old)")
|
|
135
|
+
if verbosity in ["standard", "detailed"]:
|
|
136
|
+
lines.append(f" {problem.description[:200]}...")
|
|
137
|
+
lines.append("")
|
|
138
|
+
|
|
139
|
+
# Recommended Patterns
|
|
140
|
+
if self.relevant_patterns:
|
|
141
|
+
lines.append("## Recommended Patterns")
|
|
142
|
+
count = 3 if verbosity == "minimal" else 5
|
|
143
|
+
for pattern in self.relevant_patterns[:count]:
|
|
144
|
+
eff_pct = int(pattern.effectiveness * 100)
|
|
145
|
+
lines.append(f"- {pattern.pattern_type}: {pattern.description}")
|
|
146
|
+
if verbosity in ["standard", "detailed"]:
|
|
147
|
+
lines.append(f" Effectiveness: {eff_pct}%, Used {pattern.usage_count} times")
|
|
148
|
+
lines.append("")
|
|
149
|
+
|
|
150
|
+
# Warnings
|
|
151
|
+
if self.deprecation_warnings:
|
|
152
|
+
lines.append("## Deprecation Warnings ⚠️")
|
|
153
|
+
for warning in self.deprecation_warnings:
|
|
154
|
+
lines.append(f"- {warning.deprecated_title} is deprecated")
|
|
155
|
+
if warning.replacement_title:
|
|
156
|
+
lines.append(f" Use instead: {warning.replacement_title}")
|
|
157
|
+
if verbosity == "detailed":
|
|
158
|
+
lines.append(f" Reason: {warning.reason}")
|
|
159
|
+
lines.append("")
|
|
160
|
+
|
|
161
|
+
# Summary
|
|
162
|
+
lines.append("## Summary")
|
|
163
|
+
lines.append(f"- Total memories: {self.total_memories}")
|
|
164
|
+
lines.append(f"- Active issues: {len(self.unresolved_problems)}")
|
|
165
|
+
lines.append(f"- Patterns available: {len(self.relevant_patterns)}")
|
|
166
|
+
if self.deprecation_warnings:
|
|
167
|
+
lines.append(f"- ⚠️ {len(self.deprecation_warnings)} deprecation warnings")
|
|
168
|
+
|
|
169
|
+
return "\n".join(lines)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
async def generate_session_briefing(
|
|
173
|
+
backend: GraphBackend,
|
|
174
|
+
project_dir: str,
|
|
175
|
+
recency_days: int = 7,
|
|
176
|
+
max_activities: int = 10,
|
|
177
|
+
) -> Optional[SessionBriefing]:
|
|
178
|
+
"""
|
|
179
|
+
Generate a session briefing for a project.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
backend: Database backend
|
|
183
|
+
project_dir: Project directory path
|
|
184
|
+
recency_days: How many days back to look for recent activity
|
|
185
|
+
max_activities: Maximum number of recent activities to include
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
SessionBriefing if project detected, None otherwise
|
|
189
|
+
|
|
190
|
+
Example:
|
|
191
|
+
>>> briefing = await generate_session_briefing(backend, "/Users/me/my-app")
|
|
192
|
+
>>> print(briefing.format_as_text("standard"))
|
|
193
|
+
"""
|
|
194
|
+
# Detect project
|
|
195
|
+
project = await detect_project(backend, project_dir)
|
|
196
|
+
if not project:
|
|
197
|
+
logger.warning(f"Could not detect project at {project_dir}")
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
logger.info(f"Generating session briefing for project: {project.name}")
|
|
201
|
+
|
|
202
|
+
# Initialize briefing
|
|
203
|
+
briefing = SessionBriefing(
|
|
204
|
+
project_name=project.name,
|
|
205
|
+
project_path=project.path,
|
|
206
|
+
project_type=project.project_type,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Get total memory count for project
|
|
210
|
+
total_count_query = """
|
|
211
|
+
MATCH (m:Memory)
|
|
212
|
+
WHERE m.context IS NOT NULL
|
|
213
|
+
AND (m.context CONTAINS $project_path OR m.context CONTAINS $project_name)
|
|
214
|
+
RETURN count(m) as total
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
result = await backend.execute_query(
|
|
219
|
+
total_count_query,
|
|
220
|
+
{
|
|
221
|
+
"project_path": project.path,
|
|
222
|
+
"project_name": project.name,
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
briefing.total_memories = result[0]["total"] if result else 0
|
|
226
|
+
except Exception as e:
|
|
227
|
+
logger.error(f"Error counting memories: {e}")
|
|
228
|
+
|
|
229
|
+
# Get recent activities
|
|
230
|
+
cutoff_date = datetime.now() - timedelta(days=recency_days)
|
|
231
|
+
|
|
232
|
+
recent_query = """
|
|
233
|
+
MATCH (m:Memory)
|
|
234
|
+
WHERE m.context IS NOT NULL
|
|
235
|
+
AND (m.context CONTAINS $project_path OR m.context CONTAINS $project_name)
|
|
236
|
+
AND datetime(m.created_at) >= datetime($cutoff)
|
|
237
|
+
RETURN m.id as id, m.type as type, m.title as title,
|
|
238
|
+
m.summary as summary, m.created_at as created_at,
|
|
239
|
+
m.tags as tags
|
|
240
|
+
ORDER BY m.created_at DESC
|
|
241
|
+
LIMIT $limit
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
results = await backend.execute_query(
|
|
246
|
+
recent_query,
|
|
247
|
+
{
|
|
248
|
+
"project_path": project.path,
|
|
249
|
+
"project_name": project.name,
|
|
250
|
+
"cutoff": cutoff_date.isoformat(),
|
|
251
|
+
"limit": max_activities,
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
for record in results:
|
|
256
|
+
briefing.recent_activities.append(RecentActivity(
|
|
257
|
+
memory_id=record["id"],
|
|
258
|
+
memory_type=record["type"],
|
|
259
|
+
title=record["title"],
|
|
260
|
+
summary=record.get("summary"),
|
|
261
|
+
timestamp=datetime.fromisoformat(record["created_at"]),
|
|
262
|
+
tags=record.get("tags", []),
|
|
263
|
+
))
|
|
264
|
+
except Exception as e:
|
|
265
|
+
logger.error(f"Error fetching recent activities: {e}")
|
|
266
|
+
|
|
267
|
+
# Get unresolved problems (problems without solutions)
|
|
268
|
+
problems_query = """
|
|
269
|
+
MATCH (p:Memory {type: 'problem'})
|
|
270
|
+
WHERE p.context IS NOT NULL
|
|
271
|
+
AND (p.context CONTAINS $project_path OR p.context CONTAINS $project_name)
|
|
272
|
+
AND NOT EXISTS {
|
|
273
|
+
MATCH (p)-[:SOLVES|ADDRESSES]->(:Memory)
|
|
274
|
+
}
|
|
275
|
+
OPTIONAL MATCH (p)-[r]-()
|
|
276
|
+
RETURN p.id as id, p.title as title, p.content as content,
|
|
277
|
+
p.created_at as created_at, p.tags as tags,
|
|
278
|
+
count(r) as related_count
|
|
279
|
+
ORDER BY p.created_at DESC
|
|
280
|
+
LIMIT 5
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
results = await backend.execute_query(
|
|
285
|
+
problems_query,
|
|
286
|
+
{
|
|
287
|
+
"project_path": project.path,
|
|
288
|
+
"project_name": project.name,
|
|
289
|
+
}
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
for record in results:
|
|
293
|
+
briefing.unresolved_problems.append(UnresolvedProblem(
|
|
294
|
+
problem_id=record["id"],
|
|
295
|
+
title=record["title"],
|
|
296
|
+
description=record["content"][:200],
|
|
297
|
+
created_at=datetime.fromisoformat(record["created_at"]),
|
|
298
|
+
tags=record.get("tags", []),
|
|
299
|
+
related_memories=record.get("related_count", 0),
|
|
300
|
+
))
|
|
301
|
+
|
|
302
|
+
briefing.has_active_issues = len(briefing.unresolved_problems) > 0
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logger.error(f"Error fetching unresolved problems: {e}")
|
|
305
|
+
|
|
306
|
+
# Get relevant patterns
|
|
307
|
+
patterns_query = """
|
|
308
|
+
MATCH (m:Memory {type: 'code_pattern'})
|
|
309
|
+
WHERE m.context IS NOT NULL
|
|
310
|
+
AND (m.context CONTAINS $project_path OR m.context CONTAINS $project_name)
|
|
311
|
+
RETURN m.id as id, m.title as type, m.content as description,
|
|
312
|
+
m.effectiveness as effectiveness, m.usage_count as usage_count,
|
|
313
|
+
m.last_accessed as last_used
|
|
314
|
+
ORDER BY m.effectiveness DESC, m.usage_count DESC
|
|
315
|
+
LIMIT 5
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
results = await backend.execute_query(
|
|
320
|
+
patterns_query,
|
|
321
|
+
{
|
|
322
|
+
"project_path": project.path,
|
|
323
|
+
"project_name": project.name,
|
|
324
|
+
}
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
for record in results:
|
|
328
|
+
briefing.relevant_patterns.append(RelevantPattern(
|
|
329
|
+
pattern_id=record["id"],
|
|
330
|
+
pattern_type=record["type"],
|
|
331
|
+
description=record["description"][:200],
|
|
332
|
+
effectiveness=record.get("effectiveness", 0.5),
|
|
333
|
+
usage_count=record.get("usage_count", 0),
|
|
334
|
+
last_used=datetime.fromisoformat(record["last_used"]) if record.get("last_used") else None,
|
|
335
|
+
))
|
|
336
|
+
except Exception as e:
|
|
337
|
+
logger.error(f"Error fetching patterns: {e}")
|
|
338
|
+
|
|
339
|
+
# Get deprecation warnings (memories with DEPRECATED_BY relationships)
|
|
340
|
+
deprecated_query = """
|
|
341
|
+
MATCH (old:Memory)-[r:DEPRECATED_BY]->(new:Memory)
|
|
342
|
+
WHERE old.context IS NOT NULL
|
|
343
|
+
AND (old.context CONTAINS $project_path OR old.context CONTAINS $project_name)
|
|
344
|
+
RETURN old.id as old_id, old.title as old_title,
|
|
345
|
+
new.id as new_id, new.title as new_title,
|
|
346
|
+
r.context as reason
|
|
347
|
+
LIMIT 5
|
|
348
|
+
"""
|
|
349
|
+
|
|
350
|
+
try:
|
|
351
|
+
results = await backend.execute_query(
|
|
352
|
+
deprecated_query,
|
|
353
|
+
{
|
|
354
|
+
"project_path": project.path,
|
|
355
|
+
"project_name": project.name,
|
|
356
|
+
}
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
for record in results:
|
|
360
|
+
briefing.deprecation_warnings.append(DeprecationWarning(
|
|
361
|
+
deprecated_id=record["old_id"],
|
|
362
|
+
deprecated_title=record["old_title"],
|
|
363
|
+
reason=record.get("reason", "No longer recommended"),
|
|
364
|
+
replacement_id=record.get("new_id"),
|
|
365
|
+
replacement_title=record.get("new_title"),
|
|
366
|
+
))
|
|
367
|
+
|
|
368
|
+
briefing.has_warnings = len(briefing.deprecation_warnings) > 0
|
|
369
|
+
except Exception as e:
|
|
370
|
+
logger.error(f"Error fetching deprecation warnings: {e}")
|
|
371
|
+
|
|
372
|
+
logger.info(f"Session briefing generated: {len(briefing.recent_activities)} activities, "
|
|
373
|
+
f"{len(briefing.unresolved_problems)} problems, "
|
|
374
|
+
f"{len(briefing.relevant_patterns)} patterns")
|
|
375
|
+
|
|
376
|
+
return briefing
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def get_session_briefing_resource(briefing: SessionBriefing, verbosity: str = "standard") -> Dict[str, Any]:
|
|
380
|
+
"""
|
|
381
|
+
Format session briefing as MCP resource.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
briefing: Session briefing to format
|
|
385
|
+
verbosity: Verbosity level ("minimal", "standard", "detailed")
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
MCP resource dictionary
|
|
389
|
+
|
|
390
|
+
Example:
|
|
391
|
+
>>> resource = get_session_briefing_resource(briefing, "standard")
|
|
392
|
+
"""
|
|
393
|
+
return {
|
|
394
|
+
"uri": f"memory://session/briefing/{briefing.project_name}",
|
|
395
|
+
"name": f"Session Briefing: {briefing.project_name}",
|
|
396
|
+
"description": f"Automatic session briefing for {briefing.project_name}",
|
|
397
|
+
"mimeType": "text/markdown",
|
|
398
|
+
"text": briefing.format_as_text(verbosity),
|
|
399
|
+
}
|