claude-mpm 4.7.5__py3-none-any.whl → 4.7.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.
claude_mpm/VERSION CHANGED
@@ -1 +1 @@
1
- 4.7.5
1
+ 4.7.7
@@ -374,7 +374,9 @@ class ConfigureCommand(BaseCommand):
374
374
  self.console.print(menu_panel)
375
375
  self.console.print()
376
376
 
377
- return Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="q")
377
+ choice = Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="q")
378
+ # Strip whitespace to handle leading/trailing spaces
379
+ return choice.strip().lower()
378
380
 
379
381
  def _manage_agents(self) -> None:
380
382
  """Agent management interface."""
@@ -388,12 +390,38 @@ class ConfigureCommand(BaseCommand):
388
390
 
389
391
  # Show agent menu
390
392
  self.console.print("\n[bold]Agent Management Options:[/bold]")
391
- self.console.print(" [cyan][e][/cyan] Enable an agent")
392
- self.console.print(" [cyan][d][/cyan] Disable an agent")
393
- self.console.print(" [cyan][c][/cyan] Customize agent template")
394
- self.console.print(" [cyan][v][/cyan] View agent details")
395
- self.console.print(" [cyan][r][/cyan] Reset agent to defaults")
396
- self.console.print(" [cyan][b][/cyan] Back to main menu")
393
+
394
+ # Use Text objects to properly display shortcuts with styling
395
+ text_e = Text(" ")
396
+ text_e.append("[e]", style="cyan bold")
397
+ text_e.append(" Enable an agent")
398
+ self.console.print(text_e)
399
+
400
+ text_d = Text(" ")
401
+ text_d.append("[d]", style="cyan bold")
402
+ text_d.append(" Disable an agent")
403
+ self.console.print(text_d)
404
+
405
+ text_c = Text(" ")
406
+ text_c.append("[c]", style="cyan bold")
407
+ text_c.append(" Customize agent template")
408
+ self.console.print(text_c)
409
+
410
+ text_v = Text(" ")
411
+ text_v.append("[v]", style="cyan bold")
412
+ text_v.append(" View agent details")
413
+ self.console.print(text_v)
414
+
415
+ text_r = Text(" ")
416
+ text_r.append("[r]", style="cyan bold")
417
+ text_r.append(" Reset agent to defaults")
418
+ self.console.print(text_r)
419
+
420
+ text_b = Text(" ")
421
+ text_b.append("[b]", style="cyan bold")
422
+ text_b.append(" Back to main menu")
423
+ self.console.print(text_b)
424
+
397
425
  self.console.print()
398
426
 
399
427
  choice = Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="b")
@@ -425,7 +453,7 @@ class ConfigureCommand(BaseCommand):
425
453
  table.add_column("ID", style="dim", width=3)
426
454
  table.add_column("Name", style="cyan", width=22)
427
455
  table.add_column("Status", width=12)
428
- table.add_column("Description", style="white", width=45)
456
+ table.add_column("Description", style="bold cyan", width=45)
429
457
  table.add_column("Model/Tools", style="dim", width=20)
430
458
 
431
459
  for idx, agent in enumerate(agents, 1):
@@ -456,12 +484,11 @@ class ConfigureCommand(BaseCommand):
456
484
  except Exception:
457
485
  tools_display = "Default"
458
486
 
459
- # Truncate description for table display
460
- desc_display = (
461
- agent.description[:42] + "..."
462
- if len(agent.description) > 42
463
- else agent.description
464
- )
487
+ # Truncate description for table display with bright styling
488
+ if len(agent.description) > 42:
489
+ desc_display = f"[cyan]{agent.description[:42]}[/cyan][dim]...[/dim]"
490
+ else:
491
+ desc_display = f"[cyan]{agent.description}[/cyan]"
465
492
 
466
493
  table.add_row(str(idx), agent.name, status, desc_display, tools_display)
467
494
 
@@ -612,14 +639,41 @@ class ConfigureCommand(BaseCommand):
612
639
  # Editing options
