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
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
@@ -0,0 +1,344 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ Context Analytics for HtmlGraph
5
+
6
+ Provides hierarchical context usage tracking and analytics:
7
+ Activity → Session → Feature → Track
8
+
9
+ Enables drill-down analysis of where context was consumed.
10
+ """
11
+
12
+
13
+ from dataclasses import dataclass, field
14
+ from typing import TYPE_CHECKING, Any
15
+
16
+ if TYPE_CHECKING:
17
+ from htmlgraph.sdk import SDK
18
+
19
+
20
+ @dataclass
21
+ class ContextUsage:
22
+ """Aggregated context usage at any level of the hierarchy."""
23
+
24
+ tokens_used: int = 0
25
+ peak_tokens: int = 0
26
+ cost_usd: float = 0.0
27
+ output_tokens: int = 0
28
+
29
+ # Breakdown by child entities
30
+ by_feature: dict[str, int] = field(default_factory=dict)
31
+ by_session: dict[str, int] = field(default_factory=dict)
32
+ by_tool: dict[str, int] = field(default_factory=dict)
33
+
34
+ # Metadata
35
+ entity_type: str = "" # "track", "feature", "session", "activity"
36
+ entity_id: str = ""
37
+ entity_title: str = ""
38
+
39
+ def add_child(self, child_id: str, child_usage: ContextUsage) -> None:
40
+ """Add a child's usage to this aggregate."""
41
+ self.tokens_used += child_usage.tokens_used
42
+ self.peak_tokens = max(self.peak_tokens, child_usage.peak_tokens)
43
+ self.cost_usd += child_usage.cost_usd
44
+ self.output_tokens += child_usage.output_tokens
45
+
46
+ # Track by child entity
47
+ if child_usage.entity_type == "feature":
48
+ self.by_feature[child_id] = child_usage.tokens_used
49
+ elif child_usage.entity_type == "session":
50
+ self.by_session[child_id] = child_usage.tokens_used
51
+
52
+ # Merge tool breakdown
53
+ for tool, count in child_usage.by_tool.items():
54
+ self.by_tool[tool] = self.by_tool.get(tool, 0) + count
55
+
56
+ def to_dict(self) -> dict:
57
+ """Convert to dictionary for serialization."""
58
+ return {
59
+ "entity_type": self.entity_type,
60
+ "entity_id": self.entity_id,
61
+ "entity_title": self.entity_title,
62
+ "tokens_used": self.tokens_used,
63
+ "peak_tokens": self.peak_tokens,
64
+ "cost_usd": self.cost_usd,
65
+ "output_tokens": self.output_tokens,
66
+ "by_feature": self.by_feature,
67
+ "by_session": self.by_session,
68
+ "by_tool": self.by_tool,
69
+ }
70
+
71
+
72
+ class ContextAnalytics:
73
+ """
74
+ Hierarchical context usage analytics.
75
+
76
+ Provides drill-down from Track → Feature → Session → Activity.
77
+
78
+ Example:
79
+ >>> sdk = SDK(agent="claude")
80
+ >>> ctx = ContextAnalytics(sdk)
81
+ >>>
82
+ >>> # Get track-level usage
83
+ >>> track_usage = ctx.get_track_usage("track-auth")
84
+ >>> print(f"Total: {track_usage.tokens_used:,} tokens")
85
+ >>>
86
+ >>> # Drill down to features
87
+ >>> for feat_id, tokens in track_usage.by_feature.items():
88
+ ... print(f" {feat_id}: {tokens:,}")
89
+ >>>
90
+ >>> # Get detailed feature breakdown
91
+ >>> feat_usage = ctx.get_feature_usage("feat-login")
92
+ >>> print(f"Peak: {feat_usage.peak_tokens:,}")
93
+ """
94
+
95
+ def __init__(self, sdk: SDK):
96
+ """Initialize with SDK reference."""
97
+ self._sdk = sdk
98
+
99
+ def get_session_usage(self, session_id: str) -> ContextUsage:
100
+ """
101
+ Get context usage for a specific session.
102
+
103
+ Args:
104
+ session_id: Session ID to analyze
105
+
106
+ Returns:
107
+ ContextUsage with session-level metrics
108
+ """
109
+ session = self._sdk.session_manager.get_session(session_id)
110
+ if not session:
111
+ return ContextUsage(entity_type="session", entity_id=session_id)
112
+
113
+ usage = ContextUsage(
114
+ entity_type="session",
115
+ entity_id=session.id,
116
+ entity_title=session.title,
117
+ tokens_used=0,
118
+ peak_tokens=session.peak_context_tokens,
119
+ cost_usd=session.total_cost_usd,
120
+ output_tokens=session.total_tokens_generated,
121
+ )
122
+
123
+ # Aggregate from context snapshots
124
+ for snapshot in session.context_snapshots:
125
+ usage.tokens_used = max(usage.tokens_used, snapshot.current_tokens)
126
+ if snapshot.feature_id:
127
+ prev = usage.by_feature.get(snapshot.feature_id, 0)
128
+ usage.by_feature[snapshot.feature_id] = max(
129
+ prev, snapshot.current_tokens
130
+ )
131
+
132
+ # Also use context_by_feature from session
133
+ for feat_id, tokens in session.context_by_feature.items():
134
+ usage.by_feature[feat_id] = max(usage.by_feature.get(feat_id, 0), tokens)
135
+
136
+ # Get tool breakdown from activity log
137
+ for activity in session.activity_log:
138
+ usage.by_tool[activity.tool] = usage.by_tool.get(activity.tool, 0) + 1
139
+
140
+ return usage
141
+
142
+ def get_feature_usage(self, feature_id: str) -> ContextUsage:
143
+ """
144
+ Get context usage for a specific feature.
145
+
146
+ Aggregates from all sessions that worked on this feature.
147
+
148
+ Args:
149
+ feature_id: Feature ID to analyze
150
+
151
+ Returns:
152
+ ContextUsage with feature-level metrics
153
+ """
154
+ feature = self._sdk.features.get(feature_id)
155
+ if not feature:
156
+ return ContextUsage(entity_type="feature", entity_id=feature_id)
157
+
158
+ usage = ContextUsage(
159
+ entity_type="feature",
160
+ entity_id=feature.id,
161
+ entity_title=feature.title,
162
+ tokens_used=feature.context_tokens_used,
163
+ peak_tokens=feature.context_peak_tokens,
164
+ cost_usd=feature.context_cost_usd,
165
+ )
166
+
167
+ # Get usage from each session that worked on this feature
168
+ for session_id in feature.context_sessions:
169
+ session_usage = self.get_session_usage(session_id)
170
+ usage.by_session[session_id] = session_usage.tokens_used
171
+
172
+ # Merge tool breakdown
173
+ for tool, count in session_usage.by_tool.items():
174
+ usage.by_tool[tool] = usage.by_tool.get(tool, 0) + count
175
+
176
+ return usage
177
+
178
+ def get_track_usage(self, track_id: str) -> ContextUsage:
179
+ """
180
+ Get context usage for a track (aggregate of all features).
181
+
182
+ Args:
183
+ track_id: Track ID to analyze
184
+
185
+ Returns:
186
+ ContextUsage with track-level metrics
187
+ """
188
+ # Get all features in this track
189
+ features = self._sdk.features.where(track_id=track_id)
190
+
191
+ usage = ContextUsage(
192
+ entity_type="track",
193
+ entity_id=track_id,
194
+ )
195
+
196
+ # Aggregate from each feature
197
+ for feature in features:
198
+ feat_usage = self.get_feature_usage(feature.id)
199
+ usage.add_child(feature.id, feat_usage)
200
+
201
+ return usage
202
+
203
+ def get_all_tracks_usage(self) -> list[ContextUsage]:
204
+ """
205
+ Get context usage for all tracks.
206
+
207
+ Returns:
208
+ List of ContextUsage objects, one per track
209
+ """
210
+ # Find all unique track IDs
211
+ track_ids: set[str] = set()
212
+ for feature in self._sdk.features.all():
213
+ if feature.track_id:
214
+ track_ids.add(feature.track_id)
215
+
216
+ return [self.get_track_usage(tid) for tid in sorted(track_ids)]
217
+
218
+ def get_total_usage(self) -> ContextUsage:
219
+ """
220
+ Get total context usage across all sessions.
221
+
222
+ Returns:
223
+ ContextUsage with project-wide metrics
224
+ """
225
+ usage = ContextUsage(
226
+ entity_type="project",
227
+ entity_id="total",
228
+ entity_title="All Sessions",
229
+ )
230
+
231
+ # Get all sessions
232
+ sessions = self._sdk.sessions.all()
233
+ for session in sessions:
234
+ session_usage = self.get_session_usage(session.id)
235
+ usage.add_child(session.id, session_usage)
236
+
237
+ return usage
238
+
239
+ def get_usage_by_work_type(self) -> dict[str, ContextUsage]:
240
+ """
241
+ Get context usage grouped by work type.
242
+
243
+ Returns:
244
+ Dictionary mapping work type to ContextUsage
245
+ """
246
+ by_work_type: dict[str, ContextUsage] = {}
247
+
248
+ for feature in self._sdk.features.all():
249
+ # Infer work type from feature
250
+ work_type = feature.properties.get("work_type", "feature")
251
+
252
+ if work_type not in by_work_type:
253
+ by_work_type[work_type] = ContextUsage(
254
+ entity_type="work_type",
255
+ entity_id=work_type,
256
+ entity_title=work_type.replace("-", " ").title(),
257
+ )
258
+
259
+ feat_usage = self.get_feature_usage(feature.id)
260
+ by_work_type[work_type].add_child(feature.id, feat_usage)
261
+
262
+ return by_work_type
263
+
264
+ def context_efficiency_report(self) -> dict[str, Any]:
265
+ """
266
+ Generate a context efficiency report.
267
+
268
+ Identifies:
269
+ - Features with highest context consumption
270
+ - Sessions with highest peak usage
271
+ - Cost breakdown by feature/track
272
+
273
+ Returns:
274
+ Report dictionary with efficiency metrics
275
+ """
276
+ total = self.get_total_usage()
277
+
278
+ # Get top features by context usage
279
+ feature_usages = []
280
+ for feature in self._sdk.features.all():
281
+ feat_usage = self.get_feature_usage(feature.id)
282
+ feature_usages.append((feature.id, feature.title, feat_usage))
283
+
284
+ # Sort by tokens used descending
285
+ feature_usages.sort(key=lambda x: x[2].tokens_used, reverse=True)
286
+
287
+ top_features = [
288
+ {
289
+ "id": fid,
290
+ "title": title,
291
+ "tokens": usage.tokens_used,
292
+ "cost": usage.cost_usd,
293
+ "sessions": len(usage.by_session),
294
+ }
295
+ for fid, title, usage in feature_usages[:10]
296
+ ]
297
+
298
+ # Cost per feature
299
+ total_cost = total.cost_usd
300
+ cost_efficiency = []
301
+ for fid, title, usage in feature_usages:
302
+ if usage.cost_usd > 0:
303
+ cost_efficiency.append(
304
+ {
305
+ "id": fid,
306
+ "title": title,
307
+ "cost": usage.cost_usd,
308
+ "percent_of_total": (usage.cost_usd / total_cost * 100)
309
+ if total_cost > 0
310
+ else 0,
311
+ }
312
+ )
313
+
314
+ return {
315
+ "total_tokens": total.tokens_used,
316
+ "total_cost": total.cost_usd,
317
+ "total_output_tokens": total.output_tokens,
318
+ "peak_tokens": total.peak_tokens,
319
+ "features_count": len(feature_usages),
320
+ "sessions_count": len(total.by_session),
321
+ "top_features_by_context": top_features,
322
+ "cost_by_feature": cost_efficiency[:10],
323
+ "by_tool": total.by_tool,
324
+ }
325
+
326
+ def drill_down(self, entity_type: str, entity_id: str) -> ContextUsage:
327
+ """
328
+ Drill down into a specific entity.
329
+
330
+ Args:
331
+ entity_type: "track", "feature", or "session"
332
+ entity_id: Entity ID
333
+
334
+ Returns:
335
+ ContextUsage for the entity
336
+ """
337
+ if entity_type == "track":
338
+ return self.get_track_usage(entity_id)
339
+ elif entity_type == "feature":
340
+ return self.get_feature_usage(entity_id)
341
+ elif entity_type == "session":
342
+ return self.get_session_usage(entity_id)
343
+ else:
344
+ return ContextUsage(entity_type=entity_type, entity_id=entity_id)