htmlgraph 0.20.1__py3-none-any.whl → 0.27.5__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 (304) hide show
  1. htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
  2. htmlgraph/.htmlgraph/agents.json +72 -0
  3. htmlgraph/.htmlgraph/htmlgraph.db +0 -0
  4. htmlgraph/__init__.py +51 -1
  5. htmlgraph/__init__.pyi +123 -0
  6. htmlgraph/agent_detection.py +26 -10
  7. htmlgraph/agent_registry.py +2 -1
  8. htmlgraph/analytics/__init__.py +8 -1
  9. htmlgraph/analytics/cli.py +86 -20
  10. htmlgraph/analytics/cost_analyzer.py +391 -0
  11. htmlgraph/analytics/cost_monitor.py +664 -0
  12. htmlgraph/analytics/cost_reporter.py +675 -0
  13. htmlgraph/analytics/cross_session.py +617 -0
  14. htmlgraph/analytics/dependency.py +10 -6
  15. htmlgraph/analytics/pattern_learning.py +771 -0
  16. htmlgraph/analytics/session_graph.py +707 -0
  17. htmlgraph/analytics/strategic/__init__.py +80 -0
  18. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  19. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  20. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  21. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  22. htmlgraph/analytics/work_type.py +67 -27
  23. htmlgraph/analytics_index.py +53 -20
  24. htmlgraph/api/__init__.py +3 -0
  25. htmlgraph/api/cost_alerts_websocket.py +416 -0
  26. htmlgraph/api/main.py +2498 -0
  27. htmlgraph/api/static/htmx.min.js +1 -0
  28. htmlgraph/api/static/style-redesign.css +1344 -0
  29. htmlgraph/api/static/style.css +1079 -0
  30. htmlgraph/api/templates/dashboard-redesign.html +1366 -0
  31. htmlgraph/api/templates/dashboard.html +794 -0
  32. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  33. htmlgraph/api/templates/partials/activity-feed.html +1100 -0
  34. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  35. htmlgraph/api/templates/partials/agents.html +317 -0
  36. htmlgraph/api/templates/partials/event-traces.html +373 -0
  37. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  38. htmlgraph/api/templates/partials/features.html +578 -0
  39. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  40. htmlgraph/api/templates/partials/metrics.html +346 -0
  41. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  42. htmlgraph/api/templates/partials/orchestration.html +198 -0
  43. htmlgraph/api/templates/partials/spawners.html +375 -0
  44. htmlgraph/api/templates/partials/work-items.html +613 -0
  45. htmlgraph/api/websocket.py +538 -0
  46. htmlgraph/archive/__init__.py +24 -0
  47. htmlgraph/archive/bloom.py +234 -0
  48. htmlgraph/archive/fts.py +297 -0
  49. htmlgraph/archive/manager.py +583 -0
  50. htmlgraph/archive/search.py +244 -0
  51. htmlgraph/atomic_ops.py +560 -0
  52. htmlgraph/attribute_index.py +2 -1
  53. htmlgraph/bounded_paths.py +539 -0
  54. htmlgraph/builders/base.py +57 -2
  55. htmlgraph/builders/bug.py +19 -3
  56. htmlgraph/builders/chore.py +19 -3
  57. htmlgraph/builders/epic.py +19 -3
  58. htmlgraph/builders/feature.py +27 -3
  59. htmlgraph/builders/insight.py +2 -1
  60. htmlgraph/builders/metric.py +2 -1
  61. htmlgraph/builders/pattern.py +2 -1
  62. htmlgraph/builders/phase.py +19 -3
  63. htmlgraph/builders/spike.py +29 -3
  64. htmlgraph/builders/track.py +42 -1
  65. htmlgraph/cigs/__init__.py +81 -0
  66. htmlgraph/cigs/autonomy.py +385 -0
  67. htmlgraph/cigs/cost.py +475 -0
  68. htmlgraph/cigs/messages_basic.py +472 -0
  69. htmlgraph/cigs/messaging.py +365 -0
  70. htmlgraph/cigs/models.py +771 -0
  71. htmlgraph/cigs/pattern_storage.py +427 -0
  72. htmlgraph/cigs/patterns.py +503 -0
  73. htmlgraph/cigs/posttool_analyzer.py +234 -0
  74. htmlgraph/cigs/reporter.py +818 -0
  75. htmlgraph/cigs/tracker.py +317 -0
  76. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  77. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  78. htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
  79. htmlgraph/cli/__init__.py +42 -0
  80. htmlgraph/cli/__main__.py +6 -0
  81. htmlgraph/cli/analytics.py +1424 -0
  82. htmlgraph/cli/base.py +685 -0
  83. htmlgraph/cli/constants.py +206 -0
  84. htmlgraph/cli/core.py +954 -0
  85. htmlgraph/cli/main.py +147 -0
  86. htmlgraph/cli/models.py +475 -0
  87. htmlgraph/cli/templates/__init__.py +1 -0
  88. htmlgraph/cli/templates/cost_dashboard.py +399 -0
  89. htmlgraph/cli/work/__init__.py +239 -0
  90. htmlgraph/cli/work/browse.py +115 -0
  91. htmlgraph/cli/work/features.py +568 -0
  92. htmlgraph/cli/work/orchestration.py +676 -0
  93. htmlgraph/cli/work/report.py +728 -0
  94. htmlgraph/cli/work/sessions.py +466 -0
  95. htmlgraph/cli/work/snapshot.py +559 -0
  96. htmlgraph/cli/work/tracks.py +486 -0
  97. htmlgraph/cli_commands/__init__.py +1 -0
  98. htmlgraph/cli_commands/feature.py +195 -0
  99. htmlgraph/cli_framework.py +115 -0
  100. htmlgraph/collections/__init__.py +2 -0
  101. htmlgraph/collections/base.py +197 -14
  102. htmlgraph/collections/bug.py +2 -1
  103. htmlgraph/collections/chore.py +2 -1
  104. htmlgraph/collections/epic.py +2 -1
  105. htmlgraph/collections/feature.py +2 -1
  106. htmlgraph/collections/insight.py +2 -1
  107. htmlgraph/collections/metric.py +2 -1
  108. htmlgraph/collections/pattern.py +2 -1
  109. htmlgraph/collections/phase.py +2 -1
  110. htmlgraph/collections/session.py +194 -0
  111. htmlgraph/collections/spike.py +13 -2
  112. htmlgraph/collections/task_delegation.py +241 -0
  113. htmlgraph/collections/todo.py +14 -1
  114. htmlgraph/collections/traces.py +487 -0
  115. htmlgraph/config/cost_models.json +56 -0
  116. htmlgraph/config.py +190 -0
  117. htmlgraph/context_analytics.py +2 -1
  118. htmlgraph/converter.py +116 -7
  119. htmlgraph/cost_analysis/__init__.py +5 -0
  120. htmlgraph/cost_analysis/analyzer.py +438 -0
  121. htmlgraph/dashboard.html +2246 -248
  122. htmlgraph/dashboard.html.backup +6592 -0
  123. htmlgraph/dashboard.html.bak +7181 -0
  124. htmlgraph/dashboard.html.bak2 +7231 -0
  125. htmlgraph/dashboard.html.bak3 +7232 -0
  126. htmlgraph/db/__init__.py +38 -0
  127. htmlgraph/db/queries.py +790 -0
  128. htmlgraph/db/schema.py +1788 -0
  129. htmlgraph/decorators.py +317 -0
  130. htmlgraph/dependency_models.py +2 -1
  131. htmlgraph/deploy.py +26 -27
  132. htmlgraph/docs/API_REFERENCE.md +841 -0
  133. htmlgraph/docs/HTTP_API.md +750 -0
  134. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  135. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
  136. htmlgraph/docs/README.md +532 -0
  137. htmlgraph/docs/__init__.py +77 -0
  138. htmlgraph/docs/docs_version.py +55 -0
  139. htmlgraph/docs/metadata.py +93 -0
  140. htmlgraph/docs/migrations.py +232 -0
  141. htmlgraph/docs/template_engine.py +143 -0
  142. htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  143. htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  144. htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  145. htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  146. htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  147. htmlgraph/docs/version_check.py +163 -0
  148. htmlgraph/edge_index.py +2 -1
  149. htmlgraph/error_handler.py +544 -0
  150. htmlgraph/event_log.py +86 -37
  151. htmlgraph/event_migration.py +2 -1
  152. htmlgraph/file_watcher.py +12 -8
  153. htmlgraph/find_api.py +2 -1
  154. htmlgraph/git_events.py +67 -9
  155. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  156. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  157. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  158. htmlgraph/hooks/__init__.py +8 -0
  159. htmlgraph/hooks/bootstrap.py +169 -0
  160. htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
  161. htmlgraph/hooks/concurrent_sessions.py +208 -0
  162. htmlgraph/hooks/context.py +350 -0
  163. htmlgraph/hooks/drift_handler.py +525 -0
  164. htmlgraph/hooks/event_tracker.py +790 -99
  165. htmlgraph/hooks/git_commands.py +175 -0
  166. htmlgraph/hooks/installer.py +5 -1
  167. htmlgraph/hooks/orchestrator.py +327 -76
  168. htmlgraph/hooks/orchestrator_reflector.py +31 -4
  169. htmlgraph/hooks/post_tool_use_failure.py +32 -7
  170. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  171. htmlgraph/hooks/posttooluse.py +92 -19
  172. htmlgraph/hooks/pretooluse.py +527 -7
  173. htmlgraph/hooks/prompt_analyzer.py +637 -0
  174. htmlgraph/hooks/session_handler.py +668 -0
  175. htmlgraph/hooks/session_summary.py +395 -0
  176. htmlgraph/hooks/state_manager.py +504 -0
  177. htmlgraph/hooks/subagent_detection.py +202 -0
  178. htmlgraph/hooks/subagent_stop.py +369 -0
  179. htmlgraph/hooks/task_enforcer.py +99 -4
  180. htmlgraph/hooks/validator.py +212 -91
  181. htmlgraph/ids.py +2 -1
  182. htmlgraph/learning.py +125 -100
  183. htmlgraph/mcp_server.py +2 -1
  184. htmlgraph/models.py +217 -18
  185. htmlgraph/operations/README.md +62 -0
  186. htmlgraph/operations/__init__.py +79 -0
  187. htmlgraph/operations/analytics.py +339 -0
  188. htmlgraph/operations/bootstrap.py +289 -0
  189. htmlgraph/operations/events.py +244 -0
  190. htmlgraph/operations/fastapi_server.py +231 -0
  191. htmlgraph/operations/hooks.py +350 -0
  192. htmlgraph/operations/initialization.py +597 -0
  193. htmlgraph/operations/initialization.py.backup +228 -0
  194. htmlgraph/operations/server.py +303 -0
  195. htmlgraph/orchestration/__init__.py +58 -0
  196. htmlgraph/orchestration/claude_launcher.py +179 -0
  197. htmlgraph/orchestration/command_builder.py +72 -0
  198. htmlgraph/orchestration/headless_spawner.py +281 -0
  199. htmlgraph/orchestration/live_events.py +377 -0
  200. htmlgraph/orchestration/model_selection.py +327 -0
  201. htmlgraph/orchestration/plugin_manager.py +140 -0
  202. htmlgraph/orchestration/prompts.py +137 -0
  203. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  204. htmlgraph/orchestration/spawners/__init__.py +16 -0
  205. htmlgraph/orchestration/spawners/base.py +194 -0
  206. htmlgraph/orchestration/spawners/claude.py +173 -0
  207. htmlgraph/orchestration/spawners/codex.py +435 -0
  208. htmlgraph/orchestration/spawners/copilot.py +294 -0
  209. htmlgraph/orchestration/spawners/gemini.py +471 -0
  210. htmlgraph/orchestration/subprocess_runner.py +36 -0
  211. htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
  212. htmlgraph/orchestration.md +563 -0
  213. htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
  214. htmlgraph/orchestrator.py +2 -1
  215. htmlgraph/orchestrator_config.py +357 -0
  216. htmlgraph/orchestrator_mode.py +115 -4
  217. htmlgraph/parallel.py +2 -1
  218. htmlgraph/parser.py +86 -6
  219. htmlgraph/path_query.py +608 -0
  220. htmlgraph/pattern_matcher.py +636 -0
  221. htmlgraph/pydantic_models.py +476 -0
  222. htmlgraph/quality_gates.py +350 -0
  223. htmlgraph/query_builder.py +2 -1
  224. htmlgraph/query_composer.py +509 -0
  225. htmlgraph/reflection.py +443 -0
  226. htmlgraph/refs.py +344 -0
  227. htmlgraph/repo_hash.py +512 -0
  228. htmlgraph/repositories/__init__.py +292 -0
  229. htmlgraph/repositories/analytics_repository.py +455 -0
  230. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  231. htmlgraph/repositories/feature_repository.py +581 -0
  232. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  233. htmlgraph/repositories/feature_repository_memory.py +607 -0
  234. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  235. htmlgraph/repositories/filter_service.py +620 -0
  236. htmlgraph/repositories/filter_service_standard.py +445 -0
  237. htmlgraph/repositories/shared_cache.py +621 -0
  238. htmlgraph/repositories/shared_cache_memory.py +395 -0
  239. htmlgraph/repositories/track_repository.py +552 -0
  240. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  241. htmlgraph/repositories/track_repository_memory.py +508 -0
  242. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  243. htmlgraph/sdk/__init__.py +398 -0
  244. htmlgraph/sdk/__init__.pyi +14 -0
  245. htmlgraph/sdk/analytics/__init__.py +19 -0
  246. htmlgraph/sdk/analytics/engine.py +155 -0
  247. htmlgraph/sdk/analytics/helpers.py +178 -0
  248. htmlgraph/sdk/analytics/registry.py +109 -0
  249. htmlgraph/sdk/base.py +484 -0
  250. htmlgraph/sdk/constants.py +216 -0
  251. htmlgraph/sdk/core.pyi +308 -0
  252. htmlgraph/sdk/discovery.py +120 -0
  253. htmlgraph/sdk/help/__init__.py +12 -0
  254. htmlgraph/sdk/help/mixin.py +699 -0
  255. htmlgraph/sdk/mixins/__init__.py +15 -0
  256. htmlgraph/sdk/mixins/attribution.py +113 -0
  257. htmlgraph/sdk/mixins/mixin.py +410 -0
  258. htmlgraph/sdk/operations/__init__.py +12 -0
  259. htmlgraph/sdk/operations/mixin.py +427 -0
  260. htmlgraph/sdk/orchestration/__init__.py +17 -0
  261. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  262. htmlgraph/sdk/orchestration/spawner.py +204 -0
  263. htmlgraph/sdk/planning/__init__.py +19 -0
  264. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  265. htmlgraph/sdk/planning/mixin.py +211 -0
  266. htmlgraph/sdk/planning/parallel.py +186 -0
  267. htmlgraph/sdk/planning/queue.py +210 -0
  268. htmlgraph/sdk/planning/recommendations.py +87 -0
  269. htmlgraph/sdk/planning/smart_planning.py +319 -0
  270. htmlgraph/sdk/session/__init__.py +19 -0
  271. htmlgraph/sdk/session/continuity.py +57 -0
  272. htmlgraph/sdk/session/handoff.py +110 -0
  273. htmlgraph/sdk/session/info.py +309 -0
  274. htmlgraph/sdk/session/manager.py +103 -0
  275. htmlgraph/sdk/strategic/__init__.py +26 -0
  276. htmlgraph/sdk/strategic/mixin.py +563 -0
  277. htmlgraph/server.py +295 -107
  278. htmlgraph/session_hooks.py +300 -0
  279. htmlgraph/session_manager.py +285 -3
  280. htmlgraph/session_registry.py +587 -0
  281. htmlgraph/session_state.py +436 -0
  282. htmlgraph/session_warning.py +2 -1
  283. htmlgraph/sessions/__init__.py +23 -0
  284. htmlgraph/sessions/handoff.py +756 -0
  285. htmlgraph/system_prompts.py +450 -0
  286. htmlgraph/templates/orchestration-view.html +350 -0
  287. htmlgraph/track_builder.py +33 -1
  288. htmlgraph/track_manager.py +38 -0
  289. htmlgraph/transcript.py +18 -5
  290. htmlgraph/validation.py +115 -0
  291. htmlgraph/watch.py +2 -1
  292. htmlgraph/work_type_utils.py +2 -1
  293. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
  294. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
  295. htmlgraph-0.27.5.dist-info/RECORD +337 -0
  296. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
  297. htmlgraph/cli.py +0 -4839
  298. htmlgraph/sdk.py +0 -2359
  299. htmlgraph-0.20.1.dist-info/RECORD +0 -118
  300. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
  301. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  302. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  303. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  304. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