613
640
  self.console.print("[bold]Editing Options:[/bold]")
614
641
  if not is_system:
615
- self.console.print(" [cyan][1][/cyan] Edit in external editor")
616
- self.console.print(" [cyan][2][/cyan] Add/modify a field")
617
- self.console.print(" [cyan][3][/cyan] Remove a field")
618
- self.console.print(" [cyan][4][/cyan] Reset to defaults")
642
+ text_1 = Text(" ")
643
+ text_1.append("[1]", style="cyan bold")
644
+ text_1.append(" Edit in external editor")
645
+ self.console.print(text_1)
646
+
647
+ text_2 = Text(" ")
648
+ text_2.append("[2]", style="cyan bold")
649
+ text_2.append(" Add/modify a field")
650
+ self.console.print(text_2)
651
+
652
+ text_3 = Text(" ")
653
+ text_3.append("[3]", style="cyan bold")
654
+ text_3.append(" Remove a field")
655
+ self.console.print(text_3)
656
+
657
+ text_4 = Text(" ")
658
+ text_4.append("[4]", style="cyan bold")
659
+ text_4.append(" Reset to defaults")
660
+ self.console.print(text_4)
619
661
  else:
620
- self.console.print(" [cyan][1][/cyan] Create customized copy")
621
- self.console.print(" [cyan][2][/cyan] View full template")
622
- self.console.print(" [cyan][b][/cyan] Back")
662
+ text_1 = Text(" ")
663
+ text_1.append("[1]", style="cyan bold")
664
+ text_1.append(" Create customized copy")
665
+ self.console.print(text_1)
666
+
667
+ text_2 = Text(" ")
668
+ text_2.append("[2]", style="cyan bold")
669
+ text_2.append(" View full template")
670
+ self.console.print(text_2)
671
+
672
+ text_b = Text(" ")
673
+ text_b.append("[b]", style="cyan bold")
674
+ text_b.append(" Back")
675
+ self.console.print(text_b)
676
+
623
677
  self.console.print()
624
678
 
625
679
  choice = Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="b")
@@ -821,6 +875,61 @@ class ConfigureCommand(BaseCommand):
821
875
  with self.console.pager():
822
876
  self.console.print(syntax)
823
877
 
878
+ def _reset_agent_defaults(self, agents: List[AgentConfig]) -> None:
879
+ """Reset an agent to default enabled state and remove custom template.
880
+
881
+ This method:
882
+ - Prompts for agent ID
883
+ - Resets agent to enabled state
884
+ - Removes any custom template overrides
885
+ - Shows success/error messages
886
+ """
887
+ agent_id = Prompt.ask("Enter agent ID to reset to defaults")
888
+
889
+ try:
890
+ idx = int(agent_id) - 1
891
+ if 0 <= idx < len(agents):
892
+ agent = agents[idx]
893
+
894
+ # Confirm the reset action
895
+ if not Confirm.ask(
896
+ f"[yellow]Reset '{agent.name}' to defaults? This will:[/yellow]\n"
897
+ " - Enable the agent\n"
898
+ " - Remove custom template (if any)\n"
899
+ "[yellow]Continue?[/yellow]"
900
+ ):
901
+ self.console.print("[yellow]Reset cancelled.[/yellow]")
902
+ Prompt.ask("Press Enter to continue")
903
+ return
904
+
905
+ # Enable the agent
906
+ self.agent_manager.set_agent_enabled(agent.name, True)
907
+
908
+ # Remove custom template if exists
909
+ template_path = self._get_agent_template_path(agent.name)
910
+ if template_path.exists() and not str(template_path).startswith(
911
+ str(self.agent_manager.templates_dir)
912
+ ):
913
+ # This is a custom template, remove it
914
+ template_path.unlink(missing_ok=True)
915
+ self.console.print(
916
+ f"[green]✓ Removed custom template for '{agent.name}'[/green]"
917
+ )
918
+
919
+ self.console.print(
920
+ f"[green]✓ Agent '{agent.name}' reset to defaults![/green]"
921
+ )
922
+ self.console.print(
923
+ "[dim]Agent is now enabled with system template.[/dim]"
924
+ )
925
+ else:
926
+ self.console.print("[red]Invalid agent ID.[/red]")
927
+
928
+ except ValueError:
929
+ self.console.print("[red]Invalid input. Please enter a number.[/red]")
930
+
931
+ Prompt.ask("Press Enter to continue")
932
+
824
933
  def _view_agent_details(self, agents: List[AgentConfig]) -> None:
