htmlgraph 0.9.3__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 (331) 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 +173 -17
  5. htmlgraph/__init__.pyi +123 -0
  6. htmlgraph/agent_detection.py +127 -0
  7. htmlgraph/agent_registry.py +45 -30
  8. htmlgraph/agents.py +160 -107
  9. htmlgraph/analytics/__init__.py +9 -2
  10. htmlgraph/analytics/cli.py +190 -51
  11. htmlgraph/analytics/cost_analyzer.py +391 -0
  12. htmlgraph/analytics/cost_monitor.py +664 -0
  13. htmlgraph/analytics/cost_reporter.py +675 -0
  14. htmlgraph/analytics/cross_session.py +617 -0
  15. htmlgraph/analytics/dependency.py +192 -100
  16. htmlgraph/analytics/pattern_learning.py +771 -0
  17. htmlgraph/analytics/session_graph.py +707 -0
  18. htmlgraph/analytics/strategic/__init__.py +80 -0
  19. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  20. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  21. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  22. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  23. htmlgraph/analytics/work_type.py +190 -14
  24. htmlgraph/analytics_index.py +135 -51
  25. htmlgraph/api/__init__.py +3 -0
  26. htmlgraph/api/cost_alerts_websocket.py +416 -0
  27. htmlgraph/api/main.py +2498 -0
  28. htmlgraph/api/static/htmx.min.js +1 -0
  29. htmlgraph/api/static/style-redesign.css +1344 -0
  30. htmlgraph/api/static/style.css +1079 -0
  31. htmlgraph/api/templates/dashboard-redesign.html +1366 -0
  32. htmlgraph/api/templates/dashboard.html +794 -0
  33. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  34. htmlgraph/api/templates/partials/activity-feed.html +1100 -0
  35. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  36. htmlgraph/api/templates/partials/agents.html +317 -0
  37. htmlgraph/api/templates/partials/event-traces.html +373 -0
  38. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  39. htmlgraph/api/templates/partials/features.html +578 -0
  40. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  41. htmlgraph/api/templates/partials/metrics.html +346 -0
  42. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  43. htmlgraph/api/templates/partials/orchestration.html +198 -0
  44. htmlgraph/api/templates/partials/spawners.html +375 -0
  45. htmlgraph/api/templates/partials/work-items.html +613 -0
  46. htmlgraph/api/websocket.py +538 -0
  47. htmlgraph/archive/__init__.py +24 -0
  48. htmlgraph/archive/bloom.py +234 -0
  49. htmlgraph/archive/fts.py +297 -0
  50. htmlgraph/archive/manager.py +583 -0
  51. htmlgraph/archive/search.py +244 -0
  52. htmlgraph/atomic_ops.py +560 -0
  53. htmlgraph/attribute_index.py +208 -0
  54. htmlgraph/bounded_paths.py +539 -0
  55. htmlgraph/builders/__init__.py +14 -0
  56. htmlgraph/builders/base.py +118 -29
  57. htmlgraph/builders/bug.py +150 -0
  58. htmlgraph/builders/chore.py +119 -0
  59. htmlgraph/builders/epic.py +150 -0
  60. htmlgraph/builders/feature.py +31 -6
  61. htmlgraph/builders/insight.py +195 -0
  62. htmlgraph/builders/metric.py +217 -0
  63. htmlgraph/builders/pattern.py +202 -0
  64. htmlgraph/builders/phase.py +162 -0
  65. htmlgraph/builders/spike.py +52 -19
  66. htmlgraph/builders/track.py +148 -72
  67. htmlgraph/cigs/__init__.py +81 -0
  68. htmlgraph/cigs/autonomy.py +385 -0
  69. htmlgraph/cigs/cost.py +475 -0
  70. htmlgraph/cigs/messages_basic.py +472 -0
  71. htmlgraph/cigs/messaging.py +365 -0
  72. htmlgraph/cigs/models.py +771 -0
  73. htmlgraph/cigs/pattern_storage.py +427 -0
  74. htmlgraph/cigs/patterns.py +503 -0
  75. htmlgraph/cigs/posttool_analyzer.py +234 -0
  76. htmlgraph/cigs/reporter.py +818 -0
  77. htmlgraph/cigs/tracker.py +317 -0
  78. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  79. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  80. htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
  81. htmlgraph/cli/__init__.py +42 -0
  82. htmlgraph/cli/__main__.py +6 -0
  83. htmlgraph/cli/analytics.py +1424 -0
  84. htmlgraph/cli/base.py +685 -0
  85. htmlgraph/cli/constants.py +206 -0
  86. htmlgraph/cli/core.py +954 -0
  87. htmlgraph/cli/main.py +147 -0
  88. htmlgraph/cli/models.py +475 -0
  89. htmlgraph/cli/templates/__init__.py +1 -0
  90. htmlgraph/cli/templates/cost_dashboard.py +399 -0
  91. htmlgraph/cli/work/__init__.py +239 -0
  92. htmlgraph/cli/work/browse.py +115 -0
  93. htmlgraph/cli/work/features.py +568 -0
  94. htmlgraph/cli/work/orchestration.py +676 -0
  95. htmlgraph/cli/work/report.py +728 -0
  96. htmlgraph/cli/work/sessions.py +466 -0
  97. htmlgraph/cli/work/snapshot.py +559 -0
  98. htmlgraph/cli/work/tracks.py +486 -0
  99. htmlgraph/cli_commands/__init__.py +1 -0
  100. htmlgraph/cli_commands/feature.py +195 -0
  101. htmlgraph/cli_framework.py +115 -0
  102. htmlgraph/collections/__init__.py +18 -0
  103. htmlgraph/collections/base.py +415 -98
  104. htmlgraph/collections/bug.py +53 -0
  105. htmlgraph/collections/chore.py +53 -0
  106. htmlgraph/collections/epic.py +53 -0
  107. htmlgraph/collections/feature.py +12 -26
  108. htmlgraph/collections/insight.py +100 -0
  109. htmlgraph/collections/metric.py +92 -0
  110. htmlgraph/collections/pattern.py +97 -0
  111. htmlgraph/collections/phase.py +53 -0
  112. htmlgraph/collections/session.py +194 -0
  113. htmlgraph/collections/spike.py +56 -16
  114. htmlgraph/collections/task_delegation.py +241 -0
  115. htmlgraph/collections/todo.py +511 -0
  116. htmlgraph/collections/traces.py +487 -0
  117. htmlgraph/config/cost_models.json +56 -0
  118. htmlgraph/config.py +190 -0
  119. htmlgraph/context_analytics.py +344 -0
  120. htmlgraph/converter.py +216 -28
  121. htmlgraph/cost_analysis/__init__.py +5 -0
  122. htmlgraph/cost_analysis/analyzer.py +438 -0
  123. htmlgraph/dashboard.html +2406 -307
  124. htmlgraph/dashboard.html.backup +6592 -0
  125. htmlgraph/dashboard.html.bak +7181 -0
  126. htmlgraph/dashboard.html.bak2 +7231 -0
  127. htmlgraph/dashboard.html.bak3 +7232 -0
  128. htmlgraph/db/__init__.py +38 -0
  129. htmlgraph/db/queries.py +790 -0
  130. htmlgraph/db/schema.py +1788 -0
  131. htmlgraph/decorators.py +317 -0
  132. htmlgraph/dependency_models.py +19 -2
  133. htmlgraph/deploy.py +142 -125
  134. htmlgraph/deployment_models.py +474 -0
  135. htmlgraph/docs/API_REFERENCE.md +841 -0
  136. htmlgraph/docs/HTTP_API.md +750 -0
  137. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  138. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
  139. htmlgraph/docs/README.md +532 -0
  140. htmlgraph/docs/__init__.py +77 -0
  141. htmlgraph/docs/docs_version.py +55 -0
  142. htmlgraph/docs/metadata.py +93 -0
  143. htmlgraph/docs/migrations.py +232 -0
  144. htmlgraph/docs/template_engine.py +143 -0
  145. htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  146. htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  147. htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  148. htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  149. htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  150. htmlgraph/docs/version_check.py +163 -0
  151. htmlgraph/edge_index.py +182 -27
  152. htmlgraph/error_handler.py +544 -0
  153. htmlgraph/event_log.py +100 -52
  154. htmlgraph/event_migration.py +13 -4
  155. htmlgraph/exceptions.py +49 -0
  156. htmlgraph/file_watcher.py +101 -28
  157. htmlgraph/find_api.py +75 -63
  158. htmlgraph/git_events.py +145 -63
  159. htmlgraph/graph.py +1122 -106
  160. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  161. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  162. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  163. htmlgraph/hooks/__init__.py +45 -0
  164. htmlgraph/hooks/bootstrap.py +169 -0
  165. htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
  166. htmlgraph/hooks/concurrent_sessions.py +208 -0
  167. htmlgraph/hooks/context.py +350 -0
  168. htmlgraph/hooks/drift_handler.py +525 -0
  169. htmlgraph/hooks/event_tracker.py +1314 -0
  170. htmlgraph/hooks/git_commands.py +175 -0
  171. htmlgraph/hooks/hooks-config.example.json +12 -0
  172. htmlgraph/hooks/installer.py +343 -0
  173. htmlgraph/hooks/orchestrator.py +674 -0
  174. htmlgraph/hooks/orchestrator_reflector.py +223 -0
  175. htmlgraph/hooks/post-checkout.sh +28 -0
  176. htmlgraph/hooks/post-commit.sh +24 -0
  177. htmlgraph/hooks/post-merge.sh +26 -0
  178. htmlgraph/hooks/post_tool_use_failure.py +273 -0
  179. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  180. htmlgraph/hooks/posttooluse.py +408 -0
  181. htmlgraph/hooks/pre-commit.sh +94 -0
  182. htmlgraph/hooks/pre-push.sh +28 -0
  183. htmlgraph/hooks/pretooluse.py +819 -0
  184. htmlgraph/hooks/prompt_analyzer.py +637 -0
  185. htmlgraph/hooks/session_handler.py +668 -0
  186. htmlgraph/hooks/session_summary.py +395 -0
  187. htmlgraph/hooks/state_manager.py +504 -0
  188. htmlgraph/hooks/subagent_detection.py +202 -0
  189. htmlgraph/hooks/subagent_stop.py +369 -0
  190. htmlgraph/hooks/task_enforcer.py +255 -0
  191. htmlgraph/hooks/task_validator.py +177 -0
  192. htmlgraph/hooks/validator.py +628 -0
  193. htmlgraph/ids.py +41 -27
  194. htmlgraph/index.d.ts +286 -0
  195. htmlgraph/learning.py +767 -0
  196. htmlgraph/mcp_server.py +69 -23
  197. htmlgraph/models.py +1586 -87
  198. htmlgraph/operations/README.md +62 -0
  199. htmlgraph/operations/__init__.py +79 -0
  200. htmlgraph/operations/analytics.py +339 -0
  201. htmlgraph/operations/bootstrap.py +289 -0
  202. htmlgraph/operations/events.py +244 -0
  203. htmlgraph/operations/fastapi_server.py +231 -0
  204. htmlgraph/operations/hooks.py +350 -0
  205. htmlgraph/operations/initialization.py +597 -0
  206. htmlgraph/operations/initialization.py.backup +228 -0
  207. htmlgraph/operations/server.py +303 -0
  208. htmlgraph/orchestration/__init__.py +58 -0
  209. htmlgraph/orchestration/claude_launcher.py +179 -0
  210. htmlgraph/orchestration/command_builder.py +72 -0
  211. htmlgraph/orchestration/headless_spawner.py +281 -0
  212. htmlgraph/orchestration/live_events.py +377 -0
  213. htmlgraph/orchestration/model_selection.py +327 -0
  214. htmlgraph/orchestration/plugin_manager.py +140 -0
  215. htmlgraph/orchestration/prompts.py +137 -0
  216. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  217. htmlgraph/orchestration/spawners/__init__.py +16 -0
  218. htmlgraph/orchestration/spawners/base.py +194 -0
  219. htmlgraph/orchestration/spawners/claude.py +173 -0
  220. htmlgraph/orchestration/spawners/codex.py +435 -0
  221. htmlgraph/orchestration/spawners/copilot.py +294 -0
  222. htmlgraph/orchestration/spawners/gemini.py +471 -0
  223. htmlgraph/orchestration/subprocess_runner.py +36 -0
  224. htmlgraph/orchestration/task_coordination.py +343 -0
  225. htmlgraph/orchestration.md +563 -0
  226. htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
  227. htmlgraph/orchestrator.py +669 -0
  228. htmlgraph/orchestrator_config.py +357 -0
  229. htmlgraph/orchestrator_mode.py +328 -0
  230. htmlgraph/orchestrator_validator.py +133 -0
  231. htmlgraph/parallel.py +646 -0
  232. htmlgraph/parser.py +160 -35
  233. htmlgraph/path_query.py +608 -0
  234. htmlgraph/pattern_matcher.py +636 -0
  235. htmlgraph/planning.py +147 -52
  236. htmlgraph/pydantic_models.py +476 -0
  237. htmlgraph/quality_gates.py +350 -0
  238. htmlgraph/query_builder.py +109 -72
  239. htmlgraph/query_composer.py +509 -0
  240. htmlgraph/reflection.py +443 -0
  241. htmlgraph/refs.py +344 -0
  242. htmlgraph/repo_hash.py +512 -0
  243. htmlgraph/repositories/__init__.py +292 -0
  244. htmlgraph/repositories/analytics_repository.py +455 -0
  245. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  246. htmlgraph/repositories/feature_repository.py +581 -0
  247. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  248. htmlgraph/repositories/feature_repository_memory.py +607 -0
  249. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  250. htmlgraph/repositories/filter_service.py +620 -0
  251. htmlgraph/repositories/filter_service_standard.py +445 -0
  252. htmlgraph/repositories/shared_cache.py +621 -0
  253. htmlgraph/repositories/shared_cache_memory.py +395 -0
  254. htmlgraph/repositories/track_repository.py +552 -0
  255. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  256. htmlgraph/repositories/track_repository_memory.py +508 -0
  257. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  258. htmlgraph/routing.py +8 -19
  259. htmlgraph/scripts/deploy.py +1 -2
  260. htmlgraph/sdk/__init__.py +398 -0
  261. htmlgraph/sdk/__init__.pyi +14 -0
  262. htmlgraph/sdk/analytics/__init__.py +19 -0
  263. htmlgraph/sdk/analytics/engine.py +155 -0
  264. htmlgraph/sdk/analytics/helpers.py +178 -0
  265. htmlgraph/sdk/analytics/registry.py +109 -0
  266. htmlgraph/sdk/base.py +484 -0
  267. htmlgraph/sdk/constants.py +216 -0
  268. htmlgraph/sdk/core.pyi +308 -0
  269. htmlgraph/sdk/discovery.py +120 -0
  270. htmlgraph/sdk/help/__init__.py +12 -0
  271. htmlgraph/sdk/help/mixin.py +699 -0
  272. htmlgraph/sdk/mixins/__init__.py +15 -0
  273. htmlgraph/sdk/mixins/attribution.py +113 -0
  274. htmlgraph/sdk/mixins/mixin.py +410 -0
  275. htmlgraph/sdk/operations/__init__.py +12 -0
  276. htmlgraph/sdk/operations/mixin.py +427 -0
  277. htmlgraph/sdk/orchestration/__init__.py +17 -0
  278. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  279. htmlgraph/sdk/orchestration/spawner.py +204 -0
  280. htmlgraph/sdk/planning/__init__.py +19 -0
  281. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  282. htmlgraph/sdk/planning/mixin.py +211 -0
  283. htmlgraph/sdk/planning/parallel.py +186 -0
  284. htmlgraph/sdk/planning/queue.py +210 -0
  285. htmlgraph/sdk/planning/recommendations.py +87 -0
  286. htmlgraph/sdk/planning/smart_planning.py +319 -0
  287. htmlgraph/sdk/session/__init__.py +19 -0
  288. htmlgraph/sdk/session/continuity.py +57 -0
  289. htmlgraph/sdk/session/handoff.py +110 -0
  290. htmlgraph/sdk/session/info.py +309 -0
  291. htmlgraph/sdk/session/manager.py +103 -0
  292. htmlgraph/sdk/strategic/__init__.py +26 -0
  293. htmlgraph/sdk/strategic/mixin.py +563 -0
  294. htmlgraph/server.py +685 -180
  295. htmlgraph/services/__init__.py +10 -0
  296. htmlgraph/services/claiming.py +199 -0
  297. htmlgraph/session_hooks.py +300 -0
  298. htmlgraph/session_manager.py +1392 -175
  299. htmlgraph/session_registry.py +587 -0
  300. htmlgraph/session_state.py +436 -0
  301. htmlgraph/session_warning.py +201 -0
  302. htmlgraph/sessions/__init__.py +23 -0
  303. htmlgraph/sessions/handoff.py +756 -0
  304. htmlgraph/setup.py +34 -17
  305. htmlgraph/spike_index.py +143 -0
  306. htmlgraph/sync_docs.py +12 -15
  307. htmlgraph/system_prompts.py +450 -0
  308. htmlgraph/templates/AGENTS.md.template +366 -0
  309. htmlgraph/templates/CLAUDE.md.template +97 -0
  310. htmlgraph/templates/GEMINI.md.template +87 -0
  311. htmlgraph/templates/orchestration-view.html +350 -0
  312. htmlgraph/track_builder.py +146 -15
  313. htmlgraph/track_manager.py +69 -21
  314. htmlgraph/transcript.py +890 -0
  315. htmlgraph/transcript_analytics.py +699 -0
  316. htmlgraph/types.py +323 -0
  317. htmlgraph/validation.py +115 -0
  318. htmlgraph/watch.py +8 -5
  319. htmlgraph/work_type_utils.py +3 -2
  320. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
  321. htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
  322. htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
  323. htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
  324. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
  325. htmlgraph-0.27.5.dist-info/RECORD +337 -0
  326. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
  327. htmlgraph/cli.py +0 -2688
  328. htmlgraph/sdk.py +0 -709
  329. htmlgraph-0.9.3.dist-info/RECORD +0 -61
  330. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
  331. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
