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
htmlgraph/config.py ADDED
@@ -0,0 +1,190 @@
1
+ """
2
+ HtmlGraph Configuration Management.
3
+
4
+ This module provides centralized configuration management using Pydantic Settings,
5
+ allowing configuration from environment variables, .env files, and CLI arguments.
6
+
7
+ IMPORTANT: Database path functions (get_database_path, get_analytics_cache_path)
8
+ are lightweight and have NO dependencies. They can be imported anywhere.
9
+ """
10
+
11
+ import os
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ # =============================================================================
16
+ # LIGHTWEIGHT DATABASE PATH FUNCTIONS (NO DEPENDENCIES)
17
+ # These MUST come before any heavy imports so spawners can use them
18
+ # =============================================================================
19
+
20
+ # Database filenames (SINGLE SOURCE OF TRUTH)
21
+ DATABASE_FILENAME = "htmlgraph.db" # Unified event database
22
+ ANALYTICS_CACHE_FILENAME = "index.sqlite" # Analytics cache (rebuildable)
23
+
24
+
25
+ def get_database_path(project_root: Path | str | None = None) -> Path:
26
+ """
27
+ Get the unified database path for event tracking.
28
+
29
+ This is the SINGLE source of truth for database location.
30
+ All hooks, agents, and spawners MUST use this function.
31
+
32
+ Args:
33
+ project_root: Optional project root path. If None, uses HTMLGRAPH_PROJECT_ROOT
34
+ env var or current working directory.
35
+
36
+ Returns:
37
+ Path to htmlgraph.db (the unified event database)
38
+ """
39
+ if project_root is None:
40
+ project_root = Path(os.environ.get("HTMLGRAPH_PROJECT_ROOT", os.getcwd()))
41
+ else:
42
+ project_root = Path(project_root)
43
+
44
+ return project_root / ".htmlgraph" / DATABASE_FILENAME
45
+
46
+
47
+ def get_analytics_cache_path(project_root: Path | str | None = None) -> Path:
48
+ """
49
+ Get the analytics cache database path.
50
+
51
+ This is for read-only analytics queries (rebuildable from events).
52
+ NOT for event tracking - use get_database_path() for that.
53
+
54
+ Args:
55
+ project_root: Optional project root path. If None, uses HTMLGRAPH_PROJECT_ROOT
56
+ env var or current working directory.
57
+
58
+ Returns:
59
+ Path to index.sqlite (analytics cache, gitignored)
60
+ """
61
+ if project_root is None:
62
+ project_root = Path(os.environ.get("HTMLGRAPH_PROJECT_ROOT", os.getcwd()))
63
+ else:
64
+ project_root = Path(project_root)
65
+
66
+ return project_root / ".htmlgraph" / ANALYTICS_CACHE_FILENAME
67
+
68
+
69
+ # =============================================================================
70
+ # PYDANTIC CONFIGURATION (Heavy imports below - spawners don't need this)
71
+ # =============================================================================
72
+
73
+ try:
74
+ from pydantic_settings import BaseSettings
75
+
76
+ _PYDANTIC_AVAILABLE = True
77
+ except ImportError:
78
+ _PYDANTIC_AVAILABLE = False
79
+ BaseSettings = object # type: ignore
80
+
81
+
82
+ if _PYDANTIC_AVAILABLE:
83
+
84
+ class HtmlGraphConfig(BaseSettings):
85
+ """Global HtmlGraph configuration using Pydantic Settings.
86
+
87
+ Configuration can be provided via:
88
+ 1. Environment variables (prefix: HTMLGRAPH_)
89
+ 2. .env file
90
+ 3. Direct instantiation with parameters
91
+ 4. CLI argument overrides
92
+ """
93
+
94
+ # Core paths
95
+ graph_dir: Path = Path.home() / ".htmlgraph"
96
+
97
+ # Database paths (SINGLE SOURCE OF TRUTH)
98
+ # All hooks, agents, and spawners MUST use these via get_database_path()
99
+ database_filename: str = "htmlgraph.db" # Unified event database
100
+ analytics_cache_filename: str = "index.sqlite" # Analytics cache (rebuildable)
101
+
102
+ # Feature tracking
103
+ features_dir: Path | None = None
104
+ sessions_dir: Path | None = None
105
+ spikes_dir: Path | None = None
106
+ tracks_dir: Path | None = None
107
+ archives_dir: Path | None = None
108
+
109
+ # CLI behavior
110
+ debug: bool = False
111
+ verbose: bool = False
112
+ auto_sync: bool = True
113
+ color_output: bool = True
114
+
115
+ # Session management
116
+ max_sessions: int = 100
117
+ session_retention_days: int = 30
118
+ auto_archive_sessions: bool = True
119
+
120
+ # Performance
121
+ max_query_results: int = 1000
122
+ cache_enabled: bool = True
123
+ cache_ttl_seconds: int = 3600
124
+
125
+ # Logging
126
+ log_level: str = "INFO"
127
+ log_file: Path | None = None
128
+
129
+ model_config = {
130
+ "env_prefix": "HTMLGRAPH_",
131
+ "env_file": ".env",
132
+ "case_sensitive": False,
133
+ }
134
+
135
+ def __init__(self, **data: Any) -> None:
136
+ """Initialize config and compute derived paths."""
137
+ super().__init__(**data)
138
+ # Compute derived paths if not explicitly set
139
+ if self.features_dir is None:
140
+ self.features_dir = self.graph_dir / "features"
141
+ if self.sessions_dir is None:
142
+ self.sessions_dir = self.graph_dir / "sessions"
143
+ if self.spikes_dir is None:
144
+ self.spikes_dir = self.graph_dir / "spikes"
145
+ if self.tracks_dir is None:
146
+ self.tracks_dir = self.graph_dir / "tracks"
147
+ if self.archives_dir is None:
148
+ self.archives_dir = self.graph_dir / "archives"
149
+
150
+ def ensure_directories(self) -> None:
151
+ """Create all configured directories if they don't exist."""
152
+ for directory in [
153
+ self.graph_dir,
154
+ self.features_dir,
155
+ self.sessions_dir,
156
+ self.spikes_dir,
157
+ self.tracks_dir,
158
+ self.archives_dir,
159
+ ]:
160
+ if directory:
161
+ directory.mkdir(parents=True, exist_ok=True)
162
+
163
+ def get_config_dict(self) -> dict[str, Any]:
164
+ """Get configuration as dictionary."""
165
+ return {
166
+ "graph_dir": str(self.graph_dir),
167
+ "features_dir": str(self.features_dir),
168
+ "sessions_dir": str(self.sessions_dir),
169
+ "spikes_dir": str(self.spikes_dir),
170
+ "tracks_dir": str(self.tracks_dir),
171
+ "archives_dir": str(self.archives_dir),
172
+ "debug": self.debug,
173
+ "verbose": self.verbose,
174
+ "auto_sync": self.auto_sync,
175
+ "color_output": self.color_output,
176
+ "max_sessions": self.max_sessions,
177
+ "session_retention_days": self.session_retention_days,
178
+ "auto_archive_sessions": self.auto_archive_sessions,
179
+ "max_query_results": self.max_query_results,
180
+ "cache_enabled": self.cache_enabled,
181
+ "cache_ttl_seconds": self.cache_ttl_seconds,
182
+ "log_level": self.log_level,
183
+ "log_file": str(self.log_file) if self.log_file else None,
184
+ }
185
+
186
+ # Global configuration instance
187
+ config: HtmlGraphConfig = HtmlGraphConfig()
188
+ else:
189
+ # Pydantic not available - config object won't work but database functions will
190
+ config = None # type: ignore
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Context Analytics for HtmlGraph
3
5
 
