alma-memory 0.3.0__py3-none-any.whl → 0.5.0__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.
Files changed (77) hide show
  1. alma/__init__.py +99 -29
  2. alma/confidence/__init__.py +47 -0
  3. alma/confidence/engine.py +540 -0
  4. alma/confidence/types.py +351 -0
  5. alma/config/loader.py +3 -2
  6. alma/consolidation/__init__.py +23 -0
  7. alma/consolidation/engine.py +678 -0
  8. alma/consolidation/prompts.py +84 -0
  9. alma/core.py +15 -15
  10. alma/domains/__init__.py +6 -6
  11. alma/domains/factory.py +12 -9
  12. alma/domains/schemas.py +17 -3
  13. alma/domains/types.py +8 -4
  14. alma/events/__init__.py +75 -0
  15. alma/events/emitter.py +284 -0
  16. alma/events/storage_mixin.py +246 -0
  17. alma/events/types.py +126 -0
  18. alma/events/webhook.py +425 -0
  19. alma/exceptions.py +49 -0
  20. alma/extraction/__init__.py +31 -0
  21. alma/extraction/auto_learner.py +264 -0
  22. alma/extraction/extractor.py +420 -0
  23. alma/graph/__init__.py +81 -0
  24. alma/graph/backends/__init__.py +18 -0
  25. alma/graph/backends/memory.py +236 -0
  26. alma/graph/backends/neo4j.py +417 -0
  27. alma/graph/base.py +159 -0
  28. alma/graph/extraction.py +198 -0
  29. alma/graph/store.py +860 -0
  30. alma/harness/__init__.py +4 -4
  31. alma/harness/base.py +18 -9
  32. alma/harness/domains.py +27 -11
  33. alma/initializer/__init__.py +37 -0
  34. alma/initializer/initializer.py +418 -0
  35. alma/initializer/types.py +250 -0
  36. alma/integration/__init__.py +9 -9
  37. alma/integration/claude_agents.py +10 -10
  38. alma/integration/helena.py +32 -22
  39. alma/integration/victor.py +57 -33
  40. alma/learning/__init__.py +27 -27
  41. alma/learning/forgetting.py +198 -148
  42. alma/learning/heuristic_extractor.py +40 -24
  43. alma/learning/protocols.py +62 -14
  44. alma/learning/validation.py +7 -2
  45. alma/mcp/__init__.py +4 -4
  46. alma/mcp/__main__.py +2 -1
  47. alma/mcp/resources.py +17 -16
  48. alma/mcp/server.py +102 -44
  49. alma/mcp/tools.py +174 -37
  50. alma/progress/__init__.py +3 -3
  51. alma/progress/tracker.py +26 -20
  52. alma/progress/types.py +8 -12
  53. alma/py.typed +0 -0
  54. alma/retrieval/__init__.py +11 -11
  55. alma/retrieval/cache.py +20 -21
  56. alma/retrieval/embeddings.py +4 -4
  57. alma/retrieval/engine.py +114 -35
  58. alma/retrieval/scoring.py +73 -63
  59. alma/session/__init__.py +2 -2
  60. alma/session/manager.py +5 -5
  61. alma/session/types.py +5 -4
  62. alma/storage/__init__.py +41 -0
  63. alma/storage/azure_cosmos.py +107 -31
  64. alma/storage/base.py +157 -4
  65. alma/storage/chroma.py +1443 -0
  66. alma/storage/file_based.py +56 -20
  67. alma/storage/pinecone.py +1080 -0
  68. alma/storage/postgresql.py +1452 -0
  69. alma/storage/qdrant.py +1306 -0
  70. alma/storage/sqlite_local.py +376 -31
  71. alma/types.py +62 -14
  72. alma_memory-0.5.0.dist-info/METADATA +905 -0
  73. alma_memory-0.5.0.dist-info/RECORD +76 -0
  74. {alma_memory-0.3.0.dist-info → alma_memory-0.5.0.dist-info}/WHEEL +1 -1
  75. alma_memory-0.3.0.dist-info/METADATA +0 -438
  76. alma_memory-0.3.0.dist-info/RECORD +0 -46
  77. {alma_memory-0.3.0.dist-info → alma_memory-0.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,250 @@
1
+ """
2
+ Initializer Types.
3
+
4
+ Data structures for the Session Initializer pattern.
5
+ """
6
+
7
+ import uuid
8
+ from dataclasses import dataclass, field
9
+ from datetime import datetime, timezone
10
+ from typing import Any, Dict, List, Optional
11
+
12
+
13
+ @dataclass
14
+ class CodebaseOrientation:
15
+ """Codebase orientation information."""
16
+
17
+ # Git state
18
+ current_branch: str
19
+ has_uncommitted_changes: bool
20
+ recent_commits: List[str] # Last N commit messages
21
+
22
+ # File structure
23
+ root_path: str
24
+ key_directories: List[str] # src/, tests/, etc.
25
+ config_files: List[str] # package.json, pyproject.toml, etc.
26
+
27
+ # Summary
28
+ summary: Optional[str] = None
29
+
30
+ def to_prompt(self) -> str:
31
+ """Format orientation for prompt injection."""
32
+ lines = [
33
+ f"Branch: {self.current_branch}",
34
+ f"Uncommitted changes: {'Yes' if self.has_uncommitted_changes else 'No'}",
35
+ ]
36
+
37
+ if self.recent_commits:
38
+ lines.append("Recent commits:")
39
+ for commit in self.recent_commits[:5]:
40
+ lines.append(f" - {commit}")
41
+
42
+ if self.key_directories:
43
+ lines.append(f"Key directories: {', '.join(self.key_directories)}")
44
+
45
+ if self.summary:
46
+ lines.append(f"Summary: {self.summary}")
47
+
48
+ return "\n".join(lines)
49
+
50
+
51
+ @dataclass
52
+ class RulesOfEngagement:
53
+ """Rules governing agent behavior during session."""
54
+
55
+ # What agent CAN do
56
+ scope_rules: List[str] = field(default_factory=list)
57
+
58
+ # What agent CANNOT do
59
+ constraints: List[str] = field(default_factory=list)
60
+
61
+ # Must pass before marking "done"
62
+ quality_gates: List[str] = field(default_factory=list)
63
+
64
+ def to_prompt(self) -> str:
65
+ """Format rules for prompt injection."""
66
+ lines = []
67
+
68
+ if self.scope_rules:
69
+ lines.append("You CAN:")
70
+ for rule in self.scope_rules:
71
+ lines.append(f" - {rule}")
72
+
73
+ if self.constraints:
74
+ lines.append("You CANNOT:")
75
+ for constraint in self.constraints:
76
+ lines.append(f" - {constraint}")
77
+
78
+ if self.quality_gates:
79
+ lines.append("Before marking DONE, verify:")
80
+ for gate in self.quality_gates:
81
+ lines.append(f" - {gate}")
82
+
83
+ return "\n".join(lines)
84
+
85
+
86
+ @dataclass
87
+ class InitializationResult:
88
+ """
89
+ Result of session initialization.
90
+
91
+ Contains everything an agent needs to start work:
92
+ - Expanded goal and work items
93
+ - Codebase orientation
94
+ - Relevant memories
95
+ - Rules of engagement
96
+ - Recommended starting point
97
+ """
98
+
99
+ id: str
100
+ session_id: str
101
+ project_id: str
102
+ agent: str
103
+
104
+ # Original and expanded goal
105
+ original_prompt: str
106
+ goal: str
107
+
108
+ # Work items extracted from goal
109
+ work_items: List[Any] = field(default_factory=list) # WorkItem objects
110
+
111
+ # Codebase orientation
112
+ orientation: Optional[CodebaseOrientation] = None
113
+
114
+ # Recent activity
115
+ recent_activity: List[str] = field(default_factory=list)
116
+
117
+ # Relevant memories (MemorySlice)
118
+ relevant_memories: Optional[Any] = None
119
+
120
+ # Rules of engagement
121
+ rules: RulesOfEngagement = field(default_factory=RulesOfEngagement)
122
+
123
+ # Suggested first action
124
+ recommended_start: Optional[Any] = None # WorkItem
125
+
126
+ # Metadata
127
+ initialized_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
128
+ metadata: Dict[str, Any] = field(default_factory=dict)
129
+
130
+ @classmethod
131
+ def create(
132
+ cls,
133
+ project_id: str,
134
+ agent: str,
135
+ original_prompt: str,
136
+ goal: Optional[str] = None,
137
+ session_id: Optional[str] = None,
138
+ ) -> "InitializationResult":
139
+ """Create a new initialization result."""
140
+ return cls(
141
+ id=str(uuid.uuid4()),
142
+ session_id=session_id or str(uuid.uuid4()),
143
+ project_id=project_id,
144
+ agent=agent,
145
+ original_prompt=original_prompt,
146
+ goal=goal or original_prompt,
147
+ )
148
+
149
+ def to_prompt(self) -> str:
150
+ """
151
+ Format initialization result for prompt injection.
152
+
153
+ This is the "briefing" that prepares the agent.
154
+ """
155
+ sections = []
156
+
157
+ # Header
158
+ sections.append(f"## Session Initialization for {self.agent}")
159
+ sections.append(f"Project: {self.project_id}")
160
+ sections.append(f"Session: {self.session_id}")
161
+ sections.append("")
162
+
163
+ # Goal
164
+ sections.append("### Goal")
165
+ sections.append(self.goal)
166
+ sections.append("")
167
+
168
+ # Work items
169
+ if self.work_items:
170
+ sections.append("### Work Items")
171
+ for i, item in enumerate(self.work_items, 1):
172
+ title = getattr(item, "title", str(item))
173
+ status = getattr(item, "status", "pending")
174
+ sections.append(f"{i}. [{status}] {title}")
175
+ sections.append("")
176
+
177
+ # Orientation
178
+ if self.orientation:
179
+ sections.append("### Codebase Orientation")
180
+ sections.append(self.orientation.to_prompt())
181
+ sections.append("")
182
+
183
+ # Relevant memories
184
+ if self.relevant_memories:
185
+ sections.append("### Relevant Knowledge from Past Runs")
186
+ if hasattr(self.relevant_memories, "to_prompt"):
187
+ sections.append(self.relevant_memories.to_prompt())
188
+ else:
189
+ sections.append(str(self.relevant_memories))
190
+ sections.append("")
191
+
192
+ # Rules of engagement
193
+ if self.rules.scope_rules or self.rules.constraints or self.rules.quality_gates:
194
+ sections.append("### Rules of Engagement")
195
+ sections.append(self.rules.to_prompt())
196
+ sections.append("")
197
+
198
+ # Recommended start
199
+ if self.recommended_start:
200
+ sections.append("### Recommended First Action")
201
+ title = getattr(
202
+ self.recommended_start, "title", str(self.recommended_start)
203
+ )
204
+ sections.append(f"Start with: {title}")
205
+ sections.append("")
206
+
207
+ return "\n".join(sections)
208
+
209
+ def to_dict(self) -> Dict[str, Any]:
210
+ """Serialize to dictionary."""
211
+ return {
212
+ "id": self.id,
213
+ "session_id": self.session_id,
214
+ "project_id": self.project_id,
215
+ "agent": self.agent,
216
+ "original_prompt": self.original_prompt,
217
+ "goal": self.goal,
218
+ "work_items": [
219
+ item.to_dict() if hasattr(item, "to_dict") else str(item)
220
+ for item in self.work_items
221
+ ],
222
+ "orientation": (
223
+ {
224
+ "current_branch": self.orientation.current_branch,
225
+ "has_uncommitted_changes": self.orientation.has_uncommitted_changes,
226
+ "recent_commits": self.orientation.recent_commits,
227
+ "root_path": self.orientation.root_path,
228
+ "key_directories": self.orientation.key_directories,
229
+ "config_files": self.orientation.config_files,
230
+ "summary": self.orientation.summary,
231
+ }
232
+ if self.orientation
233
+ else None
234
+ ),
235
+ "recent_activity": self.recent_activity,
236
+ "rules": {
237
+ "scope_rules": self.rules.scope_rules,
238
+ "constraints": self.rules.constraints,
239
+ "quality_gates": self.rules.quality_gates,
240
+ },
241
+ "recommended_start": (
242
+ self.recommended_start.to_dict()
243
+ if self.recommended_start and hasattr(self.recommended_start, "to_dict")
244
+ else str(self.recommended_start)
245
+ if self.recommended_start
246
+ else None
247
+ ),
248
+ "initialized_at": self.initialized_at.isoformat(),
249
+ "metadata": self.metadata,
250
+ }
@@ -5,32 +5,32 @@ Provides integration hooks for Claude Code agents (Helena, Victor, etc).
5
5
  """
6
6
 
7
7
  from alma.integration.claude_agents import (
8
+ AgentIntegration,
8
9
  AgentType,
10
+ ClaudeAgentHooks,
9
11
  TaskContext,
10
12
  TaskOutcome,
11
- ClaudeAgentHooks,
12
- AgentIntegration,
13
13
  create_integration,
14
14
  )
15
15
  from alma.integration.helena import (
16
+ HELENA_CATEGORIES,
17
+ HELENA_FORBIDDEN,
18
+ HelenaHooks,
16
19
  UITestContext,
17
20
  UITestOutcome,
18
- HelenaHooks,
19
21
  create_helena_hooks,
20
- helena_pre_task,
21
22
  helena_post_task,
22
- HELENA_CATEGORIES,
23
- HELENA_FORBIDDEN,
23
+ helena_pre_task,
24
24
  )
25
25
  from alma.integration.victor import (
26
+ VICTOR_CATEGORIES,
27
+ VICTOR_FORBIDDEN,
26
28
  APITestContext,
27
29
  APITestOutcome,
28
30
  VictorHooks,
29
31
  create_victor_hooks,
30
- victor_pre_task,
31
32
  victor_post_task,
32
- VICTOR_CATEGORIES,
33
- VICTOR_FORBIDDEN,
33
+ victor_pre_task,
34
34
  )
35
35
 
36
36
  __all__ = [
@@ -9,20 +9,21 @@ These hooks enable agents to:
9
9
  """
10
10
 
11
11
  import logging
12
- from typing import Optional, Dict, Any, List
13
12
  from dataclasses import dataclass, field
14
13
  from datetime import datetime, timezone
15
14
  from enum import Enum
15
+ from typing import Any, Dict, List, Optional
16
16
 
17
17
  from alma.core import ALMA
18
+ from alma.harness.base import Context, Harness, RunResult
18
19
  from alma.types import MemorySlice
19
- from alma.harness.base import Harness, Context, RunResult
20
20
 
21
21
  logger = logging.getLogger(__name__)
22
22
 
23
23
 
24
24
  class AgentType(Enum):
25
25
  """Supported Claude Code agent types."""
26
+
26
27
  HELENA = "helena"
27
28
  VICTOR = "victor"
28
29
  CLARA = "clara"
@@ -37,6 +38,7 @@ class TaskContext:
37
38
 
38
39
  Captures all relevant information for memory retrieval and learning.
39
40
  """
41
+
40
42
  task_description: str
41
43
  task_type: str
42
44
  agent_name: str
@@ -67,6 +69,7 @@ class TaskOutcome:
67
69
 
68
70
  Used for learning from task results.
69
71
  """
72
+
70
73
  success: bool
71
74
  strategy_used: str
72
75
  output: Any = None
@@ -272,17 +275,17 @@ class ClaudeAgentHooks:
272
275
  sections.append(f"- [{dk.domain}] {dk.fact}")
273
276
 
274
277
  # User preferences
275
- if memories.user_preferences:
278
+ if memories.preferences:
276
279
  if include_section_headers:
277
280
  sections.append("\n### User Preferences:")
278
- for up in memories.user_preferences:
281
+ for up in memories.preferences:
279
282
  sections.append(f"- [{up.category}] {up.preference}")
280
283
 
281
284
  # Recent outcomes
282
- if memories.recent_outcomes:
285
+ if memories.outcomes:
283
286
  if include_section_headers:
284
287
  sections.append("\n### Recent Outcomes:")
285
- for o in memories.recent_outcomes[:3]: # Limit to 3 most recent
288
+ for o in memories.outcomes[:3]: # Limit to 3 most recent
286
289
  status = "✓" if o.success else "✗"
287
290
  sections.append(
288
291
  f"- {status} {o.task_type}: {o.task_description[:50]}..."
@@ -391,10 +394,7 @@ class AgentIntegration:
391
394
 
392
395
  def get_all_stats(self) -> Dict[str, Dict[str, Any]]:
393
396
  """Get memory statistics for all registered agents."""
394
- return {
395
- name: hooks.get_agent_stats()
396
- for name, hooks in self._agents.items()
397
- }
397
+ return {name: hooks.get_agent_stats() for name, hooks in self._agents.items()}
398
398
 
399
399
 
400
400
  def create_integration(
@@ -14,18 +14,18 @@ This module provides Helena-specific memory categories, prompts, and utilities.
14
14
  """
15
15
 
16
16
  import logging
17
- from typing import Optional, Dict, Any, List
18
17
  from dataclasses import dataclass, field
18
+ from typing import Any, Dict, List, Optional
19
19
 
20
20
  from alma.core import ALMA
21
- from alma.types import MemorySlice
22
21
  from alma.harness.domains import CodingDomain
23
22
  from alma.integration.claude_agents import (
23
+ AgentType,
24
24
  ClaudeAgentHooks,
25
25
  TaskContext,
26
26
  TaskOutcome,
27
- AgentType,
28
27
  )
28
+ from alma.types import MemorySlice
29
29
 
30
30
  logger = logging.getLogger(__name__)
31
31
 
@@ -55,6 +55,7 @@ class UITestContext(TaskContext):
55
55
 
56
56
  Extends TaskContext with UI testing-specific fields.
57
57
  """
58
+
58
59
  component_type: Optional[str] = None
59
60
  page_url: Optional[str] = None
60
61
  viewport: Optional[Dict[str, int]] = None
@@ -89,6 +90,7 @@ class UITestOutcome(TaskOutcome):
89
90
 
90
91
  Extends TaskOutcome with UI testing-specific results.
91
92
  """
93
+
92
94
  selectors_used: List[str] = field(default_factory=list)
93
95
  accessibility_issues: List[Dict[str, Any]] = field(default_factory=list)
94
96
  visual_diffs: List[str] = field(default_factory=list)
@@ -148,11 +150,13 @@ class HelenaHooks(ClaudeAgentHooks):
148
150
  patterns = []
149
151
  for h in memories.heuristics:
150
152
  if "selector" in h.condition.lower():
151
- patterns.append({
152
- "pattern": h.strategy,
153
- "confidence": h.confidence,
154
- "occurrences": h.occurrence_count,
155
- })
153
+ patterns.append(
154
+ {
155
+ "pattern": h.strategy,
156
+ "confidence": h.confidence,
157
+ "occurrences": h.occurrence_count,
158
+ }
159
+ )
156
160
 
157
161
  return patterns
158
162
 
@@ -171,11 +175,13 @@ class HelenaHooks(ClaudeAgentHooks):
171
175
  strategies = []
172
176
  for h in memories.heuristics:
173
177
  if any(kw in h.condition.lower() for kw in ["form", "validation", "input"]):
174
- strategies.append({
175
- "condition": h.condition,
176
- "strategy": h.strategy,
177
- "confidence": h.confidence,
178
- })
178
+ strategies.append(
179
+ {
180
+ "condition": h.condition,
181
+ "strategy": h.strategy,
182
+ "confidence": h.confidence,
183
+ }
184
+ )
179
185
 
180
186
  return strategies
181
187
 
@@ -194,18 +200,22 @@ class HelenaHooks(ClaudeAgentHooks):
194
200
  patterns = []
195
201
  for h in memories.heuristics:
196
202
  if any(kw in h.condition.lower() for kw in ["access", "aria", "keyboard"]):
197
- patterns.append({
198
- "condition": h.condition,
199
- "strategy": h.strategy,
200
- "confidence": h.confidence,
201
- })
203
+ patterns.append(
204
+ {
205
+ "condition": h.condition,
206
+ "strategy": h.strategy,
207
+ "confidence": h.confidence,
208
+ }
209
+ )
202
210
 
203
211
  for dk in memories.domain_knowledge:
204
212
  if dk.domain == "accessibility_testing":
205
- patterns.append({
206
- "fact": dk.fact,
207
- "source": dk.source,
208
- })
213
+ patterns.append(
214
+ {
215
+ "fact": dk.fact,
216
+ "source": dk.source,
217
+ }
218
+ )
209
219
 
210
220
  return patterns
211
221
 
@@ -14,18 +14,18 @@ This module provides Victor-specific memory categories, prompts, and utilities.
14
14
  """
15
15
 
16
16
  import logging
17
- from typing import Optional, Dict, Any, List
18
17
  from dataclasses import dataclass, field
18
+ from typing import Any, Dict, List, Optional
19
19
 
20
20
  from alma.core import ALMA
21
- from alma.types import MemorySlice
22
21
  from alma.harness.domains import CodingDomain
23
22
  from alma.integration.claude_agents import (
23
+ AgentType,
24
24
  ClaudeAgentHooks,
25
25
  TaskContext,
26
26
  TaskOutcome,
27
- AgentType,
28
27
  )
28
+ from alma.types import MemorySlice
29
29
 
30
30
  logger = logging.getLogger(__name__)
31
31
 
@@ -55,6 +55,7 @@ class APITestContext(TaskContext):
55
55
 
56
56
  Extends TaskContext with API/backend testing-specific fields.
57
57
  """
58
+
58
59
  endpoint: Optional[str] = None
59
60
  method: str = "GET"
60
61
  expected_status: Optional[int] = None
@@ -91,6 +92,7 @@ class APITestOutcome(TaskOutcome):
91
92
 
92
93
  Extends TaskOutcome with API/backend testing-specific results.
93
94
  """
95
+
94
96
  response_status: Optional[int] = None
95
97
  response_time_ms: Optional[int] = None
96
98
  response_body: Optional[Dict[str, Any]] = None
@@ -153,12 +155,14 @@ class VictorHooks(ClaudeAgentHooks):
153
155
  patterns = []
154
156
  for h in memories.heuristics:
155
157
  if "api" in h.condition.lower() or "endpoint" in h.condition.lower():
156
- patterns.append({
157
- "pattern": h.strategy,
158
- "condition": h.condition,
159
- "confidence": h.confidence,
160
- "occurrences": h.occurrence_count,
161
- })
158
+ patterns.append(
159
+ {
160
+ "pattern": h.strategy,
161
+ "condition": h.condition,
162
+ "confidence": h.confidence,
163
+ "occurrences": h.occurrence_count,
164
+ }
165
+ )
162
166
 
163
167
  return patterns
164
168
 
@@ -176,12 +180,16 @@ class VictorHooks(ClaudeAgentHooks):
176
180
 
177
181
  patterns = []
178
182
  for h in memories.heuristics:
179
- if any(kw in h.condition.lower() for kw in ["error", "exception", "validation"]):
180
- patterns.append({
181
- "condition": h.condition,
182
- "strategy": h.strategy,
183
- "confidence": h.confidence,
184
- })
183
+ if any(
184
+ kw in h.condition.lower() for kw in ["error", "exception", "validation"]
185
+ ):
186
+ patterns.append(
187
+ {
188
+ "condition": h.condition,
189
+ "strategy": h.strategy,
190
+ "confidence": h.confidence,
191
+ }
192
+ )
185
193
 
186
194
  return patterns
187
195
 
@@ -199,19 +207,26 @@ class VictorHooks(ClaudeAgentHooks):
199
207
 
200
208
  strategies = []
201
209
  for h in memories.heuristics:
202
- if any(kw in h.condition.lower() for kw in ["performance", "cache", "query", "slow"]):
203
- strategies.append({
204
- "condition": h.condition,
205
- "strategy": h.strategy,
206
- "confidence": h.confidence,
207
- })
210
+ if any(
211
+ kw in h.condition.lower()
212
+ for kw in ["performance", "cache", "query", "slow"]
213
+ ):
214
+ strategies.append(
215
+ {
216
+ "condition": h.condition,
217
+ "strategy": h.strategy,
218
+ "confidence": h.confidence,
219
+ }
220
+ )
208
221
 
209
222
  for dk in memories.domain_knowledge:
210
223
  if dk.domain in ["performance_optimization", "caching_strategies"]:
211
- strategies.append({
212
- "fact": dk.fact,
213
- "source": dk.source,
214
- })
224
+ strategies.append(
225
+ {
226
+ "fact": dk.fact,
227
+ "source": dk.source,
228
+ }
229
+ )
215
230
 
216
231
  return strategies
217
232
 
@@ -229,12 +244,17 @@ class VictorHooks(ClaudeAgentHooks):
229
244
 
230
245
  patterns = []
231
246
  for h in memories.heuristics:
232
- if any(kw in h.condition.lower() for kw in ["auth", "token", "permission", "jwt"]):
233
- patterns.append({
234
- "condition": h.condition,
235
- "strategy": h.strategy,
236
- "confidence": h.confidence,
237
- })
247
+ if any(
248
+ kw in h.condition.lower()
249
+ for kw in ["auth", "token", "permission", "jwt"]
250
+ ):
251
+ patterns.append(
252
+ {
253
+ "condition": h.condition,
254
+ "strategy": h.strategy,
255
+ "confidence": h.confidence,
256
+ }
257
+ )
238
258
 
239
259
  return patterns
240
260
 
@@ -318,11 +338,15 @@ class VictorHooks(ClaudeAgentHooks):
318
338
  sections.append(f"- **Task Type**: {test_context.task_type}")
319
339
 
320
340
  if test_context.endpoint:
321
- sections.append(f"- **Endpoint**: {test_context.method} {test_context.endpoint}")
341
+ sections.append(
342
+ f"- **Endpoint**: {test_context.method} {test_context.endpoint}"
343
+ )
322
344
  if test_context.expected_status:
323
345
  sections.append(f"- **Expected Status**: {test_context.expected_status}")
324
346
  if test_context.request_body:
325
- sections.append(f"- **Request Body**: {len(test_context.request_body)} fields")
347
+ sections.append(
348
+ f"- **Request Body**: {len(test_context.request_body)} fields"
349
+ )
326
350
 
327
351
  if test_context.is_auth_test:
328
352
  sections.append("- **Focus**: Authentication/Authorization")