@@ -0,0 +1,503 @@
1
+ """
2
+ Pattern Detection for CIGS - Identify behavioral anti-patterns from tool usage history.
3
+
4
+ Detects anti-patterns that violate HtmlGraph delegation principles:
5
+ 1. exploration_sequence: 3+ Read/Grep/Glob in sequence (should use spawn_gemini)
6
+ 2. edit_without_test: Edit operations without subsequent test delegation
7
+ 3. direct_git_commit: git commit via Bash instead of spawn_copilot
8
+ 4. repeated_read_same_file: Same file read multiple times in short window
9
+
10
+ Reference: .htmlgraph/spikes/computational-imperative-guidance-system-design.md (Part 5, Section 5.3)
11
+ """
12
+
13
+ from dataclasses import dataclass, field
14
+ from typing import Any
15
+
16
+ from .models import PatternRecord
17
+
18
+ # Define exploration and implementation tool categories
19
+ EXPLORATION_TOOLS = {"Read", "Grep", "Glob"}
20
+ IMPLEMENTATION_TOOLS = {"Edit", "Write", "NotebookEdit"}
21
+ TESTING_TOOLS = {"Bash"} # pytest, npm test, yarn test
22
+
23
+
24
+ @dataclass
25
+ class DetectionResult:
26
+ """Result of pattern detection."""
27
+
28
+ pattern_type: str # "anti-pattern" or "good-pattern"
29
+ name: str
30
+ description: str
31
+ detected: bool
32
+ trigger_conditions: list[str] = field(default_factory=list)
33
+ example_sequence: list[str] = field(default_factory=list)
34
+ remediation: str | None = None
35
+ confidence: float = 0.0 # 0.0 to 1.0
36
+
37
+
38
+ class PatternDetector:
39
+ """
40
+ Detect behavioral patterns from tool usage history.
41
+
42
+ Uses a sliding window approach to identify anti-patterns that violate
43
+ HtmlGraph delegation principles.
44
+ """
45
+
46
+ # Window size for analysis (last N tool calls)
47
+ DEFAULT_WINDOW_SIZE = 10
48
+
49
+ def __init__(self, window_size: int = DEFAULT_WINDOW_SIZE):
50
+ """Initialize pattern detector.
51
+
52
+ Args:
53
+ window_size: Number of recent tool calls to analyze
54
+ """
55
+ self.window_size = window_size
56
+ self._anti_patterns = self._init_anti_patterns()
57
+
58
+ def _init_anti_patterns(self) -> dict[str, Any]:
59
+ """Initialize anti-pattern definitions."""
60
+ return {
61
+ "exploration_sequence": {
62
+ "description": "Multiple exploration tools in sequence",
63
+ "trigger_conditions": [
64
+ "3+ exploration tools (Read/Grep/Glob) in last N calls",
65
+ "No delegation (spawn_gemini/Task) between them",
66
+ ],
67
+ "remediation": "Use spawn_gemini() for comprehensive exploration",
68
+ "min_occurrences": 3,
69
+ "detector": self._detect_exploration_sequence,
70
+ },
71
+ "edit_without_test": {
72
+ "description": "Edit operations without subsequent test delegation",
73
+ "trigger_conditions": [
74
+ "Edit/Write operation detected",
75
+ "No Task() with 'test' in prompt within next 3 calls",
76
+ ],
77
+ "remediation": "Include testing in Task() prompt for code changes",
78
+ "detector": self._detect_edit_without_test,
79
+ },
80
+ "direct_git_commit": {
81
+ "description": "Git commit executed directly instead of via spawn_copilot",
82
+ "trigger_conditions": [
83
+ "Bash tool with 'git commit' command",
84
+ "No spawn_copilot() delegation",
85
+ ],
86
+ "remediation": "Use spawn_copilot() for git operations",
87
+ "detector": self._detect_direct_git_commit,
88
+ },
89
+ "repeated_read_same_file": {
90
+ "description": "Same file read multiple times in short window",
91
+ "trigger_conditions": [
92
+ "Same file_path in 2+ Read operations",
93
+ "Within last 10 tool calls",
94
+ "No delegation between reads",
95
+ ],
96
+ "remediation": "Delegate to Explorer (spawn_gemini) for comprehensive file analysis",
97
+ "detector": self._detect_repeated_read_same_file,
98
+ },
99
+ }
100
+
101
+ def detect_all_patterns(self, history: list[dict[str, Any]]) -> list[PatternRecord]:
102
+ """
103
+ Detect all anti-patterns in tool usage history.
104
+
105
+ Args:
106
+ history: List of tool call records with structure:
107
+ {
108
+ "tool": str,
109
+ "command": str (for Bash),
110
+ "file_path": str (for Read),
111
+ "prompt": str (for Task),
112
+ "timestamp": datetime,
113
+ ...
114
+ }
115
+
116
+ Returns:
117
+ List of detected PatternRecord instances
118
+ """
119
+ detected = []
120
+ history_window = (
121
+ history[-self.window_size :] if len(history) > self.window_size else history
122
+ )
123
+
124
+ for pattern_name, pattern_def in self._anti_patterns.items():
125
+ detector_func: Any = pattern_def["detector"]
126
+ result = detector_func(history_window)
127
+ if result.detected:
128
+ detected.append(
129
+ PatternRecord(
130
+ id=f"pattern-{pattern_name}",
131
+ pattern_type="anti-pattern",
132
+ name=pattern_name,
133
+ description=result.description,
134
+ trigger_conditions=result.trigger_conditions,
135
+ example_sequence=result.example_sequence,
136
+ occurrence_count=1,
137
+ sessions_affected=[],
138
+ correct_approach=result.remediation,
139
+ delegation_suggestion=self._get_delegation_suggestion(
140
+ pattern_name
141
+ ),
142
+ )
143
+ )
144
+
145
+ return detected
146
+
147
+ def detect_pattern(
148
+ self, pattern_name: str, history: list[dict[str, Any]]
149
+ ) -> DetectionResult:
150
+ """
151
+ Detect a specific anti-pattern.
152
+
153
+ Args:
154
+ pattern_name: Name of pattern to detect
155
+ history: Tool usage history
156
+
157
+ Returns:
158
+ DetectionResult with detection details
159
+ """
160
+ if pattern_name not in self._anti_patterns:
161
+ raise ValueError(f"Unknown pattern: {pattern_name}")
162
+
163
+ pattern_def = self._anti_patterns[pattern_name]
164
+ history_window = (
165
+ history[-self.window_size :] if len(history) > self.window_size else history
166
+ )
167
+
168
+ detector_func: Any = pattern_def["detector"]
169
+ return detector_func(history_window) # type: ignore[no-any-return]
170
+
171
+ def _detect_exploration_sequence(
172
+ self, history: list[dict[str, Any]]
173
+ ) -> DetectionResult:
174
+ """
175
+ Detect exploration_sequence anti-pattern.
176
+
177
+ Triggers when 3+ exploration tools (Read/Grep/Glob) appear in sequence
178
+ without delegation to spawn_gemini() or Task().
179
+ """
180
+ pattern_name = "exploration_sequence"
181
+ pattern_def = self._anti_patterns[pattern_name]
182
+
183
+ if not history:
184
+ return DetectionResult(
185
+ pattern_type="anti-pattern",
186
+ name=pattern_name,
187
+ description=pattern_def["description"],
188
+ detected=False,
189
+ )
190
+
191
+ # Count exploration tools in history
192
+ exploration_count = 0
193
+ exploration_tools_found = []
194
+
195
+ for call in history:
196
+ tool = call.get("tool", "")
197
+
198
+ if tool in EXPLORATION_TOOLS:
199
+ exploration_count += 1
200
+ exploration_tools_found.append(tool)
201
+ elif tool in {"Task", "Bash"} and "spawn_gemini" in call.get(
202
+ "prompt", ""
203
+ ) + call.get("command", ""):
204
+ # Delegation found, reset
205
+ exploration_count = 0
206
+ exploration_tools_found = []
207
+ elif tool not in EXPLORATION_TOOLS:
208
+ # Non-exploration tool breaks the sequence (unless it's delegation)
209
+ if tool not in {"Task"} or not any(
210
+ d in call.get("prompt", "") for d in ["spawn_", "gemini"]
211
+ ):
212
+ pass # Don't reset, continue counting
213
+
214
+ detected = exploration_count >= 3
215
+ confidence = min(1.0, exploration_count / 3) if detected else 0.0
216
+
217
+ return DetectionResult(
218
+ pattern_type="anti-pattern",
219
+ name=pattern_name,
220
+ description=pattern_def["description"],
221
+ detected=detected,
222
+ trigger_conditions=pattern_def["trigger_conditions"],
223
+ example_sequence=exploration_tools_found[-3:]
224
+ if len(exploration_tools_found) >= 3
225
+ else [],
226
+ remediation=pattern_def["remediation"],
227
+ confidence=confidence,
228
+ )
229
+
230
+ def _detect_edit_without_test(
231
+ self, history: list[dict[str, Any]]
232
+ ) -> DetectionResult:
233
+ """
234
+ Detect edit_without_test anti-pattern.
235
+
236
+ Triggers when Edit/Write operations exist without subsequent Task()
237
+ delegation containing test keywords within next 3 calls.
238
+ """
239
+ pattern_name = "edit_without_test"
240
+ pattern_def = self._anti_patterns[pattern_name]
241
+
242
+ if not history:
243
+ return DetectionResult(
244
+ pattern_type="anti-pattern",
245
+ name=pattern_name,
246
+ description=pattern_def["description"],
247
+ detected=False,
248
+ )
249
+
250
+ test_keywords = {
251
+ "test",
252
+ "pytest",
253
+ "unittest",
254
+ "vitest",
255
+ "jest",
256
+ "mocha",
257
+ "assert",
258
+ "verify",
259
+ }
260
+
261
+ # Check for Edit/Write without subsequent test delegation
262
+ for i, call in enumerate(history):
263
+ tool = call.get("tool", "")
264
+
265
+ if tool in IMPLEMENTATION_TOOLS:
266
+ # Found an edit, check next 3 calls for test delegation
267
+ test_found = False
268
+ remaining_calls = history[i + 1 : i + 4]
269
+
270
+ for next_call in remaining_calls:
271
+ next_tool = next_call.get("tool", "")
272
+ prompt = next_call.get("prompt", "").lower()
273
+
274
+ # Check if this is a test delegation
275
+ if next_tool == "Task" and any(
276
+ kw in prompt for kw in test_keywords
277
+ ):
278
+ test_found = True
279
+ break
280
+
281
+ if not test_found and len(history) > i + 1:
282
+ # Edit found without subsequent test delegation
283
+ example_seq = [call.get("tool", "")] + [
284
+ c.get("tool", "") for c in remaining_calls[:3]
285
+ ]
286
+ return DetectionResult(
287
+ pattern_type="anti-pattern",
288
+ name=pattern_name,
289
+ description=pattern_def["description"],
290
+ detected=True,
291
+ trigger_conditions=pattern_def["trigger_conditions"],
292
+ example_sequence=example_seq,
293
+ remediation=pattern_def["remediation"],
294
+ confidence=0.8,
295
+ )
296
+
297
+ return DetectionResult(
298
+ pattern_type="anti-pattern",
299
+ name=pattern_name,
300
+ description=pattern_def["description"],
301
+ detected=False,
302
+ )
303
+
304
+ def _detect_direct_git_commit(
305
+ self, history: list[dict[str, Any]]
306
+ ) -> DetectionResult:
307
+ """
308
+ Detect direct_git_commit anti-pattern.
309
+
310
+ Triggers when git commit is executed via Bash directly instead of
311
+ delegating to spawn_copilot().
312
+ """
313
+ pattern_name = "direct_git_commit"
314
+ pattern_def = self._anti_patterns[pattern_name]
315
+
316
+ if not history:
317
+ return DetectionResult(
318
+ pattern_type="anti-pattern",
319
+ name=pattern_name,
320
+ description=pattern_def["description"],
321
+ detected=False,
322
+ )
323
+
324
+ git_commit_commands = [
325
+ "git commit",
326
+ "git push",
327
+ "git add",
328
+ "git merge",
329
+ "git rebase",
330
+ ]
331
+
332
+ for i, call in enumerate(history):
333
+ tool = call.get("tool", "")
334
+
335
+ if tool == "Bash":
336
+ command = call.get("command", "").lower()
337
+
338
+ # Check if this is a git commit operation
339
+ is_git_commit = any(cmd in command for cmd in git_commit_commands)
340
+
341
+ if is_git_commit:
342
+ # Check if this was preceded by spawn_copilot delegation
343
+ was_delegated = False
344
+ if i > 0:
345
+ prev_call = history[i - 1]
346
+ if prev_call.get("tool", "") == "Task":
347
+ prompt = prev_call.get("prompt", "").lower()
348
+ if "copilot" in prompt or "git" in prompt:
349
+ was_delegated = True
350
+
351
+ if not was_delegated:
352
+ return DetectionResult(
353
+ pattern_type="anti-pattern",
354
+ name=pattern_name,
355
+ description=pattern_def["description"],
356
+ detected=True,
357
+ trigger_conditions=pattern_def["trigger_conditions"],
358
+ example_sequence=["Bash", command.split()[0:2]],
359
+ remediation=pattern_def["remediation"],
360
+ confidence=0.95,
361
+ )
362
+
363
+ return DetectionResult(
364
+ pattern_type="anti-pattern",
365
+ name=pattern_name,
366
+ description=pattern_def["description"],
367
+ detected=False,
368
+ )
369
+
370
+ def _detect_repeated_read_same_file(
371
+ self, history: list[dict[str, Any]]
372
+ ) -> DetectionResult:
373
+ """
374
+ Detect repeated_read_same_file anti-pattern.
375
+
376
+ Triggers when the same file is read multiple times within the window
377
+ without delegation to explore comprehensively.
378
+ """
379
+ pattern_name = "repeated_read_same_file"
380
+ pattern_def = self._anti_patterns[pattern_name]
381
+
382
+ if not history:
383
+ return DetectionResult(
384
+ pattern_type="anti-pattern",
385
+ name=pattern_name,
386
+ description=pattern_def["description"],
387
+ detected=False,
388
+ )
389
+
390
+ # Track file reads
391
+ file_read_count: dict[str, int] = {}
392
+ file_read_sequence: list[tuple[str, str]] = []
393
+
394
+ for call in history:
395
+ tool = call.get("tool", "")
396
+
397
+ if tool == "Read":
398
+ file_path = call.get("file_path", "")
399
+ if file_path:
400
+ file_read_count[file_path] = file_read_count.get(file_path, 0) + 1
401
+ file_read_sequence.append(("Read", file_path))
402
+
403
+ # Check for repeated reads of the same file
404
+ repeated_files = {
405
+ f: count for f, count in file_read_count.items() if count >= 2
406
+ }
407
+
408
+ if repeated_files:
409
+ # File was read multiple times
410
+ most_repeated = max(repeated_files.items(), key=lambda x: x[1])
411
+ example_seq = [tool for tool, _ in file_read_sequence if tool == "Read"][
412
+ -3:
413
+ ]
414
+
415
+ return DetectionResult(
416
+ pattern_type="anti-pattern",
417
+ name=pattern_name,
418
+ description=pattern_def["description"],
419
+ detected=True,
420
+ trigger_conditions=[f"{most_repeated[1]}x reads of: {most_repeated[0]}"]
421
+ + pattern_def["trigger_conditions"],
422
+ example_sequence=example_seq,
423
+ remediation=pattern_def["remediation"],
424
+ confidence=min(1.0, most_repeated[1] / 3),
425
+ )
426
+
427
+ return DetectionResult(
428
+ pattern_type="anti-pattern",
429
+ name=pattern_name,
430
+ description=pattern_def["description"],
431
+ detected=False,
432
+ )
433
+
434
+ def _get_delegation_suggestion(self, pattern_name: str) -> str:
435
+ """Get delegation suggestion for a detected anti-pattern."""
436
+ suggestions = {
437
+ "exploration_sequence": (
438
+ "spawn_gemini(prompt='Comprehensive search and analysis of codebase for...')"
439
+ ),
440
+ "edit_without_test": (
441
+ "Task(prompt='Make the following changes AND run tests to verify: ...')"
442
+ ),
443
+ "direct_git_commit": (
444
+ "spawn_copilot(prompt='Commit changes with message: ...')"
445
+ ),
446
+ "repeated_read_same_file": (
447
+ "spawn_gemini(prompt='Analyze the entire file and extract all relevant sections: ...')"
448
+ ),
449
+ }
450
+ return suggestions.get(
451
+ pattern_name,
452
+ "Delegate this operation to an appropriate subagent",
453
+ )
454
+
455
+ def get_pattern_statistics(
456
+ self, all_history: list[dict[str, Any]]
457
+ ) -> dict[str, Any]:
458
+ """
459
+ Analyze pattern statistics across entire history.
460
+
461
+ Args:
462
+ all_history: Complete tool usage history (not just window)
463
+
464
+ Returns:
465
+ Dictionary with pattern statistics
466
+ """
467
+ stats = {}
468
+
469
+ # Check each anti-pattern with sliding windows across history
470
+ for i in range(max(0, len(all_history) - 50), len(all_history)):
471
+ window = all_history[i : i + self.window_size]
472
+ if not window:
473
+ continue
474
+
475
+ detected = self.detect_all_patterns(window)
476
+ for pattern in detected:
477
+ if pattern.name not in stats:
478
+ stats[pattern.name] = {"count": 0, "sessions": set()}
479
+ stats[pattern.name]["count"] = stats[pattern.name]["count"] + 1 # type: ignore[operator]
480
+
481
+ return {
482
+ name: {"occurrence_count": data["count"]} for name, data in stats.items()
483
+ }
484
+
485
+
486
+ # Helper function for external use
487
+ def detect_patterns(
488
+ history: list[dict[str, Any]], window_size: int = 10
489
+ ) -> list[PatternRecord]:
490
+ """
491
+ Detect anti-patterns from tool usage history.
492
+
493
+ Convenience function that creates a detector and finds all patterns.
494
+
495
+ Args:
496
+ history: Tool usage history
497
+ window_size: Window size for pattern detection
498
+
499
+ Returns:
500
+ List of detected PatternRecord instances
501
+ """
502
+ detector = PatternDetector(window_size=window_size)
503
+ return detector.detect_all_patterns(history)