825
934
  """View detailed information about an agent."""
826
935
  agent_id = Prompt.ask("Enter agent ID to view")
@@ -917,11 +1026,32 @@ class ConfigureCommand(BaseCommand):
917
1026
 
918
1027
  # Show behavior menu
919
1028
  self.console.print("\n[bold]Options:[/bold]")
920
- self.console.print(" [cyan][1][/cyan] Edit identity configuration")
921
- self.console.print(" [cyan][2][/cyan] Edit workflow configuration")
922
- self.console.print(" [cyan][3][/cyan] Import behavior file")
923
- self.console.print(" [cyan][4][/cyan] Export behavior file")
924
- self.console.print(" [cyan][b][/cyan] Back to main menu")
1029
+
1030
+ text_1 = Text(" ")
1031
+ text_1.append("[1]", style="cyan bold")
1032
+ text_1.append(" Edit identity configuration")
1033
+ self.console.print(text_1)
1034
+
1035
+ text_2 = Text(" ")
1036
+ text_2.append("[2]", style="cyan bold")
1037
+ text_2.append(" Edit workflow configuration")
1038
+ self.console.print(text_2)
1039
+
1040
+ text_3 = Text(" ")
1041
+ text_3.append("[3]", style="cyan bold")
1042
+ text_3.append(" Import behavior file")
1043
+ self.console.print(text_3)
1044
+
1045
+ text_4 = Text(" ")
1046
+ text_4.append("[4]", style="cyan bold")
1047
+ text_4.append(" Export behavior file")
1048
+ self.console.print(text_4)
1049
+
1050
+ text_b = Text(" ")
1051
+ text_b.append("[b]", style="cyan bold")
1052
+ text_b.append(" Back to main menu")
1053
+ self.console.print(text_b)
1054
+
925
1055
  self.console.print()
926
1056
 
927
1057
  choice = Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="b")
@@ -1208,9 +1338,22 @@ class ConfigureCommand(BaseCommand):
1208
1338
  self.console.print(table)
1209
1339
  self.console.print("\n[bold]Commands:[/bold]")
1210
1340
  self.console.print(" Enter service IDs to toggle (e.g., '1,3' or '1-4')")
1211
- self.console.print(" [cyan][a][/cyan] Enable all")
1212
- self.console.print(" [cyan][n][/cyan] Disable all")
1213
- self.console.print(" [cyan][b][/cyan] Back to previous menu")
1341
+
1342
+ text_a = Text(" ")
1343
+ text_a.append("[a]", style="cyan bold")
1344
+ text_a.append(" Enable all")
1345
+ self.console.print(text_a)
1346
+
1347
+ text_n = Text(" ")
1348
+ text_n.append("[n]", style="cyan bold")
1349
+ text_n.append(" Disable all")
1350
+ self.console.print(text_n)
1351
+
1352
+ text_b = Text(" ")
1353
+ text_b.append("[b]", style="cyan bold")
1354
+ text_b.append(" Back to previous menu")
1355
+ self.console.print(text_b)
1356
+
1214
1357
  self.console.print()
1215
1358
 
1216
1359
  choice = Prompt.ask("[bold cyan]Toggle services[/bold cyan]", default="b")
@@ -1275,9 +1418,22 @@ class ConfigureCommand(BaseCommand):
1275
1418
  self.console.print(table)
1276
1419
  self.console.print("\n[bold]Commands:[/bold]")