@@ -7,7 +9,6 @@ Provides hierarchical context usage tracking and analytics:
7
9
  Enables drill-down analysis of where context was consumed.
8
10
  """
9
11
 
10
- from __future__ import annotations
11
12
 
12
13
  from dataclasses import dataclass, field
13
14
  from typing import TYPE_CHECKING, Any
htmlgraph/converter.py CHANGED
@@ -12,7 +12,16 @@ import logging
12
12
  from pathlib import Path
13
13
  from typing import Any, cast
14
14
 
15
- from htmlgraph.models import ActivityEntry, Chore, Edge, Node, Session, Spike, Step
15
+ from htmlgraph.models import (
16
+ ActivityEntry,
17
+ Chore,
18
+ Edge,
19
+ Node,
20
+ Pattern,
21
+ Session,
22
+ Spike,
23
+ Step,
24
+ )
16
25
  from htmlgraph.parser import HtmlParser
17
26
 
18
27
  logger = logging.getLogger(__name__)
@@ -74,6 +83,7 @@ def html_to_node(filepath: Path | str) -> Node:
74
83
  model_classes: dict[str, type[Node]] = {
75
84
  "spike": Spike,
76
85
  "chore": Chore,
86
+ "pattern": Pattern,
77
87
  "node": Node,
78
88
  }
79
89
 
@@ -385,7 +395,8 @@ def html_to_session(filepath: Path | str) -> Session:
385
395
  parser = HtmlParser.from_file(filepath)
386
396
 
387
397
  # Get article element with session data
388
- article = parser.query_one("article[data-type='session']")
398
+ article_results = parser.query("article[data-type='session']")
399
+ article = article_results[0] if article_results else None
389
400
  if not article:
390
401
  raise ValueError(f"No session article found in: {filepath}")
391
402
 
@@ -455,7 +466,8 @@ def html_to_session(filepath: Path | str) -> Session:
455
466
  data["transcript_git_branch"] = transcript_branch
456
467
 
457
468
  # Parse title
458
- title_el = parser.query_one("h1")
469
+ title_el_results = parser.query("h1")
470
+ title_el = title_el_results[0] if title_el_results else None
459
471
  if title_el:
460
472
  data["title"] = title_el.to_text().strip()
461
473
 
@@ -472,24 +484,28 @@ def html_to_session(filepath: Path | str) -> Session:
472
484
  data["worked_on"] = worked_on
473
485
 
474
486
  # Parse continued_from edge
475
- continued_link = parser.query_one(
487
+ continued_link_results = parser.query(
476
488
  "nav[data-graph-edges] section[data-edge-type='continued-from'] a"
477
489
  )
490
+ continued_link = continued_link_results[0] if continued_link_results else None
478
491
  if continued_link:
479
492
  href = continued_link.attrs.get("href") or ""
480
493
  data["continued_from"] = href.replace(".html", "")
481
494
 
482
495
  # Parse handoff context
483
- handoff_section = parser.query_one("section[data-handoff]")
496
+ handoff_section_results = parser.query("section[data-handoff]")
497
+ handoff_section = handoff_section_results[0] if handoff_section_results else None
484
498
  if handoff_section:
485
- notes_el = parser.query_one("section[data-handoff] [data-handoff-notes]")
499
+ notes_el_results = parser.query("section[data-handoff] [data-handoff-notes]")
500
+ notes_el = notes_el_results[0] if notes_el_results else None
486
501
  if notes_el:
487
502
  notes_text = notes_el.to_text().strip()
488
503
  if notes_text.lower().startswith("notes:"):
489
504
  notes_text = notes_text.split(":", 1)[1].strip()
490
505
  data["handoff_notes"] = notes_text
491
506
 
492
- next_el = parser.query_one("section[data-handoff] [data-recommended-next]")
507
+ next_el_results = parser.query("section[data-handoff] [data-recommended-next]")
508
+ next_el = next_el_results[0] if next_el_results else None
493
509
  if next_el:
494
510
  next_text = next_el.to_text().strip()
495
511
  if next_text.lower().startswith("recommended next:"):
@@ -504,6 +520,17 @@ def html_to_session(filepath: Path | str) -> Session:
504
520
  if blockers:
505
521
  data["blockers"] = blockers
506
522
 
523
+ # Parse recommended context files
524
+ recommended_context = []
525
+ for li in parser.query(
526
+ "section[data-handoff] div[data-recommended-context] li"
527
+ ):
528
+ file_path = li.to_text().strip()
529
+ if file_path:
530
+ recommended_context.append(file_path)
531
+ if recommended_context:
532
+ data["recommended_context"] = recommended_context
533
+
507
534
  # Parse activity log
508
535
  activity_log = []
509
536
  for li in parser.query("section[data-activity-log] ol li"):
@@ -538,6 +565,88 @@ def html_to_session(filepath: Path | str) -> Session:
538
565
  # Activity log in HTML is reversed (newest first), so reverse back
539
566
  data["activity_log"] = list(reversed(activity_log))
540
567
 
568
+ # Parse detected patterns from table (if present)
569
+ detected_patterns = []
570
+ for tr in parser.query("section[data-detected-patterns] table tbody tr"):
571
+ # Extract pattern data from table row
572
+ pattern_type = tr.attrs.get("data-pattern-type", "neutral")
573
+
574
+ # Extract sequence from first <td class="sequence">
575
+ seq_tds = tr.query("td.sequence")
576
+ seq_td = seq_tds[0] if seq_tds else None
577
+ sequence_str = seq_td.to_text().strip() if seq_td else ""
578
+ sequence = [s.strip() for s in sequence_str.split("→")] if sequence_str else []
579
+
580
+ # Extract count from third <td>
581
+ tds = tr.query("td")
582
+ count_td = tds[2] if len(tds) > 2 else None
583
+ count_str = count_td.to_text().strip() if count_td else "0"
584
+ try:
585
+ count = int(count_str)
586
+ except (ValueError, TypeError):
587
+ count = 0
588
+
589
+ # Extract timestamps from fourth <td>
590
+ time_td = tds[3] if len(tds) > 3 else None
591
+ time_str = time_td.to_text().strip() if time_td else ""
592
+ times = time_str.split(" / ")
593
+ first_detected = times[0].strip() if len(times) > 0 else ""
594
+ last_detected = times[1].strip() if len(times) > 1 else ""
595
+
596
+ if sequence: # Only add if we have a valid sequence
597
+ detected_patterns.append(
598
+ {
599
+ "sequence": sequence,
600
+ "pattern_type": pattern_type,
601
+ "detection_count": count,
602
+ "first_detected": first_detected,
603
+ "last_detected": last_detected,
604
+ }
605
+ )
606
+
607
+ data["detected_patterns"] = detected_patterns
608
+
609
+ # Parse error log from error section (if present)
610
+ error_log = []
611
+ for details in parser.query("section[data-error-log] details"):
612
+ error_data = {
613
+ "error_type": details.attrs.get("data-error-type", "Unknown"),
614
+ "message": "",
615
+ "traceback": None,
616
+ }
617
+
618
+ ts = details.attrs.get("data-ts")
619
+ if ts:
620
+ error_data["timestamp"] = datetime.fromisoformat(ts.replace("Z", "+00:00"))
621
+
622
+ tool = details.attrs.get("data-tool")
623
+ if tool:
624
+ error_data["tool"] = tool
625
+
626
+ # Parse summary text (first line of details)
627
+ summary_el_results = details.query("summary")
628
+ summary_el = summary_el_results[0] if summary_el_results else None
629
+ if summary_el:
630
+ summary_text = summary_el.to_text().strip()
631
+ # Extract message from "ErrorType: message" format
632
+ if ": " in summary_text:
633
+ error_data["message"] = summary_text.split(": ", 1)[1]
634
+ else:
635
+ error_data["message"] = summary_text
636
+
637
+ # Parse traceback (if present)
638
+ traceback_el_results = details.query("pre.traceback")
639
+ traceback_el = traceback_el_results[0] if traceback_el_results else None
640
+ if traceback_el:
641
+ error_data["traceback"] = traceback_el.to_text().strip()
642
+
643
+ if error_data.get("message") or error_data.get("traceback"):
644
+ from htmlgraph.models import ErrorEntry
645
+
646
+ error_log.append(ErrorEntry(**error_data))
647
+
648
+ data["error_log"] = error_log
649
+
541
650
  return Session(**data)
542
651
 
543
652
 
@@ -0,0 +1,5 @@
1
+ """Cost analysis module for HtmlGraph event tracking and token cost calculation."""
2
+
3
+ from .analyzer import CostAnalyzer
4
+
5
+ __all__ = ["CostAnalyzer"]