@@ -0,0 +1,427 @@
1
+ """
2
+ Pattern Storage for CIGS (Computational Imperative Guidance System).
3
+
4
+ Provides thread-safe storage and retrieval of detected behavioral patterns
5
+ in HtmlGraph format (.htmlgraph/cigs/patterns.json).
6
+
7
+ Features:
8
+ - Atomic JSON read/write with file locking
9
+ - Thread-safe operations with lock management
10
+ - Pattern persistence across sessions
11
+ - Pattern analytics and aggregation
12
+
13
+ Reference: .htmlgraph/spikes/computational-imperative-guidance-system-design.md (Part 3.2)
14
+ """
15
+
16
+ import json
17
+ import logging
18
+ from datetime import datetime
19
+ from pathlib import Path
20
+ from threading import Lock
21
+ from typing import Any, cast
22
+ from uuid import uuid4
23
+
24
+ from htmlgraph.cigs.models import PatternRecord
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class PatternStorage:
30
+ """
31
+ Thread-safe storage for behavioral patterns in HtmlGraph format.
32
+
33
+ Storage format: `.htmlgraph/cigs/patterns.json`
34
+
35
+ JSON Schema:
36
+ {
37
+ "patterns": [
38
+ {
39
+ "id": "pattern-001",
40
+ "pattern_type": "anti-pattern",
41
+ "name": "Read-Grep-Read Sequence",
42
+ "description": "Multiple exploration tools used in sequence",
43
+ "trigger_conditions": ["3+ exploration tools in last 5 calls"],
44
+ "example_sequence": ["Read", "Grep", "Read"],
45
+ "occurrence_count": 15,
46
+ "sessions_affected": ["sess-abc", "sess-def"],
47
+ "correct_approach": "Use spawn_gemini() for exploration",
48
+ "delegation_suggestion": "spawn_gemini(prompt='...')"
49
+ }
50
+ ],
51
+ "good_patterns": [...]
52
+ }
53
+ """
54
+
55
+ def __init__(self, graph_dir: Path):
56
+ """
57
+ Initialize pattern storage.
58
+
59
+ Args:
60
+ graph_dir: Path to .htmlgraph directory (e.g., /project/.htmlgraph)
61
+ """
62
+ self.graph_dir = Path(graph_dir)
63
+ self.patterns_file = self.graph_dir / "cigs" / "patterns.json"
64
+ self._lock = Lock()
65
+
66
+ # Ensure directory exists
67
+ self.patterns_file.parent.mkdir(parents=True, exist_ok=True)
68
+
69
+ # Initialize file if it doesn't exist
70
+ if not self.patterns_file.exists():
71
+ self._write_atomic({"patterns": [], "good_patterns": []})
72
+
73
+ def add_pattern(self, pattern: PatternRecord) -> str:
74
+ """
75
+ Add a new pattern to storage.
76
+
77
+ Args:
78
+ pattern: PatternRecord to add
79
+
80
+ Returns:
81
+ Pattern ID (generated if not provided)
82
+ """
83
+ if not pattern.id:
84
+ pattern.id = f"pattern-{uuid4().hex[:8]}"
85
+
86
+ with self._lock:
87
+ data = self._read_atomic()
88
+
89
+ # Determine which list to add to
90
+ if pattern.pattern_type == "anti-pattern":
91
+ patterns_list = data["patterns"]
92
+ else:
93
+ patterns_list = data["good_patterns"]
94
+
95
+ # Check if pattern already exists
96
+ existing = next((p for p in patterns_list if p["id"] == pattern.id), None)
97
+
98
+ if existing:
99
+ # Update existing pattern
100
+ patterns_list[patterns_list.index(existing)] = self._pattern_to_dict(
101
+ pattern
102
+ )
103
+ logger.debug(f"Updated pattern: {pattern.id}")
104
+ else:
105
+ # Add new pattern
106
+ patterns_list.append(self._pattern_to_dict(pattern))
107
+ logger.debug(f"Added pattern: {pattern.id}")
108
+
109
+ self._write_atomic(data)
110
+
111
+ return pattern.id
112
+
113
+ def get_pattern(self, pattern_id: str) -> PatternRecord | None:
114
+ """
115
+ Retrieve a pattern by ID.
116
+
117
+ Args:
118
+ pattern_id: Pattern ID to retrieve
119
+
120
+ Returns:
121
+ PatternRecord if found, None otherwise
122
+ """
123
+ with self._lock:
124
+ data = self._read_atomic()
125
+
126
+ # Search in both lists
127
+ for pattern_dict in data["patterns"] + data["good_patterns"]:
128
+ if pattern_dict["id"] == pattern_id:
129
+ return self._dict_to_pattern(pattern_dict)
130
+
131
+ return None
132
+
133
+ def get_all_patterns(self) -> list[PatternRecord]:
134
+ """
135
+ Retrieve all patterns (both anti-patterns and good patterns).
136
+
137
+ Returns:
138
+ List of all PatternRecord objects
139
+ """
140
+ with self._lock:
141
+ data = self._read_atomic()
142
+
143
+ patterns = []
144
+ for pattern_dict in data["patterns"] + data["good_patterns"]:
145
+ patterns.append(self._dict_to_pattern(pattern_dict))
146
+
147
+ return patterns
148
+
149
+ def get_anti_patterns(self) -> list[PatternRecord]:
150
+ """
151
+ Retrieve only anti-patterns.
152
+
153
+ Returns:
154
+ List of anti-pattern PatternRecord objects
155
+ """
156
+ with self._lock:
157
+ data = self._read_atomic()
158
+
159
+ patterns = []
160
+ for pattern_dict in data["patterns"]:
161
+ patterns.append(self._dict_to_pattern(pattern_dict))
162
+
163
+ return patterns
164
+
165
+ def get_good_patterns(self) -> list[PatternRecord]:
166
+ """
167
+ Retrieve only good patterns.
168
+
169
+ Returns:
170
+ List of good pattern PatternRecord objects
171
+ """
172
+ with self._lock:
173
+ data = self._read_atomic()
174
+
175
+ patterns = []
176
+ for pattern_dict in data["good_patterns"]:
177
+ patterns.append(self._dict_to_pattern(pattern_dict))
178
+
179
+ return patterns
180
+
181
+ def update_pattern_occurrence(self, pattern_id: str, session_id: str) -> bool:
182
+ """
183
+ Update pattern occurrence count and add session.
184
+
185
+ Args:
186
+ pattern_id: Pattern to update
187
+ session_id: Session where pattern was detected
188
+
189
+ Returns:
190
+ True if updated, False if pattern not found
191
+ """
192
+ with self._lock:
193
+ data = self._read_atomic()
194
+
195
+ # Search in both lists
196
+ for patterns_list in [data["patterns"], data["good_patterns"]]:
197
+ for pattern_dict in patterns_list:
198
+ if pattern_dict["id"] == pattern_id:
199
+ pattern_dict["occurrence_count"] += 1
200
+
201
+ # Add session if not already present
202
+ if session_id not in pattern_dict["sessions_affected"]:
203
+ pattern_dict["sessions_affected"].append(session_id)
204
+
205
+ self._write_atomic(data)
206
+ logger.debug(
207
+ f"Updated occurrence for pattern {pattern_id}: "
208
+ f"count={pattern_dict['occurrence_count']}"
209
+ )
210
+ return True
211
+
212
+ return False
213
+
214
+ def remove_pattern(self, pattern_id: str) -> bool:
215
+ """
216
+ Remove a pattern by ID.
217
+
218
+ Args:
219
+ pattern_id: Pattern ID to remove
220
+
221
+ Returns:
222
+ True if removed, False if not found
223
+ """
224
+ with self._lock:
225
+ data = self._read_atomic()
226
+
227
+ # Search and remove from both lists
228
+ for patterns_list in [data["patterns"], data["good_patterns"]]:
229
+ for i, pattern_dict in enumerate(patterns_list):
230
+ if pattern_dict["id"] == pattern_id:
231
+ patterns_list.pop(i)
232
+ self._write_atomic(data)
233
+ logger.debug(f"Removed pattern: {pattern_id}")
234
+ return True
235
+
236
+ return False
237
+
238
+ def query_patterns(
239
+ self,
240
+ pattern_type: str | None = None,
241
+ min_occurrences: int = 0,
242
+ ) -> list[PatternRecord]:
243
+ """
244
+ Query patterns with filters.
245
+
246
+ Args:
247
+ pattern_type: Filter by type ("anti-pattern", "good-pattern", None for all)
248
+ min_occurrences: Minimum occurrence count to include
249
+
250
+ Returns:
251
+ List of matching PatternRecord objects
252
+ """
253
+ with self._lock:
254
+ data = self._read_atomic()
255
+
256
+ patterns = []
257
+
258
+ # Add anti-patterns if requested
259
+ if pattern_type is None or pattern_type == "anti-pattern":
260
+ for pattern_dict in data["patterns"]:
261
+ if pattern_dict["occurrence_count"] >= min_occurrences:
262
+ patterns.append(self._dict_to_pattern(pattern_dict))
263
+
264
+ # Add good patterns if requested
265
+ if pattern_type is None or pattern_type == "good-pattern":
266
+ for pattern_dict in data["good_patterns"]:
267
+ if pattern_dict["occurrence_count"] >= min_occurrences:
268
+ patterns.append(self._dict_to_pattern(pattern_dict))
269
+
270
+ return patterns
271
+
272
+ def get_patterns_by_session(self, session_id: str) -> list[PatternRecord]:
273
+ """
274
+ Get all patterns detected in a specific session.
275
+
276
+ Args:
277
+ session_id: Session ID to query
278
+
279
+ Returns:
280
+ List of patterns detected in that session
281
+ """
282
+ with self._lock:
283
+ data = self._read_atomic()
284
+
285
+ patterns = []
286
+ for pattern_dict in data["patterns"] + data["good_patterns"]:
287
+ if session_id in pattern_dict["sessions_affected"]:
288
+ patterns.append(self._dict_to_pattern(pattern_dict))
289
+
290
+ return patterns
291
+
292
+ def export_analytics(self) -> dict:
293
+ """
294
+ Export pattern analytics for reporting.
295
+
296
+ Returns:
297
+ Dictionary with aggregated statistics
298
+ """
299
+ with self._lock:
300
+ data = self._read_atomic()
301
+
302
+ anti_patterns = data["patterns"]
303
+ good_patterns = data["good_patterns"]
304
+
305
+ total_anti = len(anti_patterns)
306
+ total_good = len(good_patterns)
307
+ total_occurrences = sum(p["occurrence_count"] for p in anti_patterns)
308
+
309
+ return {
310
+ "timestamp": datetime.utcnow().isoformat(),
311
+ "summary": {
312
+ "total_anti_patterns": total_anti,
313
+ "total_good_patterns": total_good,
314
+ "total_detections": total_occurrences,
315
+ },
316
+ "anti_patterns": [
317
+ {
318
+ "id": p["id"],
319
+ "name": p["name"],
320
+ "occurrences": p["occurrence_count"],
321
+ "sessions_affected": len(p["sessions_affected"]),
322
+ }
323
+ for p in sorted(
324
+ anti_patterns,
325
+ key=lambda x: -x["occurrence_count"],
326
+ )
327
+ ],
328
+ "good_patterns": [
329
+ {
330
+ "id": p["id"],
331
+ "name": p["name"],
332
+ "occurrences": p["occurrence_count"],
333
+ "sessions_affected": len(p["sessions_affected"]),
334
+ }
335
+ for p in sorted(
336
+ good_patterns,
337
+ key=lambda x: -x["occurrence_count"],
338
+ )
339
+ ],
340
+ }
341
+
342
+ def clear_all(self) -> None:
343
+ """Clear all patterns from storage. Use with caution."""
344
+ with self._lock:
345
+ self._write_atomic({"patterns": [], "good_patterns": []})
346
+ logger.warning("Cleared all patterns from storage")
347
+
348
+ # Private methods
349
+
350
+ def _read_atomic(self) -> dict[str, list]:
351
+ """
352
+ Read patterns file atomically.
353
+
354
+ Returns:
355
+ Dictionary with "patterns" and "good_patterns" keys
356
+ """
357
+ try:
358
+ if not self.patterns_file.exists():
359
+ return {"patterns": [], "good_patterns": []}
360
+
361
+ content = self.patterns_file.read_text(encoding="utf-8")
362
+ data: Any = json.loads(content)
363
+
364
+ # Validate structure
365
+ if "patterns" not in data:
366
+ data["patterns"] = []
367
+ if "good_patterns" not in data:
368
+ data["good_patterns"] = []
369
+
370
+ return cast(dict[str, list], data)
371
+ except (OSError, json.JSONDecodeError) as e:
372
+ logger.error(f"Error reading patterns file: {e}")
373
+ return {"patterns": [], "good_patterns": []}
374
+
375
+ def _write_atomic(self, data: dict[str, list]) -> None:
376
+ """
377
+ Write patterns file atomically using temp file + rename.
378
+
379
+ Args:
380
+ data: Dictionary with "patterns" and "good_patterns" keys
381
+ """
382
+ try:
383
+ # Write to temporary file first
384
+ temp_file = self.patterns_file.with_suffix(".json.tmp")
385
+
386
+ with open(temp_file, "w", encoding="utf-8") as f:
387
+ json.dump(data, f, indent=2, ensure_ascii=False)
388
+
389
+ # Atomic rename
390
+ temp_file.replace(self.patterns_file)
391
+ logger.debug(f"Wrote patterns to {self.patterns_file}")
392
+
393
+ except OSError as e:
394
+ logger.error(f"Error writing patterns file: {e}")
395
+ raise
396
+
397
+ @staticmethod
398
+ def _pattern_to_dict(pattern: PatternRecord) -> dict:
399
+ """Convert PatternRecord to dictionary for JSON."""
400
+ return {
401
+ "id": pattern.id,
402
+ "pattern_type": pattern.pattern_type,
403
+ "name": pattern.name,
404
+ "description": pattern.description,
405
+ "trigger_conditions": pattern.trigger_conditions,
406
+ "example_sequence": pattern.example_sequence,
407
+ "occurrence_count": pattern.occurrence_count,
408
+ "sessions_affected": pattern.sessions_affected,
409
+ "correct_approach": pattern.correct_approach,
410
+ "delegation_suggestion": pattern.delegation_suggestion,
411
+ }
412
+
413
+ @staticmethod
414
+ def _dict_to_pattern(data: dict) -> PatternRecord:
415
+ """Convert dictionary (from JSON) to PatternRecord."""
416
+ return PatternRecord(
417
+ id=data["id"],
418
+ pattern_type=data["pattern_type"],
419
+ name=data["name"],
420
+ description=data["description"],
421
+ trigger_conditions=data["trigger_conditions"],
422
+ example_sequence=data["example_sequence"],
423
+ occurrence_count=data.get("occurrence_count", 0),
424
+ sessions_affected=data.get("sessions_affected", []),
425
+ correct_approach=data.get("correct_approach"),
426
+ delegation_suggestion=data.get("delegation_suggestion"),
427
+ )