alma-memory 0.5.1__py3-none-any.whl → 0.7.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 (111) hide show
  1. alma/__init__.py +296 -226
  2. alma/compression/__init__.py +33 -0
  3. alma/compression/pipeline.py +980 -0
  4. alma/confidence/__init__.py +47 -47
  5. alma/confidence/engine.py +540 -540
  6. alma/confidence/types.py +351 -351
  7. alma/config/loader.py +157 -157
  8. alma/consolidation/__init__.py +23 -23
  9. alma/consolidation/engine.py +678 -678
  10. alma/consolidation/prompts.py +84 -84
  11. alma/core.py +1189 -430
  12. alma/domains/__init__.py +30 -30
  13. alma/domains/factory.py +359 -359
  14. alma/domains/schemas.py +448 -448
  15. alma/domains/types.py +272 -272
  16. alma/events/__init__.py +75 -75
  17. alma/events/emitter.py +285 -284
  18. alma/events/storage_mixin.py +246 -246
  19. alma/events/types.py +126 -126
  20. alma/events/webhook.py +425 -425
  21. alma/exceptions.py +49 -49
  22. alma/extraction/__init__.py +31 -31
  23. alma/extraction/auto_learner.py +265 -265
  24. alma/extraction/extractor.py +420 -420
  25. alma/graph/__init__.py +106 -106
  26. alma/graph/backends/__init__.py +32 -32
  27. alma/graph/backends/kuzu.py +624 -624
  28. alma/graph/backends/memgraph.py +432 -432
  29. alma/graph/backends/memory.py +236 -236
  30. alma/graph/backends/neo4j.py +417 -417
  31. alma/graph/base.py +159 -159
  32. alma/graph/extraction.py +198 -198
  33. alma/graph/store.py +860 -860
  34. alma/harness/__init__.py +35 -35
  35. alma/harness/base.py +386 -386
  36. alma/harness/domains.py +705 -705
  37. alma/initializer/__init__.py +37 -37
  38. alma/initializer/initializer.py +418 -418
  39. alma/initializer/types.py +250 -250
  40. alma/integration/__init__.py +62 -62
  41. alma/integration/claude_agents.py +444 -444
  42. alma/integration/helena.py +423 -423
  43. alma/integration/victor.py +471 -471
  44. alma/learning/__init__.py +101 -86
  45. alma/learning/decay.py +878 -0
  46. alma/learning/forgetting.py +1446 -1446
  47. alma/learning/heuristic_extractor.py +390 -390
  48. alma/learning/protocols.py +374 -374
  49. alma/learning/validation.py +346 -346
  50. alma/mcp/__init__.py +123 -45
  51. alma/mcp/__main__.py +156 -156
  52. alma/mcp/resources.py +122 -122
  53. alma/mcp/server.py +955 -591
  54. alma/mcp/tools.py +3254 -509
  55. alma/observability/__init__.py +91 -84
  56. alma/observability/config.py +302 -302
  57. alma/observability/guidelines.py +170 -0
  58. alma/observability/logging.py +424 -424
  59. alma/observability/metrics.py +583 -583
  60. alma/observability/tracing.py +440 -440
  61. alma/progress/__init__.py +21 -21
  62. alma/progress/tracker.py +607 -607
  63. alma/progress/types.py +250 -250
  64. alma/retrieval/__init__.py +134 -53
  65. alma/retrieval/budget.py +525 -0
  66. alma/retrieval/cache.py +1304 -1061
  67. alma/retrieval/embeddings.py +202 -202
  68. alma/retrieval/engine.py +850 -427
  69. alma/retrieval/modes.py +365 -0
  70. alma/retrieval/progressive.py +560 -0
  71. alma/retrieval/scoring.py +344 -344
  72. alma/retrieval/trust_scoring.py +637 -0
  73. alma/retrieval/verification.py +797 -0
  74. alma/session/__init__.py +19 -19
  75. alma/session/manager.py +442 -399
  76. alma/session/types.py +288 -288
  77. alma/storage/__init__.py +101 -90
  78. alma/storage/archive.py +233 -0
  79. alma/storage/azure_cosmos.py +1259 -1259
  80. alma/storage/base.py +1083 -583
  81. alma/storage/chroma.py +1443 -1443
  82. alma/storage/constants.py +103 -103
  83. alma/storage/file_based.py +614 -614
  84. alma/storage/migrations/__init__.py +21 -21
  85. alma/storage/migrations/base.py +321 -321
  86. alma/storage/migrations/runner.py +323 -323
  87. alma/storage/migrations/version_stores.py +337 -337
  88. alma/storage/migrations/versions/__init__.py +11 -11
  89. alma/storage/migrations/versions/v1_0_0.py +373 -373
  90. alma/storage/migrations/versions/v1_1_0_workflow_context.py +551 -0
  91. alma/storage/pinecone.py +1080 -1080
  92. alma/storage/postgresql.py +1948 -1559
  93. alma/storage/qdrant.py +1306 -1306
  94. alma/storage/sqlite_local.py +3041 -1457
  95. alma/testing/__init__.py +46 -46
  96. alma/testing/factories.py +301 -301
  97. alma/testing/mocks.py +389 -389
  98. alma/types.py +292 -264
  99. alma/utils/__init__.py +19 -0
  100. alma/utils/tokenizer.py +521 -0
  101. alma/workflow/__init__.py +83 -0
  102. alma/workflow/artifacts.py +170 -0
  103. alma/workflow/checkpoint.py +311 -0
  104. alma/workflow/context.py +228 -0
  105. alma/workflow/outcomes.py +189 -0
  106. alma/workflow/reducers.py +393 -0
  107. {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/METADATA +210 -72
  108. alma_memory-0.7.0.dist-info/RECORD +112 -0
  109. alma_memory-0.5.1.dist-info/RECORD +0 -93
  110. {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/WHEEL +0 -0
  111. {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/top_level.txt +0 -0
alma/testing/factories.py CHANGED
@@ -1,301 +1,301 @@
1
- """
2
- ALMA Test Factories.
3
-
4
- Provides factory functions for creating test data with sensible defaults.
5
- All factory functions accept keyword arguments to override any field.
6
- """
7
-
8
- import uuid
9
- from datetime import datetime, timedelta, timezone
10
- from typing import Any, Dict, List, Optional
11
-
12
- from alma.types import (
13
- AntiPattern,
14
- DomainKnowledge,
15
- Heuristic,
16
- Outcome,
17
- UserPreference,
18
- )
19
-
20
- __all__ = [
21
- "create_test_heuristic",
22
- "create_test_outcome",
23
- "create_test_preference",
24
- "create_test_knowledge",
25
- "create_test_anti_pattern",
26
- ]
27
-
28
-
29
- def create_test_heuristic(
30
- id: Optional[str] = None,
31
- agent: str = "test-agent",
32
- project_id: str = "test-project",
33
- condition: str = "test condition",
34
- strategy: str = "test strategy",
35
- confidence: float = 0.85,
36
- occurrence_count: int = 10,
37
- success_count: int = 8,
38
- last_validated: Optional[datetime] = None,
39
- created_at: Optional[datetime] = None,
40
- embedding: Optional[List[float]] = None,
41
- metadata: Optional[Dict[str, Any]] = None,
42
- ) -> Heuristic:
43
- """
44
- Create a test Heuristic with sensible defaults.
45
-
46
- All parameters can be overridden to customize the test data.
47
-
48
- Example:
49
- >>> heuristic = create_test_heuristic(agent="helena", confidence=0.95)
50
- >>> assert heuristic.agent == "helena"
51
- >>> assert heuristic.confidence == 0.95
52
-
53
- Args:
54
- id: Unique identifier (auto-generated if not provided)
55
- agent: Agent name that owns this heuristic
56
- project_id: Project identifier
57
- condition: When this heuristic applies
58
- strategy: What strategy to use
59
- confidence: Confidence score (0.0-1.0)
60
- occurrence_count: Number of times this heuristic has been observed
61
- success_count: Number of successful applications
62
- last_validated: Last validation timestamp
63
- created_at: Creation timestamp
64
- embedding: Optional embedding vector
65
- metadata: Additional metadata
66
-
67
- Returns:
68
- A fully populated Heuristic instance
69
- """
70
- now = datetime.now(timezone.utc)
71
- return Heuristic(
72
- id=id or str(uuid.uuid4()),
73
- agent=agent,
74
- project_id=project_id,
75
- condition=condition,
76
- strategy=strategy,
77
- confidence=confidence,
78
- occurrence_count=occurrence_count,
79
- success_count=success_count,
80
- last_validated=last_validated or now,
81
- created_at=created_at or now - timedelta(days=7),
82
- embedding=embedding,
83
- metadata=metadata or {},
84
- )
85
-
86
-
87
- def create_test_outcome(
88
- id: Optional[str] = None,
89
- agent: str = "test-agent",
90
- project_id: str = "test-project",
91
- task_type: str = "test_task",
92
- task_description: str = "Test task description",
93
- success: bool = True,
94
- strategy_used: str = "test strategy",
95
- duration_ms: Optional[int] = 500,
96
- error_message: Optional[str] = None,
97
- user_feedback: Optional[str] = None,
98
- timestamp: Optional[datetime] = None,
99
- embedding: Optional[List[float]] = None,
100
- metadata: Optional[Dict[str, Any]] = None,
101
- ) -> Outcome:
102
- """
103
- Create a test Outcome with sensible defaults.
104
-
105
- All parameters can be overridden to customize the test data.
106
-
107
- Example:
108
- >>> outcome = create_test_outcome(success=False, error_message="Failed")
109
- >>> assert outcome.success is False
110
- >>> assert outcome.error_message == "Failed"
111
-
112
- Args:
113
- id: Unique identifier (auto-generated if not provided)
114
- agent: Agent name that produced this outcome
115
- project_id: Project identifier
116
- task_type: Type of task (e.g., "api_validation")
117
- task_description: Description of the task
118
- success: Whether the task succeeded
119
- strategy_used: Strategy that was applied
120
- duration_ms: Task duration in milliseconds
121
- error_message: Error message if failed
122
- user_feedback: Optional user feedback
123
- timestamp: When the outcome occurred
124
- embedding: Optional embedding vector
125
- metadata: Additional metadata
126
-
127
- Returns:
128
- A fully populated Outcome instance
129
- """
130
- return Outcome(
131
- id=id or str(uuid.uuid4()),
132
- agent=agent,
133
- project_id=project_id,
134
- task_type=task_type,
135
- task_description=task_description,
136
- success=success,
137
- strategy_used=strategy_used,
138
- duration_ms=duration_ms,
139
- error_message=error_message,
140
- user_feedback=user_feedback,
141
- timestamp=timestamp or datetime.now(timezone.utc),
142
- embedding=embedding,
143
- metadata=metadata or {},
144
- )
145
-
146
-
147
- def create_test_preference(
148
- id: Optional[str] = None,
149
- user_id: str = "test-user",
150
- category: str = "code_style",
151
- preference: str = "Test preference value",
152
- source: str = "explicit_instruction",
153
- confidence: float = 1.0,
154
- timestamp: Optional[datetime] = None,
155
- metadata: Optional[Dict[str, Any]] = None,
156
- ) -> UserPreference:
157
- """
158
- Create a test UserPreference with sensible defaults.
159
-
160
- All parameters can be overridden to customize the test data.
161
-
162
- Example:
163
- >>> pref = create_test_preference(
164
- ... category="communication",
165
- ... preference="No emojis"
166
- ... )
167
- >>> assert pref.category == "communication"
168
-
169
- Args:
170
- id: Unique identifier (auto-generated if not provided)
171
- user_id: User identifier
172
- category: Preference category (e.g., "code_style", "communication")
173
- preference: The actual preference text
174
- source: How this preference was learned
175
- confidence: Confidence in this preference (0.0-1.0)
176
- timestamp: When the preference was recorded
177
- metadata: Additional metadata
178
-
179
- Returns:
180
- A fully populated UserPreference instance
181
- """
182
- return UserPreference(
183
- id=id or str(uuid.uuid4()),
184
- user_id=user_id,
185
- category=category,
186
- preference=preference,
187
- source=source,
188
- confidence=confidence,
189
- timestamp=timestamp or datetime.now(timezone.utc),
190
- metadata=metadata or {},
191
- )
192
-
193
-
194
- def create_test_knowledge(
195
- id: Optional[str] = None,
196
- agent: str = "test-agent",
197
- project_id: str = "test-project",
198
- domain: str = "test_domain",
199
- fact: str = "Test domain fact",
200
- source: str = "test_source",
201
- confidence: float = 1.0,
202
- last_verified: Optional[datetime] = None,
203
- embedding: Optional[List[float]] = None,
204
- metadata: Optional[Dict[str, Any]] = None,
205
- ) -> DomainKnowledge:
206
- """
207
- Create a test DomainKnowledge with sensible defaults.
208
-
209
- All parameters can be overridden to customize the test data.
210
-
211
- Example:
212
- >>> knowledge = create_test_knowledge(
213
- ... domain="authentication",
214
- ... fact="JWT tokens expire in 24h"
215
- ... )
216
- >>> assert knowledge.domain == "authentication"
217
-
218
- Args:
219
- id: Unique identifier (auto-generated if not provided)
220
- agent: Agent name that owns this knowledge
221
- project_id: Project identifier
222
- domain: Knowledge domain (e.g., "authentication", "database")
223
- fact: The factual information
224
- source: How this knowledge was acquired
225
- confidence: Confidence in this knowledge (0.0-1.0)
226
- last_verified: Last verification timestamp
227
- embedding: Optional embedding vector
228
- metadata: Additional metadata
229
-
230
- Returns:
231
- A fully populated DomainKnowledge instance
232
- """
233
- return DomainKnowledge(
234
- id=id or str(uuid.uuid4()),
235
- agent=agent,
236
- project_id=project_id,
237
- domain=domain,
238
- fact=fact,
239
- source=source,
240
- confidence=confidence,
241
- last_verified=last_verified or datetime.now(timezone.utc),
242
- embedding=embedding,
243
- metadata=metadata or {},
244
- )
245
-
246
-
247
- def create_test_anti_pattern(
248
- id: Optional[str] = None,
249
- agent: str = "test-agent",
250
- project_id: str = "test-project",
251
- pattern: str = "Test anti-pattern",
252
- why_bad: str = "This is why it's bad",
253
- better_alternative: str = "Do this instead",
254
- occurrence_count: int = 3,
255
- last_seen: Optional[datetime] = None,
256
- created_at: Optional[datetime] = None,
257
- embedding: Optional[List[float]] = None,
258
- metadata: Optional[Dict[str, Any]] = None,
259
- ) -> AntiPattern:
260
- """
261
- Create a test AntiPattern with sensible defaults.
262
-
263
- All parameters can be overridden to customize the test data.
264
-
265
- Example:
266
- >>> anti_pattern = create_test_anti_pattern(
267
- ... pattern="Using sleep() for waits",
268
- ... better_alternative="Use explicit waits"
269
- ... )
270
- >>> assert "sleep" in anti_pattern.pattern
271
-
272
- Args:
273
- id: Unique identifier (auto-generated if not provided)
274
- agent: Agent name that identified this anti-pattern
275
- project_id: Project identifier
276
- pattern: Description of the anti-pattern
277
- why_bad: Explanation of why this pattern is problematic
278
- better_alternative: Recommended alternative approach
279
- occurrence_count: How often this pattern has been observed
280
- last_seen: Last time this pattern was observed
281
- created_at: When this anti-pattern was first identified
282
- embedding: Optional embedding vector
283
- metadata: Additional metadata
284
-
285
- Returns:
286
- A fully populated AntiPattern instance
287
- """
288
- now = datetime.now(timezone.utc)
289
- return AntiPattern(
290
- id=id or str(uuid.uuid4()),
291
- agent=agent,
292
- project_id=project_id,
293
- pattern=pattern,
294
- why_bad=why_bad,
295
- better_alternative=better_alternative,
296
- occurrence_count=occurrence_count,
297
- last_seen=last_seen or now,
298
- created_at=created_at or now - timedelta(days=3),
299
- embedding=embedding,
300
- metadata=metadata or {},
301
- )
1
+ """
2
+ ALMA Test Factories.
3
+
4
+ Provides factory functions for creating test data with sensible defaults.
5
+ All factory functions accept keyword arguments to override any field.
6
+ """
7
+
8
+ import uuid
9
+ from datetime import datetime, timedelta, timezone
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ from alma.types import (
13
+ AntiPattern,
14
+ DomainKnowledge,
15
+ Heuristic,
16
+ Outcome,
17
+ UserPreference,
18
+ )
19
+
20
+ __all__ = [
21
+ "create_test_heuristic",
22
+ "create_test_outcome",
23
+ "create_test_preference",
24
+ "create_test_knowledge",
25
+ "create_test_anti_pattern",
26
+ ]
27
+
28
+
29
+ def create_test_heuristic(
30
+ id: Optional[str] = None,
31
+ agent: str = "test-agent",
32
+ project_id: str = "test-project",
33
+ condition: str = "test condition",
34
+ strategy: str = "test strategy",
35
+ confidence: float = 0.85,
36
+ occurrence_count: int = 10,
37
+ success_count: int = 8,
38
+ last_validated: Optional[datetime] = None,
39
+ created_at: Optional[datetime] = None,
40
+ embedding: Optional[List[float]] = None,
41
+ metadata: Optional[Dict[str, Any]] = None,
42
+ ) -> Heuristic:
43
+ """
44
+ Create a test Heuristic with sensible defaults.
45
+
46
+ All parameters can be overridden to customize the test data.
47
+
48
+ Example:
49
+ >>> heuristic = create_test_heuristic(agent="helena", confidence=0.95)
50
+ >>> assert heuristic.agent == "helena"
51
+ >>> assert heuristic.confidence == 0.95
52
+
53
+ Args:
54
+ id: Unique identifier (auto-generated if not provided)
55
+ agent: Agent name that owns this heuristic
56
+ project_id: Project identifier
57
+ condition: When this heuristic applies
58
+ strategy: What strategy to use
59
+ confidence: Confidence score (0.0-1.0)
60
+ occurrence_count: Number of times this heuristic has been observed
61
+ success_count: Number of successful applications
62
+ last_validated: Last validation timestamp
63
+ created_at: Creation timestamp
64
+ embedding: Optional embedding vector
65
+ metadata: Additional metadata
66
+
67
+ Returns:
68
+ A fully populated Heuristic instance
69
+ """
70
+ now = datetime.now(timezone.utc)
71
+ return Heuristic(
72
+ id=id or str(uuid.uuid4()),
73
+ agent=agent,
74
+ project_id=project_id,
75
+ condition=condition,
76
+ strategy=strategy,
77
+ confidence=confidence,
78
+ occurrence_count=occurrence_count,
79
+ success_count=success_count,
80
+ last_validated=last_validated or now,
81
+ created_at=created_at or now - timedelta(days=7),
82
+ embedding=embedding,
83
+ metadata=metadata or {},
84
+ )
85
+
86
+
87
+ def create_test_outcome(
88
+ id: Optional[str] = None,
89
+ agent: str = "test-agent",
90
+ project_id: str = "test-project",
91
+ task_type: str = "test_task",
92
+ task_description: str = "Test task description",
93
+ success: bool = True,
94
+ strategy_used: str = "test strategy",
95
+ duration_ms: Optional[int] = 500,
96
+ error_message: Optional[str] = None,
97
+ user_feedback: Optional[str] = None,
98
+ timestamp: Optional[datetime] = None,
99
+ embedding: Optional[List[float]] = None,
100
+ metadata: Optional[Dict[str, Any]] = None,
101
+ ) -> Outcome:
102
+ """
103
+ Create a test Outcome with sensible defaults.
104
+
105
+ All parameters can be overridden to customize the test data.
106
+
107
+ Example:
108
+ >>> outcome = create_test_outcome(success=False, error_message="Failed")
109
+ >>> assert outcome.success is False
110
+ >>> assert outcome.error_message == "Failed"
111
+
112
+ Args:
113
+ id: Unique identifier (auto-generated if not provided)
114
+ agent: Agent name that produced this outcome
115
+ project_id: Project identifier
116
+ task_type: Type of task (e.g., "api_validation")
117
+ task_description: Description of the task
118
+ success: Whether the task succeeded
119
+ strategy_used: Strategy that was applied
120
+ duration_ms: Task duration in milliseconds
121
+ error_message: Error message if failed
122
+ user_feedback: Optional user feedback
123
+ timestamp: When the outcome occurred
124
+ embedding: Optional embedding vector
125
+ metadata: Additional metadata
126
+
127
+ Returns:
128
+ A fully populated Outcome instance
129
+ """
130
+ return Outcome(
131
+ id=id or str(uuid.uuid4()),
132
+ agent=agent,
133
+ project_id=project_id,
134
+ task_type=task_type,
135
+ task_description=task_description,
136
+ success=success,
137
+ strategy_used=strategy_used,
138
+ duration_ms=duration_ms,
139
+ error_message=error_message,
140
+ user_feedback=user_feedback,
141
+ timestamp=timestamp or datetime.now(timezone.utc),
142
+ embedding=embedding,
143
+ metadata=metadata or {},
144
+ )
145
+
146
+
147
+ def create_test_preference(
148
+ id: Optional[str] = None,
149
+ user_id: str = "test-user",
150
+ category: str = "code_style",
151
+ preference: str = "Test preference value",
152
+ source: str = "explicit_instruction",
153
+ confidence: float = 1.0,
154
+ timestamp: Optional[datetime] = None,
155
+ metadata: Optional[Dict[str, Any]] = None,
156
+ ) -> UserPreference:
157
+ """
158
+ Create a test UserPreference with sensible defaults.
159
+
160
+ All parameters can be overridden to customize the test data.
161
+
162
+ Example:
163
+ >>> pref = create_test_preference(
164
+ ... category="communication",
165
+ ... preference="No emojis"
166
+ ... )
167
+ >>> assert pref.category == "communication"
168
+
169
+ Args:
170
+ id: Unique identifier (auto-generated if not provided)
171
+ user_id: User identifier
172
+ category: Preference category (e.g., "code_style", "communication")
173
+ preference: The actual preference text
174
+ source: How this preference was learned
175
+ confidence: Confidence in this preference (0.0-1.0)
176
+ timestamp: When the preference was recorded
177
+ metadata: Additional metadata
178
+
179
+ Returns:
180
+ A fully populated UserPreference instance
181
+ """
182
+ return UserPreference(
183
+ id=id or str(uuid.uuid4()),
184
+ user_id=user_id,
185
+ category=category,
186
+ preference=preference,
187
+ source=source,
188
+ confidence=confidence,
189
+ timestamp=timestamp or datetime.now(timezone.utc),
190
+ metadata=metadata or {},
191
+ )
192
+
193
+
194
+ def create_test_knowledge(
195
+ id: Optional[str] = None,
196
+ agent: str = "test-agent",
197
+ project_id: str = "test-project",
198
+ domain: str = "test_domain",
199
+ fact: str = "Test domain fact",
200
+ source: str = "test_source",
201
+ confidence: float = 1.0,
202
+ last_verified: Optional[datetime] = None,
203
+ embedding: Optional[List[float]] = None,
204
+ metadata: Optional[Dict[str, Any]] = None,
205
+ ) -> DomainKnowledge:
206
+ """
207
+ Create a test DomainKnowledge with sensible defaults.
208
+
209
+ All parameters can be overridden to customize the test data.
210
+
211
+ Example:
212
+ >>> knowledge = create_test_knowledge(
213
+ ... domain="authentication",
214
+ ... fact="JWT tokens expire in 24h"
215
+ ... )
216
+ >>> assert knowledge.domain == "authentication"
217
+
218
+ Args:
219
+ id: Unique identifier (auto-generated if not provided)
220
+ agent: Agent name that owns this knowledge
221
+ project_id: Project identifier
222
+ domain: Knowledge domain (e.g., "authentication", "database")
223
+ fact: The factual information
224
+ source: How this knowledge was acquired
225
+ confidence: Confidence in this knowledge (0.0-1.0)
226
+ last_verified: Last verification timestamp
227
+ embedding: Optional embedding vector
228
+ metadata: Additional metadata
229
+
230
+ Returns:
231
+ A fully populated DomainKnowledge instance
232
+ """
233
+ return DomainKnowledge(
234
+ id=id or str(uuid.uuid4()),
235
+ agent=agent,
236
+ project_id=project_id,
237
+ domain=domain,
238
+ fact=fact,
239
+ source=source,
240
+ confidence=confidence,
241
+ last_verified=last_verified or datetime.now(timezone.utc),
242
+ embedding=embedding,
243
+ metadata=metadata or {},
244
+ )
245
+
246
+
247
+ def create_test_anti_pattern(
248
+ id: Optional[str] = None,
249
+ agent: str = "test-agent",
250
+ project_id: str = "test-project",
251
+ pattern: str = "Test anti-pattern",
252
+ why_bad: str = "This is why it's bad",
253
+ better_alternative: str = "Do this instead",
254
+ occurrence_count: int = 3,
255
+ last_seen: Optional[datetime] = None,
256
+ created_at: Optional[datetime] = None,
257
+ embedding: Optional[List[float]] = None,
258
+ metadata: Optional[Dict[str, Any]] = None,
259
+ ) -> AntiPattern:
260
+ """
261
+ Create a test AntiPattern with sensible defaults.
262
+
263
+ All parameters can be overridden to customize the test data.
264
+
265
+ Example:
266
+ >>> anti_pattern = create_test_anti_pattern(
267
+ ... pattern="Using sleep() for waits",
268
+ ... better_alternative="Use explicit waits"
269
+ ... )
270
+ >>> assert "sleep" in anti_pattern.pattern
271
+
272
+ Args:
273
+ id: Unique identifier (auto-generated if not provided)
274
+ agent: Agent name that identified this anti-pattern
275
+ project_id: Project identifier
276
+ pattern: Description of the anti-pattern
277
+ why_bad: Explanation of why this pattern is problematic
278
+ better_alternative: Recommended alternative approach
279
+ occurrence_count: How often this pattern has been observed
280
+ last_seen: Last time this pattern was observed
281
+ created_at: When this anti-pattern was first identified
282
+ embedding: Optional embedding vector
283
+ metadata: Additional metadata
284
+
285
+ Returns:
286
+ A fully populated AntiPattern instance
287
+ """
288
+ now = datetime.now(timezone.utc)
289
+ return AntiPattern(
290
+ id=id or str(uuid.uuid4()),
291
+ agent=agent,
292
+ project_id=project_id,
293
+ pattern=pattern,
294
+ why_bad=why_bad,
295
+ better_alternative=better_alternative,
296
+ occurrence_count=occurrence_count,
297
+ last_seen=last_seen or now,
298
+ created_at=created_at or now - timedelta(days=3),
299
+ embedding=embedding,
300
+ metadata=metadata or {},
301
+ )