1277
1420
  self.console.print(" Enter service IDs to toggle (e.g., '1,3' or '1-4')")
1278
- self.console.print(" [cyan][a][/cyan] Enable all")
1279
- self.console.print(" [cyan][n][/cyan] Disable all")
1280
- self.console.print(" [cyan][b][/cyan] Back to previous menu")
1421
+
1422
+ text_a = Text(" ")
1423
+ text_a.append("[a]", style="cyan bold")
1424
+ text_a.append(" Enable all")
1425
+ self.console.print(text_a)
1426
+
1427
+ text_n = Text(" ")
1428
+ text_n.append("[n]", style="cyan bold")
1429
+ text_n.append(" Disable all")
1430
+ self.console.print(text_n)
1431
+
1432
+ text_b = Text(" ")
1433
+ text_b.append("[b]", style="cyan bold")
1434
+ text_b.append(" Back to previous menu")
1435
+ self.console.print(text_b)
1436
+
1281
1437
  self.console.print()
1282
1438
 
1283
1439
  choice = Prompt.ask("[bold cyan]Toggle services[/bold cyan]", default="b")
@@ -1331,7 +1487,7 @@ class ConfigureCommand(BaseCommand):
1331
1487
  table.add_column("ID", style="dim", width=5)
1332
1488
  table.add_column("Agent", style="cyan", width=25)
1333
1489
  table.add_column("Status", width=15)
1334
- table.add_column("Description", style="white", width=45)
1490
+ table.add_column("Description", style="bold cyan", width=45)
1335
1491
 
1336
1492
  for idx, agent in enumerate(agents, 1):
1337
1493
  # Agent is ENABLED if NOT in disabled list
@@ -1341,11 +1497,13 @@ class ConfigureCommand(BaseCommand):
1341
1497
  if is_enabled
1342
1498
  else "[red]✗ Disabled[/red]"
1343
1499
  )
1344
- desc_display = (
1345
- agent.description[:42] + "..."
1346
- if len(agent.description) > 42
1347
- else agent.description
1348
- )
1500
+ # Format description with bright styling
1501
+ if len(agent.description) > 42:
1502
+ desc_display = (
1503
+ f"[cyan]{agent.description[:42]}[/cyan][dim]...[/dim]"
1504
+ )
1505
+ else:
1506
+ desc_display = f"[cyan]{agent.description}[/cyan]"
1349
1507
  table.add_row(str(idx), agent.name, status, desc_display)
1350
1508
 
1351
1509
  self.console.print(table)
@@ -1,13 +1,19 @@
1
1
  """Hook system for claude-mpm."""
2
2
 
3
3
  from .base_hook import BaseHook, HookContext, HookResult, HookType
4
+ from .kuzu_enrichment_hook import KuzuEnrichmentHook, get_kuzu_enrichment_hook
4
5
  from .kuzu_memory_hook import KuzuMemoryHook, get_kuzu_memory_hook
6
+ from .kuzu_response_hook import KuzuResponseHook, get_kuzu_response_hook
5
7
 
6
8
  __all__ = [
7
9
  "BaseHook",
8
10
  "HookContext",
9
11
  "HookResult",
10
12
  "HookType",
13
+ "KuzuEnrichmentHook",
11
14
  "KuzuMemoryHook",
15
+ "KuzuResponseHook",
16
+ "get_kuzu_enrichment_hook",
12
17
  "get_kuzu_memory_hook",
18
+ "get_kuzu_response_hook",
13
19
  ]
