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
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SessionEnd hook for Claude Code.
4
+
5
+ Called when a Claude Code session ends. Performs final memory extraction
6
+ from the conversation transcript and cleans up the cursor file for
7
+ this session.
8
+
9
+ This script:
10
+ 1. Reads hook JSON from stdin (session_id, transcript_path, etc.)
11
+ 2. Runs final extraction via extract_memories.py (with is_session_end=True)
12
+ 3. Optionally invokes the existing session_end.py for full session wrapup
13
+ 4. Cleans up cursor state for this session
14
+ 5. Exits 0 on success OR failure (never blocks session teardown)
15
+
16
+ Timing budget: < 5 seconds total.
17
+ """
18
+
19
+ import sys
20
+ import json
21
+ import os
22
+ import time
23
+
24
+ # Ensure the hooks directory is on the path
25
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
26
+
27
+
28
+ def main():
29
+ start = time.time()
30
+
31
+ try:
32
+ # Read hook data from stdin
33
+ hook_data = {}
34
+ if not sys.stdin.isatty():
35
+ raw = sys.stdin.read()
36
+ if raw.strip():
37
+ hook_data = json.loads(raw)
38
+
39
+ # Ensure hook_event_name is set
40
+ if "hook_event_name" not in hook_data:
41
+ hook_data["hook_event_name"] = "SessionEnd"
42
+
43
+ session_id = hook_data.get("session_id", "")
44
+ transcript_path = hook_data.get("transcript_path", "")
45
+ project_path = hook_data.get("cwd") or hook_data.get("project_path", "")
46
+
47
+ # ---------------------------------------------------------------
48
+ # Step 1: Extract memories from transcript (final pass)
49
+ # ---------------------------------------------------------------
50
+ if transcript_path:
51
+ from extract_memories import run_extraction
52
+
53
+ results = run_extraction(
54
+ session_id=session_id,
55
+ transcript_path=transcript_path,
56
+ project_path=project_path,
57
+ is_session_end=True, # This will clean up the cursor after extraction
58
+ )
59
+
60
+ elapsed_extract = round(time.time() - start, 2)
61
+ print(
62
+ f"[SessionEnd] Extraction complete: "
63
+ f"extracted={results['extracted']} stored={results['stored']} "
64
+ f"errors={results['errors']} time={elapsed_extract}s",
65
+ file=sys.stderr,
66
+ )
67
+ else:
68
+ print("[SessionEnd] No transcript_path provided, skipping extraction.", file=sys.stderr)
69
+ # Still clean up cursor if session_id is present
70
+ if session_id:
71
+ try:
72
+ from extract_memories import cleanup_cursor
73
+ cleanup_cursor(session_id)
74
+ except ImportError:
75
+ pass
76
+
77
+ # ---------------------------------------------------------------
78
+ # Step 2: Trigger the existing session_end.py wrapup logic
79
+ # (summarization, daily log, MEMORY.md sync, flush)
80
+ # Only if we have time left in our budget
81
+ # ---------------------------------------------------------------
82
+ remaining = 5.0 - (time.time() - start)
83
+ if remaining > 1.0 and session_id:
84
+ try:
85
+ _trigger_session_wrapup(session_id, project_path, timeout=remaining - 0.5)
86
+ except Exception as e:
87
+ print(f"[SessionEnd] Session wrapup failed (non-fatal): {e}", file=sys.stderr)
88
+
89
+ elapsed_total = round(time.time() - start, 2)
90
+ print(f"[SessionEnd] Complete. Total time: {elapsed_total}s", file=sys.stderr)
91
+
92
+ except Exception as e:
93
+ elapsed = round(time.time() - start, 2)
94
+ print(f"[SessionEnd] Error (non-fatal): {e} [{elapsed}s]", file=sys.stderr)
95
+
96
+ # Always exit 0 - never block session end
97
+ sys.exit(0)
98
+
99
+
100
+ def _trigger_session_wrapup(session_id: str, project_path: str, timeout: float = 3.0):
101
+ """
102
+ Trigger the existing session_end.py summarization via the memory agent API.
103
+ This calls key skills: daily_log_append_session, sync_memory_md, pre_compaction_flush.
104
+ Uses a single lightweight API call rather than the full async pipeline.
105
+ """
106
+ import urllib.request
107
+ import urllib.error
108
+
109
+ memory_agent_url = os.getenv("MEMORY_AGENT_URL", "http://localhost:8102")
110
+ api_key = os.getenv("MEMORY_API_KEY", "")
111
+
112
+ # Call the pre_compaction_flush skill as a lightweight session wrapup
113
+ payload = {
114
+ "jsonrpc": "2.0",
115
+ "method": "tasks/send",
116
+ "params": {
117
+ "message": {"parts": [{"type": "text", "text": ""}]},
118
+ "metadata": {
119
+ "skill_id": "pre_compaction_flush",
120
+ "params": {
121
+ "project_path": project_path,
122
+ "session_id": session_id,
123
+ }
124
+ }
125
+ },
126
+ "id": f"session-end-flush-{session_id}"
127
+ }
128
+
129
+ headers = {"Content-Type": "application/json"}
130
+ if api_key:
131
+ headers["X-Memory-Key"] = api_key
132
+
133
+ try:
134
+ data = json.dumps(payload).encode("utf-8")
135
+ req = urllib.request.Request(
136
+ f"{memory_agent_url}/a2a",
137
+ data=data,
138
+ headers=headers,
139
+ method="POST"
140
+ )
141
+ with urllib.request.urlopen(req, timeout=min(timeout, 3.0)) as resp:
142
+ if resp.status == 200:
143
+ print(f"[SessionEnd] Flush triggered successfully.", file=sys.stderr)
144
+ except (urllib.error.URLError, urllib.error.HTTPError, OSError, TimeoutError) as e:
145
+ print(f"[SessionEnd] Flush API call failed: {e}", file=sys.stderr)
146
+
147
+
148
+ if __name__ == "__main__":
149
+ main()
@@ -1,227 +1,227 @@
1
- #!/usr/bin/env python3
2
- """Session start hook - auto-loads relevant context.
3
-
4
- This hook runs when a Claude Code session starts and:
5
- - Loads project info and preferences
6
- - Retrieves recent decisions and patterns
7
- - Gets unresolved items from previous sessions
8
- - Injects relevant context into the session
9
- - Loads daily logs (Moltbot-inspired)
10
- - Loads MEMORY.md core facts (Moltbot-inspired)
11
-
12
- Configure in Claude Code settings:
13
- {
14
- "hooks": {
15
- "SessionStart": ["python /path/to/session_start.py"]
16
- }
17
- }
18
-
19
- Output is printed to stdout and injected into Claude's context.
20
- """
21
- import os
22
- import sys
23
- import json
24
- import asyncio
25
- from datetime import datetime
26
- from pathlib import Path
27
- from typing import Dict, Any, List, Optional
28
-
29
- sys.path.insert(0, str(Path(__file__).parent.parent))
30
-
31
- import httpx
32
-
33
- MEMORY_AGENT_URL = os.getenv("MEMORY_AGENT_URL", "http://localhost:8102")
34
- API_KEY = os.getenv("MEMORY_API_KEY", "")
35
-
36
-
37
- async def call_memory_skill(skill_id: str, params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
38
- """Call a memory agent skill."""
39
- headers = {"Content-Type": "application/json"}
40
- if API_KEY:
41
- headers["X-Memory-Key"] = API_KEY
42
-
43
- payload = {
44
- "jsonrpc": "2.0",
45
- "method": "skills/call",
46
- "params": {
47
- "skill_id": skill_id,
48
- "params": params
49
- },
50
- "id": f"session-start-{datetime.now().isoformat()}"
51
- }
52
-
53
- try:
54
- async with httpx.AsyncClient(timeout=10.0) as client:
55
- response = await client.post(
56
- f"{MEMORY_AGENT_URL}/a2a",
57
- json=payload,
58
- headers=headers
59
- )
60
- if response.status_code == 200:
61
- data = response.json()
62
- return data.get("result", {}).get("result", {})
63
- except Exception as e:
64
- pass
65
- return None
66
-
67
-
68
- async def load_session_context(project_path: str) -> str:
69
- """Load all relevant context for a session start."""
70
- context_parts = []
71
-
72
- # ============================================================
73
- # MOLTBOT-INSPIRED: Load MEMORY.md first (core facts)
74
- # ============================================================
75
- memory_md = await call_memory_skill("read_memory_md", {
76
- "project_path": project_path
77
- })
78
-
79
- if memory_md and memory_md.get("exists"):
80
- context_parts.append("## Core Facts (from MEMORY.md)")
81
- # Include the summary or first part of content
82
- content = memory_md.get("content", "")
83
- # Truncate if too long
84
- if len(content) > 2000:
85
- content = content[:2000] + "\n...(truncated)"
86
- context_parts.append(content)
87
-
88
- # ============================================================
89
- # MOLTBOT-INSPIRED: Load recent daily logs
90
- # ============================================================
91
- daily_logs = await call_memory_skill("daily_log_read", {
92
- "project_path": project_path,
93
- "days": 2,
94
- "max_chars": 3000
95
- })
96
-
97
- if daily_logs and daily_logs.get("logs"):
98
- context_parts.append("\n## Recent Activity (from Daily Logs)")
99
- for log in daily_logs["logs"]:
100
- log_date = log.get("date", "Unknown")
101
- log_content = log.get("content", "")
102
- # Show just the highlights, not full content
103
- if len(log_content) > 1500:
104
- log_content = log_content[:1500] + "\n...(truncated)"
105
- context_parts.append(f"\n### {log_date}")
106
- context_parts.append(log_content)
107
-
108
- # ============================================================
109
- # ORIGINAL MEMORY SYSTEM CONTEXT
110
- # ============================================================
111
-
112
- # 1. Get project info
113
- project_info = await call_memory_skill("get_project_context", {
114
- "project_path": project_path,
115
- "limit": 5
116
- })
117
-
118
- if project_info and project_info.get("project"):
119
- proj = project_info["project"]
120
- context_parts.append(f"\n## Project: {proj.get('name', project_path)}")
121
- if proj.get("tech_stack"):
122
- context_parts.append(f"Tech Stack: {', '.join(proj['tech_stack'])}")
123
- if proj.get("conventions"):
124
- context_parts.append(f"Conventions: {json.dumps(proj['conventions'], indent=2)}")
125
-
126
- # 2. Get recent decisions
127
- decisions = await call_memory_skill("semantic_search", {
128
- "query": "decision architecture approach",
129
- "project_path": project_path,
130
- "type": "decision",
131
- "limit": 5
132
- })
133
-
134
- if decisions and decisions.get("results"):
135
- context_parts.append("\n## Recent Decisions")
136
- for d in decisions["results"][:3]:
137
- context_parts.append(f"- {d['content'][:150]}")
138
-
139
- # 3. Get recent errors (to avoid repeating)
140
- errors = await call_memory_skill("semantic_search", {
141
- "query": "error bug fix problem",
142
- "project_path": project_path,
143
- "type": "error",
144
- "success_only": True, # Only get solved errors
145
- "limit": 5
146
- })
147
-
148
- if errors and errors.get("results"):
149
- context_parts.append("\n## Past Errors & Solutions")
150
- for e in errors["results"][:3]:
151
- context_parts.append(f"- {e['content'][:150]}")
152
-
153
- # 4. Get session handoff (unresolved items)
154
- handoff = await call_memory_skill("get_session_handoff", {
155
- "project_path": project_path,
156
- "include_last_n_sessions": 2
157
- })
158
-
159
- if handoff:
160
- if handoff.get("unresolved_questions"):
161
- context_parts.append("\n## Unresolved from Previous Sessions")
162
- for q in handoff["unresolved_questions"][:3]:
163
- context_parts.append(f"- {q}")
164
-
165
- if handoff.get("recent_summaries"):
166
- context_parts.append("\n## Recent Session Summaries")
167
- for s in handoff["recent_summaries"][:2]:
168
- context_parts.append(f"- {s.get('summary', '')[:200]}")
169
-
170
- # 5. Get relevant patterns
171
- patterns = await call_memory_skill("search_patterns", {
172
- "query": "common patterns solutions",
173
- "limit": 3
174
- })
175
-
176
- if patterns and patterns.get("patterns"):
177
- context_parts.append("\n## Useful Patterns")
178
- for p in patterns["patterns"][:2]:
179
- context_parts.append(f"- **{p['name']}**: {p['solution'][:100]}")
180
-
181
- # 6. Check for anchor conflicts
182
- conflicts = await call_memory_skill("get_unresolved_conflicts", {
183
- "project_path": project_path,
184
- "limit": 3
185
- })
186
-
187
- if conflicts and conflicts.get("conflicts"):
188
- context_parts.append("\n## Unresolved Fact Conflicts")
189
- for c in conflicts["conflicts"]:
190
- context_parts.append(f"- {c.get('anchor1_summary', '')} vs {c.get('anchor2_summary', '')}")
191
-
192
- if context_parts:
193
- return "\n".join(context_parts)
194
- return ""
195
-
196
-
197
- async def main():
198
- # Get project path from environment or current directory
199
- project_path = os.getenv("PROJECT_PATH") or os.getcwd()
200
-
201
- # Try to read from stdin if available
202
- try:
203
- if not sys.stdin.isatty():
204
- data = sys.stdin.read()
205
- if data:
206
- hook_data = json.loads(data)
207
- project_path = hook_data.get("project_path", project_path)
208
- except:
209
- pass
210
-
211
- context = await load_session_context(project_path)
212
-
213
- if context:
214
- # Output context for Claude to see
215
- print("\n<memory-context>")
216
- print("# Loaded from Memory System")
217
- print(context)
218
- print("</memory-context>\n")
219
- else:
220
- print("\n<memory-context>")
221
- print("# No prior context found for this project")
222
- print("Starting fresh session.")
223
- print("</memory-context>\n")
224
-
225
-
226
- if __name__ == "__main__":
227
- asyncio.run(main())
1
+ #!/usr/bin/env python3
2
+ """Session start hook - auto-loads relevant context.
3
+
4
+ This hook runs when a Claude Code session starts and:
5
+ - Loads project info and preferences
6
+ - Retrieves recent decisions and patterns
7
+ - Gets unresolved items from previous sessions
8
+ - Injects relevant context into the session
9
+ - Loads daily logs (Moltbot-inspired)
10
+ - Loads MEMORY.md core facts (Moltbot-inspired)
11
+
12
+ Configure in Claude Code settings:
13
+ {
14
+ "hooks": {
15
+ "SessionStart": ["python /path/to/session_start.py"]
16
+ }
17
+ }
18
+
19
+ Output is printed to stdout and injected into Claude's context.
20
+ """
21
+ import os
22
+ import sys
23
+ import json
24
+ import asyncio
25
+ from datetime import datetime
26
+ from pathlib import Path
27
+ from typing import Dict, Any, List, Optional
28
+
29
+ sys.path.insert(0, str(Path(__file__).parent.parent))
30
+
31
+ import httpx
32
+
33
+ MEMORY_AGENT_URL = os.getenv("MEMORY_AGENT_URL", "http://localhost:8102")
34
+ API_KEY = os.getenv("MEMORY_API_KEY", "")
35
+
36
+
37
+ async def call_memory_skill(skill_id: str, params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
38
+ """Call a memory agent skill."""
39
+ headers = {"Content-Type": "application/json"}
40
+ if API_KEY:
41
+ headers["X-Memory-Key"] = API_KEY
42
+
43
+ payload = {
44
+ "jsonrpc": "2.0",
45
+ "method": "skills/call",
46
+ "params": {
47
+ "skill_id": skill_id,
48
+ "params": params
49
+ },
50
+ "id": f"session-start-{datetime.now().isoformat()}"
51
+ }
52
+
53
+ try:
54
+ async with httpx.AsyncClient(timeout=10.0) as client:
55
+ response = await client.post(
56
+ f"{MEMORY_AGENT_URL}/a2a",
57
+ json=payload,
58
+ headers=headers
59
+ )
60
+ if response.status_code == 200:
61
+ data = response.json()
62
+ return data.get("result", {}).get("result", {})
63
+ except Exception as e:
64
+ pass
65
+ return None
66
+
67
+
68
+ async def load_session_context(project_path: str) -> str:
69
+ """Load all relevant context for a session start."""
70
+ context_parts = []
71
+
72
+ # ============================================================
73
+ # MOLTBOT-INSPIRED: Load MEMORY.md first (core facts)
74
+ # ============================================================
75
+ memory_md = await call_memory_skill("read_memory_md", {
76
+ "project_path": project_path
77
+ })
78
+
79
+ if memory_md and memory_md.get("exists"):
80
+ context_parts.append("## Core Facts (from MEMORY.md)")
81
+ # Include the summary or first part of content
82
+ content = memory_md.get("content", "")
83
+ # Truncate if too long
84
+ if len(content) > 2000:
85
+ content = content[:2000] + "\n...(truncated)"
86
+ context_parts.append(content)
87
+
88
+ # ============================================================
89
+ # MOLTBOT-INSPIRED: Load recent daily logs
90
+ # ============================================================
91
+ daily_logs = await call_memory_skill("daily_log_read", {
92
+ "project_path": project_path,
93
+ "days": 2,
94
+ "max_chars": 3000
95
+ })
96
+
97
+ if daily_logs and daily_logs.get("logs"):
98
+ context_parts.append("\n## Recent Activity (from Daily Logs)")
99
+ for log in daily_logs["logs"]:
100
+ log_date = log.get("date", "Unknown")
101
+ log_content = log.get("content", "")
102
+ # Show just the highlights, not full content
103
+ if len(log_content) > 1500:
104
+ log_content = log_content[:1500] + "\n...(truncated)"
105
+ context_parts.append(f"\n### {log_date}")
106
+ context_parts.append(log_content)
107
+
108
+ # ============================================================
109
+ # ORIGINAL MEMORY SYSTEM CONTEXT
110
+ # ============================================================
111
+
112
+ # 1. Get project info
113
+ project_info = await call_memory_skill("get_project_context", {
114
+ "project_path": project_path,
115
+ "limit": 5
116
+ })
117
+
118
+ if project_info and project_info.get("project"):
119
+ proj = project_info["project"]
120
+ context_parts.append(f"\n## Project: {proj.get('name', project_path)}")
121
+ if proj.get("tech_stack"):
122
+ context_parts.append(f"Tech Stack: {', '.join(proj['tech_stack'])}")
123
+ if proj.get("conventions"):
124
+ context_parts.append(f"Conventions: {json.dumps(proj['conventions'], indent=2)}")
125
+
126
+ # 2. Get recent decisions
127
+ decisions = await call_memory_skill("semantic_search", {
128
+ "query": "decision architecture approach",
129
+ "project_path": project_path,
130
+ "type": "decision",
131
+ "limit": 5
132
+ })
133
+
134
+ if decisions and decisions.get("results"):
135
+ context_parts.append("\n## Recent Decisions")
136
+ for d in decisions["results"][:3]:
137
+ context_parts.append(f"- {d['content'][:150]}")
138
+
139
+ # 3. Get recent errors (to avoid repeating)
140
+ errors = await call_memory_skill("semantic_search", {
141
+ "query": "error bug fix problem",
142
+ "project_path": project_path,
143
+ "type": "error",
144
+ "success_only": True, # Only get solved errors
145
+ "limit": 5
146
+ })
147
+
148
+ if errors and errors.get("results"):
149
+ context_parts.append("\n## Past Errors & Solutions")
150
+ for e in errors["results"][:3]:
151
+ context_parts.append(f"- {e['content'][:150]}")
152
+
153
+ # 4. Get session handoff (unresolved items)
154
+ handoff = await call_memory_skill("get_session_handoff", {
155
+ "project_path": project_path,
156
+ "include_last_n_sessions": 2
157
+ })
158
+
159
+ if handoff:
160
+ if handoff.get("unresolved_questions"):
161
+ context_parts.append("\n## Unresolved from Previous Sessions")
162
+ for q in handoff["unresolved_questions"][:3]:
163
+ context_parts.append(f"- {q}")
164
+
165
+ if handoff.get("recent_summaries"):
166
+ context_parts.append("\n## Recent Session Summaries")
167
+ for s in handoff["recent_summaries"][:2]:
168
+ context_parts.append(f"- {s.get('summary', '')[:200]}")
169
+
170
+ # 5. Get relevant patterns
171
+ patterns = await call_memory_skill("search_patterns", {
172
+ "query": "common patterns solutions",
173
+ "limit": 3
174
+ })
175
+
176
+ if patterns and patterns.get("patterns"):
177
+ context_parts.append("\n## Useful Patterns")
178
+ for p in patterns["patterns"][:2]:
179
+ context_parts.append(f"- **{p['name']}**: {p['solution'][:100]}")
180
+
181
+ # 6. Check for anchor conflicts
182
+ conflicts = await call_memory_skill("get_unresolved_conflicts", {
183
+ "project_path": project_path,
184
+ "limit": 3
185
+ })
186
+
187
+ if conflicts and conflicts.get("conflicts"):
188
+ context_parts.append("\n## Unresolved Fact Conflicts")
189
+ for c in conflicts["conflicts"]:
190
+ context_parts.append(f"- {c.get('anchor1_summary', '')} vs {c.get('anchor2_summary', '')}")
191
+
192
+ if context_parts:
193
+ return "\n".join(context_parts)
194
+ return ""
195
+
196
+
197
+ async def main():
198
+ # Get project path from environment or current directory
199
+ project_path = os.getenv("PROJECT_PATH") or os.getcwd()
200
+
201
+ # Try to read from stdin if available
202
+ try:
203
+ if not sys.stdin.isatty():
204
+ data = sys.stdin.read()
205
+ if data:
206
+ hook_data = json.loads(data)
207
+ project_path = hook_data.get("project_path", project_path)
208
+ except:
209
+ pass
210
+
211
+ context = await load_session_context(project_path)
212
+
213
+ if context:
214
+ # Output context for Claude to see
215
+ print("\n<memory-context>")
216
+ print("# Loaded from Memory System")
217
+ print(context)
218
+ print("</memory-context>\n")
219
+ else:
220
+ print("\n<memory-context>")
221
+ print("# No prior context found for this project")
222
+ print("Starting fresh session.")
223
+ print("</memory-context>\n")
224
+
225
+
226
+ if __name__ == "__main__":
227
+ asyncio.run(main())