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,76 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ PreCompact hook for Claude Code.
4
+
5
+ Called before context compaction. Extracts memories from the conversation
6
+ transcript so important information is preserved even if compaction
7
+ discards conversation turns.
8
+
9
+ This script:
10
+ 1. Reads hook JSON from stdin (session_id, transcript_path, etc.)
11
+ 2. Delegates to extract_memories.py for the actual extraction
12
+ 3. Exits 0 on success OR failure (never blocks compaction)
13
+
14
+ Timing budget: < 5 seconds total.
15
+ """
16
+
17
+ import sys
18
+ import json
19
+ import os
20
+ import time
21
+
22
+ # Ensure the hooks directory is on the path
23
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
24
+
25
+
26
+ def main():
27
+ start = time.time()
28
+
29
+ try:
30
+ # Read hook data from stdin
31
+ hook_data = {}
32
+ if not sys.stdin.isatty():
33
+ raw = sys.stdin.read()
34
+ if raw.strip():
35
+ hook_data = json.loads(raw)
36
+
37
+ # Ensure hook_event_name is set
38
+ if "hook_event_name" not in hook_data:
39
+ hook_data["hook_event_name"] = "PreCompact"
40
+
41
+ session_id = hook_data.get("session_id", "")
42
+ transcript_path = hook_data.get("transcript_path", "")
43
+
44
+ if not transcript_path:
45
+ # No transcript available, nothing to extract
46
+ print("[PreCompact] No transcript_path provided, skipping extraction.", file=sys.stderr)
47
+ sys.exit(0)
48
+
49
+ # Import and run extraction
50
+ from extract_memories import run_extraction
51
+
52
+ results = run_extraction(
53
+ session_id=session_id,
54
+ transcript_path=transcript_path,
55
+ project_path=hook_data.get("cwd") or hook_data.get("project_path", ""),
56
+ is_session_end=False,
57
+ )
58
+
59
+ elapsed = round(time.time() - start, 2)
60
+ print(
61
+ f"[PreCompact] Extraction complete: "
62
+ f"extracted={results['extracted']} stored={results['stored']} "
63
+ f"errors={results['errors']} total_time={elapsed}s",
64
+ file=sys.stderr,
65
+ )
66
+
67
+ except Exception as e:
68
+ elapsed = round(time.time() - start, 2)
69
+ print(f"[PreCompact] Error (non-fatal): {e} [{elapsed}s]", file=sys.stderr)
70
+
71
+ # Always exit 0 - never block compaction
72
+ sys.exit(0)
73
+
74
+
75
+ if __name__ == "__main__":
76
+ main()
@@ -1,192 +1,293 @@
1
- #!/usr/bin/env python3
2
- """Session end hook - auto-summarizes and stores session.
3
-
4
- This hook runs when a Claude Code session ends and:
5
- - Summarizes the session automatically
6
- - Stores important decisions and learnings
7
- - Updates project insights
8
- - Syncs to CLAUDE.md if needed
9
- - Appends session summary to daily log (Moltbot-inspired)
10
- - Triggers MEMORY.md sync (Moltbot-inspired)
11
- - Executes pre-compaction flush (Moltbot-inspired)
12
-
13
- Configure in Claude Code settings:
14
- {
15
- "hooks": {
16
- "SessionEnd": ["python /path/to/session_end.py"]
17
- }
18
- }
19
- """
20
- import os
21
- import sys
22
- import json
23
- import asyncio
24
- from datetime import datetime
25
- from pathlib import Path
26
- from typing import Dict, Any, Optional, List
27
-
28
- sys.path.insert(0, str(Path(__file__).parent.parent))
29
-
30
- import httpx
31
-
32
- MEMORY_AGENT_URL = os.getenv("MEMORY_AGENT_URL", "http://localhost:8102")
33
- API_KEY = os.getenv("MEMORY_API_KEY", "")
34
-
35
-
36
- async def call_memory_skill(skill_id: str, params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
37
- """Call a memory agent skill."""
38
- headers = {"Content-Type": "application/json"}
39
- if API_KEY:
40
- headers["X-Memory-Key"] = API_KEY
41
-
42
- payload = {
43
- "jsonrpc": "2.0",
44
- "method": "skills/call",
45
- "params": {
46
- "skill_id": skill_id,
47
- "params": params
48
- },
49
- "id": f"session-end-{datetime.now().isoformat()}"
50
- }
51
-
52
- try:
53
- async with httpx.AsyncClient(timeout=15.0) as client:
54
- response = await client.post(
55
- f"{MEMORY_AGENT_URL}/a2a",
56
- json=payload,
57
- headers=headers
58
- )
59
- if response.status_code == 200:
60
- data = response.json()
61
- return data.get("result", {}).get("result", {})
62
- except Exception:
63
- pass
64
- return None
65
-
66
-
67
- async def end_session(session_id: str, project_path: str):
68
- """Handle session end - summarize and store."""
69
- results = []
70
- session_data = {
71
- "decisions": [],
72
- "accomplishments": [],
73
- "errors_solved": [],
74
- "notes": []
75
- }
76
-
77
- # 1. Auto-summarize the session
78
- summary = await call_memory_skill("auto_summarize_session", {
79
- "session_id": session_id,
80
- "project_path": project_path
81
- })
82
-
83
- if summary and summary.get("success"):
84
- results.append(f"Session summarized: {summary.get('summary', '')[:100]}...")
85
- # Extract decisions and accomplishments from summary if available
86
- if summary.get("key_decisions"):
87
- session_data["decisions"] = summary["key_decisions"][:5]
88
-
89
- # 2. Create diary entry
90
- diary = await call_memory_skill("create_diary_entry", {
91
- "session_id": session_id,
92
- "project_path": project_path
93
- })
94
-
95
- if diary and diary.get("success"):
96
- results.append(f"Diary entry created: ID {diary.get('memory_id')}")
97
-
98
- # 3. Run insight aggregation
99
- insights = await call_memory_skill("run_aggregation", {
100
- "project_path": project_path
101
- })
102
-
103
- if insights and insights.get("success"):
104
- new_insights = insights.get("new_insights", 0)
105
- if new_insights > 0:
106
- results.append(f"Generated {new_insights} new insights")
107
-
108
- # 4. Check for CLAUDE.md suggestions
109
- suggestions = await call_memory_skill("suggest_improvements", {
110
- "project_path": project_path
111
- })
112
-
113
- if suggestions and suggestions.get("suggestions"):
114
- results.append(f"CLAUDE.md suggestions: {len(suggestions['suggestions'])} available")
115
-
116
- # 5. Auto-resolve any obvious anchor conflicts
117
- resolved = await call_memory_skill("auto_resolve_conflicts", {
118
- "project_path": project_path
119
- })
120
-
121
- if resolved and resolved.get("resolved_count", 0) > 0:
122
- results.append(f"Auto-resolved {resolved['resolved_count']} conflicts")
123
-
124
- # ============================================================
125
- # MOLTBOT-INSPIRED FEATURES
126
- # ============================================================
127
-
128
- # 6. Append session summary to daily log
129
- daily_log = await call_memory_skill("daily_log_append_session", {
130
- "project_path": project_path,
131
- "session_id": session_id,
132
- "decisions": session_data["decisions"],
133
- "accomplishments": session_data["accomplishments"],
134
- "errors_solved": session_data["errors_solved"],
135
- "notes": session_data["notes"]
136
- })
137
-
138
- if daily_log and daily_log.get("success"):
139
- results.append(f"Daily log updated: {daily_log.get('file_path', 'unknown')}")
140
-
141
- # 7. Sync MEMORY.md with high-importance items
142
- memory_md = await call_memory_skill("sync_memory_md", {
143
- "project_path": project_path,
144
- "min_importance": 7,
145
- "min_pattern_success": 3
146
- })
147
-
148
- if memory_md and memory_md.get("success"):
149
- counts = memory_md.get("counts", {})
150
- total_synced = sum(counts.values())
151
- if total_synced > 0:
152
- results.append(f"MEMORY.md synced: {total_synced} items")
153
-
154
- # 8. Execute pre-compaction flush
155
- flush = await call_memory_skill("pre_compaction_flush", {
156
- "project_path": project_path,
157
- "session_id": session_id
158
- })
159
-
160
- if flush and flush.get("success"):
161
- results.append(f"Memory flush created: {flush.get('file_path', 'unknown')}")
162
-
163
- return results
164
-
165
-
166
- async def main():
167
- session_id = os.getenv("SESSION_ID") or f"session-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
168
- project_path = os.getenv("PROJECT_PATH") or os.getcwd()
169
-
170
- # Try to read from stdin
171
- try:
172
- if not sys.stdin.isatty():
173
- data = sys.stdin.read()
174
- if data:
175
- hook_data = json.loads(data)
176
- session_id = hook_data.get("session_id", session_id)
177
- project_path = hook_data.get("project_path", project_path)
178
- except:
179
- pass
180
-
181
- results = await end_session(session_id, project_path)
182
-
183
- if results:
184
- print("\n[Memory System] Session ended:")
185
- for r in results:
186
- print(f" - {r}")
187
- else:
188
- print("\n[Memory System] Session ended (no data captured)")
189
-
190
-
191
- if __name__ == "__main__":
192
- asyncio.run(main())
1
+ #!/usr/bin/env python3
2
+ """Session end hook - auto-summarizes and stores session.
3
+
4
+ This hook runs when a Claude Code session ends and:
5
+ - Summarizes the session automatically
6
+ - Stores important decisions and learnings
7
+ - Updates project insights
8
+ - Syncs to CLAUDE.md if needed
9
+ - Appends session summary to daily log (Moltbot-inspired)
10
+ - Triggers MEMORY.md sync (Moltbot-inspired)
11
+ - Executes pre-compaction flush (Moltbot-inspired)
12
+ - Outputs session review summary for user verification
13
+
14
+ Configure in Claude Code settings:
15
+ {
16
+ "hooks": {
17
+ "SessionEnd": ["python /path/to/session_end.py"]
18
+ }
19
+ }
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, Optional, List
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-end-{datetime.now().isoformat()}"
51
+ }
52
+
53
+ try:
54
+ async with httpx.AsyncClient(timeout=15.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:
64
+ pass
65
+ return None
66
+
67
+
68
+ async def call_api(endpoint: str, method: str = "GET", params: Dict = None) -> Optional[Dict[str, Any]]:
69
+ """Call a memory agent REST API endpoint."""
70
+ headers = {"Content-Type": "application/json"}
71
+ if API_KEY:
72
+ headers["X-Memory-Key"] = API_KEY
73
+
74
+ try:
75
+ async with httpx.AsyncClient(timeout=15.0) as client:
76
+ url = f"{MEMORY_AGENT_URL}{endpoint}"
77
+ if method == "GET":
78
+ response = await client.get(url, headers=headers, params=params)
79
+ else:
80
+ response = await client.post(url, headers=headers, json=params)
81
+
82
+ if response.status_code == 200:
83
+ return response.json()
84
+ except Exception:
85
+ pass
86
+ return None
87
+
88
+
89
+ async def get_session_review_summary(session_id: str) -> Dict[str, Any]:
90
+ """Get a summary of session memories for review."""
91
+ # Get session memories
92
+ memories = await call_api(f"/api/session/{session_id}/memories")
93
+ if not memories or not memories.get("success"):
94
+ return {"success": False, "memory_count": 0}
95
+
96
+ # Get suggestions for review
97
+ suggestions = await call_api(f"/api/session/{session_id}/suggestions")
98
+
99
+ return {
100
+ "success": True,
101
+ "memory_count": memories.get("memory_count", 0),
102
+ "memories": memories.get("memories", []),
103
+ "summary": memories.get("summary", {}),
104
+ "suggestions": suggestions.get("suggestions", []) if suggestions else [],
105
+ "suggestion_summary": suggestions.get("summary", {}) if suggestions else {}
106
+ }
107
+
108
+
109
+ def format_review_output(review_data: Dict[str, Any], session_id: str) -> List[str]:
110
+ """Format the session review data for output."""
111
+ lines = []
112
+
113
+ memory_count = review_data.get("memory_count", 0)
114
+ if memory_count == 0:
115
+ return ["No memories created this session"]
116
+
117
+ lines.append(f"Session created {memory_count} memories for review")
118
+
119
+ # Type breakdown
120
+ summary = review_data.get("summary", {})
121
+ by_type = summary.get("by_type", {})
122
+ if by_type:
123
+ type_parts = [f"{count} {mtype}" for mtype, count in by_type.items()]
124
+ lines.append(f" Types: {', '.join(type_parts)}")
125
+
126
+ # Suggestion summary
127
+ sugg_summary = review_data.get("suggestion_summary", {})
128
+ if sugg_summary:
129
+ keep = sugg_summary.get("suggested_keep", 0)
130
+ discard = sugg_summary.get("suggested_discard", 0)
131
+ partial = sugg_summary.get("suggested_partial", 0)
132
+ lines.append(f" Suggestions: {keep} keep, {partial} review, {discard} discard")
133
+
134
+ # List high-importance memories
135
+ memories = review_data.get("memories", [])
136
+ high_importance = [m for m in memories if m.get("importance", 5) >= 7]
137
+ if high_importance:
138
+ lines.append("")
139
+ lines.append(" High-importance memories to verify:")
140
+ for m in high_importance[:5]:
141
+ mtype = m.get("type", "chunk")
142
+ content = m.get("content", "")[:60]
143
+ importance = m.get("importance", 5)
144
+ lines.append(f" [{mtype}] (imp:{importance}) {content}...")
145
+
146
+ # Review URL
147
+ lines.append("")
148
+ lines.append(f" Review at: {MEMORY_AGENT_URL}/dashboard#review/{session_id}")
149
+
150
+ return lines
151
+
152
+
153
+ async def end_session(session_id: str, project_path: str):
154
+ """Handle session end - summarize and store."""
155
+ results = []
156
+ session_data = {
157
+ "decisions": [],
158
+ "accomplishments": [],
159
+ "errors_solved": [],
160
+ "notes": []
161
+ }
162
+
163
+ # 1. Auto-summarize the session
164
+ summary = await call_memory_skill("auto_summarize_session", {
165
+ "session_id": session_id,
166
+ "project_path": project_path
167
+ })
168
+
169
+ if summary and summary.get("success"):
170
+ results.append(f"Session summarized: {summary.get('summary', '')[:100]}...")
171
+ # Extract decisions and accomplishments from summary if available
172
+ if summary.get("key_decisions"):
173
+ session_data["decisions"] = summary["key_decisions"][:5]
174
+
175
+ # 2. Create diary entry
176
+ diary = await call_memory_skill("create_diary_entry", {
177
+ "session_id": session_id,
178
+ "project_path": project_path
179
+ })
180
+
181
+ if diary and diary.get("success"):
182
+ results.append(f"Diary entry created: ID {diary.get('memory_id')}")
183
+
184
+ # 3. Run insight aggregation
185
+ insights = await call_memory_skill("run_aggregation", {
186
+ "project_path": project_path
187
+ })
188
+
189
+ if insights and insights.get("success"):
190
+ new_insights = insights.get("new_insights", 0)
191
+ if new_insights > 0:
192
+ results.append(f"Generated {new_insights} new insights")
193
+
194
+ # 4. Check for CLAUDE.md suggestions
195
+ suggestions = await call_memory_skill("suggest_improvements", {
196
+ "project_path": project_path
197
+ })
198
+
199
+ if suggestions and suggestions.get("suggestions"):
200
+ results.append(f"CLAUDE.md suggestions: {len(suggestions['suggestions'])} available")
201
+
202
+ # 5. Auto-resolve any obvious anchor conflicts
203
+ resolved = await call_memory_skill("auto_resolve_conflicts", {
204
+ "project_path": project_path
205
+ })
206
+
207
+ if resolved and resolved.get("resolved_count", 0) > 0:
208
+ results.append(f"Auto-resolved {resolved['resolved_count']} conflicts")
209
+
210
+ # ============================================================
211
+ # MOLTBOT-INSPIRED FEATURES
212
+ # ============================================================
213
+
214
+ # 6. Append session summary to daily log
215
+ daily_log = await call_memory_skill("daily_log_append_session", {
216
+ "project_path": project_path,
217
+ "session_id": session_id,
218
+ "decisions": session_data["decisions"],
219
+ "accomplishments": session_data["accomplishments"],
220
+ "errors_solved": session_data["errors_solved"],
221
+ "notes": session_data["notes"]
222
+ })
223
+
224
+ if daily_log and daily_log.get("success"):
225
+ results.append(f"Daily log updated: {daily_log.get('file_path', 'unknown')}")
226
+
227
+ # 7. Sync MEMORY.md with high-importance items
228
+ memory_md = await call_memory_skill("sync_memory_md", {
229
+ "project_path": project_path,
230
+ "min_importance": 7,
231
+ "min_pattern_success": 3
232
+ })
233
+
234
+ if memory_md and memory_md.get("success"):
235
+ counts = memory_md.get("counts", {})
236
+ total_synced = sum(counts.values())
237
+ if total_synced > 0:
238
+ results.append(f"MEMORY.md synced: {total_synced} items")
239
+
240
+ # 8. Execute pre-compaction flush
241
+ flush = await call_memory_skill("pre_compaction_flush", {
242
+ "project_path": project_path,
243
+ "session_id": session_id
244
+ })
245
+
246
+ if flush and flush.get("success"):
247
+ results.append(f"Memory flush created: {flush.get('file_path', 'unknown')}")
248
+
249
+ # ============================================================
250
+ # SESSION REVIEW SUMMARY
251
+ # ============================================================
252
+
253
+ # 9. Get session review summary for user verification
254
+ review_data = await get_session_review_summary(session_id)
255
+ if review_data.get("success") and review_data.get("memory_count", 0) > 0:
256
+ review_lines = format_review_output(review_data, session_id)
257
+ results.append("")
258
+ results.append("--- Session Memory Review ---")
259
+ results.extend(review_lines)
260
+
261
+ return results
262
+
263
+
264
+ async def main():
265
+ session_id = os.getenv("SESSION_ID") or f"session-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
266
+ project_path = os.getenv("PROJECT_PATH") or os.getcwd()
267
+
268
+ # Try to read from stdin
269
+ try:
270
+ if not sys.stdin.isatty():
271
+ data = sys.stdin.read()
272
+ if data:
273
+ hook_data = json.loads(data)
274
+ session_id = hook_data.get("session_id", session_id)
275
+ project_path = hook_data.get("project_path", project_path)
276
+ except:
277
+ pass
278
+
279
+ results = await end_session(session_id, project_path)
280
+
281
+ if results:
282
+ print("\n[Memory System] Session ended:")
283
+ for r in results:
284
+ if r.startswith("---") or r.startswith(" "):
285
+ print(r)
286
+ else:
287
+ print(f" - {r}")
288
+ else:
289
+ print("\n[Memory System] Session ended (no data captured)")
290
+
291
+
292
+ if __name__ == "__main__":
293
+ asyncio.run(main())