@@ -0,0 +1,263 @@
1
+ """
2
+ Kuzu-Memory Pre-Delegation Enrichment Hook
3
+ ==========================================
4
+
5
+ Enriches agent delegation context with relevant memories from kuzu-memory
6
+ before the agent receives the task. This is the READ side of bidirectional
7
+ enrichment.
8
+
9
+ WHY: Agents need access to relevant historical knowledge when performing tasks.
10
+ This hook retrieves memories from kuzu-memory and injects them into the
11
+ delegation context.
12
+
13
+ DESIGN DECISIONS:
14
+ - Priority 10 to run early, before other context modifications
15
+ - Reuses KuzuMemoryHook's retrieval methods for consistency
16
+ - Injects memories as a dedicated section in agent context
17
+ - Falls back gracefully if kuzu-memory is not available
18
+ """
19
+
20
+ import logging
21
+ from typing import Any, Dict, Optional
22
+
23
+ from claude_mpm.hooks.base_hook import HookContext, HookResult, PreDelegationHook
24
+ from claude_mpm.hooks.kuzu_memory_hook import get_kuzu_memory_hook
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class KuzuEnrichmentHook(PreDelegationHook):
30
+ """
31
+ Hook that enriches agent delegation context with kuzu-memory.
32
+
33
+ This hook:
34
+ 1. Extracts the task/prompt from delegation context
35
+ 2. Retrieves relevant memories from kuzu-memory
36
+ 3. Injects memories into agent context
37
+ 4. Formats memories for optimal agent understanding
38
+ """
39
+
40
+ def __init__(self):
41
+ """Initialize the kuzu-memory enrichment hook."""
42
+ super().__init__(name="kuzu_memory_enrichment", priority=10)
43
+
44
+ # Reuse the kuzu-memory hook instance for retrieval
45
+ self.kuzu_hook = get_kuzu_memory_hook()
46
+ self.enabled = self.kuzu_hook.enabled
47
+
48
+ if not self.enabled:
49
+ logger.info(
50
+ "Kuzu-memory enrichment hook disabled (kuzu-memory not available)"
51
+ )
52
+ else:
53
+ logger.info("Kuzu-memory enrichment hook enabled")
54
+
55
+ def validate(self, context: HookContext) -> bool:
56
+ """
57
+ Validate if hook should process this context.
58
+
59
+ Args:
60
+ context: Hook context to validate
61
+
62
+ Returns:
63
+ True if hook should execute
64
+ """
65
+ if not self.enabled:
66
+ return False
67
+
68
+ # Check base validation (enabled, correct hook type, has agent)
69
+ if not super().validate(context):
70
+ return False
71
+
72
+ # Must have agent and context data
73
+ if not context.data.get("agent"):
74
+ return False
75
+
76
+ return True
77
+
78
+ def execute(self, context: HookContext) -> HookResult:
79
+ """
80
+ Enrich delegation context with relevant memories.
81
+
82
+ Args:
83
+ context: Hook context containing delegation data
84
+
85
+ Returns:
86
+ HookResult with enriched context
87
+ """
88
+ if not self.enabled:
89
+ return HookResult(success=True, data=context.data, modified=False)
90
+
91
+ try:
92
+ # Extract query for memory retrieval
93
+ query = self._extract_query_from_context(context.data)
94
+
95
+ if not query:
96
+ logger.debug("No query extracted from context for memory retrieval")
97
+ return HookResult(success=True, data=context.data, modified=False)
98
+
99
+ # Retrieve relevant memories
100
+ memories = self.kuzu_hook._retrieve_memories(query)
101
+
102
+ if not memories:
103
+ logger.debug("No relevant memories found")
104
+ return HookResult(success=True, data=context.data, modified=False)
105
+
106
+ # Enrich context with memories
107
+ enriched_data = self._enrich_delegation_context(
108
+ context.data, memories, context.data.get("agent", "Agent")
109
+ )
110
+
111
+ logger.info(
112
+ f"Enriched delegation context with {len(memories)} memories for {context.data.get('agent')}"
113
+ )
114
+
115
+ return HookResult(
116
+ success=True,
117
+ data=enriched_data,
118
+ modified=True,
119
+ metadata={
120
+ "memories_added": len(memories),
121
+ "memory_source": "kuzu",
122
+ "agent": context.data.get("agent"),
123
+ },
124
+ )
125
+
126
+ except Exception as e:
127
+ logger.error(f"Error in kuzu enrichment hook: {e}")
128
+ # Don't fail the delegation if memory enrichment fails
129
+ return HookResult(
130
+ success=True,
131
+ data=context.data,
132
+ modified=False,
133
+ error=f"Memory enrichment failed: {e}",
134
+ )
135
+
136
+ def _extract_query_from_context(self, data: Dict[str, Any]) -> Optional[str]:
137
+ """
138
+ Extract query text for memory retrieval.
139
+
140
+ Args:
141
+ data: Delegation context data
142
+
143
+ Returns:
144
+ Query string or None
145
+ """
146
+ # Try various context fields
147
+ delegation_context = data.get("context", {})
148
+
149
+ # Handle string context
150
+ if isinstance(delegation_context, str):
151
+ return delegation_context
152
+
153
+ # Handle dict context
154
+ if isinstance(delegation_context, dict):
155
+ # Try common fields
156
+ for field in ["prompt", "task", "query", "user_request", "description"]:
157
+ if field in delegation_context:
158
+ value = delegation_context[field]
159
+ if isinstance(value, str):
160
+ return value
161
+
162
+ # If no specific field, join all string values
163
+ text_parts = [
164
+ str(v) for v in delegation_context.values() if isinstance(v, str)
165
+ ]
166
+ if text_parts:
167
+ return " ".join(text_parts)
168
+
169
+ # Fallback: try to get task or instruction directly
170
+ if "task" in data and isinstance(data["task"], str):
171
+ return data["task"]
172
+
173
+ if "instruction" in data and isinstance(data["instruction"], str):
174
+ return data["instruction"]
175
+
176
+ return None
177
+
178
+ def _enrich_delegation_context(
179
+ self, original_data: Dict[str, Any], memories: list, agent_name: str
180
+ ) -> Dict[str, Any]:
181
+ """
182
+ Enrich delegation context with memories.
183
+
184
+ Args:
185
+ original_data: Original delegation data
186
+ memories: Retrieved memories
187
+ agent_name: Name of the agent
188
+
189
+ Returns:
190
+ Enriched delegation data
191
+ """
192
+ # Format memories
193
+ memory_section = self._format_memory_section(memories, agent_name)
194
+
195
+ # Create enriched data
196
+ enriched_data = original_data.copy()
197
+
198
+ # Get existing context
199
+ delegation_context = enriched_data.get("context", {})
200
+ if isinstance(delegation_context, str):
201
+ delegation_context = {"prompt": delegation_context}
202
+
203
+ # Add memory section
204
+ if isinstance(delegation_context, dict):
205
+ # Prepend memory section to context
206
+ delegation_context["kuzu_memories"] = memory_section
207
+
208
+ # If there's a main prompt/task, prepend memory note
209
+ for field in ["prompt", "task", "instruction"]:
210
+ if field in delegation_context and isinstance(
211
+ delegation_context[field], str
212
+ ):
213
+ delegation_context[field] = (
214
+ f"{memory_section}\n\n{delegation_context[field]}"
215
+ )
216
+ break
217
+ else:
218
+ # If context is not dict, create new dict with memory
219
+ delegation_context = {
220
+ "kuzu_memories": memory_section,
221
+ "original_context": delegation_context,
222
+ }
223
+
224
+ enriched_data["context"] = delegation_context
225
+ enriched_data["_kuzu_enriched"] = True
226
+
227
+ return enriched_data
228
+
229
+ def _format_memory_section(self, memories: list, agent_name: str) -> str:
230
+ """
231
+ Format memories into a readable section.
232
+
233
+ Args:
234
+ memories: List of memory dictionaries
235
+ agent_name: Name of the agent
236
+
237
+ Returns:
238
+ Formatted memory section
239
+ """
240
+ memory_text = self.kuzu_hook._format_memories(memories)
241
+
242
+ return f"""
243
+ === RELEVANT KNOWLEDGE FROM KUZU MEMORY ===
244
+ {agent_name}, you have access to these relevant memories from the knowledge graph:
245
+
246
+ {memory_text}
247
+
248
+ INSTRUCTIONS: Review these memories before proceeding. Apply learned patterns and avoid known mistakes.
249
+ Use this knowledge to provide more informed and contextual responses.
250
+ ===========================================
251
+ """
252
+
253
+
254
+ # Create a singleton instance
255
+ _kuzu_enrichment_hook = None
256
+
257
+
258
+ def get_kuzu_enrichment_hook() -> KuzuEnrichmentHook:
259
+ """Get the singleton kuzu-memory enrichment hook instance."""
260
+ global _kuzu_enrichment_hook
261
+ if _kuzu_enrichment_hook is None:
262
+ _kuzu_enrichment_hook = KuzuEnrichmentHook()
263
+ return _kuzu_enrichment_hook
@@ -157,9 +157,13 @@ class KuzuMemoryHook(SubmitHook):
157
157
  List of relevant memory dictionaries
158
158
  """
159
159
  try:
160
- # Use kuzu-memory recall command
160
+ # Type narrowing: ensure kuzu_memory_cmd is not None before using
161
+ if self.kuzu_memory_cmd is None:
162
+ return []
163
+
164
+ # Use kuzu-memory recall command (v1.2.7+ syntax)
161
165
  result = subprocess.run(
162
- [self.kuzu_memory_cmd, "recall", query, "--format", "json"],
166
+ [self.kuzu_memory_cmd, "memory", "recall", query, "--format", "json"],
163
167
  capture_output=True,
164
168
  text=True,
165
169
  timeout=5,
@@ -168,10 +172,21 @@ class KuzuMemoryHook(SubmitHook):
168
172
  )
169
173
 
170
174
  if result.returncode == 0 and result.stdout:
171
- memories = json.loads(result.stdout)
172
- return memories if isinstance(memories, list) else []
173
-
174
- except (subprocess.TimeoutExpired, json.JSONDecodeError, Exception) as e:
175
+ try:
176
+ # Parse JSON with strict=False to handle control characters
177
+ data = json.loads(result.stdout, strict=False)
178
+ # v1.2.7 returns dict with 'memories' key, not array
179
+ if isinstance(data, dict):
180
+ memories = data.get("memories", [])
181
+ else:
182
+ memories = data if isinstance(data, list) else []
183
+ return memories
184
+ except json.JSONDecodeError as e:
185
+ logger.warning(f"Failed to parse kuzu-memory JSON output: {e}")
186
+ logger.debug(f"Raw output: {result.stdout[:200]}")
187
+ return [] # Graceful fallback
188
+
189
+ except (subprocess.TimeoutExpired, Exception) as e:
175
190
  logger.debug(f"Memory retrieval failed: {e}")
176
191
 
177
192
  return []
@@ -255,12 +270,12 @@ Note: Use the memories above to provide more informed and contextual responses.
255
270
  Returns:
256
271
  True if storage was successful
257
272
  """
258
- if not self.enabled:
273
+ if not self.enabled or self.kuzu_memory_cmd is None:
259
274
  return False
260
275
 
261
276
  try:
262
- # Use kuzu-memory remember command (synchronous)
263
- cmd = [self.kuzu_memory_cmd, "remember", content]
277
+ # Use kuzu-memory store command (v1.2.7+ syntax)
278
+ cmd = [self.kuzu_memory_cmd, "memory", "store", content]
264
279
 
265
280
  # Execute store command in project directory
266
281
  result = subprocess.run(
@@ -273,8 +288,10 @@ Note: Use the memories above to provide more informed and contextual responses.
273
288
  )
274
289
 
275
290
  if result.returncode == 0:
276
- logger.debug(f"Stored memory: {content[:50]}...")
291
+ logger.debug(f"Stored memory in kuzu: {content[:50]}...")
277
292
  return True
293
+ logger.warning(f"Failed to store memory in kuzu: {result.stderr}")
294
+ return False
278
295
 
279
296
  except Exception as e:
280
297
  logger.error(f"Failed to store memory: {e}")